diff --git a/.eslintrc.js b/.eslintrc.js
index 800f9a4f..3c48baa8 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,14 +1,17 @@
 module.exports = {
   root: true,
-  parser: 'babel-eslint',
   parserOptions: {
+    parser: 'babel-eslint',
     sourceType: 'module'
   },
   // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
-  extends: 'standard',
+  extends: [
+    'standard',
+    'plugin:vue/recommended'
+  ],
   // required to lint *.vue files
   plugins: [
-    'html'
+    'vue'
   ],
   // add your custom rules here
   rules: {
@@ -17,6 +20,7 @@ module.exports = {
     // allow async-await
     'generator-star-spacing': 0,
     // allow debugger during development
-    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+    'vue/require-prop-types': 0
   }
 }
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6c83a123..67824ac3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,7 +1,7 @@
 # This file is a template, and might need editing before it works on your project.
 # Official framework image. Look for the different tagged releases at:
 # https://hub.docker.com/r/library/node/tags/
-image: node:7
+image: node:8
 
 stages:
   - lint
@@ -16,7 +16,12 @@ lint:
 
 test:
   stage: test
+  variables:
+    APT_CACHE_DIR: apt-cache
   script:
+    - mkdir -pv $APT_CACHE_DIR && apt-get -qq update
+    - apt install firefox-esr -y --no-install-recommends
+    - firefox --version
     - yarn
     - npm run unit
 
diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md
index 924c38da..bc0f7b9d 100644
--- a/BREAKING_CHANGES.md
+++ b/BREAKING_CHANGES.md
@@ -1,5 +1,8 @@
 # v1.0
 ## Removed features/radically changed behavior
+### formattingOptionsEnabled
+as of !833 `formattingOptionsEnabled` is no longer available and instead FE check for available post formatting options and enables formatting control if there's more than one option.
+
 ### minimalScopesMode
 As of !633, `scopeOptions` is no longer available and instead is changed for `minimalScopesMode` (default: `false`)
 
diff --git a/build/dev-server.js b/build/dev-server.js
index 9c3d4e00..48574214 100644
--- a/build/dev-server.js
+++ b/build/dev-server.js
@@ -31,8 +31,13 @@ var hotMiddleware = require('webpack-hot-middleware')(compiler)
 // force page reload when html-webpack-plugin template changes
 compiler.plugin('compilation', function (compilation) {
   compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
-    hotMiddleware.publish({ action: 'reload' })
-    cb()
+    // FIXME: This supposed to reload whole page when index.html is changed,
+    // however now it reloads entire page on every breath, i suppose the order
+    // of plugins changed or something. It's a minor thing and douesn't hurt
+    // disabling it, constant reloads hurt much more
+
+    // hotMiddleware.publish({ action: 'reload' })
+    // cb()
   })
 })
 
diff --git a/build/utils.js b/build/utils.js
index 5b90db14..b45ffc16 100644
--- a/build/utils.js
+++ b/build/utils.js
@@ -1,61 +1,62 @@
 var path = require('path')
 var config = require('../config')
-var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var sass = require('sass')
+var MiniCssExtractPlugin = require('mini-css-extract-plugin')
 
 exports.assetsPath = function (_path) {
   var assetsSubDirectory = process.env.NODE_ENV === 'production'
-    ? config.build.assetsSubDirectory
-    : config.dev.assetsSubDirectory
+      ? config.build.assetsSubDirectory
+      : config.dev.assetsSubDirectory
   return path.posix.join(assetsSubDirectory, _path)
 }
 
 exports.cssLoaders = function (options) {
   options = options || {}
-  // generate loader string to be used with extract text plugin
-  function generateLoaders (loaders) {
-    var sourceLoader = loaders.map(function (loader) {
-      var extraParamChar
-      if (/\?/.test(loader)) {
-        loader = loader.replace(/\?/, '-loader?')
-        extraParamChar = '&'
-      } else {
-        loader = loader + '-loader'
-        extraParamChar = '?'
-      }
-      return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
-    }).join('!')
 
+  function generateLoaders (loaders) {
     // Extract CSS when that option is specified
     // (which is the case during production build)
     if (options.extract) {
-      return ExtractTextPlugin.extract('vue-style-loader', sourceLoader)
+      return [MiniCssExtractPlugin.loader].concat(loaders)
     } else {
-      return ['vue-style-loader', sourceLoader].join('!')
+      return ['vue-style-loader'].concat(loaders)
     }
   }
 
   // http://vuejs.github.io/vue-loader/configurations/extract-css.html
-  return {
-    css: generateLoaders(['css']),
-    postcss: generateLoaders(['css']),
-    less: generateLoaders(['css', 'less']),
-    sass: generateLoaders(['css', 'sass?indentedSyntax']),
-    scss: generateLoaders(['css', 'sass']),
-    stylus: generateLoaders(['css', 'stylus']),
-    styl: generateLoaders(['css', 'stylus'])
-  }
+  return [
+    {
+      test: /\.(post)?css$/,
+      use: generateLoaders(['css-loader']),
+    },
+    {
+      test: /\.less$/,
+      use: generateLoaders(['css-loader', 'less-loader']),
+    },
+    {
+      test: /\.sass$/,
+      use: generateLoaders([
+        'css-loader',
+        {
+          loader: 'sass-loader',
+          options: {
+            indentedSyntax: true
+          }
+        }
+      ])
+    },
+    {
+      test: /\.scss$/,
+      use: generateLoaders(['css-loader', 'sass-loader'])
+    },
+    {
+      test: /\.styl(us)?$/,
+      use: generateLoaders(['css-loader', 'stylus-loader']),
+    },
+  ]
 }
 
 // Generate loaders for standalone style files (outside of .vue)
 exports.styleLoaders = function (options) {
-  var output = []
-  var loaders = exports.cssLoaders(options)
-  for (var extension in loaders) {
-    var loader = loaders[extension]
-    output.push({
-      test: new RegExp('\\.' + extension + '$'),
-      loader: loader
-    })
-  }
-  return output
+  return exports.cssLoaders(options)
 }
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
index e07bb7a2..f8968966 100644
--- a/build/webpack.base.conf.js
+++ b/build/webpack.base.conf.js
@@ -20,9 +20,16 @@ module.exports = {
     publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
     filename: '[name].js'
   },
+  optimization: {
+    splitChunks: {
+      chunks: 'all'
+    }
+  },
   resolve: {
-    extensions: ['', '.js', '.vue'],
-    fallback: [path.join(__dirname, '../node_modules')],
+    extensions: ['.js', '.vue'],
+    modules: [
+      path.join(__dirname, '../node_modules')
+    ],
     alias: {
       'vue$': 'vue/dist/vue.runtime.common',
       'src': path.resolve(__dirname, '../src'),
@@ -30,67 +37,53 @@ module.exports = {
       'components': path.resolve(__dirname, '../src/components')
     }
   },
-  resolveLoader: {
-    fallback: [path.join(__dirname, '../node_modules')]
-  },
   module: {
     noParse: /node_modules\/localforage\/dist\/localforage.js/,
-    preLoaders: [
+    rules: [
       {
-        test: /\.vue$/,
-        loader: 'eslint',
+        enforce: 'pre',
+        test: /\.(js|vue)$/,
         include: projectRoot,
-        exclude: /node_modules/
+        exclude: /node_modules/,
+        use: {
+          loader: 'eslint-loader',
+          options: {
+            formatter: require('eslint-friendly-formatter'),
+            sourceMap: config.build.productionSourceMap,
+            extract: true
+          }
+        }
       },
-      {
-        test: /\.js$/,
-        loader: 'eslint',
-        include: projectRoot,
-        exclude: /node_modules/
-      }
-    ],
-    loaders: [
       {
         test: /\.vue$/,
-        loader: 'vue'
+        use: 'vue-loader'
       },
       {
         test: /\.jsx?$/,
-        loader: 'babel',
         include: projectRoot,
-        exclude: /node_modules\/(?!tributejs)/
-      },
-      {
-        test: /\.json$/,
-        loader: 'json'
+        exclude: /node_modules\/(?!tributejs)/,
+        use: 'babel-loader'
       },
       {
         test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
-        loader: 'url',
-        query: {
-          limit: 10000,
-          name: utils.assetsPath('img/[name].[hash:7].[ext]')
+        use: {
+          loader: 'url-loader',
+          options: {
+            limit: 10000,
+            name: utils.assetsPath('img/[name].[hash:7].[ext]')
+          }
         }
       },
       {
         test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
-        loader: 'url',
-        query: {
-          limit: 10000,
-          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
+        use: {
+          loader: 'url-loader',
+          options: {
+            limit: 10000,
+            name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
+          }
         }
-      }
-    ]
-  },
-  eslint: {
-    formatter: require('eslint-friendly-formatter')
-  },
-  vue: {
-    loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }),
-    postcss: [
-      require('autoprefixer')({
-        browsers: ['last 2 versions']
-      })
+      },
     ]
   },
   plugins: [
diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js
index 9f34619c..159572ba 100644
--- a/build/webpack.dev.conf.js
+++ b/build/webpack.dev.conf.js
@@ -12,8 +12,9 @@ Object.keys(baseWebpackConfig.entry).forEach(function (name) {
 
 module.exports = merge(baseWebpackConfig, {
   module: {
-    loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
+    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
   },
+  mode: 'development',
   // eval-source-map is faster for development
   devtool: '#eval-source-map',
   plugins: [
@@ -23,9 +24,7 @@ module.exports = merge(baseWebpackConfig, {
       'DEV_OVERRIDES': JSON.stringify(config.dev.settings)
     }),
     // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
-    new webpack.optimize.OccurenceOrderPlugin(),
     new webpack.HotModuleReplacementPlugin(),
-    new webpack.NoErrorsPlugin(),
     // https://github.com/ampedandwired/html-webpack-plugin
     new HtmlWebpackPlugin({
       filename: 'index.html',
diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js
index 9699f221..ed11ebad 100644
--- a/build/webpack.prod.conf.js
+++ b/build/webpack.prod.conf.js
@@ -4,7 +4,7 @@ var utils = require('./utils')
 var webpack = require('webpack')
 var merge = require('webpack-merge')
 var baseWebpackConfig = require('./webpack.base.conf')
-var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var MiniCssExtractPlugin = require('mini-css-extract-plugin')
 var HtmlWebpackPlugin = require('html-webpack-plugin')
 var env = process.env.NODE_ENV === 'testing'
     ? require('../config/test.env')
@@ -13,23 +13,23 @@ var env = process.env.NODE_ENV === 'testing'
 let commitHash = require('child_process')
     .execSync('git rev-parse --short HEAD')
     .toString();
-console.log(commitHash)
 
 var webpackConfig = merge(baseWebpackConfig, {
+  mode: 'production',
   module: {
-    loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
+    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, extract: true })
   },
   devtool: config.build.productionSourceMap ? '#source-map' : false,
+  optimization: {
+    minimize: true,
+    splitChunks: {
+      chunks: 'all'
+    }
+  },
   output: {
     path: config.build.assetsRoot,
     filename: utils.assetsPath('js/[name].[chunkhash].js'),
-    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
-  },
-  vue: {
-    loaders: utils.cssLoaders({
-      sourceMap: config.build.productionSourceMap,
-      extract: true
-    })
+    chunkFilename: utils.assetsPath('js/[name].[chunkhash].js')
   },
   plugins: [
     // http://vuejs.github.io/vue-loader/workflow/production.html
@@ -38,14 +38,10 @@ var webpackConfig = merge(baseWebpackConfig, {
       'COMMIT_HASH': JSON.stringify(commitHash),
       'DEV_OVERRIDES': JSON.stringify(undefined)
     }),
-    new webpack.optimize.UglifyJsPlugin({
-      compress: {
-        warnings: false
-      }
-    }),
-    new webpack.optimize.OccurenceOrderPlugin(),
     // extract css into its own file
-    new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
+    new MiniCssExtractPlugin({
+      filename: utils.assetsPath('css/[name].[contenthash].css')
+    }),
     // generate dist index.html with correct asset hash for caching.
     // you can customize output by editing /index.html
     // see https://github.com/ampedandwired/html-webpack-plugin
@@ -67,25 +63,11 @@ var webpackConfig = merge(baseWebpackConfig, {
       chunksSortMode: 'dependency'
     }),
     // split vendor js into its own file
-    new webpack.optimize.CommonsChunkPlugin({
-      name: 'vendor',
-      minChunks: function (module, count) {
-        // any required modules inside node_modules are extracted to vendor
-        return (
-          module.resource &&
-          /\.js$/.test(module.resource) &&
-          module.resource.indexOf(
-            path.join(__dirname, '../node_modules')
-          ) === 0
-        )
-      }
-    }),
     // extract webpack runtime and module manifest to its own file in order to
     // prevent vendor hash from being updated whenever app bundle is updated
-    new webpack.optimize.CommonsChunkPlugin({
-      name: 'manifest',
-      chunks: ['vendor']
-    })
+    // new webpack.optimize.SplitChunksPlugin({
+    // name: ['app', 'vendor']
+    // }),
   ]
 })
 
diff --git a/config/index.js b/config/index.js
index 56fa5940..ccec4196 100644
--- a/config/index.js
+++ b/config/index.js
@@ -48,6 +48,11 @@ module.exports = {
         changeOrigin: true,
         cookieDomainRewrite: 'localhost',
         ws: true
+      },
+      '/oauth/revoke': {
+        target,
+        changeOrigin: true,
+        cookieDomainRewrite: 'localhost'
       }
     },
     // CSS Sourcemaps off by default because relative paths are "buggy"
diff --git a/index.html b/index.html
index d8defc2e..63161f3c 100644
--- a/index.html
+++ b/index.html
@@ -2,7 +2,7 @@
 <html lang="en">
   <head>
     <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
     <title>Pleroma</title>
     <!--server-generated-meta-->
     <link rel="icon" type="image/png" href="/favicon.png">
diff --git a/package.json b/package.json
index 03228133..28f3beba 100644
--- a/package.json
+++ b/package.json
@@ -11,9 +11,11 @@
     "unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
     "e2e": "node test/e2e/runner.js",
     "test": "npm run unit && npm run e2e",
-    "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
+    "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
+    "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",
@@ -21,18 +23,17 @@
     "diff": "^3.0.1",
     "karma-mocha-reporter": "^2.2.1",
     "localforage": "^1.5.0",
-    "node-sass": "^3.10.1",
     "object-path": "^0.11.3",
     "phoenix": "^1.3.0",
+    "portal-vue": "^2.1.4",
     "sanitize-html": "^1.13.0",
-    "sass-loader": "^4.0.2",
+    "v-click-outside": "^2.1.1",
+    "v-tooltip": "^2.0.2",
     "vue": "^2.5.13",
     "vue-chat-scroll": "^1.2.1",
-    "vue-compose": "^0.7.1",
     "vue-i18n": "^7.3.2",
     "vue-router": "^3.0.1",
     "vue-template-compiler": "^2.3.4",
-    "vue-timeago": "^3.1.2",
     "vuelidate": "^0.7.4",
     "vuex": "^3.0.1",
     "whatwg-fetch": "^2.0.3"
@@ -44,7 +45,7 @@
     "babel-core": "^6.0.0",
     "babel-eslint": "^7.0.0",
     "babel-helper-vue-jsx-merge-props": "^2.0.3",
-    "babel-loader": "^6.0.0",
+    "babel-loader": "^7.0.0",
     "babel-plugin-syntax-jsx": "^6.18.0",
     "babel-plugin-transform-runtime": "^6.0.0",
     "babel-plugin-transform-vue-jsx": "3",
@@ -57,52 +58,55 @@
     "chromedriver": "^2.21.2",
     "connect-history-api-fallback": "^1.1.0",
     "cross-spawn": "^4.0.2",
-    "css-loader": "^0.25.0",
-    "eslint": "^3.7.1",
-    "eslint-config-standard": "^6.1.0",
+    "css-loader": "^0.28.0",
+    "eslint": "^5.16.0",
+    "eslint-config-standard": "^12.0.0",
     "eslint-friendly-formatter": "^2.0.5",
-    "eslint-loader": "^1.5.0",
-    "eslint-plugin-html": "^1.5.5",
-    "eslint-plugin-promise": "^2.0.1",
-    "eslint-plugin-standard": "^2.0.1",
+    "eslint-loader": "^2.1.0",
+    "eslint-plugin-import": "^2.13.0",
+    "eslint-plugin-node": "^7.0.0",
+    "eslint-plugin-promise": "^4.0.0",
+    "eslint-plugin-standard": "^4.0.0",
+    "eslint-plugin-vue": "^5.2.2",
     "eventsource-polyfill": "^0.9.6",
     "express": "^4.13.3",
-    "extract-text-webpack-plugin": "^1.0.1",
-    "file-loader": "^0.9.0",
+    "file-loader": "^3.0.1",
     "function-bind": "^1.0.2",
-    "html-webpack-plugin": "^2.8.1",
+    "html-webpack-plugin": "^3.0.0",
     "http-proxy-middleware": "^0.17.2",
     "inject-loader": "^2.0.1",
     "iso-639-1": "^2.0.3",
     "isparta-loader": "^2.0.0",
     "json-loader": "^0.5.4",
-    "karma": "^1.3.0",
+    "karma": "^3.0.0",
     "karma-coverage": "^1.1.1",
+    "karma-firefox-launcher": "^1.1.0",
     "karma-mocha": "^1.2.0",
-    "karma-phantomjs-launcher": "^1.0.0",
-    "karma-sinon-chai": "^1.2.0",
+    "karma-sinon-chai": "^2.0.2",
     "karma-sourcemap-loader": "^0.3.7",
     "karma-spec-reporter": "0.0.26",
-    "karma-webpack": "^1.7.0",
+    "karma-webpack": "^4.0.0-rc.3",
     "lodash": "^4.16.4",
     "lolex": "^1.4.0",
+    "mini-css-extract-plugin": "^0.5.0",
     "mocha": "^3.1.0",
     "nightwatch": "^0.9.8",
     "opn": "^4.0.2",
     "ora": "^0.3.0",
-    "phantomjs-prebuilt": "^2.1.3",
     "raw-loader": "^0.5.1",
+    "sass": "^1.17.3",
+    "sass-loader": "git://github.com/webpack-contrib/sass-loader",
     "selenium-server": "2.53.1",
     "semver": "^5.3.0",
-    "serviceworker-webpack-plugin": "0.2.3",
+    "serviceworker-webpack-plugin": "^1.0.0",
     "shelljs": "^0.7.4",
-    "sinon": "^1.17.3",
+    "sinon": "^2.1.0",
     "sinon-chai": "^2.8.0",
-    "url-loader": "^0.5.7",
-    "vue-loader": "^11.1.0",
-    "vue-style-loader": "^2.0.0",
-    "webpack": "^1.13.2",
-    "webpack-dev-middleware": "^1.8.3",
+    "url-loader": "^1.1.2",
+    "vue-loader": "^14.0.0",
+    "vue-style-loader": "^4.0.0",
+    "webpack": "^4.0.0",
+    "webpack-dev-middleware": "^3.6.0",
     "webpack-hot-middleware": "^2.12.2",
     "webpack-merge": "^0.14.1"
   },
diff --git a/src/App.js b/src/App.js
index 46145b16..3624171e 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,7 +1,7 @@
 import UserPanel from './components/user_panel/user_panel.vue'
 import NavPanel from './components/nav_panel/nav_panel.vue'
 import Notifications from './components/notifications/notifications.vue'
-import UserFinder from './components/user_finder/user_finder.vue'
+import SearchBar from './components/search_bar/search_bar.vue'
 import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
 import FeaturesPanel from './components/features_panel/features_panel.vue'
 import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
@@ -10,6 +10,7 @@ import MediaModal from './components/media_modal/media_modal.vue'
 import SideDrawer from './components/side_drawer/side_drawer.vue'
 import MobilePostStatusModal from './components/mobile_post_status_modal/mobile_post_status_modal.vue'
 import MobileNav from './components/mobile_nav/mobile_nav.vue'
+import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
 import { windowWidth } from './services/window_utils/window_utils'
 
 export default {
@@ -18,7 +19,7 @@ export default {
     UserPanel,
     NavPanel,
     Notifications,
-    UserFinder,
+    SearchBar,
     InstanceSpecificPanel,
     FeaturesPanel,
     WhoToFollowPanel,
@@ -26,11 +27,12 @@ export default {
     MediaModal,
     SideDrawer,
     MobilePostStatusModal,
-    MobileNav
+    MobileNav,
+    UserReportingModal
   },
   data: () => ({
     mobileActivePanel: 'timeline',
-    finderHidden: true,
+    searchBarHidden: true,
     supportsMask: window.CSS && window.CSS.supports && (
       window.CSS.supports('mask-size', 'contain') ||
         window.CSS.supports('-webkit-mask-size', 'contain') ||
@@ -68,7 +70,7 @@ export default {
     logoBgStyle () {
       return Object.assign({
         'margin': `${this.$store.state.instance.logoMargin} 0`,
-        opacity: this.finderHidden ? 1 : 0
+        opacity: this.searchBarHidden ? 1 : 0
       }, this.enableMask ? {} : {
         'background-color': this.enableMask ? '' : 'transparent'
       })
@@ -99,8 +101,8 @@ export default {
       this.$router.replace('/main/public')
       this.$store.dispatch('logout')
     },
-    onFinderToggled (hidden) {
-      this.finderHidden = hidden
+    onSearchBarToggled (hidden) {
+      this.searchBarHidden = hidden
     },
     updateMobileState () {
       const mobileLayout = windowWidth() <= 800
diff --git a/src/App.scss b/src/App.scss
index 5fc0dd27..1299e05d 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -47,6 +47,8 @@ body {
   color: var(--text, $fallback--text);
   max-width: 100vw;
   overflow-x: hidden;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
 }
 
 a {
@@ -101,6 +103,14 @@ button {
     background-color: $fallback--bg;
     background-color: var(--bg, $fallback--bg)
   }
+
+  &.danger {
+    // TODO: add better color variable
+    color: $fallback--text;
+    color: var(--alertErrorPanelText, $fallback--text);
+    background-color: $fallback--alertError;
+    background-color: var(--alertError, $fallback--alertError);
+  }
 }
 
 label.select {
@@ -121,6 +131,7 @@ input, textarea, .select {
   font-family: sans-serif;
   font-family: var(--inputFont, sans-serif);
   font-size: 14px;
+  margin: 0;
   padding: 8px .5em;
   box-sizing: border-box;
   display: inline-block;
@@ -174,7 +185,44 @@ input, textarea, .select {
     flex: 1;
   }
 
-  &[type=radio],
+  &[type=radio] {
+    display: none;
+    &:checked + label::before {
+      box-shadow: 0px 0px 2px black inset, 0px 0px 0px 4px $fallback--fg inset;
+      box-shadow: var(--inputShadow), 0px 0px 0px 4px var(--fg, $fallback--fg) inset;
+      background-color: var(--link, $fallback--link);
+    }
+    &:disabled {
+      &,
+      & + label,
+      & + label::before {
+        opacity: .5;
+      }
+    }
+    + label::before {
+      flex-shrink: 0;
+      display: inline-block;
+      content: '';
+      transition: box-shadow 200ms;
+      width: 1.1em;
+      height: 1.1em;
+      border-radius: 100%; // Radio buttons should always be circle
+      box-shadow: 0px 0px 2px black inset;
+      box-shadow: var(--inputShadow);
+      margin-right: .5em;
+      background-color: $fallback--fg;
+      background-color: var(--input, $fallback--fg);
+      vertical-align: top;
+      text-align: center;
+      line-height: 1.1em;
+      font-size: 1.1em;
+      box-sizing: border-box;
+      color: transparent;
+      overflow: hidden;
+      box-sizing: border-box;
+    }
+  }
+
   &[type=checkbox] {
     display: none;
     &:checked + label::before {
@@ -189,6 +237,7 @@ input, textarea, .select {
       }
     }
     + label::before {
+      flex-shrink: 0;
       display: inline-block;
       content: '✔';
       transition: color 200ms;
@@ -220,11 +269,45 @@ option {
   background-color: var(--bg, $fallback--bg);
 }
 
+.hide-number-spinner {
+  -moz-appearance: textfield;
+  &[type=number]::-webkit-inner-spin-button,
+  &[type=number]::-webkit-outer-spin-button {
+    opacity: 0;
+    display: none;
+  }
+}
+
 i[class*=icon-] {
   color: $fallback--icon;
   color: var(--icon, $fallback--icon)
 }
 
+.btn-block {
+  display: block;
+  width: 100%;
+}
+
+.btn-group {
+  position: relative;
+  display: inline-flex;
+  vertical-align: middle;
+
+  button {
+    position: relative;
+    flex: 1 1 auto;
+
+    &:not(:last-child) {
+      border-top-right-radius: 0;
+      border-bottom-right-radius: 0;
+    }
+
+    &:not(:first-child) {
+      border-top-left-radius: 0;
+      border-bottom-left-radius: 0;
+    }
+  }
+}
 
 .container {
   display: flex;
@@ -371,6 +454,7 @@ main-router {
 
 .panel-heading {
   display: flex;
+  flex: none;
   border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
   border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
   background-size: cover;
@@ -616,21 +700,6 @@ nav {
   text-align: right;
 }
 
-.visibility-tray {
-  font-size: 1.2em;
-  padding: 3px;
-  cursor: pointer;
-
-  .selected {
-    color: $fallback--lightText;
-    color: var(--lightText, $fallback--lightText);
-  }
-
-  div {
-    padding-top: 5px;
-  }
-}
-
 .visibility-notice {
   padding: .5em;
   border: 1px solid $fallback--faint;
@@ -639,6 +708,19 @@ nav {
   border-radius: var(--inputRadius, $fallback--inputRadius);
 }
 
+.notice-dismissible {
+  padding-right: 4rem;
+  position: relative;
+
+  .dismiss {
+    position: absolute;
+    top: 0;
+    right: 0;
+    padding: .5em;
+    color: inherit;
+  }
+}
+
 @keyframes modal-background-fadein {
   from {
     background-color: rgba(0, 0, 0, 0);
@@ -718,6 +800,70 @@ nav {
   }
 }
 
+.setting-item {
+  border-bottom: 2px solid var(--fg, $fallback--fg);
+  margin: 1em 1em 1.4em;
+  padding-bottom: 1.4em;
+
+  > div {
+    margin-bottom: .5em;
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  &:last-child {
+    border-bottom: none;
+    padding-bottom: 0;
+    margin-bottom: 1em;
+  }
+
+  select {
+    min-width: 10em;
+  }
+
+
+  textarea {
+    width: 100%;
+    max-width: 100%;
+    height: 100px;
+  }
+
+  .unavailable,
+  .unavailable i {
+    color: var(--cRed, $fallback--cRed);
+    color: $fallback--cRed;
+  }
+
+  .btn {
+    min-height: 28px;
+    min-width: 10em;
+    padding: 0 2em;
+  }
+
+  .number-input {
+    max-width: 6em;
+  }
+}
+.select-multiple {
+  display: flex;
+  .option-list {
+    margin: 0;
+    padding-left: .5em;
+  }
+}
+.setting-list,
+.option-list{
+  list-style-type: none;
+  padding-left: 2em;
+  li {
+    margin-bottom: 0.5em;
+  }
+  .suboptions {
+    margin-top: 0.3em
+  }
+}
+
 .login-hint {
   text-align: center;
 
@@ -735,54 +881,3 @@ nav {
 .btn.btn-default {
   min-height: 28px;
 }
-
-.autocomplete {
-  &-panel {
-    position: relative;
-
-    &-body {
-      margin: 0 0.5em 0 0.5em;
-      border-radius: $fallback--tooltipRadius;
-      border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
-      position: absolute;
-      z-index: 1;
-      box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5);
-      // this doesn't match original but i don't care, making it uniform.
-      box-shadow: var(--popupShadow);
-      min-width: 75%;
-      background: $fallback--bg;
-      background: var(--bg, $fallback--bg);
-      color: $fallback--lightText;
-      color: var(--lightText, $fallback--lightText);
-    }
-  }
-
-  &-item {
-    cursor: pointer;
-    padding: 0.2em 0.4em 0.2em 0.4em;
-    border-bottom: 1px solid rgba(0, 0, 0, 0.4);
-    display: flex;
-
-    img {
-      width: 24px;
-      height: 24px;
-      object-fit: contain;
-    }
-
-    span {
-      line-height: 24px;
-      margin: 0 0.1em 0 0.2em;
-    }
-
-    small {
-      margin-left: .5em;
-      color: $fallback--faint;
-      color: var(--faint, $fallback--faint);
-    }
-
-    &.highlighted {
-      background-color: $fallback--fg;
-      background-color: var(--lightBg, $fallback--fg);
-    }
-  }
-}
\ No newline at end of file
diff --git a/src/App.vue b/src/App.vue
index 3b8623ad..be4d1f75 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,51 +1,113 @@
 <template>
-  <div id="app" v-bind:style="bgAppStyle">
-    <div class="app-bg-wrapper" v-bind:style="bgStyle"></div>
+  <div
+    id="app"
+    :style="bgAppStyle"
+  >
+    <div
+      class="app-bg-wrapper"
+      :style="bgStyle"
+    />
     <MobileNav v-if="isMobileLayout" />
-    <nav v-else class='nav-bar container' @click="scrollToTop()" id="nav">
-      <div class='logo' :style='logoBgStyle'>
-        <div class='mask' :style='logoMaskStyle'></div>
-        <img :src='logo' :style='logoStyle'>
+    <nav
+      v-else
+      id="nav"
+      class="nav-bar container"
+      @click="scrollToTop()"
+    >
+      <div
+        class="logo"
+        :style="logoBgStyle"
+      >
+        <div
+          class="mask"
+          :style="logoMaskStyle"
+        />
+        <img
+          :src="logo"
+          :style="logoStyle"
+        >
       </div>
-      <div class='inner-nav'>
-        <div class='item'>
-          <router-link class="site-name" :to="{ name: 'root' }" active-class="home">{{sitename}}</router-link>
+      <div class="inner-nav">
+        <div class="item">
+          <router-link
+            class="site-name"
+            :to="{ name: 'root' }"
+            active-class="home"
+          >
+            {{ sitename }}
+          </router-link>
         </div>
-        <div class='item right'>
-          <user-finder class="button-icon nav-icon mobile-hidden" @toggled="onFinderToggled"></user-finder>
-          <router-link class="mobile-hidden" :to="{ name: 'settings'}"><i class="button-icon icon-cog nav-icon" :title="$t('nav.preferences')"></i></router-link>
-          <a href="#" class="mobile-hidden" v-if="currentUser" @click.prevent="logout"><i class="button-icon icon-logout nav-icon" :title="$t('login.logout')"></i></a>
+        <div class="item right">
+          <search-bar
+            class="nav-icon mobile-hidden"
+            @toggled="onSearchBarToggled"
+          />
+          <router-link
+            class="mobile-hidden"
+            :to="{ name: 'settings'}"
+          >
+            <i
+              class="button-icon icon-cog nav-icon"
+              :title="$t('nav.preferences')"
+            />
+          </router-link>
+          <a
+            v-if="currentUser"
+            href="#"
+            class="mobile-hidden"
+            @click.prevent="logout"
+          ><i
+            class="button-icon icon-logout nav-icon"
+            :title="$t('login.logout')"
+          /></a>
         </div>
       </div>
     </nav>
-    <div v-if="" class="container" id="content">
-      <div class="sidebar-flexer mobile-hidden" v-if="!isMobileLayout">
+    <div
+      id="content"
+      class="container"
+    >
+      <div class="sidebar-flexer mobile-hidden">
         <div class="sidebar-bounds">
           <div class="sidebar-scroller">
             <div class="sidebar">
-              <user-panel></user-panel>
-              <nav-panel></nav-panel>
-              <instance-specific-panel v-if="showInstanceSpecificPanel"></instance-specific-panel>
-              <features-panel v-if="!currentUser && showFeaturesPanel"></features-panel>
-              <who-to-follow-panel v-if="currentUser && suggestionsEnabled"></who-to-follow-panel>
-              <notifications v-if="currentUser"></notifications>
+              <user-panel />
+              <div v-if="!isMobileLayout">
+                <nav-panel />
+                <instance-specific-panel v-if="showInstanceSpecificPanel" />
+                <features-panel v-if="!currentUser && showFeaturesPanel" />
+                <who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
+                <notifications v-if="currentUser" />
+              </div>
             </div>
           </div>
         </div>
       </div>
       <div class="main">
-        <div v-if="!currentUser" class="login-hint panel panel-default">
-          <router-link :to="{ name: 'login' }" class="panel-body">
+        <div
+          v-if="!currentUser"
+          class="login-hint panel panel-default"
+        >
+          <router-link
+            :to="{ name: 'login' }"
+            class="panel-body"
+          >
             {{ $t("login.hint") }}
           </router-link>
         </div>
         <transition name="fade">
-          <router-view></router-view>
+          <router-view />
         </transition>
       </div>
-      <media-modal></media-modal>
+      <media-modal />
     </div>
-    <chat-panel :floating="true" v-if="currentUser && chat" class="floating-chat mobile-hidden"></chat-panel>
+    <chat-panel
+      v-if="currentUser && chat"
+      :floating="true"
+      class="floating-chat mobile-hidden"
+    />
+    <UserReportingModal />
+    <portal-target name="modal" />
   </div>
 </template>
 
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index f2c1aa0f..3799359f 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -3,6 +3,8 @@ import VueRouter from 'vue-router'
 import routes from './routes'
 import App from '../App.vue'
 import { windowWidth } from '../services/window_utils/window_utils'
+import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
+import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
 
 const getStatusnetConfig = async ({ store }) => {
   try {
@@ -92,15 +94,14 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
       ? 0
       : config.logoMargin
   })
+  store.commit('authFlow/setInitialStrategy', config.loginMethod)
 
   copyInstanceOption('redirectRootNoLogin')
   copyInstanceOption('redirectRootLogin')
   copyInstanceOption('showInstanceSpecificPanel')
   copyInstanceOption('minimalScopesMode')
-  copyInstanceOption('formattingOptionsEnabled')
   copyInstanceOption('hideMutedPosts')
   copyInstanceOption('collapseMessageWithSubject')
-  copyInstanceOption('loginMethod')
   copyInstanceOption('scopeCopy')
   copyInstanceOption('subjectLineBehavior')
   copyInstanceOption('postContentType')
@@ -147,13 +148,48 @@ const getInstancePanel = async ({ store }) => {
   }
 }
 
+const getStickers = async ({ store }) => {
+  try {
+    const res = await window.fetch('/static/stickers.json')
+    if (res.ok) {
+      const values = await res.json()
+      const stickers = (await Promise.all(
+        Object.entries(values).map(async ([name, path]) => {
+          const resPack = await window.fetch(path + 'pack.json')
+          var meta = {}
+          if (resPack.ok) {
+            meta = await resPack.json()
+          }
+          return {
+            pack: name,
+            path,
+            meta
+          }
+        })
+      )).sort((a, b) => {
+        return a.meta.title.localeCompare(b.meta.title)
+      })
+      store.dispatch('setInstanceOption', { name: 'stickers', value: stickers })
+    } else {
+      throw (res)
+    }
+  } catch (e) {
+    console.warn("Can't load stickers")
+    console.warn(e)
+  }
+}
+
 const getStaticEmoji = async ({ store }) => {
   try {
     const res = await window.fetch('/static/emoji.json')
     if (res.ok) {
       const values = await res.json()
       const emoji = Object.keys(values).map((key) => {
-        return { shortcode: key, image_url: false, 'utf': values[key] }
+        return {
+          displayText: key,
+          imageUrl: false,
+          replacement: values[key]
+        }
       })
       store.dispatch('setInstanceOption', { name: 'emoji', value: emoji })
     } else {
@@ -171,9 +207,15 @@ const getCustomEmoji = async ({ store }) => {
   try {
     const res = await window.fetch('/api/pleroma/emoji.json')
     if (res.ok) {
-      const values = await res.json()
+      const result = await res.json()
+      const values = Array.isArray(result) ? Object.assign({}, ...result) : result
       const emoji = Object.keys(values).map((key) => {
-        return { shortcode: key, image_url: values[key] }
+        const imageUrl = values[key].image_url
+        return {
+          displayText: key,
+          imageUrl: imageUrl ? store.state.instance.server + imageUrl : values[key],
+          replacement: `:${key}: `
+        }
       })
       store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji })
       store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true })
@@ -187,17 +229,29 @@ const getCustomEmoji = async ({ store }) => {
   }
 }
 
+const getAppSecret = async ({ store }) => {
+  const { state, commit } = store
+  const { oauth, instance } = state
+  return getOrCreateApp({ ...oauth, instance: instance.server, commit })
+    .then((app) => getClientToken({ ...app, instance: instance.server }))
+    .then((token) => {
+      commit('setAppToken', token.access_token)
+      commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
+    })
+}
+
 const getNodeInfo = async ({ store }) => {
   try {
     const res = await window.fetch('/nodeinfo/2.0.json')
     if (res.ok) {
       const data = await res.json()
       const metadata = data.metadata
-
       const features = metadata.features
       store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
       store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') })
       store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
+      store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
+      store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
 
       store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
       store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
@@ -211,6 +265,7 @@ const getNodeInfo = async ({ store }) => {
 
       const frontendVersion = window.___pleromafe_commit_hash
       store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
+      store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') })
     } else {
       throw (res)
     }
@@ -226,14 +281,14 @@ const setConfig = async ({ store }) => {
   const apiConfig = configInfos[0]
   const staticConfig = configInfos[1]
 
-  await setSettings({ store, apiConfig, staticConfig })
+  await setSettings({ store, apiConfig, staticConfig }).then(getAppSecret({ store }))
 }
 
 const checkOAuthToken = async ({ store }) => {
   return new Promise(async (resolve, reject) => {
-    if (store.state.oauth.token) {
+    if (store.getters.getUserToken()) {
       try {
-        await store.dispatch('loginUser', store.state.oauth.token)
+        await store.dispatch('loginUser', store.getters.getUserToken())
       } catch (e) {
         console.log(e)
       }
@@ -262,6 +317,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
     setConfig({ store }),
     getTOS({ store }),
     getInstancePanel({ store }),
+    getStickers({ store }),
     getStaticEmoji({ store }),
     getCustomEmoji({ store }),
     getNodeInfo({ store })
diff --git a/src/boot/routes.js b/src/boot/routes.js
index 7e54a98b..7dc4b2a5 100644
--- a/src/boot/routes.js
+++ b/src/boot/routes.js
@@ -3,50 +3,58 @@ import PublicAndExternalTimeline from 'components/public_and_external_timeline/p
 import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
 import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
 import ConversationPage from 'components/conversation-page/conversation-page.vue'
-import Mentions from 'components/mentions/mentions.vue'
+import Interactions from 'components/interactions/interactions.vue'
 import DMs from 'components/dm_timeline/dm_timeline.vue'
 import UserProfile from 'components/user_profile/user_profile.vue'
+import Search from 'components/search/search.vue'
 import Settings from 'components/settings/settings.vue'
 import Registration from 'components/registration/registration.vue'
 import UserSettings from 'components/user_settings/user_settings.vue'
 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'
 
 export default (store) => {
+  const validateAuthenticatedRoute = (to, from, next) => {
+    if (store.state.users.currentUser) {
+      next()
+    } else {
+      next(store.state.instance.redirectRootNoLogin || '/main/all')
+    }
+  }
+
   return [
     { name: 'root',
       path: '/',
       redirect: _to => {
         return (store.state.users.currentUser
-                ? store.state.instance.redirectRootLogin
-                : store.state.instance.redirectRootNoLogin) || '/main/all'
+          ? store.state.instance.redirectRootLogin
+          : store.state.instance.redirectRootNoLogin) || '/main/all'
       }
     },
     { name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline },
     { name: 'public-timeline', path: '/main/public', component: PublicTimeline },
-    { name: 'friends', path: '/main/friends', component: FriendsTimeline },
+    { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
     { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
     { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
     { name: 'external-user-profile', path: '/users/:id', component: UserProfile },
-    { name: 'mentions', path: '/users/:username/mentions', component: Mentions },
-    { name: 'dms', path: '/users/:username/dms', component: DMs },
+    { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
+    { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
     { name: 'settings', path: '/settings', component: Settings },
     { name: 'registration', path: '/registration', component: Registration },
     { name: 'registration-token', path: '/registration/:token', component: Registration },
-    { 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: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
+    { name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute },
+    { name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
+    { 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 }) },
-    { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow },
+    { name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
+    { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
     { name: 'about', path: '/about', component: About },
     { name: 'user-profile', path: '/(users/)?:name', component: UserProfile }
   ]
diff --git a/src/components/about/about.vue b/src/components/about/about.vue
index 13dec87c..62ae16ea 100644
--- a/src/components/about/about.vue
+++ b/src/components/about/about.vue
@@ -1,8 +1,8 @@
 <template>
   <div class="sidebar">
-    <instance-specific-panel></instance-specific-panel>
-    <features-panel v-if="showFeaturesPanel"></features-panel>
-    <terms-of-service-panel></terms-of-service-panel>
+    <instance-specific-panel />
+    <features-panel v-if="showFeaturesPanel" />
+    <terms-of-service-panel />
   </div>
 </template>
 
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index 3b7f08dc..e93921fe 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -51,7 +51,7 @@ const Attachment = {
     }
   },
   methods: {
-    linkClicked ({target}) {
+    linkClicked ({ target }) {
       if (target.tagName === 'A') {
         window.open(target.href, '_blank')
       }
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index c58bebd3..ec326c45 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -1,54 +1,106 @@
 <template>
-  <div v-if="usePlaceHolder" @click="openModal">
-    <a class="placeholder"
+  <div
+    v-if="usePlaceHolder"
+    @click="openModal"
+  >
+    <a
       v-if="type !== 'html'"
-      target="_blank" :href="attachment.url"
+      class="placeholder"
+      target="_blank"
+      :href="attachment.url"
     >
-      [{{nsfw ? "NSFW/" : ""}}{{type.toUpperCase()}}]
+      [{{ nsfw ? "NSFW/" : "" }}{{ type.toUpperCase() }}]
     </a>
   </div>
   <div
-    v-else class="attachment"
-    :class="{[type]: true, loading, 'fullwidth': fullwidth, 'nsfw-placeholder': hidden}"
+    v-else
     v-show="!isEmpty"
+    class="attachment"
+    :class="{[type]: true, loading, 'fullwidth': fullwidth, 'nsfw-placeholder': hidden}"
   >
-    <a class="image-attachment" v-if="hidden" :href="attachment.url" @click.prevent="toggleHidden">
-      <img class="nsfw" :key="nsfwImage" :src="nsfwImage" :class="{'small': isSmall}"/>
-      <i v-if="type === 'video'" class="play-icon icon-play-circled"></i>
+    <a
+      v-if="hidden"
+      class="image-attachment"
+      :href="attachment.url"
+      @click.prevent="toggleHidden"
+    >
+      <img
+        :key="nsfwImage"
+        class="nsfw"
+        :src="nsfwImage"
+        :class="{'small': isSmall}"
+      >
+      <i
+        v-if="type === 'video'"
+        class="play-icon icon-play-circled"
+      />
     </a>
-    <div class="hider" v-if="nsfw && hideNsfwLocal && !hidden">
-      <a href="#" @click.prevent="toggleHidden">Hide</a>
+    <div
+      v-if="nsfw && hideNsfwLocal && !hidden"
+      class="hider"
+    >
+      <a
+        href="#"
+        @click.prevent="toggleHidden"
+      >Hide</a>
     </div>
 
-    <a v-if="type === 'image' && (!hidden || preloadImage)"
-      @click="openModal"
+    <a
+      v-if="type === 'image' && (!hidden || preloadImage)"
       class="image-attachment"
       :class="{'hidden': hidden && preloadImage }"
-      :href="attachment.url" target="_blank"
+      :href="attachment.url"
+      target="_blank"
       :title="attachment.description"
+      @click="openModal"
     >
-      <StillImage :referrerpolicy="referrerpolicy" :mimetype="attachment.mimetype" :src="attachment.large_thumb_url || attachment.url"/>
+      <StillImage
+        :referrerpolicy="referrerpolicy"
+        :mimetype="attachment.mimetype"
+        :src="attachment.large_thumb_url || attachment.url"
+      />
     </a>
 
-    <a class="video-container"
-      @click="openModal"
+    <a
       v-if="type === 'video' && !hidden"
+      class="video-container"
       :class="{'small': isSmall}"
       :href="allowPlay ? undefined : attachment.url"
+      @click="openModal"
     >
-      <VideoAttachment class="video" :attachment="attachment" :controls="allowPlay" />
-      <i v-if="!allowPlay" class="play-icon icon-play-circled"></i>
+      <VideoAttachment
+        class="video"
+        :attachment="attachment"
+        :controls="allowPlay"
+      />
+      <i
+        v-if="!allowPlay"
+        class="play-icon icon-play-circled"
+      />
     </a>
 
-    <audio v-if="type === 'audio'" :src="attachment.url" controls></audio>
+    <audio
+      v-if="type === 'audio'"
+      :src="attachment.url"
+      controls
+    />
 
-    <div @click.prevent="linkClicked" v-if="type === 'html' && attachment.oembed" class="oembed">
-      <div v-if="attachment.thumb_url" class="image">
-        <img :src="attachment.thumb_url"/>
+    <div
+      v-if="type === 'html' && attachment.oembed"
+      class="oembed"
+      @click.prevent="linkClicked"
+    >
+      <div
+        v-if="attachment.thumb_url"
+        class="image"
+      >
+        <img :src="attachment.thumb_url">
       </div>
       <div class="text">
-        <h1><a :href="attachment.url">{{attachment.oembed.title}}</a></h1>
-        <div v-html="attachment.oembed.oembedHTML"></div>
+        <!-- eslint-disable vue/no-v-html -->
+        <h1><a :href="attachment.url">{{ attachment.oembed.title }}</a></h1>
+        <div v-html="attachment.oembed.oembedHTML" />
+        <!-- eslint-enable vue/no-v-html -->
       </div>
     </div>
   </div>
@@ -68,6 +120,7 @@
     max-height: 200px;
     max-width: 100%;
     display: flex;
+    align-items: center;
     video {
       max-width: 100%;
     }
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/autosuggest/autosuggest.js b/src/components/autosuggest/autosuggest.js
new file mode 100644
index 00000000..f58f17bb
--- /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..1f86e996
--- /dev/null
+++ b/src/components/autosuggest/autosuggest.vue
@@ -0,0 +1,59 @@
+<template>
+  <div
+    v-click-outside="onClickOutside"
+    class="autosuggest"
+  >
+    <input
+      v-model="term"
+      :placeholder="placeholder"
+      class="autosuggest-input"
+      @click="onInputClick"
+    >
+    <div
+      v-if="resultsVisible && filtered.length > 0"
+      class="autosuggest-results"
+    >
+      <slot
+        v-for="item in filtered"
+        :item="item"
+      />
+    </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/avatar_list/avatar_list.js b/src/components/avatar_list/avatar_list.js
new file mode 100644
index 00000000..9b6301b2
--- /dev/null
+++ b/src/components/avatar_list/avatar_list.js
@@ -0,0 +1,21 @@
+import UserAvatar from '../user_avatar/user_avatar.vue'
+import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
+
+const AvatarList = {
+  props: ['users'],
+  computed: {
+    slicedUsers () {
+      return this.users ? this.users.slice(0, 15) : []
+    }
+  },
+  components: {
+    UserAvatar
+  },
+  methods: {
+    userProfileLink (user) {
+      return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
+    }
+  }
+}
+
+export default AvatarList
diff --git a/src/components/avatar_list/avatar_list.vue b/src/components/avatar_list/avatar_list.vue
new file mode 100644
index 00000000..e1b6e971
--- /dev/null
+++ b/src/components/avatar_list/avatar_list.vue
@@ -0,0 +1,46 @@
+<template>
+  <div class="avatars">
+    <router-link
+      v-for="user in slicedUsers"
+      :key="user.id"
+      :to="userProfileLink(user)"
+      class="avatars-item"
+    >
+      <UserAvatar
+        :user="user"
+        class="avatar-small"
+      />
+    </router-link>
+  </div>
+</template>
+
+<script src="./avatar_list.js" ></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.avatars {
+  display: flex;
+  margin: 0;
+  padding: 0;
+
+  // For hiding overflowing elements
+  flex-wrap: wrap;
+  height: 24px;
+
+  .avatars-item {
+    margin: 0 0 5px 5px;
+
+    &:first-child {
+      padding-left: 5px;
+    }
+
+    .avatar-small {
+      border-radius: $fallback--avatarAltRadius;
+      border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+      height: 24px;
+      width: 24px;
+    }
+  }
+}
+</style>
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
index 8afe8b44..568e9359 100644
--- a/src/components/basic_user_card/basic_user_card.vue
+++ b/src/components/basic_user_card/basic_user_card.vue
@@ -1,22 +1,51 @@
 <template>
   <div class="basic-user-card">
     <router-link :to="userProfileLink(user)">
-      <UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
+      <UserAvatar
+        class="avatar"
+        :user="user"
+        @click.prevent.native="toggleUserExpanded"
+      />
     </router-link>
-    <div class="basic-user-card-expanded-content" v-if="userExpanded">
-      <UserCard :user="user" :rounded="true" :bordered="true"/>
+    <div
+      v-if="userExpanded"
+      class="basic-user-card-expanded-content"
+    >
+      <UserCard
+        :user="user"
+        :rounded="true"
+        :bordered="true"
+      />
     </div>
-    <div class="basic-user-card-collapsed-content" v-else>
-      <div :title="user.name" class="basic-user-card-user-name">
-        <span v-if="user.name_html" class="basic-user-card-user-name-value" v-html="user.name_html"></span>
-        <span v-else class="basic-user-card-user-name-value">{{ user.name }}</span>
+    <div
+      v-else
+      class="basic-user-card-collapsed-content"
+    >
+      <div
+        :title="user.name"
+        class="basic-user-card-user-name"
+      >
+        <!-- eslint-disable vue/no-v-html -->
+        <span
+          v-if="user.name_html"
+          class="basic-user-card-user-name-value"
+          v-html="user.name_html"
+        />
+        <!-- eslint-enable vue/no-v-html -->
+        <span
+          v-else
+          class="basic-user-card-user-name-value"
+        >{{ user.name }}</span>
       </div>
       <div>
-        <router-link class="basic-user-card-screen-name" :to="userProfileLink(user)">
-          @{{user.screen_name}}
+        <router-link
+          class="basic-user-card-screen-name"
+          :to="userProfileLink(user)"
+        >
+          @{{ user.screen_name }}
         </router-link>
       </div>
-      <slot></slot>
+      <slot />
     </div>
   </div>
 </template>
@@ -24,19 +53,11 @@
 <script src="./basic_user_card.js"></script>
 
 <style lang="scss">
-@import '../../_variables.scss';
-
 .basic-user-card {
   display: flex;
   flex: 1 0;
   margin: 0;
-  padding-top: 0.6em;
-  padding-right: 1em;
-  padding-bottom: 0.6em;
-  padding-left: 1em;
-  border-bottom: 1px solid;
-  border-bottom-color: $fallback--border;
-  border-bottom-color: var(--border, $fallback--border);
+  padding: 0.6em 1em;
 
   &-collapsed-content {
     margin-left: 0.7em;
@@ -52,14 +73,15 @@
       width: 16px;
       vertical-align: middle;
     }
+  }
 
-    &-value {
-      display: inline-block;
-      max-width: 100%;
-      overflow: hidden;
-      white-space: nowrap;
-      text-overflow: ellipsis;
-    }
+  &-user-name-value,
+  &-screen-name {
+    display: inline-block;
+    max-width: 100%;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
   }
 
   &-expanded-content {
diff --git a/src/components/block_card/block_card.vue b/src/components/block_card/block_card.vue
index 8eb56e25..5b00b738 100644
--- a/src/components/block_card/block_card.vue
+++ b/src/components/block_card/block_card.vue
@@ -1,7 +1,12 @@
 <template>
   <basic-user-card :user="user">
     <div class="block-card-content-container">
-      <button class="btn btn-default" @click="unblockUser" :disabled="progress" v-if="blocked">
+      <button
+        v-if="blocked"
+        class="btn btn-default"
+        :disabled="progress"
+        @click="unblockUser"
+      >
         <template v-if="progress">
           {{ $t('user_card.unblock_progress') }}
         </template>
@@ -9,7 +14,12 @@
           {{ $t('user_card.unblock') }}
         </template>
       </button>
-      <button class="btn btn-default" @click="blockUser" :disabled="progress" v-else>
+      <button
+        v-else
+        class="btn btn-default"
+        :disabled="progress"
+        @click="blockUser"
+      >
         <template v-if="progress">
           {{ $t('user_card.block_progress') }}
         </template>
diff --git a/src/components/chat_panel/chat_panel.js b/src/components/chat_panel/chat_panel.js
index bbc9b49f..f2e3adf0 100644
--- a/src/components/chat_panel/chat_panel.js
+++ b/src/components/chat_panel/chat_panel.js
@@ -16,7 +16,7 @@ const chatPanel = {
   },
   methods: {
     submit (message) {
-      this.$store.state.chat.channel.push('new_msg', {text: message}, 10000)
+      this.$store.state.chat.channel.push('new_msg', { text: message }, 10000)
       this.currentMessage = ''
     },
     togglePanel () {
diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/chat_panel/chat_panel.vue
index b37469ac..3677722f 100644
--- a/src/components/chat_panel/chat_panel.vue
+++ b/src/components/chat_panel/chat_panel.vue
@@ -1,41 +1,70 @@
 <template>
-  <div class="chat-panel" v-if="!this.collapsed || !this.floating">
+  <div
+    v-if="!collapsed || !floating"
+    class="chat-panel"
+  >
     <div class="panel panel-default">
-      <div class="panel-heading timeline-heading" :class="{ 'chat-heading': floating }" @click.stop.prevent="togglePanel">
+      <div
+        class="panel-heading timeline-heading"
+        :class="{ 'chat-heading': floating }"
+        @click.stop.prevent="togglePanel"
+      >
         <div class="title">
-          <span>{{$t('chat.title')}}</span>
-          <i class="icon-cancel" v-if="floating"></i>
+          <span>{{ $t('chat.title') }}</span>
+          <i
+            v-if="floating"
+            class="icon-cancel"
+          />
         </div>
       </div>
-      <div class="chat-window" v-chat-scroll>
-        <div class="chat-message" v-for="message in messages" :key="message.id">
+      <div
+        v-chat-scroll
+        class="chat-window"
+      >
+        <div
+          v-for="message in messages"
+          :key="message.id"
+          class="chat-message"
+        >
           <span class="chat-avatar">
-            <img :src="message.author.avatar" />
+            <img :src="message.author.avatar">
           </span>
           <div class="chat-content">
             <router-link
               class="chat-name"
-              :to="userProfileLink(message.author)">
-                {{message.author.username}}
+              :to="userProfileLink(message.author)"
+            >
+              {{ message.author.username }}
             </router-link>
             <br>
             <span class="chat-text">
-              {{message.text}}
+              {{ message.text }}
             </span>
           </div>
         </div>
       </div>
       <div class="chat-input">
-        <textarea @keyup.enter="submit(currentMessage)" v-model="currentMessage" class="chat-input-textarea" rows="1"></textarea>
+        <textarea
+          v-model="currentMessage"
+          class="chat-input-textarea"
+          rows="1"
+          @keyup.enter="submit(currentMessage)"
+        />
       </div>
     </div>
   </div>
-  <div v-else class="chat-panel">
+  <div
+    v-else
+    class="chat-panel"
+  >
     <div class="panel panel-default">
-      <div class="panel-heading stub timeline-heading chat-heading" @click.stop.prevent="togglePanel">
+      <div
+        class="panel-heading stub timeline-heading chat-heading"
+        @click.stop.prevent="togglePanel"
+      >
         <div class="title">
-          <i class="icon-comment-empty"></i>
-          {{$t('chat.title')}}
+          <i class="icon-comment-empty" />
+          {{ $t('chat.title') }}
         </div>
       </div>
     </div>
diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue
new file mode 100644
index 00000000..2b822ec3
--- /dev/null
+++ b/src/components/checkbox/checkbox.vue
@@ -0,0 +1,80 @@
+<template>
+  <label class="checkbox">
+    <input
+      type="checkbox"
+      :checked="checked"
+      :indeterminate.prop="indeterminate"
+      @change="$emit('change', $event.target.checked)"
+    >
+    <i class="checkbox-indicator" />
+    <span v-if="!!$slots.default"><slot /></span>
+  </label>
+</template>
+
+<script>
+export default {
+  model: {
+    prop: 'checked',
+    event: 'change'
+  },
+  props: ['checked', 'indeterminate']
+}
+</script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.checkbox {
+  position: relative;
+  display: inline-block;
+  padding-left: 1.2em;
+  min-height: 1.2em;
+
+  &-indicator::before {
+    position: absolute;
+    left: 0;
+    top: 0;
+    display: block;
+    content: '✔';
+    transition: color 200ms;
+    width: 1.1em;
+    height: 1.1em;
+    border-radius: $fallback--checkboxRadius;
+    border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
+    box-shadow: 0px 0px 2px black inset;
+    box-shadow: var(--inputShadow);
+    background-color: $fallback--fg;
+    background-color: var(--input, $fallback--fg);
+    vertical-align: top;
+    text-align: center;
+    line-height: 1.1em;
+    font-size: 1.1em;
+    color: transparent;
+    overflow: hidden;
+    box-sizing: border-box;
+  }
+
+  input[type=checkbox] {
+    display: none;
+
+    &:checked + .checkbox-indicator::before {
+      color: $fallback--text;
+      color: var(--text, $fallback--text);
+    }
+
+    &:indeterminate + .checkbox-indicator::before {
+      content: '–';
+      color: $fallback--text;
+      color: var(--text, $fallback--text);
+    }
+
+    &:disabled + .checkbox-indicator::before {
+      opacity: .5;
+    }
+  }
+
+  & > span {
+    margin-left: .5em;
+  }
+}
+</style>
diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue
index 34eec248..9db62e81 100644
--- a/src/components/color_input/color_input.vue
+++ b/src/components/color_input/color_input.vue
@@ -1,33 +1,44 @@
 <template>
-<div class="color-control style-control" :class="{ disabled: !present || disabled }">
-  <label :for="name" class="label">
-    {{label}}
-  </label>
-  <input
-    v-if="typeof fallback !== 'undefined'"
-    class="opt exlcude-disabled"
-    :id="name + '-o'"
-    type="checkbox"
-    :checked="present"
-    @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)">
-  <label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label>
-  <input
-    :id="name"
-    class="color-input"
-    type="color"
-    :value="value || fallback"
-    :disabled="!present || disabled"
-    @input="$emit('input', $event.target.value)"
+  <div
+    class="color-control style-control"
+    :class="{ disabled: !present || disabled }"
+  >
+    <label
+      :for="name"
+      class="label"
     >
-  <input
-    :id="name + '-t'"
-    class="text-input"
-    type="text"
-    :value="value || fallback"
-    :disabled="!present || disabled"
-    @input="$emit('input', $event.target.value)"
+      {{ label }}
+    </label>
+    <input
+      v-if="typeof fallback !== 'undefined'"
+      :id="name + '-o'"
+      class="opt exlcude-disabled"
+      type="checkbox"
+      :checked="present"
+      @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
     >
-</div>
+    <label
+      v-if="typeof fallback !== 'undefined'"
+      class="opt-l"
+      :for="name + '-o'"
+    />
+    <input
+      :id="name"
+      class="color-input"
+      type="color"
+      :value="value || fallback"
+      :disabled="!present || disabled"
+      @input="$emit('input', $event.target.value)"
+    >
+    <input
+      :id="name + '-t'"
+      class="text-input"
+      type="text"
+      :value="value || fallback"
+      :disabled="!present || disabled"
+      @input="$emit('input', $event.target.value)"
+    >
+  </div>
 </template>
 
 <script>
diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue
index bd971d00..15a450a2 100644
--- a/src/components/contrast_ratio/contrast_ratio.vue
+++ b/src/components/contrast_ratio/contrast_ratio.vue
@@ -1,28 +1,38 @@
 <template>
-<span  v-if="contrast" class="contrast-ratio">
-  <span :title="hint" class="rating">
-    <span v-if="contrast.aaa">
-      <i class="icon-thumbs-up-alt"/>
+  <span
+    v-if="contrast"
+    class="contrast-ratio"
+  >
+    <span
+      :title="hint"
+      class="rating"
+    >
+      <span v-if="contrast.aaa">
+        <i class="icon-thumbs-up-alt" />
+      </span>
+      <span v-if="!contrast.aaa && contrast.aa">
+        <i class="icon-adjust" />
+      </span>
+      <span v-if="!contrast.aaa && !contrast.aa">
+        <i class="icon-attention" />
+      </span>
     </span>
-    <span v-if="!contrast.aaa && contrast.aa">
-      <i class="icon-adjust"/>
-    </span>
-    <span v-if="!contrast.aaa && !contrast.aa">
-      <i class="icon-attention"/>
+    <span
+      v-if="contrast && large"
+      class="rating"
+      :title="hint_18pt"
+    >
+      <span v-if="contrast.laaa">
+        <i class="icon-thumbs-up-alt" />
+      </span>
+      <span v-if="!contrast.laaa && contrast.laa">
+        <i class="icon-adjust" />
+      </span>
+      <span v-if="!contrast.laaa && !contrast.laa">
+        <i class="icon-attention" />
+      </span>
     </span>
   </span>
-  <span class="rating" v-if="contrast && large" :title="hint_18pt">
-    <span v-if="contrast.laaa">
-      <i class="icon-thumbs-up-alt"/>
-    </span>
-    <span v-if="!contrast.laaa && contrast.laa">
-      <i class="icon-adjust"/>
-    </span>
-    <span v-if="!contrast.laaa && !contrast.laa">
-      <i class="icon-attention"/>
-    </span>
-  </span>
-</span>
 </template>
 
 <script>
diff --git a/src/components/conversation-page/conversation-page.vue b/src/components/conversation-page/conversation-page.vue
index 9e322cf5..532f785c 100644
--- a/src/components/conversation-page/conversation-page.vue
+++ b/src/components/conversation-page/conversation-page.vue
@@ -1,9 +1,9 @@
 <template>
   <conversation
     :collapsable="false"
-    isPage="true"
+    is-page="true"
     :statusoid="statusoid"
-  ></conversation>
+  />
 </template>
 
 <script src="./conversation-page.js"></script>
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 69058bf6..a2b3aeab 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -1,5 +1,4 @@
-import { reduce, filter, findIndex } from 'lodash'
-import { set } from 'vue'
+import { reduce, filter, findIndex, clone } from 'lodash'
 import Status from '../status/status.vue'
 
 const sortById = (a, b) => {
@@ -36,14 +35,14 @@ const conversation = {
   data () {
     return {
       highlight: null,
-      expanded: false,
-      converationStatusIds: []
+      expanded: false
     }
   },
   props: [
     'statusoid',
     'collapsable',
-    'isPage'
+    'isPage',
+    'showPinned'
   ],
   created () {
     if (this.isPage) {
@@ -54,15 +53,6 @@ const conversation = {
     status () {
       return this.statusoid
     },
-    idsToShow () {
-      if (this.converationStatusIds.length > 0) {
-        return this.converationStatusIds
-      } else if (this.statusId) {
-        return [this.statusId]
-      } else {
-        return []
-      }
-    },
     statusId () {
       if (this.statusoid.retweeted_status) {
         return this.statusoid.retweeted_status.id
@@ -70,6 +60,13 @@ const conversation = {
         return this.statusoid.id
       }
     },
+    conversationId () {
+      if (this.statusoid.retweeted_status) {
+        return this.statusoid.retweeted_status.statusnet_conversation_id
+      } else {
+        return this.statusoid.statusnet_conversation_id
+      }
+    },
     conversation () {
       if (!this.status) {
         return []
@@ -79,12 +76,7 @@ const conversation = {
         return [this.status]
       }
 
-      const statusesObject = this.$store.state.statuses.allStatusesObject
-      const conversation = this.idsToShow.reduce((acc, id) => {
-        acc.push(statusesObject[id])
-        return acc
-      }, [])
-
+      const conversation = clone(this.$store.state.statuses.conversationsObject[this.conversationId])
       const statusIndex = findIndex(conversation, { id: this.statusId })
       if (statusIndex !== -1) {
         conversation[statusIndex] = this.status
@@ -94,7 +86,8 @@ const conversation = {
     },
     replies () {
       let i = 1
-      return reduce(this.conversation, (result, {id, in_reply_to_status_id}) => {
+      // eslint-disable-next-line camelcase
+      return reduce(this.conversation, (result, { id, in_reply_to_status_id }) => {
         /* eslint-disable camelcase */
         const irid = in_reply_to_status_id
         /* eslint-enable camelcase */
@@ -127,19 +120,15 @@ const conversation = {
   methods: {
     fetchConversation () {
       if (this.status) {
-        this.$store.state.api.backendInteractor.fetchConversation({id: this.status.id})
-          .then(({ancestors, descendants}) => {
+        this.$store.state.api.backendInteractor.fetchConversation({ id: this.status.id })
+          .then(({ ancestors, descendants }) => {
             this.$store.dispatch('addNewStatuses', { statuses: ancestors })
             this.$store.dispatch('addNewStatuses', { statuses: descendants })
-            set(this, 'converationStatusIds', [].concat(
-              ancestors.map(_ => _.id).filter(_ => _ !== this.statusId),
-              this.statusId,
-              descendants.map(_ => _.id).filter(_ => _ !== this.statusId)))
           })
           .then(() => this.setHighlight(this.statusId))
       } else {
         const id = this.$route.params.id
-        this.$store.state.api.backendInteractor.fetchStatus({id})
+        this.$store.state.api.backendInteractor.fetchStatus({ id })
           .then((status) => this.$store.dispatch('addNewStatuses', { statuses: [status] }))
           .then(() => this.fetchConversation())
       }
@@ -151,7 +140,9 @@ const conversation = {
       return (this.isExpanded) && id === this.status.id
     },
     setHighlight (id) {
+      if (!id) return
       this.highlight = id
+      this.$store.dispatch('fetchFavsAndRepeats', id)
     },
     getHighlight () {
       return this.isExpanded ? this.highlight : null
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index c39a3ed9..5a900607 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -1,24 +1,34 @@
 <template>
-  <div class="timeline panel-default" :class="[isExpanded ? 'panel' : 'panel-disabled']">
-    <div v-if="isExpanded" class="panel-heading conversation-heading">
+  <div
+    class="timeline panel-default"
+    :class="[isExpanded ? 'panel' : 'panel-disabled']"
+  >
+    <div
+      v-if="isExpanded"
+      class="panel-heading conversation-heading"
+    >
       <span class="title"> {{ $t('timeline.conversation') }} </span>
       <span v-if="collapsable">
-        <a href="#" @click.prevent="toggleExpanded">{{ $t('timeline.collapse') }}</a>
+        <a
+          href="#"
+          @click.prevent="toggleExpanded"
+        >{{ $t('timeline.collapse') }}</a>
       </span>
     </div>
     <status
       v-for="status in conversation"
-      @goto="setHighlight"
-      @toggleExpanded="toggleExpanded"
       :key="status.id"
-      :inlineExpanded="collapsable"
+      :inline-expanded="collapsable && isExpanded"
       :statusoid="status"
-      :expandable='!expanded'
+      :expandable="!isExpanded"
+      :show-pinned="showPinned"
       :focused="focused(status.id)"
-      :inConversation="isExpanded"
+      :in-conversation="isExpanded"
       :highlight="getHighlight()"
       :replies="getReplies(status.id)"
       class="status-fadein panel-body"
+      @goto="setHighlight"
+      @toggleExpanded="toggleExpanded"
     />
   </div>
 </template>
diff --git a/src/components/delete_button/delete_button.js b/src/components/delete_button/delete_button.js
deleted file mode 100644
index f2920666..00000000
--- a/src/components/delete_button/delete_button.js
+++ /dev/null
@@ -1,17 +0,0 @@
-const DeleteButton = {
-  props: [ 'status' ],
-  methods: {
-    deleteStatus () {
-      const confirmed = window.confirm('Do you really want to delete this status?')
-      if (confirmed) {
-        this.$store.dispatch('deleteStatus', { id: this.status.id })
-      }
-    }
-  },
-  computed: {
-    currentUser () { return this.$store.state.users.currentUser },
-    canDelete () { return this.currentUser && this.currentUser.rights.delete_others_notice || this.status.user.id === this.currentUser.id }
-  }
-}
-
-export default DeleteButton
diff --git a/src/components/delete_button/delete_button.vue b/src/components/delete_button/delete_button.vue
deleted file mode 100644
index f4c91cfd..00000000
--- a/src/components/delete_button/delete_button.vue
+++ /dev/null
@@ -1,21 +0,0 @@
-<template>
-  <div v-if="canDelete">
-    <a href="#" v-on:click.prevent="deleteStatus()">
-      <i class='button-icon icon-cancel delete-status'></i>
-    </a>
-  </div>
-</template>
-
-<script src="./delete_button.js" ></script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-
-.icon-cancel,.delete-status {
-  cursor: pointer;
-  &:hover {
-    color: $fallback--cRed;
-    color: var(--cRed, $fallback--cRed);
-  }
-}
-</style>
diff --git a/src/components/dialog_modal/dialog_modal.js b/src/components/dialog_modal/dialog_modal.js
new file mode 100644
index 00000000..f14e3fe9
--- /dev/null
+++ b/src/components/dialog_modal/dialog_modal.js
@@ -0,0 +1,14 @@
+const DialogModal = {
+  props: {
+    darkOverlay: {
+      default: true,
+      type: Boolean
+    },
+    onCancel: {
+      default: () => {},
+      type: Function
+    }
+  }
+}
+
+export default DialogModal
diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue
new file mode 100644
index 00000000..55d7a7d2
--- /dev/null
+++ b/src/components/dialog_modal/dialog_modal.vue
@@ -0,0 +1,100 @@
+<template>
+  <span
+    :class="{ 'dark-overlay': darkOverlay }"
+    @click.self.stop="onCancel()"
+  >
+    <div
+      class="dialog-modal panel panel-default"
+      @click.stop=""
+    >
+      <div class="panel-heading dialog-modal-heading">
+        <div class="title">
+          <slot name="header" />
+        </div>
+      </div>
+      <div class="dialog-modal-content">
+        <slot name="default" />
+      </div>
+      <div class="dialog-modal-footer user-interactions panel-footer">
+        <slot name="footer" />
+      </div>
+    </div>
+  </span>
+</template>
+
+<script src="./dialog_modal.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+// TODO: unify with other modals.
+.dark-overlay {
+  &::before {
+    bottom: 0;
+    content: " ";
+    display: block;
+    cursor: default;
+    left: 0;
+    position: fixed;
+    right: 0;
+    top: 0;
+    background: rgba(27,31,35,.5);
+    z-index: 99;
+  }
+}
+
+.dialog-modal.panel {
+  top: 0;
+  left: 50%;
+  max-height: 80vh;
+  max-width: 90vw;
+  margin: 15vh auto;
+  position: fixed;
+  transform: translateX(-50%);
+  z-index: 999;
+  cursor: default;
+  display: block;
+  background-color: $fallback--bg;
+  background-color: var(--bg, $fallback--bg);
+
+  .dialog-modal-heading {
+    padding: .5em .5em;
+    margin-right: auto;
+    margin-bottom: 0;
+    white-space: nowrap;
+    color: var(--panelText);
+    background-color: $fallback--fg;
+    background-color: var(--panel, $fallback--fg);
+
+    .title {
+      margin-bottom: 0;
+      text-align: center;
+    }
+  }
+
+  .dialog-modal-content {
+    margin: 0;
+    padding: 1rem 1rem;
+    background-color: $fallback--lightBg;
+    background-color: var(--lightBg, $fallback--lightBg);
+    white-space: normal;
+  }
+
+  .dialog-modal-footer {
+    margin: 0;
+    padding: .5em .5em;
+    background-color: $fallback--lightBg;
+    background-color: var(--lightBg, $fallback--lightBg);
+    border-top: 1px solid $fallback--bg;
+    border-top: 1px solid var(--bg, $fallback--bg);
+    display: flex;
+    justify-content: flex-end;
+
+    button {
+      width: auto;
+      margin-left: .5rem;
+    }
+  }
+}
+
+</style>
diff --git a/src/components/dm_timeline/dm_timeline.vue b/src/components/dm_timeline/dm_timeline.vue
index f03da4d3..c4e4d070 100644
--- a/src/components/dm_timeline/dm_timeline.vue
+++ b/src/components/dm_timeline/dm_timeline.vue
@@ -1,5 +1,9 @@
 <template>
-  <Timeline :title="$t('nav.dms')" v-bind:timeline="timeline" v-bind:timeline-name="'dms'"/>
+  <Timeline
+    :title="$t('nav.dms')"
+    :timeline="timeline"
+    :timeline-name="'dms'"
+  />
 </template>
 
 <script src="./dm_timeline.js"></script>
diff --git a/src/components/emoji-input/emoji-input.js b/src/components/emoji-input/emoji-input.js
index 112fd148..c3c5c88b 100644
--- a/src/components/emoji-input/emoji-input.js
+++ b/src/components/emoji-input/emoji-input.js
@@ -1,19 +1,66 @@
 import Completion from '../../services/completion/completion.js'
-import EmojiSelector from '../emoji-selector/emoji-selector.vue'
+import { take } from 'lodash'
 
-import { take, filter, map } from 'lodash'
+/**
+ * EmojiInput - augmented inputs for emoji and autocomplete support in inputs
+ * without having to give up the comfort of <input/> and <textarea/> elements
+ *
+ * Intended usage is:
+ * <EmojiInput v-model="something">
+ *   <input v-model="something"/>
+ * </EmojiInput>
+ *
+ * Works only with <input> and <textarea>. Intended to use with only one nested
+ * input. It will find first input or textarea and work with that, multiple
+ * nested children not tested. You HAVE TO duplicate v-model for both
+ * <emoji-input> and <input>/<textarea> otherwise it will not work.
+ *
+ * Be prepared for CSS troubles though because it still wraps component in a div
+ * while TRYING to make it look like nothing happened, but it could break stuff.
+ */
 
 const EmojiInput = {
-  props: [
-    'value',
-    'placeholder',
-    'type',
-    'classname'
-  ],
+  props: {
+    suggest: {
+      /**
+       * suggest: function (input: String) => Suggestion[]
+       *
+       * Function that takes input string which takes string (textAtCaret)
+       * and returns an array of Suggestions
+       *
+       * Suggestion is an object containing following properties:
+       * displayText: string. Main display text, what actual suggestion
+       *    represents (user's screen name/emoji shortcode)
+       * replacement: string. Text that should replace the textAtCaret
+       * detailText: string, optional. Subtitle text, providing additional info
+       *    if present (user's nickname)
+       * imageUrl: string, optional. Image to display alongside with suggestion,
+       *    currently if no image is provided, replacement will be used (for
+       *    unicode emojis)
+       *
+       * TODO: make it asynchronous when adding proper server-provided user
+       * suggestions
+       *
+       * For commonly used suggestors (emoji, users, both) use suggestor.js
+       */
+      required: true,
+      type: Function
+    },
+    value: {
+      /**
+       * Used for v-model
+       */
+      required: true,
+      type: String
+    }
+  },
   data () {
     return {
+      input: undefined,
       highlighted: 0,
-      caret: 0
+      caret: 0,
+      focused: false,
+      blurTimeout: null
     }
   },
   components: {
@@ -22,35 +69,57 @@ const EmojiInput = {
   computed: {
     suggestions () {
       const firstchar = this.textAtCaret.charAt(0)
-      if (firstchar === ':') {
-        if (this.textAtCaret === ':') { return }
-        const matchedEmoji = filter(this.emoji.concat(this.customEmoji), (emoji) => emoji.shortcode.startsWith(this.textAtCaret.slice(1)))
-        if (matchedEmoji.length <= 0) {
-          return false
-        }
-        return map(take(matchedEmoji, 5), ({shortcode, image_url, utf}, index) => ({
-          shortcode: `:${shortcode}:`,
-          utf: utf || '',
+      if (this.textAtCaret === firstchar) { return [] }
+      const matchedSuggestions = this.suggest(this.textAtCaret)
+      if (matchedSuggestions.length <= 0) {
+        return []
+      }
+      return take(matchedSuggestions, 5)
+        .map(({ imageUrl, ...rest }, index) => ({
+          ...rest,
           // eslint-disable-next-line camelcase
-          img: utf ? '' : this.$store.state.instance.server + image_url,
+          img: imageUrl || '',
           highlighted: index === this.highlighted
         }))
-      } else {
-        return false
-      }
+    },
+    showPopup () {
+      return this.focused && this.suggestions && this.suggestions.length > 0
     },
     textAtCaret () {
       return (this.wordAtCaret || {}).word || ''
     },
     wordAtCaret () {
-      const word = Completion.wordAtPosition(this.value, this.caret - 1) || {}
-      return word
-    },
-    emoji () {
-      return this.$store.state.instance.emoji || []
-    },
-    customEmoji () {
-      return this.$store.state.instance.customEmoji || []
+      if (this.value && this.caret) {
+        const word = Completion.wordAtPosition(this.value, this.caret - 1) || {}
+        return word
+      }
+    }
+  },
+  mounted () {
+    const slots = this.$slots.default
+    if (!slots || slots.length === 0) return
+    const input = slots.find(slot => ['input', 'textarea'].includes(slot.tag))
+    if (!input) return
+    this.input = input
+    this.resize()
+    input.elm.addEventListener('blur', this.onBlur)
+    input.elm.addEventListener('focus', this.onFocus)
+    input.elm.addEventListener('paste', this.onPaste)
+    input.elm.addEventListener('keyup', this.onKeyUp)
+    input.elm.addEventListener('keydown', this.onKeyDown)
+    input.elm.addEventListener('transitionend', this.onTransition)
+    input.elm.addEventListener('compositionupdate', this.onCompositionUpdate)
+  },
+  unmounted () {
+    const { input } = this
+    if (input) {
+      input.elm.removeEventListener('blur', this.onBlur)
+      input.elm.removeEventListener('focus', this.onFocus)
+      input.elm.removeEventListener('paste', this.onPaste)
+      input.elm.removeEventListener('keyup', this.onKeyUp)
+      input.elm.removeEventListener('keydown', this.onKeyDown)
+      input.elm.removeEventListener('transitionend', this.onTransition)
+      input.elm.removeEventListener('compositionupdate', this.onCompositionUpdate)
     }
   },
   methods: {
@@ -59,27 +128,35 @@ const EmojiInput = {
       this.$emit('input', newValue)
       this.caret = 0
     },
-    replaceEmoji (e) {
+    replaceText (e, suggestion) {
       const len = this.suggestions.length || 0
-      if (this.textAtCaret === ':' || e.ctrlKey) { return }
-      if (len > 0) {
-        e.preventDefault()
-        const emoji = this.suggestions[this.highlighted]
-        const replacement = emoji.utf || (emoji.shortcode + ' ')
+      if (this.textAtCaret.length === 1) { return }
+      if (len > 0 || suggestion) {
+        const chosenSuggestion = suggestion || this.suggestions[this.highlighted]
+        const replacement = chosenSuggestion.replacement
         const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement)
         this.$emit('input', newValue)
-        this.caret = 0
         this.highlighted = 0
+        const position = this.wordAtCaret.start + replacement.length
+
+        this.$nextTick(function () {
+          // Re-focus inputbox after clicking suggestion
+          this.input.elm.focus()
+          // Set selection right after the replacement instead of the very end
+          this.input.elm.setSelectionRange(position, position)
+          this.caret = position
+        })
+        e.preventDefault()
       }
     },
     cycleBackward (e) {
       const len = this.suggestions.length || 0
       if (len > 0) {
-        e.preventDefault()
         this.highlighted -= 1
         if (this.highlighted < 0) {
           this.highlighted = this.suggestions.length - 1
         }
+        e.preventDefault()
       } else {
         this.highlighted = 0
       }
@@ -87,47 +164,88 @@ const EmojiInput = {
     cycleForward (e) {
       const len = this.suggestions.length || 0
       if (len > 0) {
-        if (e.shiftKey) { return }
-        e.preventDefault()
         this.highlighted += 1
         if (this.highlighted >= len) {
           this.highlighted = 0
         }
+        e.preventDefault()
       } else {
         this.highlighted = 0
       }
     },
-    onKeydown (e) {
-      e.stopPropagation()
+    onTransition (e) {
+      this.resize()
     },
-    onInput (e) {
-      this.$emit('input', e.target.value)
+    onBlur (e) {
+      // Clicking on any suggestion removes focus from autocomplete,
+      // preventing click handler ever executing.
+      this.blurTimeout = setTimeout(() => {
+        this.focused = false
+        this.setCaret(e)
+        this.resize()
+      }, 200)
     },
-    setCaret ({target: {selectionStart}}) {
-      this.caret = selectionStart
+    onClick (e, suggestion) {
+      this.replaceText(e, suggestion)
     },
-    onEmoji (emoji) {
-      const newValue = this.value.substr(0, this.caret) + emoji + this.value.substr(this.caret)
-      this.$emit('input', newValue)
-      this.caret += emoji.length
-      setTimeout(() => {
-        this.updateCaretPos()
-      })
+    onFocus (e) {
+      if (this.blurTimeout) {
+        clearTimeout(this.blurTimeout)
+        this.blurTimeout = null
+      }
+
+      this.focused = true
+      this.setCaret(e)
+      this.resize()
     },
-    updateCaretPos () {
-      const elem = this.$refs.input
-      if (elem.createTextRange) {
-        const range = elem.createTextRange()
-        range.move('character', this.caret)
-        range.select()
-      } else {
-        if (elem.selectionStart) {
-          elem.focus()
-          elem.setSelectionRange(this.caret, this.caret)
+    onKeyUp (e) {
+      this.setCaret(e)
+      this.resize()
+    },
+    onPaste (e) {
+      this.setCaret(e)
+      this.resize()
+    },
+    onKeyDown (e) {
+      this.setCaret(e)
+      this.resize()
+
+      const { ctrlKey, shiftKey, key } = e
+      if (key === 'Tab') {
+        if (shiftKey) {
+          this.cycleBackward(e)
         } else {
-          elem.focus()
+          this.cycleForward(e)
         }
       }
+      if (key === 'ArrowUp') {
+        this.cycleBackward(e)
+      } else if (key === 'ArrowDown') {
+        this.cycleForward(e)
+      }
+      if (key === 'Enter') {
+        if (!ctrlKey) {
+          this.replaceText(e)
+        }
+      }
+    },
+    onInput (e) {
+      this.setCaret(e)
+      this.$emit('input', e.target.value)
+    },
+    onCompositionUpdate (e) {
+      this.setCaret(e)
+      this.resize()
+      this.$emit('input', e.target.value)
+    },
+    setCaret ({ target: { selectionStart } }) {
+      this.caret = selectionStart
+    },
+    resize () {
+      const { panel } = this.$refs
+      if (!panel) return
+      const { offsetHeight, offsetTop } = this.input.elm
+      this.$refs.panel.style.top = (offsetTop + offsetHeight) + 'px'
     }
   }
 }
diff --git a/src/components/emoji-input/emoji-input.vue b/src/components/emoji-input/emoji-input.vue
index 151861de..48739ec8 100644
--- a/src/components/emoji-input/emoji-input.vue
+++ b/src/components/emoji-input/emoji-input.vue
@@ -1,53 +1,30 @@
 <template>
   <div class="emoji-input">
-    <input
-      v-if="type !== 'textarea'"
-      :class="classname"
-      :type="type"
-      :value="value"
-      :placeholder="placeholder"
-      @input="onInput"
-      @click="setCaret"
-      @keyup="setCaret"
-      @keydown="onKeydown"
-      @keydown.down="cycleForward"
-      @keydown.up="cycleBackward"
-      @keydown.shift.tab="cycleBackward"
-      @keydown.tab="cycleForward"
-      @keydown.enter="replaceEmoji"
-      ref="input"
-    />
-    <textarea
-      v-else
-      :class="classname"
-      :value="value"
-      :placeholder="placeholder"
-      @input="onInput"
-      @click="setCaret"
-      @keyup="setCaret"
-      @keydown="onKeydown"
-      @keydown.down="cycleForward"
-      @keydown.up="cycleBackward"
-      @keydown.shift.tab="cycleBackward"
-      @keydown.tab="cycleForward"
-      @keydown.enter="replaceEmoji"
-      ref="input"
-    ></textarea>
-    <EmojiSelector @emoji="onEmoji" />
-    <div class="autocomplete-panel" v-if="suggestions">
+    <slot />
+    <div
+      ref="panel"
+      class="autocomplete-panel"
+      :class="{ hide: !showPopup }"
+    >
       <div class="autocomplete-panel-body">
         <div
-          v-for="(emoji, index) in suggestions"
+          v-for="(suggestion, index) in suggestions"
           :key="index"
-          @click="replace(emoji.utf || (emoji.shortcode + ' '))"
           class="autocomplete-item"
-          :class="{ highlighted: emoji.highlighted }"
+          :class="{ highlighted: suggestion.highlighted }"
+          @click.stop.prevent="onClick($event, suggestion)"
         >
-          <span v-if="emoji.img">
-            <img :src="emoji.img" />
+          <span class="image">
+            <img
+              v-if="suggestion.img"
+              :src="suggestion.img"
+            >
+            <span v-else>{{ suggestion.replacement }}</span>
           </span>
-          <span v-else>{{emoji.utf}}</span>
-          <span>{{emoji.shortcode}}</span>
+          <div class="label">
+            <span class="displayText">{{ suggestion.displayText }}</span>
+            <span class="detailText">{{ suggestion.detailText }}</span>
+          </div>
         </div>
       </div>
     </div>
@@ -60,10 +37,81 @@
 @import '../../_variables.scss';
 
 .emoji-input {
-  position: relative;
+  display: flex;
+  flex-direction: column;
 
-  .form-control {
-    width: 100%;
+  .autocomplete {
+    &-panel {
+      position: absolute;
+      z-index: 9;
+      margin-top: 2px;
+
+      &.hide {
+        display: none
+      }
+
+      &-body {
+        margin: 0 0.5em 0 0.5em;
+        border-radius: $fallback--tooltipRadius;
+        border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+        box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5);
+        box-shadow: var(--popupShadow);
+        min-width: 75%;
+        background: $fallback--bg;
+        background: var(--bg, $fallback--bg);
+        color: $fallback--lightText;
+        color: var(--lightText, $fallback--lightText);
+      }
+    }
+
+    &-item {
+      display: flex;
+      cursor: pointer;
+      padding: 0.2em 0.4em;
+      border-bottom: 1px solid rgba(0, 0, 0, 0.4);
+      height: 32px;
+
+      .image {
+        width: 32px;
+        height: 32px;
+        line-height: 32px;
+        text-align: center;
+        font-size: 32px;
+
+        margin-right: 4px;
+
+        img {
+          width: 32px;
+          height: 32px;
+          object-fit: contain;
+        }
+      }
+
+      .label {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        margin: 0 0.1em 0 0.2em;
+
+        .displayText {
+          line-height: 1.5;
+        }
+
+        .detailText {
+          font-size: 9px;
+          line-height: 9px;
+        }
+      }
+
+      &.highlighted {
+        background-color: $fallback--fg;
+        background-color: var(--lightBg, $fallback--fg);
+      }
+    }
+  }
+
+  input, textarea {
+    flex: 1 0 auto;
   }
 }
 </style>
diff --git a/src/components/emoji-input/suggestor.js b/src/components/emoji-input/suggestor.js
new file mode 100644
index 00000000..aec5c39d
--- /dev/null
+++ b/src/components/emoji-input/suggestor.js
@@ -0,0 +1,94 @@
+import { debounce } from 'lodash'
+/**
+ * suggest - generates a suggestor function to be used by emoji-input
+ * data: object providing source information for specific types of suggestions:
+ * data.emoji - optional, an array of all emoji available i.e.
+ *   (state.instance.emoji + state.instance.customEmoji)
+ * data.users - optional, an array of all known users
+ * updateUsersList - optional, a function to search and append to users
+ *
+ * Depending on data present one or both (or none) can be present, so if field
+ * doesn't support user linking you can just provide only emoji.
+ */
+
+const debounceUserSearch = debounce((data, input) => {
+  data.updateUsersList(input)
+}, 500, { leading: true, trailing: false })
+
+export default data => input => {
+  const firstChar = input[0]
+  if (firstChar === ':' && data.emoji) {
+    return suggestEmoji(data.emoji)(input)
+  }
+  if (firstChar === '@' && data.users) {
+    return suggestUsers(data)(input)
+  }
+  return []
+}
+
+export const suggestEmoji = emojis => input => {
+  const noPrefix = input.toLowerCase().substr(1)
+  return emojis
+    .filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix))
+    .sort((a, b) => {
+      let aScore = 0
+      let bScore = 0
+
+      // Make custom emojis a priority
+      aScore += a.imageUrl ? 10 : 0
+      bScore += b.imageUrl ? 10 : 0
+
+      // Sort alphabetically
+      const alphabetically = a.displayText > b.displayText ? 1 : -1
+
+      return bScore - aScore + alphabetically
+    })
+}
+
+export const suggestUsers = data => input => {
+  const noPrefix = input.toLowerCase().substr(1)
+  const users = data.users
+
+  const newUsers = users.filter(
+    user =>
+      user.screen_name.toLowerCase().startsWith(noPrefix) ||
+      user.name.toLowerCase().startsWith(noPrefix)
+
+    /* taking only 20 results so that sorting is a bit cheaper, we display
+     * only 5 anyway. could be inaccurate, but we ideally we should query
+     * backend anyway
+     */
+  ).slice(0, 20).sort((a, b) => {
+    let aScore = 0
+    let bScore = 0
+
+    // Matches on screen name (i.e. user@instance) makes a priority
+    aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
+    bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
+
+    // Matches on name takes second priority
+    aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0
+    bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0
+
+    const diff = (bScore - aScore) * 10
+
+    // Then sort alphabetically
+    const nameAlphabetically = a.name > b.name ? 1 : -1
+    const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1
+
+    return diff + nameAlphabetically + screenNameAlphabetically
+    /* eslint-disable camelcase */
+  }).map(({ screen_name, name, profile_image_url_original }) => ({
+    displayText: screen_name,
+    detailText: name,
+    imageUrl: profile_image_url_original,
+    replacement: '@' + screen_name + ' '
+  }))
+
+  // BE search users if there are no matches
+  if (newUsers.length === 0 && data.updateUsersList) {
+    debounceUserSearch(data, noPrefix)
+  }
+  return newUsers
+  /* eslint-enable camelcase */
+}
diff --git a/src/components/export_import/export_import.vue b/src/components/export_import/export_import.vue
index 451a2668..20c6f569 100644
--- a/src/components/export_import/export_import.vue
+++ b/src/components/export_import/export_import.vue
@@ -1,12 +1,27 @@
 <template>
-<div class="import-export-container">
-  <slot name="before"/>
-  <button class="btn" @click="exportData">{{ exportLabel }}</button>
-  <button class="btn" @click="importData">{{ importLabel }}</button>
-  <slot name="afterButtons"/>
-  <p v-if="importFailed" class="alert error">{{ importFailedText }}</p>
-  <slot name="afterError"/>
-</div>
+  <div class="import-export-container">
+    <slot name="before" />
+    <button
+      class="btn"
+      @click="exportData"
+    >
+      {{ exportLabel }}
+    </button>
+    <button
+      class="btn"
+      @click="importData"
+    >
+      {{ importLabel }}
+    </button>
+    <slot name="afterButtons" />
+    <p
+      v-if="importFailed"
+      class="alert error"
+    >
+      {{ importFailedText }}
+    </p>
+    <slot name="afterError" />
+  </div>
 </template>
 
 <script>
@@ -49,7 +64,7 @@ export default {
         if (event.target.files[0]) {
           // eslint-disable-next-line no-undef
           const reader = new FileReader()
-          reader.onload = ({target}) => {
+          reader.onload = ({ target }) => {
             try {
               const parsed = JSON.parse(target.result)
               const valid = this.validator(parsed)
diff --git a/src/components/exporter/exporter.js b/src/components/exporter/exporter.js
new file mode 100644
index 00000000..8f507416
--- /dev/null
+++ b/src/components/exporter/exporter.js
@@ -0,0 +1,48 @@
+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)
+          // Add delay before hiding processing state since browser takes some time to handle file download
+          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..f5126dc1
--- /dev/null
+++ b/src/components/exporter/exporter.vue
@@ -0,0 +1,26 @@
+<template>
+  <div class="exporter">
+    <div v-if="processing">
+      <i class="icon-spin4 animate-spin exporter-processing" />
+      <span>{{ processingMessage }}</span>
+    </div>
+    <button
+      v-else
+      class="btn btn-default"
+      @click="process"
+    >
+      {{ 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/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
new file mode 100644
index 00000000..2ec72729
--- /dev/null
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -0,0 +1,40 @@
+const ExtraButtons = {
+  props: [ 'status' ],
+  methods: {
+    deleteStatus () {
+      const confirmed = window.confirm(this.$t('status.delete_confirm'))
+      if (confirmed) {
+        this.$store.dispatch('deleteStatus', { id: this.status.id })
+      }
+    },
+    pinStatus () {
+      this.$store.dispatch('pinStatus', this.status.id)
+        .then(() => this.$emit('onSuccess'))
+        .catch(err => this.$emit('onError', err.error.error))
+    },
+    unpinStatus () {
+      this.$store.dispatch('unpinStatus', this.status.id)
+        .then(() => this.$emit('onSuccess'))
+        .catch(err => this.$emit('onError', err.error.error))
+    }
+  },
+  computed: {
+    currentUser () { return this.$store.state.users.currentUser },
+    canDelete () {
+      if (!this.currentUser) { return }
+      const superuser = this.currentUser.rights.moderator || this.currentUser.rights.admin
+      return superuser || this.status.user.id === this.currentUser.id
+    },
+    ownStatus () {
+      return this.status.user.id === this.currentUser.id
+    },
+    canPin () {
+      return this.ownStatus && (this.status.visibility === 'public' || this.status.visibility === 'unlisted')
+    },
+    enabled () {
+      return this.canPin || this.canDelete
+    }
+  }
+}
+
+export default ExtraButtons
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
new file mode 100644
index 00000000..cdad1666
--- /dev/null
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -0,0 +1,59 @@
+<template>
+  <v-popover
+    v-if="enabled"
+    trigger="click"
+    placement="top"
+    class="extra-button-popover"
+    :offset="5"
+    :container="false"
+  >
+    <div slot="popover">
+      <div class="dropdown-menu">
+        <button
+          v-if="!status.pinned && canPin"
+          v-close-popover
+          class="dropdown-item dropdown-item-icon"
+          @click.prevent="pinStatus"
+        >
+          <i class="icon-pin" /><span>{{ $t("status.pin") }}</span>
+        </button>
+        <button
+          v-if="status.pinned && canPin"
+          v-close-popover
+          class="dropdown-item dropdown-item-icon"
+          @click.prevent="unpinStatus"
+        >
+          <i class="icon-pin" /><span>{{ $t("status.unpin") }}</span>
+        </button>
+        <button
+          v-if="canDelete"
+          v-close-popover
+          class="dropdown-item dropdown-item-icon"
+          @click.prevent="deleteStatus"
+        >
+          <i class="icon-cancel" /><span>{{ $t("status.delete") }}</span>
+        </button>
+      </div>
+    </div>
+    <div class="button-icon">
+      <i class="icon-ellipsis" />
+    </div>
+  </v-popover>
+</template>
+
+<script src="./extra_buttons.js" ></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+@import '../popper/popper.scss';
+
+.icon-ellipsis {
+  cursor: pointer;
+
+  &:hover,
+  .extra-button-popover.open & {
+    color: $fallback--text;
+    color: var(--text, $fallback--text);
+  }
+}
+</style>
diff --git a/src/components/favorite_button/favorite_button.js b/src/components/favorite_button/favorite_button.js
index a2b4cb65..a24eacbf 100644
--- a/src/components/favorite_button/favorite_button.js
+++ b/src/components/favorite_button/favorite_button.js
@@ -11,9 +11,9 @@ const FavoriteButton = {
   methods: {
     favorite () {
       if (!this.status.favorited) {
-        this.$store.dispatch('favorite', {id: this.status.id})
+        this.$store.dispatch('favorite', { id: this.status.id })
       } else {
-        this.$store.dispatch('unfavorite', {id: this.status.id})
+        this.$store.dispatch('unfavorite', { id: this.status.id })
       }
       this.animated = true
       setTimeout(() => {
diff --git a/src/components/favorite_button/favorite_button.vue b/src/components/favorite_button/favorite_button.vue
index 05ce6bd0..06ce9983 100644
--- a/src/components/favorite_button/favorite_button.vue
+++ b/src/components/favorite_button/favorite_button.vue
@@ -1,11 +1,20 @@
 <template>
   <div v-if="loggedIn">
-    <i :class='classes' class='button-icon favorite-button fav-active' @click.prevent='favorite()' :title="$t('tool_tip.favorite')"/>
-    <span v-if='!hidePostStatsLocal && status.fave_num > 0'>{{status.fave_num}}</span>
+    <i
+      :class="classes"
+      class="button-icon favorite-button fav-active"
+      :title="$t('tool_tip.favorite')"
+      @click.prevent="favorite()"
+    />
+    <span v-if="!hidePostStatsLocal && status.fave_num > 0">{{ status.fave_num }}</span>
   </div>
   <div v-else>
-    <i :class='classes' class='button-icon favorite-button' :title="$t('tool_tip.favorite')"/>
-    <span v-if='!hidePostStatsLocal && status.fave_num > 0'>{{status.fave_num}}</span>
+    <i
+      :class="classes"
+      class="button-icon favorite-button"
+      :title="$t('tool_tip.favorite')"
+    />
+    <span v-if="!hidePostStatsLocal && status.fave_num > 0">{{ status.fave_num }}</span>
   </div>
 </template>
 
diff --git a/src/components/features_panel/features_panel.vue b/src/components/features_panel/features_panel.vue
index 7a263e01..3e5939a6 100644
--- a/src/components/features_panel/features_panel.vue
+++ b/src/components/features_panel/features_panel.vue
@@ -3,17 +3,25 @@
     <div class="panel panel-default base01-background">
       <div class="panel-heading timeline-heading base02-background base04">
         <div class="title">
-          {{$t('features_panel.title')}}
+          {{ $t('features_panel.title') }}
         </div>
       </div>
       <div class="panel-body features-panel">
         <ul>
-          <li v-if="chat">{{$t('features_panel.chat')}}</li>
-          <li v-if="gopher">{{$t('features_panel.gopher')}}</li>
-          <li v-if="whoToFollow">{{$t('features_panel.who_to_follow')}}</li>
-          <li v-if="mediaProxy">{{$t('features_panel.media_proxy')}}</li>
-          <li>{{$t('features_panel.scope_options')}}</li>
-          <li>{{$t('features_panel.text_limit')}} = {{textlimit}}</li>
+          <li v-if="chat">
+            {{ $t('features_panel.chat') }}
+          </li>
+          <li v-if="gopher">
+            {{ $t('features_panel.gopher') }}
+          </li>
+          <li v-if="whoToFollow">
+            {{ $t('features_panel.who_to_follow') }}
+          </li>
+          <li v-if="mediaProxy">
+            {{ $t('features_panel.media_proxy') }}
+          </li>
+          <li>{{ $t('features_panel.scope_options') }}</li>
+          <li>{{ $t('features_panel.text_limit') }} = {{ textlimit }}</li>
         </ul>
       </div>
     </div>
diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js
index ac4e265a..dc4a0d41 100644
--- a/src/components/follow_card/follow_card.js
+++ b/src/components/follow_card/follow_card.js
@@ -10,8 +10,7 @@ const FollowCard = {
   data () {
     return {
       inProgress: false,
-      requestSent: false,
-      updated: false
+      requestSent: false
     }
   },
   components: {
@@ -19,10 +18,8 @@ const FollowCard = {
     RemoteFollow
   },
   computed: {
-    isMe () { return this.$store.state.users.currentUser.id === this.user.id },
-    following () { return this.updated ? this.updated.following : this.user.following },
-    showFollow () {
-      return !this.following || this.updated && !this.updated.following
+    isMe () {
+      return this.$store.state.users.currentUser.id === this.user.id
     },
     loggedIn () {
       return this.$store.state.users.currentUser
@@ -31,17 +28,15 @@ const FollowCard = {
   methods: {
     followUser () {
       this.inProgress = true
-      requestFollow(this.user, this.$store).then(({ sent, updated }) => {
+      requestFollow(this.user, this.$store).then(({ sent }) => {
         this.inProgress = false
         this.requestSent = sent
-        this.updated = updated
       })
     },
     unfollowUser () {
       this.inProgress = true
-      requestUnfollow(this.user, this.$store).then(({ updated }) => {
+      requestUnfollow(this.user, this.$store).then(() => {
         this.inProgress = false
-        this.updated = updated
       })
     }
   }
diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue
index 9f314fd3..310fe843 100644
--- a/src/components/follow_card/follow_card.vue
+++ b/src/components/follow_card/follow_card.vue
@@ -1,37 +1,52 @@
 <template>
   <basic-user-card :user="user">
     <div class="follow-card-content-container">
-      <span class="faint" v-if="!noFollowsYou && user.follows_you">
+      <span
+        v-if="!noFollowsYou && user.follows_you"
+        class="faint"
+      >
         {{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}
       </span>
-      <div class="follow-card-follow-button" v-if="showFollow && !loggedIn">
-        <RemoteFollow :user="user" />
-      </div>
-      <button
-        v-if="showFollow && loggedIn"
-        class="btn btn-default follow-card-follow-button"
-        @click="followUser"
-        :disabled="inProgress"
-        :title="requestSent ? $t('user_card.follow_again') : ''"
-      >
-        <template v-if="inProgress">
-          {{ $t('user_card.follow_progress') }}
-        </template>
-        <template v-else-if="requestSent">
-          {{ $t('user_card.follow_sent') }}
-        </template>
-        <template v-else>
-          {{ $t('user_card.follow') }}
-        </template>
-      </button>
-      <button v-if="following" class="btn btn-default follow-card-follow-button pressed" @click="unfollowUser" :disabled="inProgress">
-        <template v-if="inProgress">
-          {{ $t('user_card.follow_progress') }}
-        </template>
-        <template v-else>
-          {{ $t('user_card.follow_unfollow') }}
-        </template>
-      </button>
+      <template v-if="!loggedIn">
+        <div
+          v-if="!user.following"
+          class="follow-card-follow-button"
+        >
+          <RemoteFollow :user="user" />
+        </div>
+      </template>
+      <template v-else>
+        <button
+          v-if="!user.following"
+          class="btn btn-default follow-card-follow-button"
+          :disabled="inProgress"
+          :title="requestSent ? $t('user_card.follow_again') : ''"
+          @click="followUser"
+        >
+          <template v-if="inProgress">
+            {{ $t('user_card.follow_progress') }}
+          </template>
+          <template v-else-if="requestSent">
+            {{ $t('user_card.follow_sent') }}
+          </template>
+          <template v-else>
+            {{ $t('user_card.follow') }}
+          </template>
+        </button>
+        <button
+          v-else
+          class="btn btn-default follow-card-follow-button pressed"
+          :disabled="inProgress"
+          @click="unfollowUser"
+        >
+          <template v-if="inProgress">
+            {{ $t('user_card.follow_progress') }}
+          </template>
+          <template v-else>
+            {{ $t('user_card.follow_unfollow') }}
+          </template>
+        </button>
+      </template>
     </div>
   </basic-user-card>
 </template>
diff --git a/src/components/follow_request_card/follow_request_card.vue b/src/components/follow_request_card/follow_request_card.vue
index 4a3bbba4..b217b8ed 100644
--- a/src/components/follow_request_card/follow_request_card.vue
+++ b/src/components/follow_request_card/follow_request_card.vue
@@ -1,8 +1,18 @@
 <template>
   <basic-user-card :user="user">
     <div class="follow-request-card-content-container">
-      <button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button>
-      <button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button>
+      <button
+        class="btn btn-default"
+        @click="approveUser"
+      >
+        {{ $t('user_card.approve') }}
+      </button>
+      <button
+        class="btn btn-default"
+        @click="denyUser"
+      >
+        {{ $t('user_card.deny') }}
+      </button>
     </div>
   </basic-user-card>
 </template>
diff --git a/src/components/follow_requests/follow_requests.vue b/src/components/follow_requests/follow_requests.vue
index b83c2d68..5fa4cf39 100644
--- a/src/components/follow_requests/follow_requests.vue
+++ b/src/components/follow_requests/follow_requests.vue
@@ -1,10 +1,15 @@
 <template>
   <div class="settings panel panel-default">
     <div class="panel-heading">
-      {{$t('nav.friend_requests')}}
+      {{ $t('nav.friend_requests') }}
     </div>
     <div class="panel-body">
-      <FollowRequestCard v-for="request in requests" :key="request.id" :user="request"/>
+      <FollowRequestCard
+        v-for="request in requests"
+        :key="request.id"
+        :user="request"
+        class="list-item"
+      />
     </div>
   </div>
 </template>
diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
index ed36b280..61f0384b 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -1,35 +1,56 @@
 <template>
-<div class="font-control style-control" :class="{ custom: isCustom }">
-  <label :for="preset === 'custom' ? name : name + '-font-switcher'" class="label">
-    {{label}}
-  </label>
-  <input
-    v-if="typeof fallback !== 'undefined'"
-    class="opt exlcude-disabled"
-    type="checkbox"
-    :id="name + '-o'"
-    :checked="present"
-    @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)">
-  <label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label>
-  <label :for="name + '-font-switcher'" class="select" :disabled="!present">
-    <select
+  <div
+    class="font-control style-control"
+    :class="{ custom: isCustom }"
+  >
+    <label
+      :for="preset === 'custom' ? name : name + '-font-switcher'"
+      class="label"
+    >
+      {{ label }}
+    </label>
+    <input
+      v-if="typeof fallback !== 'undefined'"
+      :id="name + '-o'"
+      class="opt exlcude-disabled"
+      type="checkbox"
+      :checked="present"
+      @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
+    >
+    <label
+      v-if="typeof fallback !== 'undefined'"
+      class="opt-l"
+      :for="name + '-o'"
+    />
+    <label
+      :for="name + '-font-switcher'"
+      class="select"
       :disabled="!present"
-      v-model="preset"
-      class="font-switcher"
-      :id="name + '-font-switcher'">
-      <option v-for="option in availableOptions" :value="option">
-        {{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
-      </option>
-    </select>
-    <i class="icon-down-open"/>
-  </label>
-  <input
-    v-if="isCustom"
-    class="custom-font"
-    type="text"
-    :id="name"
-    v-model="family">
-</div>
+    >
+      <select
+        :id="name + '-font-switcher'"
+        v-model="preset"
+        :disabled="!present"
+        class="font-switcher"
+      >
+        <option
+          v-for="option in availableOptions"
+          :key="option"
+          :value="option"
+        >
+          {{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
+        </option>
+      </select>
+      <i class="icon-down-open" />
+    </label>
+    <input
+      v-if="isCustom"
+      :id="name"
+      v-model="family"
+      class="custom-font"
+      type="text"
+    >
+  </div>
 </template>
 
 <script src="./font_control.js" ></script>
diff --git a/src/components/friends_timeline/friends_timeline.vue b/src/components/friends_timeline/friends_timeline.vue
index 66c0c058..01a56812 100644
--- a/src/components/friends_timeline/friends_timeline.vue
+++ b/src/components/friends_timeline/friends_timeline.vue
@@ -1,5 +1,9 @@
 <template>
-  <Timeline :title="$t('nav.timeline')" v-bind:timeline="timeline" v-bind:timeline-name="'friends'"/>
+  <Timeline
+    :title="$t('nav.timeline')"
+    :timeline="timeline"
+    :timeline-name="'friends'"
+  />
 </template>
 
 <script src="./friends_timeline.js"></script>
diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
index ea525c95..6adfb76c 100644
--- a/src/components/gallery/gallery.vue
+++ b/src/components/gallery/gallery.vue
@@ -1,13 +1,22 @@
 <template>
-  <div ref="galleryContainer" style="width: 100%;">
-    <div class="gallery-row" v-for="row in rows" :style="rowHeight(row.length)" :class="{ 'contain-fit': useContainFit, 'cover-fit': !useContainFit }">
+  <div
+    ref="galleryContainer"
+    style="width: 100%;"
+  >
+    <div
+      v-for="(row, index) in rows"
+      :key="index"
+      class="gallery-row"
+      :style="rowHeight(row.length)"
+      :class="{ 'contain-fit': useContainFit, 'cover-fit': !useContainFit }"
+    >
       <attachment
         v-for="attachment in row"
-        :setMedia="setMedia"
+        :key="attachment.id"
+        :set-media="setMedia"
         :nsfw="nsfw"
         :attachment="attachment"
-        :allowPlay="false"
-        :key="attachment.id"
+        :allow-play="false"
       />
     </div>
   </div>
@@ -28,7 +37,9 @@
   flex-grow: 1;
   margin-top: 0.5em;
 
-  .attachments, .attachment {
+  // FIXME: specificity problem with this and .attachments.attachment
+  // we shouldn't have the need for .image here
+  .attachment.image {
     margin: 0 0.5em 0 0;
     flex-grow: 1;
     height: 100%;
diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js
index 5ba8f04e..01361e25 100644
--- a/src/components/image_cropper/image_cropper.js
+++ b/src/components/image_cropper/image_cropper.js
@@ -70,22 +70,10 @@ const ImageCropper = {
       this.dataUrl = undefined
       this.$emit('close')
     },
-    submit () {
+    submit (cropping = true) {
       this.submitting = true
       this.avatarUploadError = null
-      this.submitHandler(this.cropper, this.file)
-        .then(() => this.destroy())
-        .catch((err) => {
-          this.submitError = err
-        })
-        .finally(() => {
-          this.submitting = false
-        })
-    },
-    submitWithoutCropping () {
-      this.submitting = true
-      this.avatarUploadError = null
-      this.submitHandler(false, this.dataUrl)
+      this.submitHandler(cropping && this.cropper, this.file)
         .then(() => this.destroy())
         .catch((err) => {
           this.submitError = err
diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue
index 129e6f46..4e1b5927 100644
--- a/src/components/image_cropper/image_cropper.vue
+++ b/src/components/image_cropper/image_cropper.vue
@@ -2,20 +2,57 @@
   <div class="image-cropper">
     <div v-if="dataUrl">
       <div class="image-cropper-image-container">
-        <img ref="img" :src="dataUrl" alt="" @load.stop="createCropper" />
+        <img
+          ref="img"
+          :src="dataUrl"
+          alt=""
+          @load.stop="createCropper"
+        >
       </div>
       <div class="image-cropper-buttons-wrapper">
-        <button class="btn" type="button" :disabled="submitting" @click="submit" v-text="saveText"></button>
-        <button class="btn" type="button" :disabled="submitting" @click="destroy" v-text="cancelText"></button>
-        <button class="btn" type="button" :disabled="submitting" @click="submitWithoutCropping" v-text="saveWithoutCroppingText"></button>
-        <i class="icon-spin4 animate-spin" v-if="submitting"></i>
+        <button
+          class="btn"
+          type="button"
+          :disabled="submitting"
+          @click="submit()"
+          v-text="saveText"
+        />
+        <button
+          class="btn"
+          type="button"
+          :disabled="submitting"
+          @click="destroy"
+          v-text="cancelText"
+        />
+        <button
+          class="btn"
+          type="button"
+          :disabled="submitting"
+          @click="submit(false)"
+          v-text="saveWithoutCroppingText"
+        />
+        <i
+          v-if="submitting"
+          class="icon-spin4 animate-spin"
+        />
       </div>
-      <div class="alert error" v-if="submitError">
-        {{submitErrorMsg}}
-        <i class="button-icon icon-cancel" @click="clearError"></i>
+      <div
+        v-if="submitError"
+        class="alert error"
+      >
+        {{ submitErrorMsg }}
+        <i
+          class="button-icon icon-cancel"
+          @click="clearError"
+        />
       </div>
     </div>
-    <input ref="input" type="file" class="image-cropper-img-input" :accept="mimes">
+    <input
+      ref="input"
+      type="file"
+      class="image-cropper-img-input"
+      :accept="mimes"
+    >
   </div>
 </template>
 
diff --git a/src/components/importer/importer.js b/src/components/importer/importer.js
new file mode 100644
index 00000000..c5f9e4d2
--- /dev/null
+++ b/src/components/importer/importer.js
@@ -0,0 +1,53 @@
+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,
+      submitting: false
+    }
+  },
+  methods: {
+    change () {
+      this.file = this.$refs.input.files[0]
+    },
+    submit () {
+      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
+      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..ed923d59
--- /dev/null
+++ b/src/components/importer/importer.vue
@@ -0,0 +1,47 @@
+<template>
+  <div class="importer">
+    <form>
+      <input
+        ref="input"
+        type="file"
+        @change="change"
+      >
+    </form>
+    <i
+      v-if="submitting"
+      class="icon-spin4 animate-spin importer-uploading"
+    />
+    <button
+      v-else
+      class="btn btn-default"
+      @click="submit"
+    >
+      {{ submitButtonLabel }}
+    </button>
+    <div v-if="success">
+      <i
+        class="icon-cross"
+        @click="dismiss"
+      />
+      <p>{{ successMessage }}</p>
+    </div>
+    <div v-else-if="error">
+      <i
+        class="icon-cross"
+        @click="dismiss"
+      />
+      <p>{{ errorMessage }}</p>
+    </div>
+  </div>
+</template>
+
+<script src="./importer.js"></script>
+
+<style lang="scss">
+.importer {
+  &-uploading {
+    font-size: 1.5em;
+    margin: 0.25em;
+  }
+}
+</style>
diff --git a/src/components/instance_specific_panel/instance_specific_panel.vue b/src/components/instance_specific_panel/instance_specific_panel.vue
index a7b74667..a7cf6b48 100644
--- a/src/components/instance_specific_panel/instance_specific_panel.vue
+++ b/src/components/instance_specific_panel/instance_specific_panel.vue
@@ -1,9 +1,13 @@
 <template>
-  <div v-if="show" class="instance-specific-panel">
+  <div
+    v-if="show"
+    class="instance-specific-panel"
+  >
     <div class="panel panel-default">
       <div class="panel-body">
-        <div v-html="instanceSpecificPanelContent">
-        </div>
+        <!-- eslint-disable vue/no-v-html -->
+        <div v-html="instanceSpecificPanelContent" />
+        <!-- eslint-enable vue/no-v-html -->
       </div>
     </div>
   </div>
diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js
new file mode 100644
index 00000000..d4e3cc17
--- /dev/null
+++ b/src/components/interactions/interactions.js
@@ -0,0 +1,25 @@
+import Notifications from '../notifications/notifications.vue'
+
+const tabModeDict = {
+  mentions: ['mention'],
+  'likes+repeats': ['repeat', 'like'],
+  follows: ['follow']
+}
+
+const Interactions = {
+  data () {
+    return {
+      filterMode: tabModeDict['mentions']
+    }
+  },
+  methods: {
+    onModeSwitch (index, dataset) {
+      this.filterMode = tabModeDict[dataset.filter]
+    }
+  },
+  components: {
+    Notifications
+  }
+}
+
+export default Interactions
diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue
new file mode 100644
index 00000000..d71c99d5
--- /dev/null
+++ b/src/components/interactions/interactions.vue
@@ -0,0 +1,37 @@
+<template>
+  <div class="panel panel-default">
+    <div class="panel-heading">
+      <div class="title">
+        {{ $t("nav.interactions") }}
+      </div>
+    </div>
+    <tab-switcher
+      ref="tabSwitcher"
+      :on-switch="onModeSwitch"
+    >
+      <span
+        data-tab-dummy
+        data-filter="mentions"
+        :label="$t('nav.mentions')"
+      />
+      <span
+        data-tab-dummy
+        data-filter="likes+repeats"
+        :label="$t('interactions.favs_repeats')"
+      />
+      <span
+        data-tab-dummy
+        data-filter="follows"
+        :label="$t('interactions.follows')"
+      />
+    </tab-switcher>
+    <Notifications
+      ref="notifications"
+      :no-heading="true"
+      :minimal-mode="true"
+      :filter-mode="filterMode"
+    />
+  </div>
+</template>
+
+<script src="./interactions.js"></script>
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index 3f58af2c..83df9a0b 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -3,39 +3,60 @@
     <label for="interface-language-switcher">
       {{ $t('settings.interfaceLanguage') }}
     </label>
-    <label for="interface-language-switcher" class='select'>
-      <select id="interface-language-switcher" v-model="language">
-        <option v-for="(langCode, i) in languageCodes" :value="langCode">
+    <label
+      for="interface-language-switcher"
+      class="select"
+    >
+      <select
+        id="interface-language-switcher"
+        v-model="language"
+      >
+        <option
+          v-for="(langCode, i) in languageCodes"
+          :key="langCode"
+          :value="langCode"
+        >
           {{ languageNames[i] }}
         </option>
       </select>
-      <i class="icon-down-open"/>
+      <i class="icon-down-open" />
     </label>
   </div>
 </template>
 
 <script>
-  import languagesObject from '../../i18n/messages'
-  import ISO6391 from 'iso-639-1'
-  import _ from 'lodash'
+import languagesObject from '../../i18n/messages'
+import ISO6391 from 'iso-639-1'
+import _ from 'lodash'
 
-  export default {
-    computed: {
-      languageCodes () {
-        return Object.keys(languagesObject)
-      },
+export default {
+  computed: {
+    languageCodes () {
+      return Object.keys(languagesObject)
+    },
 
-      languageNames () {
-        return _.map(this.languageCodes, ISO6391.getName)
-      },
+    languageNames () {
+      return _.map(this.languageCodes, this.getLanguageName)
+    },
 
-      language: {
-        get: function () { return this.$store.state.config.interfaceLanguage },
-        set: function (val) {
-          this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
-          this.$i18n.locale = val
-        }
+    language: {
+      get: function () { return this.$store.state.config.interfaceLanguage },
+      set: function (val) {
+        this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
+        this.$i18n.locale = val
       }
     }
+  },
+
+  methods: {
+    getLanguageName (code) {
+      const specialLanguageNames = {
+        'ja': 'Japanese (やさしいにほんご)',
+        'ja_pedantic': 'Japanese (日本語)',
+        'zh': 'Chinese (简体中文)'
+      }
+      return specialLanguageNames[code] || ISO6391.getName(code)
+    }
   }
+}
 </script>
diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue
index 64b1a58b..493774c2 100644
--- a/src/components/link-preview/link-preview.vue
+++ b/src/components/link-preview/link-preview.vue
@@ -1,13 +1,25 @@
 <template>
   <div>
-    <a class="link-preview-card" :href="card.url" target="_blank" rel="noopener">
-      <div class="card-image" :class="{ 'small-image': size === 'small' }" v-if="useImage">
-        <img :src="card.image"></img>
+    <a
+      class="link-preview-card"
+      :href="card.url"
+      target="_blank"
+      rel="noopener"
+    >
+      <div
+        v-if="useImage"
+        class="card-image"
+        :class="{ 'small-image': size === 'small' }"
+      >
+        <img :src="card.image">
       </div>
       <div class="card-content">
         <span class="card-host faint">{{ card.provider_name }}</span>
         <h4 class="card-title">{{ card.title }}</h4>
-        <p class="card-description" v-if="useDescription">{{ card.description }}</p>
+        <p
+          v-if="useDescription"
+          class="card-description"
+        >{{ card.description }}</p>
       </div>
     </a>
   </div>
diff --git a/src/components/list/list.vue b/src/components/list/list.vue
new file mode 100644
index 00000000..a6223cce
--- /dev/null
+++ b/src/components/list/list.vue
@@ -0,0 +1,52 @@
+<template>
+  <div class="list">
+    <div
+      v-for="item in items"
+      :key="getKey(item)"
+      class="list-item"
+    >
+      <slot
+        name="item"
+        :item="item"
+      />
+    </div>
+    <div
+      v-if="items.length === 0 && !!$slots.empty"
+      class="list-empty-content faint"
+    >
+      <slot name="empty" />
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    items: {
+      type: Array,
+      default: () => []
+    },
+    getKey: {
+      type: Function,
+      default: item => item.id
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.list {
+  &-item:not(:last-child) {
+    border-bottom: 1px solid;
+    border-bottom-color: $fallback--border;
+    border-bottom-color: var(--border, $fallback--border);
+  }
+
+  &-empty-content {
+    text-align: center;
+    padding: 10px;
+  }
+}
+</style>
diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js
index fb6dc651..10f52fe2 100644
--- a/src/components/login_form/login_form.js
+++ b/src/components/login_form/login_form.js
@@ -1,50 +1,81 @@
+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 () {
-      oauthApi.login({
-        oauth: this.$store.state.oauth,
-        instance: this.$store.state.instance.server,
-        commit: this.$store.commit
-      })
-    },
+    ...mapMutations('authFlow', ['requireMFA']),
+    ...mapActions({ login: 'authFlow/login' }),
     submit () {
+      this.isTokenAuth ? this.submitToken() : this.submitPassword()
+    },
+    submitToken () {
+      const { clientId, clientSecret } = this.oauth
       const data = {
-        oauth: this.$store.state.oauth,
-        instance: this.$store.state.instance.server
+        clientId,
+        clientSecret,
+        instance: this.instance.server,
+        commit: this.$store.commit
       }
-      this.clearError()
+
+      oauthApi.getOrCreateApp(data)
+        .then((app) => { oauthApi.login({ ...app, ...data }) })
+    },
+    submitPassword () {
+      const { clientId } = this.oauth
+      const data = {
+        clientId,
+        oauth: this.oauth,
+        instance: this.instance.server,
+        commit: this.$store.commit
+      }
+      this.error = false
+
       oauthApi.getOrCreateApp(data).then((app) => {
         oauthApi.getTokenWithCredentials(
           {
-            app,
+            ...app,
             instance: data.instance,
             username: this.user.username,
             password: this.user.password
           }
         ).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)
-          this.$store.dispatch('loginUser', result.access_token)
-          this.$router.push({name: 'friends'})
+          this.login(result).then(() => {
+            this.$router.push({ name: 'friends' })
+          })
         })
       })
     },
-    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 27a8e48a..3ec7fe0c 100644
--- a/src/components/login_form/login_form.vue
+++ b/src/components/login_form/login_form.vue
@@ -1,44 +1,80 @@
 <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='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')">
-        </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'>
-        </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>
 
-      <form v-if="loginMethod == 'token'" v-on:submit.prevent='oAuthLogin'  class="login-form">
-        <div class="form-group">
-          <p>{{$t('login.description')}}</p>
+    <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
+              id="username"
+              v-model="user.username"
+              :disabled="loggingIn"
+              class="form-control"
+              :placeholder="$t('login.placeholder')"
+            >
+          </div>
+          <div class="form-group">
+            <label for="password">{{ $t('login.password') }}</label>
+            <input
+              id="password"
+              ref="passwordInput"
+              v-model="user.password"
+              :disabled="loggingIn"
+              class="form-control"
+              type="password"
+            >
+          </div>
+        </template>
+
+        <div
+          v-if="isTokenAuth"
+          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">
+          <div class="login-bottom">
+            <div>
+              <router-link
+                v-if="registrationOpen"
+                :to="{name: 'registration'}"
+                class="register"
+              >
+                {{ $t('login.register') }}
+              </router-link>
+            </div>
+            <button
+              :disabled="loggingIn"
+              type="submit"
+              class="btn btn-default"
+            >
+              {{ $t('login.login') }}
+            </button>
           </div>
         </div>
       </form>
-      
-      <div v-if="authError" class='form-group'>
-        <div class='alert error'>
-          {{authError}}
-          <i class="button-icon icon-cancel" @click="clearError"></i>
-        </div>
+    </div>
+
+    <div
+      v-if="error"
+      class="form-group"
+    >
+      <div class="alert error">
+        {{ error }}
+        <i
+          class="button-icon icon-cancel"
+          @click="clearError"
+        />
       </div>
     </div>
   </div>
@@ -50,6 +86,10 @@
 @import '../../_variables.scss';
 
 .login-form {
+  display: flex;
+  flex-direction: column;
+  padding: 0.6em;
+
   .btn {
     min-height: 28px;
     width: 10em;
@@ -66,9 +106,30 @@
     align-items: center;
     justify-content: space-between;
   }
-}
 
-.login {
+  .form-group {
+    display: flex;
+    flex-direction: column;
+    padding: 0.3em 0.5em 0.6em;
+    line-height:24px;
+  }
+
+  .form-bottom {
+    display: flex;
+    padding: 0.5em;
+    height: 32px;
+
+    button {
+      width: 10em;
+    }
+
+    p {
+      margin: 0.35em;
+      padding: 0.35em;
+      display: flex;
+    }
+  }
+
   .error {
     text-align: center;
 
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 7f666603..0543e677 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -1,25 +1,33 @@
 <template>
-  <div class="modal-view media-modal-view" v-if="showing" @click.prevent="hide">
-    <img class="modal-image" v-if="type === 'image'" :src="currentMedia.url"></img>
-    <VideoAttachment
+  <div
+    v-if="showing"
+    class="modal-view media-modal-view"
+    @click.prevent="hide"
+  >
+    <img
+      v-if="type === 'image'"
       class="modal-image"
+      :src="currentMedia.url"
+    >
+    <VideoAttachment
       v-if="type === 'video'"
+      class="modal-image"
       :attachment="currentMedia"
       :controls="true"
-      @click.stop.native="">
-    </VideoAttachment>
+      @click.stop.native=""
+    />
     <button
+      v-if="canNavigate"
       :title="$t('media_modal.previous')"
       class="modal-view-button-arrow modal-view-button-arrow--prev"
-      v-if="canNavigate"
       @click.stop.prevent="goPrev"
     >
       <i class="icon-left-open arrow-icon" />
     </button>
     <button
+      v-if="canNavigate"
       :title="$t('media_modal.next')"
       class="modal-view-button-arrow modal-view-button-arrow--next"
-      v-if="canNavigate"
       @click.stop.prevent="goNext"
     >
       <i class="icon-right-open arrow-icon" />
@@ -33,6 +41,8 @@
 @import '../../_variables.scss';
 
 .media-modal-view {
+  z-index: 1001;
+
   &:hover {
     .modal-view-button-arrow {
       opacity: 0.75;
diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
index e4b3d460..f457d022 100644
--- a/src/components/media_upload/media_upload.js
+++ b/src/components/media_upload/media_upload.js
@@ -16,7 +16,7 @@ const mediaUpload = {
       if (file.size > store.state.instance.uploadlimit) {
         const filesize = fileSizeFormatService.fileSizeFormat(file.size)
         const allowedsize = fileSizeFormatService.fileSizeFormat(store.state.instance.uploadlimit)
-        self.$emit('upload-failed', 'file_too_big', {filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit})
+        self.$emit('upload-failed', 'file_too_big', { filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit })
         return
       }
       const formData = new FormData()
@@ -36,7 +36,7 @@ const mediaUpload = {
     },
     fileDrop (e) {
       if (e.dataTransfer.files.length > 0) {
-        e.preventDefault()  // allow dropping text like before
+        e.preventDefault() // allow dropping text like before
         this.uploadFile(e.dataTransfer.files[0])
       }
     },
@@ -54,7 +54,7 @@ const mediaUpload = {
         this.uploadReady = true
       })
     },
-    change ({target}) {
+    change ({ target }) {
       for (var i = 0; i < target.files.length; i++) {
         let file = target.files[i]
         this.uploadFile(file)
diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue
index fcdc3471..ac32ae83 100644
--- a/src/components/media_upload/media_upload.vue
+++ b/src/components/media_upload/media_upload.vue
@@ -1,9 +1,29 @@
 <template>
-  <div class="media-upload" @drop.prevent @dragover.prevent="fileDrag" @drop="fileDrop">
-    <label class="btn btn-default" :title="$t('tool_tip.media_upload')">
-      <i class="icon-spin4 animate-spin" v-if="uploading"></i>
-      <i class="icon-upload" v-if="!uploading"></i>
-      <input type="file" v-if="uploadReady" @change="change" style="position: fixed; top: -100em" multiple="true"></input>
+  <div
+    class="media-upload"
+    @drop.prevent
+    @dragover.prevent="fileDrag"
+    @drop="fileDrop"
+  >
+    <label
+      class="btn btn-default"
+      :title="$t('tool_tip.media_upload')"
+    >
+      <i
+        v-if="uploading"
+        class="icon-spin4 animate-spin"
+      />
+      <i
+        v-if="!uploading"
+        class="icon-upload"
+      />
+      <input
+        v-if="uploadReady"
+        type="file"
+        style="position: fixed; top: -100em"
+        multiple="true"
+        @change="change"
+      >
     </label>
   </div>
 </template>
@@ -13,7 +33,7 @@
 <style>
  .media-upload {
      font-size: 26px;
-     flex: 1;
+     min-width: 50px;
  }
 
  .icon-upload {
diff --git a/src/components/mentions/mentions.vue b/src/components/mentions/mentions.vue
index bba06da6..70f60baf 100644
--- a/src/components/mentions/mentions.vue
+++ b/src/components/mentions/mentions.vue
@@ -1,5 +1,9 @@
 <template>
-  <Timeline :title="$t('nav.mentions')" v-bind:timeline="timeline" v-bind:timeline-name="'mentions'"/>
+  <Timeline
+    :title="$t('nav.interactions')"
+    :timeline="timeline"
+    :timeline-name="'mentions'"
+  />
 </template>
 
 <script src="./mentions.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..7a3cc22d
--- /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..57294630
--- /dev/null
+++ b/src/components/mfa_form/recovery_form.vue
@@ -0,0 +1,65 @@
+<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
+            id="code"
+            v-model="code"
+            class="form-control"
+          >
+        </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"
+        />
+      </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..778bf8dc
--- /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..a344b395
--- /dev/null
+++ b/src/components/mfa_form/totp_form.vue
@@ -0,0 +1,67 @@
+<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
+            id="code"
+            v-model="code"
+            class="form-control"
+          >
+        </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"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+<script src="./totp_form.js"></script>
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index bc63d2ba..9b341a3b 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -63,6 +63,11 @@ const MobileNav = {
     },
     markNotificationsAsSeen () {
       this.$refs.notifications.markAsSeen()
+    },
+    onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {
+      if (this.$store.state.config.autoLoad && scrollTop + clientHeight >= scrollHeight) {
+        this.$refs.notifications.fetchOlderNotifications()
+      }
     }
   },
   watch: {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 5fa41638..f67b7ff8 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -1,38 +1,77 @@
 <template>
-  <nav class='nav-bar container' id="nav">
-    <div class='mobile-inner-nav' @click="scrollToTop()">
-      <div class='item'>
-        <a href="#" class="mobile-nav-button" @click.stop.prevent="toggleMobileSidebar()">
-          <i class="button-icon icon-menu"></i>
-        </a>
-        <router-link class="site-name" :to="{ name: 'root' }" active-class="home">{{sitename}}</router-link>
+  <div>
+    <nav
+      id="nav"
+      class="nav-bar container"
+    >
+      <div
+        class="mobile-inner-nav"
+        @click="scrollToTop()"
+      >
+        <div class="item">
+          <a
+            href="#"
+            class="mobile-nav-button"
+            @click.stop.prevent="toggleMobileSidebar()"
+          >
+            <i class="button-icon icon-menu" />
+          </a>
+          <router-link
+            class="site-name"
+            :to="{ name: 'root' }"
+            active-class="home"
+          >
+            {{ sitename }}
+          </router-link>
+        </div>
+        <div class="item right">
+          <a
+            v-if="currentUser"
+            class="mobile-nav-button"
+            href="#"
+            @click.stop.prevent="openMobileNotifications()"
+          >
+            <i class="button-icon icon-bell-alt" />
+            <div
+              v-if="unseenNotificationsCount"
+              class="alert-dot"
+            />
+          </a>
+        </div>
       </div>
-      <div class='item right'>
-        <a class="mobile-nav-button" v-if="currentUser" href="#" @click.stop.prevent="openMobileNotifications()">
-          <i class="button-icon icon-bell-alt"></i>
-          <div class="alert-dot" v-if="unseenNotificationsCount"></div>
-        </a>
-      </div>
-    </div>
-    <SideDrawer ref="sideDrawer" :logout="logout"/>
-    <div v-if="currentUser"
+    </nav>
+    <div
+      v-if="currentUser"
       class="mobile-notifications-drawer"
       :class="{ 'closed': !notificationsOpen }"
-      @touchstart="notificationsTouchStart"
-      @touchmove="notificationsTouchMove"
+      @touchstart.stop="notificationsTouchStart"
+      @touchmove.stop="notificationsTouchMove"
     >
       <div class="mobile-notifications-header">
-        <span class="title">{{$t('notifications.notifications')}}</span>
-        <a class="mobile-nav-button" @click.stop.prevent="closeMobileNotifications()">
-          <i class="button-icon icon-cancel"/>
+        <span class="title">{{ $t('notifications.notifications') }}</span>
+        <a
+          class="mobile-nav-button"
+          @click.stop.prevent="closeMobileNotifications()"
+        >
+          <i class="button-icon icon-cancel" />
         </a>
       </div>
-      <div v-if="currentUser" class="mobile-notifications">
-        <Notifications ref="notifications" noHeading="true"/>
+      <div
+        class="mobile-notifications"
+        @scroll="onScroll"
+      >
+        <Notifications
+          ref="notifications"
+          :no-heading="true"
+        />
       </div>
     </div>
+    <SideDrawer
+      ref="sideDrawer"
+      :logout="logout"
+    />
     <MobilePostStatusModal />
-  </nav>
+  </div>
 </template>
 
 <script src="./mobile_nav.js"></script>
@@ -79,6 +118,8 @@
   transition-property: transform;
   transition-duration: 0.25s;
   transform: translateX(0);
+  z-index: 1001;
+  -webkit-overflow-scrolling: touch;
 
   &.closed {
     transform: translateX(100%);
diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.js b/src/components/mobile_post_status_modal/mobile_post_status_modal.js
index 2f24dd08..3cec23c6 100644
--- a/src/components/mobile_post_status_modal/mobile_post_status_modal.js
+++ b/src/components/mobile_post_status_modal/mobile_post_status_modal.js
@@ -1,5 +1,5 @@
 import PostStatusForm from '../post_status_form/post_status_form.vue'
-import { throttle } from 'lodash'
+import { debounce } from 'lodash'
 
 const MobilePostStatusModal = {
   components: {
@@ -16,11 +16,15 @@ const MobilePostStatusModal = {
     }
   },
   created () {
-    window.addEventListener('scroll', this.handleScroll)
+    if (this.autohideFloatingPostButton) {
+      this.activateFloatingPostButtonAutohide()
+    }
     window.addEventListener('resize', this.handleOSK)
   },
   destroyed () {
-    window.removeEventListener('scroll', this.handleScroll)
+    if (this.autohideFloatingPostButton) {
+      this.deactivateFloatingPostButtonAutohide()
+    }
     window.removeEventListener('resize', this.handleOSK)
   },
   computed: {
@@ -28,10 +32,30 @@ const MobilePostStatusModal = {
       return this.$store.state.users.currentUser
     },
     isHidden () {
-      return this.hidden || this.inputActive
+      return this.autohideFloatingPostButton && (this.hidden || this.inputActive)
+    },
+    autohideFloatingPostButton () {
+      return !!this.$store.state.config.autohideFloatingPostButton
+    }
+  },
+  watch: {
+    autohideFloatingPostButton: function (isEnabled) {
+      if (isEnabled) {
+        this.activateFloatingPostButtonAutohide()
+      } else {
+        this.deactivateFloatingPostButtonAutohide()
+      }
     }
   },
   methods: {
+    activateFloatingPostButtonAutohide () {
+      window.addEventListener('scroll', this.handleScrollStart)
+      window.addEventListener('scroll', this.handleScrollEnd)
+    },
+    deactivateFloatingPostButtonAutohide () {
+      window.removeEventListener('scroll', this.handleScrollStart)
+      window.removeEventListener('scroll', this.handleScrollEnd)
+    },
     openPostForm () {
       this.postFormOpen = true
       this.hidden = true
@@ -65,26 +89,19 @@ const MobilePostStatusModal = {
         this.inputActive = false
       }
     },
-    handleScroll: throttle(function () {
-      const scrollAmount = window.scrollY - this.oldScrollPos
-      const scrollingDown = scrollAmount > 0
-
-      if (scrollingDown !== this.scrollingDown) {
-        this.amountScrolled = 0
-        this.scrollingDown = scrollingDown
-        if (!scrollingDown) {
-          this.hidden = false
-        }
-      } else if (scrollingDown) {
-        this.amountScrolled += scrollAmount
-        if (this.amountScrolled > 100 && !this.hidden) {
-          this.hidden = true
-        }
+    handleScrollStart: debounce(function () {
+      if (window.scrollY > this.oldScrollPos) {
+        this.hidden = true
+      } else {
+        this.hidden = false
       }
-
       this.oldScrollPos = window.scrollY
-      this.scrollingDown = scrollingDown
-    }, 100)
+    }, 100, { leading: true, trailing: false }),
+
+    handleScrollEnd: debounce(function () {
+      this.hidden = false
+      this.oldScrollPos = window.scrollY
+    }, 100, { leading: false, trailing: true })
   }
 }
 
diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue
index 0a451c28..5db7584b 100644
--- a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue
+++ b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue
@@ -1,23 +1,31 @@
 <template>
-<div v-if="currentUser">
-  <div
-    class="post-form-modal-view modal-view"
-    v-show="postFormOpen"
-    @click="closePostForm"
-  >
-    <div class="post-form-modal-panel panel" @click.stop="">
-      <div class="panel-heading">{{$t('post_status.new_status')}}</div>
-      <PostStatusForm class="panel-body" @posted="closePostForm"/>
+  <div v-if="currentUser">
+    <div
+      v-show="postFormOpen"
+      class="post-form-modal-view modal-view"
+      @click="closePostForm"
+    >
+      <div
+        class="post-form-modal-panel panel"
+        @click.stop=""
+      >
+        <div class="panel-heading">
+          {{ $t('post_status.new_status') }}
+        </div>
+        <PostStatusForm
+          class="panel-body"
+          @posted="closePostForm"
+        />
+      </div>
     </div>
+    <button
+      class="new-status-button"
+      :class="{ 'hidden': isHidden }"
+      @click="openPostForm"
+    >
+      <i class="icon-edit" />
+    </button>
   </div>
-  <button
-    class="new-status-button"
-    :class="{ 'hidden': isHidden }"
-    @click="openPostForm"
-  >
-    <i class="icon-edit" />
-  </button>
-</div>
 </template>
 
 <script src="./mobile_post_status_modal.js"></script>
diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
new file mode 100644
index 00000000..8aadc8c5
--- /dev/null
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -0,0 +1,101 @@
+import DialogModal from '../dialog_modal/dialog_modal.vue'
+
+const FORCE_NSFW = 'mrf_tag:media-force-nsfw'
+const STRIP_MEDIA = 'mrf_tag:media-strip'
+const FORCE_UNLISTED = 'mrf_tag:force-unlisted'
+const DISABLE_REMOTE_SUBSCRIPTION = 'mrf_tag:disable-remote-subscription'
+const DISABLE_ANY_SUBSCRIPTION = 'mrf_tag:disable-any-subscription'
+const SANDBOX = 'mrf_tag:sandbox'
+const QUARANTINE = 'mrf_tag:quarantine'
+
+const ModerationTools = {
+  props: [
+    'user'
+  ],
+  data () {
+    return {
+      showDropDown: false,
+      tags: {
+        FORCE_NSFW,
+        STRIP_MEDIA,
+        FORCE_UNLISTED,
+        DISABLE_REMOTE_SUBSCRIPTION,
+        DISABLE_ANY_SUBSCRIPTION,
+        SANDBOX,
+        QUARANTINE
+      },
+      showDeleteUserDialog: false
+    }
+  },
+  components: {
+    DialogModal
+  },
+  computed: {
+    tagsSet () {
+      return new Set(this.user.tags)
+    },
+    hasTagPolicy () {
+      return this.$store.state.instance.tagPolicyAvailable
+    }
+  },
+  methods: {
+    hasTag (tagName) {
+      return this.tagsSet.has(tagName)
+    },
+    toggleTag (tag) {
+      const store = this.$store
+      if (this.tagsSet.has(tag)) {
+        store.state.api.backendInteractor.untagUser(this.user, tag).then(response => {
+          if (!response.ok) { return }
+          store.commit('untagUser', { user: this.user, tag })
+        })
+      } else {
+        store.state.api.backendInteractor.tagUser(this.user, tag).then(response => {
+          if (!response.ok) { return }
+          store.commit('tagUser', { user: this.user, tag })
+        })
+      }
+    },
+    toggleRight (right) {
+      const store = this.$store
+      if (this.user.rights[right]) {
+        store.state.api.backendInteractor.deleteRight(this.user, right).then(response => {
+          if (!response.ok) { return }
+          store.commit('updateRight', { user: this.user, right: right, value: false })
+        })
+      } else {
+        store.state.api.backendInteractor.addRight(this.user, right).then(response => {
+          if (!response.ok) { return }
+          store.commit('updateRight', { user: this.user, right: right, value: true })
+        })
+      }
+    },
+    toggleActivationStatus () {
+      const store = this.$store
+      const status = !!this.user.deactivated
+      store.state.api.backendInteractor.setActivationStatus(this.user, status).then(response => {
+        if (!response.ok) { return }
+        store.commit('updateActivationStatus', { user: this.user, status: status })
+      })
+    },
+    deleteUserDialog (show) {
+      this.showDeleteUserDialog = show
+    },
+    deleteUser () {
+      const store = this.$store
+      const user = this.user
+      const { id, name } = user
+      store.state.api.backendInteractor.deleteUser(user)
+        .then(e => {
+          this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id)
+          const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile'
+          const isTargetUser = this.$route.params.name === name || this.$route.params.id === id
+          if (isProfile && isTargetUser) {
+            window.history.back()
+          }
+        })
+    }
+  }
+}
+
+export default ModerationTools
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
new file mode 100644
index 00000000..d97ca3aa
--- /dev/null
+++ b/src/components/moderation_tools/moderation_tools.vue
@@ -0,0 +1,193 @@
+<template>
+  <div>
+    <v-popover
+      trigger="click"
+      class="moderation-tools-popover"
+      :container="false"
+      placement="bottom-end"
+      :offset="5"
+      @show="showDropDown = true"
+      @hide="showDropDown = false"
+    >
+      <div slot="popover">
+        <div class="dropdown-menu">
+          <span v-if="user.is_local">
+            <button
+              class="dropdown-item"
+              @click="toggleRight(&quot;admin&quot;)"
+            >
+              {{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }}
+            </button>
+            <button
+              class="dropdown-item"
+              @click="toggleRight(&quot;moderator&quot;)"
+            >
+              {{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }}
+            </button>
+            <div
+              role="separator"
+              class="dropdown-divider"
+            />
+          </span>
+          <button
+            class="dropdown-item"
+            @click="toggleActivationStatus()"
+          >
+            {{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
+          </button>
+          <button
+            class="dropdown-item"
+            @click="deleteUserDialog(true)"
+          >
+            {{ $t('user_card.admin_menu.delete_account') }}
+          </button>
+          <div
+            v-if="hasTagPolicy"
+            role="separator"
+            class="dropdown-divider"
+          />
+          <span v-if="hasTagPolicy">
+            <button
+              class="dropdown-item"
+              @click="toggleTag(tags.FORCE_NSFW)"
+            >
+              {{ $t('user_card.admin_menu.force_nsfw') }}
+              <span
+                class="menu-checkbox"
+                :class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
+              />
+            </button>
+            <button
+              class="dropdown-item"
+              @click="toggleTag(tags.STRIP_MEDIA)"
+            >
+              {{ $t('user_card.admin_menu.strip_media') }}
+              <span
+                class="menu-checkbox"
+                :class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
+              />
+            </button>
+            <button
+              class="dropdown-item"
+              @click="toggleTag(tags.FORCE_UNLISTED)"
+            >
+              {{ $t('user_card.admin_menu.force_unlisted') }}
+              <span
+                class="menu-checkbox"
+                :class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
+              />
+            </button>
+            <button
+              class="dropdown-item"
+              @click="toggleTag(tags.SANDBOX)"
+            >
+              {{ $t('user_card.admin_menu.sandbox') }}
+              <span
+                class="menu-checkbox"
+                :class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
+              />
+            </button>
+            <button
+              v-if="user.is_local"
+              class="dropdown-item"
+              @click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
+            >
+              {{ $t('user_card.admin_menu.disable_remote_subscription') }}
+              <span
+                class="menu-checkbox"
+                :class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"
+              />
+            </button>
+            <button
+              v-if="user.is_local"
+              class="dropdown-item"
+              @click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
+            >
+              {{ $t('user_card.admin_menu.disable_any_subscription') }}
+              <span
+                class="menu-checkbox"
+                :class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"
+              />
+            </button>
+            <button
+              v-if="user.is_local"
+              class="dropdown-item"
+              @click="toggleTag(tags.QUARANTINE)"
+            >
+              {{ $t('user_card.admin_menu.quarantine') }}
+              <span
+                class="menu-checkbox"
+                :class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"
+              />
+            </button>
+          </span>
+        </div>
+      </div>
+      <button
+        class="btn btn-default btn-block"
+        :class="{ pressed: showDropDown }"
+      >
+        {{ $t('user_card.admin_menu.moderation') }}
+      </button>
+    </v-popover>
+    <portal to="modal">
+      <DialogModal
+        v-if="showDeleteUserDialog"
+        :on-cancel="deleteUserDialog.bind(this, false)"
+      >
+        <template slot="header">
+          {{ $t('user_card.admin_menu.delete_user') }}
+        </template>
+        <p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
+        <template slot="footer">
+          <button
+            class="btn btn-default"
+            @click="deleteUserDialog(false)"
+          >
+            {{ $t('general.cancel') }}
+          </button>
+          <button
+            class="btn btn-default danger"
+            @click="deleteUser()"
+          >
+            {{ $t('user_card.admin_menu.delete_user') }}
+          </button>
+        </template>
+      </DialogModal>
+    </portal>
+  </div>
+</template>
+
+<script src="./moderation_tools.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+@import '../popper/popper.scss';
+
+.menu-checkbox {
+  float: right;
+  min-width: 22px;
+  max-width: 22px;
+  min-height: 22px;
+  max-height: 22px;
+  line-height: 22px;
+  text-align: center;
+  border-radius: 0px;
+  background-color: $fallback--fg;
+  background-color: var(--input, $fallback--fg);
+  box-shadow: 0px 0px 2px black inset;
+  box-shadow: var(--inputShadow);
+
+  &.menu-checkbox-checked::after {
+    content: '✔';
+  }
+}
+
+.moderation-tools-popover {
+  height: 100%;
+  .trigger {
+    display: flex !important;
+    height: 100%;
+  }
+}
+</style>
diff --git a/src/components/mute_card/mute_card.vue b/src/components/mute_card/mute_card.vue
index a4edff03..9611fb82 100644
--- a/src/components/mute_card/mute_card.vue
+++ b/src/components/mute_card/mute_card.vue
@@ -1,7 +1,12 @@
 <template>
   <basic-user-card :user="user">
     <div class="mute-card-content-container">
-      <button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted">
+      <button
+        v-if="muted"
+        class="btn btn-default"
+        :disabled="progress"
+        @click="unmuteUser"
+      >
         <template v-if="progress">
           {{ $t('user_card.unmute_progress') }}
         </template>
@@ -9,7 +14,12 @@
           {{ $t('user_card.unmute') }}
         </template>
       </button>
-      <button class="btn btn-default" @click="muteUser" :disabled="progress" v-else>
+      <button
+        v-else
+        class="btn btn-default"
+        :disabled="progress"
+        @click="muteUser"
+      >
         <template v-if="progress">
           {{ $t('user_card.mute_progress') }}
         </template>
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 7a7212fb..614fadf4 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -2,26 +2,29 @@
   <div class="nav-panel">
     <div class="panel panel-default">
       <ul>
-        <li v-if='currentUser'>
+        <li v-if="currentUser">
           <router-link :to="{ name: 'friends' }">
             {{ $t("nav.timeline") }}
           </router-link>
         </li>
-        <li v-if='currentUser'>
-          <router-link :to="{ name: 'mentions', params: { username: currentUser.screen_name } }">
-            {{ $t("nav.mentions") }}
+        <li v-if="currentUser">
+          <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
+            {{ $t("nav.interactions") }}
           </router-link>
         </li>
-        <li v-if='currentUser'>
+        <li v-if="currentUser">
           <router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
             {{ $t("nav.dms") }}
           </router-link>
         </li>
-        <li v-if='currentUser && currentUser.locked'>
+        <li v-if="currentUser && currentUser.locked">
           <router-link :to="{ name: 'friend-requests' }">
-            {{ $t("nav.friend_requests")}}
-            <span v-if='followRequestCount > 0' class="badge follow-request-count">
-              {{followRequestCount}}
+            {{ $t("nav.friend_requests") }}
+            <span
+              v-if="followRequestCount > 0"
+              class="badge follow-request-count"
+            >
+              {{ followRequestCount }}
             </span>
           </router-link>
         </li>
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 42a48f3f..896c6d52 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -1,6 +1,7 @@
 import Status from '../status/status.vue'
 import UserAvatar from '../user_avatar/user_avatar.vue'
 import UserCard from '../user_card/user_card.vue'
+import Timeago from '../timeago/timeago.vue'
 import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 
@@ -13,7 +14,10 @@ const Notification = {
   },
   props: [ 'notification' ],
   components: {
-    Status, UserAvatar, UserCard
+    Status,
+    UserAvatar,
+    UserCard,
+    Timeago
   },
   methods: {
     toggleUserExpanded () {
@@ -21,25 +25,28 @@ const Notification = {
     },
     userProfileLink (user) {
       return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
+    },
+    getUser (notification) {
+      return this.$store.state.users.usersObject[notification.from_profile.id]
     }
   },
   computed: {
     userClass () {
-      return highlightClass(this.notification.action.user)
+      return highlightClass(this.notification.from_profile)
     },
     userStyle () {
       const highlight = this.$store.state.config.highlight
-      const user = this.notification.action.user
+      const user = this.notification.from_profile
       return highlightStyle(highlight[user.screen_name])
     },
     userInStore () {
-      return this.$store.getters.findUser(this.notification.action.user.id)
+      return this.$store.getters.findUser(this.notification.from_profile.id)
     },
     user () {
       if (this.userInStore) {
         return this.userInStore
       }
-      return {}
+      return this.notification.from_profile
     }
   }
 }
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 8f532747..bafcd026 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -1,41 +1,106 @@
 <template>
-  <status v-if="notification.type === 'mention'" :compact="true" :statusoid="notification.status"></status>
-  <div class="non-mention" :class="[userClass, { highlighted: userStyle }]" :style="[ userStyle ]" v-else>
-    <a class='avatar-container' :href="notification.action.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded">
-      <UserAvatar :compact="true" :betterShadow="betterShadow" :src="notification.action.user.profile_image_url_original"/>
+  <status
+    v-if="notification.type === 'mention'"
+    :compact="true"
+    :statusoid="notification.status"
+  />
+  <div
+    v-else
+    class="non-mention"
+    :class="[userClass, { highlighted: userStyle }]"
+    :style="[ userStyle ]"
+  >
+    <a
+      class="avatar-container"
+      :href="notification.from_profile.statusnet_profile_url"
+      @click.stop.prevent.capture="toggleUserExpanded"
+    >
+      <UserAvatar
+        :compact="true"
+        :better-shadow="betterShadow"
+        :user="notification.from_profile"
+      />
     </a>
-    <div class='notification-right'>
-      <UserCard :user="user" :rounded="true" :bordered="true" v-if="userExpanded"/>
+    <div class="notification-right">
+      <UserCard
+        v-if="userExpanded"
+        :user="getUser(notification)"
+        :rounded="true"
+        :bordered="true"
+      />
       <span class="notification-details">
         <div class="name-and-action">
-          <span class="username" v-if="!!notification.action.user.name_html" :title="'@'+notification.action.user.screen_name" v-html="notification.action.user.name_html"></span>
-          <span class="username" v-else :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
+          <!-- eslint-disable vue/no-v-html -->
+          <span
+            v-if="!!notification.from_profile.name_html"
+            class="username"
+            :title="'@'+notification.from_profile.screen_name"
+            v-html="notification.from_profile.name_html"
+          />
+          <!-- eslint-enable vue/no-v-html -->
+          <span
+            v-else
+            class="username"
+            :title="'@'+notification.from_profile.screen_name"
+          >{{ notification.from_profile.name }}</span>
           <span v-if="notification.type === 'like'">
-            <i class="fa icon-star lit"></i>
-            <small>{{$t('notifications.favorited_you')}}</small>
+            <i class="fa icon-star lit" />
+            <small>{{ $t('notifications.favorited_you') }}</small>
           </span>
           <span v-if="notification.type === 'repeat'">
-            <i class="fa icon-retweet lit" :title="$t('tool_tip.repeat')"></i>
-            <small>{{$t('notifications.repeated_you')}}</small>
+            <i
+              class="fa icon-retweet lit"
+              :title="$t('tool_tip.repeat')"
+            />
+            <small>{{ $t('notifications.repeated_you') }}</small>
           </span>
           <span v-if="notification.type === 'follow'">
-            <i class="fa icon-user-plus lit"></i>
-            <small>{{$t('notifications.followed_you')}}</small>
+            <i class="fa icon-user-plus lit" />
+            <small>{{ $t('notifications.followed_you') }}</small>
           </span>
         </div>
-        <div class="timeago">
-          <router-link v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }" class="faint-link">
-            <timeago :since="notification.action.created_at" :auto-update="240"></timeago>
+        <div
+          v-if="notification.type === 'follow'"
+          class="timeago"
+        >
+          <span class="faint">
+            <Timeago
+              :time="notification.created_at"
+              :auto-update="240"
+            />
+          </span>
+        </div>
+        <div
+          v-else
+          class="timeago"
+        >
+          <router-link
+            v-if="notification.status"
+            :to="{ name: 'conversation', params: { id: notification.status.id } }"
+            class="faint-link"
+          >
+            <Timeago
+              :time="notification.created_at"
+              :auto-update="240"
+            />
           </router-link>
         </div>
       </span>
-      <div class="follow-text" v-if="notification.type === 'follow'">
-        <router-link :to="userProfileLink(notification.action.user)">
-          @{{notification.action.user.screen_name}}
+      <div
+        v-if="notification.type === 'follow'"
+        class="follow-text"
+      >
+        <router-link :to="userProfileLink(notification.from_profile)">
+          @{{ notification.from_profile.screen_name }}
         </router-link>
       </div>
       <template v-else>
-        <status class="faint" :compact="true" :statusoid="notification.status" :noHeading="true"></status>
+        <status
+          class="faint"
+          :compact="true"
+          :statusoid="notification.action"
+          :no-heading="true"
+        />
       </template>
     </div>
   </div>
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index d3db4b29..6c4054fd 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -7,15 +7,14 @@ import {
 } from '../../services/notification_utils/notification_utils.js'
 
 const Notifications = {
-  props: [
-    'noHeading'
-  ],
-  created () {
-    const store = this.$store
-    const credentials = store.state.users.currentUser.credentials
-
-    const fetcherId = notificationsFetcher.startFetching({ store, credentials })
-    this.$store.commit('setNotificationFetcher', { fetcherId })
+  props: {
+    // Disables display of panel header
+    noHeading: Boolean,
+    // Disables panel styles, unread mark, potentially other notification-related actions
+    // meant for "Interactions" timeline
+    minimalMode: Boolean,
+    // Custom filter mode, an array of strings, possible values 'mention', 'repeat', 'like', 'follow', used to override global filter for use in "Interactions" timeline
+    filterMode: Array
   },
   data () {
     return {
@@ -23,6 +22,9 @@ const Notifications = {
     }
   },
   computed: {
+    mainClass () {
+      return this.minimalMode ? '' : 'panel panel-default'
+    },
     notifications () {
       return notificationsFromStore(this.$store)
     },
@@ -33,7 +35,7 @@ const Notifications = {
       return unseenNotificationsFromStore(this.$store)
     },
     visibleNotifications () {
-      return visibleNotificationsFromStore(this.$store)
+      return visibleNotificationsFromStore(this.$store, this.filterMode)
     },
     unseenCount () {
       return this.unseenNotifications.length
@@ -56,9 +58,13 @@ const Notifications = {
   },
   methods: {
     markAsSeen () {
-      this.$store.dispatch('markNotificationsAsSeen', this.visibleNotifications)
+      this.$store.dispatch('markNotificationsAsSeen')
     },
     fetchOlderNotifications () {
+      if (this.loading) {
+        return
+      }
+
       const store = this.$store
       const credentials = store.state.users.currentUser.credentials
       store.commit('setNotificationsLoading', { value: true })
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index c0b458cc..622d12f4 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -1,8 +1,10 @@
 @import '../../_variables.scss';
 
 .notifications {
-  // a bit of a hack to allow scrolling below notifications
-  padding-bottom: 15em;
+  &:not(.minimal) {
+    // a bit of a hack to allow scrolling below notifications
+    padding-bottom: 15em;
+  }
 
   .loadmore-error {
     color: $fallback--text;
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 634a03ac..c42c35e6 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -1,31 +1,67 @@
 <template>
-  <div class="notifications">
-    <div class="panel panel-default">
-      <div v-if="!noHeading" class="panel-heading">
+  <div
+    :class="{ minimal: minimalMode }"
+    class="notifications"
+  >
+    <div :class="mainClass">
+      <div
+        v-if="!noHeading"
+        class="panel-heading"
+      >
         <div class="title">
-          {{$t('notifications.notifications')}}
-          <span class="badge badge-notification unseen-count" v-if="unseenCount">{{unseenCount}}</span>
+          {{ $t('notifications.notifications') }}
+          <span
+            v-if="unseenCount"
+            class="badge badge-notification unseen-count"
+          >{{ unseenCount }}</span>
         </div>
-        <div @click.prevent class="loadmore-error alert error" v-if="error">
-          {{$t('timeline.error_fetching')}}
+        <div
+          v-if="error"
+          class="loadmore-error alert error"
+          @click.prevent
+        >
+          {{ $t('timeline.error_fetching') }}
         </div>
-        <button v-if="unseenCount" @click.prevent="markAsSeen" class="read-button">{{$t('notifications.read')}}</button>
+        <button
+          v-if="unseenCount"
+          class="read-button"
+          @click.prevent="markAsSeen"
+        >
+          {{ $t('notifications.read') }}
+        </button>
       </div>
       <div class="panel-body">
-        <div v-for="notification in visibleNotifications" :key="notification.action.id" class="notification" :class='{"unseen": !notification.seen}'>
-          <div class="notification-overlay"></div>
-          <notification :notification="notification"></notification>
+        <div
+          v-for="notification in visibleNotifications"
+          :key="notification.id"
+          class="notification"
+          :class="{&quot;unseen&quot;: !minimalMode && !notification.seen}"
+        >
+          <div class="notification-overlay" />
+          <notification :notification="notification" />
         </div>
       </div>
       <div class="panel-footer">
-        <div v-if="bottomedOut" class="new-status-notification text-center panel-footer faint">
-          {{$t('notifications.no_more_notifications')}}
+        <div
+          v-if="bottomedOut"
+          class="new-status-notification text-center panel-footer faint"
+        >
+          {{ $t('notifications.no_more_notifications') }}
         </div>
-        <a v-else-if="!loading" href="#" v-on:click.prevent="fetchOlderNotifications()">
-          <div class="new-status-notification text-center panel-footer">{{$t('notifications.load_older')}}</div>
+        <a
+          v-else-if="!loading"
+          href="#"
+          @click.prevent="fetchOlderNotifications()"
+        >
+          <div class="new-status-notification text-center panel-footer">
+            {{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
+          </div>
         </a>
-        <div v-else class="new-status-notification text-center panel-footer">
-          <i class="icon-spin3 animate-spin"/>
+        <div
+          v-else
+          class="new-status-notification text-center panel-footer"
+        >
+          <i class="icon-spin3 animate-spin" />
         </div>
       </div>
     </div>
diff --git a/src/components/oauth_callback/oauth_callback.js b/src/components/oauth_callback/oauth_callback.js
index e3d45ee1..a3c7b7f9 100644
--- a/src/components/oauth_callback/oauth_callback.js
+++ b/src/components/oauth_callback/oauth_callback.js
@@ -4,14 +4,17 @@ const oac = {
   props: ['code'],
   mounted () {
     if (this.code) {
+      const { clientId, clientSecret } = this.$store.state.oauth
+
       oauth.getToken({
-        app: this.$store.state.oauth,
+        clientId,
+        clientSecret,
         instance: this.$store.state.instance.server,
         code: this.code
       }).then((result) => {
         this.$store.commit('setToken', result.access_token)
         this.$store.dispatch('loginUser', result.access_token)
-        this.$router.push({name: 'friends'})
+        this.$router.push({ name: 'friends' })
       })
     }
   }
diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue
index 3926915b..c677f18c 100644
--- a/src/components/opacity_input/opacity_input.vue
+++ b/src/components/opacity_input/opacity_input.vue
@@ -1,27 +1,39 @@
 <template>
-<div class="opacity-control style-control" :class="{ disabled: !present || disabled }">
-  <label :for="name" class="label">
-    {{$t('settings.style.common.opacity')}}
-  </label>
-  <input
-    v-if="typeof fallback !== 'undefined'"
-    class="opt exclude-disabled"
-    :id="name + '-o'"
-    type="checkbox"
-    :checked="present"
-    @input="$emit('input', !present ? fallback : undefined)">
-  <label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label>
-  <input
-    :id="name"
-    class="input-number"
-    type="number"
-    :value="value || fallback"
-    :disabled="!present || disabled"
-    @input="$emit('input', $event.target.value)"
-    max="1"
-    min="0"
-    step=".05">
-</div>
+  <div
+    class="opacity-control style-control"
+    :class="{ disabled: !present || disabled }"
+  >
+    <label
+      :for="name"
+      class="label"
+    >
+      {{ $t('settings.style.common.opacity') }}
+    </label>
+    <input
+      v-if="typeof fallback !== 'undefined'"
+      :id="name + '-o'"
+      class="opt exclude-disabled"
+      type="checkbox"
+      :checked="present"
+      @input="$emit('input', !present ? fallback : undefined)"
+    >
+    <label
+      v-if="typeof fallback !== 'undefined'"
+      class="opt-l"
+      :for="name + '-o'"
+    />
+    <input
+      :id="name"
+      class="input-number"
+      type="number"
+      :value="value || fallback"
+      :disabled="!present || disabled"
+      max="1"
+      min="0"
+      step=".05"
+      @input="$emit('input', $event.target.value)"
+    >
+  </div>
 </template>
 
 <script>
diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js
new file mode 100644
index 00000000..98db5582
--- /dev/null
+++ b/src/components/poll/poll.js
@@ -0,0 +1,112 @@
+import Timeago from '../timeago/timeago.vue'
+import { forEach, map } from 'lodash'
+
+export default {
+  name: 'Poll',
+  props: ['basePoll'],
+  components: { Timeago },
+  data () {
+    return {
+      loading: false,
+      choices: []
+    }
+  },
+  created () {
+    if (!this.$store.state.polls.pollsObject[this.pollId]) {
+      this.$store.dispatch('mergeOrAddPoll', this.basePoll)
+    }
+    this.$store.dispatch('trackPoll', this.pollId)
+  },
+  destroyed () {
+    this.$store.dispatch('untrackPoll', this.pollId)
+  },
+  computed: {
+    pollId () {
+      return this.basePoll.id
+    },
+    poll () {
+      const storePoll = this.$store.state.polls.pollsObject[this.pollId]
+      return storePoll || {}
+    },
+    options () {
+      return (this.poll && this.poll.options) || []
+    },
+    expiresAt () {
+      return (this.poll && this.poll.expires_at) || 0
+    },
+    expired () {
+      return (this.poll && this.poll.expired) || false
+    },
+    loggedIn () {
+      return this.$store.state.users.currentUser
+    },
+    showResults () {
+      return this.poll.voted || this.expired || !this.loggedIn
+    },
+    totalVotesCount () {
+      return this.poll.votes_count
+    },
+    containerClass () {
+      return {
+        loading: this.loading
+      }
+    },
+    choiceIndices () {
+      // Convert array of booleans into an array of indices of the
+      // items that were 'true', so [true, false, false, true] becomes
+      // [0, 3].
+      return this.choices
+        .map((entry, index) => entry && index)
+        .filter(value => typeof value === 'number')
+    },
+    isDisabled () {
+      const noChoice = this.choiceIndices.length === 0
+      return this.loading || noChoice
+    }
+  },
+  methods: {
+    percentageForOption (count) {
+      return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100)
+    },
+    resultTitle (option) {
+      return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
+    },
+    fetchPoll () {
+      this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
+    },
+    activateOption (index) {
+      // forgive me father: doing checking the radio/checkboxes
+      // in code because of customized input elements need either
+      // a) an extra element for the actual graphic, or b) use a
+      // pseudo element for the label. We use b) which mandates
+      // using "for" and "id" matching which isn't nice when the
+      // same poll appears multiple times on the site (notifs and
+      // timeline for example). With code we can make sure it just
+      // works without altering the pseudo element implementation.
+      const allElements = this.$el.querySelectorAll('input')
+      const clickedElement = this.$el.querySelector(`input[value="${index}"]`)
+      if (this.poll.multiple) {
+        // Checkboxes, toggle only the clicked one
+        clickedElement.checked = !clickedElement.checked
+      } else {
+        // Radio button, uncheck everything and check the clicked one
+        forEach(allElements, element => { element.checked = false })
+        clickedElement.checked = true
+      }
+      this.choices = map(allElements, e => e.checked)
+    },
+    optionId (index) {
+      return `poll${this.poll.id}-${index}`
+    },
+    vote () {
+      if (this.choiceIndices.length === 0) return
+      this.loading = true
+      this.$store.dispatch(
+        'votePoll',
+        { id: this.statusId, pollId: this.poll.id, choices: this.choiceIndices }
+      ).then(poll => {
+        this.loading = false
+      })
+    }
+  }
+}
diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue
new file mode 100644
index 00000000..db8e33b3
--- /dev/null
+++ b/src/components/poll/poll.vue
@@ -0,0 +1,134 @@
+<template>
+  <div
+    class="poll"
+    :class="containerClass"
+  >
+    <div
+      v-for="(option, index) in options"
+      :key="index"
+      class="poll-option"
+    >
+      <div
+        v-if="showResults"
+        :title="resultTitle(option)"
+        class="option-result"
+      >
+        <div class="option-result-label">
+          <span class="result-percentage">
+            {{ percentageForOption(option.votes_count) }}%
+          </span>
+          <span>{{ option.title }}</span>
+        </div>
+        <div
+          class="result-fill"
+          :style="{ 'width': `${percentageForOption(option.votes_count)}%` }"
+        />
+      </div>
+      <div
+        v-else
+        @click="activateOption(index)"
+      >
+        <input
+          v-if="poll.multiple"
+          type="checkbox"
+          :disabled="loading"
+          :value="index"
+        >
+        <input
+          v-else
+          type="radio"
+          :disabled="loading"
+          :value="index"
+        >
+        <label class="option-vote">
+          <div>{{ option.title }}</div>
+        </label>
+      </div>
+    </div>
+    <div class="footer faint">
+      <button
+        v-if="!showResults"
+        class="btn btn-default poll-vote-button"
+        type="button"
+        :disabled="isDisabled"
+        @click="vote"
+      >
+        {{ $t('polls.vote') }}
+      </button>
+      <div class="total">
+        {{ totalVotesCount }} {{ $t("polls.votes") }}&nbsp;·&nbsp;
+      </div>
+      <i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
+        <Timeago
+          :time="expiresAt"
+          :auto-update="60"
+          :now-threshold="0"
+        />
+      </i18n>
+    </div>
+  </div>
+</template>
+
+<script src="./poll.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.poll {
+  .votes {
+    display: flex;
+    flex-direction: column;
+    margin: 0 0 0.5em;
+  }
+  .poll-option {
+    margin: 0.75em 0.5em;
+  }
+  .option-result {
+    height: 100%;
+    display: flex;
+    flex-direction: row;
+    position: relative;
+    color: $fallback--lightText;
+    color: var(--lightText, $fallback--lightText);
+  }
+  .option-result-label {
+    display: flex;
+    align-items: center;
+    padding: 0.1em 0.25em;
+    z-index: 1;
+  }
+  .result-percentage {
+    width: 3.5em;
+    flex-shrink: 0;
+  }
+  .result-fill {
+    height: 100%;
+    position: absolute;
+    background-color: $fallback--lightBg;
+    background-color: var(--linkBg, $fallback--lightBg);
+    border-radius: $fallback--panelRadius;
+    border-radius: var(--panelRadius, $fallback--panelRadius);
+    top: 0;
+    left: 0;
+    transition: width 0.5s;
+  }
+  .option-vote {
+    display: flex;
+    align-items: center;
+  }
+  input {
+    width: 3.5em;
+  }
+  .footer {
+    display: flex;
+    align-items: center;
+  }
+  &.loading * {
+    cursor: progress;
+  }
+  .poll-vote-button {
+    padding: 0 0.5em;
+    margin-right: 0.5em;
+  }
+}
+</style>
diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js
new file mode 100644
index 00000000..c0c1ccf7
--- /dev/null
+++ b/src/components/poll/poll_form.js
@@ -0,0 +1,121 @@
+import * as DateUtils from 'src/services/date_utils/date_utils.js'
+import { uniq } from 'lodash'
+
+export default {
+  name: 'PollForm',
+  props: ['visible'],
+  data: () => ({
+    pollType: 'single',
+    options: ['', ''],
+    expiryAmount: 10,
+    expiryUnit: 'minutes'
+  }),
+  computed: {
+    pollLimits () {
+      return this.$store.state.instance.pollLimits
+    },
+    maxOptions () {
+      return this.pollLimits.max_options
+    },
+    maxLength () {
+      return this.pollLimits.max_option_chars
+    },
+    expiryUnits () {
+      const allUnits = ['minutes', 'hours', 'days']
+      const expiry = this.convertExpiryFromUnit
+      return allUnits.filter(
+        unit => this.pollLimits.max_expiration >= expiry(unit, 1)
+      )
+    },
+    minExpirationInCurrentUnit () {
+      return Math.ceil(
+        this.convertExpiryToUnit(
+          this.expiryUnit,
+          this.pollLimits.min_expiration
+        )
+      )
+    },
+    maxExpirationInCurrentUnit () {
+      return Math.floor(
+        this.convertExpiryToUnit(
+          this.expiryUnit,
+          this.pollLimits.max_expiration
+        )
+      )
+    }
+  },
+  methods: {
+    clear () {
+      this.pollType = 'single'
+      this.options = ['', '']
+      this.expiryAmount = 10
+      this.expiryUnit = 'minutes'
+    },
+    nextOption (index) {
+      const element = this.$el.querySelector(`#poll-${index + 1}`)
+      if (element) {
+        element.focus()
+      } else {
+        // Try adding an option and try focusing on it
+        const addedOption = this.addOption()
+        if (addedOption) {
+          this.$nextTick(function () {
+            this.nextOption(index)
+          })
+        }
+      }
+    },
+    addOption () {
+      if (this.options.length < this.maxOptions) {
+        this.options.push('')
+        return true
+      }
+      return false
+    },
+    deleteOption (index, event) {
+      if (this.options.length > 2) {
+        this.options.splice(index, 1)
+      }
+    },
+    convertExpiryToUnit (unit, amount) {
+      // Note: we want seconds and not milliseconds
+      switch (unit) {
+        case 'minutes': return (1000 * amount) / DateUtils.MINUTE
+        case 'hours': return (1000 * amount) / DateUtils.HOUR
+        case 'days': return (1000 * amount) / DateUtils.DAY
+      }
+    },
+    convertExpiryFromUnit (unit, amount) {
+      // Note: we want seconds and not milliseconds
+      switch (unit) {
+        case 'minutes': return 0.001 * amount * DateUtils.MINUTE
+        case 'hours': return 0.001 * amount * DateUtils.HOUR
+        case 'days': return 0.001 * amount * DateUtils.DAY
+      }
+    },
+    expiryAmountChange () {
+      this.expiryAmount =
+        Math.max(this.minExpirationInCurrentUnit, this.expiryAmount)
+      this.expiryAmount =
+        Math.min(this.maxExpirationInCurrentUnit, this.expiryAmount)
+      this.updatePollToParent()
+    },
+    updatePollToParent () {
+      const expiresIn = this.convertExpiryFromUnit(
+        this.expiryUnit,
+        this.expiryAmount
+      )
+
+      const options = uniq(this.options.filter(option => option !== ''))
+      if (options.length < 2) {
+        this.$emit('update-poll', { error: this.$t('polls.not_enough_options') })
+        return
+      }
+      this.$emit('update-poll', {
+        options,
+        multiple: this.pollType === 'multiple',
+        expiresIn
+      })
+    }
+  }
+}
diff --git a/src/components/poll/poll_form.vue b/src/components/poll/poll_form.vue
new file mode 100644
index 00000000..d53f3837
--- /dev/null
+++ b/src/components/poll/poll_form.vue
@@ -0,0 +1,163 @@
+<template>
+  <div
+    v-if="visible"
+    class="poll-form"
+  >
+    <div
+      v-for="(option, index) in options"
+      :key="index"
+      class="poll-option"
+    >
+      <div class="input-container">
+        <input
+          :id="`poll-${index}`"
+          v-model="options[index]"
+          class="poll-option-input"
+          type="text"
+          :placeholder="$t('polls.option')"
+          :maxlength="maxLength"
+          @change="updatePollToParent"
+          @keydown.enter.stop.prevent="nextOption(index)"
+        >
+      </div>
+      <div
+        v-if="options.length > 2"
+        class="icon-container"
+      >
+        <i
+          class="icon-cancel"
+          @click="deleteOption(index)"
+        />
+      </div>
+    </div>
+    <a
+      v-if="options.length < maxOptions"
+      class="add-option faint"
+      @click="addOption"
+    >
+      <i class="icon-plus" />
+      {{ $t("polls.add_option") }}
+    </a>
+    <div class="poll-type-expiry">
+      <div
+        class="poll-type"
+        :title="$t('polls.type')"
+      >
+        <label
+          for="poll-type-selector"
+          class="select"
+        >
+          <select
+            v-model="pollType"
+            class="select"
+            @change="updatePollToParent"
+          >
+            <option value="single">{{ $t('polls.single_choice') }}</option>
+            <option value="multiple">{{ $t('polls.multiple_choices') }}</option>
+          </select>
+          <i class="icon-down-open" />
+        </label>
+      </div>
+      <div
+        class="poll-expiry"
+        :title="$t('polls.expiry')"
+      >
+        <input
+          v-model="expiryAmount"
+          type="number"
+          class="expiry-amount hide-number-spinner"
+          :min="minExpirationInCurrentUnit"
+          :max="maxExpirationInCurrentUnit"
+          @change="expiryAmountChange"
+        >
+        <label class="expiry-unit select">
+          <select
+            v-model="expiryUnit"
+            @change="expiryAmountChange"
+          >
+            <option
+              v-for="unit in expiryUnits"
+              :key="unit"
+              :value="unit"
+            >
+              {{ $t(`time.${unit}_short`, ['']) }}
+            </option>
+          </select>
+          <i class="icon-down-open" />
+        </label>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script src="./poll_form.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.poll-form {
+  display: flex;
+  flex-direction: column;
+  padding: 0 0.5em 0.5em;
+
+  .add-option {
+    align-self: flex-start;
+    padding-top: 0.25em;
+    cursor: pointer;
+  }
+
+  .poll-option {
+    display: flex;
+    align-items: baseline;
+    justify-content: space-between;
+    margin-bottom: 0.25em;
+  }
+
+  .input-container {
+    width: 100%;
+    input {
+      // Hack: dodge the floating X icon
+      padding-right: 2.5em;
+      width: 100%;
+    }
+  }
+
+  .icon-container {
+    // Hack: Move the icon over the input box
+    width: 2em;
+    margin-left: -2em;
+    z-index: 1;
+  }
+
+  .poll-type-expiry {
+    margin-top: 0.5em;
+    display: flex;
+    width: 100%;
+  }
+
+  .poll-type {
+    margin-right: 0.75em;
+    flex: 1 1 60%;
+    .select {
+      border: none;
+      box-shadow: none;
+      background-color: transparent;
+    }
+  }
+
+  .poll-expiry {
+    display: flex;
+
+    .expiry-amount {
+      width: 3em;
+      text-align: right;
+    }
+
+    .expiry-unit {
+      border: none;
+      box-shadow: none;
+      background-color: transparent;
+    }
+  }
+}
+</style>
diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss
new file mode 100644
index 00000000..279b01be
--- /dev/null
+++ b/src/components/popper/popper.scss
@@ -0,0 +1,148 @@
+@import '../../_variables.scss';
+
+.tooltip.popover {
+  z-index: 8;
+
+  .popover-inner {
+    box-shadow: 1px 1px 4px rgba(0,0,0,.6);
+    box-shadow: var(--panelShadow);
+    border-radius: $fallback--btnRadius;
+    border-radius: var(--btnRadius, $fallback--btnRadius);
+    background-color: $fallback--bg;
+    background-color: var(--bg, $fallback--bg);
+  }
+
+  .popover-arrow {
+    width: 0;
+    height: 0;
+    border-style: solid;
+    position: absolute;
+    margin: 5px;
+    border-color: $fallback--bg;
+    border-color: var(--bg, $fallback--bg);
+    z-index: 1;
+  }
+
+  &[x-placement^="top"] {
+    margin-bottom: 5px;
+
+    .popover-arrow {
+      border-width: 5px 5px 0 5px;
+      border-left-color: transparent !important;
+      border-right-color: transparent !important;
+      border-bottom-color: transparent !important;
+      bottom: -5px;
+      left: calc(50% - 5px);
+      margin-top: 0;
+      margin-bottom: 0;
+    }
+  }
+
+  &[x-placement^="bottom"] {
+    margin-top: 5px;
+
+    .popover-arrow {
+      border-width: 0 5px 5px 5px;
+      border-left-color: transparent !important;
+      border-right-color: transparent !important;
+      border-top-color: transparent !important;
+      top: -5px;
+      left: calc(50% - 5px);
+      margin-top: 0;
+      margin-bottom: 0;
+    }
+  }
+
+  &[x-placement^="right"] {
+    margin-left: 5px;
+
+    .popover-arrow {
+      border-width: 5px 5px 5px 0;
+      border-left-color: transparent !important;
+      border-top-color: transparent !important;
+      border-bottom-color: transparent !important;
+      left: -5px;
+      top: calc(50% - 5px);
+      margin-left: 0;
+      margin-right: 0;
+    }
+  }
+
+  &[x-placement^="left"] {
+    margin-right: 5px;
+
+    .popover-arrow {
+      border-width: 5px 0 5px 5px;
+      border-top-color: transparent !important;
+      border-right-color: transparent !important;
+      border-bottom-color: transparent !important;
+      right: -5px;
+      top: calc(50% - 5px);
+      margin-left: 0;
+      margin-right: 0;
+    }
+  }
+
+  &[aria-hidden='true'] {
+    visibility: hidden;
+    opacity: 0;
+    transition: opacity .15s, visibility .15s;
+  }
+
+  &[aria-hidden='false'] {
+    visibility: visible;
+    opacity: 1;
+    transition: opacity .15s;
+  }
+}
+
+.dropdown-menu {
+  display: block;
+  padding: .5rem 0;
+  font-size: 1rem;
+  text-align: left;
+  list-style: none;
+  max-width: 100vw;
+  z-index: 10;
+
+  .dropdown-divider {
+    height: 0;
+    margin: .5rem 0;
+    overflow: hidden;
+    border-top: 1px solid $fallback--border;
+    border-top: 1px solid var(--border, $fallback--border);
+  }
+
+  .dropdown-item {
+    line-height: 21px;
+    margin-right: 5px;
+    overflow: auto;
+    display: block;
+    padding: .25rem 1.0rem .25rem 1.5rem;
+    clear: both;
+    font-weight: 400;
+    text-align: inherit;
+    white-space: normal;
+    border: none;
+    border-radius: 0px;
+    background-color: transparent;
+    box-shadow: none;
+    width: 100%;
+    height: 100%;
+
+    &-icon {
+      padding-left: 0.5rem;
+
+      i {
+        margin-right: 0.25rem;
+      }
+    }
+
+    &:hover {
+      // TODO: improve the look on breeze themes
+      background-color: $fallback--fg;
+      background-color: var(--btn, $fallback--fg);
+      box-shadow: none;
+    }
+  }
+}
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 8b0031de..5aaac8e6 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -3,17 +3,19 @@ import MediaUpload from '../media_upload/media_upload.vue'
 import ScopeSelector from '../scope_selector/scope_selector.vue'
 import EmojiInput from '../emoji-input/emoji-input.vue'
 import EmojiSelector from '../emoji-selector/emoji-selector.vue'
+import PollForm from '../poll/poll_form.vue'
+import StickerPicker from '../sticker_picker/sticker_picker.vue'
 import fileTypeService from '../../services/file_type/file_type.service.js'
-import Completion from '../../services/completion/completion.js'
-import { take, filter, reject, map, uniqBy } from 'lodash'
+import { reject, map, uniqBy } from 'lodash'
+import suggestor from '../emoji-input/suggestor.js'
 
-const buildMentionsString = ({user, attentions}, currentUser) => {
+const buildMentionsString = ({ user, attentions }, currentUser) => {
   let allAttentions = [...attentions]
 
   allAttentions.unshift(user)
 
   allAttentions = uniqBy(allAttentions, 'id')
-  allAttentions = reject(allAttentions, {id: currentUser.id})
+  allAttentions = reject(allAttentions, { id: currentUser.id })
 
   let mentions = map(allAttentions, (attention) => {
     return `@${attention.screen_name}`
@@ -32,9 +34,11 @@ const PostStatusForm = {
   ],
   components: {
     MediaUpload,
-    ScopeSelector,
     EmojiInput,
-    EmojiSelector
+    PollForm,
+    StickerPicker,
+    EmojiSelector,
+    ScopeSelector
   },
   mounted () {
     this.resize(this.$refs.textarea)
@@ -50,17 +54,17 @@ const PostStatusForm = {
     let statusText = preset || ''
 
     const scopeCopy = typeof this.$store.state.config.scopeCopy === 'undefined'
-          ? this.$store.state.instance.scopeCopy
-          : this.$store.state.config.scopeCopy
+      ? this.$store.state.instance.scopeCopy
+      : this.$store.state.config.scopeCopy
 
     if (this.replyTo) {
       const currentUser = this.$store.state.users.currentUser
       statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
     }
 
-    const scope = (this.copyMessageScope && scopeCopy || this.copyMessageScope === 'direct')
-          ? this.copyMessageScope
-          : this.$store.state.users.currentUser.default_scope
+    const scope = ((this.copyMessageScope && scopeCopy) || this.copyMessageScope === 'direct')
+      ? this.copyMessageScope
+      : this.$store.state.users.currentUser.default_scope
 
     const contentType = typeof this.$store.state.config.postContentType === 'undefined'
       ? this.$store.state.instance.postContentType
@@ -77,57 +81,16 @@ const PostStatusForm = {
         status: statusText,
         nsfw: false,
         files: [],
+        poll: {},
         visibility: scope,
         contentType
       },
-      caret: 0
+      caret: 0,
+      pollFormVisible: false,
+      stickerPickerVisible: false
     }
   },
   computed: {
-    candidates () {
-      const firstchar = this.textAtCaret.charAt(0)
-      if (firstchar === '@') {
-        const query = this.textAtCaret.slice(1).toUpperCase()
-        const matchedUsers = filter(this.users, (user) => {
-          return user.screen_name.toUpperCase().startsWith(query) ||
-            user.name && user.name.toUpperCase().startsWith(query)
-        })
-        if (matchedUsers.length <= 0) {
-          return false
-        }
-        // eslint-disable-next-line camelcase
-        return map(take(matchedUsers, 5), ({screen_name, name, profile_image_url_original}, index) => ({
-          // eslint-disable-next-line camelcase
-          screen_name: `@${screen_name}`,
-          name: name,
-          img: profile_image_url_original,
-          highlighted: index === this.highlighted
-        }))
-      } else if (firstchar === ':') {
-        if (this.textAtCaret === ':') { return }
-        const matchedEmoji = filter(this.emoji.concat(this.customEmoji), (emoji) => emoji.shortcode.startsWith(this.textAtCaret.slice(1)))
-        if (matchedEmoji.length <= 0) {
-          return false
-        }
-        return map(take(matchedEmoji, 5), ({shortcode, image_url, utf}, index) => ({
-          screen_name: `:${shortcode}:`,
-          name: '',
-          utf: utf || '',
-          // eslint-disable-next-line camelcase
-          img: utf ? '' : this.$store.state.instance.server + image_url,
-          highlighted: index === this.highlighted
-        }))
-      } else {
-        return false
-      }
-    },
-    textAtCaret () {
-      return (this.wordAtCaret || {}).word || ''
-    },
-    wordAtCaret () {
-      const word = Completion.wordAtPosition(this.newStatus.status, this.caret - 1) || {}
-      return word
-    },
     users () {
       return this.$store.state.users.users
     },
@@ -136,10 +99,28 @@ const PostStatusForm = {
     },
     showAllScopes () {
       const minimalScopesMode = typeof this.$store.state.config.minimalScopesMode === 'undefined'
-            ? this.$store.state.instance.minimalScopesMode
-            : this.$store.state.config.minimalScopesMode
+        ? this.$store.state.instance.minimalScopesMode
+        : this.$store.state.config.minimalScopesMode
       return !minimalScopesMode
     },
+    emojiUserSuggestor () {
+      return suggestor({
+        emoji: [
+          ...this.$store.state.instance.emoji,
+          ...this.$store.state.instance.customEmoji
+        ],
+        users: this.$store.state.users.users,
+        updateUsersList: (input) => this.$store.dispatch('searchUsers', input)
+      })
+    },
+    emojiSuggestor () {
+      return suggestor({
+        emoji: [
+          ...this.$store.state.instance.emoji,
+          ...this.$store.state.instance.customEmoji
+        ]
+      })
+    },
     emoji () {
       return this.$store.state.instance.emoji || []
     },
@@ -176,91 +157,32 @@ const PostStatusForm = {
         return true
       }
     },
-    formattingOptionsEnabled () {
-      return this.$store.state.instance.formattingOptionsEnabled
-    },
     postFormats () {
       return this.$store.state.instance.postFormats || []
     },
     safeDMEnabled () {
       return this.$store.state.instance.safeDM
+    },
+    stickersAvailable () {
+      if (this.$store.state.instance.stickers) {
+        return this.$store.state.instance.stickers.length > 0
+      }
+      return 0
+    },
+    pollsAvailable () {
+      return this.$store.state.instance.pollsAvailable &&
+        this.$store.state.instance.pollLimits.max_options >= 2
+    },
+    hideScopeNotice () {
+      return this.$store.state.config.hideScopeNotice
+    },
+    pollContentError () {
+      return this.pollFormVisible &&
+        this.newStatus.poll &&
+        this.newStatus.poll.error
     }
   },
   methods: {
-    replace (replacement) {
-      this.newStatus.status = Completion.replaceWord(this.newStatus.status, this.wordAtCaret, replacement)
-      const el = this.$el.querySelector('textarea')
-      el.focus()
-      this.caret = 0
-    },
-    replaceCandidate (e) {
-      const len = this.candidates.length || 0
-      if (this.textAtCaret === ':' || e.ctrlKey) { return }
-      if (len > 0) {
-        e.preventDefault()
-        const candidate = this.candidates[this.highlighted]
-        const replacement = candidate.utf || (candidate.screen_name + ' ')
-        this.newStatus.status = Completion.replaceWord(this.newStatus.status, this.wordAtCaret, replacement)
-        const el = this.$el.querySelector('textarea')
-        el.focus()
-        this.caret = 0
-        this.highlighted = 0
-      }
-    },
-    cycleBackward (e) {
-      const len = this.candidates.length || 0
-      if (len > 0) {
-        e.preventDefault()
-        this.highlighted -= 1
-        if (this.highlighted < 0) {
-          this.highlighted = this.candidates.length - 1
-        }
-      } else {
-        this.highlighted = 0
-      }
-    },
-    cycleForward (e) {
-      const len = this.candidates.length || 0
-      if (len > 0) {
-        if (e.shiftKey) { return }
-        e.preventDefault()
-        this.highlighted += 1
-        if (this.highlighted >= len) {
-          this.highlighted = 0
-        }
-      } else {
-        this.highlighted = 0
-      }
-    },
-    onKeydown (e) {
-      e.stopPropagation()
-    },
-    onEmoji (emoji) {
-      const newValue = this.newStatus.status.substr(0, this.caret) + emoji + this.newStatus.status.substr(this.caret)
-      this.newStatus.status = newValue
-      this.caret += emoji.length
-      setTimeout(() => {
-        this.updateCaretPos()
-      })
-    },
-    updateCaretPos () {
-      const elem = this.$refs.textarea
-      if (elem.createTextRange) {
-        const range = elem.createTextRange()
-        range.move('character', this.caret)
-        range.select()
-      } else {
-        if (elem.selectionStart) {
-          elem.focus()
-          elem.setSelectionRange(this.caret, this.caret)
-        } else {
-          elem.focus()
-        }
-      }
-    },
-    setCaret ({target: {selectionStart}}) {
-      this.caret = selectionStart
-    },
     postStatus (newStatus) {
       if (this.posting) { return }
       if (this.submitDisabled) { return }
@@ -274,6 +196,12 @@ const PostStatusForm = {
         }
       }
 
+      const poll = this.pollFormVisible ? this.newStatus.poll : {}
+      if (this.pollContentError) {
+        this.error = this.pollContentError
+        return
+      }
+
       this.posting = true
       statusPoster.postStatus({
         status: newStatus.status,
@@ -283,7 +211,8 @@ const PostStatusForm = {
         media: newStatus.files,
         store: this.$store,
         inReplyToStatusId: this.replyTo,
-        contentType: newStatus.contentType
+        contentType: newStatus.contentType,
+        poll
       }).then((data) => {
         if (!data.error) {
           this.newStatus = {
@@ -291,9 +220,13 @@ const PostStatusForm = {
             spoilerText: '',
             files: [],
             visibility: newStatus.visibility,
-            contentType: newStatus.contentType
+            contentType: newStatus.contentType,
+            poll: {}
           }
+          this.pollFormVisible = false
+          this.stickerPickerVisible = false
           this.$refs.mediaUpload.clearFile()
+          this.clearPollForm()
           this.$emit('posted')
           let el = this.$el.querySelector('textarea')
           el.style.height = 'auto'
@@ -308,6 +241,7 @@ const PostStatusForm = {
     addMediaFile (fileInfo) {
       this.newStatus.files.push(fileInfo)
       this.enableSubmit()
+      this.stickerPickerVisible = false
     },
     removeMediaFile (fileInfo) {
       let index = this.newStatus.files.indexOf(fileInfo)
@@ -339,7 +273,7 @@ const PostStatusForm = {
     },
     fileDrop (e) {
       if (e.dataTransfer.files.length > 0) {
-        e.preventDefault()  // allow dropping text like before
+        e.preventDefault() // allow dropping text like before
         this.dropFiles = e.dataTransfer.files
       }
     },
@@ -349,8 +283,11 @@ const PostStatusForm = {
     resize (e) {
       const target = e.target || e
       if (!(target instanceof window.Element)) { return }
-      const vertPadding = Number(window.getComputedStyle(target)['padding-top'].substr(0, 1)) +
-            Number(window.getComputedStyle(target)['padding-bottom'].substr(0, 1))
+      const topPaddingStr = window.getComputedStyle(target)['padding-top']
+      const bottomPaddingStr = window.getComputedStyle(target)['padding-bottom']
+      // Remove "px" at the end of the values
+      const vertPadding = Number(topPaddingStr.substr(0, topPaddingStr.length - 2)) +
+            Number(bottomPaddingStr.substr(0, bottomPaddingStr.length - 2))
       // Auto is needed to make textbox shrink when removing lines
       target.style.height = 'auto'
       target.style.height = `${target.scrollHeight - vertPadding}px`
@@ -363,6 +300,28 @@ const PostStatusForm = {
     },
     changeVis (visibility) {
       this.newStatus.visibility = visibility
+    },
+    toggleStickerPicker () {
+      this.stickerPickerVisible = !this.stickerPickerVisible
+    },
+    clearStickerPicker () {
+      if (this.$refs.stickerPicker) {
+        this.$refs.stickerPicker.clear()
+      }
+    },
+    togglePollForm () {
+      this.pollFormVisible = !this.pollFormVisible
+    },
+    setPoll (poll) {
+      this.newStatus.poll = poll
+    },
+    clearPollForm () {
+      if (this.$refs.pollForm) {
+        this.$refs.pollForm.clear()
+      }
+    },
+    dismissScopeNotice () {
+      this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true })
     }
   }
 }
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 102cb484..d15c75ad 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -1,112 +1,268 @@
 <template>
-<div class="post-status-form">
-  <form @submit.prevent="postStatus(newStatus)">
-    <div class="form-group" >
-      <i18n
-        v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'"
-        path="post_status.account_not_locked_warning"
-        tag="p"
-        class="visibility-notice">
-        <router-link :to="{ name: 'user-settings' }">{{ $t('post_status.account_not_locked_warning_link') }}</router-link>
-      </i18n>
-      <p v-if="newStatus.visibility === 'direct'" class="visibility-notice">
-        <span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
-        <span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
-      </p>
-      <EmojiInput
-        v-if="newStatus.spoilerText || alwaysShowSubject"
-        type="text"
-        :placeholder="$t('post_status.content_warning')"
-        v-model="newStatus.spoilerText"
-        classname="form-control"
-      />
-      <div class="status-input-wrapper">
-        <textarea
-          ref="textarea"
-          @click="setCaret"
-          @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control"
-          @keydown="onKeydown"
-          @keydown.down="cycleForward"
-          @keydown.up="cycleBackward"
-          @keydown.shift.tab="cycleBackward"
-          @keydown.tab="cycleForward"
-          @keydown.enter="replaceCandidate"
-          @keydown.meta.enter="postStatus(newStatus)"
-          @keyup.ctrl.enter="postStatus(newStatus)"
-          @drop="fileDrop"
-          @dragover.prevent="fileDrag"
-          @input="resize"
-          @paste="paste"
-          :disabled="posting"
+  <div class="post-status-form">
+    <form
+      autocomplete="off"
+      @submit.prevent="postStatus(newStatus)"
+    >
+      <div class="form-group">
+        <i18n
+          v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'"
+          path="post_status.account_not_locked_warning"
+          tag="p"
+          class="visibility-notice"
         >
-        </textarea>
-        <EmojiSelector @emoji="onEmoji" />
-      </div>
-      <div class="visibility-tray">
-        <span class="text-format" v-if="formattingOptionsEnabled">
-          <label for="post-content-type" class="select">
-            <select id="post-content-type" v-model="newStatus.contentType" class="form-control">
-              <option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat">
-                {{$t(`post_status.content_type["${postFormat}"]`)}}
-              </option>
-            </select>
-            <i class="icon-down-open"></i>
-          </label>
-        </span>
-
-        <scope-selector
-          :showAll="showAllScopes"
-          :userDefault="userDefaultScope"
-          :originalScope="copyMessageScope"
-          :initialScope="newStatus.visibility"
-          :onScopeChange="changeVis"/>
-      </div>
-    </div>
-    <div class="autocomplete-panel" v-if="candidates">
-        <div class="autocomplete-panel-body">
-          <div
-            v-for="(candidate, index) in candidates"
-            :key="index"
-            @click="replace(candidate.utf || (candidate.screen_name + ' '))"
-            class="autocomplete-item"
-            :class="{ highlighted: candidate.highlighted }"
+          <router-link :to="{ name: 'user-settings' }">
+            {{ $t('post_status.account_not_locked_warning_link') }}
+          </router-link>
+        </i18n>
+        <p
+          v-if="!hideScopeNotice && newStatus.visibility === 'public'"
+          class="visibility-notice notice-dismissible"
+        >
+          <span>{{ $t('post_status.scope_notice.public') }}</span>
+          <a
+            class="button-icon dismiss"
+            @click.prevent="dismissScopeNotice()"
           >
-            <span v-if="candidate.img"><img :src="candidate.img" /></span>
-            <span v-else>{{candidate.utf}}</span>
-            <span>{{candidate.screen_name}}<small>{{candidate.name}}</small></span>
+            <i class="icon-cancel" />
+          </a>
+        </p>
+        <p
+          v-else-if="!hideScopeNotice && newStatus.visibility === 'unlisted'"
+          class="visibility-notice notice-dismissible"
+        >
+          <span>{{ $t('post_status.scope_notice.unlisted') }}</span>
+          <a
+            class="button-icon dismiss"
+            @click.prevent="dismissScopeNotice()"
+          >
+            <i class="icon-cancel" />
+          </a>
+        </p>
+        <p
+          v-else-if="!hideScopeNotice && newStatus.visibility === 'private' && $store.state.users.currentUser.locked"
+          class="visibility-notice notice-dismissible"
+        >
+          <span>{{ $t('post_status.scope_notice.private') }}</span>
+          <a
+            class="button-icon dismiss"
+            @click.prevent="dismissScopeNotice()"
+          >
+            <i class="icon-cancel" />
+          </a>
+        </p>
+        <p
+          v-else-if="newStatus.visibility === 'direct'"
+          class="visibility-notice"
+        >
+          <span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
+          <span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
+        </p>
+        <EmojiInput
+          v-if="newStatus.spoilerText || alwaysShowSubject"
+          v-model="newStatus.spoilerText"
+          :suggest="emojiSuggestor"
+          class="form-control"
+        >
+          <input
+
+            v-model="newStatus.spoilerText"
+            type="text"
+            :placeholder="$t('post_status.content_warning')"
+            class="form-post-subject"
+          >
+        </EmojiInput>
+        <EmojiInput
+          v-model="newStatus.status"
+          :suggest="emojiUserSuggestor"
+          class="form-control main-input"
+        >
+          <textarea
+            ref="textarea"
+            v-model="newStatus.status"
+            :placeholder="$t('post_status.default')"
+            rows="1"
+            :disabled="posting"
+            class="form-post-body"
+            @keydown.meta.enter="postStatus(newStatus)"
+            @keyup.ctrl.enter="postStatus(newStatus)"
+            @drop="fileDrop"
+            @dragover.prevent="fileDrag"
+            @input="resize"
+            @paste="paste"
+          />
+          <p
+            v-if="hasStatusLengthLimit"
+            class="character-counter faint"
+            :class="{ error: isOverLengthLimit }"
+          >
+            {{ charactersLeft }}
+          </p>
+        </EmojiInput>
+        <div class="visibility-tray">
+          <scope-selector
+            :show-all="showAllScopes"
+            :user-default="userDefaultScope"
+            :original-scope="copyMessageScope"
+            :initial-scope="newStatus.visibility"
+            :on-scope-change="changeVis"
+          />
+
+          <div
+            v-if="postFormats.length > 1"
+            class="text-format"
+          >
+            <label
+              for="post-content-type"
+              class="select"
+            >
+              <select
+                id="post-content-type"
+                v-model="newStatus.contentType"
+                class="form-control"
+              >
+                <option
+                  v-for="postFormat in postFormats"
+                  :key="postFormat"
+                  :value="postFormat"
+                >
+                  {{ $t(`post_status.content_type["${postFormat}"]`) }}
+                </option>
+              </select>
+              <i class="icon-down-open" />
+            </label>
+          </div>
+          <div
+            v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
+            class="text-format"
+          >
+            <span class="only-format">
+              {{ $t(`post_status.content_type["${postFormats[0]}"]`) }}
+            </span>
           </div>
         </div>
       </div>
-      <div class='form-bottom'>
-        <media-upload ref="mediaUpload" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" :drop-files="dropFiles"></media-upload>
-
-        <p v-if="isOverLengthLimit" class="error">{{ charactersLeft }}</p>
-        <p class="faint" v-else-if="hasStatusLengthLimit">{{ charactersLeft }}</p>
-
-        <button v-if="posting" disabled class="btn btn-default">{{$t('post_status.posting')}}</button>
-        <button v-else-if="isOverLengthLimit" disabled class="btn btn-default">{{$t('general.submit')}}</button>
-        <button v-else :disabled="submitDisabled" type="submit" class="btn btn-default">{{$t('general.submit')}}</button>
+      <poll-form
+        v-if="pollsAvailable"
+        ref="pollForm"
+        :visible="pollFormVisible"
+        @update-poll="setPoll"
+      />
+      <div class="form-bottom">
+        <div class="form-bottom-left">
+          <media-upload
+            ref="mediaUpload"
+            :drop-files="dropFiles"
+            @uploading="disableSubmit"
+            @uploaded="addMediaFile"
+            @upload-failed="uploadFailed"
+          />
+          <div
+            v-if="stickersAvailable"
+            class="sticker-icon"
+          >
+            <i
+              :title="$t('stickers.add_sticker')"
+              class="icon-picture btn btn-default"
+              :class="{ selected: stickerPickerVisible }"
+              @click="toggleStickerPicker"
+            />
+          </div>
+          <div
+            v-if="pollsAvailable"
+            class="poll-icon"
+          >
+            <i
+              :title="$t('polls.add_poll')"
+              class="icon-chart-bar btn btn-default"
+              :class="pollFormVisible && 'selected'"
+              @click="togglePollForm"
+            />
+          </div>
+        </div>
+        <button
+          v-if="posting"
+          disabled
+          class="btn btn-default"
+        >
+          {{ $t('post_status.posting') }}
+        </button>
+        <button
+          v-else-if="isOverLengthLimit"
+          disabled
+          class="btn btn-default"
+        >
+          {{ $t('general.submit') }}
+        </button>
+        <button
+          v-else
+          :disabled="submitDisabled"
+          type="submit"
+          class="btn btn-default"
+        >
+          {{ $t('general.submit') }}
+        </button>
       </div>
-      <div class='alert error' v-if="error">
+      <div
+        v-if="error"
+        class="alert error"
+      >
         Error: {{ error }}
-        <i class="button-icon icon-cancel" @click="clearError"></i>
+        <i
+          class="button-icon icon-cancel"
+          @click="clearError"
+        />
       </div>
       <div class="attachments">
-        <div class="media-upload-wrapper" v-for="file in newStatus.files">
-          <i class="fa button-icon icon-cancel" @click="removeMediaFile(file)"></i>
+        <div
+          v-for="file in newStatus.files"
+          :key="file.url"
+          class="media-upload-wrapper"
+        >
+          <i
+            class="fa button-icon icon-cancel"
+            @click="removeMediaFile(file)"
+          />
           <div class="media-upload-container attachment">
-            <img class="thumbnail media-upload" :src="file.url" v-if="type(file) === 'image'"></img>
-            <video v-if="type(file) === 'video'" :src="file.url" controls></video>
-            <audio v-if="type(file) === 'audio'" :src="file.url" controls></audio>
-            <a v-if="type(file) === 'unknown'" :href="file.url">{{file.url}}</a>
+            <img
+              v-if="type(file) === 'image'"
+              class="thumbnail media-upload"
+              :src="file.url"
+            >
+            <video
+              v-if="type(file) === 'video'"
+              :src="file.url"
+              controls
+            />
+            <audio
+              v-if="type(file) === 'audio'"
+              :src="file.url"
+              controls
+            />
+            <a
+              v-if="type(file) === 'unknown'"
+              :href="file.url"
+            >{{ file.url }}</a>
           </div>
         </div>
       </div>
-      <div class="upload_settings" v-if="newStatus.files.length > 0">
-        <input type="checkbox" id="filesSensitive" v-model="newStatus.nsfw">
-        <label for="filesSensitive">{{$t('post_status.attachments_sensitive')}}</label>
+      <div
+        v-if="newStatus.files.length > 0"
+        class="upload_settings"
+      >
+        <input
+          id="filesSensitive"
+          v-model="newStatus.nsfw"
+          type="checkbox"
+        >
+        <label for="filesSensitive">{{ $t('post_status.attachments_sensitive') }}</label>
       </div>
     </form>
+    <sticker-picker
+      v-if="stickerPickerVisible"
+      ref="stickerPicker"
+      @uploaded="addMediaFile"
+    />
   </div>
 </template>
 
@@ -136,11 +292,11 @@
   .visibility-tray {
     display: flex;
     justify-content: space-between;
-    flex-direction: row-reverse;
+    padding-top: 5px;
   }
 }
 
-.post-status-form, .login {
+.post-status-form {
   .form-bottom {
     display: flex;
     padding: 0.5em;
@@ -157,6 +313,37 @@
     }
   }
 
+  .form-bottom-left {
+    display: flex;
+    flex: 1;
+  }
+
+  .text-format {
+    .only-format {
+      color: $fallback--faint;
+      color: var(--faint, $fallback--faint);
+    }
+  }
+
+  .poll-icon, .sticker-icon {
+    font-size: 26px;
+    flex: 1;
+
+    .selected {
+      color: $fallback--lightText;
+      color: var(--lightText, $fallback--lightText);
+    }
+  }
+
+  .sticker-icon {
+    flex: 0;
+    min-width: 50px;
+  }
+
+  .icon-chart-bar {
+    cursor: pointer;
+  }
+
   .error {
     text-align: center;
   }
@@ -224,7 +411,6 @@
     }
   }
 
-
   .btn {
     cursor: pointer;
   }
@@ -242,7 +428,7 @@
   .form-group {
     display: flex;
     flex-direction: column;
-    padding: 0.3em 0.5em 0.6em;
+    padding: 0.25em 0.5em 0.5em;
     line-height:24px;
   }
 
@@ -254,19 +440,38 @@
     min-height: 1px;
   }
 
-  form textarea.form-control {
-    line-height:16px;
+  .form-post-body {
+    height: 16px; // Only affects the empty-height
+    line-height: 16px;
     resize: none;
     overflow: hidden;
     transition: min-height 200ms 100ms;
+    padding-bottom: 1.75em;
     min-height: 1px;
     box-sizing: content-box;
   }
 
-  form textarea.form-control:focus {
+  .form-post-body:focus {
     min-height: 48px;
   }
 
+  .main-input {
+    position: relative;
+  }
+
+  .character-counter {
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    padding: 0;
+    margin: 0 0.5em;
+
+    &.error {
+      color: $fallback--cRed;
+      color: var(--cRed, $fallback--cRed);
+    }
+  }
+
   .btn {
     cursor: pointer;
   }
diff --git a/src/components/progress_button/progress_button.vue b/src/components/progress_button/progress_button.vue
new file mode 100644
index 00000000..283a51af
--- /dev/null
+++ b/src/components/progress_button/progress_button.vue
@@ -0,0 +1,38 @@
+<template>
+  <button
+    :disabled="progress || disabled"
+    @click="onClick"
+  >
+    <template v-if="progress && $slots.progress">
+      <slot name="progress" />
+    </template>
+    <template v-else>
+      <slot />
+    </template>
+  </button>
+</template>
+
+<script>
+export default {
+  props: {
+    disabled: {
+      type: Boolean
+    },
+    click: { // click event handler. Must return a promise
+      type: Function,
+      default: () => Promise.resolve()
+    }
+  },
+  data () {
+    return {
+      progress: false
+    }
+  },
+  methods: {
+    onClick () {
+      this.progress = true
+      this.click().then(() => { this.progress = false })
+    }
+  }
+}
+</script>
diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.js b/src/components/public_and_external_timeline/public_and_external_timeline.js
index d45677e0..f614c13b 100644
--- a/src/components/public_and_external_timeline/public_and_external_timeline.js
+++ b/src/components/public_and_external_timeline/public_and_external_timeline.js
@@ -7,7 +7,7 @@ const PublicAndExternalTimeline = {
     timeline () { return this.$store.state.statuses.timelines.publicAndExternal }
   },
   created () {
-    this.$store.dispatch('startFetching', { timeline: 'publicAndExternal' })
+    this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' })
   },
   destroyed () {
     this.$store.dispatch('stopFetching', 'publicAndExternal')
diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.vue b/src/components/public_and_external_timeline/public_and_external_timeline.vue
index 6be9f955..fcd915ac 100644
--- a/src/components/public_and_external_timeline/public_and_external_timeline.vue
+++ b/src/components/public_and_external_timeline/public_and_external_timeline.vue
@@ -1,5 +1,9 @@
 <template>
-  <Timeline :title="$t('nav.twkn')" v-bind:timeline="timeline" v-bind:timeline-name="'publicAndExternal'"/>
+  <Timeline
+    :title="$t('nav.twkn')"
+    :timeline="timeline"
+    :timeline-name="'publicAndExternal'"
+  />
 </template>
 
 <script src="./public_and_external_timeline.js"></script>
diff --git a/src/components/public_timeline/public_timeline.js b/src/components/public_timeline/public_timeline.js
index 64c951ac..8976a99c 100644
--- a/src/components/public_timeline/public_timeline.js
+++ b/src/components/public_timeline/public_timeline.js
@@ -7,7 +7,7 @@ const PublicTimeline = {
     timeline () { return this.$store.state.statuses.timelines.public }
   },
   created () {
-    this.$store.dispatch('startFetching', { timeline: 'public' })
+    this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
   },
   destroyed () {
     this.$store.dispatch('stopFetching', 'public')
diff --git a/src/components/public_timeline/public_timeline.vue b/src/components/public_timeline/public_timeline.vue
index 85d42cca..5720068d 100644
--- a/src/components/public_timeline/public_timeline.vue
+++ b/src/components/public_timeline/public_timeline.vue
@@ -1,5 +1,9 @@
 <template>
-  <Timeline :title="$t('nav.public_tl')" v-bind:timeline="timeline" v-bind:timeline-name="'public'"/>
+  <Timeline
+    :title="$t('nav.public_tl')"
+    :timeline="timeline"
+    :timeline-name="'public'"
+  />
 </template>
 
 <script src="./public_timeline.js"></script>
diff --git a/src/components/range_input/range_input.vue b/src/components/range_input/range_input.vue
index 3e50664b..aaa2ed26 100644
--- a/src/components/range_input/range_input.vue
+++ b/src/components/range_input/range_input.vue
@@ -1,37 +1,50 @@
 <template>
-<div class="range-control style-control" :class="{ disabled: !present || disabled }">
-  <label :for="name" class="label">
-    {{label}}
-  </label>
-  <input
-    v-if="typeof fallback !== 'undefined'"
-    class="opt exclude-disabled"
-    :id="name + '-o'"
-    type="checkbox"
-    :checked="present"
-    @input="$emit('input', !present ? fallback : undefined)">
-  <label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label>
-  <input
-    :id="name"
-    class="input-number"
-    type="range"
-    :value="value || fallback"
-    :disabled="!present || disabled"
-    @input="$emit('input', $event.target.value)"
-    :max="max || hardMax || 100"
-    :min="min || hardMin || 0"
-    :step="step || 1">
-  <input
-    :id="name"
-    class="input-number"
-    type="number"
-    :value="value || fallback"
-    :disabled="!present || disabled"
-    @input="$emit('input', $event.target.value)"
-    :max="hardMax"
-    :min="hardMin"
-    :step="step || 1">
-</div>
+  <div
+    class="range-control style-control"
+    :class="{ disabled: !present || disabled }"
+  >
+    <label
+      :for="name"
+      class="label"
+    >
+      {{ label }}
+    </label>
+    <input
+      v-if="typeof fallback !== 'undefined'"
+      :id="name + '-o'"
+      class="opt exclude-disabled"
+      type="checkbox"
+      :checked="present"
+      @input="$emit('input', !present ? fallback : undefined)"
+    >
+    <label
+      v-if="typeof fallback !== 'undefined'"
+      class="opt-l"
+      :for="name + '-o'"
+    />
+    <input
+      :id="name"
+      class="input-number"
+      type="range"
+      :value="value || fallback"
+      :disabled="!present || disabled"
+      :max="max || hardMax || 100"
+      :min="min || hardMin || 0"
+      :step="step || 1"
+      @input="$emit('input', $event.target.value)"
+    >
+    <input
+      :id="name"
+      class="input-number"
+      type="number"
+      :value="value || fallback"
+      :disabled="!present || disabled"
+      :max="hardMax"
+      :min="hardMin"
+      :step="step || 1"
+      @input="$emit('input', $event.target.value)"
+    >
+  </div>
 </template>
 
 <script>
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index 8dc00420..57f3caf0 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -28,7 +28,7 @@ const registration = {
   },
   created () {
     if ((!this.registrationOpen && !this.token) || this.signedIn) {
-      this.$router.push({name: 'root'})
+      this.$router.push({ name: 'root' })
     }
 
     this.setCaptcha()
@@ -61,7 +61,7 @@ const registration = {
       if (!this.$v.$invalid) {
         try {
           await this.signUp(this.user)
-          this.$router.push({name: 'friends'})
+          this.$router.push({ name: 'friends' })
         } catch (error) {
           console.warn('Registration failed: ' + error)
         }
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 110b27bf..e0fa214a 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -1,109 +1,236 @@
 <template>
   <div class="settings panel panel-default">
     <div class="panel-heading">
-      {{$t('registration.registration')}}
+      {{ $t('registration.registration') }}
     </div>
     <div class="panel-body">
-      <form v-on:submit.prevent='submit(user)' class='registration-form'>
-        <div class='container'>
-          <div class='text-fields'>
-            <div class='form-group' :class="{ 'form-group--error': $v.user.username.$error }">
-              <label class='form--label' for='sign-up-username'>{{$t('login.username')}}</label>
-              <input :disabled="isPending" v-model.trim='$v.user.username.$model' class='form-control' id='sign-up-username' :placeholder="$t('registration.username_placeholder')">
+      <form
+        class="registration-form"
+        @submit.prevent="submit(user)"
+      >
+        <div class="container">
+          <div class="text-fields">
+            <div
+              class="form-group"
+              :class="{ 'form-group--error': $v.user.username.$error }"
+            >
+              <label
+                class="form--label"
+                for="sign-up-username"
+              >{{ $t('login.username') }}</label>
+              <input
+                id="sign-up-username"
+                v-model.trim="$v.user.username.$model"
+                :disabled="isPending"
+                class="form-control"
+                :placeholder="$t('registration.username_placeholder')"
+              >
             </div>
-            <div class="form-error" v-if="$v.user.username.$dirty">
+            <div
+              v-if="$v.user.username.$dirty"
+              class="form-error"
+            >
               <ul>
                 <li v-if="!$v.user.username.required">
-                  <span>{{$t('registration.validations.username_required')}}</span>
+                  <span>{{ $t('registration.validations.username_required') }}</span>
                 </li>
               </ul>
             </div>
 
-            <div class='form-group' :class="{ 'form-group--error': $v.user.fullname.$error }">
-              <label class='form--label' for='sign-up-fullname'>{{$t('registration.fullname')}}</label>
-              <input :disabled="isPending" v-model.trim='$v.user.fullname.$model' class='form-control' id='sign-up-fullname' :placeholder="$t('registration.fullname_placeholder')">
+            <div
+              class="form-group"
+              :class="{ 'form-group--error': $v.user.fullname.$error }"
+            >
+              <label
+                class="form--label"
+                for="sign-up-fullname"
+              >{{ $t('registration.fullname') }}</label>
+              <input
+                id="sign-up-fullname"
+                v-model.trim="$v.user.fullname.$model"
+                :disabled="isPending"
+                class="form-control"
+                :placeholder="$t('registration.fullname_placeholder')"
+              >
             </div>
-            <div class="form-error" v-if="$v.user.fullname.$dirty">
+            <div
+              v-if="$v.user.fullname.$dirty"
+              class="form-error"
+            >
               <ul>
                 <li v-if="!$v.user.fullname.required">
-                  <span>{{$t('registration.validations.fullname_required')}}</span>
+                  <span>{{ $t('registration.validations.fullname_required') }}</span>
                 </li>
               </ul>
             </div>
 
-            <div class='form-group' :class="{ 'form-group--error': $v.user.email.$error }">
-              <label class='form--label' for='email'>{{$t('registration.email')}}</label>
-              <input :disabled="isPending" v-model='$v.user.email.$model' class='form-control' id='email' type="email">
+            <div
+              class="form-group"
+              :class="{ 'form-group--error': $v.user.email.$error }"
+            >
+              <label
+                class="form--label"
+                for="email"
+              >{{ $t('registration.email') }}</label>
+              <input
+                id="email"
+                v-model="$v.user.email.$model"
+                :disabled="isPending"
+                class="form-control"
+                type="email"
+              >
             </div>
-            <div class="form-error" v-if="$v.user.email.$dirty">
+            <div
+              v-if="$v.user.email.$dirty"
+              class="form-error"
+            >
               <ul>
                 <li v-if="!$v.user.email.required">
-                  <span>{{$t('registration.validations.email_required')}}</span>
+                  <span>{{ $t('registration.validations.email_required') }}</span>
                 </li>
               </ul>
             </div>
 
-            <div class='form-group'>
-              <label class='form--label' for='bio'>{{$t('registration.bio')}} ({{$t('general.optional')}})</label>
-              <textarea :disabled="isPending" v-model='user.bio' class='form-control' id='bio' :placeholder="bioPlaceholder"></textarea>
+            <div class="form-group">
+              <label
+                class="form--label"
+                for="bio"
+              >{{ $t('registration.bio') }} ({{ $t('general.optional') }})</label>
+              <textarea
+                id="bio"
+                v-model="user.bio"
+                :disabled="isPending"
+                class="form-control"
+                :placeholder="bioPlaceholder"
+              />
             </div>
 
-            <div class='form-group' :class="{ 'form-group--error': $v.user.password.$error }">
-              <label class='form--label' for='sign-up-password'>{{$t('login.password')}}</label>
-              <input :disabled="isPending" v-model='user.password' class='form-control' id='sign-up-password' type='password'>
+            <div
+              class="form-group"
+              :class="{ 'form-group--error': $v.user.password.$error }"
+            >
+              <label
+                class="form--label"
+                for="sign-up-password"
+              >{{ $t('login.password') }}</label>
+              <input
+                id="sign-up-password"
+                v-model="user.password"
+                :disabled="isPending"
+                class="form-control"
+                type="password"
+              >
             </div>
-            <div class="form-error" v-if="$v.user.password.$dirty">
+            <div
+              v-if="$v.user.password.$dirty"
+              class="form-error"
+            >
               <ul>
                 <li v-if="!$v.user.password.required">
-                  <span>{{$t('registration.validations.password_required')}}</span>
+                  <span>{{ $t('registration.validations.password_required') }}</span>
                 </li>
               </ul>
             </div>
 
-            <div class='form-group' :class="{ 'form-group--error': $v.user.confirm.$error }">
-              <label class='form--label' for='sign-up-password-confirmation'>{{$t('registration.password_confirm')}}</label>
-              <input :disabled="isPending" v-model='user.confirm' class='form-control' id='sign-up-password-confirmation' type='password'>
+            <div
+              class="form-group"
+              :class="{ 'form-group--error': $v.user.confirm.$error }"
+            >
+              <label
+                class="form--label"
+                for="sign-up-password-confirmation"
+              >{{ $t('registration.password_confirm') }}</label>
+              <input
+                id="sign-up-password-confirmation"
+                v-model="user.confirm"
+                :disabled="isPending"
+                class="form-control"
+                type="password"
+              >
             </div>
-            <div class="form-error" v-if="$v.user.confirm.$dirty">
+            <div
+              v-if="$v.user.confirm.$dirty"
+              class="form-error"
+            >
               <ul>
                 <li v-if="!$v.user.confirm.required">
-                  <span>{{$t('registration.validations.password_confirmation_required')}}</span>
+                  <span>{{ $t('registration.validations.password_confirmation_required') }}</span>
                 </li>
                 <li v-if="!$v.user.confirm.sameAsPassword">
-                  <span>{{$t('registration.validations.password_confirmation_match')}}</span>
+                  <span>{{ $t('registration.validations.password_confirmation_match') }}</span>
                 </li>
               </ul>
             </div>
 
-            <div class="form-group" id="captcha-group" v-if="captcha.type != 'none'">
-              <label class='form--label' for='captcha-label'>{{$t('captcha')}}</label>
+            <div
+              v-if="captcha.type != 'none'"
+              id="captcha-group"
+              class="form-group"
+            >
+              <label
+                class="form--label"
+                for="captcha-label"
+              >{{ $t('captcha') }}</label>
 
               <template v-if="captcha.type == 'kocaptcha'">
-                <img v-bind:src="captcha.url" v-on:click="setCaptcha">
+                <img
+                  :src="captcha.url"
+                  @click="setCaptcha"
+                >
 
-                <sub>{{$t('registration.new_captcha')}}</sub>
+                <sub>{{ $t('registration.new_captcha') }}</sub>
 
-                <input :disabled="isPending"
-                  v-model='captcha.solution'
-                  class='form-control' id='captcha-answer' type='text' autocomplete="off">
+                <input
+                  id="captcha-answer"
+                  v-model="captcha.solution"
+                  :disabled="isPending"
+                  class="form-control"
+                  type="text"
+                  autocomplete="off"
+                >
               </template>
             </div>
 
-            <div class='form-group' v-if='token' >
-              <label for='token'>{{$t('registration.token')}}</label>
-              <input disabled='true' v-model='token' class='form-control' id='token' type='text'>
+            <div
+              v-if="token"
+              class="form-group"
+            >
+              <label for="token">{{ $t('registration.token') }}</label>
+              <input
+                id="token"
+                v-model="token"
+                disabled="true"
+                class="form-control"
+                type="text"
+              >
             </div>
-            <div class='form-group'>
-              <button :disabled="isPending" type='submit' class='btn btn-default'>{{$t('general.submit')}}</button>
+            <div class="form-group">
+              <button
+                :disabled="isPending"
+                type="submit"
+                class="btn btn-default"
+              >
+                {{ $t('general.submit') }}
+              </button>
             </div>
           </div>
 
-          <div class='terms-of-service' v-html="termsOfService">
-          </div>
+          <!-- eslint-disable vue/no-v-html -->
+          <div
+            class="terms-of-service"
+            v-html="termsOfService"
+          />
+          <!-- eslint-enable vue/no-v-html -->
         </div>
-        <div v-if="serverValidationErrors.length" class='form-group'>
-          <div class='alert error'>
-            <span v-for="error in serverValidationErrors">{{error}}</span>
+        <div
+          v-if="serverValidationErrors.length"
+          class="form-group"
+        >
+          <div class="alert error">
+            <span
+              v-for="error in serverValidationErrors"
+              :key="error"
+            >{{ error }}</span>
           </div>
         </div>
       </form>
diff --git a/src/components/remote_follow/remote_follow.vue b/src/components/remote_follow/remote_follow.vue
index fb2147bd..cb1c2a1b 100644
--- a/src/components/remote_follow/remote_follow.vue
+++ b/src/components/remote_follow/remote_follow.vue
@@ -1,9 +1,23 @@
 <template>
   <div class="remote-follow">
-    <form method="POST" :action='subscribeUrl'>
-      <input type="hidden" name="nickname" :value="user.screen_name">
-      <input type="hidden" name="profile" value="">
-      <button click="submit" class="remote-button">
+    <form
+      method="POST"
+      :action="subscribeUrl"
+    >
+      <input
+        type="hidden"
+        name="nickname"
+        :value="user.screen_name"
+      >
+      <input
+        type="hidden"
+        name="profile"
+        value=""
+      >
+      <button
+        click="submit"
+        class="remote-button"
+      >
         {{ $t('user_card.remote_follow') }}
       </button>
     </form>
diff --git a/src/components/retweet_button/retweet_button.js b/src/components/retweet_button/retweet_button.js
index eb4e4b41..fb543a9c 100644
--- a/src/components/retweet_button/retweet_button.js
+++ b/src/components/retweet_button/retweet_button.js
@@ -11,9 +11,9 @@ const RetweetButton = {
   methods: {
     retweet () {
       if (!this.status.repeated) {
-        this.$store.dispatch('retweet', {id: this.status.id})
+        this.$store.dispatch('retweet', { id: this.status.id })
       } else {
-        this.$store.dispatch('unretweet', {id: this.status.id})
+        this.$store.dispatch('unretweet', { id: this.status.id })
       }
       this.animated = true
       setTimeout(() => {
diff --git a/src/components/retweet_button/retweet_button.vue b/src/components/retweet_button/retweet_button.vue
index 6370f9dc..d58a7f8c 100644
--- a/src/components/retweet_button/retweet_button.vue
+++ b/src/components/retweet_button/retweet_button.vue
@@ -1,16 +1,29 @@
 <template>
   <div v-if="loggedIn">
     <template v-if="visibility !== 'private' && visibility !== 'direct'">
-      <i :class='classes' class='button-icon retweet-button icon-retweet rt-active' v-on:click.prevent='retweet()' :title="$t('tool_tip.repeat')"></i>
-      <span v-if='!hidePostStatsLocal && status.repeat_num > 0'>{{status.repeat_num}}</span>
+      <i
+        :class="classes"
+        class="button-icon retweet-button icon-retweet rt-active"
+        :title="$t('tool_tip.repeat')"
+        @click.prevent="retweet()"
+      />
+      <span v-if="!hidePostStatsLocal && status.repeat_num > 0">{{ status.repeat_num }}</span>
     </template>
     <template v-else>
-      <i :class='classes' class='button-icon icon-lock' :title="$t('timeline.no_retweet_hint')"></i>
+      <i
+        :class="classes"
+        class="button-icon icon-lock"
+        :title="$t('timeline.no_retweet_hint')"
+      />
     </template>
   </div>
   <div v-else-if="!loggedIn">
-    <i :class='classes' class='button-icon icon-retweet' :title="$t('tool_tip.repeat')"></i>
-    <span v-if='!hidePostStatsLocal && status.repeat_num > 0'>{{status.repeat_num}}</span>
+    <i
+      :class="classes"
+      class="button-icon icon-retweet"
+      :title="$t('tool_tip.repeat')"
+    />
+    <span v-if="!hidePostStatsLocal && status.repeat_num > 0">{{ status.repeat_num }}</span>
   </div>
 </template>
 
diff --git a/src/components/scope_selector/scope_selector.js b/src/components/scope_selector/scope_selector.js
index 8a42ee7b..e9ccdefc 100644
--- a/src/components/scope_selector/scope_selector.js
+++ b/src/components/scope_selector/scope_selector.js
@@ -29,10 +29,10 @@ const ScopeSelector = {
     },
     css () {
       return {
-        public: {selected: this.currentScope === 'public'},
-        unlisted: {selected: this.currentScope === 'unlisted'},
-        private: {selected: this.currentScope === 'private'},
-        direct: {selected: this.currentScope === 'direct'}
+        public: { selected: this.currentScope === 'public' },
+        unlisted: { selected: this.currentScope === 'unlisted' },
+        private: { selected: this.currentScope === 'private' },
+        direct: { selected: this.currentScope === 'direct' }
       }
     }
   },
diff --git a/src/components/scope_selector/scope_selector.vue b/src/components/scope_selector/scope_selector.vue
index 33ea488f..291236f2 100644
--- a/src/components/scope_selector/scope_selector.vue
+++ b/src/components/scope_selector/scope_selector.vue
@@ -1,30 +1,53 @@
 <template>
-<div v-if="!showNothing">
-  <i class="icon-mail-alt"
-     :class="css.direct"
-     :title="$t('post_status.scope.direct')"
-     v-if="showDirect"
-     @click="changeVis('direct')">
-  </i>
-  <i class="icon-lock"
-     :class="css.private"
-     :title="$t('post_status.scope.private')"
-     v-if="showPrivate"
-     v-on:click="changeVis('private')">
-  </i>
-  <i class="icon-lock-open-alt"
-     :class="css.unlisted"
-     :title="$t('post_status.scope.unlisted')"
-     v-if="showUnlisted"
-     @click="changeVis('unlisted')">
-  </i>
-  <i class="icon-globe"
-     :class="css.public"
-     :title="$t('post_status.scope.public')"
-     v-if="showPublic"
-     @click="changeVis('public')">
-  </i>
-</div>
+  <div
+    v-if="!showNothing"
+    class="scope-selector"
+  >
+    <i
+      v-if="showDirect"
+      class="icon-mail-alt"
+      :class="css.direct"
+      :title="$t('post_status.scope.direct')"
+      @click="changeVis('direct')"
+    />
+    <i
+      v-if="showPrivate"
+      class="icon-lock"
+      :class="css.private"
+      :title="$t('post_status.scope.private')"
+      @click="changeVis('private')"
+    />
+    <i
+      v-if="showUnlisted"
+      class="icon-lock-open-alt"
+      :class="css.unlisted"
+      :title="$t('post_status.scope.unlisted')"
+      @click="changeVis('unlisted')"
+    />
+    <i
+      v-if="showPublic"
+      class="icon-globe"
+      :class="css.public"
+      :title="$t('post_status.scope.public')"
+      @click="changeVis('public')"
+    />
+  </div>
 </template>
 
 <script src="./scope_selector.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.scope-selector {
+  i {
+    font-size: 1.2em;
+    cursor: pointer;
+
+    &.selected {
+      color: $fallback--lightText;
+      color: var(--lightText, $fallback--lightText);
+    }
+  }
+}
+</style>
diff --git a/src/components/search/search.js b/src/components/search/search.js
new file mode 100644
index 00000000..b434e127
--- /dev/null
+++ b/src/components/search/search.js
@@ -0,0 +1,98 @@
+import FollowCard from '../follow_card/follow_card.vue'
+import Conversation from '../conversation/conversation.vue'
+import Status from '../status/status.vue'
+import map from 'lodash/map'
+
+const Search = {
+  components: {
+    FollowCard,
+    Conversation,
+    Status
+  },
+  props: [
+    'query'
+  ],
+  data () {
+    return {
+      loaded: false,
+      loading: false,
+      searchTerm: this.query || '',
+      userIds: [],
+      statuses: [],
+      hashtags: [],
+      currenResultTab: 'statuses'
+    }
+  },
+  computed: {
+    users () {
+      return this.userIds.map(userId => this.$store.getters.findUser(userId))
+    },
+    visibleStatuses () {
+      const allStatusesObject = this.$store.state.statuses.allStatusesObject
+
+      return this.statuses.filter(status =>
+        allStatusesObject[status.id] && !allStatusesObject[status.id].deleted
+      )
+    }
+  },
+  mounted () {
+    this.search(this.query)
+  },
+  watch: {
+    query (newValue) {
+      this.searchTerm = newValue
+      this.search(newValue)
+    }
+  },
+  methods: {
+    newQuery (query) {
+      this.$router.push({ name: 'search', query: { query } })
+      this.$refs.searchInput.focus()
+    },
+    search (query) {
+      if (!query) {
+        this.loading = false
+        return
+      }
+
+      this.loading = true
+      this.userIds = []
+      this.statuses = []
+      this.hashtags = []
+      this.$refs.searchInput.blur()
+
+      this.$store.dispatch('search', { q: query, resolve: true })
+        .then(data => {
+          this.loading = false
+          this.userIds = map(data.accounts, 'id')
+          this.statuses = data.statuses
+          this.hashtags = data.hashtags
+          this.currenResultTab = this.getActiveTab()
+          this.loaded = true
+        })
+    },
+    resultCount (tabName) {
+      const length = this[tabName].length
+      return length === 0 ? '' : ` (${length})`
+    },
+    onResultTabSwitch (_index, dataset) {
+      this.currenResultTab = dataset.filter
+    },
+    getActiveTab () {
+      if (this.visibleStatuses.length > 0) {
+        return 'statuses'
+      } else if (this.users.length > 0) {
+        return 'people'
+      } else if (this.hashtags.length > 0) {
+        return 'hashtags'
+      }
+
+      return 'statuses'
+    },
+    lastHistoryRecord (hashtag) {
+      return hashtag.history && hashtag.history[0]
+    }
+  }
+}
+
+export default Search
diff --git a/src/components/search/search.vue b/src/components/search/search.vue
new file mode 100644
index 00000000..4350e672
--- /dev/null
+++ b/src/components/search/search.vue
@@ -0,0 +1,211 @@
+<template>
+  <div class="panel panel-default">
+    <div class="panel-heading">
+      <div class="title">
+        {{ $t('nav.search') }}
+      </div>
+    </div>
+    <div class="search-input-container">
+      <input
+        ref="searchInput"
+        v-model="searchTerm"
+        class="search-input"
+        :placeholder="$t('nav.search')"
+        @keyup.enter="newQuery(searchTerm)"
+      >
+      <button
+        class="btn search-button"
+        @click="newQuery(searchTerm)"
+      >
+        <i class="icon-search" />
+      </button>
+    </div>
+    <div
+      v-if="loading"
+      class="text-center loading-icon"
+    >
+      <i class="icon-spin3 animate-spin" />
+    </div>
+    <div v-else-if="loaded">
+      <div class="search-nav-heading">
+        <tab-switcher
+          ref="tabSwitcher"
+          :on-switch="onResultTabSwitch"
+          :custom-active="currenResultTab"
+        >
+          <span
+            data-tab-dummy
+            data-filter="statuses"
+            :label="$t('user_card.statuses') + resultCount('visibleStatuses')"
+          />
+          <span
+            data-tab-dummy
+            data-filter="people"
+            :label="$t('search.people') + resultCount('users')"
+          />
+          <span
+            data-tab-dummy
+            data-filter="hashtags"
+            :label="$t('search.hashtags') + resultCount('hashtags')"
+          />
+        </tab-switcher>
+      </div>
+    </div>
+    <div class="panel-body">
+      <div v-if="currenResultTab === 'statuses'">
+        <div
+          v-if="visibleStatuses.length === 0 && !loading && loaded"
+          class="search-result-heading"
+        >
+          <h4>{{ $t('search.no_results') }}</h4>
+        </div>
+        <Status
+          v-for="status in visibleStatuses"
+          :key="status.id"
+          :collapsable="false"
+          :expandable="false"
+          :compact="false"
+          class="search-result"
+          :statusoid="status"
+          :no-heading="false"
+        />
+      </div>
+      <div v-else-if="currenResultTab === 'people'">
+        <div
+          v-if="users.length === 0 && !loading && loaded"
+          class="search-result-heading"
+        >
+          <h4>{{ $t('search.no_results') }}</h4>
+        </div>
+        <FollowCard
+          v-for="user in users"
+          :key="user.id"
+          :user="user"
+          class="list-item search-result"
+        />
+      </div>
+      <div v-else-if="currenResultTab === 'hashtags'">
+        <div
+          v-if="hashtags.length === 0 && !loading && loaded"
+          class="search-result-heading"
+        >
+          <h4>{{ $t('search.no_results') }}</h4>
+        </div>
+        <div
+          v-for="hashtag in hashtags"
+          :key="hashtag.url"
+          class="status trend search-result"
+        >
+          <div class="hashtag">
+            <router-link :to="{ name: 'tag-timeline', params: { tag: hashtag.name } }">
+              #{{ hashtag.name }}
+            </router-link>
+            <div v-if="lastHistoryRecord(hashtag)">
+              <span v-if="lastHistoryRecord(hashtag).accounts == 1">
+                {{ $t('search.person_talking', { count: lastHistoryRecord(hashtag).accounts }) }}
+              </span>
+              <span v-else>
+                {{ $t('search.people_talking', { count: lastHistoryRecord(hashtag).accounts }) }}
+              </span>
+            </div>
+          </div>
+          <div
+            v-if="lastHistoryRecord(hashtag)"
+            class="count"
+          >
+            {{ lastHistoryRecord(hashtag).uses }}
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="search-result-footer text-center panel-footer faint" />
+  </div>
+</template>
+
+<script src="./search.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.search-result-heading {
+  color: $fallback--faint;
+  color: var(--faint, $fallback--faint);
+  padding: 0.75rem;
+  text-align: center;
+}
+
+@media all and (max-width: 800px) {
+  .search-nav-heading {
+    .tab-switcher .tabs .tab-wrapper {
+      display: block;
+      justify-content: center;
+      flex: 1 1 auto;
+      text-align: center;
+    }
+  }
+}
+
+.search-result {
+  box-sizing: border-box;
+  border-bottom: 1px solid;
+  border-color: $fallback--border;
+  border-color: var(--border, $fallback--border);
+}
+
+.search-result-footer {
+  border-width: 1px 0 0 0;
+  border-style: solid;
+  border-color: var(--border, $fallback--border);
+  padding: 10px;
+  background-color: $fallback--fg;
+  background-color: var(--panel, $fallback--fg);
+}
+
+.search-input-container {
+  padding: 0.8rem;
+  display: flex;
+  justify-content: center;
+
+  .search-input {
+    width: 100%;
+    line-height: 1.125rem;
+    font-size: 1rem;
+    padding: 0.5rem;
+    box-sizing: border-box;
+  }
+
+  .search-button {
+    margin-left: 0.5em;
+  }
+}
+
+.loading-icon {
+  padding: 1em;
+}
+
+.trend {
+  display: flex;
+  align-items: center;
+
+  .hashtag {
+    flex: 1 1 auto;
+    color: $fallback--text;
+    color: var(--text, $fallback--text);
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .count {
+    flex: 0 0 auto;
+    width: 2rem;
+    font-size: 1.5rem;
+    line-height: 2.25rem;
+    font-weight: 500;
+    text-align: center;
+    color: $fallback--text;
+    color: var(--text, $fallback--text);
+  }
+}
+
+</style>
diff --git a/src/components/search_bar/search_bar.js b/src/components/search_bar/search_bar.js
new file mode 100644
index 00000000..b8a792ee
--- /dev/null
+++ b/src/components/search_bar/search_bar.js
@@ -0,0 +1,27 @@
+const SearchBar = {
+  data: () => ({
+    searchTerm: undefined,
+    hidden: true,
+    error: false,
+    loading: false
+  }),
+  watch: {
+    '$route': function (route) {
+      if (route.name === 'search') {
+        this.searchTerm = route.query.query
+      }
+    }
+  },
+  methods: {
+    find (searchTerm) {
+      this.$router.push({ name: 'search', query: { query: searchTerm } })
+      this.$refs.searchInput.focus()
+    },
+    toggleHidden () {
+      this.hidden = !this.hidden
+      this.$emit('toggled', this.hidden)
+    }
+  }
+}
+
+export default SearchBar
diff --git a/src/components/search_bar/search_bar.vue b/src/components/search_bar/search_bar.vue
new file mode 100644
index 00000000..4d5a1aec
--- /dev/null
+++ b/src/components/search_bar/search_bar.vue
@@ -0,0 +1,73 @@
+<template>
+  <div>
+    <div class="search-bar-container">
+      <i
+        v-if="loading"
+        class="icon-spin4 finder-icon animate-spin-slow"
+      />
+      <a
+        v-if="hidden"
+        href="#"
+        :title="$t('nav.search')"
+      ><i
+        class="button-icon icon-search"
+        @click.prevent.stop="toggleHidden"
+      /></a>
+      <template v-else>
+        <input
+          id="search-bar-input"
+          ref="searchInput"
+          v-model="searchTerm"
+          class="search-bar-input"
+          :placeholder="$t('nav.search')"
+          type="text"
+          @keyup.enter="find(searchTerm)"
+        >
+        <button
+          class="btn search-button"
+          @click="find(searchTerm)"
+        >
+          <i class="icon-search" />
+        </button>
+        <i
+          class="button-icon icon-cancel"
+          @click.prevent.stop="toggleHidden"
+        />
+      </template>
+    </div>
+  </div>
+</template>
+
+<script src="./search_bar.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.search-bar-container {
+  max-width: 100%;
+  display: inline-flex;
+  align-items: baseline;
+  vertical-align: baseline;
+  justify-content: flex-end;
+
+  .search-bar-input,
+  .search-button {
+    height: 29px;
+  }
+
+  .search-bar-input {
+    // TODO: do this properly without a rough guesstimate of 2 icons + paddings
+    max-width: calc(100% - 30px - 30px - 20px);
+  }
+
+  .search-button {
+    margin-left: .5em;
+    margin-right: .5em;
+  }
+
+  .icon-cancel {
+    cursor: pointer;
+  }
+}
+
+</style>
diff --git a/src/components/selectable_list/selectable_list.js b/src/components/selectable_list/selectable_list.js
new file mode 100644
index 00000000..10980d46
--- /dev/null
+++ b/src/components/selectable_list/selectable_list.js
@@ -0,0 +1,66 @@
+import List from '../list/list.vue'
+import Checkbox from '../checkbox/checkbox.vue'
+
+const SelectableList = {
+  components: {
+    List,
+    Checkbox
+  },
+  props: {
+    items: {
+      type: Array,
+      default: () => []
+    },
+    getKey: {
+      type: Function,
+      default: item => item.id
+    }
+  },
+  data () {
+    return {
+      selected: []
+    }
+  },
+  computed: {
+    allKeys () {
+      return this.items.map(this.getKey)
+    },
+    filteredSelected () {
+      return this.allKeys.filter(key => this.selected.indexOf(key) !== -1)
+    },
+    allSelected () {
+      return this.filteredSelected.length === this.items.length
+    },
+    noneSelected () {
+      return this.filteredSelected.length === 0
+    },
+    someSelected () {
+      return !this.allSelected && !this.noneSelected
+    }
+  },
+  methods: {
+    isSelected (item) {
+      return this.filteredSelected.indexOf(this.getKey(item)) !== -1
+    },
+    toggle (checked, item) {
+      const key = this.getKey(item)
+      const oldChecked = this.isSelected(key)
+      if (checked !== oldChecked) {
+        if (checked) {
+          this.selected.push(key)
+        } else {
+          this.selected.splice(this.selected.indexOf(key), 1)
+        }
+      }
+    },
+    toggleAll (value) {
+      if (value) {
+        this.selected = this.allKeys.slice(0)
+      } else {
+        this.selected = []
+      }
+    }
+  }
+}
+
+export default SelectableList
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
new file mode 100644
index 00000000..d9ec7ece
--- /dev/null
+++ b/src/components/selectable_list/selectable_list.vue
@@ -0,0 +1,92 @@
+<template>
+  <div class="selectable-list">
+    <div
+      v-if="items.length > 0"
+      class="selectable-list-header"
+    >
+      <div class="selectable-list-checkbox-wrapper">
+        <Checkbox
+          :checked="allSelected"
+          :indeterminate="someSelected"
+          @change="toggleAll"
+        >
+          {{ $t('selectable_list.select_all') }}
+        </Checkbox>
+      </div>
+      <div class="selectable-list-header-actions">
+        <slot
+          name="header"
+          :selected="filteredSelected"
+        />
+      </div>
+    </div>
+    <List
+      :items="items"
+      :get-key="getKey"
+    >
+      <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(item)"
+              @change="checked => toggle(checked, item)"
+            />
+          </div>
+          <slot
+            name="item"
+            :item="item"
+          />
+        </div>
+      </template>
+      <template slot="empty">
+        <slot name="empty" />
+      </template>
+    </List>
+  </div>
+</template>
+
+<script src="./selectable_list.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.selectable-list {
+  &-item-inner {
+    display: flex;
+    align-items: center;
+
+    > * {
+      min-width: 0;
+    }
+  }
+
+  &-item-selected-inner {
+    background-color: $fallback--lightBg;
+    background-color: var(--lightBg, $fallback--lightBg);
+  }
+
+  &-header {
+    display: flex;
+    align-items: center;
+    padding: 0.6em 0;
+    border-bottom: 2px solid;
+    border-bottom-color: $fallback--border;
+    border-bottom-color: var(--border, $fallback--border);
+
+    &-actions {
+      flex: 1;
+    }
+  }
+
+  &-checkbox-wrapper {
+    padding: 0 10px;
+    flex: none;
+  }
+}
+</style>
diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index a85ab674..c4aa45b2 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -46,6 +46,7 @@ const settings = {
       streamingLocal: user.streaming,
       pauseOnUnfocusedLocal: user.pauseOnUnfocused,
       hoverPreviewLocal: user.hoverPreview,
+      autohideFloatingPostButtonLocal: user.autohideFloatingPostButton,
 
       hideMutedPostsLocal: typeof user.hideMutedPosts === 'undefined'
         ? instance.hideMutedPosts
@@ -183,6 +184,9 @@ const settings = {
     hoverPreviewLocal (value) {
       this.$store.dispatch('setOption', { name: 'hoverPreview', value })
     },
+    autohideFloatingPostButtonLocal (value) {
+      this.$store.dispatch('setOption', { name: 'autohideFloatingPostButton', value })
+    },
     muteWordsString (value) {
       value = filter(value.split('\n'), (word) => trim(word).length > 0)
       this.$store.dispatch('setOption', { name: 'muteWords', value })
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index 6ee103c7..744ec566 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -1,370 +1,483 @@
 <template>
-<div class="settings panel panel-default">
-  <div class="panel-heading">
-    <div class="title">
-      {{$t('settings.settings')}}
+  <div class="settings panel panel-default">
+    <div class="panel-heading">
+      <div class="title">
+        {{ $t('settings.settings') }}
+      </div>
+
+      <transition name="fade">
+        <template v-if="currentSaveStateNotice">
+          <div
+            v-if="currentSaveStateNotice.error"
+            class="alert error"
+            @click.prevent
+          >
+            {{ $t('settings.saving_err') }}
+          </div>
+
+          <div
+            v-if="!currentSaveStateNotice.error"
+            class="alert transparent"
+            @click.prevent
+          >
+            {{ $t('settings.saving_ok') }}
+          </div>
+        </template>
+      </transition>
     </div>
-
-    <transition name="fade">
-      <template v-if="currentSaveStateNotice">
-        <div @click.prevent class="alert error" v-if="currentSaveStateNotice.error">
-          {{ $t('settings.saving_err') }}
-        </div>
-
-        <div @click.prevent class="alert transparent" v-if="!currentSaveStateNotice.error">
-          {{ $t('settings.saving_ok') }}
-        </div>
-      </template>
-    </transition>
-  </div>
-  <div class="panel-body">
-<keep-alive>
-    <tab-switcher>
-      <div :label="$t('settings.general')" >
-        <div class="setting-item">
-          <h2>{{ $t('settings.interface') }}</h2>
-          <ul class="setting-list">
-            <li>
-              <interface-language-switcher />
-            </li>
-            <li v-if="instanceSpecificPanelPresent">
-              <input type="checkbox" id="hideISP" v-model="hideISPLocal">
-              <label for="hideISP">{{$t('settings.hide_isp')}}</label>
-            </li>
-          </ul>
-        </div>
-        <div class="setting-item">
-          <h2>{{$t('nav.timeline')}}</h2>
-          <ul class="setting-list">
-            <li>
-              <input type="checkbox" id="hideMutedPosts" v-model="hideMutedPostsLocal">
-              <label for="hideMutedPosts">{{$t('settings.hide_muted_posts')}} {{$t('settings.instance_default', { value: hideMutedPostsDefault })}}</label>
-            </li>
-            <li>
-              <input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
-              <label for="collapseMessageWithSubject">
-                {{$t('settings.collapse_subject')}} {{$t('settings.instance_default', { value: collapseMessageWithSubjectDefault })}}
-              </label>
-            </li>
-            <li>
-              <input type="checkbox" id="streaming" v-model="streamingLocal">
-              <label for="streaming">{{$t('settings.streaming')}}</label>
-              <ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
+    <div class="panel-body">
+      <keep-alive>
+        <tab-switcher>
+          <div :label="$t('settings.general')">
+            <div class="setting-item">
+              <h2>{{ $t('settings.interface') }}</h2>
+              <ul class="setting-list">
                 <li>
-                  <input :disabled="!streamingLocal" type="checkbox" id="pauseOnUnfocused" v-model="pauseOnUnfocusedLocal">
-                  <label for="pauseOnUnfocused">{{$t('settings.pause_on_unfocused')}}</label>
+                  <interface-language-switcher />
+                </li>
+                <li v-if="instanceSpecificPanelPresent">
+                  <input
+                    id="hideISP"
+                    v-model="hideISPLocal"
+                    type="checkbox"
+                  >
+                  <label for="hideISP">{{ $t('settings.hide_isp') }}</label>
                 </li>
               </ul>
-            </li>
-            <li>
-              <input type="checkbox" id="autoload" v-model="autoLoadLocal">
-              <label for="autoload">{{$t('settings.autoload')}}</label>
-            </li>
-            <li>
-              <input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal">
-              <label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label>
-            </li>
-          </ul>
-        </div>
-
-        <div class="setting-item">
-          <h2>{{$t('settings.composing')}}</h2>
-          <ul class="setting-list">
-            <li>
-              <input type="checkbox" id="scopeCopy" v-model="scopeCopyLocal">
-              <label for="scopeCopy">
-                {{$t('settings.scope_copy')}} {{$t('settings.instance_default', { value: scopeCopyDefault })}}
-              </label>
-            </li>
-            <li>
-              <input type="checkbox" id="subjectHide" v-model="alwaysShowSubjectInputLocal">
-              <label for="subjectHide">
-                {{$t('settings.subject_input_always_show')}} {{$t('settings.instance_default', { value: alwaysShowSubjectInputDefault })}}
-              </label>
-            </li>
-            <li>
-              <div>
-                {{$t('settings.subject_line_behavior')}}
-                <label for="subjectLineBehavior" class="select">
-                  <select id="subjectLineBehavior" v-model="subjectLineBehaviorLocal">
-                    <option value="email">
-                      {{$t('settings.subject_line_email')}}
-                      {{subjectLineBehaviorDefault == 'email' ? $t('settings.instance_default_simple') : ''}}
-                    </option>
-                    <option value="masto">
-                      {{$t('settings.subject_line_mastodon')}}
-                      {{subjectLineBehaviorDefault == 'mastodon' ? $t('settings.instance_default_simple') : ''}}
-                    </option>
-                    <option value="noop">
-                      {{$t('settings.subject_line_noop')}}
-                      {{subjectLineBehaviorDefault == 'noop' ? $t('settings.instance_default_simple') : ''}}
-                    </option>
-                  </select>
-                  <i class="icon-down-open"/>
-                </label>
-              </div>
-            </li>
-            <li>
-              <div>
-                {{$t('settings.post_status_content_type')}}
-                <label for="postContentType" class="select">
-                  <select id="postContentType" v-model="postContentTypeLocal">
-                    <option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat">
-                      {{$t(`post_status.content_type["${postFormat}"]`)}}
-                      {{postContentTypeDefault === postFormat ? $t('settings.instance_default_simple') : ''}}
-                    </option>
-                  </select>
-                  <i class="icon-down-open"/>
-                </label>
-              </div>
-            </li>
-            <li>
-              <input type="checkbox" id="minimalScopesMode" v-model="minimalScopesModeLocal">
-              <label for="minimalScopesMode">
-                {{$t('settings.minimal_scopes_mode')}} {{$t('settings.instance_default', { value: minimalScopesModeDefault })}}
-              </label>
-            </li>
-          </ul>
-        </div>
-
-        <div class="setting-item">
-          <h2>{{$t('settings.attachments')}}</h2>
-          <ul class="setting-list">
-            <li>
-              <input type="checkbox" id="hideAttachments" v-model="hideAttachmentsLocal">
-              <label for="hideAttachments">{{$t('settings.hide_attachments_in_tl')}}</label>
-            </li>
-            <li>
-              <input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal">
-              <label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label>
-            </li>
-            <li>
-              <label for="maxThumbnails">{{$t('settings.max_thumbnails')}}</label>
-              <input class="number-input" type="number" id="maxThumbnails" v-model.number="maxThumbnails" min="0" step="1">
-            </li>
-            <li>
-              <input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
-              <label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label>
-            </li>
-            <ul class="setting-list suboptions" >
-              <li>
-                <input :disabled="!hideNsfwLocal" type="checkbox" id="preloadImage" v-model="preloadImage">
-                <label for="preloadImage">{{$t('settings.preload_images')}}</label>
-              </li>
-              <li>
-                <input :disabled="!hideNsfwLocal" type="checkbox" id="useOneClickNsfw" v-model="useOneClickNsfw">
-                <label for="useOneClickNsfw">{{$t('settings.use_one_click_nsfw')}}</label>
-              </li>
-            </ul>
-            <li>
-              <input type="checkbox" id="stopGifs" v-model="stopGifs">
-              <label for="stopGifs">{{$t('settings.stop_gifs')}}</label>
-            </li>
-            <li>
-              <input type="checkbox" id="loopVideo" v-model="loopVideoLocal">
-              <label for="loopVideo">{{$t('settings.loop_video')}}</label>
-              <ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
+            </div>
+            <div class="setting-item">
+              <h2>{{ $t('nav.timeline') }}</h2>
+              <ul class="setting-list">
                 <li>
-                  <input :disabled="!loopVideoLocal || !loopSilentAvailable" type="checkbox" id="loopVideoSilentOnly" v-model="loopVideoSilentOnlyLocal">
-                  <label for="loopVideoSilentOnly">{{$t('settings.loop_video_silent_only')}}</label>
-                  <div v-if="!loopSilentAvailable" class="unavailable">
-                    <i class="icon-globe"/>! {{$t('settings.limited_availability')}}
+                  <input
+                    id="hideMutedPosts"
+                    v-model="hideMutedPostsLocal"
+                    type="checkbox"
+                  >
+                  <label for="hideMutedPosts">{{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsDefault }) }}</label>
+                </li>
+                <li>
+                  <input
+                    id="collapseMessageWithSubject"
+                    v-model="collapseMessageWithSubjectLocal"
+                    type="checkbox"
+                  >
+                  <label for="collapseMessageWithSubject">{{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectDefault }) }}</label>
+                </li>
+                <li>
+                  <input
+                    id="streaming"
+                    v-model="streamingLocal"
+                    type="checkbox"
+                  >
+                  <label for="streaming">{{ $t('settings.streaming') }}</label>
+                  <ul
+                    class="setting-list suboptions"
+                    :class="[{disabled: !streamingLocal}]"
+                  >
+                    <li>
+                      <input
+                        id="pauseOnUnfocused"
+                        v-model="pauseOnUnfocusedLocal"
+                        :disabled="!streamingLocal"
+                        type="checkbox"
+                      >
+                      <label for="pauseOnUnfocused">{{ $t('settings.pause_on_unfocused') }}</label>
+                    </li>
+                  </ul>
+                </li>
+                <li>
+                  <input
+                    id="autoload"
+                    v-model="autoLoadLocal"
+                    type="checkbox"
+                  >
+                  <label for="autoload">{{ $t('settings.autoload') }}</label>
+                </li>
+                <li>
+                  <input
+                    id="hoverPreview"
+                    v-model="hoverPreviewLocal"
+                    type="checkbox"
+                  >
+                  <label for="hoverPreview">{{ $t('settings.reply_link_preview') }}</label>
+                </li>
+              </ul>
+            </div>
+
+            <div class="setting-item">
+              <h2>{{ $t('settings.composing') }}</h2>
+              <ul class="setting-list">
+                <li>
+                  <input
+                    id="scopeCopy"
+                    v-model="scopeCopyLocal"
+                    type="checkbox"
+                  >
+                  <label for="scopeCopy">
+                    {{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyDefault }) }}
+                  </label>
+                </li>
+                <li>
+                  <input
+                    id="subjectHide"
+                    v-model="alwaysShowSubjectInputLocal"
+                    type="checkbox"
+                  >
+                  <label for="subjectHide">
+                    {{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputDefault }) }}
+                  </label>
+                </li>
+                <li>
+                  <div>
+                    {{ $t('settings.subject_line_behavior') }}
+                    <label
+                      for="subjectLineBehavior"
+                      class="select"
+                    >
+                      <select
+                        id="subjectLineBehavior"
+                        v-model="subjectLineBehaviorLocal"
+                      >
+                        <option value="email">
+                          {{ $t('settings.subject_line_email') }}
+                          {{ subjectLineBehaviorDefault == 'email' ? $t('settings.instance_default_simple') : '' }}
+                        </option>
+                        <option value="masto">
+                          {{ $t('settings.subject_line_mastodon') }}
+                          {{ subjectLineBehaviorDefault == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
+                        </option>
+                        <option value="noop">
+                          {{ $t('settings.subject_line_noop') }}
+                          {{ subjectLineBehaviorDefault == 'noop' ? $t('settings.instance_default_simple') : '' }}
+                        </option>
+                      </select>
+                      <i class="icon-down-open" />
+                    </label>
+                  </div>
+                </li>
+                <li v-if="postFormats.length > 0">
+                  <div>
+                    {{ $t('settings.post_status_content_type') }}
+                    <label
+                      for="postContentType"
+                      class="select"
+                    >
+                      <select
+                        id="postContentType"
+                        v-model="postContentTypeLocal"
+                      >
+                        <option
+                          v-for="postFormat in postFormats"
+                          :key="postFormat"
+                          :value="postFormat"
+                        >
+                          {{ $t(`post_status.content_type["${postFormat}"]`) }}
+                          {{ postContentTypeDefault === postFormat ? $t('settings.instance_default_simple') : '' }}
+                        </option>
+                      </select>
+                      <i class="icon-down-open" />
+                    </label>
                   </div>
                 </li>
-              </ul>
-            </li>
-            <li>
-              <input type="checkbox" id="playVideosInModal" v-model="playVideosInModal">
-              <label for="playVideosInModal">{{$t('settings.play_videos_in_modal')}}</label>
-            </li>
-            <li>
-              <input type="checkbox" id="useContainFit" v-model="useContainFit">
-              <label for="useContainFit">{{$t('settings.use_contain_fit')}}</label>
-            </li>
-          </ul>
-        </div>
-
-       <div class="setting-item">
-          <h2>{{$t('settings.notifications')}}</h2>
-          <ul class="setting-list">
-            <li>
-              <input type="checkbox" id="webPushNotifications" v-model="webPushNotificationsLocal">
-              <label for="webPushNotifications">
-                {{$t('settings.enable_web_push_notifications')}}
-              </label>
-            </li>
-          </ul>
-        </div>
-      </div>
-
-      <div :label="$t('settings.theme')" >
-        <div class="setting-item">
-          <style-switcher></style-switcher>
-        </div>
-      </div>
-
-      <div :label="$t('settings.filtering')" >
-        <div class="setting-item">
-          <div class="select-multiple">
-            <span class="label">{{$t('settings.notification_visibility')}}</span>
-            <ul class="option-list">
-              <li>
-                <input type="checkbox" id="notification-visibility-likes" v-model="notificationVisibilityLocal.likes">
-                <label for="notification-visibility-likes">
-                  {{$t('settings.notification_visibility_likes')}}
-                </label>
-              </li>
-              <li>
-                <input type="checkbox" id="notification-visibility-repeats" v-model="notificationVisibilityLocal.repeats">
-                <label for="notification-visibility-repeats">
-                {{$t('settings.notification_visibility_repeats')}}
-                </label>
-              </li>
-              <li>
-                <input type="checkbox" id="notification-visibility-follows" v-model="notificationVisibilityLocal.follows">
-                <label for="notification-visibility-follows">
-                {{$t('settings.notification_visibility_follows')}}
-                </label>
-              </li>
-              <li>
-                <input type="checkbox" id="notification-visibility-mentions" v-model="notificationVisibilityLocal.mentions">
-                <label for="notification-visibility-mentions">
-                {{$t('settings.notification_visibility_mentions')}}
-                </label>
-              </li>
-            </ul>
-          </div>
-          <div>
-            {{$t('settings.replies_in_timeline')}}
-            <label for="replyVisibility" class="select">
-              <select id="replyVisibility" v-model="replyVisibilityLocal">
-                <option value="all" selected>{{$t('settings.reply_visibility_all')}}</option>
-                <option value="following">{{$t('settings.reply_visibility_following')}}</option>
-                <option value="self">{{$t('settings.reply_visibility_self')}}</option>
-              </select>
-              <i class="icon-down-open"/>
-            </label>
-          </div>
-          <div>
-            <input type="checkbox" id="hidePostStats" v-model="hidePostStatsLocal">
-            <label for="hidePostStats">
-              {{$t('settings.hide_post_stats')}} {{$t('settings.instance_default', { value: hidePostStatsDefault })}}
-            </label>
-          </div>
-          <div>
-            <input type="checkbox" id="hideUserStats" v-model="hideUserStatsLocal">
-            <label for="hideUserStats">
-              {{$t('settings.hide_user_stats')}} {{$t('settings.instance_default', { value: hideUserStatsDefault })}}
-            </label>
-          </div>
-        </div>
-        <div class="setting-item">
-          <div>
-            <p>{{$t('settings.filtering_explanation')}}</p>
-            <textarea id="muteWords" v-model="muteWordsString"></textarea>
-          </div>
-          <div>
-            <input type="checkbox" id="hideFilteredStatuses" v-model="hideFilteredStatusesLocal">
-            <label for="hideFilteredStatuses">
-              {{$t('settings.hide_filtered_statuses')}} {{$t('settings.instance_default', { value: hideFilteredStatusesDefault })}}
-            </label>
-          </div>
-        </div>
-      </div>
-      <div :label="$t('settings.version.title')" >
-        <div class="setting-item">
-          <ul class="setting-list">
-            <li>
-              <p>{{$t('settings.version.backend_version')}}</p>
-              <ul class="option-list">
                 <li>
-                  <a :href="backendVersionLink" target="_blank">{{backendVersion}}</a>
+                  <input
+                    id="minimalScopesMode"
+                    v-model="minimalScopesModeLocal"
+                    type="checkbox"
+                  >
+                  <label for="minimalScopesMode">
+                    {{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeDefault }) }}
+                  </label>
+                </li>
+                <li>
+                  <input
+                    id="autohideFloatingPostButton"
+                    v-model="autohideFloatingPostButtonLocal"
+                    type="checkbox"
+                  >
+                  <label for="autohideFloatingPostButton">{{ $t('settings.autohide_floating_post_button') }}</label>
                 </li>
               </ul>
-            </li>
-            <li>
-              <p>{{$t('settings.version.frontend_version')}}</p>
-              <ul class="option-list">
+            </div>
+
+            <div class="setting-item">
+              <h2>{{ $t('settings.attachments') }}</h2>
+              <ul class="setting-list">
                 <li>
-                  <a :href="frontendVersionLink" target="_blank">{{frontendVersion}}</a>
+                  <input
+                    id="hideAttachments"
+                    v-model="hideAttachmentsLocal"
+                    type="checkbox"
+                  >
+                  <label for="hideAttachments">{{ $t('settings.hide_attachments_in_tl') }}</label>
+                </li>
+                <li>
+                  <input
+                    id="hideAttachmentsInConv"
+                    v-model="hideAttachmentsInConvLocal"
+                    type="checkbox"
+                  >
+                  <label for="hideAttachmentsInConv">{{ $t('settings.hide_attachments_in_convo') }}</label>
+                </li>
+                <li>
+                  <label for="maxThumbnails">{{ $t('settings.max_thumbnails') }}</label>
+                  <input
+                    id="maxThumbnails"
+                    v-model.number="maxThumbnails"
+                    class="number-input"
+                    type="number"
+                    min="0"
+                    step="1"
+                  >
+                </li>
+                <li>
+                  <input
+                    id="hideNsfw"
+                    v-model="hideNsfwLocal"
+                    type="checkbox"
+                  >
+                  <label for="hideNsfw">{{ $t('settings.nsfw_clickthrough') }}</label>
+                </li>
+                <ul class="setting-list suboptions">
+                  <li>
+                    <input
+                      id="preloadImage"
+                      v-model="preloadImage"
+                      :disabled="!hideNsfwLocal"
+                      type="checkbox"
+                    >
+                    <label for="preloadImage">{{ $t('settings.preload_images') }}</label>
+                  </li>
+                  <li>
+                    <input
+                      id="useOneClickNsfw"
+                      v-model="useOneClickNsfw"
+                      :disabled="!hideNsfwLocal"
+                      type="checkbox"
+                    >
+                    <label for="useOneClickNsfw">{{ $t('settings.use_one_click_nsfw') }}</label>
+                  </li>
+                </ul>
+                <li>
+                  <input
+                    id="stopGifs"
+                    v-model="stopGifs"
+                    type="checkbox"
+                  >
+                  <label for="stopGifs">{{ $t('settings.stop_gifs') }}</label>
+                </li>
+                <li>
+                  <input
+                    id="loopVideo"
+                    v-model="loopVideoLocal"
+                    type="checkbox"
+                  >
+                  <label for="loopVideo">{{ $t('settings.loop_video') }}</label>
+                  <ul
+                    class="setting-list suboptions"
+                    :class="[{disabled: !streamingLocal}]"
+                  >
+                    <li>
+                      <input
+                        id="loopVideoSilentOnly"
+                        v-model="loopVideoSilentOnlyLocal"
+                        :disabled="!loopVideoLocal || !loopSilentAvailable"
+                        type="checkbox"
+                      >
+                      <label for="loopVideoSilentOnly">{{ $t('settings.loop_video_silent_only') }}</label>
+                      <div
+                        v-if="!loopSilentAvailable"
+                        class="unavailable"
+                      >
+                        <i class="icon-globe" />! {{ $t('settings.limited_availability') }}
+                      </div>
+                    </li>
+                  </ul>
+                </li>
+                <li>
+                  <input
+                    id="playVideosInModal"
+                    v-model="playVideosInModal"
+                    type="checkbox"
+                  >
+                  <label for="playVideosInModal">{{ $t('settings.play_videos_in_modal') }}</label>
+                </li>
+                <li>
+                  <input
+                    id="useContainFit"
+                    v-model="useContainFit"
+                    type="checkbox"
+                  >
+                  <label for="useContainFit">{{ $t('settings.use_contain_fit') }}</label>
                 </li>
               </ul>
-            </li>
-          </ul>
-        </div>
-      </div>
-    </tab-switcher>
-</keep-alive>
+            </div>
+
+            <div class="setting-item">
+              <h2>{{ $t('settings.notifications') }}</h2>
+              <ul class="setting-list">
+                <li>
+                  <input
+                    id="webPushNotifications"
+                    v-model="webPushNotificationsLocal"
+                    type="checkbox"
+                  >
+                  <label for="webPushNotifications">
+                    {{ $t('settings.enable_web_push_notifications') }}
+                  </label>
+                </li>
+              </ul>
+            </div>
+          </div>
+
+          <div :label="$t('settings.theme')">
+            <div class="setting-item">
+              <style-switcher />
+            </div>
+          </div>
+
+          <div :label="$t('settings.filtering')">
+            <div class="setting-item">
+              <div class="select-multiple">
+                <span class="label">{{ $t('settings.notification_visibility') }}</span>
+                <ul class="option-list">
+                  <li>
+                    <input
+                      id="notification-visibility-likes"
+                      v-model="notificationVisibilityLocal.likes"
+                      type="checkbox"
+                    >
+                    <label for="notification-visibility-likes">
+                      {{ $t('settings.notification_visibility_likes') }}
+                    </label>
+                  </li>
+                  <li>
+                    <input
+                      id="notification-visibility-repeats"
+                      v-model="notificationVisibilityLocal.repeats"
+                      type="checkbox"
+                    >
+                    <label for="notification-visibility-repeats">
+                      {{ $t('settings.notification_visibility_repeats') }}
+                    </label>
+                  </li>
+                  <li>
+                    <input
+                      id="notification-visibility-follows"
+                      v-model="notificationVisibilityLocal.follows"
+                      type="checkbox"
+                    >
+                    <label for="notification-visibility-follows">
+                      {{ $t('settings.notification_visibility_follows') }}
+                    </label>
+                  </li>
+                  <li>
+                    <input
+                      id="notification-visibility-mentions"
+                      v-model="notificationVisibilityLocal.mentions"
+                      type="checkbox"
+                    >
+                    <label for="notification-visibility-mentions">
+                      {{ $t('settings.notification_visibility_mentions') }}
+                    </label>
+                  </li>
+                </ul>
+              </div>
+              <div>
+                {{ $t('settings.replies_in_timeline') }}
+                <label
+                  for="replyVisibility"
+                  class="select"
+                >
+                  <select
+                    id="replyVisibility"
+                    v-model="replyVisibilityLocal"
+                  >
+                    <option
+                      value="all"
+                      selected
+                    >{{ $t('settings.reply_visibility_all') }}</option>
+                    <option value="following">{{ $t('settings.reply_visibility_following') }}</option>
+                    <option value="self">{{ $t('settings.reply_visibility_self') }}</option>
+                  </select>
+                  <i class="icon-down-open" />
+                </label>
+              </div>
+              <div>
+                <input
+                  id="hidePostStats"
+                  v-model="hidePostStatsLocal"
+                  type="checkbox"
+                >
+                <label for="hidePostStats">
+                  {{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsDefault }) }}
+                </label>
+              </div>
+              <div>
+                <input
+                  id="hideUserStats"
+                  v-model="hideUserStatsLocal"
+                  type="checkbox"
+                >
+                <label for="hideUserStats">
+                  {{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsDefault }) }}
+                </label>
+              </div>
+            </div>
+            <div class="setting-item">
+              <div>
+                <p>{{ $t('settings.filtering_explanation') }}</p>
+                <textarea
+                  id="muteWords"
+                  v-model="muteWordsString"
+                />
+              </div>
+              <div>
+                <input
+                  id="hideFilteredStatuses"
+                  v-model="hideFilteredStatusesLocal"
+                  type="checkbox"
+                >
+                <label for="hideFilteredStatuses">
+                  {{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesDefault }) }}
+                </label>
+              </div>
+            </div>
+          </div>
+          <div :label="$t('settings.version.title')">
+            <div class="setting-item">
+              <ul class="setting-list">
+                <li>
+                  <p>{{ $t('settings.version.backend_version') }}</p>
+                  <ul class="option-list">
+                    <li>
+                      <a
+                        :href="backendVersionLink"
+                        target="_blank"
+                      >{{ backendVersion }}</a>
+                    </li>
+                  </ul>
+                </li>
+                <li>
+                  <p>{{ $t('settings.version.frontend_version') }}</p>
+                  <ul class="option-list">
+                    <li>
+                      <a
+                        :href="frontendVersionLink"
+                        target="_blank"
+                      >{{ frontendVersion }}</a>
+                    </li>
+                  </ul>
+                </li>
+              </ul>
+            </div>
+          </div>
+        </tab-switcher>
+      </keep-alive>
+    </div>
   </div>
-</div>
 </template>
 
 <script src="./settings.js">
 </script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-
-.setting-item {
-  border-bottom: 2px solid var(--fg, $fallback--fg);
-  margin: 1em 1em 1.4em;
-  padding-bottom: 1.4em;
-
-  > div {
-    margin-bottom: .5em;
-    &:last-child {
-      margin-bottom: 0;
-    }
-  }
-
-  &:last-child {
-    border-bottom: none;
-    padding-bottom: 0;
-    margin-bottom: 1em;
-  }
-
-  select {
-    min-width: 10em;
-  }
-
-
-  textarea {
-    width: 100%;
-    height: 100px;
-  }
-
-  .unavailable,
-  .unavailable i {
-    color: var(--cRed, $fallback--cRed);
-    color: $fallback--cRed;
-  }
-
-  .btn {
-    min-height: 28px;
-    min-width: 10em;
-    padding: 0 2em;
-  }
-
-  .number-input {
-    max-width: 6em;
-  }
-}
-.select-multiple {
-  display: flex;
-  .option-list {
-    margin: 0;
-    padding-left: .5em;
-  }
-}
-.setting-list,
-.option-list{
-  list-style-type: none;
-  padding-left: 2em;
-  li {
-    margin-bottom: 0.5em;
-  }
-  .suboptions {
-    margin-top: 0.3em
-  }
-}
-</style>
diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue
index 744925d4..de8a42d1 100644
--- a/src/components/shadow_control/shadow_control.vue
+++ b/src/components/shadow_control/shadow_control.vue
@@ -1,134 +1,207 @@
 <template>
-<div class="shadow-control" :class="{ disabled: !present }">
-  <div class="shadow-preview-container">
-    <div :disabled="!present" class="y-shift-control">
-      <input
-        v-model="selected.y"
+  <div
+    class="shadow-control"
+    :class="{ disabled: !present }"
+  >
+    <div class="shadow-preview-container">
+      <div
         :disabled="!present"
-        class="input-number"
-        type="number">
-      <div class="wrap">
+        class="y-shift-control"
+      >
         <input
           v-model="selected.y"
           :disabled="!present"
-          class="input-range"
-          type="range"
-          max="20"
-          min="-20">
+          class="input-number"
+          type="number"
+        >
+        <div class="wrap">
+          <input
+            v-model="selected.y"
+            :disabled="!present"
+            class="input-range"
+            type="range"
+            max="20"
+            min="-20"
+          >
+        </div>
       </div>
-    </div>
-    <div class="preview-window">
-      <div class="preview-block" :style="style"></div>
-    </div>
-    <div :disabled="!present" class="x-shift-control">
-      <input
-        v-model="selected.x"
+      <div class="preview-window">
+        <div
+          class="preview-block"
+          :style="style"
+        />
+      </div>
+      <div
         :disabled="!present"
-        class="input-number"
-        type="number">
-      <div class="wrap">
+        class="x-shift-control"
+      >
         <input
           v-model="selected.x"
           :disabled="!present"
+          class="input-number"
+          type="number"
+        >
+        <div class="wrap">
+          <input
+            v-model="selected.x"
+            :disabled="!present"
+            class="input-range"
+            type="range"
+            max="20"
+            min="-20"
+          >
+        </div>
+      </div>
+    </div>
+
+    <div class="shadow-tweak">
+      <div
+        :disabled="usingFallback"
+        class="id-control style-control"
+      >
+        <label
+          for="shadow-switcher"
+          class="select"
+          :disabled="!ready || usingFallback"
+        >
+          <select
+            id="shadow-switcher"
+            v-model="selectedId"
+            class="shadow-switcher"
+            :disabled="!ready || usingFallback"
+          >
+            <option
+              v-for="(shadow, index) in cValue"
+              :key="index"
+              :value="index"
+            >
+              {{ $t('settings.style.shadows.shadow_id', { value: index }) }}
+            </option>
+          </select>
+          <i class="icon-down-open" />
+        </label>
+        <button
+          class="btn btn-default"
+          :disabled="!ready || !present"
+          @click="del"
+        >
+          <i class="icon-cancel" />
+        </button>
+        <button
+          class="btn btn-default"
+          :disabled="!moveUpValid"
+          @click="moveUp"
+        >
+          <i class="icon-up-open" />
+        </button>
+        <button
+          class="btn btn-default"
+          :disabled="!moveDnValid"
+          @click="moveDn"
+        >
+          <i class="icon-down-open" />
+        </button>
+        <button
+          class="btn btn-default"
+          :disabled="usingFallback"
+          @click="add"
+        >
+          <i class="icon-plus" />
+        </button>
+      </div>
+      <div
+        :disabled="!present"
+        class="inset-control style-control"
+      >
+        <label
+          for="inset"
+          class="label"
+        >
+          {{ $t('settings.style.shadows.inset') }}
+        </label>
+        <input
+          id="inset"
+          v-model="selected.inset"
+          :disabled="!present"
+          name="inset"
+          class="input-inset"
+          type="checkbox"
+        >
+        <label
+          class="checkbox-label"
+          for="inset"
+        />
+      </div>
+      <div
+        :disabled="!present"
+        class="blur-control style-control"
+      >
+        <label
+          for="spread"
+          class="label"
+        >
+          {{ $t('settings.style.shadows.blur') }}
+        </label>
+        <input
+          id="blur"
+          v-model="selected.blur"
+          :disabled="!present"
+          name="blur"
           class="input-range"
           type="range"
           max="20"
-          min="-20">
+          min="0"
+        >
+        <input
+          v-model="selected.blur"
+          :disabled="!present"
+          class="input-number"
+          type="number"
+          min="0"
+        >
       </div>
+      <div
+        :disabled="!present"
+        class="spread-control style-control"
+      >
+        <label
+          for="spread"
+          class="label"
+        >
+          {{ $t('settings.style.shadows.spread') }}
+        </label>
+        <input
+          id="spread"
+          v-model="selected.spread"
+          :disabled="!present"
+          name="spread"
+          class="input-range"
+          type="range"
+          max="20"
+          min="-20"
+        >
+        <input
+          v-model="selected.spread"
+          :disabled="!present"
+          class="input-number"
+          type="number"
+        >
+      </div>
+      <ColorInput
+        v-model="selected.color"
+        :disabled="!present"
+        :label="$t('settings.style.common.color')"
+        name="shadow"
+      />
+      <OpacityInput
+        v-model="selected.alpha"
+        :disabled="!present"
+      />
+      <p>
+        {{ $t('settings.style.shadows.hint') }}
+      </p>
     </div>
   </div>
-
-  <div class="shadow-tweak">
-    <div :disabled="usingFallback" class="id-control style-control">
-      <label for="shadow-switcher" class="select" :disabled="!ready || usingFallback">
-        <select
-          v-model="selectedId" class="shadow-switcher"
-          :disabled="!ready || usingFallback"
-          id="shadow-switcher">
-          <option v-for="(shadow, index) in cValue" :value="index">
-            {{$t('settings.style.shadows.shadow_id', { value: index })}}
-          </option>
-        </select>
-        <i class="icon-down-open"/>
-      </label>
-      <button class="btn btn-default" :disabled="!ready || !present" @click="del">
-        <i class="icon-cancel"/>
-      </button>
-      <button class="btn btn-default" :disabled="!moveUpValid" @click="moveUp">
-        <i class="icon-up-open"/>
-      </button>
-      <button class="btn btn-default" :disabled="!moveDnValid" @click="moveDn">
-        <i class="icon-down-open"/>
-      </button>
-      <button class="btn btn-default" :disabled="usingFallback" @click="add">
-        <i class="icon-plus"/>
-      </button>
-    </div>
-    <div :disabled="!present" class="inset-control style-control">
-      <label for="inset" class="label">
-        {{$t('settings.style.shadows.inset')}}
-      </label>
-      <input
-        v-model="selected.inset"
-        :disabled="!present"
-        name="inset"
-        id="inset"
-        class="input-inset"
-        type="checkbox">
-      <label class="checkbox-label" for="inset"></label>
-    </div>
-    <div :disabled="!present" class="blur-control style-control">
-      <label for="spread" class="label">
-        {{$t('settings.style.shadows.blur')}}
-      </label>
-      <input
-        v-model="selected.blur"
-        :disabled="!present"
-        name="blur"
-        id="blur"
-        class="input-range"
-        type="range"
-        max="20"
-        min="0">
-      <input
-        v-model="selected.blur"
-        :disabled="!present"
-        class="input-number"
-        type="number"
-        min="0">
-    </div>
-    <div :disabled="!present" class="spread-control style-control">
-      <label for="spread" class="label">
-        {{$t('settings.style.shadows.spread')}}
-      </label>
-      <input
-        v-model="selected.spread"
-        :disabled="!present"
-        name="spread"
-        id="spread"
-        class="input-range"
-        type="range"
-        max="20"
-        min="-20">
-      <input
-        v-model="selected.spread"
-        :disabled="!present"
-        class="input-number"
-        type="number">
-    </div>
-    <ColorInput
-      v-model="selected.color"
-      :disabled="!present"
-      :label="$t('settings.style.common.color')"
-      name="shadow"/>
-    <OpacityInput
-      v-model="selected.alpha"
-      :disabled="!present"/>
-    <p>
-      {{$t('settings.style.shadows.hint')}}
-    </p>
-  </div>
-</div>
 </template>
 
 <script src="./shadow_control.js" ></script>
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 9abb8cef..5b2d4473 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -1,58 +1,98 @@
 <template>
-  <div class="side-drawer-container"
+  <div
+    class="side-drawer-container"
     :class="{ 'side-drawer-container-closed': closed, 'side-drawer-container-open': !closed }"
   >
-    <div class="side-drawer-darken" :class="{ 'side-drawer-darken-closed': closed}" />
-    <div class="side-drawer"
+    <div
+      class="side-drawer-darken"
+      :class="{ 'side-drawer-darken-closed': closed}"
+    />
+    <div
+      class="side-drawer"
       :class="{'side-drawer-closed': closed}"
       @touchstart="touchStart"
       @touchmove="touchMove"
     >
-      <div class="side-drawer-heading" @click="toggleDrawer">
-        <UserCard :user="currentUser" :hideBio="true" v-if="currentUser"/>
-        <div class="side-drawer-logo-wrapper" v-else>
-          <img :src="logo"/>
-          <span>{{sitename}}</span>
+      <div
+        class="side-drawer-heading"
+        @click="toggleDrawer"
+      >
+        <UserCard
+          v-if="currentUser"
+          :user="currentUser"
+          :hide-bio="true"
+        />
+        <div
+          v-else
+          class="side-drawer-logo-wrapper"
+        >
+          <img :src="logo">
+          <span>{{ sitename }}</span>
         </div>
       </div>
       <ul>
-        <li v-if="!currentUser" @click="toggleDrawer">
+        <li
+          v-if="!currentUser"
+          @click="toggleDrawer"
+        >
           <router-link :to="{ name: 'login' }">
             {{ $t("login.login") }}
           </router-link>
         </li>
-        <li v-if="currentUser" @click="toggleDrawer">
+        <li
+          v-if="currentUser"
+          @click="toggleDrawer"
+        >
           <router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
             {{ $t("nav.dms") }}
           </router-link>
         </li>
+        <li
+          v-if="currentUser"
+          @click="toggleDrawer"
+        >
+          <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
+            {{ $t("nav.interactions") }}
+          </router-link>
+        </li>
       </ul>
       <ul>
-        <li v-if="currentUser" @click="toggleDrawer">
+        <li
+          v-if="currentUser"
+          @click="toggleDrawer"
+        >
           <router-link :to="{ name: 'friends' }">
             {{ $t("nav.timeline") }}
           </router-link>
         </li>
-        <li v-if="currentUser && currentUser.locked" @click="toggleDrawer">
-          <router-link to='/friend-requests'>
+        <li
+          v-if="currentUser && currentUser.locked"
+          @click="toggleDrawer"
+        >
+          <router-link to="/friend-requests">
             {{ $t("nav.friend_requests") }}
-            <span v-if='followRequestCount > 0' class="badge follow-request-count">
-              {{followRequestCount}}
+            <span
+              v-if="followRequestCount > 0"
+              class="badge follow-request-count"
+            >
+              {{ followRequestCount }}
             </span>
-
           </router-link>
         </li>
         <li @click="toggleDrawer">
-          <router-link to='/main/public'>
+          <router-link to="/main/public">
             {{ $t("nav.public_tl") }}
           </router-link>
         </li>
         <li @click="toggleDrawer">
-          <router-link to='/main/all'>
+          <router-link to="/main/all">
             {{ $t("nav.twkn") }}
           </router-link>
         </li>
-        <li v-if="currentUser && chat" @click="toggleDrawer">
+        <li
+          v-if="currentUser && chat"
+          @click="toggleDrawer"
+        >
           <router-link :to="{ name: 'chat' }">
             {{ $t("nav.chat") }}
           </router-link>
@@ -60,11 +100,14 @@
       </ul>
       <ul>
         <li @click="toggleDrawer">
-          <router-link :to="{ name: 'user-search' }">
-            {{ $t("nav.user_search") }}
+          <router-link :to="{ name: 'search' }">
+            {{ $t("nav.search") }}
           </router-link>
         </li>
-        <li v-if="currentUser && suggestionsEnabled" @click="toggleDrawer">
+        <li
+          v-if="currentUser && suggestionsEnabled"
+          @click="toggleDrawer"
+        >
           <router-link :to="{ name: 'who-to-follow' }">
             {{ $t("nav.who_to_follow") }}
           </router-link>
@@ -79,17 +122,24 @@
             {{ $t("nav.about") }}
           </router-link>
         </li>
-        <li v-if="currentUser" @click="toggleDrawer">
-          <a @click="doLogout" href="#">
+        <li
+          v-if="currentUser"
+          @click="toggleDrawer"
+        >
+          <a
+            href="#"
+            @click="doLogout"
+          >
             {{ $t("login.logout") }}
           </a>
         </li>
       </ul>
     </div>
-    <div class="side-drawer-click-outside"
-      @click.stop.prevent="toggleDrawer"
+    <div
+      class="side-drawer-click-outside"
       :class="{'side-drawer-click-outside-closed': closed}"
-    ></div>
+      @click.stop.prevent="toggleDrawer"
+    />
   </div>
 </template>
 
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 0295cd04..3c172e5b 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -1,17 +1,20 @@
 import Attachment from '../attachment/attachment.vue'
 import FavoriteButton from '../favorite_button/favorite_button.vue'
 import RetweetButton from '../retweet_button/retweet_button.vue'
-import DeleteButton from '../delete_button/delete_button.vue'
+import Poll from '../poll/poll.vue'
+import ExtraButtons from '../extra_buttons/extra_buttons.vue'
 import PostStatusForm from '../post_status_form/post_status_form.vue'
 import UserCard from '../user_card/user_card.vue'
 import UserAvatar from '../user_avatar/user_avatar.vue'
 import Gallery from '../gallery/gallery.vue'
 import LinkPreview from '../link-preview/link-preview.vue'
+import AvatarList from '../avatar_list/avatar_list.vue'
+import Timeago from '../timeago/timeago.vue'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 import fileType from 'src/services/file_type/file_type.service'
 import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
 import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
-import { filter, find, unescape } from 'lodash'
+import { filter, find, unescape, uniqBy } from 'lodash'
 
 const Status = {
   name: 'Status',
@@ -25,18 +28,19 @@ const Status = {
     'replies',
     'isPreview',
     'noHeading',
-    'inlineExpanded'
+    'inlineExpanded',
+    'showPinned'
   ],
   data () {
     return {
       replying: false,
-      expanded: false,
       unmuted: false,
       userExpanded: false,
       preview: null,
       showPreview: false,
       showingTall: this.inConversation && this.focused,
       showingLongSubject: false,
+      error: null,
       expandingSubject: typeof this.$store.state.config.collapseMessageWithSubject === 'undefined'
         ? !this.$store.state.instance.collapseMessageWithSubject
         : !this.$store.state.config.collapseMessageWithSubject,
@@ -97,13 +101,18 @@ const Status = {
         return this.statusoid
       }
     },
+    statusFromGlobalRepository () {
+      // NOTE: Consider to replace status with statusFromGlobalRepository
+      return this.$store.state.statuses.allStatusesObject[this.status.id]
+    },
     loggedIn () {
       return !!this.$store.state.users.currentUser
     },
     muteWordHits () {
       const statusText = this.status.text.toLowerCase()
+      const statusSummary = this.status.summary.toLowerCase()
       const hits = filter(this.muteWords, (muteWord) => {
-        return statusText.includes(muteWord.toLowerCase())
+        return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase())
       })
 
       return hits
@@ -156,7 +165,7 @@ const Status = {
       if (this.$store.state.config.replyVisibility === 'all') {
         return false
       }
-      if (this.inlineExpanded || this.expanded || this.inConversation || !this.isReply) {
+      if (this.inConversation || !this.isReply) {
         return false
       }
       if (this.status.user.id === this.$store.state.users.currentUser.id) {
@@ -165,12 +174,13 @@ const Status = {
       if (this.status.type === 'retweet') {
         return false
       }
-      var checkFollowing = this.$store.state.config.replyVisibility === 'following'
+      const checkFollowing = this.$store.state.config.replyVisibility === 'following'
       for (var i = 0; i < this.status.attentions.length; ++i) {
         if (this.status.user.id === this.status.attentions[i].id) {
           continue
         }
-        if (checkFollowing && this.status.attentions[i].following) {
+        const taggedUser = this.$store.getters.findUser(this.status.attentions[i].id)
+        if (checkFollowing && taggedUser && taggedUser.following) {
           return false
         }
         if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) {
@@ -210,10 +220,10 @@ const Status = {
       if (!this.status.summary) return ''
       const decodedSummary = unescape(this.status.summary)
       const behavior = typeof this.$store.state.config.subjectLineBehavior === 'undefined'
-            ? this.$store.state.instance.subjectLineBehavior
-            : this.$store.state.config.subjectLineBehavior
+        ? this.$store.state.instance.subjectLineBehavior
+        : this.$store.state.config.subjectLineBehavior
       const startsWithRe = decodedSummary.match(/^re[: ]/i)
-      if (behavior !== 'noop' && startsWithRe || behavior === 'masto') {
+      if ((behavior !== 'noop' && startsWithRe) || behavior === 'masto') {
         return decodedSummary
       } else if (behavior === 'email') {
         return 're: '.concat(decodedSummary)
@@ -257,18 +267,40 @@ const Status = {
         return this.status.statusnet_html
       }
       return this.status.summary_html + '<br />' + this.status.statusnet_html
+    },
+    combinedFavsAndRepeatsUsers () {
+      // Use the status from the global status repository since favs and repeats are saved in it
+      const combinedUsers = [].concat(
+        this.statusFromGlobalRepository.favoritedBy,
+        this.statusFromGlobalRepository.rebloggedBy
+      )
+      return uniqBy(combinedUsers, 'id')
+    },
+    ownStatus () {
+      return this.status.user.id === this.$store.state.users.currentUser.id
+    },
+    tags () {
+      return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')
+    },
+    hidePostStats () {
+      return typeof this.$store.state.config.hidePostStats === 'undefined'
+        ? this.$store.state.instance.hidePostStats
+        : this.$store.state.config.hidePostStats
     }
   },
   components: {
     Attachment,
     FavoriteButton,
     RetweetButton,
-    DeleteButton,
+    ExtraButtons,
     PostStatusForm,
+    Poll,
     UserCard,
     UserAvatar,
     Gallery,
-    LinkPreview
+    LinkPreview,
+    AvatarList,
+    Timeago
   },
   methods: {
     visibilityIcon (visibility) {
@@ -283,12 +315,15 @@ const Status = {
           return 'icon-globe'
       }
     },
+    showError (error) {
+      this.error = error
+    },
+    clearError () {
+      this.error = undefined
+    },
     linkClicked (event) {
-      let { target } = event
-      if (target.tagName === 'SPAN') {
-        target = target.parentNode
-      }
-      if (target.tagName === 'A') {
+      const target = event.target.closest('.status-content a')
+      if (target) {
         if (target.className.match(/mention/)) {
           const href = target.href
           const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href))
@@ -350,7 +385,7 @@ const Status = {
         this.preview = find(statuses, { 'id': targetId })
         // or if we have to fetch it
         if (!this.preview) {
-          this.$store.state.api.backendInteractor.fetchStatus({id}).then((status) => {
+          this.$store.state.api.backendInteractor.fetchStatus({ id }).then((status) => {
             this.preview = status
           })
         }
@@ -387,6 +422,18 @@ const Status = {
           window.scrollBy(0, rect.bottom - window.innerHeight + 50)
         }
       }
+    },
+    'status.repeat_num': function (num) {
+      // refetch repeats when repeat_num is changed in any way
+      if (this.isFocused && this.statusFromGlobalRepository.rebloggedBy && this.statusFromGlobalRepository.rebloggedBy.length !== num) {
+        this.$store.dispatch('fetchRepeats', this.status.id)
+      }
+    },
+    'status.fave_num': function (num) {
+      // refetch favs when fave_num is changed in any way
+      if (this.isFocused && this.statusFromGlobalRepository.favoritedBy && this.statusFromGlobalRepository.favoritedBy.length !== num) {
+        this.$store.dispatch('fetchFavs', this.status.id)
+      }
     }
   },
   filters: {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 690e8318..ab506632 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -1,155 +1,431 @@
 <template>
-  <div class="status-el" v-if="!hideStatus" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]">
+  <!-- eslint-disable vue/no-v-html -->
+  <div
+    v-if="!hideStatus"
+    class="status-el"
+    :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]"
+  >
+    <div
+      v-if="error"
+      class="alert error"
+    >
+      {{ error }}
+      <i
+        class="button-icon icon-cancel"
+        @click="clearError"
+      />
+    </div>
     <template v-if="muted && !isPreview">
       <div class="media status container muted">
         <small>
           <router-link :to="userProfileLink">
-            {{status.user.screen_name}}
+            {{ status.user.screen_name }}
           </router-link>
         </small>
-        <small class="muteWords">{{muteWordHits.join(', ')}}</small>
-        <a href="#" class="unmute" @click.prevent="toggleMute"><i class="button-icon icon-eye-off"></i></a>
+        <small class="muteWords">{{ muteWordHits.join(', ') }}</small>
+        <a
+          href="#"
+          class="unmute"
+          @click.prevent="toggleMute"
+        ><i class="button-icon icon-eye-off" /></a>
       </div>
     </template>
     <template v-else>
-      <div v-if="retweet && !noHeading && !inConversation" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info">
-        <UserAvatar class="media-left" v-if="retweet" :betterShadow="betterShadow" :src="statusoid.user.profile_image_url_original"/>
+      <div
+        v-if="showPinned && statusoid.pinned"
+        class="status-pin"
+      >
+        <i class="fa icon-pin faint" />
+        <span class="faint">{{ $t('status.pinned') }}</span>
+      </div>
+      <div
+        v-if="retweet && !noHeading && !inConversation"
+        :class="[repeaterClass, { highlighted: repeaterStyle }]"
+        :style="[repeaterStyle]"
+        class="media container retweet-info"
+      >
+        <UserAvatar
+          v-if="retweet"
+          class="media-left"
+          :better-shadow="betterShadow"
+          :user="statusoid.user"
+        />
         <div class="media-body faint">
           <span class="user-name">
-            <router-link v-if="retweeterHtml" :to="retweeterProfileLink" v-html="retweeterHtml"/>
-            <router-link v-else :to="retweeterProfileLink">{{retweeter}}</router-link>
+            <router-link
+              v-if="retweeterHtml"
+              :to="retweeterProfileLink"
+              v-html="retweeterHtml"
+            />
+            <router-link
+              v-else
+              :to="retweeterProfileLink"
+            >{{ retweeter }}</router-link>
           </span>
-          <i class='fa icon-retweet retweeted' :title="$t('tool_tip.repeat')"></i>
-          {{$t('timeline.repeated')}}
+          <i
+            class="fa icon-retweet retweeted"
+            :title="$t('tool_tip.repeat')"
+          />
+          {{ $t('timeline.repeated') }}
         </div>
       </div>
 
-      <div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet && !inConversation }]" :style="[ userStyle ]" class="media status">
-        <div v-if="!noHeading" class="media-left">
-          <router-link :to="userProfileLink" @click.stop.prevent.capture.native="toggleUserExpanded">
-            <UserAvatar :compact="compact" :betterShadow="betterShadow" :src="status.user.profile_image_url_original"/>
+      <div
+        :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet && !inConversation }]"
+        :style="[ userStyle ]"
+        class="media status"
+        :data-tags="tags"
+      >
+        <div
+          v-if="!noHeading"
+          class="media-left"
+        >
+          <router-link
+            :to="userProfileLink"
+            @click.stop.prevent.capture.native="toggleUserExpanded"
+          >
+            <UserAvatar
+              :compact="compact"
+              :better-shadow="betterShadow"
+              :user="status.user"
+            />
           </router-link>
         </div>
         <div class="status-body">
-          <UserCard :user="status.user" :rounded="true" :bordered="true" class="status-usercard" v-if="userExpanded"/>
-          <div v-if="!noHeading" class="media-heading">
+          <UserCard
+            v-if="userExpanded"
+            :user="status.user"
+            :rounded="true"
+            :bordered="true"
+            class="status-usercard"
+          />
+          <div
+            v-if="!noHeading"
+            class="media-heading"
+          >
             <div class="heading-name-row">
               <div class="name-and-account-name">
-                <h4 class="user-name" v-if="status.user.name_html" v-html="status.user.name_html"></h4>
-                <h4 class="user-name" v-else>{{status.user.name}}</h4>
-                <router-link class="account-name" :to="userProfileLink">
-                  {{status.user.screen_name}}
+                <h4
+                  v-if="status.user.name_html"
+                  class="user-name"
+                  v-html="status.user.name_html"
+                />
+                <h4
+                  v-else
+                  class="user-name"
+                >
+                  {{ status.user.name }}
+                </h4>
+                <router-link
+                  class="account-name"
+                  :to="userProfileLink"
+                >
+                  {{ status.user.screen_name }}
                 </router-link>
               </div>
 
               <span class="heading-right">
-                <router-link class="timeago faint-link" :to="{ name: 'conversation', params: { id: status.id } }">
-                  <timeago :since="status.created_at" :auto-update="60"></timeago>
+                <router-link
+                  class="timeago faint-link"
+                  :to="{ name: 'conversation', params: { id: status.id } }"
+                >
+                  <Timeago
+                    :time="status.created_at"
+                    :auto-update="60"
+                  />
                 </router-link>
-                <div class="button-icon visibility-icon" v-if="status.visibility">
-                  <i :class="visibilityIcon(status.visibility)" :title="status.visibility | capitalize"></i>
+                <div
+                  v-if="status.visibility"
+                  class="button-icon visibility-icon"
+                >
+                  <i
+                    :class="visibilityIcon(status.visibility)"
+                    :title="status.visibility | capitalize"
+                  />
                 </div>
-                <a :href="status.external_url" target="_blank" v-if="!status.is_local && !isPreview" class="source_url" title="Source">
-                  <i class="button-icon icon-link-ext-alt"></i>
+                <a
+                  v-if="!status.is_local && !isPreview"
+                  :href="status.external_url"
+                  target="_blank"
+                  class="source_url"
+                  title="Source"
+                >
+                  <i class="button-icon icon-link-ext-alt" />
                 </a>
                 <template v-if="expandable && !isPreview">
-                  <a href="#" @click.prevent="toggleExpanded" title="Expand">
-                    <i class="button-icon icon-plus-squared"></i>
+                  <a
+                    href="#"
+                    title="Expand"
+                    @click.prevent="toggleExpanded"
+                  >
+                    <i class="button-icon icon-plus-squared" />
                   </a>
                 </template>
-                <a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="button-icon icon-eye-off"></i></a>
+                <a
+                  v-if="unmuted"
+                  href="#"
+                  @click.prevent="toggleMute"
+                ><i class="button-icon icon-eye-off" /></a>
               </span>
             </div>
 
             <div class="heading-reply-row">
-              <div v-if="isReply" class="reply-to-and-accountname">
-                <a class="reply-to"
-                  href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)"
+              <div
+                v-if="isReply"
+                class="reply-to-and-accountname"
+              >
+                <a
+                  class="reply-to"
+                  href="#"
                   :aria-label="$t('tool_tip.reply')"
+                  @click.prevent="gotoOriginal(status.in_reply_to_status_id)"
                   @mouseenter.prevent.stop="replyEnter(status.in_reply_to_status_id, $event)"
                   @mouseleave.prevent.stop="replyLeave()"
                 >
-                  <i class="button-icon icon-reply" v-if="!isPreview"></i>
-                  <span class="faint-link reply-to-text">{{$t('status.reply_to')}}</span>
+                  <i
+                    v-if="!isPreview"
+                    class="button-icon icon-reply"
+                  />
+                  <span class="faint-link reply-to-text">{{ $t('status.reply_to') }}</span>
                 </a>
                 <router-link :to="replyProfileLink">
-                  {{replyToName}}
+                  {{ replyToName }}
                 </router-link>
-                <span class="faint replies-separator" v-if="replies && replies.length">
+                <span
+                  v-if="replies && replies.length"
+                  class="faint replies-separator"
+                >
                   -
                 </span>
               </div>
-              <div class="replies" v-if="inConversation && !isPreview">
-                <span class="faint" v-if="replies && replies.length">{{$t('status.replies_list')}}</span>
-                <span class="reply-link faint" v-if="replies" v-for="reply in replies">
-                  <a href="#" @click.prevent="gotoOriginal(reply.id)" @mouseenter="replyEnter(reply.id, $event)" @mouseout="replyLeave()">{{reply.name}}</a>
-                </span>
+              <div
+                v-if="inConversation && !isPreview"
+                class="replies"
+              >
+                <span
+                  v-if="replies && replies.length"
+                  class="faint"
+                >{{ $t('status.replies_list') }}</span>
+                <template v-if="replies">
+                  <span
+                    v-for="reply in replies"
+                    :key="reply.id"
+                    class="reply-link faint"
+                  >
+                    <a
+                      href="#"
+                      @click.prevent="gotoOriginal(reply.id)"
+                      @mouseenter="replyEnter(reply.id, $event)"
+                      @mouseout="replyLeave()"
+                    >{{ reply.name }}</a>
+                  </span>
+                </template>
               </div>
             </div>
-
-
           </div>
 
-          <div v-if="showPreview" class="status-preview-container">
-            <status class="status-preview" v-if="preview" :isPreview="true" :statusoid="preview" :compact=true></status>
-            <div class="status-preview status-preview-loading" v-else>
-              <i class="icon-spin4 animate-spin"></i>
+          <div
+            v-if="showPreview"
+            class="status-preview-container"
+          >
+            <status
+              v-if="preview"
+              class="status-preview"
+              :is-preview="true"
+              :statusoid="preview"
+              :compact="true"
+            />
+            <div
+              v-else
+              class="status-preview status-preview-loading"
+            >
+              <i class="icon-spin4 animate-spin" />
             </div>
           </div>
 
-          <div class="status-content-wrapper" :class="{ 'tall-status': !showingLongSubject }" v-if="longSubject">
-            <a class="tall-status-hider" :class="{ 'tall-status-hider_focused': isFocused }" v-if="!showingLongSubject" href="#" @click.prevent="showingLongSubject=true">{{$t("general.show_more")}}</a>
-            <div @click.prevent="linkClicked" class="status-content media-body" v-html="contentHtml"></div>
-            <a v-if="showingLongSubject" href="#" class="status-unhider" @click.prevent="showingLongSubject=false">{{$t("general.show_less")}}</a>
+          <div
+            v-if="longSubject"
+            class="status-content-wrapper"
+            :class="{ 'tall-status': !showingLongSubject }"
+          >
+            <a
+              v-if="!showingLongSubject"
+              class="tall-status-hider"
+              :class="{ 'tall-status-hider_focused': isFocused }"
+              href="#"
+              @click.prevent="showingLongSubject=true"
+            >{{ $t("general.show_more") }}</a>
+            <div
+              class="status-content media-body"
+              @click.prevent="linkClicked"
+              v-html="contentHtml"
+            />
+            <a
+              v-if="showingLongSubject"
+              href="#"
+              class="status-unhider"
+              @click.prevent="showingLongSubject=false"
+            >{{ $t("general.show_less") }}</a>
           </div>
-          <div :class="{'tall-status': hideTallStatus}" class="status-content-wrapper" v-else>
-            <a class="tall-status-hider" :class="{ 'tall-status-hider_focused': isFocused }" v-if="hideTallStatus" href="#" @click.prevent="toggleShowMore">{{$t("general.show_more")}}</a>
-            <div @click.prevent="linkClicked" class="status-content media-body" v-html="contentHtml" v-if="!hideSubjectStatus"></div>
-            <div @click.prevent="linkClicked" class="status-content media-body" v-html="status.summary_html" v-else></div>
-            <a v-if="hideSubjectStatus" href="#" class="cw-status-hider" @click.prevent="toggleShowMore">{{$t("general.show_more")}}</a>
-            <a v-if="showingMore" href="#" class="status-unhider" @click.prevent="toggleShowMore">{{$t("general.show_less")}}</a>
+          <div
+            v-else
+            :class="{'tall-status': hideTallStatus}"
+            class="status-content-wrapper"
+          >
+            <a
+              v-if="hideTallStatus"
+              class="tall-status-hider"
+              :class="{ 'tall-status-hider_focused': isFocused }"
+              href="#"
+              @click.prevent="toggleShowMore"
+            >{{ $t("general.show_more") }}</a>
+            <div
+              v-if="!hideSubjectStatus"
+              class="status-content media-body"
+              @click.prevent="linkClicked"
+              v-html="contentHtml"
+            />
+            <div
+              v-else
+              class="status-content media-body"
+              @click.prevent="linkClicked"
+              v-html="status.summary_html"
+            />
+            <a
+              v-if="hideSubjectStatus"
+              href="#"
+              class="cw-status-hider"
+              @click.prevent="toggleShowMore"
+            >{{ $t("general.show_more") }}</a>
+            <a
+              v-if="showingMore"
+              href="#"
+              class="status-unhider"
+              @click.prevent="toggleShowMore"
+            >{{ $t("general.show_less") }}</a>
           </div>
 
-          <div v-if="status.attachments && (!hideSubjectStatus || showingLongSubject)" class="attachments media-body">
+          <div v-if="status.poll && status.poll.options">
+            <poll :base-poll="status.poll" />
+          </div>
+
+          <div
+            v-if="status.attachments && (!hideSubjectStatus || showingLongSubject)"
+            class="attachments media-body"
+          >
             <attachment
-              class="non-gallery"
               v-for="attachment in nonGalleryAttachments"
+              :key="attachment.id"
+              class="non-gallery"
               :size="attachmentSize"
               :nsfw="nsfwClickthrough"
               :attachment="attachment"
-              :allowPlay="true"
-              :setMedia="setMedia()"
-              :key="attachment.id"
+              :allow-play="true"
+              :set-media="setMedia()"
             />
             <gallery
               v-if="galleryAttachments.length > 0"
               :nsfw="nsfwClickthrough"
               :attachments="galleryAttachments"
-              :setMedia="setMedia()"
+              :set-media="setMedia()"
             />
           </div>
 
-          <div v-if="status.card && !hideSubjectStatus && !noHeading" class="link-preview media-body">
-            <link-preview :card="status.card" :size="attachmentSize" :nsfw="nsfwClickthrough" />
+          <div
+            v-if="status.card && !hideSubjectStatus && !noHeading"
+            class="link-preview media-body"
+          >
+            <link-preview
+              :card="status.card"
+              :size="attachmentSize"
+              :nsfw="nsfwClickthrough"
+            />
           </div>
 
-          <div v-if="!noHeading && !isPreview" class='status-actions media-body'>
-            <div v-if="loggedIn">
-              <i class="button-icon icon-reply" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')" :class="{'icon-reply-active': replying}"></i>
-              <span v-if="status.replies_count > 0">{{status.replies_count}}</span>
+          <transition name="fade">
+            <div
+              v-if="!hidePostStats && isFocused && combinedFavsAndRepeatsUsers.length > 0"
+              class="favs-repeated-users"
+            >
+              <div class="stats">
+                <div
+                  v-if="statusFromGlobalRepository.rebloggedBy && statusFromGlobalRepository.rebloggedBy.length > 0"
+                  class="stat-count"
+                >
+                  <a class="stat-title">{{ $t('status.repeats') }}</a>
+                  <div class="stat-number">
+                    {{ statusFromGlobalRepository.rebloggedBy.length }}
+                  </div>
+                </div>
+                <div
+                  v-if="statusFromGlobalRepository.favoritedBy && statusFromGlobalRepository.favoritedBy.length > 0"
+                  class="stat-count"
+                >
+                  <a class="stat-title">{{ $t('status.favorites') }}</a>
+                  <div class="stat-number">
+                    {{ statusFromGlobalRepository.favoritedBy.length }}
+                  </div>
+                </div>
+                <div class="avatar-row">
+                  <AvatarList :users="combinedFavsAndRepeatsUsers" />
+                </div>
+              </div>
             </div>
-            <retweet-button :visibility='status.visibility' :loggedIn='loggedIn' :status='status'></retweet-button>
-            <favorite-button :loggedIn='loggedIn' :status='status'></favorite-button>
-            <delete-button :status='status'></delete-button>
+          </transition>
+
+          <div
+            v-if="!noHeading && !isPreview"
+            class="status-actions media-body"
+          >
+            <div>
+              <i
+                v-if="loggedIn"
+                class="button-icon icon-reply"
+                :title="$t('tool_tip.reply')"
+                :class="{'button-icon-active': replying}"
+                @click.prevent="toggleReplying"
+              />
+              <i
+                v-else
+                class="button-icon button-icon-disabled icon-reply"
+                :title="$t('tool_tip.reply')"
+              />
+              <span v-if="status.replies_count > 0">{{ status.replies_count }}</span>
+            </div>
+            <retweet-button
+              :visibility="status.visibility"
+              :logged-in="loggedIn"
+              :status="status"
+            />
+            <favorite-button
+              :logged-in="loggedIn"
+              :status="status"
+            />
+            <extra-buttons
+              :status="status"
+              @onError="showError"
+              @onSuccess="clearError"
+            />
           </div>
         </div>
       </div>
-      <div class="container" v-if="replying">
-        <div class="reply-left"/>
-        <post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" :copy-message-scope="status.visibility" :subject="replySubject" v-on:posted="toggleReplying"/>
+      <div
+        v-if="replying"
+        class="container"
+      >
+        <post-status-form
+          class="reply-body"
+          :reply-to="status.id"
+          :attentions="status.attentions"
+          :replied-user="status.user"
+          :copy-message-scope="status.visibility"
+          :subject="replySubject"
+          @posted="toggleReplying"
+        />
       </div>
     </template>
   </div>
+<!-- eslint-enable vue/no-v-html -->
 </template>
 
 <script src="./status.js" ></script>
@@ -175,6 +451,13 @@ $status-margin: 0.75em;
   max-width: 100%;
 }
 
+.status-pin {
+  padding: $status-margin $status-margin 0;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+}
+
 .status-preview {
   position: absolute;
   max-width: 95%;
@@ -218,7 +501,6 @@ $status-margin: 0.75em;
 }
 
 .status-el {
-  hyphens: auto;
   overflow-wrap: break-word;
   word-wrap: break-word;
   word-break: break-word;
@@ -412,6 +694,7 @@ $status-margin: 0.75em;
   .status-content {
     font-family: var(--postFont, sans-serif);
     line-height: 1.4em;
+    white-space: pre-wrap;
 
     img, video {
       max-width: 100%;
@@ -537,25 +820,24 @@ $status-margin: 0.75em;
 }
 
 .status-actions {
+  position: relative;
   width: 100%;
   display: flex;
   margin-top: $status-margin;
 
-  div, favorite-button {
+  > * {
     max-width: 4em;
     flex: 1;
   }
 }
 
-.icon-reply:hover {
-  color: $fallback--cBlue;
-  color: var(--cBlue, $fallback--cBlue);
-  cursor: pointer;
-}
-
-.icon-reply.icon-reply-active {
-  color: $fallback--cBlue;
-  color: var(--cBlue, $fallback--cBlue);
+.button-icon.icon-reply {
+  &:not(.button-icon-disabled):hover,
+  &.button-icon-active {
+    color: $fallback--cBlue;
+    color: var(--cBlue, $fallback--cBlue);
+    cursor: pointer;
+  }
 }
 
 .status:hover .animated.avatar {
@@ -595,16 +877,11 @@ a.unmute {
   margin-left: auto;
 }
 
-.reply-left {
-  flex: 0;
-  min-width: 48px;
-}
-
 .reply-body {
   flex: 1;
 }
 
-.timeline > {
+.timeline :not(.panel-disabled) > {
   .status-el:last-child {
     border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
     border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
@@ -612,6 +889,50 @@ a.unmute {
   }
 }
 
+.favs-repeated-users {
+  margin-top: $status-margin;
+
+  .stats {
+    width: 100%;
+    display: flex;
+    line-height: 1em;
+
+    .stat-count {
+      margin-right: $status-margin;
+
+      .stat-title {
+        color: var(--faint, $fallback--faint);
+        font-size: 12px;
+        text-transform: uppercase;
+        position: relative;
+      }
+
+      .stat-number {
+        font-weight: bolder;
+        font-size: 16px;
+        line-height: 1em;
+      }
+    }
+
+    .avatar-row {
+      flex: 1;
+      overflow: hidden;
+      position: relative;
+      display: flex;
+      align-items: center;
+
+      &::before {
+        content: '';
+        position: absolute;
+        height: 100%;
+        width: 1px;
+        left: 0;
+        background-color: var(--faint, $fallback--faint);
+      }
+    }
+  }
+}
+
 @media all and (max-width: 800px) {
   .status-el {
     .retweet-info {
diff --git a/src/components/sticker_picker/sticker_picker.js b/src/components/sticker_picker/sticker_picker.js
new file mode 100644
index 00000000..a6dcded3
--- /dev/null
+++ b/src/components/sticker_picker/sticker_picker.js
@@ -0,0 +1,52 @@
+/* eslint-env browser */
+import statusPosterService from '../../services/status_poster/status_poster.service.js'
+import TabSwitcher from '../tab_switcher/tab_switcher.js'
+
+const StickerPicker = {
+  components: [
+    TabSwitcher
+  ],
+  data () {
+    return {
+      meta: {
+        stickers: []
+      },
+      path: ''
+    }
+  },
+  computed: {
+    pack () {
+      return this.$store.state.instance.stickers || []
+    }
+  },
+  methods: {
+    clear () {
+      this.meta = {
+        stickers: []
+      }
+    },
+    pick (sticker, name) {
+      const store = this.$store
+      // TODO remove this workaround by finding a way to bypass reuploads
+      fetch(sticker)
+        .then((res) => {
+          res.blob().then((blob) => {
+            var file = new File([blob], name, { mimetype: 'image/png' })
+            var formData = new FormData()
+            formData.append('file', file)
+            statusPosterService.uploadMedia({ store, formData })
+              .then((fileData) => {
+                this.$emit('uploaded', fileData)
+                this.clear()
+              }, (error) => {
+                console.warn("Can't attach sticker")
+                console.warn(error)
+                this.$emit('upload-failed', 'default')
+              })
+          })
+        })
+    }
+  }
+}
+
+export default StickerPicker
diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue
new file mode 100644
index 00000000..938204c8
--- /dev/null
+++ b/src/components/sticker_picker/sticker_picker.vue
@@ -0,0 +1,62 @@
+<template>
+  <div
+    class="sticker-picker"
+  >
+    <div
+      class="sticker-picker-panel"
+    >
+      <tab-switcher
+        :render-only-focused="true"
+      >
+        <div
+          v-for="stickerpack in pack"
+          :key="stickerpack.path"
+          :image-tooltip="stickerpack.meta.title"
+          :image="stickerpack.path + stickerpack.meta.tabIcon"
+          class="sticker-picker-content"
+        >
+          <div
+            v-for="sticker in stickerpack.meta.stickers"
+            :key="sticker"
+            class="sticker"
+            @click="pick(stickerpack.path + sticker, stickerpack.meta.title)"
+          >
+            <img
+              :src="stickerpack.path + sticker"
+            >
+          </div>
+        </div>
+      </tab-switcher>
+    </div>
+  </div>
+</template>
+
+<script src="./sticker_picker.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.sticker-picker {
+  .sticker-picker-panel {
+    display: inline-block;
+    width: 100%;
+    .sticker-picker-content {
+      max-height: 300px;
+      overflow-y: scroll;
+      overflow-x: auto;
+      .sticker {
+        display: inline-block;
+        width: 20%;
+        height: 20%;
+        img {
+          width: 100%;
+          &:hover {
+            filter: drop-shadow(0 0 5px var(--link, $fallback--link));
+          }
+        }
+      }
+    }
+  }
+}
+
+</style>
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
index af824fa2..3fff63f9 100644
--- a/src/components/still-image/still-image.vue
+++ b/src/components/still-image/still-image.vue
@@ -1,7 +1,19 @@
 <template>
-  <div class='still-image' :class='{ animated: animated }' >
-    <canvas ref="canvas" v-if="animated"></canvas>
-    <img ref="src" :src="src" :referrerpolicy="referrerpolicy" v-on:load="onLoad" @error="onError"/>
+  <div
+    class="still-image"
+    :class="{ animated: animated }"
+  >
+    <canvas
+      v-if="animated"
+      ref="canvas"
+    />
+    <img
+      ref="src"
+      :src="src"
+      :referrerpolicy="referrerpolicy"
+      @load="onLoad"
+      @error="onError"
+    >
   </div>
 </template>
 
diff --git a/src/components/style_switcher/preview.vue b/src/components/style_switcher/preview.vue
index 634f5b35..101a32bd 100644
--- a/src/components/style_switcher/preview.vue
+++ b/src/components/style_switcher/preview.vue
@@ -1,78 +1,101 @@
 <template>
-<div class="panel dummy">
-  <div class="panel-heading">
-    <div class="title">
-      {{$t('settings.style.preview.header')}}
-      <span class="badge badge-notification">
-        99
+  <div class="panel dummy">
+    <div class="panel-heading">
+      <div class="title">
+        {{ $t('settings.style.preview.header') }}
+        <span class="badge badge-notification">
+          99
+        </span>
+      </div>
+      <span class="faint">
+        {{ $t('settings.style.preview.header_faint') }}
       </span>
-    </div>
-    <span class="faint">
-      {{$t('settings.style.preview.header_faint')}}
-    </span>
-    <span class="alert error">
-      {{$t('settings.style.preview.error')}}
-    </span>
-    <button class="btn">
-      {{$t('settings.style.preview.button')}}
-    </button>
-  </div>
-  <div class="panel-body theme-preview-content">
-    <div class="post">
-      <div class="avatar">
-        ( ͡° ͜ʖ ͡°)
-      </div>
-      <div class="content">
-        <h4>
-          {{$t('settings.style.preview.content')}}
-        </h4>
-
-        <i18n path="settings.style.preview.text">
-          <code style="font-family: var(--postCodeFont)">
-            {{$t('settings.style.preview.mono')}}
-          </code>
-          <a style="color: var(--link)">
-            {{$t('settings.style.preview.link')}}
-          </a>
-        </i18n>
-
-        <div class="icons">
-          <i style="color: var(--cBlue)" class="button-icon icon-reply"/>
-          <i style="color: var(--cGreen)" class="button-icon icon-retweet"/>
-          <i style="color: var(--cOrange)" class="button-icon icon-star"/>
-          <i style="color: var(--cRed)" class="button-icon icon-cancel"/>
-        </div>
-      </div>
-    </div>
-
-    <div class="after-post">
-      <div class="avatar-alt">
-        :^)
-      </div>
-      <div class="content">
-        <i18n path="settings.style.preview.fine_print" tag="span" class="faint">
-          <a style="color: var(--faintLink)">
-            {{$t('settings.style.preview.faint_link')}}
-          </a>
-        </i18n>
-      </div>
-    </div>
-    <div class="separator"></div>
-
-    <span class="alert error">
-      {{$t('settings.style.preview.error')}}
-    </span>
-    <input :value="$t('settings.style.preview.input')" type="text">
-
-    <div class="actions">
-      <span class="checkbox">
-        <input checked="very yes" type="checkbox" id="preview_checkbox">
-        <label for="preview_checkbox">{{$t('settings.style.preview.checkbox')}}</label>
+      <span class="alert error">
+        {{ $t('settings.style.preview.error') }}
       </span>
       <button class="btn">
-        {{$t('settings.style.preview.button')}}
+        {{ $t('settings.style.preview.button') }}
       </button>
     </div>
+    <div class="panel-body theme-preview-content">
+      <div class="post">
+        <div class="avatar">
+          ( ͡° ͜ʖ ͡°)
+        </div>
+        <div class="content">
+          <h4>
+            {{ $t('settings.style.preview.content') }}
+          </h4>
+
+          <i18n path="settings.style.preview.text">
+            <code style="font-family: var(--postCodeFont)">
+              {{ $t('settings.style.preview.mono') }}
+            </code>
+            <a style="color: var(--link)">
+              {{ $t('settings.style.preview.link') }}
+            </a>
+          </i18n>
+
+          <div class="icons">
+            <i
+              style="color: var(--cBlue)"
+              class="button-icon icon-reply"
+            />
+            <i
+              style="color: var(--cGreen)"
+              class="button-icon icon-retweet"
+            />
+            <i
+              style="color: var(--cOrange)"
+              class="button-icon icon-star"
+            />
+            <i
+              style="color: var(--cRed)"
+              class="button-icon icon-cancel"
+            />
+          </div>
+        </div>
+      </div>
+
+      <div class="after-post">
+        <div class="avatar-alt">
+          :^)
+        </div>
+        <div class="content">
+          <i18n
+            path="settings.style.preview.fine_print"
+            tag="span"
+            class="faint"
+          >
+            <a style="color: var(--faintLink)">
+              {{ $t('settings.style.preview.faint_link') }}
+            </a>
+          </i18n>
+        </div>
+      </div>
+      <div class="separator" />
+
+      <span class="alert error">
+        {{ $t('settings.style.preview.error') }}
+      </span>
+      <input
+        :value="$t('settings.style.preview.input')"
+        type="text"
+      >
+
+      <div class="actions">
+        <span class="checkbox">
+          <input
+            id="preview_checkbox"
+            checked="very yes"
+            type="checkbox"
+          >
+          <label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label>
+        </span>
+        <button class="btn">
+          {{ $t('settings.style.preview.button') }}
+        </button>
+      </div>
+    </div>
   </div>
-</div>
 </template>
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 84963c81..d24394a4 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -1,274 +1,593 @@
 <template>
-<div class="style-switcher">
-  <div class="presets-container">
-    <div class="save-load">
-      <export-import
-        :exportObject='exportedTheme'
-        :exportLabel='$t("settings.export_theme")'
-        :importLabel='$t("settings.import_theme")'
-        :importFailedText='$t("settings.invalid_theme_imported")'
-        :onImport='onImport'
-        :validator='importValidator'>
-        <template slot="before">
-          <div class="presets">
-            {{$t('settings.presets')}}
-            <label for="preset-switcher" class='select'>
-              <select id="preset-switcher" v-model="selected" class="preset-switcher">
-                <option v-for="style in availableStyles"
-                        :value="style"
-                        :style="{
-                                backgroundColor: style[1] || style.theme.colors.bg,
-                                color: style[3] || style.theme.colors.text
-                                }">
-                  {{style[0] || style.name}}
-                </option>
-              </select>
-              <i class="icon-down-open"/>
-            </label>
-          </div>
-        </template>
-      </export-import>
+  <div class="style-switcher">
+    <div class="presets-container">
+      <div class="save-load">
+        <export-import
+          :export-object="exportedTheme"
+          :export-label="$t(&quot;settings.export_theme&quot;)"
+          :import-label="$t(&quot;settings.import_theme&quot;)"
+          :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
+          :on-import="onImport"
+          :validator="importValidator"
+        >
+          <template slot="before">
+            <div class="presets">
+              {{ $t('settings.presets') }}
+              <label
+                for="preset-switcher"
+                class="select"
+              >
+                <select
+                  id="preset-switcher"
+                  v-model="selected"
+                  class="preset-switcher"
+                >
+                  <option
+                    v-for="style in availableStyles"
+                    :key="style.name"
+                    :value="style"
+                    :style="{
+                      backgroundColor: style[1] || style.theme.colors.bg,
+                      color: style[3] || style.theme.colors.text
+                    }"
+                  >
+                    {{ style[0] || style.name }}
+                  </option>
+                </select>
+                <i class="icon-down-open" />
+              </label>
+            </div>
+          </template>
+        </export-import>
+      </div>
+      <div class="save-load-options">
+        <span class="keep-option">
+          <input
+            id="keep-color"
+            v-model="keepColor"
+            type="checkbox"
+          >
+          <label for="keep-color">{{ $t('settings.style.switcher.keep_color') }}</label>
+        </span>
+        <span class="keep-option">
+          <input
+            id="keep-shadows"
+            v-model="keepShadows"
+            type="checkbox"
+          >
+          <label for="keep-shadows">{{ $t('settings.style.switcher.keep_shadows') }}</label>
+        </span>
+        <span class="keep-option">
+          <input
+            id="keep-opacity"
+            v-model="keepOpacity"
+            type="checkbox"
+          >
+          <label for="keep-opacity">{{ $t('settings.style.switcher.keep_opacity') }}</label>
+        </span>
+        <span class="keep-option">
+          <input
+            id="keep-roundness"
+            v-model="keepRoundness"
+            type="checkbox"
+          >
+          <label for="keep-roundness">{{ $t('settings.style.switcher.keep_roundness') }}</label>
+        </span>
+        <span class="keep-option">
+          <input
+            id="keep-fonts"
+            v-model="keepFonts"
+            type="checkbox"
+          >
+          <label for="keep-fonts">{{ $t('settings.style.switcher.keep_fonts') }}</label>
+        </span>
+        <p>{{ $t('settings.style.switcher.save_load_hint') }}</p>
+      </div>
     </div>
-    <div class="save-load-options">
-      <span class="keep-option">
-        <input
-          id="keep-color"
-          type="checkbox"
-          v-model="keepColor">
-        <label for="keep-color">{{$t('settings.style.switcher.keep_color')}}</label>
-      </span>
-      <span class="keep-option">
-        <input
-          id="keep-shadows"
-          type="checkbox"
-          v-model="keepShadows">
-        <label for="keep-shadows">{{$t('settings.style.switcher.keep_shadows')}}</label>
-      </span>
-      <span class="keep-option">
-        <input
-          id="keep-opacity"
-          type="checkbox"
-          v-model="keepOpacity">
-        <label for="keep-opacity">{{$t('settings.style.switcher.keep_opacity')}}</label>
-      </span>
-      <span class="keep-option">
-        <input
-          id="keep-roundness"
-          type="checkbox"
-          v-model="keepRoundness">
-        <label for="keep-roundness">{{$t('settings.style.switcher.keep_roundness')}}</label>
-      </span>
-      <span class="keep-option">
-        <input
-          id="keep-fonts"
-          type="checkbox"
-          v-model="keepFonts">
-        <label for="keep-fonts">{{$t('settings.style.switcher.keep_fonts')}}</label>
-      </span>
-      <p>{{$t('settings.style.switcher.save_load_hint')}}</p>
+
+    <div class="preview-container">
+      <preview :style="previewRules" />
+    </div>
+
+    <keep-alive>
+      <tab-switcher key="style-tweak">
+        <div
+          :label="$t('settings.style.common_colors._tab_label')"
+          class="color-container"
+        >
+          <div class="tab-header">
+            <p>{{ $t('settings.theme_help') }}</p>
+            <button
+              class="btn"
+              @click="clearOpacity"
+            >
+              {{ $t('settings.style.switcher.clear_opacity') }}
+            </button>
+            <button
+              class="btn"
+              @click="clearV1"
+            >
+              {{ $t('settings.style.switcher.clear_all') }}
+            </button>
+          </div>
+          <p>{{ $t('settings.theme_help_v2_1') }}</p>
+          <h4>{{ $t('settings.style.common_colors.main') }}</h4>
+          <div class="color-item">
+            <ColorInput
+              v-model="bgColorLocal"
+              name="bgColor"
+              :label="$t('settings.background')"
+            />
+            <OpacityInput
+              v-model="bgOpacityLocal"
+              name="bgOpacity"
+              :fallback="previewTheme.opacity.bg || 1"
+            />
+            <ColorInput
+              v-model="textColorLocal"
+              name="textColor"
+              :label="$t('settings.text')"
+            />
+            <ContrastRatio :contrast="previewContrast.bgText" />
+            <ColorInput
+              v-model="linkColorLocal"
+              name="linkColor"
+              :label="$t('settings.links')"
+            />
+            <ContrastRatio :contrast="previewContrast.bgLink" />
+          </div>
+          <div class="color-item">
+            <ColorInput
+              v-model="fgColorLocal"
+              name="fgColor"
+              :label="$t('settings.foreground')"
+            />
+            <ColorInput
+              v-model="fgTextColorLocal"
+              name="fgTextColor"
+              :label="$t('settings.text')"
+              :fallback="previewTheme.colors.fgText"
+            />
+            <ColorInput
+              v-model="fgLinkColorLocal"
+              name="fgLinkColor"
+              :label="$t('settings.links')"
+              :fallback="previewTheme.colors.fgLink"
+            />
+            <p>{{ $t('settings.style.common_colors.foreground_hint') }}</p>
+          </div>
+          <h4>{{ $t('settings.style.common_colors.rgbo') }}</h4>
+          <div class="color-item">
+            <ColorInput
+              v-model="cRedColorLocal"
+              name="cRedColor"
+              :label="$t('settings.cRed')"
+            />
+            <ContrastRatio :contrast="previewContrast.bgRed" />
+            <ColorInput
+              v-model="cBlueColorLocal"
+              name="cBlueColor"
+              :label="$t('settings.cBlue')"
+            />
+            <ContrastRatio :contrast="previewContrast.bgBlue" />
+          </div>
+          <div class="color-item">
+            <ColorInput
+              v-model="cGreenColorLocal"
+              name="cGreenColor"
+              :label="$t('settings.cGreen')"
+            />
+            <ContrastRatio :contrast="previewContrast.bgGreen" />
+            <ColorInput
+              v-model="cOrangeColorLocal"
+              name="cOrangeColor"
+              :label="$t('settings.cOrange')"
+            />
+            <ContrastRatio :contrast="previewContrast.bgOrange" />
+          </div>
+          <p>{{ $t('settings.theme_help_v2_2') }}</p>
+        </div>
+
+        <div
+          :label="$t('settings.style.advanced_colors._tab_label')"
+          class="color-container"
+        >
+          <div class="tab-header">
+            <p>{{ $t('settings.theme_help') }}</p>
+            <button
+              class="btn"
+              @click="clearOpacity"
+            >
+              {{ $t('settings.style.switcher.clear_opacity') }}
+            </button>
+            <button
+              class="btn"
+              @click="clearV1"
+            >
+              {{ $t('settings.style.switcher.clear_all') }}
+            </button>
+          </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
+            <ColorInput
+              v-model="alertErrorColorLocal"
+              name="alertError"
+              :label="$t('settings.style.advanced_colors.alert_error')"
+              :fallback="previewTheme.colors.alertError"
+            />
+            <ContrastRatio :contrast="previewContrast.alertError" />
+          </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
+            <ColorInput
+              v-model="badgeNotificationColorLocal"
+              name="badgeNotification"
+              :label="$t('settings.style.advanced_colors.badge_notification')"
+              :fallback="previewTheme.colors.badgeNotification"
+            />
+          </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4>
+            <ColorInput
+              v-model="panelColorLocal"
+              name="panelColor"
+              :fallback="fgColorLocal"
+              :label="$t('settings.background')"
+            />
+            <OpacityInput
+              v-model="panelOpacityLocal"
+              name="panelOpacity"
+              :fallback="previewTheme.opacity.panel || 1"
+            />
+            <ColorInput
+              v-model="panelTextColorLocal"
+              name="panelTextColor"
+              :fallback="previewTheme.colors.panelText"
+              :label="$t('settings.text')"
+            />
+            <ContrastRatio
+              :contrast="previewContrast.panelText"
+              large="1"
+            />
+            <ColorInput
+              v-model="panelLinkColorLocal"
+              name="panelLinkColor"
+              :fallback="previewTheme.colors.panelLink"
+              :label="$t('settings.links')"
+            />
+            <ContrastRatio
+              :contrast="previewContrast.panelLink"
+              large="1"
+            />
+          </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.top_bar') }}</h4>
+            <ColorInput
+              v-model="topBarColorLocal"
+              name="topBarColor"
+              :fallback="fgColorLocal"
+              :label="$t('settings.background')"
+            />
+            <ColorInput
+              v-model="topBarTextColorLocal"
+              name="topBarTextColor"
+              :fallback="previewTheme.colors.topBarText"
+              :label="$t('settings.text')"
+            />
+            <ContrastRatio :contrast="previewContrast.topBarText" />
+            <ColorInput
+              v-model="topBarLinkColorLocal"
+              name="topBarLinkColor"
+              :fallback="previewTheme.colors.topBarLink"
+              :label="$t('settings.links')"
+            />
+            <ContrastRatio :contrast="previewContrast.topBarLink" />
+          </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.inputs') }}</h4>
+            <ColorInput
+              v-model="inputColorLocal"
+              name="inputColor"
+              :fallback="fgColorLocal"
+              :label="$t('settings.background')"
+            />
+            <OpacityInput
+              v-model="inputOpacityLocal"
+              name="inputOpacity"
+              :fallback="previewTheme.opacity.input || 1"
+            />
+            <ColorInput
+              v-model="inputTextColorLocal"
+              name="inputTextColor"
+              :fallback="previewTheme.colors.inputText"
+              :label="$t('settings.text')"
+            />
+            <ContrastRatio :contrast="previewContrast.inputText" />
+          </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.buttons') }}</h4>
+            <ColorInput
+              v-model="btnColorLocal"
+              name="btnColor"
+              :fallback="fgColorLocal"
+              :label="$t('settings.background')"
+            />
+            <OpacityInput
+              v-model="btnOpacityLocal"
+              name="btnOpacity"
+              :fallback="previewTheme.opacity.btn || 1"
+            />
+            <ColorInput
+              v-model="btnTextColorLocal"
+              name="btnTextColor"
+              :fallback="previewTheme.colors.btnText"
+              :label="$t('settings.text')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnText" />
+          </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.borders') }}</h4>
+            <ColorInput
+              v-model="borderColorLocal"
+              name="borderColor"
+              :fallback="previewTheme.colors.border"
+              :label="$t('settings.style.common.color')"
+            />
+            <OpacityInput
+              v-model="borderOpacityLocal"
+              name="borderOpacity"
+              :fallback="previewTheme.opacity.border || 1"
+            />
+          </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.faint_text') }}</h4>
+            <ColorInput
+              v-model="faintColorLocal"
+              name="faintColor"
+              :fallback="previewTheme.colors.faint || 1"
+              :label="$t('settings.text')"
+            />
+            <ColorInput
+              v-model="faintLinkColorLocal"
+              name="faintLinkColor"
+              :fallback="previewTheme.colors.faintLink"
+              :label="$t('settings.links')"
+            />
+            <ColorInput
+              v-model="panelFaintColorLocal"
+              name="panelFaintColor"
+              :fallback="previewTheme.colors.panelFaint"
+              :label="$t('settings.style.advanced_colors.panel_header')"
+            />
+            <OpacityInput
+              v-model="faintOpacityLocal"
+              name="faintOpacity"
+              :fallback="previewTheme.opacity.faint || 0.5"
+            />
+          </div>
+        </div>
+
+        <div
+          :label="$t('settings.style.radii._tab_label')"
+          class="radius-container"
+        >
+          <div class="tab-header">
+            <p>{{ $t('settings.radii_help') }}</p>
+            <button
+              class="btn"
+              @click="clearRoundness"
+            >
+              {{ $t('settings.style.switcher.clear_all') }}
+            </button>
+          </div>
+          <RangeInput
+            v-model="btnRadiusLocal"
+            name="btnRadius"
+            :label="$t('settings.btnRadius')"
+            :fallback="previewTheme.radii.btn"
+            max="16"
+            hard-min="0"
+          />
+          <RangeInput
+            v-model="inputRadiusLocal"
+            name="inputRadius"
+            :label="$t('settings.inputRadius')"
+            :fallback="previewTheme.radii.input"
+            max="9"
+            hard-min="0"
+          />
+          <RangeInput
+            v-model="checkboxRadiusLocal"
+            name="checkboxRadius"
+            :label="$t('settings.checkboxRadius')"
+            :fallback="previewTheme.radii.checkbox"
+            max="16"
+            hard-min="0"
+          />
+          <RangeInput
+            v-model="panelRadiusLocal"
+            name="panelRadius"
+            :label="$t('settings.panelRadius')"
+            :fallback="previewTheme.radii.panel"
+            max="50"
+            hard-min="0"
+          />
+          <RangeInput
+            v-model="avatarRadiusLocal"
+            name="avatarRadius"
+            :label="$t('settings.avatarRadius')"
+            :fallback="previewTheme.radii.avatar"
+            max="28"
+            hard-min="0"
+          />
+          <RangeInput
+            v-model="avatarAltRadiusLocal"
+            name="avatarAltRadius"
+            :label="$t('settings.avatarAltRadius')"
+            :fallback="previewTheme.radii.avatarAlt"
+            max="28"
+            hard-min="0"
+          />
+          <RangeInput
+            v-model="attachmentRadiusLocal"
+            name="attachmentRadius"
+            :label="$t('settings.attachmentRadius')"
+            :fallback="previewTheme.radii.attachment"
+            max="50"
+            hard-min="0"
+          />
+          <RangeInput
+            v-model="tooltipRadiusLocal"
+            name="tooltipRadius"
+            :label="$t('settings.tooltipRadius')"
+            :fallback="previewTheme.radii.tooltip"
+            max="50"
+            hard-min="0"
+          />
+        </div>
+
+        <div
+          :label="$t('settings.style.shadows._tab_label')"
+          class="shadow-container"
+        >
+          <div class="tab-header shadow-selector">
+            <div class="select-container">
+              {{ $t('settings.style.shadows.component') }}
+              <label
+                for="shadow-switcher"
+                class="select"
+              >
+                <select
+                  id="shadow-switcher"
+                  v-model="shadowSelected"
+                  class="shadow-switcher"
+                >
+                  <option
+                    v-for="shadow in shadowsAvailable"
+                    :key="shadow"
+                    :value="shadow"
+                  >
+                    {{ $t('settings.style.shadows.components.' + shadow) }}
+                  </option>
+                </select>
+                <i class="icon-down-open" />
+              </label>
+            </div>
+            <div class="override">
+              <label
+                for="override"
+                class="label"
+              >
+                {{ $t('settings.style.shadows.override') }}
+              </label>
+              <input
+                id="override"
+                v-model="currentShadowOverriden"
+                name="override"
+                class="input-override"
+                type="checkbox"
+              >
+              <label
+                class="checkbox-label"
+                for="override"
+              />
+            </div>
+            <button
+              class="btn"
+              @click="clearShadows"
+            >
+              {{ $t('settings.style.switcher.clear_all') }}
+            </button>
+          </div>
+          <shadow-control
+            v-model="currentShadow"
+            :ready="!!currentShadowFallback"
+            :fallback="currentShadowFallback"
+          />
+          <div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'">
+            <i18n
+              path="settings.style.shadows.filter_hint.always_drop_shadow"
+              tag="p"
+            >
+              <code>filter: drop-shadow()</code>
+            </i18n>
+            <p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p>
+            <i18n
+              path="settings.style.shadows.filter_hint.drop_shadow_syntax"
+              tag="p"
+            >
+              <code>drop-shadow</code>
+              <code>spread-radius</code>
+              <code>inset</code>
+            </i18n>
+            <i18n
+              path="settings.style.shadows.filter_hint.inset_classic"
+              tag="p"
+            >
+              <code>box-shadow</code>
+            </i18n>
+            <p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p>
+          </div>
+        </div>
+
+        <div
+          :label="$t('settings.style.fonts._tab_label')"
+          class="fonts-container"
+        >
+          <div class="tab-header">
+            <p>{{ $t('settings.style.fonts.help') }}</p>
+            <button
+              class="btn"
+              @click="clearFonts"
+            >
+              {{ $t('settings.style.switcher.clear_all') }}
+            </button>
+          </div>
+          <FontControl
+            v-model="fontsLocal.interface"
+            name="ui"
+            :label="$t('settings.style.fonts.components.interface')"
+            :fallback="previewTheme.fonts.interface"
+            no-inherit="1"
+          />
+          <FontControl
+            v-model="fontsLocal.input"
+            name="input"
+            :label="$t('settings.style.fonts.components.input')"
+            :fallback="previewTheme.fonts.input"
+          />
+          <FontControl
+            v-model="fontsLocal.post"
+            name="post"
+            :label="$t('settings.style.fonts.components.post')"
+            :fallback="previewTheme.fonts.post"
+          />
+          <FontControl
+            v-model="fontsLocal.postCode"
+            name="postCode"
+            :label="$t('settings.style.fonts.components.postCode')"
+            :fallback="previewTheme.fonts.postCode"
+          />
+        </div>
+      </tab-switcher>
+    </keep-alive>
+
+    <div class="apply-container">
+      <button
+        class="btn submit"
+        :disabled="!themeValid"
+        @click="setCustomTheme"
+      >
+        {{ $t('general.apply') }}
+      </button>
+      <button
+        class="btn"
+        @click="clearAll"
+      >
+        {{ $t('settings.style.switcher.reset') }}
+      </button>
     </div>
   </div>
-
-  <div class="preview-container">
-    <preview :style="previewRules"/>
-  </div>
-
-  <keep-alive>
-    <tab-switcher key="style-tweak">
-      <div :label="$t('settings.style.common_colors._tab_label')" class="color-container">
-        <div class="tab-header">
-          <p>{{$t('settings.theme_help')}}</p>
-          <button class="btn" @click="clearOpacity">{{$t('settings.style.switcher.clear_opacity')}}</button>
-          <button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button>
-        </div>
-        <p>{{$t('settings.theme_help_v2_1')}}</p>
-        <h4>{{ $t('settings.style.common_colors.main') }}</h4>
-        <div class="color-item">
-          <ColorInput name="bgColor" v-model="bgColorLocal" :label="$t('settings.background')"/>
-          <OpacityInput name="bgOpacity" v-model="bgOpacityLocal" :fallback="previewTheme.opacity.bg || 1"/>
-          <ColorInput name="textColor" v-model="textColorLocal" :label="$t('settings.text')"/>
-          <ContrastRatio :contrast="previewContrast.bgText"/>
-          <ColorInput name="linkColor" v-model="linkColorLocal" :label="$t('settings.links')"/>
-          <ContrastRatio :contrast="previewContrast.bgLink"/>
-        </div>
-        <div class="color-item">
-          <ColorInput name="fgColor" v-model="fgColorLocal" :label="$t('settings.foreground')"/>
-          <ColorInput name="fgTextColor" v-model="fgTextColorLocal" :label="$t('settings.text')" :fallback="previewTheme.colors.fgText"/>
-          <ColorInput name="fgLinkColor" v-model="fgLinkColorLocal" :label="$t('settings.links')" :fallback="previewTheme.colors.fgLink"/>
-          <p>{{ $t('settings.style.common_colors.foreground_hint') }}</p>
-        </div>
-        <h4>{{ $t('settings.style.common_colors.rgbo') }}</h4>
-        <div class="color-item">
-          <ColorInput name="cRedColor" v-model="cRedColorLocal" :label="$t('settings.cRed')"/>
-          <ContrastRatio :contrast="previewContrast.bgRed"/>
-          <ColorInput name="cBlueColor" v-model="cBlueColorLocal" :label="$t('settings.cBlue')"/>
-          <ContrastRatio :contrast="previewContrast.bgBlue"/>
-        </div>
-        <div class="color-item">
-          <ColorInput name="cGreenColor" v-model="cGreenColorLocal" :label="$t('settings.cGreen')"/>
-          <ContrastRatio :contrast="previewContrast.bgGreen"/>
-          <ColorInput name="cOrangeColor" v-model="cOrangeColorLocal" :label="$t('settings.cOrange')"/>
-          <ContrastRatio :contrast="previewContrast.bgOrange"/>
-        </div>
-        <p>{{$t('settings.theme_help_v2_2')}}</p>
-      </div>
-
-      <div :label="$t('settings.style.advanced_colors._tab_label')" class="color-container">
-        <div class="tab-header">
-          <p>{{$t('settings.theme_help')}}</p>
-          <button class="btn" @click="clearOpacity">{{$t('settings.style.switcher.clear_opacity')}}</button>
-          <button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button>
-        </div>
-        <div class="color-item">
-          <h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
-          <ColorInput name="alertError" v-model="alertErrorColorLocal" :label="$t('settings.style.advanced_colors.alert_error')" :fallback="previewTheme.colors.alertError"/>
-          <ContrastRatio :contrast="previewContrast.alertError"/>
-        </div>
-        <div class="color-item">
-          <h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
-          <ColorInput name="badgeNotification" v-model="badgeNotificationColorLocal" :label="$t('settings.style.advanced_colors.badge_notification')" :fallback="previewTheme.colors.badgeNotification"/>
-        </div>
-        <div class="color-item">
-          <h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4>
-          <ColorInput name="panelColor" v-model="panelColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
-          <OpacityInput name="panelOpacity" v-model="panelOpacityLocal" :fallback="previewTheme.opacity.panel || 1"/>
-          <ColorInput name="panelTextColor" v-model="panelTextColorLocal" :fallback="previewTheme.colors.panelText" :label="$t('settings.text')"/>
-          <ContrastRatio :contrast="previewContrast.panelText" large="1"/>
-          <ColorInput name="panelLinkColor" v-model="panelLinkColorLocal" :fallback="previewTheme.colors.panelLink" :label="$t('settings.links')"/>
-          <ContrastRatio :contrast="previewContrast.panelLink" large="1"/>
-        </div>
-        <div class="color-item">
-          <h4>{{ $t('settings.style.advanced_colors.top_bar') }}</h4>
-          <ColorInput name="topBarColor" v-model="topBarColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
-          <ColorInput name="topBarTextColor" v-model="topBarTextColorLocal" :fallback="previewTheme.colors.topBarText" :label="$t('settings.text')"/>
-          <ContrastRatio :contrast="previewContrast.topBarText"/>
-          <ColorInput name="topBarLinkColor" v-model="topBarLinkColorLocal" :fallback="previewTheme.colors.topBarLink" :label="$t('settings.links')"/>
-          <ContrastRatio :contrast="previewContrast.topBarLink"/>
-        </div>
-        <div class="color-item">
-          <h4>{{ $t('settings.style.advanced_colors.inputs') }}</h4>
-          <ColorInput name="inputColor" v-model="inputColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
-          <OpacityInput name="inputOpacity" v-model="inputOpacityLocal" :fallback="previewTheme.opacity.input || 1"/>
-          <ColorInput name="inputTextColor" v-model="inputTextColorLocal" :fallback="previewTheme.colors.inputText" :label="$t('settings.text')"/>
-          <ContrastRatio :contrast="previewContrast.inputText"/>
-        </div>
-        <div class="color-item">
-          <h4>{{ $t('settings.style.advanced_colors.buttons') }}</h4>
-          <ColorInput name="btnColor" v-model="btnColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
-          <OpacityInput name="btnOpacity" v-model="btnOpacityLocal" :fallback="previewTheme.opacity.btn || 1"/>
-          <ColorInput name="btnTextColor" v-model="btnTextColorLocal" :fallback="previewTheme.colors.btnText" :label="$t('settings.text')"/>
-          <ContrastRatio :contrast="previewContrast.btnText"/>
-        </div>
-        <div class="color-item">
-          <h4>{{ $t('settings.style.advanced_colors.borders') }}</h4>
-          <ColorInput name="borderColor" v-model="borderColorLocal" :fallback="previewTheme.colors.border" :label="$t('settings.style.common.color')"/>
-          <OpacityInput name="borderOpacity" v-model="borderOpacityLocal" :fallback="previewTheme.opacity.border || 1"/>
-        </div>
-        <div class="color-item">
-          <h4>{{ $t('settings.style.advanced_colors.faint_text') }}</h4>
-          <ColorInput name="faintColor" v-model="faintColorLocal" :fallback="previewTheme.colors.faint || 1" :label="$t('settings.text')"/>
-          <ColorInput name="faintLinkColor" v-model="faintLinkColorLocal" :fallback="previewTheme.colors.faintLink" :label="$t('settings.links')"/>
-          <ColorInput name="panelFaintColor" v-model="panelFaintColorLocal" :fallback="previewTheme.colors.panelFaint" :label="$t('settings.style.advanced_colors.panel_header')"/>
-          <OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/>
-        </div>
-      </div>
-
-      <div :label="$t('settings.style.radii._tab_label')" class="radius-container">
-        <div class="tab-header">
-          <p>{{$t('settings.radii_help')}}</p>
-          <button class="btn" @click="clearRoundness">{{$t('settings.style.switcher.clear_all')}}</button>
-        </div>
-        <RangeInput name="btnRadius" :label="$t('settings.btnRadius')" v-model="btnRadiusLocal" :fallback="previewTheme.radii.btn" max="16" hardMin="0"/>
-        <RangeInput name="inputRadius" :label="$t('settings.inputRadius')" v-model="inputRadiusLocal" :fallback="previewTheme.radii.input" max="9" hardMin="0"/>
-        <RangeInput name="checkboxRadius" :label="$t('settings.checkboxRadius')" v-model="checkboxRadiusLocal" :fallback="previewTheme.radii.checkbox" max="16" hardMin="0"/>
-        <RangeInput name="panelRadius" :label="$t('settings.panelRadius')" v-model="panelRadiusLocal" :fallback="previewTheme.radii.panel" max="50" hardMin="0"/>
-        <RangeInput name="avatarRadius" :label="$t('settings.avatarRadius')" v-model="avatarRadiusLocal" :fallback="previewTheme.radii.avatar" max="28" hardMin="0"/>
-        <RangeInput name="avatarAltRadius" :label="$t('settings.avatarAltRadius')" v-model="avatarAltRadiusLocal" :fallback="previewTheme.radii.avatarAlt" max="28" hardMin="0"/>
-        <RangeInput name="attachmentRadius" :label="$t('settings.attachmentRadius')" v-model="attachmentRadiusLocal" :fallback="previewTheme.radii.attachment" max="50" hardMin="0"/>
-        <RangeInput name="tooltipRadius" :label="$t('settings.tooltipRadius')" v-model="tooltipRadiusLocal" :fallback="previewTheme.radii.tooltip" max="50" hardMin="0"/>
-      </div>
-
-      <div :label="$t('settings.style.shadows._tab_label')" class="shadow-container">
-        <div class="tab-header shadow-selector">
-          <div class="select-container">
-            {{$t('settings.style.shadows.component')}}
-            <label for="shadow-switcher" class="select">
-              <select id="shadow-switcher" v-model="shadowSelected" class="shadow-switcher">
-                <option v-for="shadow in shadowsAvailable"
-                        :value="shadow">
-                  {{$t('settings.style.shadows.components.' + shadow)}}
-                </option>
-              </select>
-              <i class="icon-down-open"/>
-            </label>
-          </div>
-          <div class="override">
-            <label for="override" class="label">
-              {{$t('settings.style.shadows.override')}}
-            </label>
-            <input
-              v-model="currentShadowOverriden"
-              name="override"
-              id="override"
-              class="input-override"
-              type="checkbox">
-            <label class="checkbox-label" for="override"></label>
-          </div>
-          <button class="btn" @click="clearShadows">{{$t('settings.style.switcher.clear_all')}}</button>
-        </div>
-        <shadow-control :ready="!!currentShadowFallback" :fallback="currentShadowFallback" v-model="currentShadow"/>
-        <div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'">
-          <i18n path="settings.style.shadows.filter_hint.always_drop_shadow" tag="p">
-            <code>filter: drop-shadow()</code>
-          </i18n>
-          <p>{{$t('settings.style.shadows.filter_hint.avatar_inset')}}</p>
-          <i18n path="settings.style.shadows.filter_hint.drop_shadow_syntax" tag="p">
-            <code>drop-shadow</code>
-            <code>spread-radius</code>
-            <code>inset</code>
-          </i18n>
-          <i18n path="settings.style.shadows.filter_hint.inset_classic" tag="p">
-            <code>box-shadow</code>
-          </i18n>
-          <p>{{$t('settings.style.shadows.filter_hint.spread_zero')}}</p>
-        </div>
-      </div>
-
-      <div :label="$t('settings.style.fonts._tab_label')" class="fonts-container">
-        <div class="tab-header">
-          <p>{{$t('settings.style.fonts.help')}}</p>
-          <button class="btn" @click="clearFonts">{{$t('settings.style.switcher.clear_all')}}</button>
-        </div>
-        <FontControl
-          name="ui"
-          v-model="fontsLocal.interface"
-          :label="$t('settings.style.fonts.components.interface')"
-          :fallback="previewTheme.fonts.interface"
-          no-inherit="1"/>
-        <FontControl
-          name="input"
-          v-model="fontsLocal.input"
-          :label="$t('settings.style.fonts.components.input')"
-          :fallback="previewTheme.fonts.input"/>
-        <FontControl
-          name="post"
-          v-model="fontsLocal.post"
-          :label="$t('settings.style.fonts.components.post')"
-          :fallback="previewTheme.fonts.post"/>
-        <FontControl
-          name="postCode"
-          v-model="fontsLocal.postCode"
-          :label="$t('settings.style.fonts.components.postCode')"
-          :fallback="previewTheme.fonts.postCode"/>
-      </div>
-    </tab-switcher>
-  </keep-alive>
-
-  <div class="apply-container">
-    <button class="btn submit" :disabled="!themeValid" @click="setCustomTheme">{{$t('general.apply')}}</button>
-    <button class="btn" @click="clearAll">{{$t('settings.style.switcher.reset')}}</button>
-  </div>
-</div>
 </template>
 
 <script src="./style_switcher.js"></script>
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index 423df258..a5fe019c 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -4,43 +4,70 @@ import './tab_switcher.scss'
 
 export default Vue.component('tab-switcher', {
   name: 'TabSwitcher',
-  props: ['renderOnlyFocused'],
+  props: ['renderOnlyFocused', 'onSwitch', 'customActive'],
   data () {
     return {
       active: this.$slots.default.findIndex(_ => _.tag)
     }
   },
-  methods: {
-    activateTab (index) {
-      return () => {
-        this.active = index
-      }
-    }
-  },
   beforeUpdate () {
     const currentSlot = this.$slots.default[this.active]
     if (!currentSlot.tag) {
       this.active = this.$slots.default.findIndex(_ => _.tag)
     }
   },
+  methods: {
+    activateTab (index, dataset) {
+      return () => {
+        if (typeof this.onSwitch === 'function') {
+          this.onSwitch.call(null, index, this.$slots.default[index].elm.dataset)
+        }
+        this.active = index
+      }
+    },
+    isActiveTab (index) {
+      const customActiveIndex = this.$slots.default.findIndex(slot => {
+        const dataFilter = slot.data && slot.data.attrs && slot.data.attrs['data-filter']
+        return this.customActive && this.customActive === dataFilter
+      })
+
+      return customActiveIndex > -1 ? customActiveIndex === index : index === this.active
+    }
+  },
   render (h) {
     const tabs = this.$slots.default
-          .map((slot, index) => {
-            if (!slot.tag) return
-            const classesTab = ['tab']
-            const classesWrapper = ['tab-wrapper']
+      .map((slot, index) => {
+        if (!slot.tag) return
+        const classesTab = ['tab']
+        const classesWrapper = ['tab-wrapper']
 
-            if (index === this.active) {
-              classesTab.push('active')
-              classesWrapper.push('active')
-            }
-
-            return (
-              <div class={ classesWrapper.join(' ')}>
-                <button disabled={slot.data.attrs.disabled} onClick={this.activateTab(index)} class={ classesTab.join(' ') }>{slot.data.attrs.label}</button>
-              </div>
-            )
-          })
+        if (this.isActiveTab(index)) {
+          classesTab.push('active')
+          classesWrapper.push('active')
+        }
+        if (slot.data.attrs.image) {
+          return (
+            <div class={ classesWrapper.join(' ')}>
+              <button
+                disabled={slot.data.attrs.disabled}
+                onClick={this.activateTab(index)}
+                class={classesTab.join(' ')}>
+                <img src={slot.data.attrs.image} title={slot.data.attrs['image-tooltip']}/>
+                {slot.data.attrs.label ? '' : slot.data.attrs.label}
+              </button>
+            </div>
+          )
+        }
+        return (
+          <div class={ classesWrapper.join(' ')}>
+            <button
+              disabled={slot.data.attrs.disabled}
+              onClick={this.activateTab(index)}
+              class={classesTab.join(' ')}>
+              {slot.data.attrs.label}</button>
+          </div>
+        )
+      })
 
     const contents = this.$slots.default.map((slot, index) => {
       if (!slot.tag) return
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index f7449439..4eeb42e0 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -53,6 +53,12 @@
           background: transparent;
           z-index: 5;
         }
+
+        img {
+          max-height: 26px;
+          vertical-align: top;
+          margin-top: -5px;
+        }
       }
 
       &:not(.active) {
diff --git a/src/components/tag_timeline/tag_timeline.js b/src/components/tag_timeline/tag_timeline.js
index 41b09706..458eb1c5 100644
--- a/src/components/tag_timeline/tag_timeline.js
+++ b/src/components/tag_timeline/tag_timeline.js
@@ -3,7 +3,7 @@ import Timeline from '../timeline/timeline.vue'
 const TagTimeline = {
   created () {
     this.$store.commit('clearTimeline', { timeline: 'tag' })
-    this.$store.dispatch('startFetching', { timeline: 'tag', tag: this.tag })
+    this.$store.dispatch('startFetchingTimeline', { timeline: 'tag', tag: this.tag })
   },
   components: {
     Timeline
@@ -15,7 +15,7 @@ const TagTimeline = {
   watch: {
     tag () {
       this.$store.commit('clearTimeline', { timeline: 'tag' })
-      this.$store.dispatch('startFetching', { timeline: 'tag', tag: this.tag })
+      this.$store.dispatch('startFetchingTimeline', { timeline: 'tag', tag: this.tag })
     }
   },
   destroyed () {
diff --git a/src/components/tag_timeline/tag_timeline.vue b/src/components/tag_timeline/tag_timeline.vue
index 62bb579a..ace96c3f 100644
--- a/src/components/tag_timeline/tag_timeline.vue
+++ b/src/components/tag_timeline/tag_timeline.vue
@@ -1,5 +1,10 @@
 <template>
-  <Timeline :title="tag" :timeline="timeline" :timeline-name="'tag'" :tag="tag" />
+  <Timeline
+    :title="tag"
+    :timeline="timeline"
+    :timeline-name="'tag'"
+    :tag="tag"
+  />
 </template>
 
-<script src='./tag_timeline.js'></script>
\ No newline at end of file
+<script src='./tag_timeline.js'></script>
diff --git a/src/components/terms_of_service_panel/terms_of_service_panel.vue b/src/components/terms_of_service_panel/terms_of_service_panel.vue
index eb0f2527..63dc58b8 100644
--- a/src/components/terms_of_service_panel/terms_of_service_panel.vue
+++ b/src/components/terms_of_service_panel/terms_of_service_panel.vue
@@ -2,8 +2,12 @@
   <div>
     <div class="panel panel-default">
       <div class="panel-body">
-        <div v-html="content" class="tos-content">
-        </div>
+        <!-- eslint-disable vue/no-v-html -->
+        <div
+          class="tos-content"
+          v-html="content"
+        />
+      <!-- eslint-enable vue/no-v-html -->
       </div>
     </div>
   </div>
diff --git a/src/components/timeago/timeago.vue b/src/components/timeago/timeago.vue
new file mode 100644
index 00000000..6df0524d
--- /dev/null
+++ b/src/components/timeago/timeago.vue
@@ -0,0 +1,51 @@
+<template>
+  <time
+    :datetime="time"
+    :title="localeDateString"
+  >
+    {{ $t(relativeTime.key, [relativeTime.num]) }}
+  </time>
+</template>
+
+<script>
+import * as DateUtils from 'src/services/date_utils/date_utils.js'
+
+export default {
+  name: 'Timeago',
+  props: ['time', 'autoUpdate', 'longFormat', 'nowThreshold'],
+  data () {
+    return {
+      relativeTime: { key: 'time.now', num: 0 },
+      interval: null
+    }
+  },
+  computed: {
+    localeDateString () {
+      return typeof this.time === 'string'
+        ? new Date(Date.parse(this.time)).toLocaleString()
+        : this.time.toLocaleString()
+    }
+  },
+  created () {
+    this.refreshRelativeTimeObject()
+  },
+  destroyed () {
+    clearTimeout(this.interval)
+  },
+  methods: {
+    refreshRelativeTimeObject () {
+      const nowThreshold = typeof this.nowThreshold === 'number' ? this.nowThreshold : 1
+      this.relativeTime = this.longFormat
+        ? DateUtils.relativeTime(this.time, nowThreshold)
+        : DateUtils.relativeTimeShort(this.time, nowThreshold)
+
+      if (this.autoUpdate) {
+        this.interval = setTimeout(
+          this.refreshRelativeTimeObject,
+          1000 * this.autoUpdate
+        )
+      }
+    }
+  }
+}
+</script>
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 1da7d5cc..5e24bd15 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -52,7 +52,7 @@ const Timeline = {
 
     window.addEventListener('scroll', this.scrollLoad)
 
-    if (this.timelineName === 'friends' && !credentials) { return false }
+    if (store.state.api.fetchers[this.timelineName]) { return false }
 
     timelineFetcher.fetchAndUpdate({
       store,
@@ -78,13 +78,15 @@ const Timeline = {
   },
   methods: {
     handleShortKey (e) {
+      // Ignore when input fields are focused
+      if (['textarea', 'input'].includes(e.target.tagName.toLowerCase())) return
       if (e.key === '.') this.showNewStatuses()
     },
     showNewStatuses () {
       if (this.newStatusCount === 0) return
 
       if (this.timeline.flushMarker !== 0) {
-        this.$store.commit('clearTimeline', { timeline: this.timelineName })
+        this.$store.commit('clearTimeline', { timeline: this.timelineName, excludeUserId: true })
         this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
         this.fetchOlderStatuses()
       } else {
@@ -137,7 +139,7 @@ const Timeline = {
         if (top < 15 &&
             !this.paused &&
             !(this.unfocused && this.$store.state.config.pauseOnUnfocused)
-           ) {
+        ) {
           this.showNewStatuses()
         } else {
           this.paused = true
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index e0a34bd1..1fc52083 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -2,41 +2,66 @@
   <div :class="classes.root">
     <div :class="classes.header">
       <div class="title">
-        {{title}}
+        {{ title }}
       </div>
-      <div @click.prevent class="loadmore-error alert error" v-if="timelineError">
-        {{$t('timeline.error_fetching')}}
+      <div
+        v-if="timelineError"
+        class="loadmore-error alert error"
+        @click.prevent
+      >
+        {{ $t('timeline.error_fetching') }}
       </div>
-      <button @click.prevent="showNewStatuses" class="loadmore-button" v-if="timeline.newStatusCount > 0 && !timelineError">
-        {{$t('timeline.show_new')}}{{newStatusCountStr}}
+      <button
+        v-if="timeline.newStatusCount > 0 && !timelineError"
+        class="loadmore-button"
+        @click.prevent="showNewStatuses"
+      >
+        {{ $t('timeline.show_new') }}{{ newStatusCountStr }}
       </button>
-      <div @click.prevent class="loadmore-text faint" v-if="!timeline.newStatusCount > 0 && !timelineError">
-        {{$t('timeline.up_to_date')}}
+      <div
+        v-if="!timeline.newStatusCount > 0 && !timelineError"
+        class="loadmore-text faint"
+        @click.prevent
+      >
+        {{ $t('timeline.up_to_date') }}
       </div>
     </div>
     <div :class="classes.body">
       <div class="timeline">
-        <conversation 
+        <conversation
           v-for="status in timeline.visibleStatuses"
-          class="status-fadein"
           :key="status.id"
+          class="status-fadein"
           :statusoid="status"
           :collapsable="true"
         />
       </div>
     </div>
     <div :class="classes.footer">
-      <div v-if="count===0" class="new-status-notification text-center panel-footer faint">
-        {{$t('timeline.no_statuses')}}
+      <div
+        v-if="count===0"
+        class="new-status-notification text-center panel-footer faint"
+      >
+        {{ $t('timeline.no_statuses') }}
       </div>
-      <div v-else-if="bottomedOut" class="new-status-notification text-center panel-footer faint">
-        {{$t('timeline.no_more_statuses')}}
+      <div
+        v-else-if="bottomedOut"
+        class="new-status-notification text-center panel-footer faint"
+      >
+        {{ $t('timeline.no_more_statuses') }}
       </div>
-      <a v-else-if="!timeline.loading" href="#" v-on:click.prevent='fetchOlderStatuses()'>
-        <div class="new-status-notification text-center panel-footer">{{$t('timeline.load_older')}}</div>
+      <a
+        v-else-if="!timeline.loading"
+        href="#"
+        @click.prevent="fetchOlderStatuses()"
+      >
+        <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div>
       </a>
-      <div v-else class="new-status-notification text-center panel-footer">
-        <i class="icon-spin3 animate-spin"/>
+      <div
+        v-else
+        class="new-status-notification text-center panel-footer"
+      >
+        <i class="icon-spin3 animate-spin" />
       </div>
     </div>
   </div>
diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js
index e6fed3b5..a42b9c71 100644
--- a/src/components/user_avatar/user_avatar.js
+++ b/src/components/user_avatar/user_avatar.js
@@ -2,7 +2,7 @@ import StillImage from '../still-image/still-image.vue'
 
 const UserAvatar = {
   props: [
-    'src',
+    'user',
     'betterShadow',
     'compact'
   ],
diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue
index 6bf7123d..811efd3c 100644
--- a/src/components/user_avatar/user_avatar.vue
+++ b/src/components/user_avatar/user_avatar.vue
@@ -1,9 +1,11 @@
 <template>
   <StillImage
     class="avatar"
+    :alt="user.screen_name"
+    :title="user.screen_name"
+    :src="user.profile_image_url_original"
     :class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
-    :src="imgSrc"
-    :imageLoadError="imageLoadError"
+    :image-load-error="imageLoadError"
   />
 </template>
 
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 197c61d5..e019ebbd 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -1,5 +1,7 @@
 import UserAvatar from '../user_avatar/user_avatar.vue'
 import RemoteFollow from '../remote_follow/remote_follow.vue'
+import ProgressButton from '../progress_button/progress_button.vue'
+import ModerationTools from '../moderation_tools/moderation_tools.vue'
 import { hex2rgb } from '../../services/color_convert/color_convert.js'
 import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -22,15 +24,15 @@ export default {
   computed: {
     classes () {
       return [{
-        'user-card-rounded-t': this.rounded === 'top',  // set border-top-left-radius and border-top-right-radius
-        'user-card-rounded': this.rounded === true,     // set border-radius for all sides
-        'user-card-bordered': this.bordered === true    // set border for all sides
+        'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius
+        'user-card-rounded': this.rounded === true, // set border-radius for all sides
+        'user-card-bordered': this.bordered === true // set border for all sides
       }]
     },
     style () {
       const color = this.$store.state.config.customTheme.colors
-            ? this.$store.state.config.customTheme.colors.bg  // v2
-            : this.$store.state.config.colors.bg // v1
+        ? this.$store.state.config.customTheme.colors.bg // v2
+        : this.$store.state.config.colors.bg // v1
 
       if (color) {
         const rgb = (typeof color === 'string') ? hex2rgb(color) : color
@@ -72,12 +74,12 @@ export default {
     userHighlightType: {
       get () {
         const data = this.$store.state.config.highlight[this.user.screen_name]
-        return data && data.type || 'disabled'
+        return (data && data.type) || 'disabled'
       },
       set (type) {
         const data = this.$store.state.config.highlight[this.user.screen_name]
         if (type !== 'disabled') {
-          this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: data && data.color || '#FFFFFF', type })
+          this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: (data && data.color) || '#FFFFFF', type })
         } else {
           this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: undefined })
         }
@@ -93,21 +95,24 @@ export default {
       }
     },
     visibleRole () {
-      const validRole = (this.user.role === 'admin' || this.user.role === 'moderator')
-      const showRole = this.isOtherUser || this.user.show_role
-
-      return validRole && showRole && this.user.role
+      const rights = this.user.rights
+      if (!rights) { return }
+      const validRole = rights.admin || rights.moderator
+      const roleTitle = rights.admin ? 'admin' : 'moderator'
+      return validRole && roleTitle
     }
   },
   components: {
     UserAvatar,
-    RemoteFollow
+    RemoteFollow,
+    ModerationTools,
+    ProgressButton
   },
   methods: {
     followUser () {
       const store = this.$store
       this.followRequestInProgress = true
-      requestFollow(this.user, store).then(({sent}) => {
+      requestFollow(this.user, store).then(({ sent }) => {
         this.followRequestInProgress = false
         this.followRequestSent = sent
       })
@@ -132,13 +137,19 @@ export default {
     unmuteUser () {
       this.$store.dispatch('unmuteUser', this.user.id)
     },
+    subscribeUser () {
+      return this.$store.dispatch('subscribeUser', this.user.id)
+    },
+    unsubscribeUser () {
+      return this.$store.dispatch('unsubscribeUser', this.user.id)
+    },
     setProfileView (v) {
       if (this.switcher) {
         const store = this.$store
         store.commit('setProfileView', { v })
       }
     },
-    linkClicked ({target}) {
+    linkClicked ({ target }) {
       if (target.tagName === 'SPAN') {
         target = target.parentNode
       }
@@ -148,6 +159,9 @@ export default {
     },
     userProfileLink (user) {
       return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
+    },
+    reportUser () {
+      this.$store.dispatch('openUserReportingModal', this.user.id)
     }
   }
 }
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 3259d1c5..9e142480 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -1,65 +1,127 @@
 <template>
-<div class="user-card" :class="classes" :style="style">
-  <div class="panel-heading">
-    <div class='user-info'>
-      <div class='container'>
-        <router-link :to="userProfileLink(user)">
-          <UserAvatar :betterShadow="betterShadow" :src="user.profile_image_url_original"/>
-        </router-link>
-        <div class="name-and-screen-name">
-          <div class="top-line">
-            <div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div>
-            <div :title="user.name" class='user-name' v-else>{{user.name}}</div>
-            <router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser">
-              <i class="button-icon icon-pencil usersettings" :title="$t('tool_tip.user_settings')"></i>
-            </router-link>
-            <a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser && !user.is_local">
-              <i class="icon-link-ext usersettings"></i>
-            </a>
-          </div>
-
-          <router-link class='user-screen-name' :to="userProfileLink(user)">
-            <span class="handle">@{{user.screen_name}}
-              <span class="alert staff" v-if="!hideBio && !!visibleRole">{{visibleRole}}</span>
-            </span><span v-if="user.locked"><i class="icon icon-lock"></i></span>
-            <span v-if="!hideUserStatsLocal && !hideBio" class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span>
+  <div
+    class="user-card"
+    :class="classes"
+    :style="style"
+  >
+    <div class="panel-heading">
+      <div class="user-info">
+        <div class="container">
+          <router-link :to="userProfileLink(user)">
+            <UserAvatar
+              :better-shadow="betterShadow"
+              :user="user"
+            />
           </router-link>
+          <div class="user-summary">
+            <div class="top-line">
+              <!-- eslint-disable vue/no-v-html -->
+              <div
+                v-if="user.name_html"
+                :title="user.name"
+                class="user-name"
+                v-html="user.name_html"
+              />
+              <!-- eslint-enable vue/no-v-html -->
+              <div
+                v-else
+                :title="user.name"
+                class="user-name"
+              >
+                {{ user.name }}
+              </div>
+              <router-link
+                v-if="!isOtherUser"
+                :to="{ name: 'user-settings' }"
+              >
+                <i
+                  class="button-icon icon-wrench usersettings"
+                  :title="$t('tool_tip.user_settings')"
+                />
+              </router-link>
+              <a
+                v-if="isOtherUser && !user.is_local"
+                :href="user.statusnet_profile_url"
+                target="_blank"
+              >
+                <i class="icon-link-ext usersettings" />
+              </a>
+            </div>
+
+            <div class="bottom-line">
+              <router-link
+                class="user-screen-name"
+                :to="userProfileLink(user)"
+              >
+                @{{ user.screen_name }}
+              </router-link>
+              <span
+                v-if="!hideBio && !!visibleRole"
+                class="alert staff"
+              >{{ visibleRole }}</span>
+              <span v-if="user.locked"><i class="icon icon-lock" /></span>
+              <span
+                v-if="!hideUserStatsLocal && !hideBio"
+                class="dailyAvg"
+              >{{ dailyAvg }} {{ $t('user_card.per_day') }}</span>
+            </div>
+          </div>
         </div>
-      </div>
-      <div class="user-meta">
-        <div v-if="user.follows_you && loggedIn && isOtherUser" class="following">
-          {{ $t('user_card.follows_you') }}
+        <div class="user-meta">
+          <div
+            v-if="user.follows_you && loggedIn && isOtherUser"
+            class="following"
+          >
+            {{ $t('user_card.follows_you') }}
+          </div>
+          <div
+            v-if="isOtherUser && (loggedIn || !switcher)"
+            class="highlighter"
+          >
+            <!-- id's need to be unique, otherwise vue confuses which user-card checkbox belongs to -->
+            <input
+              v-if="userHighlightType !== 'disabled'"
+              :id="'userHighlightColorTx'+user.id"
+              v-model="userHighlightColor"
+              class="userHighlightText"
+              type="text"
+            >
+            <input
+              v-if="userHighlightType !== 'disabled'"
+              :id="'userHighlightColor'+user.id"
+              v-model="userHighlightColor"
+              class="userHighlightCl"
+              type="color"
+            >
+            <label
+              for="style-switcher"
+              class="userHighlightSel select"
+            >
+              <select
+                :id="'userHighlightSel'+user.id"
+                v-model="userHighlightType"
+                class="userHighlightSel"
+              >
+                <option value="disabled">No highlight</option>
+                <option value="solid">Solid bg</option>
+                <option value="striped">Striped bg</option>
+                <option value="side">Side stripe</option>
+              </select>
+              <i class="icon-down-open" />
+            </label>
+          </div>
         </div>
-        <div class="highlighter" v-if="isOtherUser && (loggedIn || !switcher)">
-          <!-- id's need to be unique, otherwise vue confuses which user-card checkbox belongs to -->
-          <input class="userHighlightText" type="text" :id="'userHighlightColorTx'+user.id" v-if="userHighlightType !== 'disabled'" v-model="userHighlightColor"/>
-          <input class="userHighlightCl" type="color" :id="'userHighlightColor'+user.id" v-if="userHighlightType !== 'disabled'" v-model="userHighlightColor"/>
-          <label for="style-switcher" class='userHighlightSel select'>
-            <select class="userHighlightSel" :id="'userHighlightSel'+user.id" v-model="userHighlightType">
-              <option value="disabled">No highlight</option>
-              <option value="solid">Solid bg</option>
-              <option value="striped">Striped bg</option>
-              <option value="side">Side stripe</option>
-            </select>
-            <i class="icon-down-open"/>
-          </label>
-        </div>
-      </div>
-      <div v-if="isOtherUser" class="user-interactions">
-        <div class="follow" v-if="loggedIn">
-          <span v-if="user.following">
-            <!--Following them!-->
-            <button @click="unfollowUser" class="pressed" :disabled="followRequestInProgress" :title="$t('user_card.follow_unfollow')">
-              <template v-if="followRequestInProgress">
-                {{ $t('user_card.follow_progress') }}
-              </template>
-              <template v-else>
-                {{ $t('user_card.following') }}
-              </template>
-            </button>
-          </span>
-          <span v-if="!user.following">
-            <button @click="followUser" :disabled="followRequestInProgress" :title="followRequestSent ? $t('user_card.follow_again') : ''">
+        <div
+          v-if="loggedIn && isOtherUser"
+          class="user-interactions"
+        >
+          <div v-if="!user.following">
+            <button
+              class="btn btn-default btn-block"
+              :disabled="followRequestInProgress"
+              :title="followRequestSent ? $t('user_card.follow_again') : ''"
+              @click="followUser"
+            >
               <template v-if="followRequestInProgress">
                 {{ $t('user_card.follow_progress') }}
               </template>
@@ -70,57 +132,148 @@
                 {{ $t('user_card.follow') }}
               </template>
             </button>
-          </span>
-        </div>
-        <div class='mute' v-if='isOtherUser && loggedIn'>
-          <span v-if='user.muted'>
-            <button @click="unmuteUser" class="pressed">
+          </div>
+          <div v-else-if="followRequestInProgress">
+            <button
+              class="btn btn-default btn-block pressed"
+              disabled
+              :title="$t('user_card.follow_unfollow')"
+              @click="unfollowUser"
+            >
+              {{ $t('user_card.follow_progress') }}
+            </button>
+          </div>
+          <div
+            v-else
+            class="btn-group"
+          >
+            <button
+              class="btn btn-default pressed"
+              :title="$t('user_card.follow_unfollow')"
+              @click="unfollowUser"
+            >
+              {{ $t('user_card.following') }}
+            </button>
+            <ProgressButton
+              v-if="!user.subscribed"
+              class="btn btn-default"
+              :click="subscribeUser"
+              :title="$t('user_card.subscribe')"
+            >
+              <i class="icon-bell-alt" />
+            </ProgressButton>
+            <ProgressButton
+              v-else
+              class="btn btn-default pressed"
+              :click="unsubscribeUser"
+              :title="$t('user_card.unsubscribe')"
+            >
+              <i class="icon-bell-ringing-o" />
+            </ProgressButton>
+          </div>
+
+          <div>
+            <button
+              v-if="user.muted"
+              class="btn btn-default btn-block pressed"
+              @click="unmuteUser"
+            >
               {{ $t('user_card.muted') }}
             </button>
-          </span>
-          <span v-if='!user.muted'>
-            <button @click="muteUser">
+            <button
+              v-else
+              class="btn btn-default btn-block"
+              @click="muteUser"
+            >
               {{ $t('user_card.mute') }}
             </button>
-          </span>
-        </div>
-        <div v-if='!loggedIn && user.is_local'>
-          <RemoteFollow :user="user" />
-        </div>
-        <div class='block' v-if='isOtherUser && loggedIn'>
-          <span v-if='user.statusnet_blocking'>
-            <button @click="unblockUser" class="pressed">
+          </div>
+
+          <div>
+            <button
+              v-if="user.statusnet_blocking"
+              class="btn btn-default btn-block pressed"
+              @click="unblockUser"
+            >
               {{ $t('user_card.blocked') }}
             </button>
-          </span>
-          <span v-if='!user.statusnet_blocking'>
-            <button @click="blockUser">
+            <button
+              v-else
+              class="btn btn-default btn-block"
+              @click="blockUser"
+            >
               {{ $t('user_card.block') }}
             </button>
-          </span>
+          </div>
+
+          <div>
+            <button
+              class="btn btn-default btn-block"
+              @click="reportUser"
+            >
+              {{ $t('user_card.report') }}
+            </button>
+          </div>
+
+          <ModerationTools
+            v-if="loggedIn.role === &quot;admin&quot;"
+            :user="user"
+          />
+        </div>
+        <div
+          v-if="!loggedIn && user.is_local"
+          class="user-interactions"
+        >
+          <RemoteFollow :user="user" />
         </div>
       </div>
     </div>
-  </div>
-  <div class="panel-body" v-if="!hideBio">
-    <div v-if="!hideUserStatsLocal && switcher" class="user-counts">
-      <div class="user-count" v-on:click.prevent="setProfileView('statuses')">
-        <h5>{{ $t('user_card.statuses') }}</h5>
-        <span>{{user.statuses_count}} <br></span>
-      </div>
-      <div class="user-count" v-on:click.prevent="setProfileView('friends')">
-        <h5>{{ $t('user_card.followees') }}</h5>
-        <span>{{user.friends_count}}</span>
-      </div>
-      <div class="user-count" v-on:click.prevent="setProfileView('followers')">
-        <h5>{{ $t('user_card.followers') }}</h5>
-        <span>{{user.followers_count}}</span>
+    <div
+      v-if="!hideBio"
+      class="panel-body"
+    >
+      <div
+        v-if="!hideUserStatsLocal && switcher"
+        class="user-counts"
+      >
+        <div
+          class="user-count"
+          @click.prevent="setProfileView('statuses')"
+        >
+          <h5>{{ $t('user_card.statuses') }}</h5>
+          <span>{{ user.statuses_count }} <br></span>
+        </div>
+        <div
+          class="user-count"
+          @click.prevent="setProfileView('friends')"
+        >
+          <h5>{{ $t('user_card.followees') }}</h5>
+          <span>{{ user.friends_count }}</span>
+        </div>
+        <div
+          class="user-count"
+          @click.prevent="setProfileView('followers')"
+        >
+          <h5>{{ $t('user_card.followers') }}</h5>
+          <span>{{ user.followers_count }}</span>
+        </div>
       </div>
+      <!-- eslint-disable vue/no-v-html -->
+      <p
+        v-if="!hideBio && user.description_html"
+        class="user-card-bio"
+        @click.prevent="linkClicked"
+        v-html="user.description_html"
+      />
+      <!-- eslint-enable vue/no-v-html -->
+      <p
+        v-else-if="!hideBio"
+        class="user-card-bio"
+      >
+        {{ user.description }}
+      </p>
     </div>
-    <p @click.prevent="linkClicked" v-if="!hideBio && user.description_html" class="user-card-bio" v-html="user.description_html"></p>
-    <p v-else-if="!hideBio" class="user-card-bio">{{ user.description }}</p>
   </div>
-</div>
 </template>
 
 <script src="./user_card.js"></script>
@@ -130,7 +283,6 @@
 
 .user-card {
   background-size: cover;
-  overflow: hidden;
 
   .panel-heading {
     padding: .5em 0;
@@ -145,6 +297,8 @@
     word-wrap: break-word;
     background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%);
     background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%);
+    border-bottom-right-radius: inherit;
+    border-bottom-left-radius: inherit;
   }
 
   p {
@@ -160,7 +314,7 @@
       max-width: 100%;
       max-height: 400px;
 
-      .emoji {
+      &.emoji {
         width: 32px;
         height: 32px;
       }
@@ -224,7 +378,7 @@
     opacity: .8;
   }
 
-  .name-and-screen-name {
+  .user-summary {
     display: block;
     margin-left: 0.6em;
     text-align: left;
@@ -241,6 +395,7 @@
       vertical-align: middle;
       object-fit: contain
     }
+
     .top-line {
       display: flex;
     }
@@ -261,15 +416,19 @@
     }
   }
 
-  .user-screen-name {
-    color: $fallback--lightText;
-    color: var(--lightText, $fallback--lightText);
-    display: inline-block;
+  .bottom-line {
+    display: flex;
     font-weight: light;
     font-size: 15px;
-    padding-right: 0.1em;
-    width: 100%;
-    display: flex;
+
+    .user-screen-name {
+      min-width: 1px;
+      flex: 0 1 auto;
+      text-overflow: ellipsis;
+      overflow: hidden;
+      color: $fallback--lightText;
+      color: var(--lightText, $fallback--lightText);
+    }
 
     .dailyAvg {
       min-width: 1px;
@@ -280,15 +439,9 @@
       color: var(--text, $fallback--text);
     }
 
-    .handle {
-      min-width: 1px;
-      flex: 0 1 auto;
-      text-overflow: ellipsis;
-      overflow: hidden;
-    }
-
     // TODO use proper colors
     .staff {
+      flex: none;
       text-transform: capitalize;
       color: $fallback--text;
       color: var(--btnText, $fallback--text);
@@ -351,43 +504,26 @@
     }
   }
   .user-interactions {
+    position: relative;
     display: flex;
     flex-flow: row wrap;
     justify-content: space-between;
-
     margin-right: -.75em;
 
-    div {
+    > * {
       flex: 1 0 0;
-      margin-right: .75em;
-      margin-bottom: .6em;
+      margin: 0 .75em .6em 0;
       white-space: nowrap;
     }
 
-    .mute {
-      max-width: 220px;
-      min-height: 28px;
-    }
-
-    .follow {
-      max-width: 220px;
-      min-height: 28px;
-    }
-
     button {
-      width: 100%;
-      height: 100%;
       margin: 0;
-    }
 
-    .remote-button {
-      height: 28px !important;
-      width: 92%;
-    }
-
-    .pressed {
-      border-bottom-color: rgba(255, 255, 255, 0.2);
-      border-top-color: rgba(0, 0, 0, 0.2);
+      &.pressed {
+        // TODO: This should be themed.
+        border-bottom-color: rgba(255, 255, 255, 0.2);
+        border-top-color: rgba(0, 0, 0, 0.2);
+      }
     }
   }
 }
diff --git a/src/components/user_finder/user_finder.js b/src/components/user_finder/user_finder.js
deleted file mode 100644
index 27153f45..00000000
--- a/src/components/user_finder/user_finder.js
+++ /dev/null
@@ -1,20 +0,0 @@
-const UserFinder = {
-  data: () => ({
-    username: undefined,
-    hidden: true,
-    error: false,
-    loading: false
-  }),
-  methods: {
-    findUser (username) {
-      this.$router.push({ name: 'user-search', query: { query: username } })
-      this.$refs.userSearchInput.focus()
-    },
-    toggleHidden () {
-      this.hidden = !this.hidden
-      this.$emit('toggled', this.hidden)
-    }
-  }
-}
-
-export default UserFinder
diff --git a/src/components/user_finder/user_finder.vue b/src/components/user_finder/user_finder.vue
deleted file mode 100644
index a118ffe2..00000000
--- a/src/components/user_finder/user_finder.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-<template>
-  <div>
-    <div class="user-finder-container">
-      <i class="icon-spin4 user-finder-icon animate-spin-slow" v-if="loading" />
-      <a href="#" v-if="hidden" :title="$t('finder.find_user')"><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden" /></a>
-      <template v-else>
-        <input class="user-finder-input" ref="userSearchInput" @keyup.enter="findUser(username)" v-model="username" :placeholder="$t('finder.find_user')" id="user-finder-input" type="text"/>
-        <button class="btn search-button" @click="findUser(username)">
-          <i class="icon-search"/>
-        </button>
-        <i class="button-icon icon-cancel user-finder-icon" @click.prevent.stop="toggleHidden"/>
-      </template>
-    </div>
-  </div>
-</template>
-
-<script src="./user_finder.js"></script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-
-.user-finder-container {
-  max-width: 100%;
-  display: inline-flex;
-  align-items: baseline;
-  vertical-align: baseline;
-
-
-  .user-finder-input,
-  .search-button {
-    height: 29px;
-  }
-  .user-finder-input {
-    // TODO: do this properly without a rough guesstimate of 2 icons + paddings
-    max-width: calc(100% - 30px - 30px - 20px);
-  }
-
-  .search-button {
-    margin-left: .5em;
-    margin-right: .5em;
-  }
-}
-
-</style>
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..c92630e3 100644
--- a/src/components/user_panel/user_panel.vue
+++ b/src/components/user_panel/user_panel.vue
@@ -1,13 +1,30 @@
 <template>
   <div class="user-panel">
-    <div v-if='user' class="panel panel-default" style="overflow: visible;">
-      <UserCard :user="user" :hideBio="true" rounded="top"/>
+    <div
+      v-if="signedIn"
+      key="user-panel"
+      class="panel panel-default signed-in"
+    >
+      <UserCard
+        :user="user"
+        :hide-bio="true"
+        rounded="top"
+      />
       <div class="panel-footer">
-        <post-status-form v-if='user'></post-status-form>
+        <post-status-form v-if="user" />
       </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_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 1df06fe6..39b99dac 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -1,47 +1,39 @@
-import { compose } from 'vue-compose'
 import get from 'lodash/get'
 import UserCard from '../user_card/user_card.vue'
 import FollowCard from '../follow_card/follow_card.vue'
 import Timeline from '../timeline/timeline.vue'
+import Conversation from '../conversation/conversation.vue'
+import List from '../list/list.vue'
 import withLoadMore from '../../hocs/with_load_more/with_load_more'
-import withList from '../../hocs/with_list/with_list'
 
-const FollowerList = compose(
-  withLoadMore({
-    fetch: (props, $store) => $store.dispatch('addFollowers', props.userId),
-    select: (props, $store) => get($store.getters.findUser(props.userId), 'followers', []),
-    destory: (props, $store) => $store.dispatch('clearFollowers', props.userId),
-    childPropName: 'entries',
-    additionalPropNames: ['userId']
-  }),
-  withList({ getEntryProps: user => ({ user }) })
-)(FollowCard)
+const FollowerList = withLoadMore({
+  fetch: (props, $store) => $store.dispatch('fetchFollowers', props.userId),
+  select: (props, $store) => get($store.getters.findUser(props.userId), 'followerIds', []).map(id => $store.getters.findUser(id)),
+  destroy: (props, $store) => $store.dispatch('clearFollowers', props.userId),
+  childPropName: 'items',
+  additionalPropNames: ['userId']
+})(List)
 
-const FriendList = compose(
-  withLoadMore({
-    fetch: (props, $store) => $store.dispatch('addFriends', props.userId),
-    select: (props, $store) => get($store.getters.findUser(props.userId), 'friends', []),
-    destory: (props, $store) => $store.dispatch('clearFriends', props.userId),
-    childPropName: 'entries',
-    additionalPropNames: ['userId']
-  }),
-  withList({ getEntryProps: user => ({ user }) })
-)(FollowCard)
+const FriendList = withLoadMore({
+  fetch: (props, $store) => $store.dispatch('fetchFriends', props.userId),
+  select: (props, $store) => get($store.getters.findUser(props.userId), 'friendIds', []).map(id => $store.getters.findUser(id)),
+  destroy: (props, $store) => $store.dispatch('clearFriends', props.userId),
+  childPropName: 'items',
+  additionalPropNames: ['userId']
+})(List)
 
 const UserProfile = {
   data () {
     return {
       error: false,
-      fetchedUserId: null
+      userId: null
     }
   },
   created () {
-    if (!this.user.id) {
-      this.fetchUserId()
-        .then(() => this.startUp())
-    } else {
-      this.startUp()
-    }
+    // Make sure that timelines used in this page are empty
+    this.cleanUp()
+    const routeParams = this.$route.params
+    this.load(routeParams.name || routeParams.id)
   },
   destroyed () {
     this.cleanUp()
@@ -56,26 +48,12 @@ const UserProfile = {
     media () {
       return this.$store.state.statuses.timelines.media
     },
-    userId () {
-      return this.$route.params.id || this.user.id || this.fetchedUserId
-    },
-    userName () {
-      return this.$route.params.name || this.user.screen_name
-    },
     isUs () {
       return this.userId && this.$store.state.users.currentUser.id &&
         this.userId === this.$store.state.users.currentUser.id
     },
-    userInStore () {
-      const routeParams = this.$route.params
-      // This needs fetchedUserId so that computed will be refreshed when user is fetched
-      return this.$store.getters.findUser(this.fetchedUserId || routeParams.name || routeParams.id)
-    },
     user () {
-      if (this.userInStore) {
-        return this.userInStore
-      }
-      return {}
+      return this.$store.getters.findUser(this.userId)
     },
     isExternal () {
       return this.$route.name === 'external-user-profile'
@@ -88,40 +66,39 @@ const UserProfile = {
     }
   },
   methods: {
-    startFetchFavorites () {
-      if (this.isUs) {
-        this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.userId })
-      }
-    },
-    fetchUserId () {
-      let fetchPromise
-      if (this.userId && !this.$route.params.name) {
-        fetchPromise = this.$store.dispatch('fetchUser', this.userId)
+    load (userNameOrId) {
+      // Check if user data is already loaded in store
+      const user = this.$store.getters.findUser(userNameOrId)
+      if (user) {
+        this.userId = user.id
+        this.fetchTimelines()
       } else {
-        fetchPromise = this.$store.dispatch('fetchUser', this.userName)
+        this.$store.dispatch('fetchUser', userNameOrId)
           .then(({ id }) => {
-            this.fetchedUserId = id
+            this.userId = id
+            this.fetchTimelines()
+          })
+          .catch((reason) => {
+            const errorMessage = get(reason, 'error.error')
+            if (errorMessage === 'No user with such user_id') { // Known error
+              this.error = this.$t('user_profile.profile_does_not_exist')
+            } else if (errorMessage) {
+              this.error = errorMessage
+            } else {
+              this.error = this.$t('user_profile.profile_loading_error')
+            }
           })
       }
-      return fetchPromise
-        .catch((reason) => {
-          const errorMessage = get(reason, 'error.error')
-          if (errorMessage === 'No user with such user_id') { // Known error
-            this.error = this.$t('user_profile.profile_does_not_exist')
-          } else if (errorMessage) {
-            this.error = errorMessage
-          } else {
-            this.error = this.$t('user_profile.profile_loading_error')
-          }
-        })
-        .then(() => this.startUp())
     },
-    startUp () {
-      if (this.userId) {
-        this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId })
-        this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId })
-        this.startFetchFavorites()
+    fetchTimelines () {
+      const userId = this.userId
+      this.$store.dispatch('startFetchingTimeline', { timeline: 'user', userId })
+      this.$store.dispatch('startFetchingTimeline', { timeline: 'media', userId })
+      if (this.isUs) {
+        this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId })
       }
+      // Fetch all pinned statuses immediately
+      this.$store.dispatch('fetchPinnedStatuses', userId)
     },
     cleanUp () {
       this.$store.dispatch('stopFetching', 'user')
@@ -133,18 +110,16 @@ const UserProfile = {
     }
   },
   watch: {
-    // userId can be undefined if we don't know it yet
-    userId (newVal) {
+    '$route.params.id': function (newVal) {
       if (newVal) {
         this.cleanUp()
-        this.startUp()
+        this.load(newVal)
       }
     },
-    userName () {
-      if (this.$route.params.name) {
-        this.fetchUserId()
+    '$route.params.name': function (newVal) {
+      if (newVal) {
         this.cleanUp()
-        this.startUp()
+        this.load(newVal)
       }
     },
     $route () {
@@ -155,7 +130,9 @@ const UserProfile = {
     UserCard,
     Timeline,
     FollowerList,
-    FriendList
+    FriendList,
+    FollowCard,
+    Conversation
   }
 }
 
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index d449eb85..4ea0a869 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -1,55 +1,110 @@
 <template>
-<div>
-  <div v-if="user.id" class="user-profile panel panel-default">
-    <UserCard :user="user" :switcher="true" :selected="timeline.viewing" rounded="top"/>
-    <tab-switcher :renderOnlyFocused="true" ref="tabSwitcher">
-      <Timeline
-        :label="$t('user_card.statuses')"
-        :disabled="!user.statuses_count"
-        :count="user.statuses_count"
-        :embedded="true"
-        :title="$t('user_profile.timeline_title')"
-        :timeline="timeline"
-        :timeline-name="'user'"
-        :user-id="userId"
+  <div>
+    <div
+      v-if="user"
+      class="user-profile panel panel-default"
+    >
+      <UserCard
+        :user="user"
+        :switcher="true"
+        :selected="timeline.viewing"
+        rounded="top"
       />
-      <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
-        <FriendList :userId="userId" />
+      <tab-switcher
+        ref="tabSwitcher"
+        :render-only-focused="true"
+      >
+        <div :label="$t('user_card.statuses')">
+          <div class="timeline">
+            <template v-for="statusId in user.pinnedStatuseIds">
+              <Conversation
+                v-if="timeline.statusesObject[statusId]"
+                :key="statusId"
+                class="status-fadein"
+                :statusoid="timeline.statusesObject[statusId]"
+                :collapsable="true"
+                :show-pinned="true"
+              />
+            </template>
+          </div>
+          <Timeline
+            :count="user.statuses_count"
+            :embedded="true"
+            :title="$t('user_profile.timeline_title')"
+            :timeline="timeline"
+            :timeline-name="'user'"
+            :user-id="userId"
+          />
+        </div>
+        <div
+          v-if="followsTabVisible"
+          :label="$t('user_card.followees')"
+          :disabled="!user.friends_count"
+        >
+          <FriendList :user-id="userId">
+            <template
+              slot="item"
+              slot-scope="{item}"
+            >
+              <FollowCard :user="item" />
+            </template>
+          </FriendList>
+        </div>
+        <div
+          v-if="followersTabVisible"
+          :label="$t('user_card.followers')"
+          :disabled="!user.followers_count"
+        >
+          <FollowerList :user-id="userId">
+            <template
+              slot="item"
+              slot-scope="{item}"
+            >
+              <FollowCard
+                :user="item"
+                :no-follows-you="isUs"
+              />
+            </template>
+          </FollowerList>
+        </div>
+        <Timeline
+          :label="$t('user_card.media')"
+          :disabled="!media.visibleStatuses.length"
+          :embedded="true"
+          :title="$t('user_card.media')"
+          timeline-name="media"
+          :timeline="media"
+          :user-id="userId"
+        />
+        <Timeline
+          v-if="isUs"
+          :label="$t('user_card.favorites')"
+          :disabled="!favorites.visibleStatuses.length"
+          :embedded="true"
+          :title="$t('user_card.favorites')"
+          timeline-name="favorites"
+          :timeline="favorites"
+        />
+      </tab-switcher>
+    </div>
+    <div
+      v-else
+      class="panel user-profile-placeholder"
+    >
+      <div class="panel-heading">
+        <div class="title">
+          {{ $t('settings.profile_tab') }}
+        </div>
       </div>
-      <div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count">
-        <FollowerList :userId="userId" :entryProps="{noFollowsYou: isUs}" />
-      </div>
-      <Timeline
-        :label="$t('user_card.media')"
-        :disabled="!media.visibleStatuses.length"
-        :embedded="true" :title="$t('user_card.media')"
-        timeline-name="media"
-        :timeline="media"
-        :user-id="userId"
-      />
-      <Timeline
-        v-if="isUs"
-        :label="$t('user_card.favorites')"
-        :disabled="!favorites.visibleStatuses.length"
-        :embedded="true"
-        :title="$t('user_card.favorites')"
-        timeline-name="favorites"
-        :timeline="favorites"
-      />
-    </tab-switcher>
-  </div>
-  <div v-else class="panel user-profile-placeholder">
-    <div class="panel-heading">
-      <div class="title">
-        {{ $t('settings.profile_tab') }}
+      <div class="panel-body">
+        <span v-if="error">{{ error }}</span>
+        <i
+          v-else
+          class="icon-spin3 animate-spin"
+        />
       </div>
     </div>
-    <div class="panel-body">
-      <span v-if="error">{{ error }}</span>
-      <i class="icon-spin3 animate-spin" v-else></i>
-    </div>
   </div>
-</div>
 </template>
 
 <script src="./user_profile.js"></script>
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
new file mode 100644
index 00000000..7c6ea409
--- /dev/null
+++ b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -0,0 +1,106 @@
+
+import Status from '../status/status.vue'
+import List from '../list/list.vue'
+import Checkbox from '../checkbox/checkbox.vue'
+
+const UserReportingModal = {
+  components: {
+    Status,
+    List,
+    Checkbox
+  },
+  data () {
+    return {
+      comment: '',
+      forward: false,
+      statusIdsToReport: [],
+      processing: false,
+      error: false
+    }
+  },
+  computed: {
+    isLoggedIn () {
+      return !!this.$store.state.users.currentUser
+    },
+    isOpen () {
+      return this.isLoggedIn && this.$store.state.reports.modalActivated
+    },
+    userId () {
+      return this.$store.state.reports.userId
+    },
+    user () {
+      return this.$store.getters.findUser(this.userId)
+    },
+    remoteInstance () {
+      return !this.user.is_local && this.user.screen_name.substr(this.user.screen_name.indexOf('@') + 1)
+    },
+    statuses () {
+      return this.$store.state.reports.statuses
+    }
+  },
+  watch: {
+    userId: 'resetState'
+  },
+  methods: {
+    resetState () {
+      // Reset state
+      this.comment = ''
+      this.forward = false
+      this.statusIdsToReport = []
+      this.processing = false
+      this.error = false
+    },
+    closeModal () {
+      this.$store.dispatch('closeUserReportingModal')
+    },
+    reportUser () {
+      this.processing = true
+      this.error = false
+      const params = {
+        userId: this.userId,
+        comment: this.comment,
+        forward: this.forward,
+        statusIds: this.statusIdsToReport
+      }
+      this.$store.state.api.backendInteractor.reportUser(params)
+        .then(() => {
+          this.processing = false
+          this.resetState()
+          this.closeModal()
+        })
+        .catch(() => {
+          this.processing = false
+          this.error = true
+        })
+    },
+    clearError () {
+      this.error = false
+    },
+    isChecked (statusId) {
+      return this.statusIdsToReport.indexOf(statusId) !== -1
+    },
+    toggleStatus (checked, statusId) {
+      if (checked === this.isChecked(statusId)) {
+        return
+      }
+
+      if (checked) {
+        this.statusIdsToReport.push(statusId)
+      } else {
+        this.statusIdsToReport.splice(this.statusIdsToReport.indexOf(statusId), 1)
+      }
+    },
+    resize (e) {
+      const target = e.target || e
+      if (!(target instanceof window.Element)) { return }
+      // Auto is needed to make textbox shrink when removing lines
+      target.style.height = 'auto'
+      target.style.height = `${target.scrollHeight}px`
+      if (target.value === '') {
+        target.style.height = null
+      }
+    }
+  }
+}
+
+export default UserReportingModal
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
new file mode 100644
index 00000000..c79a3707
--- /dev/null
+++ b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -0,0 +1,187 @@
+<template>
+  <div
+    v-if="isOpen"
+    class="modal-view"
+    @click="closeModal"
+  >
+    <div
+      class="user-reporting-panel panel"
+      @click.stop=""
+    >
+      <div class="panel-heading">
+        <div class="title">
+          {{ $t('user_reporting.title', [user.screen_name]) }}
+        </div>
+      </div>
+      <div class="panel-body">
+        <div class="user-reporting-panel-left">
+          <div>
+            <p>{{ $t('user_reporting.add_comment_description') }}</p>
+            <textarea
+              v-model="comment"
+              class="form-control"
+              :placeholder="$t('user_reporting.additional_comments')"
+              rows="1"
+              @input="resize"
+            />
+          </div>
+          <div v-if="!user.is_local">
+            <p>{{ $t('user_reporting.forward_description') }}</p>
+            <Checkbox v-model="forward">
+              {{ $t('user_reporting.forward_to', [remoteInstance]) }}
+            </Checkbox>
+          </div>
+          <div>
+            <button
+              class="btn btn-default"
+              :disabled="processing"
+              @click="reportUser"
+            >
+              {{ $t('user_reporting.submit') }}
+            </button>
+            <div
+              v-if="error"
+              class="alert error"
+            >
+              {{ $t('user_reporting.generic_error') }}
+            </div>
+          </div>
+        </div>
+        <div class="user-reporting-panel-right">
+          <List :items="statuses">
+            <template
+              slot="item"
+              slot-scope="{item}"
+            >
+              <div class="status-fadein user-reporting-panel-sitem">
+                <Status
+                  :in-conversation="false"
+                  :focused="false"
+                  :statusoid="item"
+                />
+                <Checkbox
+                  :checked="isChecked(item.id)"
+                  @change="checked => toggleStatus(checked, item.id)"
+                />
+              </div>
+            </template>
+          </List>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script src="./user_reporting_modal.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.user-reporting-panel {
+  width: 90vw;
+  max-width: 700px;
+  min-height: 20vh;
+  max-height: 80vh;
+
+  .panel-heading {
+    .title {
+      text-align: center;
+      // TODO: Consider making these as default of panel
+      flex: 1;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+
+  .panel-body {
+    display: flex;
+    flex-direction: column-reverse;
+    border-top: 1px solid;
+    border-color: $fallback--border;
+    border-color: var(--border, $fallback--border);
+    overflow: hidden;
+  }
+
+  &-left {
+    padding: 1.1em 0.7em 0.7em;
+    line-height: 1.4em;
+    box-sizing: border-box;
+
+    > div {
+      margin-bottom: 1em;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+
+    p {
+      margin-top: 0;
+    }
+
+    textarea.form-control {
+      line-height: 16px;
+      resize: none;
+      overflow: hidden;
+      transition: min-height 200ms 100ms;
+      min-height: 44px;
+      width: 100%;
+    }
+
+    .btn {
+      min-width: 10em;
+      padding: 0 2em;
+    }
+
+    .alert {
+      margin: 1em 0 0 0;
+      line-height: 1.3em;
+    }
+  }
+
+  &-right {
+    display: flex;
+    flex-direction: column;
+    overflow-y: auto;
+  }
+
+  &-sitem {
+    display: flex;
+    justify-content: space-between;
+
+    > .status-el {
+      flex: 1;
+    }
+
+    > .checkbox {
+      margin: 0.75em;
+    }
+  }
+
+  @media all and (min-width: 801px) {
+    .panel-body {
+      flex-direction: row;
+    }
+
+    &-left {
+      width: 50%;
+      max-width: 320px;
+      border-right: 1px solid;
+      border-color: $fallback--border;
+      border-color: var(--border, $fallback--border);
+      padding: 1.1em;
+
+      > div {
+        margin-bottom: 2em;
+      }
+    }
+
+    &-right {
+      width: 50%;
+      flex: 1 1 auto;
+      margin-bottom: 12px;
+    }
+  }
+}
+</style>
diff --git a/src/components/user_search/user_search.js b/src/components/user_search/user_search.js
deleted file mode 100644
index 55040826..00000000
--- a/src/components/user_search/user_search.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import FollowCard from '../follow_card/follow_card.vue'
-import userSearchApi from '../../services/new_api/user_search.js'
-const userSearch = {
-  components: {
-    FollowCard
-  },
-  props: [
-    'query'
-  ],
-  data () {
-    return {
-      username: '',
-      users: [],
-      loading: false
-    }
-  },
-  mounted () {
-    this.search(this.query)
-  },
-  watch: {
-    query (newV) {
-      this.search(newV)
-    }
-  },
-  methods: {
-    newQuery (query) {
-      this.$router.push({ name: 'user-search', query: { query } })
-      this.$refs.userSearchInput.focus()
-    },
-    search (query) {
-      if (!query) {
-        this.users = []
-        return
-      }
-      this.loading = true
-      userSearchApi.search({query, store: this.$store})
-        .then((res) => {
-          this.loading = false
-          this.users = res
-        })
-    }
-  }
-}
-
-export default userSearch
diff --git a/src/components/user_search/user_search.vue b/src/components/user_search/user_search.vue
deleted file mode 100644
index 1269eea6..00000000
--- a/src/components/user_search/user_search.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-<template>
-  <div class="user-search panel panel-default">
-    <div class="panel-heading">
-      {{$t('nav.user_search')}}
-    </div>
-    <div class="user-search-input-container">
-      <input class="user-finder-input" ref="userSearchInput" @keyup.enter="newQuery(username)" v-model="username" :placeholder="$t('finder.find_user')"/>
-      <button class="btn search-button" @click="newQuery(username)">
-        <i class="icon-search"/>
-      </button>
-    </div>
-    <div v-if="loading" class="text-center loading-icon">
-      <i class="icon-spin3 animate-spin"/>
-    </div>
-    <div v-else class="panel-body">
-      <FollowCard v-for="user in users" :key="user.id" :user="user"/>
-    </div>
-  </div>
-</template>
-
-<script src="./user_search.js"></script>
-
-<style lang="scss">
-.user-search-input-container {
-  margin: 0.5em;
-  display: flex;
-  justify-content: center;
-
-  .search-button {
-    margin-left: 0.5em;
-  }
-}
-
-.loading-icon {
-  padding: 1em;
-}
-</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..69b3811b
--- /dev/null
+++ b/src/components/user_settings/confirm.vue
@@ -0,0 +1,22 @@
+<template>
+  <div>
+    <slot />
+    <button
+      class="btn btn-default"
+      :disabled="disabled"
+      @click="confirm"
+    >
+      {{ $t('general.confirm') }}
+    </button>
+    <button
+      class="btn btn-default"
+      :disabled="disabled"
+      @click="cancel"
+    >
+      {{ $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..3090138a
--- /dev/null
+++ b/src/components/user_settings/mfa.js
@@ -0,0 +1,155 @@
+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
+      available: false,
+      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()
+      if (result.error) return
+      this.settings = result.settings
+      this.settings.available = true
+      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..14ea10a1
--- /dev/null
+++ b/src/components/user_settings/mfa.vue
@@ -0,0 +1,173 @@
+<template>
+  <div
+    v-if="readyInit && settings.available"
+    class="setting-item mfa-settings"
+  >
+    <div class="mfa-heading">
+      <h2>{{ $t('settings.mfa.title') }}</h2>
+    </div>
+
+    <div>
+      <div
+        v-if="!setupInProgress"
+        class="setting-item"
+      >
+        <!-- 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
+            v-if="!confirmNewBackupCodes"
+            :backup-codes="backupCodes"
+          />
+          <button
+            v-if="!confirmNewBackupCodes"
+            class="btn btn-default"
+            @click="getBackupCodes"
+          >
+            {{ $t('settings.mfa.generate_new_recovery_codes') }}
+          </button>
+
+          <div v-if="confirmNewBackupCodes">
+            <confirm
+              :disabled="backupCodes.inProgress"
+              @confirm="confirmBackupCodes"
+              @cancel="cancelBackupCodes"
+            >
+              <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
+          v-if="!setupOTPInProgress"
+          :backup-codes="backupCodes"
+        />
+
+        <button
+          v-if="canSetupOTP"
+          class="btn btn-default"
+          @click="cancelSetup"
+        >
+          {{ $t('general.cancel') }}
+        </button>
+
+        <button
+          v-if="canSetupOTP"
+          class="btn btn-default"
+          @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 }"
+                />
+                <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
+                  v-model="otpConfirmToken"
+                  type="text"
+                >
+
+                <p>{{ $t('settings.enter_current_password_to_confirm') }}:</p>
+                <input
+                  v-model="currentPassword"
+                  type="password"
+                >
+                <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
+                  v-if="error"
+                  class="alert 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..e6c8ede2
--- /dev/null
+++ b/src/components/user_settings/mfa_backup_codes.vue
@@ -0,0 +1,33 @@
+<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"
+          :key="code"
+        >
+          {{ 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..c6f2cc7b
--- /dev/null
+++ b/src/components/user_settings/mfa_totp.vue
@@ -0,0 +1,43 @@
+<template>
+  <div>
+    <div class="method-item">
+      <strong>{{ $t('settings.mfa.otp') }}</strong>
+      <button
+        v-if="!isActivated"
+        class="btn btn-default"
+        @click="doActivate"
+      >
+        {{ $t('general.enable') }}
+      </button>
+
+      <button
+        v-if="isActivated"
+        class="btn btn-default"
+        :disabled="deactivate"
+        @click="doDeactivate"
+      >
+        {{ $t('general.disable') }}
+      </button>
+    </div>
+
+    <confirm
+      v-if="deactivate"
+      :disabled="inProgress"
+      @confirm="confirmDeactivate"
+      @cancel="cancelDeactivate"
+    >
+      {{ $t('settings.enter_current_password_to_confirm') }}:
+      <input
+        v-model="currentPassword"
+        type="password"
+      >
+    </confirm>
+    <div
+      v-if="error"
+      class="alert 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 b6a0479d..b5a7f0df 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -1,6 +1,7 @@
-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'
@@ -8,27 +9,27 @@ 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 SelectableList from '../selectable_list/selectable_list.vue'
+import ProgressButton from '../progress_button/progress_button.vue'
 import EmojiInput from '../emoji-input/emoji-input.vue'
+import suggestor from '../emoji-input/suggestor.js'
+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 withList from '../../hocs/with_list/with_list'
+import Mfa from './mfa.vue'
 
-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'
+})(SelectableList)
 
-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'
+})(SelectableList)
 
 const UserSettings = {
   data () {
@@ -42,15 +43,12 @@ 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,
+      banner: null,
       bannerPreview: null,
+      background: null,
       backgroundPreview: null,
       bannerUploadError: null,
       backgroundUploadError: null,
@@ -60,7 +58,8 @@ const UserSettings = {
       changePasswordInputs: [ '', '', '' ],
       changedPassword: false,
       changePasswordError: false,
-      activeTab: 'profile'
+      activeTab: 'profile',
+      notificationSettings: this.$store.state.users.currentUser.notification_settings
     }
   },
   created () {
@@ -73,12 +72,35 @@ const UserSettings = {
     ImageCropper,
     BlockList,
     MuteList,
-    EmojiInput
+    EmojiInput,
+    Autosuggest,
+    BlockCard,
+    MuteCard,
+    ProgressButton,
+    Importer,
+    Exporter,
+    Mfa
   },
   computed: {
     user () {
       return this.$store.state.users.currentUser
     },
+    emojiUserSuggestor () {
+      return suggestor({
+        emoji: [
+          ...this.$store.state.instance.emoji,
+          ...this.$store.state.instance.customEmoji
+        ],
+        users: this.$store.state.users.users,
+        updateUsersList: (input) => this.$store.dispatch('searchUsers', input)
+      })
+    },
+    emojiSuggestor () {
+      return suggestor({ emoji: [
+        ...this.$store.state.instance.emoji,
+        ...this.$store.state.instance.customEmoji
+      ] })
+    },
     pleromaBackend () {
       return this.$store.state.instance.pleromaBackend
     },
@@ -108,38 +130,28 @@ const UserSettings = {
   },
   methods: {
     updateProfile () {
-      const name = this.newName
-      const description = this.newBio
-      const locked = this.newLocked
-      // Backend notation.
-      /* eslint-disable camelcase */
-      const default_scope = this.newDefaultScope
-      const no_rich_text = this.newNoRichText
-      const hide_follows = this.hideFollows
-      const hide_followers = this.hideFollowers
-      const show_role = this.showRole
-
-      /* eslint-enable camelcase */
       this.$store.state.api.backendInteractor
         .updateProfile({
           params: {
-            name,
-            description,
-            locked,
+            note: this.newBio,
+            locked: this.newLocked,
             // Backend notation.
             /* eslint-disable camelcase */
-            default_scope,
-            no_rich_text,
-            hide_follows,
-            hide_followers,
-            show_role
+            display_name: this.newName,
+            default_scope: this.newDefaultScope,
+            no_rich_text: this.newNoRichText,
+            hide_follows: this.hideFollows,
+            hide_followers: this.hideFollowers,
+            show_role: this.showRole
             /* eslint-enable camelcase */
-          }}).then((user) => {
-            if (!user.error) {
-              this.$store.commit('addNewUsers', [user])
-              this.$store.commit('setCurrentUser', user)
-            }
-          })
+          } }).then((user) => {
+          this.$store.commit('addNewUsers', [user])
+          this.$store.commit('setCurrentUser', user)
+        })
+    },
+    updateNotificationSettings () {
+      this.$store.state.api.backendInteractor
+        .updateNotificationSettings({ settings: this.notificationSettings })
     },
     changeVis (visibility) {
       this.newDefaultScope = visibility
@@ -150,31 +162,37 @@ const UserSettings = {
       if (file.size > this.$store.state.instance[slot + 'limit']) {
         const filesize = fileSizeFormatService.fileSizeFormat(file.size)
         const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
-        this[slot + 'UploadError'] = this.$t('upload.error.base') + ' ' + this.$t('upload.error.file_too_big', {filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit})
+        this[slot + 'UploadError'] = this.$t('upload.error.base') + ' ' + this.$t('upload.error.file_too_big', { filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit })
         return
       }
       // eslint-disable-next-line no-undef
       const reader = new FileReader()
-      reader.onload = ({target}) => {
+      reader.onload = ({ target }) => {
         const img = target.result
         this[slot + 'Preview'] = img
+        this[slot] = file
       }
       reader.readAsDataURL(file)
     },
     submitAvatar (cropper, file) {
-      let img
-      if (cropper) {
-        img = cropper.getCroppedCanvas().toDataURL(file.type)
-      } else {
-        img = file
-      }
+      const that = this
+      return new Promise((resolve, reject) => {
+        function updateAvatar (avatar) {
+          that.$store.state.api.backendInteractor.updateAvatar({ avatar })
+            .then((user) => {
+              that.$store.commit('addNewUsers', [user])
+              that.$store.commit('setCurrentUser', user)
+              resolve()
+            })
+            .catch((err) => {
+              reject(new Error(that.$t('upload.error.base') + ' ' + err.message))
+            })
+        }
 
-      return this.$store.state.api.backendInteractor.updateAvatar({ params: { img } }).then((user) => {
-        if (!user.error) {
-          this.$store.commit('addNewUsers', [user])
-          this.$store.commit('setCurrentUser', user)
+        if (cropper) {
+          cropper.getCroppedCanvas().toBlob(updateAvatar, file.type)
         } else {
-          throw new Error(this.$t('upload.error.base') + user.error)
+          updateAvatar(file)
         }
       })
     },
@@ -184,49 +202,26 @@ const UserSettings = {
     submitBanner () {
       if (!this.bannerPreview) { return }
 
-      let banner = this.bannerPreview
-      // eslint-disable-next-line no-undef
-      let imginfo = new Image()
-      /* eslint-disable camelcase */
-      let offset_top, offset_left, width, height
-      imginfo.src = banner
-      width = imginfo.width
-      height = imginfo.height
-      offset_top = 0
-      offset_left = 0
       this.bannerUploading = true
-      this.$store.state.api.backendInteractor.updateBanner({params: {banner, offset_top, offset_left, width, height}}).then((data) => {
-        if (!data.error) {
-          let clone = JSON.parse(JSON.stringify(this.$store.state.users.currentUser))
-          clone.cover_photo = data.url
-          this.$store.commit('addNewUsers', [clone])
-          this.$store.commit('setCurrentUser', clone)
+      this.$store.state.api.backendInteractor.updateBanner({ banner: this.banner })
+        .then((user) => {
+          this.$store.commit('addNewUsers', [user])
+          this.$store.commit('setCurrentUser', user)
           this.bannerPreview = null
-        } else {
-          this.bannerUploadError = this.$t('upload.error.base') + data.error
-        }
-        this.bannerUploading = false
-      })
-      /* eslint-enable camelcase */
+        })
+        .catch((err) => {
+          this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message
+        })
+        .then(() => { this.bannerUploading = false })
     },
     submitBg () {
       if (!this.backgroundPreview) { return }
-      let img = this.backgroundPreview
-      // eslint-disable-next-line no-undef
-      let imginfo = new Image()
-      let cropX, cropY, cropW, cropH
-      imginfo.src = img
-      cropX = 0
-      cropY = 0
-      cropW = imginfo.width
-      cropH = imginfo.width
+      let background = this.background
       this.backgroundUploading = true
-      this.$store.state.api.backendInteractor.updateBg({params: {img, cropX, cropY, cropW, cropH}}).then((data) => {
+      this.$store.state.api.backendInteractor.updateBg({ background }).then((data) => {
         if (!data.error) {
-          let clone = JSON.parse(JSON.stringify(this.$store.state.users.currentUser))
-          clone.background_image = data.url
-          this.$store.commit('addNewUsers', [clone])
-          this.$store.commit('setCurrentUser', clone)
+          this.$store.commit('addNewUsers', [data])
+          this.$store.commit('setCurrentUser', data)
           this.backgroundPreview = null
         } else {
           this.backgroundUploadError = this.$t('upload.error.base') + data.error
@@ -234,72 +229,51 @@ const UserSettings = {
         this.backgroundUploading = false
       })
     },
-    importFollows () {
-      this.followListUploading = true
-      const followList = this.followList
-      this.$store.state.api.backendInteractor.followImport({params: followList})
+    importFollows (file) {
+      return this.$store.state.api.backendInteractor.importFollows(file)
         .then((status) => {
-          if (status) {
-            this.followsImported = true
-          } else {
-            this.followImportError = true
+          if (!status) {
+            throw new Error('failed')
           }
-          this.followListUploading = false
         })
     },
-    /* 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) {
+    importBlocks (file) {
+      return this.$store.state.api.backendInteractor.importBlocks(file)
+        .then((status) => {
+          if (!status) {
+            throw new Error('failed')
+          }
+        })
+    },
+    generateExportableUsersContent (users) {
+      // Get 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
-          user.screen_name += '@' + location.hostname
+          return 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
-        })
-        .then((friendList) => {
-          this.exportPeople(friendList, 'friends.csv')
-          setTimeout(() => { this.enableFollowsExport = true }, 2000)
-        })
+    getFollowsContent () {
+      return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
+        .then(this.generateExportableUsersContent)
     },
-    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
+    getBlocksContent () {
+      return this.$store.state.api.backendInteractor.fetchBlocks()
+        .then(this.generateExportableUsersContent)
     },
     confirmDelete () {
       this.deletingAccount = true
     },
     deleteAccount () {
-      this.$store.state.api.backendInteractor.deleteAccount({password: this.deleteAccountConfirmPasswordInput})
+      this.$store.state.api.backendInteractor.deleteAccount({ password: this.deleteAccountConfirmPasswordInput })
         .then((res) => {
           if (res.status === 'success') {
             this.$store.dispatch('logout')
-            this.$router.push({name: 'root'})
+            this.$router.push({ name: 'root' })
           } else {
             this.deleteAccountError = res.error
           }
@@ -334,6 +308,37 @@ 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
+      })
+    },
+    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 this.$store.dispatch('searchUsers', query)
+        .then((users) => map(users, 'id'))
+    },
+    blockUsers (ids) {
+      return this.$store.dispatch('blockUsers', ids)
+    },
+    unblockUsers (ids) {
+      return this.$store.dispatch('unblockUsers', ids)
+    },
+    muteUsers (ids) {
+      return this.$store.dispatch('muteUsers', ids)
+    },
+    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 c08698dc..34ea8569 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -2,15 +2,23 @@
   <div class="settings panel panel-default">
     <div class="panel-heading">
       <div class="title">
-        {{$t('settings.user_settings')}}
+        {{ $t('settings.user_settings') }}
       </div>
       <transition name="fade">
         <template v-if="currentSaveStateNotice">
-          <div @click.prevent class="alert error" v-if="currentSaveStateNotice.error">
+          <div
+            v-if="currentSaveStateNotice.error"
+            class="alert error"
+            @click.prevent
+          >
             {{ $t('settings.saving_err') }}
           </div>
 
-          <div @click.prevent class="alert transparent" v-if="!currentSaveStateNotice.error">
+          <div
+            v-if="!currentSaveStateNotice.error"
+            class="alert transparent"
+            @click.prevent
+          >
             {{ $t('settings.saving_ok') }}
           </div>
         </template>
@@ -19,191 +27,520 @@
     <div class="panel-body profile-edit">
       <tab-switcher>
         <div :label="$t('settings.profile_tab')">
-          <div class="setting-item" >
-            <h2>{{$t('settings.name_bio')}}</h2>
-            <p>{{$t('settings.name')}}</p>
-            <EmojiInput 
-              type="text"
-              v-model="newName"
-              id="username"
-              classname="name-changer"
-            />
-            <p>{{$t('settings.bio')}}</p>
+          <div class="setting-item">
+            <h2>{{ $t('settings.name_bio') }}</h2>
+            <p>{{ $t('settings.name') }}</p>
+            <EmojiInput
+              v-model="newName"
+              :suggest="emojiSuggestor"
+            >
+              <input
+                id="username"
+                v-model="newName"
+                classname="name-changer"
+              >
+            </EmojiInput>
+            <p>{{ $t('settings.bio') }}</p>
             <EmojiInput
-              type="textarea"
               v-model="newBio"
-              classname="bio"
-            />
+              :suggest="emojiUserSuggestor"
+            >
+              <textarea
+                v-model="newBio"
+                classname="bio"
+              />
+            </EmojiInput>
             <p>
-              <input type="checkbox" v-model="newLocked" id="account-locked">
-              <label for="account-locked">{{$t('settings.lock_account_description')}}</label>
+              <input
+                id="account-locked"
+                v-model="newLocked"
+                type="checkbox"
+              >
+              <label for="account-locked">{{ $t('settings.lock_account_description') }}</label>
             </p>
             <div>
-              <label for="default-vis">{{$t('settings.default_vis')}}</label>
-              <div id="default-vis" class="visibility-tray">
+              <label for="default-vis">{{ $t('settings.default_vis') }}</label>
+              <div
+                id="default-vis"
+                class="visibility-tray"
+              >
                 <scope-selector
-                  :showAll="true"
-                  :userDefault="newDefaultScope"
-                  :onScopeChange="changeVis"/>
+                  :show-all="true"
+                  :user-default="newDefaultScope"
+                  :initial-scope="newDefaultScope"
+                  :on-scope-change="changeVis"
+                />
               </div>
             </div>
             <p>
-              <input type="checkbox" v-model="newNoRichText" id="account-no-rich-text">
-              <label for="account-no-rich-text">{{$t('settings.no_rich_text_description')}}</label>
+              <input
+                id="account-no-rich-text"
+                v-model="newNoRichText"
+                type="checkbox"
+              >
+              <label for="account-no-rich-text">{{ $t('settings.no_rich_text_description') }}</label>
             </p>
             <p>
-              <input type="checkbox" v-model="hideFollows" id="account-hide-follows">
-              <label for="account-hide-follows">{{$t('settings.hide_follows_description')}}</label>
+              <input
+                id="account-hide-follows"
+                v-model="hideFollows"
+                type="checkbox"
+              >
+              <label for="account-hide-follows">{{ $t('settings.hide_follows_description') }}</label>
             </p>
             <p>
-              <input type="checkbox" v-model="hideFollowers" id="account-hide-followers">
-              <label for="account-hide-followers">{{$t('settings.hide_followers_description')}}</label>
+              <input
+                id="account-hide-followers"
+                v-model="hideFollowers"
+                type="checkbox"
+              >
+              <label for="account-hide-followers">{{ $t('settings.hide_followers_description') }}</label>
             </p>
             <p>
-              <input type="checkbox" v-model="showRole" id="account-show-role">
-              <label for="account-show-role" v-if="role === 'admin'">{{$t('settings.show_admin_badge')}}</label>
-              <label for="account-show-role" v-if="role === 'moderator'">{{$t('settings.show_moderator_badge')}}</label>
+              <input
+                id="account-show-role"
+                v-model="showRole"
+                type="checkbox"
+              >
+              <label
+                v-if="role === 'admin'"
+                for="account-show-role"
+              >{{ $t('settings.show_admin_badge') }}</label>
+              <label
+                v-if="role === 'moderator'"
+                for="account-show-role"
+              >{{ $t('settings.show_moderator_badge') }}</label>
             </p>
-            <button :disabled='newName && newName.length === 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button>
+            <button
+              :disabled="newName && newName.length === 0"
+              class="btn btn-default"
+              @click="updateProfile"
+            >
+              {{ $t('general.submit') }}
+            </button>
           </div>
           <div class="setting-item">
-            <h2>{{$t('settings.avatar')}}</h2>
-            <p class="visibility-notice">{{$t('settings.avatar_size_instruction')}}</p>
-            <p>{{$t('settings.current_avatar')}}</p>
-            <img :src="user.profile_image_url_original" class="current-avatar" />
-            <p>{{$t('settings.set_new_avatar')}}</p>
-            <button class="btn" type="button" id="pick-avatar" v-show="pickAvatarBtnVisible">{{$t('settings.upload_a_photo')}}</button>
-            <image-cropper trigger="#pick-avatar" :submitHandler="submitAvatar" @open="pickAvatarBtnVisible=false" @close="pickAvatarBtnVisible=true" />
+            <h2>{{ $t('settings.avatar') }}</h2>
+            <p class="visibility-notice">
+              {{ $t('settings.avatar_size_instruction') }}
+            </p>
+            <p>{{ $t('settings.current_avatar') }}</p>
+            <img
+              :src="user.profile_image_url_original"
+              class="current-avatar"
+            >
+            <p>{{ $t('settings.set_new_avatar') }}</p>
+            <button
+              v-show="pickAvatarBtnVisible"
+              id="pick-avatar"
+              class="btn"
+              type="button"
+            >
+              {{ $t('settings.upload_a_photo') }}
+            </button>
+            <image-cropper
+              trigger="#pick-avatar"
+              :submit-handler="submitAvatar"
+              @open="pickAvatarBtnVisible=false"
+              @close="pickAvatarBtnVisible=true"
+            />
           </div>
           <div class="setting-item">
-            <h2>{{$t('settings.profile_banner')}}</h2>
-            <p>{{$t('settings.current_profile_banner')}}</p>
-            <img :src="user.cover_photo" class="banner" />
-            <p>{{$t('settings.set_new_profile_banner')}}</p>
-            <img class="banner" v-bind:src="bannerPreview" v-if="bannerPreview" />
+            <h2>{{ $t('settings.profile_banner') }}</h2>
+            <p>{{ $t('settings.current_profile_banner') }}</p>
+            <img
+              :src="user.cover_photo"
+              class="banner"
+            >
+            <p>{{ $t('settings.set_new_profile_banner') }}</p>
+            <img
+              v-if="bannerPreview"
+              class="banner"
+              :src="bannerPreview"
+            >
             <div>
-              <input type="file" @change="uploadFile('banner', $event)" />
+              <input
+                type="file"
+                @change="uploadFile('banner', $event)"
+              >
             </div>
-            <i class=" icon-spin4 animate-spin uploading" v-if="bannerUploading"></i>
-            <button class="btn btn-default" v-else-if="bannerPreview" @click="submitBanner">{{$t('general.submit')}}</button>
-            <div class='alert error' v-if="bannerUploadError">
+            <i
+              v-if="bannerUploading"
+              class=" icon-spin4 animate-spin uploading"
+            />
+            <button
+              v-else-if="bannerPreview"
+              class="btn btn-default"
+              @click="submitBanner"
+            >
+              {{ $t('general.submit') }}
+            </button>
+            <div
+              v-if="bannerUploadError"
+              class="alert error"
+            >
               Error: {{ bannerUploadError }}
-              <i class="button-icon icon-cancel" @click="clearUploadError('banner')"></i>
+              <i
+                class="button-icon icon-cancel"
+                @click="clearUploadError('banner')"
+              />
             </div>
           </div>
           <div class="setting-item">
-            <h2>{{$t('settings.profile_background')}}</h2>
-            <p>{{$t('settings.set_new_profile_background')}}</p>
-            <img class="bg" v-bind:src="backgroundPreview" v-if="backgroundPreview" />
+            <h2>{{ $t('settings.profile_background') }}</h2>
+            <p>{{ $t('settings.set_new_profile_background') }}</p>
+            <img
+              v-if="backgroundPreview"
+              class="bg"
+              :src="backgroundPreview"
+            >
             <div>
-              <input type="file" @change="uploadFile('background', $event)" />
+              <input
+                type="file"
+                @change="uploadFile('background', $event)"
+              >
             </div>
-            <i class=" icon-spin4 animate-spin uploading" v-if="backgroundUploading"></i>
-            <button class="btn btn-default" v-else-if="backgroundPreview" @click="submitBg">{{$t('general.submit')}}</button>
-            <div class='alert error' v-if="backgroundUploadError">
+            <i
+              v-if="backgroundUploading"
+              class=" icon-spin4 animate-spin uploading"
+            />
+            <button
+              v-else-if="backgroundPreview"
+              class="btn btn-default"
+              @click="submitBg"
+            >
+              {{ $t('general.submit') }}
+            </button>
+            <div
+              v-if="backgroundUploadError"
+              class="alert error"
+            >
               Error: {{ backgroundUploadError }}
-              <i class="button-icon icon-cancel" @click="clearUploadError('background')"></i>
+              <i
+                class="button-icon icon-cancel"
+                @click="clearUploadError('background')"
+              />
             </div>
           </div>
         </div>
 
         <div :label="$t('settings.security_tab')">
           <div class="setting-item">
-            <h2>{{$t('settings.change_password')}}</h2>
+            <h2>{{ $t('settings.change_password') }}</h2>
             <div>
-              <p>{{$t('settings.current_password')}}</p>
-              <input type="password" v-model="changePasswordInputs[0]">
+              <p>{{ $t('settings.current_password') }}</p>
+              <input
+                v-model="changePasswordInputs[0]"
+                type="password"
+              >
             </div>
             <div>
-              <p>{{$t('settings.new_password')}}</p>
-              <input type="password" v-model="changePasswordInputs[1]">
+              <p>{{ $t('settings.new_password') }}</p>
+              <input
+                v-model="changePasswordInputs[1]"
+                type="password"
+              >
             </div>
             <div>
-              <p>{{$t('settings.confirm_new_password')}}</p>
-              <input type="password" v-model="changePasswordInputs[2]">
+              <p>{{ $t('settings.confirm_new_password') }}</p>
+              <input
+                v-model="changePasswordInputs[2]"
+                type="password"
+              >
             </div>
-            <button class="btn btn-default" @click="changePassword">{{$t('general.submit')}}</button>
-            <p v-if="changedPassword">{{$t('settings.changed_password')}}</p>
-            <p v-else-if="changePasswordError !== false">{{$t('settings.change_password_error')}}</p>
-            <p v-if="changePasswordError">{{changePasswordError}}</p>
+            <button
+              class="btn btn-default"
+              @click="changePassword"
+            >
+              {{ $t('general.submit') }}
+            </button>
+            <p v-if="changedPassword">
+              {{ $t('settings.changed_password') }}
+            </p>
+            <p v-else-if="changePasswordError !== false">
+              {{ $t('settings.change_password_error') }}
+            </p>
+            <p v-if="changePasswordError">
+              {{ changePasswordError }}
+            </p>
           </div>
 
           <div class="setting-item">
-            <h2>{{$t('settings.oauth_tokens')}}</h2>
+            <h2>{{ $t('settings.oauth_tokens') }}</h2>
             <table class="oauth-tokens">
               <thead>
                 <tr>
-                  <th>{{$t('settings.app_name')}}</th>
-                  <th>{{$t('settings.valid_until')}}</th>
-                  <th></th>
+                  <th>{{ $t('settings.app_name') }}</th>
+                  <th>{{ $t('settings.valid_until') }}</th>
+                  <th />
                 </tr>
               </thead>
               <tbody>
-                <tr v-for="oauthToken in oauthTokens" :key="oauthToken.id">
-                  <td>{{oauthToken.appName}}</td>
-                  <td>{{oauthToken.validUntil}}</td>
+                <tr
+                  v-for="oauthToken in oauthTokens"
+                  :key="oauthToken.id"
+                >
+                  <td>{{ oauthToken.appName }}</td>
+                  <td>{{ oauthToken.validUntil }}</td>
                   <td class="actions">
-                    <button class="btn btn-default" @click="revokeToken(oauthToken.id)">
-                      {{$t('settings.revoke_token')}}
+                    <button
+                      class="btn btn-default"
+                      @click="revokeToken(oauthToken.id)"
+                    >
+                      {{ $t('settings.revoke_token') }}
                     </button>
                   </td>
                 </tr>
               </tbody>
             </table>
           </div>
-
+          <mfa />
           <div class="setting-item">
-            <h2>{{$t('settings.delete_account')}}</h2>
-            <p v-if="!deletingAccount">{{$t('settings.delete_account_description')}}</p>
+            <h2>{{ $t('settings.delete_account') }}</h2>
+            <p v-if="!deletingAccount">
+              {{ $t('settings.delete_account_description') }}
+            </p>
             <div v-if="deletingAccount">
-              <p>{{$t('settings.delete_account_instructions')}}</p>
-              <p>{{$t('login.password')}}</p>
-              <input type="password" v-model="deleteAccountConfirmPasswordInput">
-              <button class="btn btn-default" @click="deleteAccount">{{$t('settings.delete_account')}}</button>
+              <p>{{ $t('settings.delete_account_instructions') }}</p>
+              <p>{{ $t('login.password') }}</p>
+              <input
+                v-model="deleteAccountConfirmPasswordInput"
+                type="password"
+              >
+              <button
+                class="btn btn-default"
+                @click="deleteAccount"
+              >
+                {{ $t('settings.delete_account') }}
+              </button>
             </div>
-            <p v-if="deleteAccountError !== false">{{$t('settings.delete_account_error')}}</p>
-            <p v-if="deleteAccountError">{{deleteAccountError}}</p>
-            <button class="btn btn-default" v-if="!deletingAccount" @click="confirmDelete">{{$t('general.submit')}}</button>
+            <p v-if="deleteAccountError !== false">
+              {{ $t('settings.delete_account_error') }}
+            </p>
+            <p v-if="deleteAccountError">
+              {{ deleteAccountError }}
+            </p>
+            <button
+              v-if="!deletingAccount"
+              class="btn btn-default"
+              @click="confirmDelete"
+            >
+              {{ $t('general.submit') }}
+            </button>
           </div>
         </div>
 
-        <div :label="$t('settings.data_import_export_tab')" v-if="pleromaBackend">
+        <div
+          v-if="pleromaBackend"
+          :label="$t('settings.notifications')"
+        >
           <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 class="select-multiple">
+              <span class="label">{{ $t('settings.notification_setting') }}</span>
+              <ul class="option-list">
+                <li>
+                  <input
+                    id="notification-setting-follows"
+                    v-model="notificationSettings.follows"
+                    type="checkbox"
+                  >
+                  <label for="notification-setting-follows">
+                    {{ $t('settings.notification_setting_follows') }}
+                  </label>
+                </li>
+                <li>
+                  <input
+                    id="notification-setting-followers"
+                    v-model="notificationSettings.followers"
+                    type="checkbox"
+                  >
+                  <label for="notification-setting-followers">
+                    {{ $t('settings.notification_setting_followers') }}
+                  </label>
+                </li>
+                <li>
+                  <input
+                    id="notification-setting-non-follows"
+                    v-model="notificationSettings.non_follows"
+                    type="checkbox"
+                  >
+                  <label for="notification-setting-non-follows">
+                    {{ $t('settings.notification_setting_non_follows') }}
+                  </label>
+                </li>
+                <li>
+                  <input
+                    id="notification-setting-non-followers"
+                    v-model="notificationSettings.non_followers"
+                    type="checkbox"
+                  >
+                  <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 class="setting-item" v-if="enableFollowsExport">
-            <h2>{{$t('settings.follow_export')}}</h2>
-            <button class="btn btn-default" @click="exportFollows">{{$t('settings.follow_export_button')}}</button>
+        </div>
+
+        <div
+          v-if="pleromaBackend"
+          :label="$t('settings.data_import_export_tab')"
+        >
+          <div class="setting-item">
+            <h2>{{ $t('settings.follow_import') }}</h2>
+            <p>{{ $t('settings.import_followers_from_a_csv_file') }}</p>
+            <Importer
+              :submit-handler="importFollows"
+              :success-message="$t('settings.follows_imported')"
+              :error-message="$t('settings.follow_import_error')"
+            />
           </div>
-          <div class="setting-item" v-else>
-            <h2>{{$t('settings.follow_export_processing')}}</h2>
+          <div class="setting-item">
+            <h2>{{ $t('settings.follow_export') }}</h2>
+            <Exporter
+              :get-content="getFollowsContent"
+              filename="friends.csv"
+              :export-button-label="$t('settings.follow_export_button')"
+            />
+          </div>
+          <div class="setting-item">
+            <h2>{{ $t('settings.block_import') }}</h2>
+            <p>{{ $t('settings.import_blocks_from_a_csv_file') }}</p>
+            <Importer
+              :submit-handler="importBlocks"
+              :success-message="$t('settings.blocks_imported')"
+              :error-message="$t('settings.block_import_error')"
+            />
+          </div>
+          <div class="setting-item">
+            <h2>{{ $t('settings.block_export') }}</h2>
+            <Exporter
+              :get-content="getBlocksContent"
+              filename="blocks.csv"
+              :export-button-label="$t('settings.block_export_button')"
+            />
           </div>
         </div>
 
         <div :label="$t('settings.blocks_tab')">
-          <block-list :refresh="true">
-            <template slot="empty">{{$t('settings.no_blocks')}}</template>
-          </block-list>
+          <div class="profile-edit-usersearch-wrapper">
+            <Autosuggest
+              :filter="filterUnblockedUsers"
+              :query="queryUserIds"
+              :placeholder="$t('settings.search_user_to_block')"
+            >
+              <BlockCard
+                slot-scope="row"
+                :user-id="row.item"
+              />
+            </Autosuggest>
+          </div>
+          <BlockList
+            :refresh="true"
+            :get-key="identity"
+          >
+            <template
+              slot="header"
+              slot-scope="{selected}"
+            >
+              <div class="profile-edit-bulk-actions">
+                <ProgressButton
+                  v-if="selected.length > 0"
+                  class="btn btn-default"
+                  :click="() => blockUsers(selected)"
+                >
+                  {{ $t('user_card.block') }}
+                  <template slot="progress">
+                    {{ $t('user_card.block_progress') }}
+                  </template>
+                </ProgressButton>
+                <ProgressButton
+                  v-if="selected.length > 0"
+                  class="btn btn-default"
+                  :click="() => unblockUsers(selected)"
+                >
+                  {{ $t('user_card.unblock') }}
+                  <template slot="progress">
+                    {{ $t('user_card.unblock_progress') }}
+                  </template>
+                </ProgressButton>
+              </div>
+            </template>
+            <template
+              slot="item"
+              slot-scope="{item}"
+            >
+              <BlockCard :user-id="item" />
+            </template>
+            <template slot="empty">
+              {{ $t('settings.no_blocks') }}
+            </template>
+          </BlockList>
         </div>
 
         <div :label="$t('settings.mutes_tab')">
-          <mute-list :refresh="true">
-            <template slot="empty">{{$t('settings.no_mutes')}}</template>
-          </mute-list>
+          <div class="profile-edit-usersearch-wrapper">
+            <Autosuggest
+              :filter="filterUnMutedUsers"
+              :query="queryUserIds"
+              :placeholder="$t('settings.search_user_to_mute')"
+            >
+              <MuteCard
+                slot-scope="row"
+                :user-id="row.item"
+              />
+            </Autosuggest>
+          </div>
+          <MuteList
+            :refresh="true"
+            :get-key="identity"
+          >
+            <template
+              slot="header"
+              slot-scope="{selected}"
+            >
+              <div class="profile-edit-bulk-actions">
+                <ProgressButton
+                  v-if="selected.length > 0"
+                  class="btn btn-default"
+                  :click="() => muteUsers(selected)"
+                >
+                  {{ $t('user_card.mute') }}
+                  <template slot="progress">
+                    {{ $t('user_card.mute_progress') }}
+                  </template>
+                </ProgressButton>
+                <ProgressButton
+                  v-if="selected.length > 0"
+                  class="btn btn-default"
+                  :click="() => unmuteUsers(selected)"
+                >
+                  {{ $t('user_card.unmute') }}
+                  <template slot="progress">
+                    {{ $t('user_card.unmute_progress') }}
+                  </template>
+                </ProgressButton>
+              </div>
+            </template>
+            <template
+              slot="item"
+              slot-scope="{item}"
+            >
+              <MuteCard :user-id="item" />
+            </template>
+            <template slot="empty">
+              {{ $t('settings.no_mutes') }}
+            </template>
+          </MuteList>
         </div>
       </tab-switcher>
     </div>
@@ -221,6 +558,10 @@
     margin: 0;
   }
 
+  .visibility-tray {
+    padding-top: 5px;
+  }
+
   input[type=file] {
     padding: 5px;
     height: auto;
@@ -262,5 +603,19 @@
       text-align: right;
     }
   }
+
+  &-usersearch-wrapper {
+    padding: 1em;
+  }
+
+  &-bulk-actions {
+    text-align: right;
+    padding: 0 1em;
+    min-height: 28px;
+
+    button {
+      width: 10em;
+    }
+  }
 }
 </style>
diff --git a/src/components/video_attachment/video_attachment.vue b/src/components/video_attachment/video_attachment.vue
index 68de201e..97ddf1cd 100644
--- a/src/components/video_attachment/video_attachment.vue
+++ b/src/components/video_attachment/video_attachment.vue
@@ -1,10 +1,11 @@
 <template>
-  <video class="video"
-    @loadeddata="onVideoDataLoad"
+  <video
+    class="video"
     :src="attachment.url"
     :loop="loopVideo"
     :controls="controls"
     playsinline
+    @loadeddata="onVideoDataLoad"
   />
 </template>
 
diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js
index be0b8827..f8100257 100644
--- a/src/components/who_to_follow/who_to_follow.js
+++ b/src/components/who_to_follow/who_to_follow.js
@@ -20,7 +20,8 @@ const WhoToFollow = {
           id: 0,
           name: i.display_name,
           screen_name: i.acct,
-          profile_image_url: i.avatar || '/images/avi.png'
+          profile_image_url: i.avatar || '/images/avi.png',
+          profile_image_url_original: i.avatar || '/images/avi.png'
         }
         this.users.push(user)
 
@@ -36,7 +37,7 @@ const WhoToFollow = {
     getWhoToFollow () {
       const credentials = this.$store.state.users.currentUser.credentials
       if (credentials) {
-        apiService.suggestions({credentials: credentials})
+        apiService.suggestions({ credentials: credentials })
           .then((reply) => {
             this.showWhoToFollow(reply)
           })
diff --git a/src/components/who_to_follow/who_to_follow.vue b/src/components/who_to_follow/who_to_follow.vue
index 1630f5ac..3a17d0e2 100644
--- a/src/components/who_to_follow/who_to_follow.vue
+++ b/src/components/who_to_follow/who_to_follow.vue
@@ -1,10 +1,15 @@
 <template>
   <div class="panel panel-default">
     <div class="panel-heading">
-      {{$t('who_to_follow.who_to_follow')}}
+      {{ $t('who_to_follow.who_to_follow') }}
     </div>
     <div class="panel-body">
-      <FollowCard v-for="user in users" :key="user.id" :user="user"/>
+      <FollowCard
+        v-for="user in users"
+        :key="user.id"
+        :user="user"
+        class="list-item"
+      />
     </div>
   </div>
 </template>
diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js
index a56a27ea..7d01678b 100644
--- a/src/components/who_to_follow_panel/who_to_follow_panel.js
+++ b/src/components/who_to_follow_panel/who_to_follow_panel.js
@@ -29,7 +29,7 @@ function getWhoToFollow (panel) {
     panel.usersToFollow.forEach(toFollow => {
       toFollow.name = 'Loading...'
     })
-    apiService.suggestions({credentials: credentials})
+    apiService.suggestions({ credentials: credentials })
       .then((reply) => {
         showWhoToFollow(panel, reply)
       })
diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.vue b/src/components/who_to_follow_panel/who_to_follow_panel.vue
index 25e3a9f6..518acd97 100644
--- a/src/components/who_to_follow_panel/who_to_follow_panel.vue
+++ b/src/components/who_to_follow_panel/who_to_follow_panel.vue
@@ -3,17 +3,25 @@
     <div class="panel panel-default base01-background">
       <div class="panel-heading timeline-heading base02-background base04">
         <div class="title">
-          {{$t('who_to_follow.who_to_follow')}}
+          {{ $t('who_to_follow.who_to_follow') }}
         </div>
       </div>
-      <div class="panel-body who-to-follow">
-        <span v-for="user in usersToFollow">
-          <img v-bind:src="user.img" />
-            <router-link v-bind:to="userProfileLink(user.id, user.name)">
-              {{user.name}}
-            </router-link><br />
-        </span>
-        <img v-bind:src="$store.state.instance.logo"> <router-link :to="{ name: 'who-to-follow' }">{{$t('who_to_follow.more')}}</router-link>
+      <div class="who-to-follow">
+        <p
+          v-for="user in usersToFollow"
+          :key="user.id"
+          class="who-to-follow-items"
+        >
+          <img :src="user.img">
+          <router-link :to="userProfileLink(user.id, user.name)">
+            {{ user.name }}
+          </router-link><br>
+        </p>
+        <p class="who-to-follow-more">
+          <router-link :to="{ name: 'who-to-follow' }">
+            {{ $t('who_to_follow.more') }}
+          </router-link>
+        </p>
       </div>
     </div>
   </div>
@@ -30,11 +38,19 @@
     height: 32px;
   }
   .who-to-follow {
-    padding: 0.5em 1em 0.5em 1em;
+    padding: 0em 1em;
     margin: 0px;
-    line-height: 40px;
+  }
+  .who-to-follow-items {
     white-space: nowrap;
     overflow: hidden;
     text-overflow: ellipsis;
+    padding: 0px;
+    margin: 1em 0em;
+  }
+  .who-to-follow-more {
+    padding: 0px;
+    margin: 1em 0em;
+    text-align: center;
   }
 </style>
diff --git a/src/hocs/with_list/with_list.js b/src/hocs/with_list/with_list.js
deleted file mode 100644
index 896f8fc8..00000000
--- a/src/hocs/with_list/with_list.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import Vue from 'vue'
-import map from 'lodash/map'
-import isEmpty from 'lodash/isEmpty'
-import './with_list.scss'
-
-const defaultEntryPropsGetter = entry => ({ entry })
-const defaultKeyGetter = entry => entry.id
-
-const withList = ({
-  getEntryProps = defaultEntryPropsGetter,  // function to accept entry and index values and return props to be passed into the item component
-  getKey = defaultKeyGetter                 // funciton to accept entry and index values and return key prop value
-}) => (ItemComponent) => (
-  Vue.component('withList', {
-    props: [
-      'entries',                            // array of entry
-      'entryProps',                         // additional props to be passed into each entry
-      'entryListeners'                      // additional event listeners to be passed into each entry
-    ],
-    render (createElement) {
-      return (
-        <div class="with-list">
-          {map(this.entries, (entry, index) => {
-            const props = {
-              key: getKey(entry, index),
-              props: {
-                ...this.$props.entryProps,
-                ...getEntryProps(entry, index)
-              },
-              on: this.$props.entryListeners
-            }
-            return <ItemComponent {...props} />
-          })}
-          {isEmpty(this.entries) && this.$slots.empty && <div class="with-list-empty-content faint">{this.$slots.empty}</div>}
-        </div>
-      )
-    }
-  })
-)
-
-export default withList
diff --git a/src/hocs/with_list/with_list.scss b/src/hocs/with_list/with_list.scss
deleted file mode 100644
index c6e13d5b..00000000
--- a/src/hocs/with_list/with_list.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.with-list {
-  &-empty-content {
-    text-align: center;
-    padding: 10px;
-  }
-}
\ No newline at end of file
diff --git a/src/hocs/with_load_more/with_load_more.js b/src/hocs/with_load_more/with_load_more.js
index 74979b87..1e1b2a74 100644
--- a/src/hocs/with_load_more/with_load_more.js
+++ b/src/hocs/with_load_more/with_load_more.js
@@ -4,39 +4,16 @@ import { getComponentProps } from '../../services/component_utils/component_util
 import './with_load_more.scss'
 
 const withLoadMore = ({
-  fetch,                      // function to fetch entries and return a promise
-  select,                     // function to select data from store
-  destroy,                    // function called at "destroyed" lifecycle
-  childPropName = 'entries',  // name of the prop to be passed into the wrapped component
-  additionalPropNames = []    // additional prop name list of the wrapper component
+  fetch, // function to fetch entries and return a promise
+  select, // function to select data from store
+  destroy, // function called at "destroyed" lifecycle
+  childPropName = 'entries', // name of the prop to be passed into the wrapped component
+  additionalPropNames = [] // additional prop name list of the wrapper component
 }) => (WrappedComponent) => {
   const originalProps = Object.keys(getComponentProps(WrappedComponent))
   const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames)
 
   return Vue.component('withLoadMore', {
-    render (createElement) {
-      const props = {
-        props: {
-          ...this.$props,
-          [childPropName]: this.entries
-        },
-        on: this.$listeners,
-        scopedSlots: this.$scopedSlots
-      }
-      const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
-      return (
-        <div class="with-load-more">
-          <WrappedComponent {...props}>
-            {children}
-          </WrappedComponent>
-          <div class="with-load-more-footer">
-            {this.error && <a onClick={this.fetchEntries} class="alert error">{this.$t('general.generic_error')}</a>}
-            {!this.error && this.loading && <i class="icon-spin3 animate-spin"/>}
-            {!this.error && !this.loading && !this.bottomedOut && <a onClick={this.fetchEntries}>{this.$t('general.more')}</a>}
-          </div>
-        </div>
-      )
-    },
     props,
     data () {
       return {
@@ -87,6 +64,29 @@ const withLoadMore = ({
           this.fetchEntries()
         }
       }
+    },
+    render (createElement) {
+      const props = {
+        props: {
+          ...this.$props,
+          [childPropName]: this.entries
+        },
+        on: this.$listeners,
+        scopedSlots: this.$scopedSlots
+      }
+      const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
+      return (
+        <div class="with-load-more">
+          <WrappedComponent {...props}>
+            {children}
+          </WrappedComponent>
+          <div class="with-load-more-footer">
+            {this.error && <a onClick={this.fetchEntries} class="alert error">{this.$t('general.generic_error')}</a>}
+            {!this.error && this.loading && <i class="icon-spin3 animate-spin"/>}
+            {!this.error && !this.loading && !this.bottomedOut && <a onClick={this.fetchEntries}>{this.$t('general.more')}</a>}
+          </div>
+        </div>
+      )
     }
   })
 }
diff --git a/src/hocs/with_load_more/with_load_more.scss b/src/hocs/with_load_more/with_load_more.scss
index 1a0a9c40..4cefe2be 100644
--- a/src/hocs/with_load_more/with_load_more.scss
+++ b/src/hocs/with_load_more/with_load_more.scss
@@ -1,10 +1,16 @@
+
+@import '../../_variables.scss';
+
 .with-load-more {
   &-footer {
     padding: 10px;
     text-align: center;
+    border-top: 1px solid;
+    border-top-color: $fallback--border;
+    border-top-color: var(--border, $fallback--border);
 
     .error {
       font-size: 14px;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/hocs/with_subscription/with_subscription.js b/src/hocs/with_subscription/with_subscription.js
index 679409cf..91fc4cca 100644
--- a/src/hocs/with_subscription/with_subscription.js
+++ b/src/hocs/with_subscription/with_subscription.js
@@ -4,10 +4,10 @@ import { getComponentProps } from '../../services/component_utils/component_util
 import './with_subscription.scss'
 
 const withSubscription = ({
-  fetch,                      // function to fetch entries and return a promise
-  select,                     // function to select data from store
-  childPropName = 'content',  // name of the prop to be passed into the wrapped component
-  additionalPropNames = []    // additional prop name list of the wrapper component
+  fetch, // function to fetch entries and return a promise
+  select, // function to select data from store
+  childPropName = 'content', // name of the prop to be passed into the wrapped component
+  additionalPropNames = [] // additional prop name list of the wrapper component
 }) => (WrappedComponent) => {
   const originalProps = Object.keys(getComponentProps(WrappedComponent))
   const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames)
@@ -15,37 +15,8 @@ const withSubscription = ({
   return Vue.component('withSubscription', {
     props: [
       ...props,
-      'refresh'               // boolean saying to force-fetch data whenever created
+      'refresh' // boolean saying to force-fetch data whenever created
     ],
-    render (createElement) {
-      if (!this.error && !this.loading) {
-        const props = {
-          props: {
-            ...this.$props,
-            [childPropName]: this.fetchedData
-          },
-          on: this.$listeners,
-          scopedSlots: this.$scopedSlots
-        }
-        const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
-        return (
-          <div class="with-subscription">
-            <WrappedComponent {...props}>
-              {children}
-            </WrappedComponent>
-          </div>
-        )
-      } else {
-        return (
-          <div class="with-subscription-loading">
-            {this.error
-              ? <a onClick={this.fetchData} class="alert error">{this.$t('general.generic_error')}</a>
-              : <i class="icon-spin3 animate-spin"/>
-            }
-          </div>
-        )
-      }
-    },
     data () {
       return {
         loading: false,
@@ -77,6 +48,35 @@ const withSubscription = ({
             })
         }
       }
+    },
+    render (createElement) {
+      if (!this.error && !this.loading) {
+        const props = {
+          props: {
+            ...this.$props,
+            [childPropName]: this.fetchedData
+          },
+          on: this.$listeners,
+          scopedSlots: this.$scopedSlots
+        }
+        const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
+        return (
+          <div class="with-subscription">
+            <WrappedComponent {...props}>
+              {children}
+            </WrappedComponent>
+          </div>
+        )
+      } else {
+        return (
+          <div class="with-subscription-loading">
+            {this.error
+              ? <a onClick={this.fetchData} class="alert error">{this.$t('general.generic_error')}</a>
+              : <i class="icon-spin3 animate-spin"/>
+            }
+          </div>
+        )
+      }
     }
   })
 }
diff --git a/src/i18n/ca.json b/src/i18n/ca.json
index 8fa3a88b..42d7745c 100644
--- a/src/i18n/ca.json
+++ b/src/i18n/ca.json
@@ -168,6 +168,40 @@
       "true": "sí"
     }
   },
+  "time": {
+    "day": "{0} dia",
+    "days": "{0} dies",
+    "day_short": "{0} dia",
+    "days_short": "{0} dies",
+    "hour": "{0} hour",
+    "hours": "{0} hours",
+    "hour_short": "{0}h",
+    "hours_short": "{0}h",
+    "in_future": "in {0}",
+    "in_past": "fa {0}",
+    "minute": "{0} minute",
+    "minutes": "{0} minutes",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} mes",
+    "months": "{0} mesos",
+    "month_short": "{0} mes",
+    "months_short": "{0} mesos",
+    "now": "ara mateix",
+    "now_short": "ara mateix",
+    "second": "{0} second",
+    "seconds": "{0} seconds",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} setm.",
+    "weeks": "{0} setm.",
+    "week_short": "{0} setm.",
+    "weeks_short": "{0} setm.",
+    "year": "{0} any",
+    "years": "{0} anys",
+    "year_short": "{0} any",
+    "years_short": "{0} anys"
+  },
   "timeline": {
     "collapse": "Replega",
     "conversation": "Conversa",
diff --git a/src/i18n/compare.js b/src/i18n/compare
similarity index 99%
rename from src/i18n/compare.js
rename to src/i18n/compare
index e9314376..4dc1e47d 100755
--- a/src/i18n/compare.js
+++ b/src/i18n/compare
@@ -19,7 +19,7 @@ if (typeof arg === 'undefined') {
   console.log('')
   console.log('There are no other arguments or options. Make an issue if you encounter a bug or want')
   console.log('some feature to be implemented. Merge requests are welcome as well.')
-  return
+  process.exit()
 }
 
 const english = require('./en.json')
diff --git a/src/i18n/cs.json b/src/i18n/cs.json
index 020092a6..42e75567 100644
--- a/src/i18n/cs.json
+++ b/src/i18n/cs.json
@@ -73,7 +73,8 @@
     "content_type": {
       "text/plain": "Prostý text",
       "text/html": "HTML",
-      "text/markdown": "Markdown"
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
     },
     "content_warning": "Předmět (volitelný)",
     "default": "Právě jsem přistál v L.A.",
@@ -349,6 +350,40 @@
       }
     }
   },
+  "time": {
+    "day": "{0} day",
+    "days": "{0} days",
+    "day_short": "{0}d",
+    "days_short": "{0}d",
+    "hour": "{0} hour",
+    "hours": "{0} hours",
+    "hour_short": "{0}h",
+    "hours_short": "{0}h",
+    "in_future": "in {0}",
+    "in_past": "{0} ago",
+    "minute": "{0} minute",
+    "minutes": "{0} minutes",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} měs",
+    "months": "{0} měs",
+    "month_short": "{0} měs",
+    "months_short": "{0} měs",
+    "now": "teď",
+    "now_short": "teď",
+    "second": "{0} second",
+    "seconds": "{0} seconds",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} týd",
+    "weeks": "{0} týd",
+    "week_short": "{0} týd",
+    "weeks_short": "{0} týd",
+    "year": "{0} r",
+    "years": "{0} l",
+    "year_short": "{0}r",
+    "years_short": "{0}l"
+  },
   "timeline": {
     "collapse": "Zabalit",
     "conversation": "Konverzace",
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 1e82cd0a..60a3e284 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",
@@ -22,7 +26,12 @@
     "generic_error": "An error occured",
     "optional": "optional",
     "show_more": "Show more",
-    "show_less": "Show less"
+    "show_less": "Show less",
+    "cancel": "Cancel",
+    "disable": "Disable",
+    "enable": "Enable",
+    "confirm": "Confirm",
+    "verify": "Verify"
   },
   "image_cropper": {
     "crop_picture": "Crop picture",
@@ -30,6 +39,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",
@@ -38,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",
@@ -50,11 +72,13 @@
     "chat": "Local Chat",
     "friend_requests": "Follow Requests",
     "mentions": "Mentions",
+    "interactions": "Interactions",
     "dms": "Direct Messages",
     "public_tl": "Public Timeline",
     "timeline": "Timeline",
     "twkn": "The Whole Known Network",
     "user_search": "User Search",
+    "search": "Search",
     "who_to_follow": "Who to follow",
     "preferences": "Preferences"
   },
@@ -68,6 +92,28 @@
     "repeated_you": "repeated your status",
     "no_more_notifications": "No more notifications"
   },
+  "polls": {
+    "add_poll": "Add Poll",
+    "add_option": "Add Option",
+    "option": "Option",
+    "votes": "votes",
+    "vote": "Vote",
+    "type": "Poll type",
+    "single_choice": "Single choice",
+    "multiple_choices": "Multiple choices",
+    "expiry": "Poll age",
+    "expires_in": "Poll ends in {0}",
+    "expired": "Poll ended {0} ago",
+    "not_enough_options": "Too few unique options in poll"
+  },
+  "stickers": {
+    "add_sticker": "Add Sticker"
+  },
+  "interactions": {
+    "favs_repeats": "Repeats and Favorites",
+    "follows": "New follows",
+    "load_older": "Load older interactions"
+  },
   "post_status": {
     "new_status": "Post new status",
     "account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
@@ -76,13 +122,19 @@
     "content_type": {
       "text/plain": "Plain text",
       "text/html": "HTML",
-      "text/markdown": "Markdown"
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
     },
     "content_warning": "Subject (optional)",
     "default": "Just landed in L.A.",
     "direct_warning_to_all": "This post will be visible to all the mentioned users.",
     "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
     "posting": "Posting",
+    "scope_notice": {
+      "public": "This post will be visible to everyone",
+      "private": "This post will be visible to your followers only",
+      "unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
+    },
     "scope": {
       "direct": "Direct - Post to mentioned users only",
       "private": "Followers-only - Post to followers only",
@@ -111,8 +163,34 @@
       "password_confirmation_match": "should be the same as password"
     }
   },
+  "selectable_list": {
+    "select_all": "Select all"
+  },
   "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",
@@ -121,6 +199,11 @@
     "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.",
     "blocks_tab": "Blocks",
     "btnRadius": "Buttons",
     "cBlue": "Blue (Reply, follow)",
@@ -148,7 +231,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.",
@@ -164,6 +246,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",
@@ -214,8 +297,11 @@
     "reply_visibility_all": "Show all replies",
     "reply_visibility_following": "Only show replies directed at me or users I'm following",
     "reply_visibility_self": "Only show replies directed at me",
+    "autohide_floating_post_button": "Automatically hide New Post button (mobile)",
     "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",
@@ -244,6 +330,13 @@
       "true": "yes"
     },
     "notifications": "Notifications",
+    "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": {
@@ -360,6 +453,40 @@
       "frontend_version": "Frontend Version"
     }
   },
+  "time": {
+    "day": "{0} day",
+    "days": "{0} days",
+    "day_short": "{0}d",
+    "days_short": "{0}d",
+    "hour": "{0} hour",
+    "hours": "{0} hours",
+    "hour_short": "{0}h",
+    "hours_short": "{0}h",
+    "in_future": "in {0}",
+    "in_past": "{0} ago",
+    "minute": "{0} minute",
+    "minutes": "{0} minutes",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} month",
+    "months": "{0} months",
+    "month_short": "{0}mo",
+    "months_short": "{0}mo",
+    "now": "just now",
+    "now_short": "now",
+    "second": "{0} second",
+    "seconds": "{0} seconds",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} week",
+    "weeks": "{0} weeks",
+    "week_short": "{0}w",
+    "weeks_short": "{0}w",
+    "year": "{0} year",
+    "years": "{0} years",
+    "year_short": "{0}y",
+    "years_short": "{0}y"
+  },
   "timeline": {
     "collapse": "Collapse",
     "conversation": "Conversation",
@@ -373,6 +500,13 @@
     "no_statuses": "No statuses"
   },
   "status": {
+    "favorites": "Favorites",
+    "repeats": "Repeats",
+    "delete": "Delete status",
+    "pin": "Pin on profile",
+    "unpin": "Unpin from profile",
+    "pinned": "Pinned",
+    "delete_confirm": "Do you really want to delete this status?",
     "reply_to": "Reply to",
     "replies_list": "Replies:"
   },
@@ -397,19 +531,50 @@
     "muted": "Muted",
     "per_day": "per day",
     "remote_follow": "Remote follow",
+    "report": "Report",
     "statuses": "Statuses",
+    "subscribe": "Subscribe",
+    "unsubscribe": "Unsubscribe",
     "unblock": "Unblock",
     "unblock_progress": "Unblocking...",
     "block_progress": "Blocking...",
     "unmute": "Unmute",
     "unmute_progress": "Unmuting...",
-    "mute_progress": "Muting..."
+    "mute_progress": "Muting...",
+    "admin_menu": {
+      "moderation": "Moderation",
+      "grant_admin": "Grant Admin",
+      "revoke_admin": "Revoke Admin",
+      "grant_moderator": "Grant Moderator",
+      "revoke_moderator": "Revoke Moderator",
+      "activate_account": "Activate account",
+      "deactivate_account": "Deactivate account",
+      "delete_account": "Delete account",
+      "force_nsfw": "Mark all posts as NSFW",
+      "strip_media": "Remove media from posts",
+      "force_unlisted": "Force posts to be unlisted",
+      "sandbox": "Force posts to be followers-only",
+      "disable_remote_subscription": "Disallow following user from remote instances",
+      "disable_any_subscription": "Disallow following user at all",
+      "quarantine": "Disallow user posts from federating",
+      "delete_user": "Delete user",
+      "delete_user_confirmation": "Are you absolutely sure? This action cannot be undone."
+    }
   },
   "user_profile": {
     "timeline_title": "User Timeline",
     "profile_does_not_exist": "Sorry, this profile does not exist.",
     "profile_loading_error": "Sorry, there was an error loading this profile."
   },
+  "user_reporting": {
+    "title": "Reporting {0}",
+    "add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+    "additional_comments": "Additional comments",
+    "forward_description": "The account is from another server. Send a copy of the report there as well?",
+    "forward_to": "Forward to {0}",
+    "submit": "Submit",
+    "generic_error": "An error occurred while processing your request."
+  },
   "who_to_follow": {
     "more": "More",
     "who_to_follow": "Who to follow"
@@ -434,5 +599,12 @@
       "GiB": "GiB",
       "TiB": "TiB"
     }
+  },
+  "search": {
+    "people": "People",
+    "hashtags": "Hashtags",
+    "person_talking": "{count} person talking",
+    "people_talking": "{count} people talking",
+    "no_results": "No results"
   }
 }
diff --git a/src/i18n/es.json b/src/i18n/es.json
index a692eef9..bf87937a 100644
--- a/src/i18n/es.json
+++ b/src/i18n/es.json
@@ -2,6 +2,10 @@
   "chat": {
     "title": "Chat"
   },
+  "exporter": {
+    "export": "Exportar",
+    "processing": "Procesando. Pronto se te pedirá que descargues tu archivo"
+  },
   "features_panel": {
     "chat": "Chat",
     "gopher": "Gopher",
@@ -19,7 +23,26 @@
     "apply": "Aplicar",
     "submit": "Enviar",
     "more": "Más",
-    "generic_error": "Ha ocurrido un error"
+    "generic_error": "Ha ocurrido un error",
+    "optional": "opcional",
+    "show_more": "Mostrar más",
+    "show_less": "Mostrar menos",
+    "cancel": "Cancelar",
+    "disable": "Inhabilitar",
+    "enable": "Habilitar",
+    "confirm": "Confirmar",
+    "verify": "Verificar"
+  },
+  "image_cropper": {
+    "crop_picture": "Recortar la foto",
+    "save": "Guardar",
+    "save_without_cropping": "Guardar sin recortar",
+    "cancel": "Cancelar"
+  },
+  "importer": {
+    "submit": "Enviar",
+    "success": "Importado con éxito",
+    "error": "Se ha producido un error al importar el archivo."
   },
   "login": {
     "login": "Identificación",
@@ -29,7 +52,19 @@
     "placeholder": "p.ej. lain",
     "register": "Registrar",
     "username": "Usuario",
-    "hint": "Inicia sesión para unirte a la discusión"
+    "hint": "Inicia sesión para unirte a la discusión",
+    "authentication_code": "Código de autentificación",
+    "enter_recovery_code": "Inserta el código de recuperación",
+    "enter_two_factor_code": "Inserta el código de doble factor",
+    "recovery_code": "Código de recuperación",
+    "heading" : {
+      "totp" : "Autentificación de doble factor",
+      "recovery" : "Recuperación de doble factor"
+    }
+  },
+   "media_modal": {
+    "previous": "Anterior",
+    "next": "Siguiente"
   },
   "nav": {
     "about": "Sobre",
@@ -37,11 +72,13 @@
     "chat": "Chat Local",
     "friend_requests": "Solicitudes de amistad",
     "mentions": "Menciones",
+    "interactions": "Interacciones",
     "dms": "Mensajes Directo",
     "public_tl": "Línea Temporal Pública",
     "timeline": "Línea Temporal",
     "twkn": "Toda La Red Conocida",
     "user_search": "Búsqueda de Usuarios",
+    "search": "Buscar",
     "who_to_follow": "A quién seguir",
     "preferences": "Preferencias"
   },
@@ -55,21 +92,49 @@
     "repeated_you": "repite tu estado",
     "no_more_notifications": "No hay más notificaciones"
   },
+  "polls": {
+    "add_poll": "Añadir encuesta",
+    "add_option": "Añadir opción",
+    "option": "Opción",
+    "votes": "votos",
+    "vote": "Votar",
+    "type": "Tipo de encuesta",
+    "single_choice": "Elección única",
+    "multiple_choices": "Múltiples elecciones",
+    "expiry": "Tiempo de vida de la encuesta",
+    "expires_in": "La encuensta termina en {0}",
+    "expired": "La encuesta terminó hace {0}",
+    "not_enough_options": "Muy pocas opciones únicas en la encuesta"
+  },
+  "interactions": {
+    "favs_repeats": "Favoritos y Repetidos",
+    "follows": "Nuevos seguidores",
+    "load_older": "Cargar interacciones antiguas"
+  },
   "post_status": {
     "new_status": "Publicar un nuevo estado",
     "account_not_locked_warning": "Tu cuenta no está {0}. Cualquiera puede seguirte y leer las entradas para Solo-Seguidores.",
     "account_not_locked_warning_link": "bloqueada",
     "attachments_sensitive": "Contenido sensible",
     "content_type": {
-      "text/plain": "Texto Plano"
+      "text/plain": "Texto Plano",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
     },
     "content_warning": "Tema (opcional)",
     "default": "Acabo de aterrizar en L.A.",
-    "direct_warning": "Esta entrada solo será visible para los usuarios mencionados.",
+    "direct_warning_to_all": "Esta publicación será visible para todos los usarios mencionados.",
+    "direct_warning_to_first_only": "Esta publicación solo será visible para los usuarios mencionados al comienzo del mensaje.",
     "posting": "Publicando",
+    "scope_notice": {
+      "public": "Esta publicación será visible para todo el mundo",
+      "private": "Esta publicación solo será visible para tus seguidores.",
+      "unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida"
+    },
     "scope": {
       "direct": "Directo - Solo para los usuarios mencionados.",
-      "private": "Solo-Seguidores - Solo tus seguidores leeran la entrada",
+      "private": "Solo-Seguidores - Solo tus seguidores leeran la publicación",
       "public": "Público - Entradas visibles en las Líneas Temporales Públicas",
       "unlisted": "Sin Listar - Entradas no visibles en las Líneas Temporales Públicas"
     }
@@ -83,6 +148,9 @@
     "token": "Token de invitación",
     "captcha": "CAPTCHA",
     "new_captcha": "Click en la imagen para obtener un nuevo captca",
+    "username_placeholder": "p.ej. lain",
+    "fullname_placeholder": "p.ej. Lain Iwakura",
+    "bio_placeholder": "e.g.\nHola, soy un ejemplo.\nAquí puedes poner algo representativo tuyo... o no.",
     "validations": {
       "username_required": "no puede estar vacío",
       "fullname_required": "no puede estar vacío",
@@ -91,8 +159,35 @@
       "password_confirmation_required": "no puede estar vacío",
       "password_confirmation_match": "la contraseña no coincide"
     }
+  },
+   "selectable_list": {
+    "select_all": "Seleccionarlo todo"
   },
   "settings": {
+    "app_name": "Nombre de la aplicación",
+    "security": "Seguridad",
+    "enter_current_password_to_confirm": "Introduce la contraseña actual para confirmar tu identidad",
+    "mfa": {
+      "otp" : "OTP",
+      "setup_otp" : "Configurar OTP",
+      "wait_pre_setup_otp" : "preconfiguración OTP",
+      "confirm_and_enable" : "Confirmar y habilitar OTP",
+      "title": "Autentificación de Doble Factor",
+      "generate_new_recovery_codes" : "Generar nuevos códigos de recuperación",
+      "warning_of_generate_new_codes" : "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.",
+      "recovery_codes" : "Códigos de recuperación.",
+      "waiting_a_recovery_codes": "Recibiendo códigos de respaldo",
+      "recovery_codes_warning" : "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.",
+      "authentication_methods" : "Métodos de autentificación",
+      "scan": {
+        "title": "Escanear",
+        "desc": "Usando su aplicación de doble factor, escanee este código QR o ingrese la clave de texto:",
+        "secret_code": "Clave"
+      },
+      "verify": {
+        "desc": "Para habilitar la autenticación de doble factor, ingrese el código de su aplicación 2FA:"
+      }
+    },
     "attachmentRadius": "Adjuntos",
     "attachments": "Adjuntos",
     "autoload": "Activar carga automática al llegar al final de la página",
@@ -101,6 +196,12 @@
     "avatarRadius": "Avatares",
     "background": "Fondo",
     "bio": "Biografía",
+    "block_export": "Exportar usuarios bloqueados",
+    "block_export_button": "Exporta la lista de tus usarios bloqueados a un archivo csv",
+    "block_import": "Importar usuarios bloqueados",
+    "block_import_error": "Error importando la lista de usuarios bloqueados",
+    "blocks_imported": "¡Lista de usuarios bloqueados importada! El procesado puede tardar un poco.",
+    "blocks_tab": "Bloqueados",
     "btnRadius": "Botones",
     "cBlue": "Azul (Responder, seguir)",
     "cGreen": "Verde (Retweet)",
@@ -127,7 +228,6 @@
     "filtering_explanation": "Todos los estados que contengan estas palabras serán silenciados, una por línea",
     "follow_export": "Exportar personas que tú sigues",
     "follow_export_button": "Exporta tus seguidores a un archivo csv",
-    "follow_export_processing": "Procesando, en breve se te preguntará para guardar el archivo",
     "follow_import": "Importar personas que tú sigues",
     "follow_import_error": "Error al importal el archivo",
     "follows_imported": "¡Importado! Procesarlos llevará tiempo.",
@@ -135,12 +235,15 @@
     "general": "General",
     "hide_attachments_in_convo": "Ocultar adjuntos en las conversaciones",
     "hide_attachments_in_tl": "Ocultar adjuntos en la línea temporal",
+    "hide_muted_posts": "Ocultar las publicaciones de los usuarios silenciados",
+    "max_thumbnails": "Cantidad máxima de miniaturas por publicación",
     "hide_isp": "Ocultar el panel específico de la instancia",
     "preload_images": "Precargar las imágenes",
     "use_one_click_nsfw": "Abrir los adjuntos NSFW con un solo click.",
     "hide_post_stats": "Ocultar las estadísticas de las entradas (p.ej. el número de favoritos)",
     "hide_user_stats": "Ocultar las estadísticas del usuario (p.ej. el número de seguidores)",
     "hide_filtered_statuses": "Ocultar estados filtrados",
+    "import_blocks_from_a_csv_file": "Importar lista de usuarios bloqueados dese un archivo csv",
     "import_followers_from_a_csv_file": "Importar personas que tú sigues a partir de un archivo csv",
     "import_theme": "Importar tema",
     "inputRadius": "Campos de entrada",
@@ -155,6 +258,7 @@
     "lock_account_description": "Restringir el acceso a tu cuenta solo a seguidores admitidos",
     "loop_video": "Vídeos en bucle",
     "loop_video_silent_only": "Bucle solo en vídeos sin sonido (p.ej. \"gifs\" de Mastodon)",
+    "mutes_tab": "Silenciados",
     "play_videos_in_modal": "Reproducir los vídeos directamente en el visor de medios",
     "use_contain_fit": "No recortar los adjuntos en miniaturas",
     "name": "Nombre",
@@ -166,6 +270,8 @@
     "notification_visibility_mentions": "Menciones",
     "notification_visibility_repeats": "Repeticiones (Repeats)",
     "no_rich_text_description": "Eliminar el formato de texto enriquecido de todas las entradas",
+    "no_blocks": "No hay usuarios bloqueados",
+    "no_mutes": "No hay usuarios sinlenciados",
     "hide_follows_description": "No mostrar a quién sigo",
     "hide_followers_description": "No mostrar quién me sigue",
     "show_admin_badge": "Mostrar la placa de administrador en mi perfil",
@@ -188,10 +294,14 @@
     "reply_visibility_all": "Mostrar todas las réplicas",
     "reply_visibility_following": "Solo mostrar réplicas para mí o usuarios a los que sigo",
     "reply_visibility_self": "Solo mostrar réplicas para mí",
+    "autohide_floating_post_button": "Ocultar automáticamente el botón 'Nueva Publicación' (móvil)",
     "saving_err": "Error al guardar los ajustes",
     "saving_ok": "Ajustes guardados",
+    "search_user_to_block": "Buscar usuarios a bloquear",
+    "search_user_to_mute": "Buscar usuarios a silenciar",
     "security_tab": "Seguridad",
-    "scope_copy": "Copiar la visibilidad cuando contestamos (En los mensajes directos (MDs) siempre se copia)",
+    "scope_copy": "Copiar la visibilidad de la publicación cuando contestamos (En los mensajes directos (MDs) siempre se copia)",
+    "minimal_scopes_mode": "Minimizar las opciones de publicación",
     "set_new_avatar": "Cambiar avatar",
     "set_new_profile_background": "Cambiar fondo del perfil",
     "set_new_profile_banner": "Cambiar cabecera del perfil",
@@ -210,12 +320,20 @@
     "theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación, use el botón \"Borrar todo\" para deshacer los cambios.",
     "theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón para obtener información detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
     "tooltipRadius": "Información/alertas",
+     "upload_a_photo": "Subir una foto",
     "user_settings": "Ajustes de Usuario",
     "values": {
       "false": "no",
       "true": "sí"
     },
     "notifications": "Notificaciones",
+    "notification_setting": "Recibir notificaciones de:",
+    "notification_setting_follows": "Usuarios que sigues",
+    "notification_setting_non_follows": "Usuarios que no sigues",
+    "notification_setting_followers": "Usuarios que te siguen",
+    "notification_setting_non_followers": "Usuarios que no te siguen",
+    "notification_mutes": "Para dejar de recibir notificaciones de un usuario específico, siléncialo.",
+    "notification_blocks": "El bloqueo de un usuario detiene todas las notificaciones y también las cancela.",
     "enable_web_push_notifications": "Habilitar las notificiaciones en el navegador",
     "style": {
       "switcher": {
@@ -325,8 +443,47 @@
         "checkbox": "He revisado los términos y condiciones",
         "link": "un bonito enlace"
       }
+    },
+    "version": {
+      "title": "Versión",
+      "backend_version": "Versión del Backend",
+      "frontend_version": "Versión del Frontend"
     }
   },
+  "time": {
+    "day": "{0} día",
+    "days": "{0} días",
+    "day_short": "{0}d",
+    "days_short": "{0}d",
+    "hour": "{0} hora",
+    "hours": "{0} horas",
+    "hour_short": "{0}h",
+    "hours_short": "{0}h",
+    "in_future": "en {0}",
+    "in_past": "hace {0}",
+    "minute": "{0} minuto",
+    "minutes": "{0} minutos",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} mes",
+    "months": "{0} meses",
+    "month_short": "{0}m",
+    "months_short": "{0}m",
+    "now": "justo ahora",
+    "now_short": "ahora",
+    "second": "{0} segundo",
+    "seconds": "{0} segundos",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} semana",
+    "weeks": "{0} semana",
+    "week_short": "{0}sem",
+    "weeks_short": "{0}sem",
+    "year": "{0} año",
+    "years": "{0} años",
+    "year_short": "{0}a",
+    "years_short": "{0}a"
+  },
   "timeline": {
     "collapse": "Colapsar",
     "conversation": "Conversación",
@@ -336,7 +493,19 @@
     "repeated": "repetida",
     "show_new": "Mostrar lo nuevo",
     "up_to_date": "Actualizado",
-    "no_more_statuses": "No hay más estados"
+    "no_more_statuses": "No hay más estados",
+    "no_statuses": "Sin estados"
+  },
+  "status": {
+    "favorites": "Favoritos",
+    "repeats": "Repetidos",
+    "delete": "Eliminar publicación",
+    "pin": "Fijar en tu perfil",
+    "unpin": "Desclavar de tu perfil",
+    "pinned": "Fijado",
+    "delete_confirm": "¿Realmente quieres borrar la publicación?",
+    "reply_to": "Responder a",
+    "replies_list": "Respuestas:"
   },
   "user_card": {
     "approve": "Aprovar",
@@ -359,10 +528,49 @@
     "muted": "Silenciado",
     "per_day": "por día",
     "remote_follow": "Seguir",
-    "statuses": "Estados"
+    "report": "Reportar",
+    "statuses": "Estados",
+    "subscribe": "Suscribirse",
+    "unsubscribe": "Desuscribirse",
+    "unblock": "Desbloquear",
+    "unblock_progress": "Desbloqueando...",
+    "block_progress": "Bloqueando...",
+    "unmute": "Desenmudecer",
+    "unmute_progress": "Sesenmudeciendo...",
+    "mute_progress": "Silenciando...",
+    "admin_menu": {
+      "moderation": "Moderación",
+      "grant_admin": "Conceder permisos de Administrador",
+      "revoke_admin": "Revocar permisos de Administrador",
+      "grant_moderator": "Conceder permisos de Moderador",
+      "revoke_moderator": "Revocar permisos de Moderador",
+      "activate_account": "Activar cuenta",
+      "deactivate_account": "Desactivar cuenta",
+      "delete_account": "Borrar cuenta",
+      "force_nsfw": "Marcar todas las publicaciones como NSFW (no es seguro/apropiado para el trabajo)",
+      "strip_media": "Eliminar archivos multimedia de las publicaciones",
+      "force_unlisted": "Forzar que se publique en el modo -Sin Listar-",
+      "sandbox": "Forzar que se publique solo para tus seguidores",
+      "disable_remote_subscription": "No permitir que usuarios de instancias remotas te siga.",
+      "disable_any_subscription": "No permitir que ningún usuario te siga",
+      "quarantine": "No permitir publicaciones de usuarios de instancias remotas",
+      "delete_user": "Borrar usuario",
+      "delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer."
+    }
   },
   "user_profile": {
-    "timeline_title": "Linea temporal del usuario"
+    "timeline_title": "Linea temporal del usuario",
+    "profile_does_not_exist": "Lo sentimos, este perfil no existe.",
+    "profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil."
+  },
+   "user_reporting": {
+    "title": "Reportando a {0}",
+    "add_comment_description": "El informe será enviado a los moderadores de su instancia. Puedes proporcionar una explicación de por qué estás reportando esta cuenta a continuación:",
+    "additional_comments": "Comentarios adicionales",
+    "forward_description": "La cuenta es de otro servidor. ¿Enviar una copia del informe allí también?",
+    "forward_to": "Reenviar a {0}",
+    "submit": "Enviar",
+    "generic_error": "Se produjo un error al procesar la solicitud."
   },
   "who_to_follow": {
     "more": "Más",
@@ -388,5 +596,12 @@
       "GiB": "GiB",
       "TiB": "TiB"
     }
+  },
+  "search": {
+    "people": "Personas",
+    "hashtags": "Hashtags",
+    "person_talking": "{count} personas hablando",
+    "people_talking": "{count} gente hablando",
+    "no_results": "Sin resultados"
   }
-}
+}
\ No newline at end of file
diff --git a/src/i18n/fi.json b/src/i18n/fi.json
index fbe676cf..f4179495 100644
--- a/src/i18n/fi.json
+++ b/src/i18n/fi.json
@@ -36,6 +36,7 @@
     "chat": "Paikallinen Chat",
     "friend_requests": "Seurauspyynnöt",
     "mentions": "Maininnat",
+    "interactions": "Interaktiot",
     "dms": "Yksityisviestit",
     "public_tl": "Julkinen Aikajana",
     "timeline": "Aikajana",
@@ -54,6 +55,25 @@
     "repeated_you": "toisti viestisi",
     "no_more_notifications": "Ei enempää ilmoituksia"
   },
+  "polls": {
+    "add_poll": "Lisää äänestys",
+    "add_option": "Lisää vaihtoehto",
+    "option": "Vaihtoehto",
+    "votes": "ääntä",
+    "vote": "Äänestä",
+    "type": "Äänestyksen tyyppi",
+    "single_choice": "Yksi valinta",
+    "multiple_choices": "Monivalinta",
+    "expiry": "Äänestyksen kesto",
+    "expires_in": "Päättyy {0} päästä",
+    "expired": "Päättyi {0} sitten",
+    "not_enough_option": "Liian vähän uniikkeja vaihtoehtoja äänestyksessä"
+  },
+  "interactions": {
+    "favs_repeats": "Toistot ja tykkäykset",
+    "follows": "Uudet seuraukset",
+    "load_older": "Lataa vanhempia interaktioita"
+  },
   "post_status": {
     "new_status": "Uusi viesti",
     "account_not_locked_warning": "Tilisi ei ole {0}. Kuka vain voi seurata sinua nähdäksesi 'vain-seuraajille' -viestisi",
@@ -210,6 +230,40 @@
       "true": "päällä"
     }
   },
+  "time": {
+    "day": "{0} päivä",
+    "days": "{0} päivää",
+    "day_short": "{0}pv",
+    "days_short": "{0}pv",
+    "hour": "{0} tunti",
+    "hours": "{0} tuntia",
+    "hour_short": "{0}t",
+    "hours_short": "{0}t",
+    "in_future": "{0} tulevaisuudessa",
+    "in_past": "{0} sitten",
+    "minute": "{0} minuutti",
+    "minutes": "{0} minuuttia",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} kuukausi",
+    "months": "{0} kuukautta",
+    "month_short": "{0}kk",
+    "months_short": "{0}kk",
+    "now": "nyt",
+    "now_short": "juuri nyt",
+    "second": "{0} sekunti",
+    "seconds": "{0} sekuntia",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} viikko",
+    "weeks": "{0} viikkoa",
+    "week_short": "{0}vk",
+    "weeks_short": "{0}vk",
+    "year": "{0} vuosi",
+    "years": "{0} vuotta",
+    "year_short": "{0}v",
+    "years_short": "{0}v"
+  },
   "timeline": {
     "collapse": "Sulje",
     "conversation": "Keskustelu",
@@ -222,6 +276,8 @@
     "no_more_statuses": "Ei enempää viestejä"
   },
   "status": {
+    "favorites": "Tykkäykset",
+    "repeats": "Toistot",
     "reply_to": "Vastaus",
     "replies_list": "Vastaukset:"
   },
@@ -262,9 +318,9 @@
   },
   "upload":{
     "error": {
-    "base": "Lataus epäonnistui.",
-    "file_too_big": "Tiedosto liian suuri [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-    "default": "Yritä uudestaan myöhemmin"
+      "base": "Lataus epäonnistui.",
+      "file_too_big": "Tiedosto liian suuri [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+      "default": "Yritä uudestaan myöhemmin"
     },
     "file_size_units": {
       "B": "tavua",
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 8f9f243e..5f0053d5 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -1,209 +1,549 @@
 {
-  "chat": {
-    "title": "Chat"
-  },
-  "features_panel": {
-    "chat": "Chat",
-    "gopher": "Gopher",
-    "media_proxy": "Proxy média",
-    "scope_options": "Options de visibilité",
-    "text_limit": "Limite du texte",
-    "title": "Caractéristiques",
-    "who_to_follow": "Qui s'abonner"
-  },
-  "finder": {
-    "error_fetching_user": "Erreur lors de la recherche de l'utilisateur",
-    "find_user": "Chercher un utilisateur"
-  },
-  "general": {
-    "apply": "Appliquer",
-    "submit": "Envoyer"
-  },
-  "login": {
-    "login": "Connexion",
-    "description": "Connexion avec OAuth",
-    "logout": "Déconnexion",
-    "password": "Mot de passe",
-    "placeholder": "p.e. lain",
-    "register": "S'inscrire",
-    "username": "Identifiant"
-  },
-  "nav": {
-    "chat": "Chat local",
-    "friend_requests": "Demandes d'ami",
-    "dms": "Messages adressés",
-    "mentions": "Notifications",
-    "public_tl": "Statuts locaux",
-    "timeline": "Journal",
-    "twkn": "Le réseau connu"
-  },
-  "notifications": {
-    "broken_favorite": "Chargement d'un message inconnu ...",
-    "favorited_you": "a aimé votre statut",
-    "followed_you": "a commencé à vous suivre",
-    "load_older": "Charger les notifications précédentes",
-    "notifications": "Notifications",
-    "read": "Lu !",
-    "repeated_you": "a partagé votre statut"
-  },
-  "post_status": {
-    "account_not_locked_warning": "Votre compte n'est pas {0}. N'importe qui peut vous suivre pour voir vos billets en Abonné·e·s uniquement.",
-    "account_not_locked_warning_link": "verrouillé",
-    "attachments_sensitive": "Marquer le média comme sensible",
-    "content_type": {
-      "text/plain": "Texte brut"
+    "chat": {
+        "title": "Chat"
     },
-    "content_warning": "Sujet (optionnel)",
-    "default": "Écrivez ici votre prochain statut.",
-    "direct_warning": "Ce message sera visible à toutes les personnes mentionnées.",
-    "posting": "Envoi en cours",
-    "scope": {
-      "direct": "Direct - N'envoyer qu'aux personnes mentionnées",
-      "private": "Abonné·e·s uniquement - Seul·e·s vos abonné·e·s verront vos billets",
-      "public": "Publique - Afficher dans les fils publics",
-      "unlisted": "Non-Listé - Ne pas afficher dans les fils publics"
+    "exporter": {
+        "export": "Exporter",
+        "processing": "En cours de traitement, vous pourrez bientôt télécharger votre fichier"
+    },
+    "features_panel": {
+        "chat": "Chat",
+        "gopher": "Gopher",
+        "media_proxy": "Proxy média",
+        "scope_options": "Options de visibilité",
+        "text_limit": "Limite de texte",
+        "title": "Caractéristiques",
+        "who_to_follow": "Personnes à suivre"
+    },
+    "finder": {
+        "error_fetching_user": "Erreur lors de la recherche de l'utilisateur·ice",
+        "find_user": "Chercher un-e utilisateur·ice"
+    },
+    "general": {
+        "apply": "Appliquer",
+        "submit": "Envoyer",
+        "more": "Plus",
+        "generic_error": "Une erreur s'est produite",
+        "optional": "optionnel",
+        "show_more": "Montrer plus",
+        "show_less": "Montrer moins",
+        "cancel": "Annuler",
+        "disable": "Désactiver",
+        "enable": "Activer",
+        "confirm": "Confirmer",
+        "verify": "Vérifier"
+    },
+    "image_cropper": {
+        "crop_picture": "Rogner l'image",
+        "save": "Sauvegarder",
+        "save_without_cropping": "Sauvegarder sans rogner",
+        "cancel": "Annuler"
+    },
+    "importer": {
+        "submit": "Soumettre",
+        "success": "Importé avec succès.",
+        "error": "Une erreur est survenue pendant l'import de ce fichier."
+    },
+    "login": {
+        "login": "Connexion",
+        "description": "Connexion avec OAuth",
+        "logout": "Déconnexion",
+        "password": "Mot de passe",
+        "placeholder": "p.e. lain",
+        "register": "S'inscrire",
+        "username": "Identifiant",
+        "hint": "Connectez-vous pour rejoindre la discussion",
+        "authentication_code": "Code d'authentification",
+        "enter_recovery_code": "Entrez un code de récupération",
+        "enter_two_factor_code": "Entrez un code à double authentification",
+        "recovery_code": "Code de récupération",
+        "heading": {
+            "totp": "Authentification à double authentification",
+            "recovery": "Récuperation de la double authentification"
+        }
+    },
+    "media_modal": {
+        "previous": "Précédent",
+        "next": "Suivant"
+    },
+    "nav": {
+        "about": "À propos",
+        "back": "Retour",
+        "chat": "Chat local",
+        "friend_requests": "Demandes de suivi",
+        "mentions": "Notifications",
+        "interactions": "Interactions",
+        "dms": "Messages directs",
+        "public_tl": "Fil d'actualité public",
+        "timeline": "Fil d'actualité",
+        "twkn": "Ensemble du réseau connu",
+        "user_search": "Recherche d'utilisateur·ice",
+        "who_to_follow": "Qui suivre",
+        "preferences": "Préférences"
+    },
+    "notifications": {
+        "broken_favorite": "Chargement d'un message inconnu…",
+        "favorited_you": "a aimé votre statut",
+        "followed_you": "a commencé à vous suivre",
+        "load_older": "Charger les notifications précédentes",
+        "notifications": "Notifications",
+        "read": "Lu !",
+        "repeated_you": "a partagé votre statut",
+        "no_more_notifications": "Aucune notification supplémentaire"
+    },
+    "interactions": {
+        "favs_repeats": "Partages et favoris",
+        "follows": "Nouveaux⋅elles abonné⋅e⋅s ?",
+        "load_older": "Chargez d'anciennes interactions"
+    },
+    "post_status": {
+        "new_status": "Poster un nouveau statut",
+        "account_not_locked_warning": "Votre compte n'est pas {0}. N'importe qui peut vous suivre pour voir vos billets en Abonné·e·s uniquement.",
+        "account_not_locked_warning_link": "verrouillé",
+        "attachments_sensitive": "Marquer le média comme sensible",
+        "content_type": {
+            "text/plain": "Texte brut",
+            "text/html": "HTML",
+            "text/markdown": "Markdown",
+            "text/bbcode": "BBCode"
+        },
+        "content_warning": "Sujet (optionnel)",
+        "default": "Écrivez ici votre prochain statut.",
+        "direct_warning_to_all": "Ce message sera visible pour toutes les personnes mentionnées.",
+        "direct_warning_to_first_only": "Ce message sera visible uniquement pour personnes mentionnées au début du message.",
+        "posting": "Envoi en cours",
+        "scope_notice": {
+            "public": "Ce statut sera visible par tout le monde",
+            "private": "Ce statut sera visible par seulement vos abonné⋅e⋅s",
+            "unlisted": "Ce statut ne sera pas visible dans le Fil d'actualité public et l'Ensemble du réseau connu"
+        },
+        "scope": {
+            "direct": "Direct - N'envoyer qu'aux personnes mentionnées",
+            "private": "Abonné·e·s uniquement - Seul·e·s vos abonné·e·s verront vos billets",
+            "public": "Publique - Afficher dans les fils publics",
+            "unlisted": "Non-Listé - Ne pas afficher dans les fils publics"
+        }
+    },
+    "registration": {
+        "bio": "Biographie",
+        "email": "Adresse mail",
+        "fullname": "Pseudonyme",
+        "password_confirm": "Confirmation du mot de passe",
+        "registration": "Inscription",
+        "token": "Jeton d'invitation",
+        "captcha": "CAPTCHA",
+        "new_captcha": "Cliquez sur l'image pour avoir un nouveau captcha",
+        "username_placeholder": "p.e. lain",
+        "fullname_placeholder": "p.e. Lain Iwakura",
+        "bio_placeholder": "p.e.\nSalut, je suis Lain\nJe suis une héroïne d'animé qui vit dans une banlieue japonaise. Vous me connaissez peut-être du Wired.",
+        "validations": {
+            "username_required": "ne peut pas être laissé vide",
+            "fullname_required": "ne peut pas être laissé vide",
+            "email_required": "ne peut pas être laissé vide",
+            "password_required": "ne peut pas être laissé vide",
+            "password_confirmation_required": "ne peut pas être laissé vide",
+            "password_confirmation_match": "doit être identique au mot de passe"
+        }
+    },
+    "selectable_list": {
+        "select_all": "Tout selectionner"
+    },
+    "settings": {
+        "app_name": "Nom de l'application",
+        "security": "Sécurité",
+        "enter_current_password_to_confirm": "Entrez votre mot de passe actuel pour confirmer votre identité",
+        "mfa": {
+            "otp": "OTP",
+            "setup_otp": "Configurer OTP",
+            "wait_pre_setup_otp": "préconfiguration OTP",
+            "confirm_and_enable": "Confirmer & activer OTP",
+            "title": "Double authentification",
+            "generate_new_recovery_codes": "Générer de nouveaux codes de récupération",
+            "warning_of_generate_new_codes": "Quand vous générez de nouveauc codes de récupération, vos anciens codes ne fonctionnerons plus.",
+            "recovery_codes": "Codes de récupération.",
+            "waiting_a_recovery_codes": "Récéption des codes de récupération…",
+            "recovery_codes_warning": "Écrivez les codes ou sauvez les quelquepart sécurisé - sinon vous ne les verrez plus jamais. Si vous perdez l'accès à votre application de double authentification et codes de récupération vous serez vérouillé en dehors de votre compte.",
+            "authentication_methods": "Methodes d'authentification",
+            "scan": {
+                "title": "Scanner",
+                "desc": "En utilisant votre application de double authentification, scannez ce QR code ou entrez la clé textuelle :",
+                "secret_code": "Clé"
+            },
+            "verify": {
+                "desc": "Pour activer la double authentification, entrez le code depuis votre application:"
+            }
+        },
+        "attachmentRadius": "Pièces jointes",
+        "attachments": "Pièces jointes",
+        "autoload": "Charger la suite automatiquement une fois le bas de la page atteint",
+        "avatar": "Avatar",
+        "avatarAltRadius": "Avatars (Notifications)",
+        "avatarRadius": "Avatars",
+        "background": "Arrière-plan",
+        "bio": "Biographie",
+        "block_export": "Export des comptes bloqués",
+        "block_export_button": "Export des comptes bloqués vers un fichier csv",
+        "block_import": "Import des comptes bloqués",
+        "block_import_error": "Erreur lors de l'import des comptes bloqués",
+        "blocks_imported": "Blocks importés! Le traitement va prendre un moment.",
+        "blocks_tab": "Bloqué·e·s",
+        "btnRadius": "Boutons",
+        "cBlue": "Bleu (répondre, suivre)",
+        "cGreen": "Vert (partager)",
+        "cOrange": "Orange (aimer)",
+        "cRed": "Rouge (annuler)",
+        "change_password": "Changez votre mot de passe",
+        "change_password_error": "Il y a eu un problème pour changer votre mot de passe.",
+        "changed_password": "Mot de passe modifié avec succès !",
+        "collapse_subject": "Réduire les messages avec des sujets",
+        "composing": "Composition",
+        "confirm_new_password": "Confirmation du nouveau mot de passe",
+        "current_avatar": "Avatar actuel",
+        "current_password": "Mot de passe actuel",
+        "current_profile_banner": "Bannière de profil actuelle",
+        "data_import_export_tab": "Import / Export des Données",
+        "default_vis": "Visibilité par défaut",
+        "delete_account": "Supprimer le compte",
+        "delete_account_description": "Supprimer définitivement votre compte et tous vos statuts.",
+        "delete_account_error": "Il y a eu un problème lors de la tentative de suppression de votre compte. Si le problème persiste, contactez l'administrateur⋅ice de cette instance.",
+        "delete_account_instructions": "Indiquez votre mot de passe ci-dessous pour confirmer la suppression de votre compte.",
+        "avatar_size_instruction": "La taille minimale recommandée pour l'image de l'avatar est de 150x150 pixels.",
+        "export_theme": "Enregistrer le thème",
+        "filtering": "Filtre",
+        "filtering_explanation": "Tous les statuts contenant ces mots seront masqués. Un mot par ligne",
+        "follow_export": "Exporter les abonnements",
+        "follow_export_button": "Exporter les abonnements en csv",
+        "follow_import": "Importer des abonnements",
+        "follow_import_error": "Erreur lors de l'importation des abonnements",
+        "follows_imported": "Abonnements importés ! Le traitement peut prendre un moment.",
+        "foreground": "Premier plan",
+        "general": "Général",
+        "hide_attachments_in_convo": "Masquer les pièces jointes dans les conversations",
+        "hide_attachments_in_tl": "Masquer les pièces jointes dans le journal",
+        "hide_muted_posts": "Masquer les statuts des utilisateurs masqués",
+        "max_thumbnails": "Nombre maximum de miniatures par statuts",
+        "hide_isp": "Masquer le panneau spécifique a l'instance",
+        "preload_images": "Précharger les images",
+        "use_one_click_nsfw": "Ouvrir les pièces-jointes NSFW avec un seul clic",
+        "hide_post_stats": "Masquer les statistiques de publication (le nombre de favoris)",
+        "hide_user_stats": "Masquer les statistiques de profil (le nombre d'amis)",
+        "hide_filtered_statuses": "Masquer les statuts filtrés",
+        "import_blocks_from_a_csv_file": "Importer les blocages depuis un fichier csv",
+        "import_followers_from_a_csv_file": "Importer des abonnements depuis un fichier csv",
+        "import_theme": "Charger le thème",
+        "inputRadius": "Champs de texte",
+        "checkboxRadius": "Cases à cocher",
+        "instance_default": "(default: {value})",
+        "instance_default_simple": "(default)",
+        "interface": "Interface",
+        "interfaceLanguage": "Langue de l'interface",
+        "invalid_theme_imported": "Le fichier sélectionné n'est pas un thème Pleroma pris en charge. Aucun changement n'a été apporté à votre thème.",
+        "limited_availability": "Non disponible dans votre navigateur",
+        "links": "Liens",
+        "lock_account_description": "Limitez votre compte aux abonnés acceptés uniquement",
+        "loop_video": "Vidéos en boucle",
+        "loop_video_silent_only": "Boucle uniquement les vidéos sans le son (les « gifs » de Mastodon)",
+        "mutes_tab": "Comptes silenciés",
+        "play_videos_in_modal": "Jouer les vidéos directement dans le visionneur de médias",
+        "use_contain_fit": "Ne pas rogner les miniatures des pièces-jointes",
+        "name": "Nom",
+        "name_bio": "Nom & Bio",
+        "new_password": "Nouveau mot de passe",
+        "notification_visibility": "Types de notifications à afficher",
+        "notification_visibility_follows": "Abonnements",
+        "notification_visibility_likes": "J'aime",
+        "notification_visibility_mentions": "Mentionnés",
+        "notification_visibility_repeats": "Partages",
+        "no_rich_text_description": "Ne formatez pas le texte",
+        "no_blocks": "Aucun bloqués",
+        "no_mutes": "Aucun masqués",
+        "hide_follows_description": "Ne pas afficher à qui je suis abonné",
+        "hide_followers_description": "Ne pas afficher qui est abonné à moi",
+        "show_admin_badge": "Afficher le badge d'Administrateur⋅ice sur mon profil",
+        "show_moderator_badge": "Afficher le badge de Modérateur⋅ice sur mon profil",
+        "nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible",
+        "oauth_tokens": "Jetons OAuth",
+        "token": "Jeton",
+        "refresh_token": "Refresh Token",
+        "valid_until": "Valable jusque",
+        "revoke_token": "Révoquer",
+        "panelRadius": "Fenêtres",
+        "pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas actif",
+        "presets": "Thèmes prédéfinis",
+        "profile_background": "Image de fond",
+        "profile_banner": "Bannière de profil",
+        "profile_tab": "Profil",
+        "radii_help": "Vous pouvez ici choisir le niveau d'arrondi des angles de l'interface (en pixels)",
+        "replies_in_timeline": "Réponses au journal",
+        "reply_link_preview": "Afficher un aperçu lors du survol de liens vers une réponse",
+        "reply_visibility_all": "Montrer toutes les réponses",
+        "reply_visibility_following": "Afficher uniquement les réponses adressées à moi ou aux personnes que je suis",
+        "reply_visibility_self": "Afficher uniquement les réponses adressées à moi",
+        "autohide_floating_post_button": "Automatiquement cacher le bouton de Nouveau Statut (sur mobile)",
+        "saving_err": "Erreur lors de l'enregistrement des paramètres",
+        "saving_ok": "Paramètres enregistrés",
+        "search_user_to_block": "Rechercher qui vous voulez bloquer",
+        "search_user_to_mute": "Rechercher qui vous voulez masquer",
+        "security_tab": "Sécurité",
+        "scope_copy": "Garder la même visibilité en répondant (les DMs restent toujours des DMs)",
+        "minimal_scopes_mode": "Rétrécir les options de séléction de la portée",
+        "set_new_avatar": "Changer d'avatar",
+        "set_new_profile_background": "Changer d'image de fond",
+        "set_new_profile_banner": "Changer de bannière",
+        "settings": "Paramètres",
+        "subject_input_always_show": "Toujours copier le champ de sujet",
+        "subject_line_behavior": "Copier le sujet en répondant",
+        "subject_line_email": "Comme les mails: « re: sujet »",
+        "subject_line_mastodon": "Comme mastodon: copier tel quel",
+        "subject_line_noop": "Ne pas copier",
+        "post_status_content_type": "Type de contenu du statuts",
+        "stop_gifs": "N'animer les GIFS que lors du survol du curseur de la souris",
+        "streaming": "Charger automatiquement les nouveaux statuts lorsque vous êtes au haut de la page",
+        "text": "Texte",
+        "theme": "Thème",
+        "theme_help": "Spécifiez des codes couleur hexadécimaux (#rrvvbb) pour personnaliser les couleurs du thème.",
+        "theme_help_v2_1": "Vous pouvez aussi surcharger certaines couleurs de composants et transparence via la case à cocher, utilisez le bouton « Vider tout » pour effacer toutes les surcharges.",
+        "theme_help_v2_2": "Les icônes sous certaines des entrées ont un indicateur de contraste du fond/texte, survolez les pour plus d'informations détailles. Veuillez garder a l'esprit que lors de l'utilisation de transparence l'indicateur de contraste indique le pire des cas.",
+        "tooltipRadius": "Info-bulles/alertes",
+        "upload_a_photo": "Envoyer une photo",
+        "user_settings": "Paramètres utilisateur",
+        "values": {
+            "false": "non",
+            "true": "oui"
+        },
+        "notifications": "Notifications",
+        "notification_setting": "Reçevoir les notifications de:",
+        "notification_setting_follows": "Utilisateurs que vous suivez",
+        "notification_setting_non_follows": "Utilisateurs que vous ne suivez pas",
+        "notification_setting_followers": "Utilisateurs qui vous suivent",
+        "notification_setting_non_followers": "Utilisateurs qui ne vous suivent pas",
+        "notification_mutes": "Pour stopper la récéption de notifications d'un utilisateur particulier, utilisez un masquage.",
+        "notification_blocks": "Bloquer un utilisateur stoppe toute notification et se désabonne de lui.",
+        "enable_web_push_notifications": "Activer les notifications de push web",
+        "style": {
+            "switcher": {
+                "keep_color": "Garder les couleurs",
+                "keep_shadows": "Garder les ombres",
+                "keep_opacity": "Garder la transparence",
+                "keep_roundness": "Garder la rondeur",
+                "keep_fonts": "Garder les polices",
+                "save_load_hint": "L'option « Garder » préserve les options activés en cours lors de la séléction ou chargement des thèmes, il sauve aussi les dites options lors de l'export d'un thème. Quand toutes les cases sont décochés, exporter un thème sauvera tout.",
+                "reset": "Remise à zéro",
+                "clear_all": "Tout vider",
+                "clear_opacity": "Vider la transparence"
+            },
+            "common": {
+                "color": "Couleur",
+                "opacity": "Transparence",
+                "contrast": {
+                    "hint": "Le ratio de contraste est {ratio}, il {level} {context}",
+                    "level": {
+                        "aa": "répond aux directives de niveau AA (minimum)",
+                        "aaa": "répond aux directives de niveau AAA (recommandé)",
+                        "bad": "ne réponds à aucune directive d'accessibilité"
+                    },
+                    "context": {
+                        "18pt": "pour texte large (19pt+)",
+                        "text": "pour texte"
+                    }
+                }
+            },
+            "common_colors": {
+                "_tab_label": "Commun",
+                "main": "Couleurs communes",
+                "foreground_hint": "Voir l'onglet « Avancé » pour plus de contrôle détaillé",
+                "rgbo": "Icônes, accents, badges"
+            },
+            "advanced_colors": {
+                "_tab_label": "Avancé",
+                "alert": "Fond d'alerte",
+                "alert_error": "Erreur",
+                "badge": "Fond de badge",
+                "badge_notification": "Notification",
+                "panel_header": "Entête de panneau",
+                "top_bar": "Barre du haut",
+                "borders": "Bordures",
+                "buttons": "Boutons",
+                "inputs": "Champs de saisie",
+                "faint_text": "Texte en fondu"
+            },
+            "radii": {
+                "_tab_label": "Rondeur"
+            },
+            "shadows": {
+                "_tab_label": "Ombres et éclairage",
+                "component": "Composant",
+                "override": "Surcharger",
+                "shadow_id": "Ombre #{value}",
+                "blur": "Flou",
+                "spread": "Dispersion",
+                "inset": "Interne",
+                "hint": "Pour les ombres, vous pouvez aussi utiliser --variable comme valeur de couleur en CSS3. Veuillez noter que spécifier la transparence ne fonctionnera pas dans ce cas.",
+                "filter_hint": {
+                    "always_drop_shadow": "Attention, cette ombre utilise toujours {0} quand le navigateur le supporte.",
+                    "drop_shadow_syntax": "{0} ne supporte pas le paramètre {1} et mot-clé {2}.",
+                    "avatar_inset": "Veuillez noter que combiner a la fois les ombres internes et non-internes sur les avatars peut fournir des résultats innatendus avec la transparence des avatars.",
+                    "spread_zero": "Les ombres avec une dispersion > 0 apparaitrons comme si ils étaient à zéro",
+                    "inset_classic": "L'ombre interne utilisera toujours {0}"
+                },
+                "components": {
+                    "panel": "Panneau",
+                    "panelHeader": "En-tête de panneau",
+                    "topBar": "Barre du haut",
+                    "avatar": "Avatar utilisateur⋅ice (dans la vue de profil)",
+                    "avatarStatus": "Avatar utilisateur⋅ice (dans la vue de statuts)",
+                    "popup": "Popups et infobulles",
+                    "button": "Bouton",
+                    "buttonHover": "Bouton (survol)",
+                    "buttonPressed": "Bouton (cliqué)",
+                    "buttonPressedHover": "Bouton (cliqué+survol)",
+                    "input": "Champ de saisie"
+                }
+            },
+            "fonts": {
+                "_tab_label": "Polices",
+                "help": "Sélectionnez la police à utiliser pour les éléments de l'UI. Pour « personnalisé » vous avez à entrer le nom exact de la police comme il apparaît dans le système.",
+                "components": {
+                    "interface": "Interface",
+                    "input": "Champs de saisie",
+                    "post": "Post text",
+                    "postCode": "Texte à taille fixe dans un article (texte enrichi)"
+                },
+                "family": "Nom de la police",
+                "size": "Taille (en px)",
+                "weight": "Poid (gras)",
+                "custom": "Personnalisé"
+            },
+            "preview": {
+                "header": "Prévisualisation",
+                "content": "Contenu",
+                "error": "Exemple d'erreur",
+                "button": "Bouton",
+                "text": "Un certain nombre de {0} et {1}",
+                "mono": "contenu",
+                "input": "Je viens juste d’atterrir à L.A.",
+                "faint_link": "manuel utile",
+                "fine_print": "Lisez notre {0} pour n'apprendre rien d'utile !",
+                "header_faint": "Tout va bien",
+                "checkbox": "J'ai survolé les conditions d'utilisation",
+                "link": "un petit lien sympa"
+            }
+        },
+        "version": {
+            "title": "Version",
+            "backend_version": "Version du Backend",
+            "frontend_version": "Version du Frontend"
+        }
+    },
+    "timeline": {
+        "collapse": "Fermer",
+        "conversation": "Conversation",
+        "error_fetching": "Erreur en cherchant les mises à jour",
+        "load_older": "Afficher plus",
+        "no_retweet_hint": "Le message est marqué en abonnés-seulement ou direct et ne peut pas être partagé",
+        "repeated": "a partagé",
+        "show_new": "Afficher plus",
+        "up_to_date": "À jour",
+        "no_more_statuses": "Pas plus de statuts",
+        "no_statuses": "Aucun statuts"
+    },
+    "status": {
+        "favorites": "Favoris",
+        "repeats": "Partages",
+        "delete": "Supprimer statuts",
+        "pin": "Agraffer sur le profil",
+        "unpin": "Dégraffer du profil",
+        "pinned": "Agraffé",
+        "delete_confirm": "Voulez-vous vraiment supprimer ce statuts ?",
+        "reply_to": "Réponse à",
+        "replies_list": "Réponses:"
+    },
+    "user_card": {
+        "approve": "Accepter",
+        "block": "Bloquer",
+        "blocked": "Bloqué !",
+        "deny": "Rejeter",
+        "favorites": "Favoris",
+        "follow": "Suivre",
+        "follow_sent": "Demande envoyée !",
+        "follow_progress": "Demande en cours…",
+        "follow_again": "Renvoyer la demande ?",
+        "follow_unfollow": "Désabonner",
+        "followees": "Suivis",
+        "followers": "Vous suivent",
+        "following": "Suivi !",
+        "follows_you": "Vous suit !",
+        "its_you": "C'est vous !",
+        "media": "Media",
+        "mute": "Masquer",
+        "muted": "Masqué",
+        "per_day": "par jour",
+        "remote_follow": "Suivre d'une autre instance",
+        "report": "Signalement",
+        "statuses": "Statuts",
+        "unblock": "Débloquer",
+        "unblock_progress": "Déblocage…",
+        "block_progress": "Blocage…",
+        "unmute": "Démasquer",
+        "unmute_progress": "Démasquage…",
+        "mute_progress": "Masquage…",
+        "admin_menu": {
+            "moderation": "Moderation",
+            "grant_admin": "Promouvoir Administrateur⋅ice",
+            "revoke_admin": "Dégrader Administrateur⋅ice",
+            "grant_moderator": "Promouvoir Modérateur⋅ice",
+            "revoke_moderator": "Dégrader Modérateur⋅ice",
+            "activate_account": "Activer le compte",
+            "deactivate_account": "Désactiver le compte",
+            "delete_account": "Supprimer le compte",
+            "force_nsfw": "Marquer tous les statuts comme NSFW",
+            "strip_media": "Supprimer les medias des statuts",
+            "force_unlisted": "Forcer les statuts à être délistés",
+            "sandbox": "Forcer les statuts à être visibles seuleument pour les abonné⋅e⋅s",
+            "disable_remote_subscription": "Interdir de s'abonner a l'utilisateur depuis l'instance distante",
+            "disable_any_subscription": "Interdir de s'abonner à l'utilisateur tout court",
+            "quarantine": "Interdir les statuts de l'utilisateur à fédérer",
+            "delete_user": "Supprimer l'utilisateur",
+            "delete_user_confirmation": "Êtes-vous absolument-sûr⋅e ? Cette action ne peut être annulée."
+        }
+    },
+    "user_profile": {
+        "timeline_title": "Journal de l'utilisateur⋅ice",
+        "profile_does_not_exist": "Désolé, ce profil n'existe pas.",
+        "profile_loading_error": "Désolé, il y a eu une erreur au chargement du profil."
+    },
+    "user_reporting": {
+        "title": "Signaler {0}",
+        "add_comment_description": "Ce signalement sera envoyé aux modérateur⋅ice⋅s de votre instance. Vous pouvez fournir une explication de pourquoi vous signalez ce compte ci-dessous :",
+        "additional_comments": "Commentaires additionnels",
+        "forward_description": "Le compte vient d'un autre serveur. Envoyer une copie du signalement à celui-ci aussi ?",
+        "forward_to": "Transmettre à {0}",
+        "submit": "Envoyer",
+        "generic_error": "Une erreur est survenue lors du traitement de votre requête."
+    },
+    "who_to_follow": {
+        "more": "Plus",
+        "who_to_follow": "À qui s'abonner"
+    },
+    "tool_tip": {
+        "media_upload": "Envoyer un media",
+        "repeat": "Répéter",
+        "reply": "Répondre",
+        "favorite": "Favoriser",
+        "user_settings": "Paramètres utilisateur"
+    },
+    "upload": {
+        "error": {
+            "base": "L'envoi a échoué.",
+            "file_too_big": "Fichier trop gros [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+            "default": "Réessayez plus tard"
+        },
+        "file_size_units": {
+            "B": "O",
+            "KiB": "KiO",
+            "MiB": "MiO",
+            "GiB": "GiO",
+            "TiB": "TiO"
+        }
     }
-  },
-  "registration": {
-    "bio": "Biographie",
-    "email": "Adresse email",
-    "fullname": "Pseudonyme",
-    "password_confirm": "Confirmation du mot de passe",
-    "registration": "Inscription",
-    "token": "Jeton d'invitation"
-  },
-  "settings": {
-    "attachmentRadius": "Pièces jointes",
-    "attachments": "Pièces jointes",
-    "autoload": "Charger la suite automatiquement une fois le bas de la page atteint",
-    "avatar": "Avatar",
-    "avatarAltRadius": "Avatars (Notifications)",
-    "avatarRadius": "Avatars",
-    "background": "Arrière-plan",
-    "bio": "Biographie",
-    "btnRadius": "Boutons",
-    "cBlue": "Bleu (Répondre, suivre)",
-    "cGreen": "Vert (Partager)",
-    "cOrange": "Orange (Aimer)",
-    "cRed": "Rouge (Annuler)",
-    "change_password": "Changez votre mot de passe",
-    "change_password_error": "Il y a eu un problème pour changer votre mot de passe.",
-    "changed_password": "Mot de passe modifié avec succès !",
-    "collapse_subject": "Réduire les messages avec des sujets",
-    "confirm_new_password": "Confirmation du nouveau mot de passe",
-    "current_avatar": "Avatar actuel",
-    "current_password": "Mot de passe actuel",
-    "current_profile_banner": "Bannière de profil actuelle",
-    "data_import_export_tab": "Import / Export des Données",
-    "default_vis": "Portée de visibilité par défaut",
-    "delete_account": "Supprimer le compte",
-    "delete_account_description": "Supprimer définitivement votre compte et tous vos statuts.",
-    "delete_account_error": "Il y a eu un problème lors de la tentative de suppression de votre compte. Si le problème persiste, contactez l'administrateur de cette instance.",
-    "delete_account_instructions": "Indiquez votre mot de passe ci-dessous pour confirmer la suppression de votre compte.",
-    "export_theme": "Enregistrer le thème",
-    "filtering": "Filtre",
-    "filtering_explanation": "Tous les statuts contenant ces mots seront masqués. Un mot par ligne",
-    "follow_export": "Exporter les abonnements",
-    "follow_export_button": "Exporter les abonnements en csv",
-    "follow_export_processing": "Exportation en cours…",
-    "follow_import": "Importer des abonnements",
-    "follow_import_error": "Erreur lors de l'importation des abonnements",
-    "follows_imported": "Abonnements importés ! Le traitement peut prendre un moment.",
-    "foreground": "Premier plan",
-    "general": "Général",
-    "hide_attachments_in_convo": "Masquer les pièces jointes dans les conversations",
-    "hide_attachments_in_tl": "Masquer les pièces jointes dans le journal",
-    "hide_post_stats": "Masquer les statistiques de publication (le nombre de favoris)",
-    "hide_user_stats": "Masquer les statistiques de profil (le nombre d'amis)",
-    "import_followers_from_a_csv_file": "Importer des abonnements depuis un fichier csv",
-    "import_theme": "Charger le thème",
-    "inputRadius": "Champs de texte",
-    "instance_default": "(default: {value})",
-    "instance_default_simple" : "(default)",
-    "interfaceLanguage": "Langue de l'interface",
-    "invalid_theme_imported": "Le fichier sélectionné n'est pas un thème Pleroma pris en charge. Aucun changement n'a été apporté à votre thème.",
-    "limited_availability": "Non disponible dans votre navigateur",
-    "links": "Liens",
-    "lock_account_description": "Limitez votre compte aux abonnés acceptés uniquement",
-    "loop_video": "Vidéos en boucle",
-    "loop_video_silent_only": "Boucle uniquement les vidéos sans le son (les «gifs» de Mastodon)",
-    "name": "Nom",
-    "name_bio": "Nom & Bio",
-    "new_password": "Nouveau mot de passe",
-    "no_rich_text_description": "Ne formatez pas le texte",
-    "notification_visibility": "Types de notifications à afficher",
-    "notification_visibility_follows": "Abonnements",
-    "notification_visibility_likes": "J’aime",
-    "notification_visibility_mentions": "Mentionnés",
-    "notification_visibility_repeats": "Partages",
-    "nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible",
-    "oauth_tokens": "Jetons OAuth",
-    "token": "Jeton",
-    "refresh_token": "Refresh Token",
-    "valid_until": "Valable jusque",
-    "revoke_token": "Révoquer",
-    "panelRadius": "Fenêtres",
-    "pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas centré",
-    "presets": "Thèmes prédéfinis",
-    "profile_background": "Image de fond",
-    "profile_banner": "Bannière de profil",
-    "profile_tab": "Profil",
-    "radii_help": "Vous pouvez ici choisir le niveau d'arrondi des angles de l'interface (en pixels)",
-    "replies_in_timeline": "Réponses au journal",
-    "reply_link_preview": "Afficher un aperçu lors du survol de liens vers une réponse",
-    "reply_visibility_all": "Montrer toutes les réponses",
-    "reply_visibility_following": "Afficher uniquement les réponses adressées à moi ou aux utilisateurs que je suis",
-    "reply_visibility_self": "Afficher uniquement les réponses adressées à moi",
-    "saving_err": "Erreur lors de l'enregistrement des paramètres",
-    "saving_ok": "Paramètres enregistrés",
-    "security_tab": "Sécurité",
-    "set_new_avatar": "Changer d'avatar",
-    "set_new_profile_background": "Changer d'image de fond",
-    "set_new_profile_banner": "Changer de bannière",
-    "settings": "Paramètres",
-    "stop_gifs": "N'animer les GIFS que lors du survol du curseur de la souris",
-    "streaming": "Charger automatiquement les nouveaux statuts lorsque vous êtes au haut de la page",
-    "text": "Texte",
-    "theme": "Thème",
-    "theme_help": "Spécifiez des codes couleur hexadécimaux (#rrvvbb) pour personnaliser les couleurs du thème.",
-    "tooltipRadius": "Info-bulles/alertes",
-    "user_settings": "Paramètres utilisateur",
-    "values": {
-      "false": "non",
-      "true": "oui"
-    }
-  },
-  "timeline": {
-    "collapse": "Fermer",
-    "conversation": "Conversation",
-    "error_fetching": "Erreur en cherchant les mises à jour",
-    "load_older": "Afficher plus",
-    "no_retweet_hint": "Le message est marqué en abonnés-seulement ou direct et ne peut pas être répété",
-    "repeated": "a partagé",
-    "show_new": "Afficher plus",
-    "up_to_date": "À jour"
-  },
-  "user_card": {
-    "approve": "Accepter",
-    "block": "Bloquer",
-    "blocked": "Bloqué !",
-    "deny": "Rejeter",
-    "follow": "Suivre",
-    "followees": "Suivis",
-    "followers": "Vous suivent",
-    "following": "Suivi !",
-    "follows_you": "Vous suit !",
-    "mute": "Masquer",
-    "muted": "Masqué",
-    "per_day": "par jour",
-    "remote_follow": "Suivre d'une autre instance",
-    "statuses": "Statuts"
-  },
-  "user_profile": {
-    "timeline_title": "Journal de l'utilisateur"
-  },
-  "who_to_follow": {
-    "more": "Plus",
-    "who_to_follow": "Qui s'abonner"
-  }
 }
diff --git a/src/i18n/ga.json b/src/i18n/ga.json
index 31250876..7a10ba40 100644
--- a/src/i18n/ga.json
+++ b/src/i18n/ga.json
@@ -170,6 +170,40 @@
       "true": "tá"
     }
   },
+  "time": {
+    "day": "{0} lá",
+    "days": "{0} lá",
+    "day_short": "{0}l",
+    "days_short": "{0}l",
+    "hour": "{0} uair",
+    "hours": "{0} uair",
+    "hour_short": "{0}u",
+    "hours_short": "{0}u",
+    "in_future": "in {0}",
+    "in_past": "{0} ago",
+    "minute": "{0} nóimeád",
+    "minutes": "{0} nóimeád",
+    "minute_short": "{0}n",
+    "minutes_short": "{0}n",
+    "month": "{0} mí",
+    "months": "{0} mí",
+    "month_short": "{0}m",
+    "months_short": "{0}m",
+    "now": "Anois",
+    "now_short": "Anois",
+    "second": "{0} s",
+    "seconds": "{0} s",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} seachtain",
+    "weeks": "{0} seachtaine",
+    "week_short": "{0}se",
+    "weeks_short": "{0}se",
+    "year": "{0} bliainta",
+    "years": "{0} bliainta",
+    "year_short": "{0}b",
+    "years_short": "{0}b"
+  },
   "timeline": {
     "collapse": "Folaigh",
     "conversation": "Cómhra",
diff --git a/src/i18n/he.json b/src/i18n/he.json
index ea581e05..1c034960 100644
--- a/src/i18n/he.json
+++ b/src/i18n/he.json
@@ -2,6 +2,10 @@
   "chat": {
     "title": "צ'אט"
   },
+  "exporter": {
+    "export": "ייצוא",
+    "processing": "מעבד, בקרוב תופיע אפשרות להוריד את הקובץ"
+  },
   "features_panel": {
     "chat": "צ'אט",
     "gopher": "גופר",
@@ -17,23 +21,53 @@
   },
   "general": {
     "apply": "החל",
-    "submit": "שלח"
+    "submit": "שלח",
+    "more": "עוד",
+    "generic_error": "קרתה שגיאה",
+    "optional": "לבחירה",
+    "show_more": "הראה עוד",
+    "show_less": "הראה פחות",
+    "cancel": "בטל"
+  },
+  "image_cropper": {
+    "crop_picture": "חתוך תמונה",
+    "save": "שמור",
+    "save_without_cropping": "שמור בלי לחתוך",
+    "cancel": "בטל"
+  },
+  "importer": {
+    "submit": "שלח",
+    "success": "ייובא בהצלחה.",
+    "error": "אירעתה שגיאה בזמן ייבוא קובץ זה."
   },
   "login": {
     "login": "התחבר",
+    "description": "היכנס עם OAuth",
     "logout": "התנתק",
     "password": "סיסמה",
     "placeholder": "למשל lain",
     "register": "הירשם",
-    "username": "שם המשתמש"
+    "username": "שם המשתמש",
+    "hint": "הירשם על מנת להצטרף לדיון"
+  },
+  "media_modal": {
+    "previous": "הקודם",
+    "next": "הבא"
   },
   "nav": {
+    "about": "על-אודות",
+    "back": "חזור",
     "chat": "צ'אט מקומי",
     "friend_requests": "בקשות עקיבה",
     "mentions": "אזכורים",
+    "interactions": "אינטרקציות",
+    "dms": "הודעות ישירות",
     "public_tl": "ציר הזמן הציבורי",
     "timeline": "ציר הזמן",
-    "twkn": "כל הרשת הידועה"
+    "twkn": "כל הרשת הידועה",
+    "user_search": "חיפוש משתמש",
+    "who_to_follow": "אחרי מי לעקוב",
+    "preferences": "העדפות"
   },
   "notifications": {
     "broken_favorite": "סטאטוס לא ידוע, מחפש...",
@@ -42,19 +76,35 @@
     "load_older": "טען התראות ישנות",
     "notifications": "התראות",
     "read": "קרא!",
-    "repeated_you": "חזר על הסטטוס שלך"
+    "repeated_you": "חזר על הסטטוס שלך",
+    "no_more_notifications": "לא עוד התראות"
+  },
+  "interactions": {
+    "favs_repeats": "חזרות ומועדפים",
+    "follows": "עוקבים חדשים",
+    "load_older": "טען אינטרקציות ישנות"
   },
   "post_status": {
+    "new_status": "פרסם סטאטוס חדש",
     "account_not_locked_warning": "המשתמש שלך אינו {0}. כל אחד יכול לעקוב אחריך ולראות את ההודעות לעוקבים-בלבד שלך.",
     "account_not_locked_warning_link": "נעול",
     "attachments_sensitive": "סמן מסמכים מצורפים כלא בטוחים לצפייה",
     "content_type": {
-      "text/plain": "טקסט פשוט"
+      "text/plain": "טקסט פשוט",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
     },
     "content_warning": "נושא (נתון לבחירה)",
     "default": "הרגע נחת ב-ל.א.",
-    "direct_warning": "הודעה זו תהיה זמינה רק לאנשים המוזכרים.",
+    "direct_warning_to_all": "הודעה זו תהיה נראית לכל המשתמשים המוזכרים.",
+    "direct_warning_to_first_only": "הודעה זו תהיה נראית לכל המשתמשים במוזכרים בתחילת ההודעה בלבד.",
     "posting": "מפרסם",
+    "scope_notice": {
+      "public": "הודעה זו תהיה נראית לכולם",
+      "private": "הודעה זו תהיה נראית לעוקבים שלך בלבד",
+      "unlisted": "הודעה זו לא תהיה נראית בציר זמן הציבורי או בכל הרשת הידועה"
+    },
     "scope": {
       "direct": "ישיר - שלח לאנשים המוזכרים בלבד",
       "private": "עוקבים-בלבד - שלח לעוקבים בלבד",
@@ -68,9 +118,26 @@
     "fullname": "שם תצוגה",
     "password_confirm": "אישור סיסמה",
     "registration": "הרשמה",
-    "token": "טוקן הזמנה"
+    "token": "טוקן הזמנה",
+    "captcha": "אימות אנוש",
+    "new_captcha": "לחץ על התמונה על מנת לקבל אימות אנוש חדש",
+    "username_placeholder": "למשל lain",
+    "fullname_placeholder": "למשל Lain Iwakura",
+    "bio_placeholder": "למשל\nהיי, אני ליין.\nאני ילדת אנימה שגרה בפרוורי יפן. אולי אתם מכירים אותי מהWired.",
+    "validations": {
+      "username_required": "לא יכול להישאר ריק",
+      "fullname_required": "לא יכול להישאר ריק",
+      "email_required": "לא יכול להישאר ריק",
+      "password_required": "לא יכול להישאר ריק",
+      "password_confirmation_required": "לא יכול להישאר ריק",
+      "password_confirmation_match": "צריך להיות דומה לסיסמה"
+    }
+  },
+  "selectable_list": {
+    "select_all": "בחר הכל"
   },
   "settings": {
+    "app_name": "שם האפליקציה",
     "attachmentRadius": "צירופים",
     "attachments": "צירופים",
     "autoload": "החל טעינה אוטומטית בגלילה לתחתית הדף",
@@ -79,6 +146,12 @@
     "avatarRadius": "תמונות פרופיל",
     "background": "רקע",
     "bio": "אודות",
+    "block_export": "ייצוא חסימות",
+    "block_export_button": "ייצוא חסימות אל קובץ csv",
+    "block_import": "ייבוא חסימות",
+    "block_import_error": "שגיאה בייבוא החסימות",
+    "blocks_imported": "החסימות יובאו! ייקח מעט זמן לעבד אותן.",
+    "blocks_tab": "חסימות",
     "btnRadius": "כפתורים",
     "cBlue": "כחול (תגובה, עקיבה)",
     "cGreen": "ירוק (חזרה)",
@@ -88,6 +161,7 @@
     "change_password_error": "הייתה בעיה בשינוי סיסמתך.",
     "changed_password": "סיסמה שונתה בהצלחה!",
     "collapse_subject": "מזער הודעות עם נושאים",
+    "composing": "מרכיב",
     "confirm_new_password": "אשר סיסמה",
     "current_avatar": "תמונת הפרופיל הנוכחית שלך",
     "current_password": "סיסמה נוכחית",
@@ -98,21 +172,35 @@
     "delete_account_description": "מחק לצמיתות את המשתמש שלך ואת כל הודעותיך.",
     "delete_account_error": "הייתה בעיה במחיקת המשתמש. אם זה ממשיך, אנא עדכן את מנהל השרת שלך.",
     "delete_account_instructions": "הכנס את סיסמתך בקלט למטה על מנת לאשר מחיקת משתמש.",
+    "avatar_size_instruction": "הגודל המינימלי המומלץ לתמונות פרופיל הוא 150x150 פיקסלים.",
     "export_theme": "שמור ערכים",
     "filtering": "סינון",
     "filtering_explanation": "כל הסטטוסים הכוללים את המילים הללו יושתקו, אחד לשורה",
     "follow_export": "יצוא עקיבות",
     "follow_export_button": "ייצא את הנעקבים שלך לקובץ csv",
-    "follow_export_processing": "טוען. בקרוב תתבקש להוריד את הקובץ את הקובץ שלך",
     "follow_import": "יבוא עקיבות",
     "follow_import_error": "שגיאה בייבוא נעקבים.",
     "follows_imported": "נעקבים יובאו! ייקח זמן מה לעבד אותם.",
     "foreground": "חזית",
+    "general": "כללי",
     "hide_attachments_in_convo": "החבא צירופים בשיחות",
     "hide_attachments_in_tl": "החבא צירופים בציר הזמן",
+    "hide_muted_posts": "הסתר הודעות של משתמשים מושתקים",
+    "max_thumbnails": "מספר מירבי של תמונות ממוזערות להודעה",
+    "hide_isp": "הסתר פאנל-צד",
+    "preload_images": "טען תמונות מראש",
+    "use_one_click_nsfw": "פתח תמונות לא-בטוחות-לעבודה עם לחיצה אחת בלבד",
+    "hide_post_stats": "הסתר נתוני הודעה (למשל, מספר החזרות)",
+    "hide_user_stats": "הסתר נתוני משתמש (למשל, מספר העוקבים)",
+    "hide_filtered_statuses": "מסתר סטטוסים מסוננים",
+    "import_blocks_from_a_csv_file": "ייבא חסימות מקובץ csv",
     "import_followers_from_a_csv_file": "ייבא את הנעקבים שלך מקובץ csv",
     "import_theme": "טען ערכים",
     "inputRadius": "שדות קלט",
+    "checkboxRadius": "תיבות סימון",
+    "instance_default": "(default: {value})",
+    "instance_default_simple": "(default)",
+    "interface": "ממשק",
     "interfaceLanguage": "שפת הממשק",
     "invalid_theme_imported": "הקובץ הנבחר אינו תמה הנתמכת ע\"י פלרומה. שום שינויים לא נעשו לתמה שלך.",
     "limited_availability": "לא זמין בדפדפן שלך",
@@ -120,6 +208,9 @@
     "lock_account_description": "הגבל את המשתמש לעוקבים מאושרים בלבד",
     "loop_video": "נגן סרטונים ללא הפסקה",
     "loop_video_silent_only": "נגן רק סרטונים חסרי קול ללא הפסקה",
+    "mutes_tab": "השתקות",
+    "play_videos_in_modal": "נגן סרטונים ישירות בנגן המדיה",
+    "use_contain_fit": "אל תחתוך את הצירוף בתמונות הממוזערות",
     "name": "שם",
     "name_bio": "שם ואודות",
     "new_password": "סיסמה חדשה",
@@ -128,6 +219,13 @@
     "notification_visibility_likes": "לייקים",
     "notification_visibility_mentions": "אזכורים",
     "notification_visibility_repeats": "חזרות",
+    "no_rich_text_description": "הסר פורמט טקסט עשיר מכל ההודעות",
+    "no_blocks": "ללא חסימות",
+    "no_mutes": "ללא השתקות",
+    "hide_follows_description": "אל תראה אחרי מי אני עוקב",
+    "hide_followers_description": "אל תראה מי עוקב אחרי",
+    "show_admin_badge": "הראה סמל מנהל בפרופיל שלי",
+    "show_moderator_badge": "הראה סמל צוות בפרופיל שלי",
     "nsfw_clickthrough": "החל החבאת צירופים לא בטוחים לצפיה בעת עבודה בעזרת לחיצת עכבר",
     "oauth_tokens": "אסימוני OAuth",
     "token": "אסימון",
@@ -146,18 +244,43 @@
     "reply_visibility_all": "הראה את כל התגובות",
     "reply_visibility_following": "הראה תגובות שמופנות אליי או לעקובים שלי בלבד",
     "reply_visibility_self": "הראה תגובות שמופנות אליי בלבד",
+    "autohide_floating_post_button": "החבא אוטומטית את הכפתור הודעה חדשה (נייד)",
+    "saving_err": "שגיאה בשמירת הגדרות",
+    "saving_ok": "הגדרות נשמרו",
+    "search_user_to_block": "חפש משתמש לחסימה",
+    "search_user_to_mute": "חפש משתמש להשתקה",
     "security_tab": "ביטחון",
+    "scope_copy": "העתק תחום הודעה בתגובה להודעה (הודעות ישירות תמיד מועתקות)",
+    "minimal_scopes_mode": "צמצם אפשרויות בחירה לתחום הודעה",
     "set_new_avatar": "קבע תמונת פרופיל חדשה",
     "set_new_profile_background": "קבע רקע פרופיל חדש",
     "set_new_profile_banner": "קבע כרזת פרופיל חדשה",
     "settings": "הגדרות",
+    "subject_input_always_show": "תמיד הראה את שדה הנושא",
+    "subject_line_behavior": "העתק נושא בתגובה",
+    "subject_line_email": "כמו אימייל: \"re: נושא\"",
+    "subject_line_mastodon": "כמו מסטודון: העתק כפי שזה",
+    "subject_line_noop": "אל תעתיק",
+    "post_status_content_type": "שלח את סוג תוכן ההודעה",
     "stop_gifs": "נגן-בעת-ריחוף GIFs",
     "streaming": "החל זרימת הודעות אוטומטית בעת גלילה למעלה הדף",
     "text": "טקסט",
     "theme": "תמה",
     "theme_help": "השתמש בקודי צבע הקס (#אדום-אדום-ירוק-ירוק-כחול-כחול) על מנת להתאים אישית את תמת הצבע שלך.",
     "tooltipRadius": "טולטיפ \\ התראות",
-    "user_settings": "הגדרות משתמש"
+    "upload_a_photo": "העלה תמונה",
+    "user_settings": "הגדרות משתמש",
+    "values": {
+      "false": "לא",
+      "true": "כן"
+    },
+    "notifications": "התראות",
+    "enable_web_push_notifications": "אפשר התראות web push",
+    "version": {
+      "title": "גרסה",
+      "backend_version": "גרסת קצה אחורי",
+      "frontend_version": "גרסת קצה קדמי"
+    }
   },
   "timeline": {
     "collapse": "מוטט",
@@ -167,29 +290,107 @@
     "no_retweet_hint": "ההודעה מסומנת כ\"לעוקבים-בלבד\" ולא ניתן לחזור עליה",
     "repeated": "חזר",
     "show_new": "הראה חדש",
-    "up_to_date": "עדכני"
+    "up_to_date": "עדכני",
+    "no_more_statuses": "אין עוד סטטוסים",
+    "no_statuses": "אין סטטוסים"
+  },
+  "status": {
+    "favorites": "מועדפים",
+    "repeats": "חזרות",
+    "delete": "מחק סטטוס",
+    "pin": "הצמד לפרופיל",
+    "unpin": "הסר הצמדה מהפרופיל",
+    "pinned": "מוצמד",
+    "delete_confirm": "האם באמת למחוק סטטוס זה?",
+    "reply_to": "הגב ל",
+    "replies_list": "תגובות:"
   },
   "user_card": {
     "approve": "אשר",
     "block": "חסימה",
     "blocked": "חסום!",
     "deny": "דחה",
+    "favorites": "מועדפים",
     "follow": "עקוב",
+    "follow_sent": "בקשה נשלחה!",
+    "follow_progress": "מבקש...",
+    "follow_again": "שלח בקשה שוב?",
+    "follow_unfollow": "בטל עקיבה",
     "followees": "נעקבים",
     "followers": "עוקבים",
     "following": "עוקב!",
     "follows_you": "עוקב אחריך!",
+    "its_you": "זה אתה!",
+    "media": "מדיה",
     "mute": "השתק",
     "muted": "מושתק",
     "per_day": "ליום",
     "remote_follow": "עקיבה מרחוק",
-    "statuses": "סטטוסים"
+    "report": "דווח",
+    "statuses": "סטטוסים",
+    "unblock": "הסר חסימה",
+    "unblock_progress": "מסיר חסימה...",
+    "block_progress": "חוסם...",
+    "unmute": "הסר השתקה",
+    "unmute_progress": "מסיר השתקה...",
+    "mute_progress": "משתיק...",
+    "admin_menu": {
+      "moderation": "ניהול (צוות)",
+      "grant_admin": "הפוך למנהל",
+      "revoke_admin": "הסר מנהל",
+      "grant_moderator": "הפוך לצוות",
+      "revoke_moderator": "הסר צוות",
+      "activate_account": "הפעל משתמש",
+      "deactivate_account": "השבת משתמש",
+      "delete_account": "מחק משתמש",
+      "force_nsfw": "סמן את כל ההודעות בתור לא-מתאימות-לעבודה",
+      "strip_media": "הסר מדיה מההודעות",
+      "force_unlisted": "הפוך הודעות ללא רשומות",
+      "sandbox": "הפוך הודעות לנראות לעוקבים-בלבד",
+      "disable_remote_subscription": "אל תאפשר עקיבה של המשתמש מאינסטנס אחר",
+      "disable_any_subscription": "אל תאפשר עקיבה של המשתמש בכלל",
+      "quarantine": "אל תאפשר פדרציה של ההודעות של המשתמש",
+      "delete_user": "מחק משתמש",
+      "delete_user_confirmation": "בטוח? פעולה זו הינה בלתי הפיכה."
+    }
   },
   "user_profile": {
-    "timeline_title": "ציר זמן המשתמש"
+    "timeline_title": "ציר זמן המשתמש",
+    "profile_does_not_exist": "סליחה, פרופיל זה אינו קיים.",
+    "profile_loading_error": "סליחה, הייתה שגיאה בטעינת הפרופיל."
+  },
+  "user_reporting": {
+    "title": "מדווח על {0}",
+    "add_comment_description": "הדיווח ישלח לצוות האינסטנס. אפשר להסביר למה הנך מדווחים על משתמש זה למטה:",
+    "additional_comments": "תגובות נוספות",
+    "forward_description": "המשתמש משרת אחר. לשלוח לשם עותק של הדיווח?",
+    "forward_to": "העבר ל {0}",
+    "submit": "הגש",
+    "generic_error": "קרתה שגיאה בעת עיבוד הבקשה."
   },
   "who_to_follow": {
     "more": "עוד",
     "who_to_follow": "אחרי מי לעקוב"
+  },
+  "tool_tip": {
+    "media_upload": "העלה מדיה",
+    "repeat": "חזור",
+    "reply": "הגב",
+    "favorite": "מועדף",
+    "user_settings": "הגדרות משתמש"
+  },
+  "upload":{
+    "error": {
+      "base": "העלאה נכשלה.",
+      "file_too_big": "קובץ גדול מדי [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+      "default": "נסה שוב אחר כך"
+    },
+    "file_size_units": {
+      "B": "B",
+      "KiB": "KiB",
+      "MiB": "MiB",
+      "GiB": "GiB",
+      "TiB": "TiB"
+    }
   }
 }
diff --git a/src/i18n/ja.json b/src/i18n/ja.json
index b77f5531..c77f28a6 100644
--- a/src/i18n/ja.json
+++ b/src/i18n/ja.json
@@ -2,6 +2,10 @@
   "chat": {
     "title": "チャット"
   },
+  "exporter": {
+    "export": "エクスポート",
+    "processing": "おまちください。しばらくすると、あなたのファイルをダウンロードするように、メッセージがでます。"
+  },
   "features_panel": {
     "chat": "チャット",
     "gopher": "Gopher",
@@ -19,7 +23,26 @@
     "apply": "てきよう",
     "submit": "そうしん",
     "more": "つづき",
-    "generic_error": "エラーになりました"
+    "generic_error": "エラーになりました",
+    "optional": "かかなくてもよい",
+    "show_more": "つづきをみる",
+    "show_less": "たたむ",
+    "cancel": "キャンセル",
+    "disable": "なし",
+    "enable": "あり",
+    "confirm": "たしかめる",
+    "verify": "たしかめる"
+  },
+  "image_cropper": {
+    "crop_picture": "がぞうをきりぬく",
+    "save": "セーブ",
+    "save_without_cropping": "きりぬかずにセーブ",
+    "cancel": "キャンセル"
+  },
+  "importer": {
+    "submit": "そうしん",
+    "success": "インポートできました。",
+    "error": "インポートがエラーになりました。"
   },
   "login": {
     "login": "ログイン",
@@ -29,7 +52,19 @@
     "placeholder": "れい: lain",
     "register": "はじめる",
     "username": "ユーザーめい",
-    "hint": "はなしあいにくわわるには、ログインしてください"
+    "hint": "はなしあいにくわわるには、ログインしてください",
+    "authentication_code": "にんしょうコード",
+    "enter_recovery_code": "リカバリーコードをいれてください",
+    "enter_two_factor_code": "2-ファクターコードをいれてください",
+    "recovery_code": "リカバリーコード",
+    "heading" : {
+      "totp" : "2-ファクターにんしょう",
+      "recovery" : "2-ファクターリカバリー"
+    }
+  },
+  "media_modal": {
+    "previous": "まえ",
+    "next": "つぎ"
   },
   "nav": {
     "about": "これはなに?",
@@ -37,6 +72,7 @@
     "chat": "ローカルチャット",
     "friend_requests": "フォローリクエスト",
     "mentions": "メンション",
+    "interactions": "やりとり",
     "dms": "ダイレクトメッセージ",
     "public_tl": "パブリックタイムライン",
     "timeline": "タイムライン",
@@ -55,18 +91,47 @@
     "repeated_you": "あなたのステータスがリピートされました",
     "no_more_notifications": "つうちはありません"
   },
+  "polls": {
+    "add_poll": "いれふだをはじめる",
+    "add_option": "オプションをふやす",
+    "option": "オプション",
+    "votes": "いれふだ",
+    "vote": "ふだをいれる",
+    "type": "いれふだのかた",
+    "single_choice": "ひとつえらぶ",
+    "multiple_choices": "いくつでもえらべる",
+    "expiry": "いれふだのながさ",
+    "expires_in": "いれふだは {0} で、おわります",
+    "expired": "いれふだは {0} まえに、おわりました",
+    "not_enough_options": "ユニークなオプションが、たりません"
+  },
+  "interactions": {
+    "favs_repeats": "リピートとおきにいり",
+    "follows": "あたらしいフォロー",
+    "load_older": "ふるいやりとりをみる"
+  },
   "post_status": {
     "new_status": "とうこうする",
     "account_not_locked_warning": "あなたのアカウントは {0} ではありません。あなたをフォローすれば、だれでも、フォロワーげんていのステータスをよむことができます。",
     "account_not_locked_warning_link": "ロックされたアカウント",
     "attachments_sensitive": "ファイルをNSFWにする",
     "content_type": {
-      "text/plain": "プレーンテキスト"
+      "text/plain": "プレーンテキスト",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
     },
     "content_warning": "せつめい (かかなくてもよい)",
     "default": "はねだくうこうに、つきました。",
+    "direct_warning_to_all": "このとうこうは、メンションされたすべてのユーザーが、みることができます。",
+    "direct_warning_to_first_only": "このとうこうは、メッセージのはじめでメンションされたユーザーだけが、みることができます。",
     "direct_warning": "このステータスは、メンションされたユーザーだけが、よむことができます。",
     "posting": "とうこう",
+    "scope_notice": {
+      "public": "このとうこうは、だれでもみることができます",
+      "private": "このとうこうは、あなたのフォロワーだけが、みることができます",
+      "unlisted": "このとうこうは、パブリックタイムラインと、つながっているすべてのネットワークでは、みることができません"
+    },
     "scope": {
       "direct": "ダイレクト: メンションされたユーザーのみにとどきます。",
       "private": "フォロワーげんてい: フォロワーのみにとどきます。",
@@ -83,6 +148,9 @@
     "token": "しょうたいトークン",
     "captcha": "CAPTCHA",
     "new_captcha": "もじがよめないときは、がぞうをクリックすると、あたらしいがぞうになります",
+    "username_placeholder": "れい: lain",
+    "fullname_placeholder": "れい: いわくら れいん",
+    "bio_placeholder": "れい:\nごきげんよう。わたしはれいん。\nわたしはアニメのおんなのこで、にほんのベッドタウンにすんでいます。ワイヤードで、わたしにあったことが、あるかもしれませんね。",
     "validations": {
       "username_required": "なにかかいてください",
       "fullname_required": "なにかかいてください",
@@ -92,7 +160,34 @@
       "password_confirmation_match": "パスワードがちがいます"
     }
   },
+  "selectable_list": {
+    "select_all": "すべてえらぶ"
+  },
   "settings": {
+    "app_name": "アプリのなまえ",
+    "security": "セキュリティ",
+    "enter_current_password_to_confirm": "あなたのアイデンティティをたしかめるため、あなたのいまのパスワードをかいてください",
+    "mfa": {
+      "otp" : "OTP",
+      "setup_otp" : "OTPをつくる",
+      "wait_pre_setup_otp" : "OTPをよういしています",
+      "confirm_and_enable" : "OTPをたしかめて、ゆうこうにする",
+      "title": "2-ファクターにんしょう",
+      "generate_new_recovery_codes" : "あたらしいリカバリーコードをつくる",
+      "warning_of_generate_new_codes" : "あたらしいリカバリーコードをつくったら、ふるいコードはつかえなくなります。",
+      "recovery_codes" : "リカバリーコード。",
+      "waiting_a_recovery_codes": "バックアップコードをうけとっています...",
+      "recovery_codes_warning" : "コードをかきうつすか、ひとにみられないところにセーブしてください。そうでなければ、あなたはこのコードをふたたびみることはできません。もしあなたが、2FAアプリのアクセスをうしなって、なおかつ、リカバリーコードもおもいだせないならば、あなたはあなたのアカウントから、しめだされます。",
+      "authentication_methods" : "にんしょうメソッド",
+      "scan": {
+        "title": "スキャン",
+        "desc": "あなたの2-ファクターアプリをつかって、このQRコードをスキャンするか、テキストキーをうちこんでください:",
+        "secret_code": "キー"
+      },
+      "verify": {
+        "desc": "2-ファクターにんしょうをつかうには、あなたの2-ファクターアプリのコードをいれてください:"
+      }
+    },
     "attachmentRadius": "ファイル",
     "attachments": "ファイル",
     "autoload": "したにスクロールしたとき、じどうてきによみこむ。",
@@ -101,6 +196,12 @@
     "avatarRadius": "アバター",
     "background": "バックグラウンド",
     "bio": "プロフィール",
+    "block_export": "ブロックのエクスポート",
+    "block_export_button": "ブロックをCSVファイルにエクスポート",
+    "block_import": "ブロックのインポート",
+    "block_import_error": "ブロックのインポートがエラーになりました",
+    "blocks_imported": "ブロックをインポートしました! じっさいにブロックするまでには、もうしばらくかかります。",
+    "blocks_tab": "ブロック",
     "btnRadius": "ボタン",
     "cBlue": "リプライとフォロー",
     "cGreen": "リピート",
@@ -135,12 +236,15 @@
     "general": "ぜんぱん",
     "hide_attachments_in_convo": "スレッドのファイルをかくす",
     "hide_attachments_in_tl": "タイムラインのファイルをかくす",
+    "hide_muted_posts": "ミュートしたユーザーのとうこうをかくす",
+    "max_thumbnails": "ひとつのとうこうにいれられるサムネイルのかず",
     "hide_isp": "インスタンススペシフィックパネルをかくす",
     "preload_images": "がぞうをさきよみする",
     "use_one_click_nsfw": "NSFWなファイルを1クリックでひらく",
     "hide_post_stats": "とうこうのとうけいをかくす (れい: おきにいりのかず)",
     "hide_user_stats": "ユーザーのとうけいをかくす (れい: フォロワーのかず)",
     "hide_filtered_statuses": "フィルターされたとうこうをかくす",
+    "import_blocks_from_a_csv_file": "CSVファイルからブロックをインポートする",
     "import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
     "import_theme": "ロード",
     "inputRadius": "インプットフィールド",
@@ -155,6 +259,7 @@
     "lock_account_description": "あなたがみとめたひとだけ、あなたのアカウントをフォローできる",
     "loop_video": "ビデオをくりかえす",
     "loop_video_silent_only": "おとのないビデオだけくりかえす",
+    "mutes_tab": "ミュート",
     "play_videos_in_modal": "ビデオをメディアビューアーでみる",
     "use_contain_fit": "がぞうのサムネイルを、きりぬかない",
     "name": "なまえ",
@@ -166,16 +271,18 @@
     "notification_visibility_mentions": "メンション",
     "notification_visibility_repeats": "リピート",
     "no_rich_text_description": "リッチテキストをつかわない",
+    "no_blocks": "ブロックしていません",
+    "no_mutes": "ミュートしていません",
     "hide_follows_description": "フォローしているひとをみせない",
     "hide_followers_description": "フォロワーをみせない",
-    "show_admin_badge": "アドミンのしるしをみる",
-    "show_moderator_badge": "モデレーターのしるしをみる",
+    "show_admin_badge": "アドミンのしるしをみせる",
+    "show_moderator_badge": "モデレーターのしるしをみせる",
     "nsfw_clickthrough": "NSFWなファイルをかくす",
     "oauth_tokens": "OAuthトークン",
     "token": "トークン",
-    "refresh_token": "トークンを更新",
-    "valid_until": "まで有効",
-    "revoke_token": "取り消す",
+    "refresh_token": "トークンをリフレッシュ",
+    "valid_until": "おわりのとき",
+    "revoke_token": "とりけす",
     "panelRadius": "パネル",
     "pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる",
     "presets": "プリセット",
@@ -188,10 +295,14 @@
     "reply_visibility_all": "すべてのリプライをみる",
     "reply_visibility_following": "わたしにあてられたリプライと、フォローしているひとからのリプライをみる",
     "reply_visibility_self": "わたしにあてられたリプライをみる",
+    "autohide_floating_post_button": "あたらしいとうこうのボタンを、じどうてきにかくす (モバイル)",
     "saving_err": "せっていをセーブできませんでした",
     "saving_ok": "せっていをセーブしました",
+    "search_user_to_block": "ブロックしたいひとを、ここでけんさくできます",
+    "search_user_to_mute": "ミュートしたいひとを、ここでけんさくできます",
     "security_tab": "セキュリティ",
     "scope_copy": "リプライするとき、こうかいはんいをコピーする (DMのこうかいはんいは、つねにコピーされます)",
+    "minimal_scopes_mode": "こうかいはんいせんたくオプションを、ちいさくする",
     "set_new_avatar": "あたらしいアバターをせっていする",
     "set_new_profile_background": "あたらしいプロフィールのバックグラウンドをせっていする",
     "set_new_profile_banner": "あたらしいプロフィールバナーを設定する",
@@ -209,6 +320,7 @@
     "theme_help": "カラーテーマをカスタマイズできます",
     "theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、いろと、とうめいどを、オーバーライドできます。「すべてクリア」ボタンをおすと、すべてのオーバーライドを、やめます。",
     "theme_help_v2_2": "バックグラウンドとテキストのコントラストをあらわすアイコンがあります。マウスをホバーすると、くわしいせつめいがでます。とうめいないろをつかっているときは、もっともわるいばあいのコントラストがしめされます。",
+    "upload_a_photo": "がぞうをアップロード",
     "tooltipRadius": "ツールチップとアラート",
     "user_settings": "ユーザーせってい",
     "values": {
@@ -216,6 +328,13 @@
       "true": "はい"
     },
     "notifications": "つうち",
+    "notification_setting": "つうちをうけとる:",
+    "notification_setting_follows": "あなたがフォローしているひとから",
+    "notification_setting_non_follows": "あなたがフォローしていないひとから",
+    "notification_setting_followers": "あなたをフォローしているひとから",
+    "notification_setting_non_followers": "あなたをフォローしていないひとから",
+    "notification_mutes": "あるユーザーからのつうちをとめるには、ミュートしてください。",
+    "notification_blocks": "ブロックしているユーザーからのつうちは、すべてとまります。",
     "enable_web_push_notifications": "ウェブプッシュつうちをゆるす",
     "style": {
       "switcher": {
@@ -325,8 +444,47 @@
         "checkbox": "りようきやくを、よみました",
         "link": "ハイパーリンク"
       }
+    },
+    "version": {
+      "title": "バージョン",
+      "backend_version": "バックエンドのバージョン",
+      "frontend_version": "フロントエンドのバージョン"
     }
   },
+  "time": {
+    "day": "{0}日",
+    "days": "{0}日",
+    "day_short": "{0}日",
+    "days_short": "{0}日",
+    "hour": "{0}時間",
+    "hours": "{0}時間",
+    "hour_short": "{0}時間",
+    "hours_short": "{0}時間",
+    "in_future": "{0}で",
+    "in_past": "{0}前",
+    "minute": "{0}分",
+    "minutes": "{0}分",
+    "minute_short": "{0}分",
+    "minutes_short": "{0}分",
+    "month": "{0}ヶ月前",
+    "months": "{0}ヶ月前",
+    "month_short": "{0}ヶ月前",
+    "months_short": "{0}ヶ月前",
+    "now": "たった今",
+    "now_short": "たった今",
+    "second": "{0}秒",
+    "seconds": "{0}秒",
+    "second_short": "{0}秒",
+    "seconds_short": "{0}秒",
+    "week": "{0}週間",
+    "weeks": "{0}週間",
+    "week_short": "{0}週間",
+    "weeks_short": "{0}週間",
+    "year": "{0}年",
+    "years": "{0}年",
+    "year_short": "{0}年",
+    "years_short": "{0}年"
+  },
   "timeline": {
     "collapse": "たたむ",
     "conversation": "スレッド",
@@ -336,7 +494,19 @@
     "repeated": "リピート",
     "show_new": "よみこみ",
     "up_to_date": "さいしん",
-    "no_more_statuses": "これでおわりです"
+    "no_more_statuses": "これでおわりです",
+    "no_statuses": "ありません"
+  },
+  "status": {
+    "favorites": "おきにいり",
+    "repeats": "リピート",
+    "delete": "ステータスをけす",
+    "pin": "プロフィールにピンどめする",
+    "unpin": "プロフィールにピンどめするのをやめる",
+    "pinned": "ピンどめ",
+    "delete_confirm": "ほんとうに、このステータスを、けしてもいいですか?",
+    "reply_to": "へんしん:",
+    "replies_list": "へんしん:"
   },
   "user_card": {
     "approve": "うけいれ",
@@ -359,10 +529,47 @@
     "muted": "ミュートしています!",
     "per_day": "/日",
     "remote_follow": "リモートフォロー",
-    "statuses": "ステータス"
+    "report": "つうほう",
+    "statuses": "ステータス",
+    "unblock": "ブロックをやめる",
+    "unblock_progress": "ブロックをとりけしています...",
+    "block_progress": "ブロックしています...",
+    "unmute": "ミュートをやめる",
+    "unmute_progress": "ミュートをとりけしています...",
+    "mute_progress": "ミュートしています...",
+    "admin_menu": {
+      "moderation": "モデレーション",
+      "grant_admin": "アドミンにする",
+      "revoke_admin": "アドミンをやめさせる",
+      "grant_moderator": "モデレーターにする",
+      "revoke_moderator": "モデレーターをやめさせる",
+      "activate_account": "アカウントをアクティブにする",
+      "deactivate_account": "アカウントをアクティブでなくする",
+      "delete_account": "アカウントをけす",
+      "force_nsfw": "すべてのとうこうをNSFWにする",
+      "strip_media": "とうこうからメディアをなくす",
+      "force_unlisted": "とうこうをアンリステッドにする",
+      "sandbox": "とうこうをフォロワーのみにする",
+      "disable_remote_subscription": "ほかのインスタンスからフォローされないようにする",
+      "disable_any_subscription": "フォローされないようにする",
+      "quarantine": "ほかのインスタンスのユーザーのとうこうをとめる",
+      "delete_user": "ユーザーをけす",
+      "delete_user_confirmation": "あなたは、ほんとうに、きはたしかですか? これは、とりけすことが、できません。"
+    }
   },
   "user_profile": {
-    "timeline_title": "ユーザータイムライン"
+    "timeline_title": "ユーザータイムライン",
+    "profile_does_not_exist": "ごめんなさい。このプロフィールは、そんざいしません。",
+    "profile_loading_error": "ごめんなさい。プロフィールのロードがエラーになりました。"
+  },
+  "user_reporting": {
+    "title": "つうほうする: {0}",
+    "add_comment_description": "このつうほうは、あなたのインスタンスのモデレーターに、おくられます。このアカウントを、つうほうするりゆうを、せつめいすることができます:",
+    "additional_comments": "ついかのコメント",
+    "forward_description": "このアカウントは、ほかのインスタンスのものです。そのインスタンスにも、このつうほうのコピーを、おくりますか?",
+    "forward_to": "コピーをおくる: {0}",
+    "submit": "そうしん",
+    "generic_error": "あなたのリクエストをうけつけようとしましたが、エラーになってしまいました。"
   },
   "who_to_follow": {
     "more": "くわしく",
diff --git a/src/i18n/ja_pedantic.json b/src/i18n/ja_pedantic.json
new file mode 100644
index 00000000..992a2fa6
--- /dev/null
+++ b/src/i18n/ja_pedantic.json
@@ -0,0 +1,599 @@
+{
+  "chat": {
+    "title": "チャット"
+  },
+  "exporter": {
+    "export": "エクスポート",
+    "processing": "処理中です。処理が完了すると、ファイルをダウンロードするよう指示があります。"
+  },
+  "features_panel": {
+    "chat": "チャット",
+    "gopher": "Gopher",
+    "media_proxy": "メディアプロクシ",
+    "scope_options": "公開範囲選択",
+    "text_limit": "文字の数",
+    "title": "有効な機能",
+    "who_to_follow": "おすすめユーザー"
+  },
+  "finder": {
+    "error_fetching_user": "ユーザー検索がエラーになりました。",
+    "find_user": "ユーザーを探す"
+  },
+  "general": {
+    "apply": "適用",
+    "submit": "送信",
+    "more": "続き",
+    "generic_error": "エラーになりました",
+    "optional": "省略可",
+    "show_more": "もっと見る",
+    "show_less": "たたむ",
+    "cancel": "キャンセル",
+    "disable": "無効",
+    "enable": "有効",
+    "confirm": "確認",
+    "verify": "検査"
+  },
+  "image_cropper": {
+    "crop_picture": "画像を切り抜く",
+    "save": "保存",
+    "save_without_cropping": "切り抜かずに保存",
+    "cancel": "キャンセル"
+  },
+  "importer": {
+    "submit": "送信",
+    "success": "正常にインポートされました。",
+    "error": "このファイルをインポートするとき、エラーが発生しました。"
+  },
+  "login": {
+    "login": "ログイン",
+    "description": "OAuthでログイン",
+    "logout": "ログアウト",
+    "password": "パスワード",
+    "placeholder": "例: lain",
+    "register": "登録",
+    "username": "ユーザー名",
+    "hint": "会話に加わるには、ログインしてください",
+    "authentication_code": "認証コード",
+    "enter_recovery_code": "リカバリーコードを入力してください",
+    "enter_two_factor_code": "2段階認証コードを入力してください",
+    "recovery_code": "リカバリーコード",
+    "heading" : {
+      "totp" : "2段階認証",
+      "recovery" : "2段階リカバリー"
+    }
+  },
+  "media_modal": {
+    "previous": "前",
+    "next": "次"
+  },
+  "nav": {
+    "about": "このインスタンスについて",
+    "back": "戻る",
+    "chat": "ローカルチャット",
+    "friend_requests": "フォローリクエスト",
+    "mentions": "通知",
+    "interactions": "インタラクション",
+    "dms": "ダイレクトメッセージ",
+    "public_tl": "パブリックタイムライン",
+    "timeline": "タイムライン",
+    "twkn": "接続しているすべてのネットワーク",
+    "user_search": "ユーザーを探す",
+    "who_to_follow": "おすすめユーザー",
+    "preferences": "設定"
+  },
+  "notifications": {
+    "broken_favorite": "ステータスが見つかりません。探しています...",
+    "favorited_you": "あなたのステータスがお気に入りされました",
+    "followed_you": "フォローされました",
+    "load_older": "古い通知をみる",
+    "notifications": "通知",
+    "read": "読んだ!",
+    "repeated_you": "あなたのステータスがリピートされました",
+    "no_more_notifications": "通知はありません"
+  },
+  "polls": {
+    "add_poll": "投票を追加",
+    "add_option": "選択肢を追加",
+    "option": "選択肢",
+    "votes": "票",
+    "vote": "投票",
+    "type": "投票の形式",
+    "single_choice": "択一式",
+    "multiple_choices": "複数選択式",
+    "expiry": "投票期間",
+    "expires_in": "投票は {0} で終了します",
+    "expired": "投票は {0} 前に終了しました",
+    "not_enough_options": "相異なる選択肢が不足しています"
+  },
+  "interactions": {
+    "favs_repeats": "リピートとお気に入り",
+    "follows": "新しいフォロワー",
+    "load_older": "古いインタラクションを見る"
+  },
+  "post_status": {
+    "new_status": "投稿する",
+    "account_not_locked_warning": "あなたのアカウントは {0} ではありません。あなたをフォローすれば、誰でも、フォロワー限定のステータスを読むことができます。",
+    "account_not_locked_warning_link": "ロックされたアカウント",
+    "attachments_sensitive": "ファイルをNSFWにする",
+    "content_type": {
+      "text/plain": "プレーンテキスト",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
+    },
+    "content_warning": "説明 (省略可)",
+    "default": "羽田空港に着きました。",
+    "direct_warning_to_all": "この投稿は、メンションされたすべてのユーザーが、見ることができます。",
+    "direct_warning_to_first_only": "この投稿は、メッセージの冒頭でメンションされたユーザーだけが、見ることができます。",
+    "direct_warning": "このステータスは、メンションされたユーザーだけが、読むことができます。",
+    "posting": "投稿",
+    "scope_notice": {
+      "public": "この投稿は、誰でも見ることができます",
+      "private": "この投稿は、あなたのフォロワーだけが、見ることができます。",
+      "unlisted": "この投稿は、パブリックタイムラインと、接続しているすべてのネットワークには、表示されません。"
+    },
+    "scope": {
+      "direct": "ダイレクト: メンションされたユーザーのみに届きます。",
+      "private": "フォロワーげんてい: フォロワーのみに届きます。",
+      "public": "パブリック: パブリックタイムラインに届きます。",
+      "unlisted": "アンリステッド: パブリックタイムラインに届きません。"
+    }
+  },
+  "registration": {
+    "bio": "プロフィール",
+    "email": "Eメール",
+    "fullname": "スクリーンネーム",
+    "password_confirm": "パスワードの確認",
+    "registration": "登録",
+    "token": "招待トークン",
+    "captcha": "CAPTCHA",
+    "new_captcha": "文字が読めないときは、画像をクリックすると、新しい画像になります",
+    "username_placeholder": "例: lain",
+    "fullname_placeholder": "例: 岩倉玲音",
+    "bio_placeholder": "例:\nこんにちは。私は玲音。\n私はアニメのキャラクターで、日本の郊外に住んでいます。私をWiredで見たことがあるかもしれません。",
+    "validations": {
+      "username_required": "必須",
+      "fullname_required": "必須",
+      "email_required": "必須",
+      "password_required": "必須",
+      "password_confirmation_required": "必須",
+      "password_confirmation_match": "パスワードが違います"
+    }
+  },
+  "selectable_list": {
+    "select_all": "すべて選択"
+  },
+  "settings": {
+    "app_name": "アプリの名称",
+    "security": "セキュリティ",
+    "enter_current_password_to_confirm": "あなたのアイデンティティを証明するため、現在のパスワードを入力してください",
+    "mfa": {
+      "otp" : "OTP",
+      "setup_otp" : "OTPのセットアップ",
+      "wait_pre_setup_otp" : "OTPのプリセット",
+      "confirm_and_enable" : "OTPの確認と有効化",
+      "title": "2段階認証",
+      "generate_new_recovery_codes" : "新しいリカバリーコードを生成",
+      "warning_of_generate_new_codes" : "新しいリカバリーコードを生成すると、古いコードは使用できなくなります。",
+      "recovery_codes" : "リカバリーコード。",
+      "waiting_a_recovery_codes": "バックアップコードを受信しています...",
+      "recovery_codes_warning" : "コードを紙に書くか、安全な場所に保存してください。そうでなければ、あなたはコードを再び見ることはできません。もし2段階認証アプリのアクセスを喪失し、なおかつ、リカバリーコードもないならば、あなたは自分のアカウントから閉め出されます。",
+      "authentication_methods" : "認証方法",
+      "scan": {
+        "title": "スキャン",
+        "desc": "あなたの2段階認証アプリを使って、このQRコードをスキャンするか、テキストキーを入力してください:",
+        "secret_code": "キー"
+      },
+      "verify": {
+        "desc": "2段階認証を有効にするには、あなたの2段階認証アプリのコードを入力してください:"
+      }
+    },
+    "attachmentRadius": "ファイル",
+    "attachments": "ファイル",
+    "autoload": "下にスクロールしたとき、自動的に読み込む。",
+    "avatar": "アバター",
+    "avatarAltRadius": "通知のアバター",
+    "avatarRadius": "アバター",
+    "background": "バックグラウンド",
+    "bio": "プロフィール",
+    "block_export": "ブロックのエクスポート",
+    "block_export_button": "ブロックをCSVファイルにエクスポートする",
+    "block_import": "ブロックのインポート",
+    "block_import_error": "ブロックのインポートに失敗しました",
+    "blocks_imported": "ブロックをインポートしました! 実際に処理されるまでに、しばらく時間がかかります。",
+    "blocks_tab": "ブロック",
+    "btnRadius": "ボタン",
+    "cBlue": "返信とフォロー",
+    "cGreen": "リピート",
+    "cOrange": "お気に入り",
+    "cRed": "キャンセル",
+    "change_password": "パスワードを変える",
+    "change_password_error": "パスワードを変えることが、できなかったかもしれません。",
+    "changed_password": "パスワードが、変わりました!",
+    "collapse_subject": "説明のある投稿をたたむ",
+    "composing": "投稿",
+    "confirm_new_password": "新しいパスワードの確認",
+    "current_avatar": "現在のアバター",
+    "current_password": "現在のパスワード",
+    "current_profile_banner": "現在のプロフィールバナー",
+    "data_import_export_tab": "インポートとエクスポート",
+    "default_vis": "デフォルトの公開範囲",
+    "delete_account": "アカウントを消す",
+    "delete_account_description": "あなたのアカウントとメッセージが、消えます。",
+    "delete_account_error": "アカウントを消すことが、できなかったかもしれません。インスタンスの管理者に、連絡してください。",
+    "delete_account_instructions": "本当にアカウントを消してもいいなら、パスワードを入力してください。",
+    "avatar_size_instruction": "アバターの大きさは、150×150ピクセルか、それよりも大きくするといいです。",
+    "export_theme": "保存",
+    "filtering": "フィルタリング",
+    "filtering_explanation": "これらの言葉を含むすべてのものがミュートされます。1行に1つの言葉を書いてください。",
+    "follow_export": "フォローのエクスポート",
+    "follow_export_button": "エクスポート",
+    "follow_export_processing": "お待ちください。まもなくファイルをダウンロードできます。",
+    "follow_import": "フォローのインポート",
+    "follow_import_error": "フォローのインポートがエラーになりました。",
+    "follows_imported": "フォローがインポートされました! 少し時間がかかるかもしれません。",
+    "foreground": "フォアグラウンド",
+    "general": "全般",
+    "hide_attachments_in_convo": "スレッドのファイルを隠す",
+    "hide_attachments_in_tl": "タイムラインのファイルを隠す",
+    "hide_muted_posts": "ミュートしているユーザーの投稿を隠す",
+    "max_thumbnails": "投稿に含まれるサムネイルの最大数",
+    "hide_isp": "インスタンス固有パネルを隠す",
+    "preload_images": "画像を先読みする",
+    "use_one_click_nsfw": "NSFWなファイルを1クリックで開く",
+    "hide_post_stats": "投稿の統計を隠す (例: お気に入りの数)",
+    "hide_user_stats": "ユーザーの統計を隠す (例: フォロワーの数)",
+    "hide_filtered_statuses": "フィルターされた投稿を隠す",
+    "import_blocks_from_a_csv_file": "CSVファイルからブロックをインポートする",
+    "import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
+    "import_theme": "ロード",
+    "inputRadius": "インプットフィールド",
+    "checkboxRadius": "チェックボックス",
+    "instance_default": "(デフォルト: {value})",
+    "instance_default_simple": "(デフォルト)",
+    "interface": "インターフェース",
+    "interfaceLanguage": "インターフェースの言語",
+    "invalid_theme_imported": "このファイルはPleromaのテーマではありません。テーマは変更されませんでした。",
+    "limited_availability": "あなたのブラウザではできません",
+    "links": "リンク",
+    "lock_account_description": "あなたが認めた人だけ、あなたのアカウントをフォローできる",
+    "loop_video": "ビデオを繰り返す",
+    "loop_video_silent_only": "音のないビデオだけ繰り返す",
+    "mutes_tab": "ミュート",
+    "play_videos_in_modal": "ビデオをメディアビューアーで見る",
+    "use_contain_fit": "画像のサムネイルを、切り抜かない",
+    "name": "名前",
+    "name_bio": "名前とプロフィール",
+    "new_password": "新しいパスワード",
+    "notification_visibility": "表示する通知",
+    "notification_visibility_follows": "フォロー",
+    "notification_visibility_likes": "お気に入り",
+    "notification_visibility_mentions": "メンション",
+    "notification_visibility_repeats": "リピート",
+    "no_rich_text_description": "リッチテキストを使わない",
+    "no_blocks": "ブロックはありません",
+    "no_mutes": "ミュートはありません",
+    "hide_follows_description": "フォローしている人を見せない",
+    "hide_followers_description": "フォロワーを見せない",
+    "show_admin_badge": "管理者のバッジを見せる",
+    "show_moderator_badge": "モデレーターのバッジを見せる",
+    "nsfw_clickthrough": "NSFWなファイルを隠す",
+    "oauth_tokens": "OAuthトークン",
+    "token": "トークン",
+    "refresh_token": "トークンを更新",
+    "valid_until": "まで有効",
+    "revoke_token": "取り消す",
+    "panelRadius": "パネル",
+    "pause_on_unfocused": "タブにフォーカスがないときストリーミングを止める",
+    "presets": "プリセット",
+    "profile_background": "プロフィールのバックグラウンド",
+    "profile_banner": "プロフィールバナー",
+    "profile_tab": "プロフィール",
+    "radii_help": "インターフェースの丸さを設定する。",
+    "replies_in_timeline": "タイムラインのリプライ",
+    "reply_link_preview": "カーソルを重ねたとき、リプライのプレビューを見る",
+    "reply_visibility_all": "すべてのリプライを見る",
+    "reply_visibility_following": "私に宛てられたリプライと、フォローしている人からのリプライを見る",
+    "reply_visibility_self": "私に宛てられたリプライを見る",
+    "autohide_floating_post_button": "新しい投稿ボタンを自動的に隠す (モバイル)",
+    "saving_err": "設定を保存できませんでした",
+    "saving_ok": "設定を保存しました",
+    "search_user_to_block": "ブロックしたいユーザーを検索",
+    "search_user_to_mute": "ミュートしたいユーザーを検索",
+    "security_tab": "セキュリティ",
+    "scope_copy": "返信するとき、公開範囲をコピーする (DMの公開範囲は、常にコピーされます)",
+    "minimal_scopes_mode": "公開範囲選択オプションを最小にする",
+    "set_new_avatar": "新しいアバターを設定する",
+    "set_new_profile_background": "新しいプロフィールのバックグラウンドを設定する",
+    "set_new_profile_banner": "新しいプロフィールバナーを設定する",
+    "settings": "設定",
+    "subject_input_always_show": "サブジェクトフィールドをいつでも表示する",
+    "subject_line_behavior": "返信するときサブジェクトをコピーする",
+    "subject_line_email": "メール風: \"re: サブジェクト\"",
+    "subject_line_mastodon": "マストドン風: そのままコピー",
+    "subject_line_noop": "コピーしない",
+    "post_status_content_type": "投稿のコンテントタイプ",
+    "stop_gifs": "カーソルを重ねたとき、GIFを動かす",
+    "streaming": "上までスクロールしたとき、自動的にストリーミングする",
+    "text": "文字",
+    "theme": "テーマ",
+    "theme_help": "カラーテーマをカスタマイズできます",
+    "theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、色と透明度をオーバーライドできます。「すべてクリア」ボタンを押すと、すべてのオーバーライドをやめます。",
+    "theme_help_v2_2": "バックグラウンドとテキストのコントラストを表すアイコンがあります。マウスをホバーすると、詳しい説明が出ます。透明な色を使っているときは、最悪の場合のコントラストが示されます。",
+    "tooltipRadius": "ツールチップとアラート",
+    "upload_a_photo": "画像をアップロード",
+    "user_settings": "ユーザー設定",
+    "values": {
+      "false": "いいえ",
+      "true": "はい"
+    },
+    "notifications": "通知",
+    "notification_setting": "通知を受け取る:",
+    "notification_setting_follows": "あなたがフォローしているユーザーから",
+    "notification_setting_non_follows": "あなたがフォローしていないユーザーから",
+    "notification_setting_followers": "あなたをフォローしているユーザーから",
+    "notification_setting_non_followers": "あなたをフォローしていないユーザーから",
+    "notification_mutes": "特定のユーザーからの通知を止めるには、ミュートしてください。",
+    "notification_blocks": "ブロックしているユーザーからの通知は、すべて止まります。",
+    "enable_web_push_notifications": "ウェブプッシュ通知を許可する",
+    "style": {
+      "switcher": {
+        "keep_color": "色を残す",
+        "keep_shadows": "影を残す",
+        "keep_opacity": "透明度を残す",
+        "keep_roundness": "丸さを残す",
+        "keep_fonts": "フォントを残す",
+        "save_load_hint": "「残す」オプションをONにすると、テーマを選んだときとロードしたとき、現在の設定を残します。また、テーマをエクスポートするとき、これらのオプションを維持します。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべての設定を保存します。",
+        "reset": "リセット",
+        "clear_all": "すべてクリア",
+        "clear_opacity": "透明度をクリア"
+      },
+      "common": {
+        "color": "色",
+        "opacity": "透明度",
+        "contrast": {
+          "hint": "コントラストは {ratio} です。{level}。({context})",
+          "level": {
+            "aa": "AAレベルガイドライン (ミニマル) を満たします",
+            "aaa": "AAAレベルガイドライン (レコメンデッド) を満たします。",
+            "bad": "ガイドラインを満たしません。"
+          },
+          "context": {
+            "18pt": "大きい (18ポイント以上) テキスト",
+            "text": "テキスト"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "共通",
+        "main": "共通の色",
+        "foreground_hint": "「詳細」タブで、もっと細かく設定できます",
+        "rgbo": "アイコンとアクセントとバッジ"
+      },
+      "advanced_colors": {
+        "_tab_label": "詳細",
+        "alert": "アラートのバックグラウンド",
+        "alert_error": "エラー",
+        "badge": "バッジのバックグラウンド",
+        "badge_notification": "通知",
+        "panel_header": "パネルヘッダー",
+        "top_bar": "トップバー",
+        "borders": "境界",
+        "buttons": "ボタン",
+        "inputs": "インプットフィールド",
+        "faint_text": "薄いテキスト"
+      },
+      "radii": {
+        "_tab_label": "丸さ"
+      },
+      "shadows": {
+        "_tab_label": "光と影",
+        "component": "コンポーネント",
+        "override": "オーバーライド",
+        "shadow_id": "影 #{value}",
+        "blur": "ぼかし",
+        "spread": "広がり",
+        "inset": "内側",
+        "hint": "影の設定では、色の値として --variable を使うことができます。これはCSS3変数です。ただし、透明度の設定は、効かなくなります。",
+        "filter_hint": {
+          "always_drop_shadow": "ブラウザーがサポートしていれば、常に {0} が使われます。",
+          "drop_shadow_syntax": "{0} は、{1} パラメーターと {2} キーワードをサポートしていません。",
+          "avatar_inset": "内側の影と外側の影を同時に使うと、透明なアバターの表示が乱れます。",
+          "spread_zero": "広がりが 0 よりも大きな影は、0 と同じです。",
+          "inset_classic": "内側の影は {0} を使います。"
+        },
+        "components": {
+          "panel": "パネル",
+          "panelHeader": "パネルヘッダー",
+          "topBar": "トップバー",
+          "avatar": "ユーザーアバター (プロフィール)",
+          "avatarStatus": "ユーザーアバター (投稿)",
+          "popup": "ポップアップとツールチップ",
+          "button": "ボタン",
+          "buttonHover": "ボタン (ホバー)",
+          "buttonPressed": "ボタン (押されているとき)",
+          "buttonPressedHover": "ボタン (ホバー、かつ、押されているとき)",
+          "input": "インプットフィールド"
+        }
+      },
+      "fonts": {
+        "_tab_label": "フォント",
+        "help": "「カスタム」を選んだときは、システムにあるフォントの名前を、正しく入力してください。",
+        "components": {
+          "interface": "インターフェース",
+          "input": "インプットフィールド",
+          "post": "投稿",
+          "postCode": "等幅 (投稿がリッチテキストであるとき)"
+        },
+        "family": "フォント名",
+        "size": "大きさ (px)",
+        "weight": "太さ",
+        "custom": "カスタム"
+      },
+      "preview": {
+        "header": "プレビュー",
+        "content": "本文",
+        "error": "エラーの例",
+        "button": "ボタン",
+        "text": "これは{0}と{1}の例です。",
+        "mono": "monospace",
+        "input": "羽田空港に着きました。",
+        "faint_link": "とても助けになるマニュアル",
+        "fine_print": "私たちの{0}を、読まないでください!",
+        "header_faint": "エラーではありません",
+        "checkbox": "利用規約を読みました",
+        "link": "ハイパーリンク"
+      }
+    },
+    "version": {
+      "title": "バージョン",
+      "backend_version": "バックエンドのバージョン",
+      "frontend_version": "フロントエンドのバージョン"
+    }
+  },
+  "time": {
+    "day": "{0}日",
+    "days": "{0}日",
+    "day_short": "{0}日",
+    "days_short": "{0}日",
+    "hour": "{0}時間",
+    "hours": "{0}時間",
+    "hour_short": "{0}時間",
+    "hours_short": "{0}時間",
+    "in_future": "{0}で",
+    "in_past": "{0}前",
+    "minute": "{0}分",
+    "minutes": "{0}分",
+    "minute_short": "{0}分",
+    "minutes_short": "{0}分",
+    "month": "{0}ヶ月前",
+    "months": "{0}ヶ月前",
+    "month_short": "{0}ヶ月前",
+    "months_short": "{0}ヶ月前",
+    "now": "たった今",
+    "now_short": "たった今",
+    "second": "{0}秒",
+    "seconds": "{0}秒",
+    "second_short": "{0}秒",
+    "seconds_short": "{0}秒",
+    "week": "{0}週間",
+    "weeks": "{0}週間",
+    "week_short": "{0}週間",
+    "weeks_short": "{0}週間",
+    "year": "{0}年",
+    "years": "{0}年",
+    "year_short": "{0}年",
+    "years_short": "{0}年"
+  },
+  "timeline": {
+    "collapse": "たたむ",
+    "conversation": "スレッド",
+    "error_fetching": "読み込みがエラーになりました",
+    "load_older": "古いステータス",
+    "no_retweet_hint": "投稿を「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります",
+    "repeated": "リピート",
+    "show_new": "読み込み",
+    "up_to_date": "最新",
+    "no_more_statuses": "これで終わりです",
+    "no_statuses": "ステータスはありません"
+  },
+  "status": {
+    "favorites": "お気に入り",
+    "repeats": "リピート",
+    "delete": "ステータスを削除",
+    "pin": "プロフィールにピン留め",
+    "unpin": "プロフィールのピン留めを外す",
+    "pinned": "ピン留め",
+    "delete_confirm": "本当にこのステータスを削除してもよろしいですか?",
+    "reply_to": "返信",
+    "replies_list": "返信:"
+  },
+  "user_card": {
+    "approve": "受け入れ",
+    "block": "ブロック",
+    "blocked": "ブロックしています!",
+    "deny": "お断り",
+    "favorites": "お気に入り",
+    "follow": "フォロー",
+    "follow_sent": "リクエストを送りました!",
+    "follow_progress": "リクエストしています…",
+    "follow_again": "再びリクエストを送りますか?",
+    "follow_unfollow": "フォローをやめる",
+    "followees": "フォロー",
+    "followers": "フォロワー",
+    "following": "フォローしています!",
+    "follows_you": "フォローされました!",
+    "its_you": "これはあなたです!",
+    "media": "メディア",
+    "mute": "ミュート",
+    "muted": "ミュートしています!",
+    "per_day": "/日",
+    "remote_follow": "リモートフォロー",
+    "report": "通報",
+    "statuses": "ステータス",
+    "unblock": "ブロック解除",
+    "unblock_progress": "ブロックを解除しています...",
+    "block_progress": "ブロックしています...",
+    "unmute": "ミュート解除",
+    "unmute_progress": "ミュートを解除しています...",
+    "mute_progress": "ミュートしています...",
+    "admin_menu": {
+      "moderation": "モデレーション",
+      "grant_admin": "管理者権限を付与",
+      "revoke_admin": "管理者権限を解除",
+      "grant_moderator": "モデレーター権限を付与",
+      "revoke_moderator": "モデレーター権限を解除",
+      "activate_account": "アカウントをアクティブにする",
+      "deactivate_account": "アカウントをアクティブでなくする",
+      "delete_account": "アカウントを削除",
+      "force_nsfw": "すべての投稿をNSFWにする",
+      "strip_media": "投稿からメディアを除去する",
+      "force_unlisted": "投稿を未収載にする",
+      "sandbox": "投稿をフォロワーのみにする",
+      "disable_remote_subscription": "他のインスタンスからフォローされないようにする",
+      "disable_any_subscription": "フォローされないようにする",
+      "quarantine": "他のインスタンスからの投稿を止める",
+      "delete_user": "ユーザーを削除",
+      "delete_user_confirmation": "あなたの精神状態に何か問題はございませんか? この操作を取り消すことはできません。"
+    }
+  },
+  "user_profile": {
+    "timeline_title": "ユーザータイムライン",
+    "profile_does_not_exist": "申し訳ない。このプロフィールは存在しません。",
+    "profile_loading_error": "申し訳ない。プロフィールの読み込みがエラーになりました。"
+  },
+  "user_reporting": {
+    "title": "通報する: {0}",
+    "add_comment_description": "この通報は、あなたのインスタンスのモデレーターに送られます。このアカウントを通報する理由を説明することができます:",
+    "additional_comments": "追加のコメント",
+    "forward_description": "このアカウントは他のサーバーに置かれています。この通報のコピーをリモートのサーバーに送りますか?",
+    "forward_to": "転送する: {0}",
+    "submit": "送信",
+    "generic_error": "あなたのリクエストを処理しようとしましたが、エラーになりました。"
+  },
+  "who_to_follow": {
+    "more": "詳細",
+    "who_to_follow": "おすすめユーザー"
+  },
+  "tool_tip": {
+    "media_upload": "メディアをアップロード",
+    "repeat": "リピート",
+    "reply": "返信",
+    "favorite": "お気に入り",
+    "user_settings": "ユーザー設定"
+  },
+  "upload":{
+    "error": {
+    "base": "アップロードに失敗しました。",
+    "file_too_big": "ファイルが大きすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
+    "default": "しばらくしてから試してください"
+    },
+    "file_size_units": {
+      "B": "B",
+      "KiB": "KiB",
+      "MiB": "MiB",
+      "GiB": "GiB",
+      "TiB": "TiB"
+    }
+  }
+}
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index ab697948..404a4079 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -23,6 +23,7 @@ const messages = {
   hu: require('./hu.json'),
   it: require('./it.json'),
   ja: require('./ja.json'),
+  ja_pedantic: require('./ja_pedantic.json'),
   ko: require('./ko.json'),
   nb: require('./nb.json'),
   nl: require('./nl.json'),
diff --git a/src/i18n/oc.json b/src/i18n/oc.json
index 9214799d..6100a4d2 100644
--- a/src/i18n/oc.json
+++ b/src/i18n/oc.json
@@ -2,6 +2,10 @@
   "chat": {
     "title": "Messatjariá"
   },
+  "exporter": {
+     "export": "Exportar",
+     "processing": "Tractament, vos demandarem lèu de telecargar lo fichièr"
+  },  
     "features_panel": {
       "chat": "Chat",
       "gopher": "Gopher",
@@ -20,13 +24,21 @@
     "submit": "Mandar",
     "more": "Mai",
     "generic_error": "Una error s’es producha",
-    "optional": "opcional"
+    "optional": "opcional",
+    "show_more": "Mostrar mai",
+    "show_less": "Mostrar mens",
+    "cancel": "Anullar"
   },
   "image_cropper": {
      "crop_picture": "Talhar l’imatge",
      "save": "Salvar",
      "save_without_cropping": "Salvar sens talhada",
      "cancel": "Anullar"
+  },
+    "importer": {
+    "submit": "Mandar",
+    "success": "Corrèctament importat.",
+    "error": "Una error s’es producha pendent l’importacion d’aqueste fichièr."
   },
   "login": {
     "login": "Connexion",
@@ -66,6 +78,20 @@
     "repeated_you": "a repetit vòstre estatut",
     "no_more_notifications": "Pas mai de notificacions"
   },
+  "polls": {
+"add_poll": "Ajustar un sondatge",
+    "add_option": "Ajustar d’opcions",
+	"option": "Opcion",
+	"votes": "vòtes",
+	"vote": "Votar",
+	"type": "Tipe de sondatge",
+	"single_choice": "Causida unica",
+	"multiple_choices": "Causida multipla",
+	"expiry": "Durada del sondatge",
+	"expires_in": "Lo sondatge s’acabarà {0}",
+	"expired": "Sondatge acabat {0}",
+	"not_enough_options": "I a pas pro d’opcions"
+	},  
   "post_status": {
     "new_status": "Publicar d’estatuts novèls",
     "account_not_locked_warning": "Vòstre compte es pas {0}. Qual que siá pòt vos seguir per veire vòstras publicacions destinadas pas qu’a vòstres seguidors.",
@@ -74,11 +100,13 @@
     "content_type": {
       "text/plain": "Tèxte brut",
       "text/html": "HTML",
-      "text/markdown": "Markdown"
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
     },
     "content_warning": "Avís de contengut (opcional)",
     "default": "Escrivètz aquí vòstre estatut.",
-    "direct_warning": "Aquesta publicacion serà pas que visibla pels utilizaires mencionats.",
+    "direct_warning_to_all": "Aquesta publicacion serà pas que visibla pels utilizaires mencionats.",
+    "direct_warning_to_first_only": "Aquesta publicacion serà pas que visibla pels utilizaires mencionats a la debuta del messatge.",
     "posting": "Mandadís",
     "scope": {
       "direct": "Dirècte - Publicar pels utilizaires mencionats solament",
@@ -108,6 +136,9 @@
       "password_confirmation_match": "deu èsser lo meteis senhal"
     }
   },
+  "selectable_list": {
+	    "select_all": "O seleccionar tot"
+  },  
   "settings": {
     "app_name": "Nom de l’aplicacion",
     "attachmentRadius": "Pèças juntas",
@@ -118,6 +149,11 @@
     "avatarRadius": "Avatars",
     "background": "Rèire plan",
     "bio": "Biografia",
+    "block_export": "Exportar los blocatges",
+    "block_export_button": "Exportar los blocatges dins un fichièr csv",
+    "block_import": "Impòrt de blocatges",
+    "block_import_error": "Error en importar los blocatges",
+    "blocks_imported": "Blocatges importats ! Lo tractament tardarà un pauc.",
     "blocks_tab": "Blocatges",
     "btnRadius": "Botons",
     "cBlue": "Blau (Respondre, seguir)",
@@ -145,7 +181,6 @@
     "filtering_explanation": "Totes los estatuts amb aqueles mots seràn en silenci, un mot per linha",
     "follow_export": "Exportar los abonaments",
     "follow_export_button": "Exportar vòstres abonaments dins un fichièr csv",
-    "follow_export_processing": "Tractament, vos demandarem lèu de telecargar lo fichièr",
     "follow_import": "Importar los abonaments",
     "follow_import_error": "Error en important los seguidors",
     "follows_imported": "Seguidors importats. Lo tractament pòt trigar una estona.",
@@ -176,10 +211,12 @@
     "loop_video": "Bocla vidèo",
     "loop_video_silent_only": "Legir en bocla solament las vidèos sens son (coma los « Gifs » de Mastodon)",
     "mutes_tab": "Agamats",
+    "interactions_tab": "Interaccions",
     "play_videos_in_modal": "Legir las vidèos dirèctament dins la visualizaira mèdia",
     "use_contain_fit": "Talhar pas las pèças juntas per las vinhetas",
     "name": "Nom",
     "name_bio": "Nom & Bio",
+
     "new_password": "Nòu senhal",
     "notification_visibility_follows": "Abonaments",
     "notification_visibility_likes": "Aimar",
@@ -213,8 +250,11 @@
     "reply_visibility_self": "Mostrar pas que las responsas que me son destinadas",
     "saving_err": "Error en enregistrant los paramètres",
     "saving_ok": "Paramètres enregistrats",
-    "scope_copy": "Copiar lo nivèl de confidencialitat per las responsas (Totjorn aissí pels Messatges Dirèctes)",
+    "search_user_to_block": "Cercatz qual volètz blocar",
+    "search_user_to_mute": "Cercatz qual volètz rescondre",
     "security_tab": "Seguretat",
+    "scope_copy": "Copiar lo nivèl de confidencialitat per las responsas (Totjorn aissí pels Messatges Dirèctes)",
+    "minimal_scopes_mode": "Minimizar lo nombre d’opcions per publicacion",
     "set_new_avatar": "Definir un nòu avatar",
     "set_new_profile_background": "Definir un nòu fons de perfil",
     "set_new_profile_banner": "Definir una nòva bandièra de perfil",
@@ -239,8 +279,15 @@
       "false": "non",
       "true": "òc"
     },
- "notifications": "Notificacions",
-    "enable_web_push_notifications": "Activar las notificacions web push",
+    "notifications": "Notificacions",
+    "notification_setting": "Receber las notificacions de :",
+    "notification_setting_follows": "Utilizaires que seguissètz",
+    "notification_setting_non_follows": "Utilizaires que seguissètz pas",
+    "notification_setting_followers": "Utilizaires que vos seguisson",
+    "notification_setting_non_followers": "Utilizaires que vos seguisson pas",
+    "notification_mutes": "Per receber pas mai d’un utilizaire en particular, botatz-lo en silenci.",
+    "notification_blocks": "Blocar un utilizaire arrèsta totas las notificacions tan coma quitar de los seguir.",
+	"enable_web_push_notifications": "Activar las notificacions web push",
     "style": {
       "switcher": {
         "keep_color": "Gardar las colors",
@@ -349,8 +396,47 @@
         "checkbox": "Ai legit los tèrmes e condicions d’utilizacion",
         "link": "un pichon ligam simpatic"
       }
+    },
+    "version": {
+      "title": "Version",
+      "backend_version": "Version Backend",
+      "frontend_version": "Version Frontend"
     }
   },
+  "time": {
+    "day": "{0} jorn",
+    "days": "{0} jorns",
+    "day_short": "{0} jorn",
+    "days_short": "{0} jorns",
+    "hour": "{0} ora",
+    "hours": "{0} oras",
+    "hour_short": "{0}h",
+    "hours_short": "{0}h",
+    "in_future": "d’aquí {0}",
+    "in_past": "fa {0}",
+    "minute": "{0} minuta",
+    "minutes": "{0} minutas",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} mes",
+    "months": "{0} meses",
+    "month_short": "{0} mes",
+    "months_short": "{0} meses",
+    "now": "ara meteis",
+    "now_short": "ara meteis",
+    "second": "{0} segonda",
+    "seconds": "{0} segondas",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} setmana.",
+    "weeks": "{0} setmanas.",
+    "week_short": "{0} setm.",
+    "weeks_short": "{0} setm.",
+    "year": "{0} an",
+    "years": "{0} ans",
+    "year_short": "{0} an",
+    "years_short": "{0} ans"
+  },
   "timeline": {
     "collapse": "Tampar",
     "conversation": "Conversacion",
@@ -364,6 +450,8 @@
     "no_statuses": "Cap d’estatuts"
   },
   "status": {
+    "favorites": "Li a agradat",
+    "repeats": "A repetit",
     "reply_to": "Respond a",
     "replies_list": "Responsas :"
   },
@@ -394,7 +482,26 @@
     "block_progress": "Blocatge...",
     "unmute": "Tornar mostrar",
     "unmute_progress": "Afichatge...",
-    "mute_progress": "A amagar..."
+    "mute_progress": "A amagar...",
+    "admin_menu": {
+      "moderation": "Moderacion",
+      "grant_admin": "Passar Admin",
+      "revoke_admin": "Revocar Admin",
+      "grant_moderator": "Passar Moderator",
+      "revoke_moderator": "Revocar Moderator",
+      "activate_account": "Activar lo compte",
+      "deactivate_account": "Desactivar lo compte",
+      "delete_account": "Suprimir lo compte",
+      "force_nsfw": "Marcar totas las publicacions coma sensiblas",
+      "strip_media": "Tirar los mèdias de las publicacions",
+      "force_unlisted": "Forçar las publicacions en pas-listadas",
+      "sandbox": "Forçar las publicacions en seguidors solament",
+      "disable_remote_subscription": "Desactivar lo seguiment d’utilizaire d’instàncias alonhadas",
+      "disable_any_subscription": "Desactivar tot seguiment",
+      "quarantine": "Defendre la federacion de las publicacions de l’utilizaire",
+      "delete_user": "Suprimir l’utilizaire",
+      "delete_user_confirmation": "Volètz vertadièrament far aquò ? Aquesta accion se pòt pas anullar."
+    }    
   },
   "user_profile": {
     "timeline_title": "Flux utilizaire",
diff --git a/src/i18n/pl.json b/src/i18n/pl.json
index 8efce168..51cadfb6 100644
--- a/src/i18n/pl.json
+++ b/src/i18n/pl.json
@@ -74,7 +74,8 @@
     "content_type": {
       "text/plain": "Czysty tekst",
       "text/html": "HTML",
-      "text/markdown": "Markdown"
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
     },
     "content_warning": "Temat (nieobowiązkowy)",
     "default": "Właśnie wróciłem z kościoła",
@@ -362,7 +363,7 @@
     "error_fetching": "Błąd pobierania",
     "load_older": "Załaduj starsze statusy",
     "no_retweet_hint": "Wpis oznaczony jako tylko dla obserwujących lub bezpośredni nie może zostać powtórzony",
-    "repeated": "powtórzono",
+    "repeated": "powtórzył(-a)",
     "show_new": "Pokaż nowe",
     "up_to_date": "Na bieżąco",
     "no_more_statuses": "Brak kolejnych statusów",
@@ -431,4 +432,4 @@
       "TiB": "TiB"
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 89aa43f4..90ed6664 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -8,7 +8,12 @@
   },
   "general": {
     "apply": "Применить",
-    "submit": "Отправить"
+    "submit": "Отправить",
+    "cancel": "Отмена",
+    "disable": "Оключить",
+    "enable": "Включить",
+    "confirm": "Подтвердить",
+    "verify": "Проверить"
   },
   "login": {
     "login": "Войти",
@@ -16,15 +21,25 @@
     "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": "Назад",
     "chat": "Локальный чат",
     "mentions": "Упоминания",
+    "interactions": "Взаимодействия",
     "public_tl": "Публичная лента",
     "timeline": "Лента",
-    "twkn": "Федеративная лента"
+    "twkn": "Федеративная лента",
+    "search": "Поиск"
   },
   "notifications": {
     "broken_favorite": "Неизвестный статус, ищем...",
@@ -35,14 +50,24 @@
     "read": "Прочесть",
     "repeated_you": "повторил(а) ваш статус"
   },
+  "interactions": {
+    "favs_repeats": "Повторы и фавориты",
+    "follows": "Новые подписки",
+    "load_older": "Загрузить старые взаимодействия"
+  },
   "post_status": {
     "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков",
     "account_not_locked_warning_link": "залочен",
     "attachments_sensitive": "Вложения содержат чувствительный контент",
     "content_warning": "Тема (не обязательно)",
     "default": "Что нового?",
-    "direct_warning": "Этот пост будет видет только упомянутым пользователям",
+    "direct_warning": "Этот пост будет виден только упомянутым пользователям",
     "posting": "Отправляется",
+    "scope_notice": {
+      "public": "Этот пост будет виден всем",
+      "private": "Этот пост будет виден только вашим подписчикам",
+      "unlisted": "Этот пост не будет виден в публичной и федеративной ленте"
+    },
     "scope": {
       "direct": "Личное - этот пост видят только те кто в нём упомянут",
       "private": "Для подписчиков - этот пост видят только подписчики",
@@ -67,6 +92,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": "Включить автоматическую загрузку при прокрутке вниз",
@@ -151,6 +198,7 @@
     "reply_visibility_all": "Показывать все ответы",
     "reply_visibility_following": "Показывать только ответы мне и тех на кого я подписан",
     "reply_visibility_self": "Показывать только ответы мне",
+    "autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)",
     "saving_err": "Не удалось сохранить настройки",
     "saving_ok": "Сохранено",
     "security_tab": "Безопасность",
@@ -311,9 +359,35 @@
     "muted": "Игнорирую",
     "per_day": "в день",
     "remote_follow": "Читать удалённо",
-    "statuses": "Статусы"
+    "statuses": "Статусы",
+    "admin_menu": {
+      "moderation": "Опции модератора",
+      "grant_admin": "Сделать администратором",
+      "revoke_admin": "Забрать права администратора",
+      "grant_moderator": "Сделать модератором",
+      "revoke_moderator": "Забрать права модератора",
+      "activate_account": "Активировать аккаунт",
+      "deactivate_account": "Деактивировать аккаунт",
+      "delete_account": "Удалить аккаунт",
+      "force_nsfw": "Отмечать посты пользователя как NSFW",
+      "strip_media": "Убирать вложения из постов пользователя",
+      "force_unlisted": "Не добавлять посты в публичные ленты",
+      "sandbox": "Посты доступны только для подписчиков",
+      "disable_remote_subscription": "Запретить подписываться с удаленных серверов",
+      "disable_any_subscription": "Запретить подписываться на пользователя",
+      "quarantine": "Не федерировать посты пользователя",
+      "delete_user": "Удалить пользователя",
+      "delete_user_confirmation": "Вы уверены? Это действие нельзя отменить."
+    }
   },
   "user_profile": {
     "timeline_title": "Лента пользователя"
+  },
+  "search": {
+    "people": "Люди",
+    "hashtags": "Хэштэги",
+    "person_talking": "Популярно у {count} человека",
+    "people_talking": "Популярно у {count} человек",
+    "no_results": "Ничего не найдено"
   }
 }
diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js
index 7ab89c12..cad7ea25 100644
--- a/src/lib/persisted_state.js
+++ b/src/lib/persisted_state.js
@@ -19,7 +19,8 @@ const saveImmedeatelyActions = [
   'setHighlight',
   'setOption',
   'setClientData',
-  'setToken'
+  'setToken',
+  'clearToken'
 ]
 
 const defaultStorage = (() => {
diff --git a/src/main.js b/src/main.js
index 9ffc3727..b3256e8e 100644
--- a/src/main.js
+++ b/src/main.js
@@ -10,10 +10,12 @@ 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'
+import pollsModule from './modules/polls.js'
 
-import VueTimeago from 'vue-timeago'
 import VueI18n from 'vue-i18n'
 
 import createPersistedState from './lib/persisted_state.js'
@@ -22,6 +24,9 @@ import pushNotifications from './lib/push_notifications_plugin.js'
 import messages from './i18n/messages.js'
 
 import VueChatScroll from 'vue-chat-scroll'
+import VueClickOutside from 'v-click-outside'
+import PortalVue from 'portal-vue'
+import VTooltip from 'v-tooltip'
 
 import afterStoreSetup from './boot/after_store.js'
 
@@ -29,16 +34,11 @@ const currentLocale = (window.navigator.language || 'en').split('-')[0]
 
 Vue.use(Vuex)
 Vue.use(VueRouter)
-Vue.use(VueTimeago, {
-  locale: currentLocale === 'cs' ? 'cs' : currentLocale === 'ja' ? 'ja' : 'en',
-  locales: {
-    'cs': require('../static/timeago-cs.json'),
-    'en': require('../static/timeago-en.json'),
-    'ja': require('../static/timeago-ja.json')
-  }
-})
 Vue.use(VueI18n)
 Vue.use(VueChatScroll)
+Vue.use(VueClickOutside)
+Vue.use(PortalVue)
+Vue.use(VTooltip)
 
 const i18n = new VueI18n({
   // By default, use the browser locale, we will update it if neccessary
@@ -59,6 +59,11 @@ const persistedStateOptions = {
   const persistedState = await createPersistedState(persistedStateOptions)
   const store = new Vuex.Store({
     modules: {
+      i18n: {
+        getters: {
+          i18n: () => i18n
+        }
+      },
       interface: interfaceModule,
       instance: instanceModule,
       statuses: statusesModule,
@@ -67,8 +72,11 @@ const persistedStateOptions = {
       config: configModule,
       chat: chatModule,
       oauth: oauthModule,
+      authFlow: authFlowModule,
       mediaViewer: mediaViewerModule,
-      oauthTokens: oauthTokensModule
+      oauthTokens: oauthTokensModule,
+      reports: reportsModule,
+      polls: pollsModule
     },
     plugins: [persistedState, pushNotifications],
     strict: false // Socket modifies itself, let's ignore this for now.
diff --git a/src/modules/api.js b/src/modules/api.js
index 31cb55c6..d51b31f3 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -13,11 +13,11 @@ const api = {
     setBackendInteractor (state, backendInteractor) {
       state.backendInteractor = backendInteractor
     },
-    addFetcher (state, {timeline, fetcher}) {
-      state.fetchers[timeline] = fetcher
+    addFetcher (state, { fetcherName, fetcher }) {
+      state.fetchers[fetcherName] = fetcher
     },
-    removeFetcher (state, {timeline}) {
-      delete state.fetchers[timeline]
+    removeFetcher (state, { fetcherName }) {
+      delete state.fetchers[fetcherName]
     },
     setWsToken (state, token) {
       state.wsToken = token
@@ -33,17 +33,24 @@ const api = {
     }
   },
   actions: {
-    startFetching (store, {timeline = 'friends', tag = false, userId = false}) {
+    startFetchingTimeline (store, { timeline = 'friends', tag = false, userId = false }) {
       // Don't start fetching if we already are.
       if (store.state.fetchers[timeline]) return
 
-      const fetcher = store.state.backendInteractor.startFetching({ timeline, store, userId, tag })
-      store.commit('addFetcher', { timeline, fetcher })
+      const fetcher = store.state.backendInteractor.startFetchingTimeline({ timeline, store, userId, tag })
+      store.commit('addFetcher', { fetcherName: timeline, fetcher })
     },
-    stopFetching (store, timeline) {
-      const fetcher = store.state.fetchers[timeline]
+    startFetchingNotifications (store) {
+      // Don't start fetching if we already are.
+      if (store.state.fetchers['notifications']) return
+
+      const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
+      store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
+    },
+    stopFetching (store, fetcherName) {
+      const fetcher = store.state.fetchers[fetcherName]
       window.clearInterval(fetcher)
-      store.commit('removeFetcher', {timeline})
+      store.commit('removeFetcher', { fetcherName })
     },
     setWsToken (store, token) {
       store.commit('setWsToken', token)
@@ -52,7 +59,7 @@ const api = {
       // Set up websocket connection
       if (!store.state.chatDisabled) {
         const token = store.state.wsToken
-        const socket = new Socket('/socket', {params: {token}})
+        const socket = new Socket('/socket', { params: { token } })
         socket.connect()
         store.dispatch('initializeChat', socket)
       }
diff --git a/src/modules/auth_flow.js b/src/modules/auth_flow.js
new file mode 100644
index 00000000..d0a90feb
--- /dev/null
+++ b/src/modules/auth_flow.js
@@ -0,0 +1,90 @@
+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 = {
+  // eslint-disable-next-line camelcase
+  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/chat.js b/src/modules/chat.js
index 2804e577..e1b03bca 100644
--- a/src/modules/chat.js
+++ b/src/modules/chat.js
@@ -1,7 +1,7 @@
 const chat = {
   state: {
     messages: [],
-    channel: {state: ''},
+    channel: { state: '' },
     socket: null
   },
   mutations: {
@@ -21,7 +21,7 @@ const chat = {
   },
   actions: {
     disconnectFromChat (store) {
-      store.state.socket.disconnect()
+      store.state.socket && store.state.socket.disconnect()
     },
     initializeChat (store, socket) {
       const channel = socket.channel('chat:public')
@@ -29,7 +29,7 @@ const chat = {
       channel.on('new_msg', (msg) => {
         store.commit('addMessage', msg)
       })
-      channel.on('messages', ({messages}) => {
+      channel.on('messages', ({ messages }) => {
         store.commit('setMessages', messages)
       })
       channel.join()
diff --git a/src/modules/config.js b/src/modules/config.js
index 1666a2c5..2bfad8f6 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -17,6 +17,7 @@ const defaultState = {
   autoLoad: true,
   streaming: false,
   hoverPreview: true,
+  autohideFloatingPostButton: false,
   pauseOnUnfocused: true,
   stopGifs: false,
   replyVisibility: 'all',
@@ -30,6 +31,7 @@ const defaultState = {
   muteWords: [],
   highlight: {},
   interfaceLanguage: browserLocale,
+  hideScopeNotice: false,
   scopeCopy: undefined, // instance default
   subjectLineBehavior: undefined, // instance default
   alwaysShowSubjectInput: undefined, // instance default
@@ -54,10 +56,10 @@ const config = {
   },
   actions: {
     setHighlight ({ commit, dispatch }, { user, color, type }) {
-      commit('setHighlight', {user, color, type})
+      commit('setHighlight', { user, color, type })
     },
     setOption ({ commit, dispatch }, { name, value }) {
-      commit('setOption', {name, value})
+      commit('setOption', { name, value })
       switch (name) {
         case 'theme':
           setPreset(value, commit)
diff --git a/src/modules/errors.js b/src/modules/errors.js
index c809e1b5..ca89dc0f 100644
--- a/src/modules/errors.js
+++ b/src/modules/errors.js
@@ -9,4 +9,3 @@ export function humanizeErrors (errors) {
     return [...errs, message]
   }, [])
 }
-
diff --git a/src/modules/instance.js b/src/modules/instance.js
index d4185f6a..93b56577 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -16,7 +16,6 @@ const defaultState = {
   redirectRootNoLogin: '/main/all',
   redirectRootLogin: '/main/friends',
   showInstanceSpecificPanel: false,
-  formattingOptionsEnabled: false,
   alwaysShowSubjectInput: true,
   hideMutedPosts: false,
   collapseMessageWithSubject: false,
@@ -27,7 +26,6 @@ const defaultState = {
   scopeCopy: true,
   subjectLineBehavior: 'email',
   postContentType: 'text/plain',
-  loginMethod: 'password',
   nsfwCensorImage: undefined,
   vapidPublicKey: undefined,
   noAttachmentLinks: false,
@@ -54,7 +52,15 @@ const defaultState = {
 
   // Version Information
   backendVersion: '',
-  frontendVersion: ''
+  frontendVersion: '',
+
+  pollsAvailable: false,
+  pollLimits: {
+    max_options: 4,
+    max_option_chars: 255,
+    min_expiration: 60,
+    max_expiration: 60 * 60 * 24
+  }
 }
 
 const instance = {
@@ -68,7 +74,7 @@ const instance = {
   },
   actions: {
     setInstanceOption ({ commit, dispatch }, { name, value }) {
-      commit('setInstanceOption', {name, value})
+      commit('setInstanceOption', { name, value })
       switch (name) {
         case 'name':
           dispatch('setPageTitle')
diff --git a/src/modules/interface.js b/src/modules/interface.js
index 71554787..5b2762e5 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -48,7 +48,6 @@ const interfaceMod = {
       commit('setNotificationPermission', permission)
     },
     setMobileLayout ({ commit }, value) {
-      console.log('setMobileLayout called')
       commit('setMobileLayout', value)
     }
   }
diff --git a/src/modules/oauth.js b/src/modules/oauth.js
index 144ff830..a2a83450 100644
--- a/src/modules/oauth.js
+++ b/src/modules/oauth.js
@@ -1,16 +1,47 @@
+import { delete as del } from 'vue'
+
 const oauth = {
   state: {
-    client_id: false,
-    client_secret: false,
-    token: false
+    clientId: false,
+    clientSecret: false,
+    /* App token is authentication for app without any user, used mostly for
+     * MastoAPI's registration of new users, stored so that we can fall back to
+     * it on logout
+     */
+    appToken: false,
+    /* User token is authentication for app with user, this is for every calls
+     * that need authorized user to be successful (i.e. posting, liking etc)
+     */
+    userToken: false
   },
   mutations: {
-    setClientData (state, data) {
-      state.client_id = data.client_id
-      state.client_secret = data.client_secret
+    setClientData (state, { clientId, clientSecret }) {
+      state.clientId = clientId
+      state.clientSecret = clientSecret
+    },
+    setAppToken (state, token) {
+      state.appToken = token
     },
     setToken (state, token) {
-      state.token = token
+      state.userToken = token
+    },
+    clearToken (state) {
+      state.userToken = false
+      // state.token is userToken with older name, coming from persistent state
+      // let's clear it as well, since it is being used as a fallback of state.userToken
+      del(state, 'token')
+    }
+  },
+  getters: {
+    getToken: state => () => {
+      // state.token is userToken with older name, coming from persistent state
+      // added here for smoother transition, otherwise user will be logged out
+      return state.userToken || state.token || state.appToken
+    },
+    getUserToken: state => () => {
+      // state.token is userToken with older name, coming from persistent state
+      // added here for smoother transition, otherwise user will be logged out
+      return state.userToken || state.token
     }
   }
 }
diff --git a/src/modules/oauth_tokens.js b/src/modules/oauth_tokens.js
index 00ac1431..0159a3f1 100644
--- a/src/modules/oauth_tokens.js
+++ b/src/modules/oauth_tokens.js
@@ -3,12 +3,12 @@ const oauthTokens = {
     tokens: []
   },
   actions: {
-    fetchTokens ({rootState, commit}) {
+    fetchTokens ({ rootState, commit }) {
       rootState.api.backendInteractor.fetchOAuthTokens().then((tokens) => {
         commit('swapTokens', tokens)
       })
     },
-    revokeToken ({rootState, commit, state}, id) {
+    revokeToken ({ rootState, commit, state }, id) {
       rootState.api.backendInteractor.revokeOAuthToken(id).then((response) => {
         if (response.status === 201) {
           commit('swapTokens', state.tokens.filter(token => token.id !== id))
diff --git a/src/modules/polls.js b/src/modules/polls.js
new file mode 100644
index 00000000..e6158b63
--- /dev/null
+++ b/src/modules/polls.js
@@ -0,0 +1,70 @@
+import { merge } from 'lodash'
+import { set } from 'vue'
+
+const polls = {
+  state: {
+    // Contains key = id, value = number of trackers for this poll
+    trackedPolls: {},
+    pollsObject: {}
+  },
+  mutations: {
+    mergeOrAddPoll (state, poll) {
+      const existingPoll = state.pollsObject[poll.id]
+      // Make expired-state change trigger re-renders properly
+      poll.expired = Date.now() > Date.parse(poll.expires_at)
+      if (existingPoll) {
+        set(state.pollsObject, poll.id, merge(existingPoll, poll))
+      } else {
+        set(state.pollsObject, poll.id, poll)
+      }
+    },
+    trackPoll (state, pollId) {
+      const currentValue = state.trackedPolls[pollId]
+      if (currentValue) {
+        set(state.trackedPolls, pollId, currentValue + 1)
+      } else {
+        set(state.trackedPolls, pollId, 1)
+      }
+    },
+    untrackPoll (state, pollId) {
+      const currentValue = state.trackedPolls[pollId]
+      if (currentValue) {
+        set(state.trackedPolls, pollId, currentValue - 1)
+      } else {
+        set(state.trackedPolls, pollId, 0)
+      }
+    }
+  },
+  actions: {
+    mergeOrAddPoll ({ commit }, poll) {
+      commit('mergeOrAddPoll', poll)
+    },
+    updateTrackedPoll ({ rootState, dispatch, commit }, pollId) {
+      rootState.api.backendInteractor.fetchPoll(pollId).then(poll => {
+        setTimeout(() => {
+          if (rootState.polls.trackedPolls[pollId]) {
+            dispatch('updateTrackedPoll', pollId)
+          }
+        }, 30 * 1000)
+        commit('mergeOrAddPoll', poll)
+      })
+    },
+    trackPoll ({ rootState, commit, dispatch }, pollId) {
+      if (!rootState.polls.trackedPolls[pollId]) {
+        setTimeout(() => dispatch('updateTrackedPoll', pollId), 30 * 1000)
+      }
+      commit('trackPoll', pollId)
+    },
+    untrackPoll ({ commit }, pollId) {
+      commit('untrackPoll', pollId)
+    },
+    votePoll ({ rootState, commit }, { id, pollId, choices }) {
+      return rootState.api.backendInteractor.vote(pollId, choices).then(poll => {
+        commit('mergeOrAddPoll', poll)
+        return poll
+      })
+    }
+  }
+}
+
+export default polls
diff --git a/src/modules/reports.js b/src/modules/reports.js
new file mode 100644
index 00000000..904022f1
--- /dev/null
+++ b/src/modules/reports.js
@@ -0,0 +1,30 @@
+import filter from 'lodash/filter'
+
+const reports = {
+  state: {
+    userId: null,
+    statuses: [],
+    modalActivated: false
+  },
+  mutations: {
+    openUserReportingModal (state, { userId, statuses }) {
+      state.userId = userId
+      state.statuses = statuses
+      state.modalActivated = true
+    },
+    closeUserReportingModal (state) {
+      state.modalActivated = false
+    }
+  },
+  actions: {
+    openUserReportingModal ({ rootState, commit }, userId) {
+      const statuses = filter(rootState.statuses.allStatuses, status => status.user.id === userId)
+      commit('openUserReportingModal', { userId, statuses })
+    },
+    closeUserReportingModal ({ commit }) {
+      commit('closeUserReportingModal')
+    }
+  }
+}
+
+export default reports
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 8e0203e3..7d5d5a67 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -1,4 +1,4 @@
-import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
+import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
 import { set } from 'vue'
 import apiService from '../services/api/api.service.js'
 // import parse from '../services/status_parser/status_parser.js'
@@ -20,20 +20,22 @@ const emptyTl = (userId = 0) => ({
   flushMarker: 0
 })
 
+const emptyNotifications = () => ({
+  desktopNotificationSilence: true,
+  maxId: 0,
+  minId: Number.POSITIVE_INFINITY,
+  data: [],
+  idStore: {},
+  loading: false,
+  error: false
+})
+
 export const defaultState = () => ({
   allStatuses: [],
   allStatusesObject: {},
+  conversationsObject: {},
   maxId: 0,
-  notifications: {
-    desktopNotificationSilence: true,
-    maxId: 0,
-    minId: Number.POSITIVE_INFINITY,
-    data: [],
-    idStore: {},
-    loading: false,
-    error: false,
-    fetcherId: null
-  },
+  notifications: emptyNotifications(),
   favorites: new Set(),
   error: false,
   timelines: {
@@ -78,13 +80,13 @@ const mergeOrAdd = (arr, obj, item) => {
     merge(oldItem, omitBy(item, (v, k) => v === null || k === 'user'))
     // Reactivity fix.
     oldItem.attachments.splice(oldItem.attachments.length)
-    return {item: oldItem, new: false}
+    return { item: oldItem, new: false }
   } else {
     // This is a new item, prepare it
     prepareStatus(item)
     arr.push(item)
     set(obj, item.id, item)
-    return {item, new: true}
+    return { item, new: true }
   }
 }
 
@@ -111,14 +113,47 @@ const sortTimeline = (timeline) => {
   return timeline
 }
 
-const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {}, noIdUpdate = false, userId }) => {
+// Add status to the global storages (arrays and objects maintaining statuses) except timelines
+const addStatusToGlobalStorage = (state, data) => {
+  const result = mergeOrAdd(state.allStatuses, state.allStatusesObject, data)
+  if (result.new) {
+    // Add to conversation
+    const status = result.item
+    const conversationsObject = state.conversationsObject
+    const conversationId = status.statusnet_conversation_id
+    if (conversationsObject[conversationId]) {
+      conversationsObject[conversationId].push(status)
+    } else {
+      set(conversationsObject, conversationId, [status])
+    }
+  }
+  return result
+}
+
+// Remove status from the global storages (arrays and objects maintaining statuses) except timelines
+const removeStatusFromGlobalStorage = (state, status) => {
+  remove(state.allStatuses, { id: status.id })
+
+  // TODO: Need to remove from allStatusesObject?
+
+  // Remove possible notification
+  remove(state.notifications.data, ({ action: { id } }) => id === status.id)
+
+  // Remove from conversation
+  const conversationId = status.statusnet_conversation_id
+  if (state.conversationsObject[conversationId]) {
+    remove(state.conversationsObject[conversationId], { id: status.id })
+  }
+}
+
+const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {},
+  noIdUpdate = false, userId }) => {
   // Sanity check
   if (!isArray(statuses)) {
     return false
   }
 
   const allStatuses = state.allStatuses
-  const allStatusesObject = state.allStatusesObject
   const timelineObject = state.timelines[timeline]
 
   const maxNew = statuses.length > 0 ? maxBy(statuses, 'id').id : 0
@@ -141,7 +176,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
   }
 
   const addStatus = (data, showImmediately, addToTimeline = true) => {
-    const result = mergeOrAdd(allStatuses, allStatusesObject, data)
+    const result = addStatusToGlobalStorage(state, data)
     const status = result.item
 
     if (result.new) {
@@ -235,16 +270,13 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
     },
     'deletion': (deletion) => {
       const uri = deletion.uri
-
-      // Remove possible notification
-      const status = find(allStatuses, {uri})
+      const status = find(allStatuses, { uri })
       if (!status) {
         return
       }
 
-      remove(state.notifications.data, ({action: {id}}) => id === status.id)
+      removeStatusFromGlobalStorage(state, status)
 
-      remove(allStatuses, { uri })
       if (timeline) {
         remove(timelineObject.statuses, { uri })
         remove(timelineObject.visibleStatuses, { uri })
@@ -271,12 +303,12 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
   }
 }
 
-const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes }) => {
-  const allStatuses = state.allStatuses
-  const allStatusesObject = state.allStatusesObject
+const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => {
   each(notifications, (notification) => {
-    notification.action = mergeOrAdd(allStatuses, allStatusesObject, notification.action).item
-    notification.status = notification.status && mergeOrAdd(allStatuses, allStatusesObject, notification.status).item
+    if (notification.type !== 'follow') {
+      notification.action = addStatusToGlobalStorage(state, notification.action).item
+      notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
+    }
 
     // Only add a new notification if we don't have one for the same action
     if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
@@ -292,15 +324,32 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
 
       if ('Notification' in window && window.Notification.permission === 'granted') {
         const notifObj = {}
-        const action = notification.action
-        const title = action.user.name
-        notifObj.icon = action.user.profile_image_url
-        notifObj.body = action.text // there's a problem that it doesn't put a space before links tho
+        const status = notification.status
+        const title = notification.from_profile.name
+        notifObj.icon = notification.from_profile.profile_image_url
+        let i18nString
+        switch (notification.type) {
+          case 'like':
+            i18nString = 'favorited_you'
+            break
+          case 'repeat':
+            i18nString = 'repeated_you'
+            break
+          case 'follow':
+            i18nString = 'followed_you'
+            break
+        }
+
+        if (i18nString) {
+          notifObj.body = rootGetters.i18n.t('notifications.' + i18nString)
+        } else {
+          notifObj.body = notification.status.text
+        }
 
         // Shows first attached non-nsfw image, if any. Should add configuration for this somehow...
-        if (action.attachments && action.attachments.length > 0 && !action.nsfw &&
-            action.attachments[0].mimetype.startsWith('image/')) {
-          notifObj.image = action.attachments[0].url
+        if (status && status.attachments && status.attachments.length > 0 && !status.nsfw &&
+          status.attachments[0].mimetype.startsWith('image/')) {
+          notifObj.image = status.attachments[0].url
         }
 
         if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) {
@@ -340,26 +389,46 @@ export const mutations = {
     oldTimeline.visibleStatusesObject = {}
     each(oldTimeline.visibleStatuses, (status) => { oldTimeline.visibleStatusesObject[status.id] = status })
   },
-  setNotificationFetcher (state, { fetcherId }) {
-    state.notifications.fetcherId = fetcherId
-  },
   resetStatuses (state) {
     const emptyState = defaultState()
     Object.entries(emptyState).forEach(([key, value]) => {
       state[key] = value
     })
   },
-  clearTimeline (state, { timeline }) {
-    state.timelines[timeline] = emptyTl(state.timelines[timeline].userId)
+  clearTimeline (state, { timeline, excludeUserId = false }) {
+    const userId = excludeUserId ? state.timelines[timeline].userId : undefined
+    state.timelines[timeline] = emptyTl(userId)
+  },
+  clearNotifications (state) {
+    state.notifications = emptyNotifications()
   },
   setFavorited (state, { status, value }) {
     const newStatus = state.allStatusesObject[status.id]
+
+    if (newStatus.favorited !== value) {
+      if (value) {
+        newStatus.fave_num++
+      } else {
+        newStatus.fave_num--
+      }
+    }
+
     newStatus.favorited = value
   },
-  setFavoritedConfirm (state, { status }) {
+  setFavoritedConfirm (state, { status, user }) {
     const newStatus = state.allStatusesObject[status.id]
     newStatus.favorited = status.favorited
     newStatus.fave_num = status.fave_num
+    const index = findIndex(newStatus.favoritedBy, { id: user.id })
+    if (index !== -1 && !newStatus.favorited) {
+      newStatus.favoritedBy.splice(index, 1)
+    } else if (index === -1 && newStatus.favorited) {
+      newStatus.favoritedBy.push(user)
+    }
+  },
+  setPinned (state, status) {
+    const newStatus = state.allStatusesObject[status.id]
+    newStatus.pinned = status.pinned
   },
   setRetweeted (state, { status, value }) {
     const newStatus = state.allStatusesObject[status.id]
@@ -374,10 +443,28 @@ export const mutations = {
 
     newStatus.repeated = value
   },
+  setRetweetedConfirm (state, { status, user }) {
+    const newStatus = state.allStatusesObject[status.id]
+    newStatus.repeated = status.repeated
+    newStatus.repeat_num = status.repeat_num
+    const index = findIndex(newStatus.rebloggedBy, { id: user.id })
+    if (index !== -1 && !newStatus.repeated) {
+      newStatus.rebloggedBy.splice(index, 1)
+    } else if (index === -1 && newStatus.repeated) {
+      newStatus.rebloggedBy.push(user)
+    }
+  },
   setDeleted (state, { status }) {
     const newStatus = state.allStatusesObject[status.id]
     newStatus.deleted = true
   },
+  setManyDeleted (state, condition) {
+    Object.values(state.allStatusesObject).forEach(status => {
+      if (condition(status)) {
+        status.deleted = true
+      }
+    })
+  },
   setLoading (state, { timeline, value }) {
     state.timelines[timeline].loading = value
   },
@@ -404,6 +491,24 @@ export const mutations = {
   },
   queueFlush (state, { timeline, id }) {
     state.timelines[timeline].flushMarker = id
+  },
+  addRepeats (state, { id, rebloggedByUsers, currentUser }) {
+    const newStatus = state.allStatusesObject[id]
+    newStatus.rebloggedBy = rebloggedByUsers.filter(_ => _)
+    // repeats stats can be incorrect based on polling condition, let's update them using the most recent data
+    newStatus.repeat_num = newStatus.rebloggedBy.length
+    newStatus.repeated = !!newStatus.rebloggedBy.find(({ id }) => currentUser.id === id)
+  },
+  addFavs (state, { id, favoritedByUsers, currentUser }) {
+    const newStatus = state.allStatusesObject[id]
+    newStatus.favoritedBy = favoritedByUsers.filter(_ => _)
+    // favorites stats can be incorrect based on polling condition, let's update them using the most recent data
+    newStatus.fave_num = newStatus.favoritedBy.length
+    newStatus.favorited = !!newStatus.favoritedBy.find(({ id }) => currentUser.id === id)
+  },
+  updateStatusWithPoll (state, { id, poll }) {
+    const status = state.allStatusesObject[id]
+    status.poll = poll
   }
 }
 
@@ -413,8 +518,8 @@ const statuses = {
     addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId }) {
       commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId })
     },
-    addNewNotifications ({ rootState, commit, dispatch }, { notifications, older }) {
-      commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older })
+    addNewNotifications ({ rootState, commit, dispatch, rootGetters }, { notifications, older }) {
+      commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older, rootGetters })
     },
     setError ({ rootState, commit }, { value }) {
       commit('setError', { value })
@@ -428,40 +533,48 @@ const statuses = {
     setNotificationsSilence ({ rootState, commit }, { value }) {
       commit('setNotificationsSilence', { value })
     },
-    stopFetchingNotifications ({ rootState, commit }) {
-      if (rootState.statuses.notifications.fetcherId) {
-        window.clearInterval(rootState.statuses.notifications.fetcherId)
-      }
-      commit('setNotificationFetcher', { fetcherId: null })
-    },
     deleteStatus ({ rootState, commit }, status) {
       commit('setDeleted', { status })
       apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials })
     },
+    markStatusesAsDeleted ({ commit }, condition) {
+      commit('setManyDeleted', condition)
+    },
     favorite ({ rootState, commit }, status) {
       // Optimistic favoriting...
       commit('setFavorited', { status, value: true })
-      apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
-        .then(status => {
-          commit('setFavoritedConfirm', { status })
-        })
+      rootState.api.backendInteractor.favorite(status.id)
+        .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
     },
     unfavorite ({ rootState, commit }, status) {
-      // Optimistic favoriting...
+      // Optimistic unfavoriting...
       commit('setFavorited', { status, value: false })
-      apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
-        .then(status => {
-          commit('setFavoritedConfirm', { status })
-        })
+      rootState.api.backendInteractor.unfavorite(status.id)
+        .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
+    },
+    fetchPinnedStatuses ({ rootState, dispatch }, userId) {
+      rootState.api.backendInteractor.fetchPinnedStatuses(userId)
+        .then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true }))
+    },
+    pinStatus ({ rootState, commit }, statusId) {
+      return rootState.api.backendInteractor.pinOwnStatus(statusId)
+        .then((status) => commit('setPinned', status))
+    },
+    unpinStatus ({ rootState, commit }, statusId) {
+      rootState.api.backendInteractor.unpinOwnStatus(statusId)
+        .then((status) => commit('setPinned', status))
     },
     retweet ({ rootState, commit }, status) {
       // Optimistic retweeting...
       commit('setRetweeted', { status, value: true })
-      apiService.retweet({ id: status.id, credentials: rootState.users.currentUser.credentials })
+      rootState.api.backendInteractor.retweet(status.id)
+        .then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser }))
     },
     unretweet ({ rootState, commit }, status) {
+      // Optimistic unretweeting...
       commit('setRetweeted', { status, value: false })
-      apiService.unretweet({ id: status.id, credentials: rootState.users.currentUser.credentials })
+      rootState.api.backendInteractor.unretweet(status.id)
+        .then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser }))
     },
     queueFlush ({ rootState, commit }, { timeline, id }) {
       commit('queueFlush', { timeline, id })
@@ -472,6 +585,31 @@ const statuses = {
         id: rootState.statuses.notifications.maxId,
         credentials: rootState.users.currentUser.credentials
       })
+    },
+    fetchFavsAndRepeats ({ rootState, commit }, id) {
+      Promise.all([
+        rootState.api.backendInteractor.fetchFavoritedByUsers(id),
+        rootState.api.backendInteractor.fetchRebloggedByUsers(id)
+      ]).then(([favoritedByUsers, rebloggedByUsers]) => {
+        commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser })
+        commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
+      })
+    },
+    fetchFavs ({ rootState, commit }, id) {
+      rootState.api.backendInteractor.fetchFavoritedByUsers(id)
+        .then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
+    },
+    fetchRepeats ({ rootState, commit }, id) {
+      rootState.api.backendInteractor.fetchRebloggedByUsers(id)
+        .then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
+    },
+    search (store, { q, resolve, limit, offset, following }) {
+      return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following })
+        .then((data) => {
+          store.commit('addNewUsers', data.accounts)
+          store.commit('addNewStatuses', { statuses: data.statuses })
+          return data
+        })
     }
   },
   mutations
diff --git a/src/modules/users.js b/src/modules/users.js
index 1a507d31..57d3a3e3 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -1,8 +1,8 @@
 import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
-import { compact, map, each, merge, find, last } from 'lodash'
+import oauthApi from '../services/new_api/oauth.js'
+import { compact, map, each, merge, last, concat, uniq } from 'lodash'
 import { set } from 'vue'
 import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
-import oauthApi from '../services/new_api/oauth'
 import { humanizeErrors } from './errors'
 
 // TODO: Unify with mergeOrAdd in statuses.js
@@ -32,11 +32,62 @@ const getNotificationPermission = () => {
   return Promise.resolve(Notification.permission)
 }
 
+const blockUser = (store, id) => {
+  return store.rootState.api.backendInteractor.blockUser(id)
+    .then((relationship) => {
+      store.commit('updateUserRelationship', [relationship])
+      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, 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]))
+}
+
 export const mutations = {
   setMuted (state, { user: { id }, muted }) {
     const user = state.usersObject[id]
     set(user, 'muted', muted)
   },
+  tagUser (state, { user: { id }, tag }) {
+    const user = state.usersObject[id]
+    const tags = user.tags || []
+    const newTags = tags.concat([tag])
+    set(user, 'tags', newTags)
+  },
+  untagUser (state, { user: { id }, tag }) {
+    const user = state.usersObject[id]
+    const tags = user.tags || []
+    const newTags = tags.filter(t => t !== tag)
+    set(user, 'tags', newTags)
+  },
+  updateRight (state, { user: { id }, right, value }) {
+    const user = state.usersObject[id]
+    let newRights = user.rights
+    newRights[right] = value
+    set(user, 'rights', newRights)
+  },
+  updateActivationStatus (state, { user: { id }, status }) {
+    const user = state.usersObject[id]
+    set(user, 'deactivated', !status)
+  },
   setCurrentUser (state, user) {
     state.lastLoginName = user.screen_name
     state.currentUser = merge(state.currentUser || {}, user)
@@ -51,42 +102,27 @@ export const mutations = {
   endLogin (state) {
     state.loggingIn = false
   },
-  // TODO Clean after ourselves?
-  addFriends (state, { id, friends }) {
+  saveFriendIds (state, { id, friendIds }) {
     const user = state.usersObject[id]
-    each(friends, friend => {
-      if (!find(user.friends, { id: friend.id })) {
-        user.friends.push(friend)
-      }
-    })
-    user.lastFriendId = last(friends).id
+    user.friendIds = uniq(concat(user.friendIds, friendIds))
   },
-  addFollowers (state, { id, followers }) {
+  saveFollowerIds (state, { id, followerIds }) {
     const user = state.usersObject[id]
-    each(followers, follower => {
-      if (!find(user.followers, { id: follower.id })) {
-        user.followers.push(follower)
-      }
-    })
-    user.lastFollowerId = last(followers).id
+    user.followerIds = uniq(concat(user.followerIds, followerIds))
   },
   // Because frontend doesn't have a reason to keep these stuff in memory
   // outside of viewing someones user profile.
   clearFriends (state, userId) {
     const user = state.usersObject[userId]
-    if (!user) {
-      return
+    if (user) {
+      set(user, 'friendIds', [])
     }
-    user.friends = []
-    user.lastFriendId = null
   },
   clearFollowers (state, userId) {
     const user = state.usersObject[userId]
-    if (!user) {
-      return
+    if (user) {
+      set(user, 'followerIds', [])
     }
-    user.followers = []
-    user.lastFollowerId = null
   },
   addNewUsers (state, users) {
     each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
@@ -99,6 +135,7 @@ export const mutations = {
         user.following = relationship.following
         user.muted = relationship.muting
         user.statusnet_blocking = relationship.blocking
+        user.subscribed = relationship.subscribing
       }
     })
   },
@@ -110,6 +147,11 @@ export const mutations = {
   saveBlockIds (state, blockIds) {
     state.currentUser.blockIds = blockIds
   },
+  addBlockId (state, blockId) {
+    if (state.currentUser.blockIds.indexOf(blockId) === -1) {
+      state.currentUser.blockIds.push(blockId)
+    }
+  },
   updateMutes (state, mutedUsers) {
     // Reset muted of all fetched users
     each(state.users, (user) => { user.muted = false })
@@ -118,12 +160,28 @@ export const mutations = {
   saveMuteIds (state, muteIds) {
     state.currentUser.muteIds = muteIds
   },
+  addMuteId (state, muteId) {
+    if (state.currentUser.muteIds.indexOf(muteId) === -1) {
+      state.currentUser.muteIds.push(muteId)
+    }
+  },
+  setPinned (state, status) {
+    const user = state.usersObject[status.user.id]
+    const index = user.pinnedStatuseIds.indexOf(status.id)
+    if (status.pinned && index === -1) {
+      user.pinnedStatuseIds.push(status.id)
+    } else if (!status.pinned && index !== -1) {
+      user.pinnedStatuseIds.splice(index, 1)
+    }
+  },
   setUserForStatus (state, status) {
     status.user = state.usersObject[status.user.id]
   },
   setUserForNotification (state, notification) {
-    notification.action.user = state.usersObject[notification.action.user.id]
-    notification.from_profile = state.usersObject[notification.action.user.id]
+    if (notification.type !== 'follow') {
+      notification.action.user = state.usersObject[notification.action.user.id]
+    }
+    notification.from_profile = state.usersObject[notification.from_profile.id]
   },
   setColor (state, { user: { id }, highlighted }) {
     const user = state.usersObject[id]
@@ -176,8 +234,10 @@ const users = {
         })
     },
     fetchUserRelationship (store, id) {
-      return store.rootState.api.backendInteractor.fetchUserRelationship({ id })
-        .then((relationships) => store.commit('updateUserRelationship', relationships))
+      if (store.state.currentUser) {
+        store.rootState.api.backendInteractor.fetchUserRelationship({ id })
+          .then((relationships) => store.commit('updateUserRelationship', relationships))
+      }
     },
     fetchBlocks (store) {
       return store.rootState.api.backendInteractor.fetchBlocks()
@@ -187,18 +247,17 @@ const users = {
           return blocks
         })
     },
-    blockUser (store, userId) {
-      return store.rootState.api.backendInteractor.blockUser(userId)
-        .then((relationship) => {
-          store.commit('updateUserRelationship', [relationship])
-          store.commit('removeStatus', { timeline: 'friends', userId })
-          store.commit('removeStatus', { timeline: 'public', userId })
-          store.commit('removeStatus', { timeline: 'publicAndExternal', userId })
-        })
+    blockUser (store, id) {
+      return blockUser(store, id)
     },
     unblockUser (store, id) {
-      return store.rootState.api.backendInteractor.unblockUser(id)
-        .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+      return unblockUser(store, id)
+    },
+    blockUsers (store, ids = []) {
+      return Promise.all(ids.map(id => blockUser(store, id)))
+    },
+    unblockUsers (store, ids = []) {
+      return Promise.all(ids.map(id => unblockUser(store, id)))
     },
     fetchMutes (store) {
       return store.rootState.api.backendInteractor.fetchMutes()
@@ -209,32 +268,34 @@ const users = {
         })
     },
     muteUser (store, id) {
-      return store.rootState.api.backendInteractor.muteUser(id)
-        .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+      return muteUser(store, id)
     },
     unmuteUser (store, id) {
-      return store.rootState.api.backendInteractor.unmuteUser(id)
-        .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+      return unmuteUser(store, id)
     },
-    addFriends ({ rootState, commit }, fetchBy) {
-      return new Promise((resolve, reject) => {
-        const user = rootState.users.usersObject[fetchBy]
-        const maxId = user.lastFriendId
-        rootState.api.backendInteractor.fetchFriends({ id: user.id, maxId })
-          .then((friends) => {
-            commit('addFriends', { id: user.id, friends })
-            resolve(friends)
-          }).catch(() => {
-            reject()
-          })
-      })
+    muteUsers (store, ids = []) {
+      return Promise.all(ids.map(id => muteUser(store, id)))
     },
-    addFollowers ({ rootState, commit }, fetchBy) {
-      const user = rootState.users.usersObject[fetchBy]
-      const maxId = user.lastFollowerId
-      return rootState.api.backendInteractor.fetchFollowers({ id: user.id, maxId })
+    unmuteUsers (store, ids = []) {
+      return Promise.all(ids.map(id => unmuteUser(store, id)))
+    },
+    fetchFriends ({ rootState, commit }, id) {
+      const user = rootState.users.usersObject[id]
+      const maxId = last(user.friendIds)
+      return rootState.api.backendInteractor.fetchFriends({ id, maxId })
+        .then((friends) => {
+          commit('addNewUsers', friends)
+          commit('saveFriendIds', { id, friendIds: map(friends, 'id') })
+          return friends
+        })
+    },
+    fetchFollowers ({ rootState, commit }, id) {
+      const user = rootState.users.usersObject[id]
+      const maxId = last(user.followerIds)
+      return rootState.api.backendInteractor.fetchFollowers({ id, maxId })
         .then((followers) => {
-          commit('addFollowers', { id: user.id, followers })
+          commit('addNewUsers', followers)
+          commit('saveFollowerIds', { id, followerIds: map(followers, 'id') })
           return followers
         })
     },
@@ -244,6 +305,14 @@ const users = {
     clearFollowers ({ commit }, userId) {
       commit('clearFollowers', userId)
     },
+    subscribeUser ({ rootState, commit }, id) {
+      return rootState.api.backendInteractor.subscribeUser(id)
+        .then((relationship) => commit('updateUserRelationship', [relationship]))
+    },
+    unsubscribeUser ({ rootState, commit }, id) {
+      return rootState.api.backendInteractor.unsubscribeUser(id)
+        .then((relationship) => commit('updateUserRelationship', [relationship]))
+    },
     registerPushNotifications (store) {
       const token = store.state.currentUser.credentials
       const vapidPublicKey = store.rootState.instance.vapidPublicKey
@@ -257,19 +326,26 @@ const users = {
 
       unregisterPushNotifications(token)
     },
+    addNewUsers ({ commit }, users) {
+      commit('addNewUsers', users)
+    },
     addNewStatuses (store, { statuses }) {
       const users = map(statuses, 'user')
       const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))
       store.commit('addNewUsers', users)
       store.commit('addNewUsers', retweetedUsers)
 
-      // Reconnect users to statuses
       each(statuses, (status) => {
+        // Reconnect users to statuses
         store.commit('setUserForStatus', status)
+        // Set pinned statuses to user
+        store.commit('setPinned', status)
       })
-      // Reconnect users to retweets
       each(compact(map(statuses, 'retweeted_status')), (status) => {
+        // Reconnect users to retweets
         store.commit('setUserForStatus', status)
+        // Set pinned retweets to user
+        store.commit('setPinned', status)
       })
     },
     addNewNotifications (store, { notifications }) {
@@ -279,60 +355,78 @@ const users = {
 
       const notificationsObject = store.rootState.statuses.notifications.idStore
       const relevantNotifications = Object.entries(notificationsObject)
-            .filter(([k, val]) => notificationIds.includes(k))
-            .map(([k, val]) => val)
+        .filter(([k, val]) => notificationIds.includes(k))
+        .map(([k, val]) => val)
 
       // Reconnect users to notifications
       each(relevantNotifications, (notification) => {
         store.commit('setUserForNotification', notification)
       })
     },
+    searchUsers (store, query) {
+      return store.rootState.api.backendInteractor.searchUsers(query)
+        .then((users) => {
+          store.commit('addNewUsers', users)
+          return users
+        })
+    },
     async signUp (store, userInfo) {
       store.commit('signUpPending')
 
       let rootState = store.rootState
 
-      let response = await rootState.api.backendInteractor.register(userInfo)
-      if (response.ok) {
-        const data = {
-          oauth: rootState.oauth,
-          instance: rootState.instance.server
-        }
-        let app = await oauthApi.getOrCreateApp(data)
-        let result = await oauthApi.getTokenWithCredentials({
-          app,
-          instance: data.instance,
-          username: userInfo.username,
-          password: userInfo.password
-        })
+      try {
+        let data = await rootState.api.backendInteractor.register(userInfo)
         store.commit('signUpSuccess')
-        store.commit('setToken', result.access_token)
-        store.dispatch('loginUser', result.access_token)
-      } else {
-        const data = await response.json()
-        let errors = JSON.parse(data.error)
+        store.commit('setToken', data.access_token)
+        store.dispatch('loginUser', data.access_token)
+      } catch (e) {
+        let errors = e.message
         // replace ap_id with username
-        if (errors.ap_id) {
-          errors.username = errors.ap_id
-          delete errors.ap_id
+        if (typeof errors === 'object') {
+          if (errors.ap_id) {
+            errors.username = errors.ap_id
+            delete errors.ap_id
+          }
+          errors = humanizeErrors(errors)
         }
-        errors = humanizeErrors(errors)
         store.commit('signUpFailure', errors)
         throw Error(errors)
       }
     },
     async getCaptcha (store) {
-      return await store.rootState.api.backendInteractor.getCaptcha()
+      return store.rootState.api.backendInteractor.getCaptcha()
     },
 
     logout (store) {
-      store.commit('clearCurrentUser')
-      store.dispatch('disconnectFromChat')
-      store.commit('setToken', false)
-      store.dispatch('stopFetching', 'friends')
-      store.commit('setBackendInteractor', backendInteractorService())
-      store.dispatch('stopFetchingNotifications')
-      store.commit('resetStatuses')
+      const { oauth, instance } = store.rootState
+
+      const data = {
+        ...oauth,
+        commit: store.commit,
+        instance: instance.server
+      }
+
+      return oauthApi.getOrCreateApp(data)
+        .then((app) => {
+          const params = {
+            app,
+            instance: data.instance,
+            token: oauth.userToken
+          }
+
+          return oauthApi.revokeToken(params)
+        })
+        .then(() => {
+          store.commit('clearCurrentUser')
+          store.dispatch('disconnectFromChat')
+          store.commit('clearToken')
+          store.dispatch('stopFetching', 'friends')
+          store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
+          store.dispatch('stopFetching', 'notifications')
+          store.commit('clearNotifications')
+          store.commit('resetStatuses')
+        })
     },
     loginUser (store, accessToken) {
       return new Promise((resolve, reject) => {
@@ -363,7 +457,10 @@ const users = {
               }
 
               // Start getting fresh posts.
-              store.dispatch('startFetching', { timeline: 'friends' })
+              store.dispatch('startFetchingTimeline', { timeline: 'friends' })
+
+              // Start fetching notifications
+              store.dispatch('startFetchingNotifications')
 
               // Get user mutes
               store.dispatch('fetchMutes')
@@ -376,19 +473,19 @@ const users = {
               // Authentication failed
               commit('endLogin')
               if (response.status === 401) {
-                reject('Wrong username or password')
+                reject(new Error('Wrong username or password'))
               } else {
-                reject('An error occurred, please try again')
+                reject(new Error('An error occurred, please try again'))
               }
             }
             commit('endLogin')
             resolve()
           })
-        .catch((error) => {
-          console.log(error)
-          commit('endLogin')
-          reject('Failed to connect to server, try again')
-        })
+          .catch((error) => {
+            console.log(error)
+            commit('endLogin')
+            reject(new Error('Failed to connect to server, try again'))
+          })
       })
     }
   }
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 030c2f5e..d4ad1c4e 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1,24 +1,36 @@
+import { each, map, concat, last } from 'lodash'
+import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
+import 'whatwg-fetch'
+import { StatusCodeError } from '../errors/errors'
+
 /* eslint-env browser */
-const LOGIN_URL = '/api/account/verify_credentials.json'
-const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing'
-const MENTIONS_URL = '/api/statuses/mentions.json'
-const REGISTRATION_URL = '/api/account/register.json'
-const AVATAR_UPDATE_URL = '/api/qvitter/update_avatar.json'
-const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
-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_URL = '/api/qvitter/statuses/notifications.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'
 const FOLLOW_REQUESTS_URL = '/api/pleroma/friend_requests'
 const APPROVE_USER_URL = '/api/pleroma/friendships/approve'
 const DENY_USER_URL = '/api/pleroma/friendships/deny'
+const TAG_USER_URL = '/api/pleroma/admin/users/tag'
+const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
+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 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_REGISTRATION_URL = '/api/v1/accounts'
 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`
 const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite`
 const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog`
@@ -43,13 +55,20 @@ const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
 const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock`
 const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
 const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
+const MASTODON_SUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/subscribe`
+const MASTODON_UNSUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/unsubscribe`
 const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
 const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
-
-import { each, map } from 'lodash'
-import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
-import 'whatwg-fetch'
-import { StatusCodeError } from '../errors/errors'
+const MASTODON_VOTE_URL = id => `/api/v1/polls/${id}/votes`
+const MASTODON_POLL_URL = id => `/api/v1/polls/${id}`
+const MASTODON_STATUS_FAVORITEDBY_URL = id => `/api/v1/statuses/${id}/favourited_by`
+const MASTODON_STATUS_REBLOGGEDBY_URL = id => `/api/v1/statuses/${id}/reblogged_by`
+const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
+const MASTODON_REPORT_USER_URL = '/api/v1/reports'
+const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin`
+const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin`
+const MASTODON_SEARCH_2 = `/api/v2/search`
+const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
 
 const oldfetch = window.fetch
 
@@ -61,7 +80,29 @@ let fetch = (url, options) => {
   return oldfetch(fullUrl, options)
 }
 
-const promisedRequest = (url, options) => {
+const promisedRequest = ({ method, url, params, payload, credentials, headers = {} }) => {
+  const options = {
+    method,
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json',
+      ...headers
+    }
+  }
+  if (params) {
+    url += '?' + Object.entries(params)
+      .map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value))
+      .join('&')
+  }
+  if (payload) {
+    options.body = JSON.stringify(payload)
+  }
+  if (credentials) {
+    options.headers = {
+      ...options.headers,
+      ...authHeaders(credentials)
+    }
+  }
   return fetch(url, options)
     .then((response) => {
       return new Promise((resolve, reject) => response.json()
@@ -74,94 +115,61 @@ const promisedRequest = (url, options) => {
     })
 }
 
-// Params
-// cropH
-// cropW
-// cropX
-// cropY
-// img (base 64 encodend data url)
-const updateAvatar = ({credentials, params}) => {
-  let url = AVATAR_UPDATE_URL
-
+const updateNotificationSettings = ({ credentials, settings }) => {
   const form = new FormData()
 
-  each(params, (value, key) => {
-    if (value) {
-      form.append(key, value)
-    }
+  each(settings, (value, key) => {
+    form.append(key, value)
   })
 
-  return fetch(url, {
+  return fetch(NOTIFICATION_SETTINGS_URL, {
     headers: authHeaders(credentials),
-    method: 'POST',
+    method: 'PUT',
     body: form
   }).then((data) => data.json())
 }
 
-const updateBg = ({credentials, params}) => {
-  let url = BG_UPDATE_URL
-
+const updateAvatar = ({ credentials, avatar }) => {
   const form = new FormData()
-
-  each(params, (value, key) => {
-    if (value) {
-      form.append(key, value)
-    }
-  })
-
-  return fetch(url, {
+  form.append('avatar', avatar)
+  return fetch(MASTODON_PROFILE_UPDATE_URL, {
     headers: authHeaders(credentials),
-    method: 'POST',
+    method: 'PATCH',
     body: form
   }).then((data) => data.json())
+    .then((data) => parseUser(data))
 }
 
-// Params
-// height
-// width
-// offset_left
-// offset_top
-// banner (base 64 encodend data url)
-const updateBanner = ({credentials, params}) => {
-  let url = BANNER_UPDATE_URL
-
+const updateBg = ({ credentials, background }) => {
   const form = new FormData()
-
-  each(params, (value, key) => {
-    if (value) {
-      form.append(key, value)
-    }
-  })
-
-  return fetch(url, {
+  form.append('pleroma_background_image', background)
+  return fetch(MASTODON_PROFILE_UPDATE_URL, {
     headers: authHeaders(credentials),
-    method: 'POST',
+    method: 'PATCH',
     body: form
-  }).then((data) => data.json())
+  })
+    .then((data) => data.json())
+    .then((data) => parseUser(data))
 }
 
-// Params
-// name
-// url
-// location
-// description
-const updateProfile = ({credentials, params}) => {
-  // Always include these fields, because they might be empty or false
-  const fields = ['description', 'locked', 'no_rich_text', 'hide_follows', 'hide_followers', 'show_role']
-  let url = PROFILE_UPDATE_URL
-
+const updateBanner = ({ credentials, banner }) => {
   const form = new FormData()
-
-  each(params, (value, key) => {
-    if (fields.includes(key) || value) {
-      form.append(key, value)
-    }
-  })
-  return fetch(url, {
+  form.append('header', banner)
+  return fetch(MASTODON_PROFILE_UPDATE_URL, {
     headers: authHeaders(credentials),
-    method: 'POST',
+    method: 'PATCH',
     body: form
   }).then((data) => data.json())
+    .then((data) => parseUser(data))
+}
+
+const updateProfile = ({ credentials, params }) => {
+  return promisedRequest({
+    url: MASTODON_PROFILE_UPDATE_URL,
+    method: 'PATCH',
+    payload: params,
+    credentials
+  }).then((data) => parseUser(data))
 }
 
 // Params needed:
@@ -176,19 +184,29 @@ const updateProfile = ({credentials, params}) => {
 // homepage
 // location
 // token
-const register = (params) => {
-  const form = new FormData()
-
-  each(params, (value, key) => {
-    if (value) {
-      form.append(key, value)
-    }
-  })
-
-  return fetch(REGISTRATION_URL, {
+const register = ({ params, credentials }) => {
+  const { nickname, ...rest } = params
+  return fetch(MASTODON_REGISTRATION_URL, {
     method: 'POST',
-    body: form
+    headers: {
+      ...authHeaders(credentials),
+      'Content-Type': 'application/json'
+    },
+    body: JSON.stringify({
+      nickname,
+      locale: 'en_US',
+      agreement: true,
+      ...rest
+    })
   })
+    .then((response) => [response.ok, response])
+    .then(([ok, response]) => {
+      if (ok) {
+        return response.json()
+      } else {
+        return response.json().then((error) => { throw new Error(error) })
+      }
+    })
 }
 
 const getCaptcha = () => fetch('/api/pleroma/captcha').then(resp => resp.json())
@@ -201,7 +219,7 @@ const authHeaders = (accessToken) => {
   }
 }
 
-const externalProfile = ({profileUrl, credentials}) => {
+const externalProfile = ({ profileUrl, credentials }) => {
   let url = `${EXTERNAL_PROFILE_URL}?profileurl=${profileUrl}`
   return fetch(url, {
     headers: authHeaders(credentials),
@@ -209,7 +227,7 @@ const externalProfile = ({profileUrl, credentials}) => {
   }).then((data) => data.json())
 }
 
-const followUser = ({id, credentials}) => {
+const followUser = ({ id, credentials }) => {
   let url = MASTODON_FOLLOW_URL(id)
   return fetch(url, {
     headers: authHeaders(credentials),
@@ -217,7 +235,7 @@ const followUser = ({id, credentials}) => {
   }).then((data) => data.json())
 }
 
-const unfollowUser = ({id, credentials}) => {
+const unfollowUser = ({ id, credentials }) => {
   let url = MASTODON_UNFOLLOW_URL(id)
   return fetch(url, {
     headers: authHeaders(credentials),
@@ -225,21 +243,31 @@ const unfollowUser = ({id, credentials}) => {
   }).then((data) => data.json())
 }
 
-const blockUser = ({id, credentials}) => {
+const pinOwnStatus = ({ id, credentials }) => {
+  return promisedRequest({ url: MASTODON_PIN_OWN_STATUS(id), credentials, method: 'POST' })
+    .then((data) => parseStatus(data))
+}
+
+const unpinOwnStatus = ({ id, credentials }) => {
+  return promisedRequest({ url: MASTODON_UNPIN_OWN_STATUS(id), credentials, method: 'POST' })
+    .then((data) => parseStatus(data))
+}
+
+const blockUser = ({ id, credentials }) => {
   return fetch(MASTODON_BLOCK_USER_URL(id), {
     headers: authHeaders(credentials),
     method: 'POST'
   }).then((data) => data.json())
 }
 
-const unblockUser = ({id, credentials}) => {
+const unblockUser = ({ id, credentials }) => {
   return fetch(MASTODON_UNBLOCK_USER_URL(id), {
     headers: authHeaders(credentials),
     method: 'POST'
   }).then((data) => data.json())
 }
 
-const approveUser = ({id, credentials}) => {
+const approveUser = ({ id, credentials }) => {
   let url = `${APPROVE_USER_URL}?user_id=${id}`
   return fetch(url, {
     headers: authHeaders(credentials),
@@ -247,7 +275,7 @@ const approveUser = ({id, credentials}) => {
   }).then((data) => data.json())
 }
 
-const denyUser = ({id, credentials}) => {
+const denyUser = ({ id, credentials }) => {
   let url = `${DENY_USER_URL}?user_id=${id}`
   return fetch(url, {
     headers: authHeaders(credentials),
@@ -255,13 +283,13 @@ const denyUser = ({id, credentials}) => {
   }).then((data) => data.json())
 }
 
-const fetchUser = ({id, credentials}) => {
+const fetchUser = ({ id, credentials }) => {
   let url = `${MASTODON_USER_URL}/${id}`
-  return promisedRequest(url, { headers: authHeaders(credentials) })
+  return promisedRequest({ url, credentials })
     .then((data) => parseUser(data))
 }
 
-const fetchUserRelationship = ({id, credentials}) => {
+const fetchUserRelationship = ({ id, credentials }) => {
   let url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`
   return fetch(url, { headers: authHeaders(credentials) })
     .then((response) => {
@@ -275,7 +303,7 @@ const fetchUserRelationship = ({id, credentials}) => {
     })
 }
 
-const fetchFriends = ({id, maxId, sinceId, limit = 20, credentials}) => {
+const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => {
   let url = MASTODON_FOLLOWING_URL(id)
   const args = [
     maxId && `max_id=${maxId}`,
@@ -289,14 +317,27 @@ const fetchFriends = ({id, maxId, sinceId, limit = 20, credentials}) => {
     .then((data) => data.map(parseUser))
 }
 
-const exportFriends = ({id, credentials}) => {
-  let url = MASTODON_FOLLOWING_URL(id) + `?all=true`
-  return fetch(url, { headers: authHeaders(credentials) })
-    .then((data) => data.json())
-    .then((data) => data.map(parseUser))
+const exportFriends = ({ id, credentials }) => {
+  return new Promise(async (resolve, reject) => {
+    try {
+      let friends = []
+      let more = true
+      while (more) {
+        const maxId = friends.length > 0 ? last(friends).id : undefined
+        const users = await fetchFriends({ id, maxId, credentials })
+        friends = concat(friends, users)
+        if (users.length === 0) {
+          more = false
+        }
+      }
+      resolve(friends)
+    } catch (err) {
+      reject(err)
+    }
+  })
 }
 
-const fetchFollowers = ({id, maxId, sinceId, limit = 20, credentials}) => {
+const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => {
   let url = MASTODON_FOLLOWERS_URL(id)
   const args = [
     maxId && `max_id=${maxId}`,
@@ -310,20 +351,13 @@ const fetchFollowers = ({id, maxId, sinceId, limit = 20, credentials}) => {
     .then((data) => data.map(parseUser))
 }
 
-const fetchAllFollowing = ({username, credentials}) => {
-  const url = `${ALL_FOLLOWING_URL}/${username}.json`
-  return fetch(url, { headers: authHeaders(credentials) })
-    .then((data) => data.json())
-    .then((data) => data.map(parseUser))
-}
-
-const fetchFollowRequests = ({credentials}) => {
+const fetchFollowRequests = ({ credentials }) => {
   const url = FOLLOW_REQUESTS_URL
   return fetch(url, { headers: authHeaders(credentials) })
     .then((data) => data.json())
 }
 
-const fetchConversation = ({id, credentials}) => {
+const fetchConversation = ({ id, credentials }) => {
   let urlContext = MASTODON_STATUS_CONTEXT_URL(id)
   return fetch(urlContext, { headers: authHeaders(credentials) })
     .then((data) => {
@@ -333,13 +367,13 @@ const fetchConversation = ({id, credentials}) => {
       throw new Error('Error fetching timeline', data)
     })
     .then((data) => data.json())
-    .then(({ancestors, descendants}) => ({
+    .then(({ ancestors, descendants }) => ({
       ancestors: ancestors.map(parseStatus),
       descendants: descendants.map(parseStatus)
     }))
 }
 
-const fetchStatus = ({id, credentials}) => {
+const fetchStatus = ({ id, credentials }) => {
   let url = MASTODON_STATUS_URL(id)
   return fetch(url, { headers: authHeaders(credentials) })
     .then((data) => {
@@ -352,13 +386,100 @@ const fetchStatus = ({id, credentials}) => {
     .then((data) => parseStatus(data))
 }
 
-const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false, withMuted = false}) => {
+const tagUser = ({ tag, credentials, ...options }) => {
+  const screenName = options.screen_name
+  const form = {
+    nicknames: [screenName],
+    tags: [tag]
+  }
+
+  const headers = authHeaders(credentials)
+  headers['Content-Type'] = 'application/json'
+
+  return fetch(TAG_USER_URL, {
+    method: 'PUT',
+    headers: headers,
+    body: JSON.stringify(form)
+  })
+}
+
+const untagUser = ({ tag, credentials, ...options }) => {
+  const screenName = options.screen_name
+  const body = {
+    nicknames: [screenName],
+    tags: [tag]
+  }
+
+  const headers = authHeaders(credentials)
+  headers['Content-Type'] = 'application/json'
+
+  return fetch(TAG_USER_URL, {
+    method: 'DELETE',
+    headers: headers,
+    body: JSON.stringify(body)
+  })
+}
+
+const addRight = ({ right, credentials, ...user }) => {
+  const screenName = user.screen_name
+
+  return fetch(PERMISSION_GROUP_URL(screenName, right), {
+    method: 'POST',
+    headers: authHeaders(credentials),
+    body: {}
+  })
+}
+
+const deleteRight = ({ right, credentials, ...user }) => {
+  const screenName = user.screen_name
+
+  return fetch(PERMISSION_GROUP_URL(screenName, right), {
+    method: 'DELETE',
+    headers: authHeaders(credentials),
+    body: {}
+  })
+}
+
+const setActivationStatus = ({ status, credentials, ...user }) => {
+  const screenName = user.screen_name
+  const body = {
+    status: status
+  }
+
+  const headers = authHeaders(credentials)
+  headers['Content-Type'] = 'application/json'
+
+  return fetch(ACTIVATION_STATUS_URL(screenName), {
+    method: 'PUT',
+    headers: headers,
+    body: JSON.stringify(body)
+  })
+}
+
+const deleteUser = ({ credentials, ...user }) => {
+  const screenName = user.screen_name
+  const headers = authHeaders(credentials)
+
+  return fetch(`${ADMIN_USERS_URL}?nickname=${screenName}`, {
+    method: 'DELETE',
+    headers: headers
+  })
+}
+
+const fetchTimeline = ({
+  timeline,
+  credentials,
+  since = false,
+  until = false,
+  userId = false,
+  tag = false,
+  withMuted = false
+}) => {
   const timelineUrls = {
     public: MASTODON_PUBLIC_TIMELINE,
     friends: MASTODON_USER_HOME_TIMELINE_URL,
-    mentions: MENTIONS_URL,
     dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
-    notifications: QVITTER_USER_NOTIFICATIONS_URL,
+    notifications: MASTODON_USER_NOTIFICATIONS_URL,
     'publicAndExternal': MASTODON_PUBLIC_TIMELINE,
     user: MASTODON_USER_TIMELINE_URL,
     media: MASTODON_USER_TIMELINE_URL,
@@ -410,9 +531,14 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
     .then((data) => data.map(isNotifications ? parseNotification : parseStatus))
 }
 
+const fetchPinnedStatuses = ({ id, credentials }) => {
+  const url = MASTODON_USER_TIMELINE_URL(id) + '?pinned=true'
+  return promisedRequest({ url, credentials })
+    .then((data) => data.map(parseStatus))
+}
+
 const verifyCredentials = (user) => {
-  return fetch(LOGIN_URL, {
-    method: 'POST',
+  return fetch(MASTODON_LOGIN_URL, {
     headers: authHeaders(user)
   })
     .then((response) => {
@@ -428,67 +554,38 @@ const verifyCredentials = (user) => {
 }
 
 const favorite = ({ id, credentials }) => {
-  return fetch(MASTODON_FAVORITE_URL(id), {
-    headers: authHeaders(credentials),
-    method: 'POST'
-  })
-    .then(response => {
-      if (response.ok) {
-        return response.json()
-      } else {
-        throw new Error('Error favoriting post')
-      }
-    })
+  return promisedRequest({ url: MASTODON_FAVORITE_URL(id), method: 'POST', credentials })
     .then((data) => parseStatus(data))
 }
 
 const unfavorite = ({ id, credentials }) => {
-  return fetch(MASTODON_UNFAVORITE_URL(id), {
-    headers: authHeaders(credentials),
-    method: 'POST'
-  })
-    .then(response => {
-      if (response.ok) {
-        return response.json()
-      } else {
-        throw new Error('Error removing favorite')
-      }
-    })
+  return promisedRequest({ url: MASTODON_UNFAVORITE_URL(id), method: 'POST', credentials })
     .then((data) => parseStatus(data))
 }
 
 const retweet = ({ id, credentials }) => {
-  return fetch(MASTODON_RETWEET_URL(id), {
-    headers: authHeaders(credentials),
-    method: 'POST'
-  })
-    .then(response => {
-      if (response.ok) {
-        return response.json()
-      } else {
-        throw new Error('Error repeating post')
-      }
-    })
+  return promisedRequest({ url: MASTODON_RETWEET_URL(id), method: 'POST', credentials })
     .then((data) => parseStatus(data))
 }
 
 const unretweet = ({ id, credentials }) => {
-  return fetch(MASTODON_UNRETWEET_URL(id), {
-    headers: authHeaders(credentials),
-    method: 'POST'
-  })
-    .then(response => {
-      if (response.ok) {
-        return response.json()
-      } else {
-        throw new Error('Error removing repeat')
-      }
-    })
+  return promisedRequest({ url: MASTODON_UNRETWEET_URL(id), method: 'POST', credentials })
     .then((data) => parseStatus(data))
 }
 
-const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds = [], inReplyToStatusId, contentType}) => {
+const postStatus = ({
+  credentials,
+  status,
+  spoilerText,
+  visibility,
+  sensitive,
+  poll,
+  mediaIds = [],
+  inReplyToStatusId,
+  contentType
+}) => {
   const form = new FormData()
+  const pollOptions = poll.options || []
 
   form.append('status', status)
   form.append('source', 'Pleroma FE')
@@ -499,6 +596,19 @@ const postStatus = ({credentials, status, spoilerText, visibility, sensitive, me
   mediaIds.forEach(val => {
     form.append('media_ids[]', val)
   })
+  if (pollOptions.some(option => option !== '')) {
+    const normalizedPoll = {
+      expires_in: poll.expiresIn,
+      multiple: poll.multiple
+    }
+    Object.keys(normalizedPoll).forEach(key => {
+      form.append(`poll[${key}]`, normalizedPoll[key])
+    })
+
+    pollOptions.forEach(option => {
+      form.append('poll[options][]', option)
+    })
+  }
   if (inReplyToStatusId) {
     form.append('in_reply_to_id', inReplyToStatusId)
   }
@@ -527,7 +637,7 @@ const deleteStatus = ({ id, credentials }) => {
   })
 }
 
-const uploadMedia = ({formData, credentials}) => {
+const uploadMedia = ({ formData, credentials }) => {
   return fetch(MASTODON_MEDIA_UPLOAD_URL, {
     body: formData,
     method: 'POST',
@@ -537,16 +647,29 @@ const uploadMedia = ({formData, credentials}) => {
     .then((data) => parseAttachment(data))
 }
 
-const followImport = ({params, credentials}) => {
-  return fetch(FOLLOW_IMPORT_URL, {
-    body: params,
+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 deleteAccount = ({credentials, password}) => {
+const importFollows = ({ file, credentials }) => {
+  const formData = new FormData()
+  formData.append('list', file)
+  return fetch(FOLLOW_IMPORT_URL, {
+    body: formData,
+    method: 'POST',
+    headers: authHeaders(credentials)
+  })
+    .then((response) => response.ok)
+}
+
+const deleteAccount = ({ credentials, password }) => {
   const form = new FormData()
 
   form.append('password', password)
@@ -559,7 +682,7 @@ const deleteAccount = ({credentials, password}) => {
     .then((response) => response.json())
 }
 
-const changePassword = ({credentials, password, newPassword, newPasswordConfirmation}) => {
+const changePassword = ({ credentials, password, newPassword, newPasswordConfirmation }) => {
   const form = new FormData()
 
   form.append('password', password)
@@ -574,31 +697,78 @@ const changePassword = ({credentials, password, newPassword, newPasswordConfirma
     .then((response) => response.json())
 }
 
-const fetchMutes = ({credentials}) => {
-  return promisedRequest(MASTODON_USER_MUTES_URL, { headers: authHeaders(credentials) })
+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))
 }
 
-const muteUser = ({id, credentials}) => {
-  return promisedRequest(MASTODON_MUTE_USER_URL(id), {
-    headers: authHeaders(credentials),
-    method: 'POST'
-  })
+const muteUser = ({ id, credentials }) => {
+  return promisedRequest({ url: MASTODON_MUTE_USER_URL(id), credentials, method: 'POST' })
 }
 
-const unmuteUser = ({id, credentials}) => {
-  return promisedRequest(MASTODON_UNMUTE_USER_URL(id), {
-    headers: authHeaders(credentials),
-    method: 'POST'
-  })
+const unmuteUser = ({ id, credentials }) => {
+  return promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST' })
 }
 
-const fetchBlocks = ({credentials}) => {
-  return promisedRequest(MASTODON_USER_BLOCKS_URL, { headers: authHeaders(credentials) })
+const subscribeUser = ({ id, credentials }) => {
+  return promisedRequest({ url: MASTODON_SUBSCRIBE_USER(id), credentials, method: 'POST' })
+}
+
+const unsubscribeUser = ({ id, credentials }) => {
+  return promisedRequest({ url: MASTODON_UNSUBSCRIBE_USER(id), credentials, method: 'POST' })
+}
+
+const fetchBlocks = ({ credentials }) => {
+  return promisedRequest({ url: MASTODON_USER_BLOCKS_URL, credentials })
     .then((users) => users.map(parseUser))
 }
 
-const fetchOAuthTokens = ({credentials}) => {
+const fetchOAuthTokens = ({ credentials }) => {
   const url = '/api/oauth_tokens.json'
 
   return fetch(url, {
@@ -611,7 +781,7 @@ const fetchOAuthTokens = ({credentials}) => {
   })
 }
 
-const revokeOAuthToken = ({id, credentials}) => {
+const revokeOAuthToken = ({ id, credentials }) => {
   const url = `/api/oauth_tokens/${id}`
 
   return fetch(url, {
@@ -620,13 +790,13 @@ const revokeOAuthToken = ({id, credentials}) => {
   })
 }
 
-const suggestions = ({credentials}) => {
+const suggestions = ({ credentials }) => {
   return fetch(SUGGESTIONS_URL, {
     headers: authHeaders(credentials)
   }).then((data) => data.json())
 }
 
-const markNotificationsAsSeen = ({id, credentials}) => {
+const markNotificationsAsSeen = ({ id, credentials }) => {
   const body = new FormData()
 
   body.append('latest_id', id)
@@ -638,9 +808,110 @@ const markNotificationsAsSeen = ({id, credentials}) => {
   }).then((data) => data.json())
 }
 
+const vote = ({ pollId, choices, credentials }) => {
+  const form = new FormData()
+  form.append('choices', choices)
+
+  return promisedRequest({
+    url: MASTODON_VOTE_URL(encodeURIComponent(pollId)),
+    method: 'POST',
+    credentials,
+    payload: {
+      choices: choices
+    }
+  })
+}
+
+const fetchPoll = ({ pollId, credentials }) => {
+  return promisedRequest(
+    {
+      url: MASTODON_POLL_URL(encodeURIComponent(pollId)),
+      method: 'GET',
+      credentials
+    }
+  )
+}
+
+const fetchFavoritedByUsers = ({ id }) => {
+  return promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id) }).then((users) => users.map(parseUser))
+}
+
+const fetchRebloggedByUsers = ({ id }) => {
+  return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser))
+}
+
+const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
+  return promisedRequest({
+    url: MASTODON_REPORT_USER_URL,
+    method: 'POST',
+    payload: {
+      'account_id': userId,
+      'status_ids': statusIds,
+      comment,
+      forward
+    },
+    credentials
+  })
+}
+
+const searchUsers = ({ credentials, query }) => {
+  return promisedRequest({
+    url: MASTODON_USER_SEARCH_URL,
+    params: {
+      q: query,
+      resolve: true
+    },
+    credentials
+  })
+    .then((data) => data.map(parseUser))
+}
+
+const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
+  let url = MASTODON_SEARCH_2
+  let params = []
+
+  if (q) {
+    params.push(['q', encodeURIComponent(q)])
+  }
+
+  if (resolve) {
+    params.push(['resolve', resolve])
+  }
+
+  if (limit) {
+    params.push(['limit', limit])
+  }
+
+  if (offset) {
+    params.push(['offset', offset])
+  }
+
+  if (following) {
+    params.push(['following', true])
+  }
+
+  let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
+  url += `?${queryString}`
+
+  return fetch(url, { headers: authHeaders(credentials) })
+    .then((data) => {
+      if (data.ok) {
+        return data
+      }
+      throw new Error('Error fetching search result', data)
+    })
+    .then((data) => { return data.json() })
+    .then((data) => {
+      data.accounts = data.accounts.slice(0, limit).map(u => parseUser(u))
+      data.statuses = data.statuses.slice(0, limit).map(s => parseStatus(s))
+      return data
+    })
+}
+
 const apiService = {
   verifyCredentials,
   fetchTimeline,
+  fetchPinnedStatuses,
   fetchConversation,
   fetchStatus,
   fetchFriends,
@@ -648,6 +919,8 @@ const apiService = {
   fetchFollowers,
   followUser,
   unfollowUser,
+  pinOwnStatus,
+  unpinOwnStatus,
   blockUser,
   unblockUser,
   fetchUser,
@@ -659,13 +932,20 @@ const apiService = {
   postStatus,
   deleteStatus,
   uploadMedia,
-  fetchAllFollowing,
   fetchMutes,
   muteUser,
   unmuteUser,
+  subscribeUser,
+  unsubscribeUser,
   fetchBlocks,
   fetchOAuthTokens,
   revokeOAuthToken,
+  tagUser,
+  untagUser,
+  deleteUser,
+  addRight,
+  deleteRight,
+  setActivationStatus,
   register,
   getCaptcha,
   updateAvatar,
@@ -673,14 +953,28 @@ const apiService = {
   updateProfile,
   updateBanner,
   externalProfile,
-  followImport,
+  importBlocks,
+  importFollows,
   deleteAccount,
   changePassword,
+  settingsMFA,
+  mfaDisableOTP,
+  generateMfaBackupCodes,
+  mfaSetupOTP,
+  mfaConfirmOTP,
   fetchFollowRequests,
   approveUser,
   denyUser,
   suggestions,
-  markNotificationsAsSeen
+  markNotificationsAsSeen,
+  vote,
+  fetchPoll,
+  fetchFavoritedByUsers,
+  fetchRebloggedByUsers,
+  reportUser,
+  updateNotificationSettings,
+  search2,
+  searchUsers
 }
 
 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 71e78d2f..bdfe0465 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -1,87 +1,156 @@
 import apiService from '../api/api.service.js'
 import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
+import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
 
-const backendInteractorService = (credentials) => {
-  const fetchStatus = ({id}) => {
-    return apiService.fetchStatus({id, credentials})
+const backendInteractorService = credentials => {
+  const fetchStatus = ({ id }) => {
+    return apiService.fetchStatus({ id, credentials })
   }
 
-  const fetchConversation = ({id}) => {
-    return apiService.fetchConversation({id, credentials})
+  const fetchConversation = ({ id }) => {
+    return apiService.fetchConversation({ id, credentials })
   }
 
-  const fetchFriends = ({id, maxId, sinceId, limit}) => {
-    return apiService.fetchFriends({id, maxId, sinceId, limit, credentials})
+  const fetchFriends = ({ id, maxId, sinceId, limit }) => {
+    return apiService.fetchFriends({ id, maxId, sinceId, limit, credentials })
   }
 
-  const exportFriends = ({id}) => {
-    return apiService.exportFriends({id, credentials})
+  const exportFriends = ({ id }) => {
+    return apiService.exportFriends({ id, credentials })
   }
 
-  const fetchFollowers = ({id, maxId, sinceId, limit}) => {
-    return apiService.fetchFollowers({id, maxId, sinceId, limit, credentials})
+  const fetchFollowers = ({ id, maxId, sinceId, limit }) => {
+    return apiService.fetchFollowers({ id, maxId, sinceId, limit, credentials })
   }
 
-  const fetchAllFollowing = ({username}) => {
-    return apiService.fetchAllFollowing({username, credentials})
+  const fetchUser = ({ id }) => {
+    return apiService.fetchUser({ id, credentials })
   }
 
-  const fetchUser = ({id}) => {
-    return apiService.fetchUser({id, credentials})
-  }
-
-  const fetchUserRelationship = ({id}) => {
-    return apiService.fetchUserRelationship({id, credentials})
+  const fetchUserRelationship = ({ id }) => {
+    return apiService.fetchUserRelationship({ id, credentials })
   }
 
   const followUser = (id) => {
-    return apiService.followUser({credentials, id})
+    return apiService.followUser({ credentials, id })
   }
 
   const unfollowUser = (id) => {
-    return apiService.unfollowUser({credentials, id})
+    return apiService.unfollowUser({ credentials, id })
   }
 
   const blockUser = (id) => {
-    return apiService.blockUser({credentials, id})
+    return apiService.blockUser({ credentials, id })
   }
 
   const unblockUser = (id) => {
-    return apiService.unblockUser({credentials, id})
+    return apiService.unblockUser({ credentials, id })
   }
 
   const approveUser = (id) => {
-    return apiService.approveUser({credentials, id})
+    return apiService.approveUser({ credentials, id })
   }
 
   const denyUser = (id) => {
-    return apiService.denyUser({credentials, id})
+    return apiService.denyUser({ credentials, id })
   }
 
-  const startFetching = ({timeline, store, userId = false, tag}) => {
-    return timelineFetcherService.startFetching({timeline, store, credentials, userId, tag})
+  const startFetchingTimeline = ({ timeline, store, userId = false, tag }) => {
+    return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag })
   }
 
-  const fetchMutes = () => apiService.fetchMutes({credentials})
-  const muteUser = (id) => apiService.muteUser({credentials, id})
-  const unmuteUser = (id) => apiService.unmuteUser({credentials, id})
-  const fetchBlocks = () => apiService.fetchBlocks({credentials})
-  const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials})
-  const fetchOAuthTokens = () => apiService.fetchOAuthTokens({credentials})
-  const revokeOAuthToken = (id) => apiService.revokeOAuthToken({id, credentials})
+  const startFetchingNotifications = ({ store }) => {
+    return notificationsFetcher.startFetching({ store, credentials })
+  }
+
+  // eslint-disable-next-line camelcase
+  const tagUser = ({ screen_name }, tag) => {
+    return apiService.tagUser({ screen_name, tag, credentials })
+  }
+
+  // eslint-disable-next-line camelcase
+  const untagUser = ({ screen_name }, tag) => {
+    return apiService.untagUser({ screen_name, tag, credentials })
+  }
+
+  // eslint-disable-next-line camelcase
+  const addRight = ({ screen_name }, right) => {
+    return apiService.addRight({ screen_name, right, credentials })
+  }
+
+  // eslint-disable-next-line camelcase
+  const deleteRight = ({ screen_name }, right) => {
+    return apiService.deleteRight({ screen_name, right, credentials })
+  }
+
+  // eslint-disable-next-line camelcase
+  const setActivationStatus = ({ screen_name }, status) => {
+    return apiService.setActivationStatus({ screen_name, status, credentials })
+  }
+
+  // eslint-disable-next-line camelcase
+  const deleteUser = ({ screen_name }) => {
+    return apiService.deleteUser({ screen_name, credentials })
+  }
+
+  const vote = (pollId, choices) => {
+    return apiService.vote({ credentials, pollId, choices })
+  }
+
+  const fetchPoll = (pollId) => {
+    return apiService.fetchPoll({ credentials, pollId })
+  }
+
+  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 })
+  const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
+  const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
+  const fetchBlocks = () => apiService.fetchBlocks({ credentials })
+  const fetchFollowRequests = () => apiService.fetchFollowRequests({ credentials })
+  const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
+  const revokeOAuthToken = (id) => apiService.revokeOAuthToken({ id, credentials })
+  const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
+  const pinOwnStatus = (id) => apiService.pinOwnStatus({ credentials, id })
+  const unpinOwnStatus = (id) => apiService.unpinOwnStatus({ credentials, id })
 
   const getCaptcha = () => apiService.getCaptcha()
-  const register = (params) => apiService.register(params)
-  const updateAvatar = ({params}) => apiService.updateAvatar({credentials, params})
-  const updateBg = ({params}) => apiService.updateBg({credentials, params})
-  const updateBanner = ({params}) => apiService.updateBanner({credentials, params})
-  const updateProfile = ({params}) => apiService.updateProfile({credentials, params})
+  const register = (params) => apiService.register({ credentials, params })
+  const updateAvatar = ({ avatar }) => apiService.updateAvatar({ credentials, avatar })
+  const updateBg = ({ background }) => apiService.updateBg({ credentials, background })
+  const updateBanner = ({ banner }) => apiService.updateBanner({ credentials, banner })
+  const updateProfile = ({ params }) => apiService.updateProfile({ credentials, params })
 
-  const externalProfile = (profileUrl) => apiService.externalProfile({profileUrl, credentials})
-  const followImport = ({params}) => apiService.followImport({params, credentials})
+  const externalProfile = (profileUrl) => apiService.externalProfile({ profileUrl, credentials })
 
-  const deleteAccount = ({password}) => apiService.deleteAccount({credentials, password})
-  const changePassword = ({password, newPassword, newPasswordConfirmation}) => apiService.changePassword({credentials, password, newPassword, newPasswordConfirmation})
+  const importBlocks = (file) => apiService.importBlocks({ file, credentials })
+  const importFollows = (file) => apiService.importFollows({ file, 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 })
+
+  const favorite = (id) => apiService.favorite({ id, credentials })
+  const unfavorite = (id) => apiService.unfavorite({ id, credentials })
+  const retweet = (id) => apiService.retweet({ id, credentials })
+  const unretweet = (id) => apiService.unretweet({ id, credentials })
+  const search2 = ({ q, resolve, limit, offset, following }) =>
+    apiService.search2({ credentials, q, resolve, limit, offset, following })
+  const searchUsers = (query) => apiService.searchUsers({ query, credentials })
 
   const backendInteractorServiceInstance = {
     fetchStatus,
@@ -95,15 +164,26 @@ const backendInteractorService = (credentials) => {
     unblockUser,
     fetchUser,
     fetchUserRelationship,
-    fetchAllFollowing,
     verifyCredentials: apiService.verifyCredentials,
-    startFetching,
+    startFetchingTimeline,
+    startFetchingNotifications,
     fetchMutes,
     muteUser,
     unmuteUser,
+    subscribeUser,
+    unsubscribeUser,
     fetchBlocks,
     fetchOAuthTokens,
     revokeOAuthToken,
+    fetchPinnedStatuses,
+    pinOwnStatus,
+    unpinOwnStatus,
+    tagUser,
+    untagUser,
+    addRight,
+    deleteRight,
+    deleteUser,
+    setActivationStatus,
     register,
     getCaptcha,
     updateAvatar,
@@ -111,12 +191,30 @@ const backendInteractorService = (credentials) => {
     updateBanner,
     updateProfile,
     externalProfile,
-    followImport,
+    importBlocks,
+    importFollows,
     deleteAccount,
     changePassword,
+    fetchSettingsMFA,
+    generateMfaBackupCodes,
+    mfaSetupOTP,
+    mfaConfirmOTP,
+    mfaDisableOTP,
     fetchFollowRequests,
     approveUser,
-    denyUser
+    denyUser,
+    vote,
+    fetchPoll,
+    fetchFavoritedByUsers,
+    fetchRebloggedByUsers,
+    reportUser,
+    favorite,
+    unfavorite,
+    retweet,
+    unretweet,
+    updateNotificationSettings,
+    search2,
+    searchUsers
   }
 
   return backendInteractorServiceInstance
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 7576c518..d1b17c61 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -59,7 +59,7 @@ const srgbToLinear = (srgb) => {
  * @returns {Number} relative luminance
  */
 const relativeLuminance = (srgb) => {
-  const {r, g, b} = srgbToLinear(srgb)
+  const { r, g, b } = srgbToLinear(srgb)
   return 0.2126 * r + 0.7152 * g + 0.0722 * b
 }
 
diff --git a/src/services/completion/completion.js b/src/services/completion/completion.js
index 11c45867..df83d03d 100644
--- a/src/services/completion/completion.js
+++ b/src/services/completion/completion.js
@@ -8,7 +8,7 @@ export const wordAtPosition = (str, pos) => {
   const words = splitIntoWords(str)
   const wordsWithPosition = addPositionToWords(words)
 
-  return find(wordsWithPosition, ({start, end}) => start <= pos && end > pos)
+  return find(wordsWithPosition, ({ start, end }) => start <= pos && end > pos)
 }
 
 export const addPositionToWords = (words) => {
diff --git a/src/services/date_utils/date_utils.js b/src/services/date_utils/date_utils.js
new file mode 100644
index 00000000..32e13bca
--- /dev/null
+++ b/src/services/date_utils/date_utils.js
@@ -0,0 +1,45 @@
+export const SECOND = 1000
+export const MINUTE = 60 * SECOND
+export const HOUR = 60 * MINUTE
+export const DAY = 24 * HOUR
+export const WEEK = 7 * DAY
+export const MONTH = 30 * DAY
+export const YEAR = 365.25 * DAY
+
+export const relativeTime = (date, nowThreshold = 1) => {
+  if (typeof date === 'string') date = Date.parse(date)
+  const round = Date.now() > date ? Math.floor : Math.ceil
+  const d = Math.abs(Date.now() - date)
+  let r = { num: round(d / YEAR), key: 'time.years' }
+  if (d < nowThreshold * SECOND) {
+    r.num = 0
+    r.key = 'time.now'
+  } else if (d < MINUTE) {
+    r.num = round(d / SECOND)
+    r.key = 'time.seconds'
+  } else if (d < HOUR) {
+    r.num = round(d / MINUTE)
+    r.key = 'time.minutes'
+  } else if (d < DAY) {
+    r.num = round(d / HOUR)
+    r.key = 'time.hours'
+  } else if (d < WEEK) {
+    r.num = round(d / DAY)
+    r.key = 'time.days'
+  } else if (d < MONTH) {
+    r.num = round(d / WEEK)
+    r.key = 'time.weeks'
+  } else if (d < YEAR) {
+    r.num = round(d / MONTH)
+    r.key = 'time.months'
+  }
+  // Remove plural form when singular
+  if (r.num === 1) r.key = r.key.slice(0, -1)
+  return r
+}
+
+export const relativeTimeShort = (date, nowThreshold = 1) => {
+  const r = relativeTime(date, nowThreshold)
+  r.key += '_short'
+  return r
+}
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index ea57e6b2..de6664d1 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -33,16 +33,17 @@ export const parseUser = (data) => {
 
   if (masto) {
     output.screen_name = data.acct
+    output.statusnet_profile_url = data.url
 
     // There's nothing else to get
     if (mastoShort) {
       return output
     }
 
-    // output.name = ??? missing
+    output.name = data.display_name
     output.name_html = addEmojis(data.display_name, data.emojis)
 
-    // output.description = ??? missing
+    output.description = data.note
     output.description_html = addEmojis(data.note, data.emojis)
 
     // Utilize avatar_static for gif avatars?
@@ -56,20 +57,47 @@ export const parseUser = (data) => {
 
     output.bot = data.bot
 
-    output.statusnet_profile_url = data.url
-
     if (data.pleroma) {
       const relationship = data.pleroma.relationship
 
+      output.background_image = data.pleroma.background_image
+      output.token = data.pleroma.chat_token
+
       if (relationship) {
         output.follows_you = relationship.followed_by
         output.following = relationship.following
         output.statusnet_blocking = relationship.blocking
         output.muted = relationship.muting
+        output.subscribed = relationship.subscribing
+      }
+
+      output.hide_follows = data.pleroma.hide_follows
+      output.hide_followers = data.pleroma.hide_followers
+
+      output.rights = {
+        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'
       }
     }
 
-    // Missing, trying to recover
+    if (data.source) {
+      output.description = data.source.note
+      output.default_scope = data.source.privacy
+      if (data.source.pleroma) {
+        output.no_rich_text = data.source.pleroma.no_rich_text
+        output.show_role = data.source.pleroma.show_role
+      }
+    }
+
+    // TODO: handle is_local
     output.is_local = !output.screen_name.includes('@')
   } else {
     output.screen_name = data.screen_name
@@ -101,9 +129,12 @@ export const parseUser = (data) => {
 
     output.muted = data.muted
 
-    // QVITTER ONLY FOR NOW
-    // Really only applies to logged in user, really.. I THINK
-    output.rights = data.rights
+    if (data.rights) {
+      output.rights = {
+        moderator: data.rights.delete_others_notice,
+        admin: data.rights.admin
+      }
+    }
     output.no_rich_text = data.no_rich_text
     output.default_scope = data.default_scope
     output.hide_follows = data.hide_follows
@@ -119,12 +150,23 @@ export const parseUser = (data) => {
   output.locked = data.locked
   output.followers_count = data.followers_count
   output.statuses_count = data.statuses_count
-  output.friends = []
-  output.followers = []
+  output.friendIds = []
+  output.followerIds = []
+  output.pinnedStatuseIds = []
+
   if (data.pleroma) {
     output.follow_request_count = data.pleroma.follow_request_count
+
+    output.tags = data.pleroma.tags
+    output.deactivated = data.pleroma.deactivated
+
+    output.notification_settings = data.pleroma.notification_settings
   }
 
+  output.tags = output.tags || []
+  output.rights = output.rights || {}
+  output.notification_settings = output.notification_settings || {}
+
   return output
 }
 
@@ -151,7 +193,7 @@ export const addEmojis = (string, emojis) => {
   return emojis.reduce((acc, emoji) => {
     return acc.replace(
       new RegExp(`:${emoji.shortcode}:`, 'g'),
-      `<img src='${emoji.url}' alt='${emoji.shortcode}' class='emoji' />`
+      `<img src='${emoji.url}' alt='${emoji.shortcode}' title='${emoji.shortcode}' class='emoji' />`
     )
   }, string)
 }
@@ -172,28 +214,32 @@ export const parseStatus = (data) => {
 
     output.statusnet_html = addEmojis(data.content, data.emojis)
 
-    // Not exactly the same but works?
-    output.text = data.content
+    output.tags = data.tags
+
+    if (data.pleroma) {
+      const { pleroma } = data
+      output.text = pleroma.content ? data.pleroma.content['text/plain'] : data.content
+      output.summary = pleroma.spoiler_text ? data.pleroma.spoiler_text['text/plain'] : data.spoiler_text
+      output.statusnet_conversation_id = data.pleroma.conversation_id
+      output.is_local = pleroma.local
+      output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
+    } else {
+      output.text = data.content
+      output.summary = data.spoiler_text
+    }
 
     output.in_reply_to_status_id = data.in_reply_to_id
     output.in_reply_to_user_id = data.in_reply_to_account_id
     output.replies_count = data.replies_count
 
-    // Missing!! fix in UI?
-    // output.in_reply_to_screen_name = ???
-
-    // Not exactly the same but works
-    output.statusnet_conversation_id = data.id
-
     if (output.type === 'retweet') {
       output.retweeted_status = parseStatus(data.reblog)
     }
 
-    output.summary = data.spoiler_text
     output.summary_html = addEmojis(data.spoiler_text, data.emojis)
     output.external_url = data.url
-
-    // output.is_local = ??? missing
+    output.poll = data.poll
+    output.pinned = data.pinned
   } else {
     output.favorited = data.favorited
     output.fave_num = data.fave_num
@@ -221,7 +267,6 @@ export const parseStatus = (data) => {
     output.in_reply_to_status_id = data.in_reply_to_status_id
     output.in_reply_to_user_id = data.in_reply_to_user_id
     output.in_reply_to_screen_name = data.in_reply_to_screen_name
-
     output.statusnet_conversation_id = data.statusnet_conversation_id
 
     if (output.type === 'retweet') {
@@ -259,6 +304,9 @@ export const parseStatus = (data) => {
     output.retweeted_status = parseStatus(retweetedStatus)
   }
 
+  output.favoritedBy = []
+  output.rebloggedBy = []
+
   return output
 }
 
@@ -272,9 +320,11 @@ export const parseNotification = (data) => {
 
   if (masto) {
     output.type = mastoDict[data.type] || data.type
-    // output.seen = ??? missing
-    output.status = parseStatus(data.status)
-    output.action = output.status // not sure
+    output.seen = data.pleroma.is_seen
+    output.status = output.type === 'follow'
+      ? null
+      : parseStatus(data.status)
+    output.action = output.status // TODO: Refactor, this is unneeded
     output.from_profile = parseUser(data.account)
   } else {
     const parsedNotice = parseStatus(data.notice)
@@ -288,7 +338,7 @@ export const parseNotification = (data) => {
   }
 
   output.created_at = new Date(data.created_at)
-  output.id = data.id
+  output.id = parseInt(data.id)
 
   return output
 }
diff --git a/src/services/file_size_format/file_size_format.js b/src/services/file_size_format/file_size_format.js
index add56ee0..7e6cd4d7 100644
--- a/src/services/file_size_format/file_size_format.js
+++ b/src/services/file_size_format/file_size_format.js
@@ -9,7 +9,7 @@ const fileSizeFormat = (num) => {
   exponent = Math.min(Math.floor(Math.log(num) / Math.log(1024)), units.length - 1)
   num = (num / Math.pow(1024, exponent)).toFixed(2) * 1
   unit = units[exponent]
-  return {num: num, unit: unit}
+  return { num: num, unit: unit }
 }
 const fileSizeFormatService = {
   fileSizeFormat
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index 51dafe84..b2486e7c 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -23,18 +23,12 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
 
       // For locked users we just mark it that we sent the follow request
       if (updated.locked) {
-        resolve({
-          sent: true,
-          updated
-        })
+        resolve({ sent: true })
       }
 
       if (updated.following) {
         // If we get result immediately, just stop.
-        resolve({
-          sent: false,
-          updated
-        })
+        resolve({ sent: false })
       }
 
       // But usually we don't get result immediately, so we ask server
@@ -48,16 +42,10 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
         .then((following) => {
           if (following) {
             // We confirmed and everything's good.
-            resolve({
-              sent: false,
-              updated
-            })
+            resolve({ sent: false })
           } else {
             // If after all the tries, just treat it as if user is locked
-            resolve({
-              sent: false,
-              updated
-            })
+            resolve({ sent: false })
           }
         })
     })
diff --git a/src/services/follow_request_fetcher/follow_request_fetcher.service.js b/src/services/follow_request_fetcher/follow_request_fetcher.service.js
index 125ff3e1..786740b7 100644
--- a/src/services/follow_request_fetcher/follow_request_fetcher.service.js
+++ b/src/services/follow_request_fetcher/follow_request_fetcher.service.js
@@ -8,7 +8,7 @@ const fetchAndUpdate = ({ store, credentials }) => {
     .catch(() => {})
 }
 
-const startFetching = ({credentials, store}) => {
+const startFetching = ({ credentials, store }) => {
   fetchAndUpdate({ credentials, store })
   const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
   return setInterval(boundFetchAndUpdate, 10000)
diff --git a/src/services/new_api/mfa.js b/src/services/new_api/mfa.js
new file mode 100644
index 00000000..cbba06d5
--- /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..d0d18c03 100644
--- a/src/services/new_api/oauth.js
+++ b/src/services/new_api/oauth.js
@@ -1,51 +1,57 @@
-import {reduce} from 'lodash'
+import { reduce } from 'lodash'
+
+const REDIRECT_URI = `${window.location.origin}/oauth-callback`
+
+export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) => {
+  if (clientId && clientSecret) {
+    return Promise.resolve({ clientId, clientSecret })
+  }
 
-const getOrCreateApp = ({oauth, instance}) => {
   const url = `${instance}/api/v1/apps`
   const form = new window.FormData()
 
-  form.append('client_name', `PleromaFE_${Math.random()}`)
-  form.append('redirect_uris', `${window.location.origin}/oauth-callback`)
+  form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`)
+  form.append('redirect_uris', REDIRECT_URI)
   form.append('scopes', 'read write follow')
 
   return window.fetch(url, {
     method: 'POST',
     body: form
-  }).then((data) => data.json())
-}
-const login = (args) => {
-  getOrCreateApp(args).then((app) => {
-    args.commit('setClientData', app)
-
-    const data = {
-      response_type: 'code',
-      client_id: app.client_id,
-      redirect_uri: app.redirect_uri,
-      scope: 'read write follow'
-    }
-
-    const dataString = reduce(data, (acc, v, k) => {
-      const encoded = `${k}=${encodeURIComponent(v)}`
-      if (!acc) {
-        return encoded
-      } else {
-        return `${acc}&${encoded}`
-      }
-    }, false)
-
-    // Do the redirect...
-    const url = `${args.instance}/oauth/authorize?${dataString}`
-
-    window.location.href = url
   })
+    .then((data) => data.json())
+    .then((app) => ({ clientId: app.client_id, clientSecret: app.client_secret }))
+    .then((app) => commit('setClientData', app) || app)
 }
 
-const getTokenWithCredentials = ({app, instance, username, password}) => {
+const login = ({ instance, clientId }) => {
+  const data = {
+    response_type: 'code',
+    client_id: clientId,
+    redirect_uri: REDIRECT_URI,
+    scope: 'read write follow'
+  }
+
+  const dataString = reduce(data, (acc, v, k) => {
+    const encoded = `${k}=${encodeURIComponent(v)}`
+    if (!acc) {
+      return encoded
+    } else {
+      return `${acc}&${encoded}`
+    }
+  }, false)
+
+  // Do the redirect...
+  const url = `${instance}/oauth/authorize?${dataString}`
+
+  window.location.href = url
+}
+
+const getTokenWithCredentials = ({ clientId, clientSecret, instance, username, password }) => {
   const url = `${instance}/oauth/token`
   const form = new window.FormData()
 
-  form.append('client_id', app.client_id)
-  form.append('client_secret', app.client_secret)
+  form.append('client_id', clientId)
+  form.append('client_secret', clientSecret)
   form.append('grant_type', 'password')
   form.append('username', username)
   form.append('password', password)
@@ -56,15 +62,76 @@ const getTokenWithCredentials = ({app, instance, username, password}) => {
   }).then((data) => data.json())
 }
 
-const getToken = ({app, instance, code}) => {
+const getToken = ({ clientId, clientSecret, instance, code }) => {
   const url = `${instance}/oauth/token`
   const form = new window.FormData()
 
+  form.append('client_id', clientId)
+  form.append('client_secret', clientSecret)
+  form.append('grant_type', 'authorization_code')
+  form.append('code', code)
+  form.append('redirect_uri', `${window.location.origin}/oauth-callback`)
+
+  return window.fetch(url, {
+    method: 'POST',
+    body: form
+  })
+    .then((data) => data.json())
+}
+
+export const getClientToken = ({ clientId, clientSecret, instance }) => {
+  const url = `${instance}/oauth/token`
+  const form = new window.FormData()
+
+  form.append('client_id', clientId)
+  form.append('client_secret', clientSecret)
+  form.append('grant_type', 'client_credentials')
+  form.append('redirect_uri', `${window.location.origin}/oauth-callback`)
+
+  return window.fetch(url, {
+    method: 'POST',
+    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('grant_type', 'authorization_code')
+  form.append('mfa_token', mfaToken)
   form.append('code', code)
-  form.append('redirect_uri', `${window.location.origin}/oauth-callback`)
+  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 revokeToken = ({ app, instance, token }) => {
+  const url = `${instance}/oauth/revoke`
+  const form = new window.FormData()
+
+  form.append('client_id', app.clientId)
+  form.append('client_secret', app.clientSecret)
+  form.append('token', token)
 
   return window.fetch(url, {
     method: 'POST',
@@ -76,7 +143,10 @@ const oauth = {
   login,
   getToken,
   getTokenWithCredentials,
-  getOrCreateApp
+  getOrCreateApp,
+  verifyOTPCode,
+  verifyRecoveryCode,
+  revokeToken
 }
 
 export default oauth
diff --git a/src/services/new_api/user_search.js b/src/services/new_api/user_search.js
deleted file mode 100644
index 869afa9c..00000000
--- a/src/services/new_api/user_search.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import utils from './utils.js'
-import { parseUser } from '../entity_normalizer/entity_normalizer.service.js'
-
-const search = ({query, store}) => {
-  return utils.request({
-    store,
-    url: '/api/v1/accounts/search',
-    params: {
-      q: query
-    }
-  })
-  .then((data) => data.json())
-  .then((data) => data.map(parseUser))
-}
-const UserSearch = {
-  search
-}
-
-export default UserSearch
diff --git a/src/services/new_api/utils.js b/src/services/new_api/utils.js
deleted file mode 100644
index 078f392f..00000000
--- a/src/services/new_api/utils.js
+++ /dev/null
@@ -1,36 +0,0 @@
-const queryParams = (params) => {
-  return Object.keys(params)
-    .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
-    .join('&')
-}
-
-const headers = (store) => {
-  const accessToken = store.state.oauth.token
-  if (accessToken) {
-    return {'Authorization': `Bearer ${accessToken}`}
-  } else {
-    return {}
-  }
-}
-
-const request = ({method = 'GET', url, params, store}) => {
-  const instance = store.state.instance.server
-  let fullUrl = `${instance}${url}`
-
-  if (method === 'GET' && params) {
-    fullUrl = fullUrl + `?${queryParams(params)}`
-  }
-
-  return window.fetch(fullUrl, {
-    method,
-    headers: headers(store),
-    credentials: 'same-origin'
-  })
-}
-
-const utils = {
-  queryParams,
-  request
-}
-
-export default utils
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index cd8f3f9e..7021adbd 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -10,8 +10,8 @@ export const visibleTypes = store => ([
 ].filter(_ => _))
 
 const sortById = (a, b) => {
-  const seqA = Number(a.action.id)
-  const seqB = Number(b.action.id)
+  const seqA = Number(a.id)
+  const seqB = Number(b.id)
   const isSeqA = !Number.isNaN(seqA)
   const isSeqB = !Number.isNaN(seqB)
   if (isSeqA && isSeqB) {
@@ -21,16 +21,18 @@ const sortById = (a, b) => {
   } else if (!isSeqA && isSeqB) {
     return -1
   } else {
-    return a.action.id > b.action.id ? -1 : 1
+    return a.id > b.id ? -1 : 1
   }
 }
 
-export const visibleNotificationsFromStore = store => {
+export const visibleNotificationsFromStore = (store, types) => {
   // map is just to clone the array since sort mutates it and it causes some issues
   let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById)
   sortedNotifications = sortBy(sortedNotifications, 'seen')
-  return sortedNotifications.filter((notification) => visibleTypes(store).includes(notification.type))
+  return sortedNotifications.filter(
+    (notification) => (types || visibleTypes(store)).includes(notification.type)
+  )
 }
 
 export const unseenNotificationsFromStore = store =>
-  filter(visibleNotificationsFromStore(store), ({seen}) => !seen)
+  filter(visibleNotificationsFromStore(store), ({ seen }) => !seen)
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index 3ecdae6a..f9ec3f6e 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -1,45 +1,51 @@
 import apiService from '../api/api.service.js'
 
-const update = ({store, notifications, older}) => {
+const update = ({ store, notifications, older }) => {
   store.dispatch('setNotificationsError', { value: false })
 
   store.dispatch('addNewNotifications', { notifications, older })
 }
 
-const fetchAndUpdate = ({store, credentials, older = false}) => {
+const fetchAndUpdate = ({ store, credentials, older = false }) => {
   const args = { credentials }
   const rootState = store.rootState || store.state
   const timelineData = rootState.statuses.notifications
 
+  args['timeline'] = 'notifications'
   if (older) {
     if (timelineData.minId !== Number.POSITIVE_INFINITY) {
       args['until'] = timelineData.minId
     }
+    return fetchNotifications({ store, args, older })
   } else {
-    // load unread notifications repeadedly to provide consistency between browser tabs
+    // fetch new notifications
+    if (timelineData.maxId !== Number.POSITIVE_INFINITY) {
+      args['since'] = timelineData.maxId
+    }
+    const result = fetchNotifications({ store, args, older })
+
+    // load unread notifications repeatedly to provide consistency between browser tabs
     const notifications = timelineData.data
     const unread = notifications.filter(n => !n.seen).map(n => n.id)
-    if (!unread.length) {
-      args['since'] = timelineData.maxId
-    } else {
-      args['since'] = Math.min(...unread) - 1
-      if (timelineData.maxId !== Math.max(...unread)) {
-        args['until'] = Math.max(...unread, args['since'] + 20)
-      }
+    if (unread.length) {
+      args['since'] = Math.min(...unread)
+      fetchNotifications({ store, args, older })
     }
+
+    return result
   }
+}
 
-  args['timeline'] = 'notifications'
-
+const fetchNotifications = ({ store, args, older }) => {
   return apiService.fetchTimeline(args)
     .then((notifications) => {
-      update({store, notifications, older})
+      update({ store, notifications, older })
       return notifications
     }, () => store.dispatch('setNotificationsError', { value: true }))
     .catch(() => store.dispatch('setNotificationsError', { value: true }))
 }
 
-const startFetching = ({credentials, store}) => {
+const startFetching = ({ credentials, store }) => {
   fetchAndUpdate({ credentials, store })
   const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
   // Initially there's set flag to silence all desktop notifications so
diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js
index e70b0f26..9e904d3a 100644
--- a/src/services/status_poster/status_poster.service.js
+++ b/src/services/status_poster/status_poster.service.js
@@ -1,10 +1,19 @@
 import { map } from 'lodash'
 import apiService from '../api/api.service.js'
 
-const postStatus = ({ store, status, spoilerText, visibility, sensitive, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => {
+const postStatus = ({ store, status, spoilerText, visibility, sensitive, poll, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => {
   const mediaIds = map(media, 'id')
 
-  return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType})
+  return apiService.postStatus({
+    credentials: store.state.users.currentUser.credentials,
+    status,
+    spoilerText,
+    visibility,
+    sensitive,
+    mediaIds,
+    inReplyToStatusId,
+    contentType,
+    poll })
     .then((data) => {
       if (!data.error) {
         store.dispatch('addNewStatuses', {
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index d0b6ccbf..f186d202 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -202,6 +202,7 @@ const generateColors = (input) => {
   colors.topBarLink = col.topBarLink || getTextColor(colors.topBar, colors.fgLink)
 
   colors.faintLink = col.faintLink || Object.assign({}, col.link)
+  colors.linkBg = alphaBlend(colors.link, 0.4, colors.bg)
 
   colors.icon = mixrgb(colors.bg, colors.text)
 
@@ -238,12 +239,12 @@ const generateColors = (input) => {
   })
 
   const htmlColors = Object.entries(colors)
-        .reduce((acc, [k, v]) => {
-          if (!v) return acc
-          acc.solid[k] = rgb2hex(v)
-          acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgb2rgba(v)
-          return acc
-        }, { complete: {}, solid: {} })
+    .reduce((acc, [k, v]) => {
+      if (!v) return acc
+      acc.solid[k] = rgb2hex(v)
+      acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgb2rgba(v)
+      return acc
+    }, { complete: {}, solid: {} })
   return {
     rules: {
       colors: Object.entries(htmlColors.complete)
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index 8e954cdf..f72688f8 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -2,7 +2,7 @@ import { camelCase } from 'lodash'
 
 import apiService from '../api/api.service.js'
 
-const update = ({store, statuses, timeline, showImmediately, userId}) => {
+const update = ({ store, statuses, timeline, showImmediately, userId }) => {
   const ccTimeline = camelCase(timeline)
 
   store.dispatch('setError', { value: false })
@@ -15,7 +15,7 @@ const update = ({store, statuses, timeline, showImmediately, userId}) => {
   })
 }
 
-const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false, showImmediately = false, userId = false, tag = false, until}) => {
+const fetchAndUpdate = ({ store, credentials, timeline = 'friends', older = false, showImmediately = false, userId = false, tag = false, until }) => {
   const args = { timeline, credentials }
   const rootState = store.rootState || store.state
   const timelineData = rootState.statuses.timelines[camelCase(timeline)]
@@ -40,17 +40,17 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false
       if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {
         store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId })
       }
-      update({store, statuses, timeline, showImmediately, userId})
+      update({ store, statuses, timeline, showImmediately, userId })
       return statuses
     }, () => store.dispatch('setError', { value: true }))
 }
 
-const startFetching = ({timeline = 'friends', credentials, store, userId = false, tag = false}) => {
+const startFetching = ({ timeline = 'friends', credentials, store, userId = false, tag = false }) => {
   const rootState = store.rootState || store.state
   const timelineData = rootState.statuses.timelines[camelCase(timeline)]
   const showImmediately = timelineData.visibleStatuses.length === 0
   timelineData.userId = userId
-  fetchAndUpdate({timeline, credentials, store, showImmediately, userId, tag})
+  fetchAndUpdate({ timeline, credentials, store, showImmediately, userId, tag })
   const boundFetchAndUpdate = () => fetchAndUpdate({ timeline, credentials, store, userId, tag })
   return setInterval(boundFetchAndUpdate, 10000)
 }
diff --git a/src/services/user_highlighter/user_highlighter.js b/src/services/user_highlighter/user_highlighter.js
index f6ddfb9c..b91c0f78 100644
--- a/src/services/user_highlighter/user_highlighter.js
+++ b/src/services/user_highlighter/user_highlighter.js
@@ -1,7 +1,7 @@
 import { hex2rgb } from '../color_convert/color_convert.js'
 const highlightStyle = (prefs) => {
   if (prefs === undefined) return
-  const {color, type} = prefs
+  const { color, type } = prefs
   if (typeof color !== 'string') return
   const rgb = hex2rgb(color)
   if (rgb == null) return
diff --git a/src/services/version/version.service.js b/src/services/version/version.service.js
index a750b0dd..2e11bf3a 100644
--- a/src/services/version/version.service.js
+++ b/src/services/version/version.service.js
@@ -1,6 +1,6 @@
 
 export const extractCommit = versionString => {
-  const regex = /-g(\w+)$/i
+  const regex = /-g(\w+)/i
   const matches = versionString.match(regex)
   return matches ? matches[1] : ''
 }
diff --git a/static/config.json b/static/config.json
index 04cbb97b..5cdb33a0 100644
--- a/static/config.json
+++ b/static/config.json
@@ -8,7 +8,6 @@
   "redirectRootLogin": "/main/friends",
   "chatDisabled": false,
   "showInstanceSpecificPanel": false,
-  "formattingOptionsEnabled": false,
   "collapseMessageWithSubject": false,
   "scopeCopy": true,
   "subjectLineBehavior": "email",
diff --git a/static/font/LICENSE.txt b/static/font/LICENSE.txt
old mode 100644
new mode 100755
diff --git a/static/font/README.txt b/static/font/README.txt
old mode 100644
new mode 100755
diff --git a/static/font/config.json b/static/font/config.json
index 380912c9..6a68019c 100755
--- a/static/font/config.json
+++ b/static/font/config.json
@@ -150,12 +150,6 @@
       "code": 61669,
       "src": "fontawesome"
     },
-    {
-      "uid": "cd21cbfb28ad4d903cede582157f65dc",
-      "css": "bell",
-      "code": 59408,
-      "src": "fontawesome"
-    },
     {
       "uid": "ccc2329632396dc096bb638d4b46fb98",
       "css": "mail-alt",
@@ -241,9 +235,9 @@
       "src": "fontawesome"
     },
     {
-      "uid": "c64623255a4a7c72436b199b05296c4f",
-      "css": "emo-happy",
-      "code": 59417,
+      "uid": "266d5d9adf15a61800477a5acf9a4462",
+      "css": "chart-bar",
+      "code": 59419,
       "src": "fontelico"
     },
     {
@@ -257,6 +251,46 @@
       "css": "bell-alt",
       "code": 61683,
       "src": "fontawesome"
+    },
+    {
+      "uid": "5bb103cd29de77e0e06a52638527b575",
+      "css": "wrench",
+      "code": 59418,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "5b0772e9484a1a11646793a82edd622a",
+      "css": "pin",
+      "code": 59417,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "22411a88489225a018f68db737de3c77",
+      "css": "ellipsis",
+      "code": 61761,
+      "src": "custom_icons",
+      "selected": true,
+      "svg": {
+        "path": "M214 411V518Q214 540 199 556T161 571H54Q31 571 16 556T0 518V411Q0 388 16 373T54 357H161Q183 357 199 373T214 411ZM500 411V518Q500 540 484 556T446 571H339Q317 571 301 556T286 518V411Q286 388 301 373T339 357H446Q469 357 484 373T500 411ZM786 411V518Q786 540 770 556T732 571H625Q603 571 587 556T571 518V411Q571 388 587 373T625 357H732Q755 357 770 373T786 411Z",
+        "width": 785.7
+      },
+      "search": [
+        "ellipsis"
+      ]
+    },
+    {
+      "uid": "0bef873af785ead27781fdf98b3ae740",
+      "css": "bell-ringing-o",
+      "code": 59408,
+      "src": "custom_icons",
+      "selected": true,
+      "svg": {
+        "path": "M497.8 0C468.3 0 444.4 23.9 444.4 53.3 444.4 61.1 446.1 68.3 448.9 75 301.7 96.7 213.3 213.3 213.3 320 213.3 588.3 117.8 712.8 35.6 782.2 35.6 821.1 67.8 853.3 106.7 853.3H355.6C355.6 931.7 419.4 995.6 497.8 995.6S640 931.7 640 853.3H888.9C927.8 853.3 960 821.1 960 782.2 877.8 712.8 782.2 588.3 782.2 320 782.2 213.3 693.9 96.7 546.7 75 549.4 68.3 551.1 61.1 551.1 53.3 551.1 23.9 527.2 0 497.8 0ZM189.4 44.8C108.4 118.6 70.5 215.1 71.1 320.2L142.2 319.8C141.7 231.2 170.4 158.3 237.3 97.4L189.4 44.8ZM806.2 44.8L758.3 97.4C825.2 158.3 853.9 231.2 853.3 319.8L924.4 320.2C925.1 215.1 887.2 118.6 806.2 44.8ZM408.9 844.4C413.9 844.4 417.8 848.3 417.8 853.3 417.8 897.2 453.9 933.3 497.8 933.3 502.8 933.3 506.7 937.2 506.7 942.2S502.8 951.1 497.8 951.1C443.9 951.1 400 907.2 400 853.3 400 848.3 403.9 844.4 408.9 844.4Z",
+        "width": 1000
+      },
+      "search": [
+        "bell-ringing-o"
+      ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/static/font/css/fontello-codes.css b/static/font/css/fontello-codes.css
index b19319a7..273fce35 100755
--- a/static/font/css/fontello-codes.css
+++ b/static/font/css/fontello-codes.css
@@ -15,7 +15,7 @@
 .icon-right-open:before { content: '\e80d'; } /* '' */
 .icon-left-open:before { content: '\e80e'; } /* '' */
 .icon-up-open:before { content: '\e80f'; } /* '' */
-.icon-bell:before { content: '\e810'; } /* '' */
+.icon-bell-ringing-o:before { content: '\e810'; } /* '' */
 .icon-lock:before { content: '\e811'; } /* '' */
 .icon-globe:before { content: '\e812'; } /* '' */
 .icon-brush:before { content: '\e813'; } /* '' */
@@ -24,7 +24,9 @@
 .icon-adjust:before { content: '\e816'; } /* '' */
 .icon-edit:before { content: '\e817'; } /* '' */
 .icon-pencil:before { content: '\e818'; } /* '' */
-.icon-emo-happy:before { content: '\e819'; } /* '' */
+.icon-pin:before { content: '\e819'; } /* '' */
+.icon-wrench:before { content: '\e81a'; } /* '' */
+.icon-chart-bar:before { content: '\e81b'; } /* '' */
 .icon-spin3:before { content: '\e832'; } /* '' */
 .icon-spin4:before { content: '\e834'; } /* '' */
 .icon-link-ext:before { content: '\f08e'; } /* '' */
@@ -35,8 +37,8 @@
 .icon-bell-alt:before { content: '\f0f3'; } /* '' */
 .icon-plus-squared:before { content: '\f0fe'; } /* '' */
 .icon-reply:before { content: '\f112'; } /* '' */
-.icon-smile:before { content: '\f118'; } /* '' */
 .icon-lock-open-alt:before { content: '\f13e'; } /* '' */
+.icon-ellipsis:before { content: '\f141'; } /* '' */
 .icon-play-circled:before { content: '\f144'; } /* '' */
 .icon-thumbs-up-alt:before { content: '\f164'; } /* '' */
 .icon-binoculars:before { content: '\f1e5'; } /* '' */
diff --git a/static/font/css/fontello-embedded.css b/static/font/css/fontello-embedded.css
index 431da336..44b26e90 100755
--- a/static/font/css/fontello-embedded.css
+++ b/static/font/css/fontello-embedded.css
@@ -1,15 +1,15 @@
 @font-face {
   font-family: 'fontello';
-  src: url('../font/fontello.eot?24909640');
-  src: url('../font/fontello.eot?24909640#iefix') format('embedded-opentype'),
-       url('../font/fontello.svg?24909640#fontello') format('svg');
+  src: url('../font/fontello.eot?36125818');
+  src: url('../font/fontello.eot?36125818#iefix') format('embedded-opentype'),
+       url('../font/fontello.svg?36125818#fontello') format('svg');
   font-weight: normal;
   font-style: normal;
 }
 @font-face {
   font-family: 'fontello';
-  src: url('data:application/octet-stream;base64,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') format('woff'),
-       url('data:application/octet-stream;base64,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') format('truetype');
+  src: url('data:application/octet-stream;base64,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') format('woff'),
+       url('data:application/octet-stream;base64,AAEAAAAPAIAAAwBwR1NVQiCLJXoAAAD8AAAAVE9TLzI+L1N4AAABUAAAAFZjbWFwz7H7LgAAAagAAARCY3Z0IAb//vQAAD20AAAAIGZwZ22KkZBZAAA91AAAC3BnYXNwAAAAEAAAPawAAAAIZ2x5ZjA3qD4AAAXsAAAxbmhlYWQWJnP4AAA3XAAAADZoaGVhB8kECAAAN5QAAAAkaG10eKHc/+IAADe4AAAAtGxvY2ERUxtXAAA4bAAAAFxtYXhwAYINpgAAOMgAAAAgbmFtZcydHyEAADjoAAACzXBvc3SjVL5FAAA7uAAAAfNwcmVw5UErvAAASUQAAACGAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEDmQGQAAUAAAJ6ArwAAACMAnoCvAAAAeAAMQECAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOgA8jQDWf9xAFoDZwCeAAAAAQAAAAAAAAAAAAUAAAADAAAALAAAAAQAAAIiAAEAAAAAARwAAwABAAAALAADAAoAAAIiAAQA8AAAACIAIAAEAALoG+gy6DTwj/DJ8ODw5fDz8P7xEvE+8UHxRPFk8eXyNP//AADoAOgy6DTwjvDJ8ODw5fDz8P7xEvE+8UHxRPFk8eXyNP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAiAFgAWABYAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAAAAAQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAAABBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAIgAAAAAAAAACwAAOgAAADoAAAAAAEAAOgBAADoAQAAAAIAAOgCAADoAgAAAAMAAOgDAADoAwAAAAQAAOgEAADoBAAAAAUAAOgFAADoBQAAAAYAAOgGAADoBgAAAAcAAOgHAADoBwAAAAgAAOgIAADoCAAAAAkAAOgJAADoCQAAAAoAAOgKAADoCgAAAAsAAOgLAADoCwAAAAwAAOgMAADoDAAAAA0AAOgNAADoDQAAAA4AAOgOAADoDgAAAA8AAOgPAADoDwAAABAAAOgQAADoEAAAABEAAOgRAADoEQAAABIAAOgSAADoEgAAABMAAOgTAADoEwAAABQAAOgUAADoFAAAABUAAOgVAADoFQAAABYAAOgWAADoFgAAABcAAOgXAADoFwAAABgAAOgYAADoGAAAABkAAOgZAADoGQAAABoAAOgaAADoGgAAABsAAOgbAADoGwAAABwAAOgyAADoMgAAAB0AAOg0AADoNAAAAB4AAPCOAADwjgAAAB8AAPCPAADwjwAAACAAAPDJAADwyQAAACEAAPDgAADw4AAAACIAAPDlAADw5QAAACMAAPDzAADw8wAAACQAAPD+AADw/gAAACUAAPESAADxEgAAACYAAPE+AADxPgAAACcAAPFBAADxQQAAACgAAPFEAADxRAAAACkAAPFkAADxZAAAACoAAPHlAADx5QAAACsAAPI0AADyNAAAACwAAAABAAD/9gLUAo0AJAAeQBsiGRAHBAACAUcDAQIAAm8BAQAAZhQcFBQEBRgrJRQPAQYiLwEHBiIvASY0PwEnJjQ/ATYyHwE3NjIfARYUDwEXFgLUD0wQLBCkpBAsEEwQEKSkEBBMECwQpKQQLBBMDw+kpA93FhBMDw+lpQ8PTBAsEKSkECwQTBAQpKQQEEwPLg+kpA8ABAAA/7gDoQM1AAgAEQApAEAARkBDNQEHBgkAAgIAAkcACQYJbwgBBgcGbwAHAwdvAAQAAgRUBQEDAQEAAgMAYAAEBAJYAAIEAkw9PCMzIyIyJTkYEgoFHSslNCYOAh4BNjc0Jg4CHgE2NxUUBiMhIiYnNTQ2FzMeATsBMjY3MzIWAwYrARUUBgcjIiYnNSMiJj8BNjIfARYCyhQeFAIYGhiNFCASAhYcGEYgFvzLFx4BIBbuDDYjjyI2De4WILYJGI8UD48PFAGPFxMR+goeCvoSJA4WAhIgEgQaDA4WAhIgEgQaibMWICAWsxYgAR8oKB8eAVIW+g8UARYO+iwR+goK+hEAAAAAAQAA/9EDoQNHAB8AHUAaEg8KBAMFAAIBRwACAAJvAQEAAGYdFBcDBRcrARQPARMVFA4BLwEHBiImNTQ3EycmNTQ3JTc2Mh8BBRYDoQ/KMAwVDPv6DBYMATDLDh8BGH4LIAx9ARggAfAMD8X+6QwLEAEHhIQHEgoECAEXxQ8MFQUo/hcX/igFAAIAAP/RA6EDRwAJACkAJ0AkHBkUDg0JCAcGBQMBDAACAUcAAgACbwEBAABmJSQXFhIQAwUUKwE3LwEPARcHNxcTFA8BExUUIyIvAQcGIiY1NDcTJyY1NDclNzYyHwEFFgJ7qutqaeyrKdPT/g/KMBcKDPv6DBYMATDLDh8BGH4LIAx9ARggASmmItXVIqbrb28BsgwPxf7pDBwHhIQHEgoECAEXxQ8MFQUo/hcX/igFAAAAAAIAAP//BDACgwAhAEMAQkA/IgEEBgFHAwEBBwYHAQZtCQEGBAcGBGsIAQIABwECB2AABAAABFQABAQAWAUBAAQATEJAFiElGCEWFSgTCgUdKyUUBichIiYvAS4BMxEjIi4BPwE2Mh8BFhQGByMVITIfARYlFA8BBiIvASY0NjsBNSEiLwEmNDY3ITIWHwEeARURMzIWAsoKCP3pBQYCAwECAWsPFAEIswsgDLIJFg5rAUEJBVkEAWUIsgwgC7MIFg5r/r4JBVkECggCGAQGAgMBAmsOFhIHDAECAwQBDAFPFhsK1gwM1gocFAHWBmwF4g0K1g0N1gobFtYHawUNCgECAwUCCAP+shYAAAAFAAD/ygPoArgACQAaAD4ARABXAFdAVDQbAgAEUwYCAgBSQwIBAlBCKScIAQYGAQRHAAUEBW8AAgABAAIBbQABBgABBmsABgMABgNrAAMDbgAEAAAEVAAEBABYAAAEAExMSxMuGSQUHQcFGislNy4BNzQ3BgcWATQmByIGFRQWMjY1NDYzMjY3FBUGAg8BBiMiJyY1NDcuAScmNDc+ATMyFzc2MzIWHwEWBxYTFAYHExYXFAcGBw4BIzc+ATcmJzceARcWATYrMDgBIoBVXgFqEAtGZBAWEEQwCxDKO+o7HAUKB0QJGVCGMgsLVvyXMjIfBQoDDgskCwEJFVhJnQT6CxYnVNx8KXfIRUFdIzViIAtwTyNqPUM6QYSQAWcLEAFkRQsQEAswRBB1BAFp/lppMgknBgoHKiR4TREqEoOYCjYJBgYUBgEF/v1OgBsBGBleExMkLWBqSgqEaWRAPyRiNhMAAAL///9xA6EDFAAIACEAVEAKHwEBAA4BAwECR0uwIVBYQBYABAAAAQQAYAABAAMCAQNgAAICDQJJG0AdAAIDAnAABAAAAQQAYAABAwMBVAABAQNYAAMBA0xZtxcjFBMSBQUZKwE0LgEGFBY+AQEUBiIvAQYjIi4CPgQeAhcUBxcWAoOS0JKS0JIBHiw6FL9ke1CSaEACPGyOpI5sPAFFvxUBiWeSApbKmAaM/podKhW/RT5qkKKObjoEQmaWTXtkvxUAAAACAAD/uANaAxIACABqAEVAQmVZTEEEAAQ7CgIBADQoGxAEAwEDRwAFBAVvBgEEAARvAAABAG8AAQMBbwADAgNvAAICZlxbU1FJSCsqIiATEgcFFisBNCYiDgEWMjYlFRQGDwEGBxYXFhQHDgEnIi8BBgcGBwYrASImNScmJwcGIicmJyY0Nz4BNyYvAS4BJzU0Nj8BNjcmJyY0Nz4BMzIfATY3Njc2OwEyFh8BFhc3NjIXFhcWFAcOAQcWHwEeAQI7UnhSAlZ0VgEcCAdoCgsTKAYFD1ANBwdNGRoJBwQQfAgMEBsXTwYQBkYWBAUIKAoPCGYHCAEKBWgIDhclBgUPUA0HCE0YGgkIAxF8BwwBDxwXTwUPB0gUBAQJKAoPCGYHCgFlO1RUdlRUeHwHDAEQHhUbMgYOBhVQAQU8DQhMHBAKB2cJDDwFBkAeBQ4GDDIPHBsPAQwHfAcMARAZGiAtBwwHFFAFPA0ITBwQCgdnCQs7BQVDHAUOBgwyDxwaEAEMAAAAAgAAAAADawLKACcAQABCQD8UAQIBAUcABgIFAgYFbQAFAwIFA2sABAMAAwQAbQABAAIGAQJgAAMEAANUAAMDAFgAAAMATBYjGSUqJScHBRsrJRQWDwEOAQcjIiY1ETQ2OwEyFhUXFg8BDgEnIyIGBxEUFhczMh4CARQHAQYiJj0BIyImPQE0NjczNTQ2FhcBFgFlAgECAQgIskNeXkOyCAoBAQECAQgIsiU0ATYktAYCBgICBgv+0QscFvoOFhYO+hYcCwEvCzUCEgUOCQIDXkMBiENeCggLCQYNBwgBNCb+eCU0AQQCCAEsDgv+0AoUD6EWDtYPFAGhDhYCCf7QCgAAAAABAAD/7gO2AjAAFAAZQBYNAQABAUcCAQEAAW8AAABmFBcSAwUXKwkBBiInASY0PwE2MhcJATYyHwEWFAOr/mIKHgr+YgsLXQoeCgEoASgLHAxcCwGW/mMLCwGdCx4KXAsL/tgBKAsLXAscAAAB//7/ewO4A2cAMQAfQBwAAQAAAVQAAQEAWAIBAAEATAEAKikAMQExAwUUKxciJy4BNwE2Fx4BFxYHAQ4BJyY2NwE2FgcBBhcWNzY3ATYmJyYHAQYeAjcBNhYHAQb0ZkRIBFYB8FBeLEYMGlD+JihgIB4GLAFMGDQa/rQsGAwMGBYB2jIgPDY2/hJCBGSGSgHwGDQa/hBShUhGwF4B8FAaDEYsYFD+JigKIBhkKgFOGjQY/rQsGggCBBYB2jJ2EA4y/hJMhmIEQAHuGC4a/hBSAAAAAAT///+4BC8DEgAIAA8AHwAvAFVAUh0UAgEDDwEAAQ4NDAkEAgAcFQIEAgRHAAIABAACBG0ABgcBAwEGA2AAAQAAAgEAYAAEBQUEVAAEBAVYAAUEBUwREC4rJiMZFxAfER8TExIIBRcrARQOASY0Nh4BARUhNTcXASUhIgYHERQWNyEyNicRNCYXERQGByEiJjcRNDY3ITIWAWU+Wj4+Wj4CPPzusloBHQEe/IMHCgEMBgN9BwwBClE0JfyDJDYBNCUDfSU0AhgtPgJCVkIEOv76+muzWQEdoQoI/VoHDAEKCAKmCAoS/VolNAE2JAKmJTQBNgAL////cQQvAxIADwAfAC8APwBPAF8AbwB/AI8AnwCvAMRAGZBAAgkIiIBgIAQFBHg4AgMCUDAAAwEABEdLsCFQWEA3ABUSDAIICRUIYBMBCRABBAUJBGARDQIFDgYCAgMFAmAPAQMKAQABAwBgCwcCAQEUWAAUFA0USRtAPgAVEgwCCAkVCGATAQkQAQQFCQRgEQ0CBQ4GAgIDBQJgDwEDCgEAAQMAYAsHAgEUFAFUCwcCAQEUWAAUARRMWUAmrqumo56blpSOjIaEfnx2c25rZmReW1ZUTks1NTUmNSY1NTMWBR0rFzU0JgcjIgYdARQWOwEyNic1NCYrASIGHQEUFjczMjYnNTQmJyMiBh0BFBYXMzI2ARE0JiMhIgYXERQWMyEyNgE1NCYHIyIGHQEUFjsBMjYBNTQmByMiBgcVFBY7ATI2AxE0JgchIgYXERQWFyEyNhc1NCYrASIGBxUUFjczMjY3NTQmJyMiBgcVFBYXMzI2NzU0JgcjIgYHFRQWOwEyNjcRFAYjISImNxE0NjchMhbWFA9IDhYWDkgOFgEUD0gOFhYOSA4WARQPSA4WFg5IDhYCOxYO/lMOFgEUDwGtDxT9xRQPSA4WFg5IDhYDERYORw8UARYORw8U1RYO/lMOFgEUDwGtDxTXFg5HDxQBFg5HDxQBFg5HDxQBFg5HDxQBFg5HDxQBFg5HDxRINCX8gyQ2ATQlA30lNCRIDhYBFA9IDhYW5EgOFhYOSA4WARTmRw8UARYORw8UARb+YQEeDhYWDv7iDhYWApFHDxYBFBBHDhYW/YtIDhYBFA9IDhYWAbsBHQ8WARQQ/uMPFAEWyUgOFhYOSA4WARTmRw8UARYORw8UARbkRw8WARQQRw4WFmf9EiU0NCUC7iU0ATYAAQAA/8cCdANLABQAF0AUCQEAAQFHAAEAAW8AAABmHBICBRYrCQEGIi8BJjQ3CQEmND8BNjIXARYUAmr+YgscC10LCwEo/tgLC10KHgoBngoBcP5hCgpdCxwLASkBKAscC10LC/5iCxwAAAAAAQAA/8cCmANLABQAF0AUAQEAAQFHAAEAAW8AAABmFxcCBRYrCQIWFA8BBiInASY0NwE2Mh8BFhQCjv7XASkKCl0LHAv+YgsLAZ4KHgpdCgKx/tj+1woeCl0KCgGfCh4KAZ4LC10KHgABAAAAAAO2Ak0AFAAZQBYFAQACAUcAAgACbwEBAABmFxQSAwUXKyUHBiInCQEGIi8BJjQ3ATYyFwEWFAOrXAseCv7Y/tgLHAtdCwsBngscCwGeC3JcCgoBKf7XCgpcCx4KAZ4KCv5iCxwAAAAEAAD/dQPAA1kAKgA0AD0ATgC3QBE2NAIEAB0OAgEEAkdMAQEBRkuwGlBYQCkFAQQAAQAEAW0DAQEGAAEGawAGBwAGB2sIAQAADEgABwcCWAACAg0CSRtLsCRQWEAmBQEEAAEABAFtAwEBBgABBmsABgcABgdrAAcAAgcCXAgBAAAMAEkbQCcIAQAEAG8FAQQBBG8DAQEGAW8ABgcGbwAHAgIHVAAHBwJYAAIHAkxZWUAXAQBKSERDOjkwLxsZFhUSEAAqASoJBRQrASIGFRQXBgcOARUUBwYHFBY7ARQeATI+ATUzMjY1JicmNTQmJyYnNjU0JgUGBwYVMzQ3NjclBx4BBzM2JyYBMhYVFBYzMhYUBiMiJjU0NgHyFiAFRzgzOjoqTSod+SZBTkEm+R0qTSs6OTQ3RwQf/rU7Hh1HFhgxAjkwMi4BRwEdHv43BAUvIQQFBQQoOgUDWR8WCgwLJyRpNrV9W0EdKidCJiZCJyodQVt9tTZpJCcLDggWHy02SERRRDY4LTQ0LW5EUEVH/RgFBCEvBQgFOigEBQAAAAIAAAAAAoMDEgAHAB8AKkAnBQMCAAECAQACbQACAm4ABAEBBFQABAQBWAABBAFMIxMlNhMQBgUaKxMhNTQmDgEXBREUBgchIiYnETQ2FzM1NDYyFgcVMzIWswEdVHZUAQHQIBb96RceASAWEZTMlgISFx4BrGw7VAJQPaH+vhYeASAVAUIWIAFsZpSUZmweAAP//f+4A1kDEgAMAb0B9wJ3S7AJUFhBPAC9ALsAuACfAJYAiAAGAAMAAACPAAEAAgADANoA0wBtAFkAUQBCAD4AMwAgABkACgAHAAIBngGYAZYBjAGLAXoBdQFlAWMBAwDhAOAADAAGAAcBUwFNASgAAwAIAAYB9AHbAdEBywHAAb4BOAEzAAgAAQAIAAYARxtLsApQWEFDALsAuACfAIgABAAFAAAAvQABAAMABQCPAAEAAgADANoA0wBtAFkAUQBCAD4AMwAgABkACgAHAAIBngGYAZYBjAGLAXoBdQFlAWMBAwDhAOAADAAGAAcBUwFNASgAAwAIAAYB9AHbAdEBywHAAb4BOAEzAAgAAQAIAAcARwCWAAEABQABAEYbQTwAvQC7ALgAnwCWAIgABgADAAAAjwABAAIAAwDaANMAbQBZAFEAQgA+ADMAIAAZAAoABwACAZ4BmAGWAYwBiwF6AXUBZQFjAQMA4QDgAAwABgAHAVMBTQEoAAMACAAGAfQB2wHRAcsBwAG+ATgBMwAIAAEACAAGAEdZWUuwCVBYQDUAAgMHAwIHbQAHBgMHBmsABggDBghrAAgBAwgBawABAW4JAQADAwBUCQEAAANYBQQCAwADTBtLsApQWEA6BAEDBQIFA2UAAgcFAgdrAAcGBQcGawAGCAUGCGsACAEFCAFrAAEBbgkBAAUFAFQJAQAABVYABQAFShtANQACAwcDAgdtAAcGAwcGawAGCAMGCGsACAEDCAFrAAEBbgkBAAMDAFQJAQAAA1gFBAIDAANMWVlBGQABAAAB2AHWAbkBtwFXAVYAxwDFALUAtACxAK4AeQB2AAcABgAAAAwAAQAMAAoABQAUKwEyHgEUDgEiLgI+AQEOAQcyPgE1PgE3NhcmNj8BNj8BBiY1FAc0JgY1LgQvASY0LwEHBhQqARQiBiIHNicmIzYmJzMuAicuAQcGFB8BFgYeAQcGDwEGFhcWFAYiDwEGJicmJyYHJicmBzImBz4BIzY/ATYnFj8BNjc2MhYzFjQnMicmJyYHBhciDwEGLwEmJyIHNiYjNicmIg8BBh4BMhcWByIGIgYWBy4BJxYnIyIGIicmNzQXJwYHMjY/ATYXNxcmBwYHFgcnLgEnIgcGBx4CFDcWBzIXFhcWBycmBhYzIg8BBh8BBhY3Bh8DHgIXBhYHIgY1HgIUFjc2Jy4CNTMyHwEGHgIzHgEHMh4EHwMWMj8BNhYXFjciHwEeARUeARc2NQYWMzY1Bi8BJjQmNhcyNi4CJwYmJxQGFSM2ND8BNi8BJgciBw4DJicuATQ/ATYnNj8BNjsBMjQ2JiMWNhcWNycmNxY3HgIfARY2NxYXHgE+ASY1JzUuATY3NDY/ATYnMjcnJiI3Nic+ATMWNic+ATcWNiY+ARU3NiMWNzYnNiYnMzI1NicmAzY3JiIvATYmLwEmLwEmDwEiDwEVJiciLgEOAQ8BJjYmBg8BBjYGFQ4BFS4BNx4BFxYHBgcGFxQGFgGtdMZycsboyG4GerwBEwIIAwECBAMRFRMKAQwCCAYDAQcGBAQKBQYEAQgBAgEDAwQEBAQGAQYCCAkFBAYCBAMBCAwBBRwEAwICAQgBDgECBwkDBAQBBAIDAQcKAgQFDQMDFA4TBAgGAQIBAgUJAgETCQYEAgUGCgMIBAcFAgMGCQQGAQUJBAUDAwIFBAEOBwsPBBADAwEIBAgBCAMBCAQDAgIDBAIEEgUDDAwBAwMCDBkbAwYFBRMFAwsEDQsBBAIGBAgECQRRMgQFAgYFAwEYCgECBwUEAwQEBAECAQEBAgoHBxIEBwkEAwgEAg4BAQICDgIEAgIPCAMEAwIDBQEECgoBBAgEBQwHAgMIAwkHFgYGBQgIEAQUCgECBAIGAw4DBAEKBQgRCgICAgIBBQIEAQoCAwwDAggBAggDAQMCBwsEAQICCBQDCAoBAgEEAgMFAgEDAgEDAQQYAwkDAQEBAw0CDgQCAwEEAwUCBggEAgIBCAQEBwgFBwwEBAICAgYBBQQDAgMFDAQCEgEEAgIFDgkCAgoIBQkCBgYHBQkMCmlzUAEMAQ0BBAMVAQMFAgMCAgEFDAgDBgYGBgEBBAgECgEHBgIKAgQBDAEBAgIECw8BAgkKAQMSdMTqxHR0xOrEdP7dAQgCBgYBBAgDBQsBDAEDAgIMAQoHAgMEAgQBAgYMBQYDAwIEAQEDAwQCBAEDAwICCAQCBgQBAwQBBAQGBwMIBwoHBAUGBQwDAQIEAgEDDAkOAwQFBwgFAxECAw4IBQwDAQMJCQYEAwYBDgQKBAECBQICBgoEBwcHAQkFCAcIAwIHAwIEAgYCBAUKAwMOAgUCAgUEBwIBCggPAgMDBwMCDgMCAwQGBAYEBAEBLU8EAQgEAwQGDwoCBgQFBAUOCRQLAgEGGgIBFwUEBgMFFAMDEAUCAQQIBQgEAQsYDQUMAgIEBAwIDgQOAQoLFAcIAQUDDQIBAgESAwoEBAkFBgIDCgMCAwUMAhAIEgMDBAQGAgQKBw4BBQIEAQQCAhAFDwUCBQMCCwIIBAQCAgQYDgkOBQkBBAYBAgMCAQQDBgcGBQIPCgEEAQIDAQIDCAUXBAIICAMFDgIKCgUBAgMECwkFAgICAgYCCgYKBAQEAwEECgQGAQcCAQcGBQQCAwEFBAL+DRVVAgIFBAYCDwEBAgECAQEDAgoDBgICBQYHAw4GAgEFBAIIAQIIAgICAgUcCBEJDgkMAgQQBwACAAD/pQOPAyQADAAXACJAHxQBAQIRBQIAAQJHAAIBAm8AAQABbwAAAGYbFiIDBRcrJRQGJyInPgEnNDYyFgEWFAcBLgEnATYyAdCue1FERFIBWHpYAZ4gIf7CFFI4AT4gXtF8sAEoJ4pSPVhYAfUgXiD+wjdUFAE+IAAAA//1/7gD8wNZAA8AIQAzAGRADBsRAgMCCQECAQACR0uwJFBYQB0AAgUDBQIDbQADAAABAwBgAAEABAEEXAAFBQwFSRtAIgAFAgVvAAIDAm8AAwAAAQMAYAABBAQBVAABAQRYAAQBBExZQAkXOCcnJiMGBRorJTU0JisBIgYdARQWFzMyNicTNCcmKwEiBwYVFxQWNzMyNgMBFgcOAQchIiYnJjcBPgEyFgI7CgdsBwoKB2wHCgEKBQcHegYIBQkMB2cIDAgBrBQVCSIS/KYSIgkVFAGtCSImIlpqCAoKCGoICgEM1wEBBgQGBgQI/wUIAQYCEPzuIyMREgEUECMjAxIRFBQAAAAAAQAAAAADEgMSACMAKUAmAAQDBG8AAQABcAUBAwAAA1QFAQMDAFgCAQADAEwjMyUjMyMGBRorARUUBicjFRQGByMiJjc1IyImJzU0NjczNTQ2OwEyFhcVMzIWAxIgFuggFmsWIAHoFx4BIBboHhdrFx4B6BceAb5rFiAB6RYeASAV6R4XaxceAegWICAW6CAAAv/9/7gDXwMSAAcAFAArQCgAAwAAAQMAYAQBAQICAVQEAQEBAlgAAgECTAAAEhEMCwAHAAcRBQUVKyURIg4CHgEBFA4BIi4CPgEyHgEBrVOMUAJUiAIBcsboyG4Gerz0un41AmBSjKSMUgEwdcR0dMTqxHR0xAAABQAAAAAD5AMSAAYADwA5AD4ASAEHQBVAPjsQAwIBBwAENAEBAAJHQQEEAUZLsApQWEAwAAcDBAMHBG0AAAQBAQBlAAMABAADBGAIAQEABgUBBl8ABQICBVQABQUCWAACBQJMG0uwC1BYQCkAAAQBAQBlBwEDAAQAAwRgCAEBAAYFAQZfAAUCAgVUAAUFAlgAAgUCTBtLsBhQWEAwAAcDBAMHBG0AAAQBAQBlAAMABAADBGAIAQEABgUBBl8ABQICBVQABQUCWAACBQJMG0AxAAcDBAMHBG0AAAQBBAABbQADAAQAAwRgCAEBAAYFAQZfAAUCAgVUAAUFAlgAAgUCTFlZWUAWAABEQz08MS4pJh4bFhMABgAGFAkFFSslNycHFTMVASYPAQYWPwE2ExUUBiMhIiY1ETQ2NyEyFx4BDwEGJyYjISIGBxEUFhchMjY9ATQ/ATYWAxcBIzUBByc3NjIfARYUAfBAVUA1ARUJCcQJEgnECSReQ/4wQ15eQwHQIx4JAwcbCAoNDP4wJTQBNiQB0CU0BSQIGDeh/omhAm8zoTMQLBBVEMRBVUEfNgGSCQnECRIJxAn+vmpDXl5DAdBCXgEOBBMGHAgEAzQl/jAlNAE2JEYHBSQICAGPoP6JoAEuNKE0Dw9VECwABAAA/7gDTQMGAAYAFAAZACQAhkAXHgECBR0WDgcEAwIZAwIDAAMBAQEABEdLsBJQWEAnAAUCBW8AAgMCbwADAANvAAABAQBjBgEBBAQBUgYBAQEEVwAEAQRLG0AmAAUCBW8AAgMCbwADAANvAAABAG8GAQEEBAFSBgEBAQRXAAQBBEtZQBIAACEgGBcQDwkIAAYABhQHBRUrMzcnBxUzFQE0IyIHAQYVFDMyNwE2JxcBIzUBFA8BJzc2Mh8BFssygzNIAV8MBQT+0QQNBQQBLwMe6P4w6ANNFF3oXRQ7FoMUM4MzPEcCBgwE/tIEBgwEAS4Ecej+L+kBmh0VXelcFRWDFgACAAD/cQKDAxIACwAuAGO2BwECAQABR0uwIVBYQBsABwgGAgABBwBgCQUCAQQBAgMBAmAAAwMNA0kbQCQAAwIDcAAHCAYCAAEHAGAJBQIBAgIBVAkFAgEBAlgEAQIBAkxZQA4tLBMzERQiMxUVEwoFHSsBNTQmIgYdARQWMjYFFAYnIwMOAQcjIicDIyImJzQ2MxEiLgE2NyEyFhQGJxEyFgEMChAKChAKAXcWDu8dAQoGAQ8CK+EPFAFYNx0qAi4bAWUdKiodN1gBd/oICgoI+ggKCr0OFgH+8gcIAQ8BDxQPRW4BHio6KgEsOCwB/uJuAAAAAwAA/30DoAMSAAgAFAAuADNAMCYBBAMoJxIDAgQAAQEAA0cAAwQDbwAEAgRvAAIAAm8AAAEAbwABAWYcIy0YEgUFGSs3NCYOAh4BNiUBBiIvASY0NwEeASUUBw4BJyImNDY3MhYXFhQPARUXNj8BNjIW1hQeFAIYGhgBZv6DFToWOxUVAXwWVAGZDRuCT2iSkmggRhkJCaNsAipLIQ8KJA4WAhIgEgQa9v6DFBQ9FDsWAXw3VN0WJUteAZLQkAIUEAYSB159PAIZLRQKAAAAAAUAAP+4BHcDEgADAAcADQARABUAZkBjAAUKBW8PAQoDCm8MAQMIA28OAQgBCG8LAQEAAW8JBwIDAAYAbw0BBgQEBlINAQYGBFYABAYEShISDg4ICAQEAAASFRIVFBMOEQ4REA8IDQgNDAsKCQQHBAcGBQADAAMREAUVKwERIxEBESMRARUhETMRAREjESURIxEBZY8BZY4CyvuJRwLLjwFljwFl/uIBHgEe/cQCPP19SANa/O4B9P5TAa3W/X0CgwAAAAAC//3/cQPrA1kAJwBQALBADiQWBgMBAkxCNAMEAwJHS7AhUFhAJgABAgMCAQNtBwEDBAIDBGsAAgIAWAYBAAAMSAAEBAVYAAUFDQVJG0uwJFBYQCMAAQIDAgEDbQcBAwQCAwRrAAQABQQFXAACAgBYBgEAAAwCSRtAKQABAgMCAQNtBwEDBAIDBGsGAQAAAgEAAmAABAUFBFQABAQFWAAFBAVMWVlAFykoAQBHRTEvKFApUBQSDAoAJwEnCAUUKwEiBwYHBgcUFh8BMzI1Njc2NzYzMhYXBwYWHwEWPgEvAS4BDwEmJyYBIhUGBwYHBiMiJyYnNzYmLwEmDgEfAR4BPwEWFxYzMjc2NzY3NCYvAQHug3FtQ0UFBQQEVBMFNTNTV2NPjjQ6CQIM9wsUCgQ6AhIJQURaXAEzEwU1M1NWY1BIRTU7CAIL+AsUCgQ6AhIKQERaXWaCcW5CRQUFBAQDWUA+a26BCAkCARJiU1EvMT44OQkTAzIDCRYQ4wgLBjxGJij+BBJiU1EvMSAeODkJEwMyAwkWEOMICwY8RiYoQD5rboIICAIBAAAAAAL///9iA+oDWQAfAEEASUAKBAECAAFHMQEBREuwJFBYQBMAAgABAAIBbQABAW4DAQAADABJG0APAwEAAgBvAAIBAm8AAQFmWUANAQAhIBQTAB8BHwQFFCsBIgcGBzE2NzYXFhcWFxYGBwYXHgE3PgE3NiYnLgEnJgEiBwYHBgcGFhcWFxYXFjc2NzEGBwYnJicmJyY2NzYmJyYB8ldRVERWbGpnak9CISEGJQ4aEDMRAwoCIwElJpBeW/4FGA8EBAYBJAIkJkhbe3d5fWFWbGpna09CISAFJQgGDhIDWR0eOUUVFB4gT0JWU7NRKRsQAREDDwZaw1ldkCYl/u4QBAYIBlrDWV1IWyQiGBlRRRUUHiBPQlZTs1EVIQ4SAAAAAAIAAAAAA+gDWQAnAD8AfUATKAEBBhEBAgE3LgIEAiEBBQQER0uwJFBYQCQABAIFAgQFbQAFAwIFA2sAAQACBAECYAADAAADAFwABgYMBkkbQCwABgEGbwAEAgUCBAVtAAUDAgUDawABAAIEAQJgAAMAAANUAAMDAFgAAAMATFlACjobJTU2JTMHBRsrARUUBiMhIiY1ETQ2NyEyFh0BFAYjISIGBxEUFhchMjY9ATQ2OwEyFhMRFA4BLwEBBiIvASY0NwEnJjQ2MyEyFgMSXkP+MENeXkMBiQcKCgf+dyU0ATYkAdAlNAoIJAgK1hYcC2L+lAUQBEAGBgFsYgsWDgEdDxQBU7JDXl5DAdBCXgEKCCQICjQl/jAlNAE2JLIICgoB2v7jDxQCDGL+lAYGQAUOBgFsYgscFhYAAAACAAD/uANZAxIAGAAoADJALxIJAgIAAUcAAgABAAIBbQAEAAACBABgAAEDAwFUAAEBA1gAAwEDTDU3FBkzBQUZKwERNCYnISIGHwEBBhQfARYyNwEXFjMyNzYTERQGByEiJjURNDY3ITIWAsoUD/70GBMSUP7WCws5CxwLASpRCg8GCBWPXkP96UNeXkMCF0NeAVMBDA8UAS0QUP7WCx4KOQoKASpQCwMKATX96EJeAWBBAhhCXgFgAAAAAAMAAAAAA1oCywAPAB8ALwA3QDQoAQQFCAACAAECRwAFAAQDBQRgAAMAAgEDAmAAAQAAAVQAAQEAWAAAAQBMJjUmNSYzBgUaKyUVFAYHISImJzU0NjchMhYDFRQGJyEiJic1NDYXITIWAxUUBiMhIiYnNTQ2FyEyFgNZFBD87w8UARYOAxEPFgEUEPzvDxQBFg4DEQ8WARQQ/O8PFAEWDgMRDxZrRw8UARYORw8UARYBEEgOFgEUD0gOFgEUAQ5HDhYWDkcPFgEUAAAAAAL///+4A+kCygAZADgALUAqCQACAgMBRwADAgNvAAIBAm8AAQAAAVQAAQEAWAAAAQBMNzQmJDozBAUWKwERFAYHISImNxEWFxYXHgI3MzI+ATc2NzY3FAYHBg8BDgInIyImLwEuAS8BJicuASc0NjMhMhYD6DQl/MokNgEZH8pMICZEGwIcQigfX7cgGDYp0jQ1DCIeDQIMHhEeDSIGk2ASIzwBLisDNiQ2Ac3+RSU0ATYkAbsbFok3GBocARocF0R8Fr8sUB2SIycJEgwBCgoSCBwDZUIOF1IkKzo0AAAAAgAA/3ED6ALKABcAPQBiQAw0CAIBACYLAgMCAkdLsCFQWEAXAAQFAQABBABgAAEAAgMBAmAAAwMNA0kbQB4AAwIDcAAEBQEAAQQAYAABAgIBVAABAQJYAAIBAkxZQBEBADs6JCIdGxIQABcBFwYFFCsBIg4BBxQWHwEHBgc2PwEXFjMyPgIuAQEUDgEjIicGBwYHIyImJzUmNiY/ATY/AT4CPwEuASc0PgEgHgEB9HLGdAFQSTAPDRpVRRggJiJyxnQCeMIBgIbmiCcqbpMbJAMIDgICBAIDDAQNFAcUEAcPWGQBhuYBEOaGAoNOhEw+cikcNTMuJDwVAwVOhJiETv7iYaRgBGEmCAQMCQECCAQDDwUOFggcHBMqMpJUYaRgYKQAAAIAAP9xA8QDWgAMADQAnkALGg0CAQYAAQIAAkdLsCFQWEAnAAEGAwYBA20FAQMABgMAawAAAgYAAmsABgYMSAACAgRYAAQEDQRJG0uwJFBYQCQAAQYDBgEDbQUBAwAGAwBrAAACBgACawACAAQCBFwABgYMBkkbQCUABgEGbwABAwFvBQEDAANvAAACAG8AAgQEAlQAAgIEWAAEAgRMWVlACh8iEiMjExIHBRsrBTQjIiY3NCIVFBY3MiUUBisBFAYiJjUjIiY1PgQ3NDY3JjU0PgEWFRQHHgEXFB4DAf0JITABEjooCQHHKh36VHZU+h0qHC4wJBIChGkFICwgBWqCARYiMDBZCDAhCQkpOgGpHSo7VFQ7Kh0YMlReiE1UkhAKCxceAiIVCwoQklROhmBSNAAAAgAA/7gDWQMSACMAMwBBQD4NAQABHwEEAwJHAgEAAQMBAANtBQEDBAEDBGsABwABAAcBYAAEBgYEVAAEBAZYAAYEBkw1NSMzFiMkIwgFHCsBNTQmByM1NCYnIyIGBxUjIgYHFRQWNzMVFBY7ATI2NzUzMjYTERQGByEiJjURNDY3ITIWAsoUD7MWDkcPFAGyDxQBFg6yFg5HDxQBsw4Wjl5D/elDXl5DAhdDXgFBSA4WAbMPFAEWDrMUD0gOFgGzDhYWDrMUAT/96EJeAWBBAhhCXgFgAAAAAQAA/7gD6AM1ACsAKUAmJgEEAwFHAAMEA28ABAEEbwABAgFvAAIAAm8AAABmIxcTPRcFBRkrJRQHDgIHBiImNTQ2NzY1NC4FKwEVFAYiJwEmNDcBNjIWBxUzIBcWA+hHAQoEBQcRCgIBAxQiOD5WVjd9FCAJ/uMLCwEdCxwYAn0Bjloe6F2fBBIQBAoMCAUUAyYfOFpAMB4SBo8OFgsBHgoeCgEeChQPj+FLAAEAAAAAAoMDWgAjAGZLsCRQWEAgAAQFAAUEAG0CBgIAAQUAAWsAAQFuAAUFA1gAAwMMBUkbQCUABAUABQQAbQIGAgABBQABawABAW4AAwUFA1QAAwMFWAAFAwVMWUATAQAgHxsYFBMQDgkGACMBIwcFFCsBMhYXERQGByEiJicRNDYXMzU0Nh4BBxQGKwEiJjU0JiIGFxUCTRceASAW/ekXHgEgFhGUzJYCFA8kDhZUdlQBAaweF/6+Fh4BIBUBQhYgAbNnlAKQaQ4WFg47VFQ7swAAAwAAAAADEgH0AA8AHwAvACJAHwUDAgEAAAFUBQMCAQEAWAQCAgABAEw1NTU1NTMGBRorExUUBicjIiYnNTQ2NzMyFgUVFAYnIyImNzU0NjczMhYFFRQGJyMiJj0BNDY3MzIW1h4XaxceASAWaxYgAR0gFmsWIAEeF2sXHgEfIBZrFiAgFmsXHgG+axYgAR4XaxceASAWaxYgAR4XaxceASAWaxYgAR4XaxceASAAAAAC//3/uANZAxIADAAaACZAIwMBAAIAbwACAQECVAACAgFYAAECAUwBABkYBwYADAEMBAUUKwEyHgEUDgEiLgI+AQE2NCclJgYVERQXFjI3Aa10xnJyxujIbgZ6vAFQEhL+0BEkEgkSCAMSdMTqxHR0xOrEdP40CioKsgsVFP6aFAsEBQADAAD/uAN9AxIACAAYAFUATkBLSgEIBx8bAgADAAEBADERAgIBBEcABwgHbwAIAwhvBgEDAANvAAABAG8ABAIEcAABAgIBVAABAQJYBQECAQJMLywVJD8mNRMSCQUdKzc0LgEOAR4BNhMRFAYHIyImJxE0NhczMhYFFAcWFRYHFgcGBxYHBgcjIi4BJyYnIiYnETQ+Ajc2Nz4CNz4DMzIeBAYXFA4BBw4CBzMyFo8WHRQBFh0UWhQQoA8UARYOoA8WApQfCQEZCQkJFgUgJEpIJVYyKkUTDxQBFBs6HCYSCg4GBQQGEBUPGSoYFAgGAgIMCAwBCAQDmytAaw8UARYdFAEWASz+mw8UARYOAWUOFgEUDzAjGRIqIh8jHxU+JysBEg4PGAEWDgFlDhYBQCMxEgoiFBgWGCIWDBIaGCASDRUsFhQEDA4GQAAAAAUAAP9xA+gDWQAQABQAJQAvADkA20AXMykCBwghAQUCHRUNDAQABQNHBAEFAUZLsCFQWEAtBgwDCwQBBwIHAQJtAAIFBwIFawAFAAcFAGsJAQcHCFgKAQgIDEgEAQAADQBJG0uwJFBYQCwGDAMLBAEHAgcBAm0AAgUHAgVrAAUABwUAawQBAABuCQEHBwhYCgEICAwHSRtAMgYMAwsEAQcCBwECbQACBQcCBWsABQAHBQBrBAEAAG4KAQgHBwhUCgEICAdWCQEHCAdKWVlAIBERAAA3NTIxLSsoJyQiHx4bGREUERQTEgAQAA83DQUVKwERFAYHERQGByEiJicREzYzIREjEQERFAYHISImJxEiJicRMzIXJRUjNTQ2OwEyFgUVIzU0NjsBMhYBiRYOFBD+4w8UAYsEDQGfjgI7Fg7+4w8UAQ8UAe0NBP4+xQoIoQgKAXfFCgihCAoCpv5UDxQB/r8PFAEWDgEdAegM/ngBiP4M/uMPFAEWDgFBFg4BrAytfX0ICgoIfX0ICgoAAAADAAD/uAR4AxMACAAsAE8Ad0B0LCUCCgcgHw4DAwIyEwIECANHAAEHAW8ABwoHbw4BAAoNCgANbQALDQINCwJtDAEKAA0LCg1gBgECBQEDCAIDYAAIBAQIVAAICARYCQEECARMAQBNS0pIRURBPzYzMS8pKCQiHBsXFRIQCgkFBAAIAQgPBRQrASImPgEeAgYFMzIWBxUUBisBFRQGByMiJj0BIyImJzU0NjczNTQ2FzMyFhcBFBY3MxUGIyEiJjU0PgUXMhceATI2NzYzMhcjIgYVAYlZfgJ6tngGhAHDxAcMAQoIxAwGawgKxQcKAQwGxQoIawcKAf5lKh2PJjn+GENSBAwSHiY6IQsLLFRkVCwLC0kwfR0qAWV+sIACfLR6SQwGawgKxQcKAQwGxQoIawcKAcQHDAEKCP6/HSwBhRxOQx44QjY4IhoCCiIiIiIKNiodAAAAAAEAAAABAACMblUeXw889QALA+gAAAAA2U0YJQAAAADZTRgl//X/YgR4A2cAAAAIAAIAAAAAAAAAAQAAA1n/cQAABHb/9f/zBHgAAQAAAAAAAAAAAAAAAAAAAC0D6AAAAxEAAAOgAAADoAAAA6AAAAQvAAAD6AAAA6D//wNZAAADoAAAA+gAAAOr//4EL///BC///wLKAAACygAAA+gAAAPoAAACggAAA1n//QOgAAAD6P/1AxEAAANZ//0D6AAAA1kAAAKCAAADoAAABHYAAAPo//0D6f//A+gAAANZAAADWQAAA+j//wPoAAAD6AAAA1kAAAPoAAACggAAAxEAAANZ//0DoAAAA+gAAAR2AAAAAAAAAEoAzgESAWwB8gKkAwYDyARKBIAE6gVkBrYG7AcgB1YIJghuDHIMsA00DXwNuA6uDzAPqhASEHQRShHaEngS1hM8E6wUPhTYFUQVmhYEFlwWnhdEGAwYtwABAAAALQH4AAsAAAAAAAIALAA8AHMAAACqC3AAAAAAAAAAEgDeAAEAAAAAAAAANQAAAAEAAAAAAAEACAA1AAEAAAAAAAIABwA9AAEAAAAAAAMACABEAAEAAAAAAAQACABMAAEAAAAAAAUACwBUAAEAAAAAAAYACABfAAEAAAAAAAoAKwBnAAEAAAAAAAsAEwCSAAMAAQQJAAAAagClAAMAAQQJAAEAEAEPAAMAAQQJAAIADgEfAAMAAQQJAAMAEAEtAAMAAQQJAAQAEAE9AAMAAQQJAAUAFgFNAAMAAQQJAAYAEAFjAAMAAQQJAAoAVgFzAAMAAQQJAAsAJgHJQ29weXJpZ2h0IChDKSAyMDE5IGJ5IG9yaWdpbmFsIGF1dGhvcnMgQCBmb250ZWxsby5jb21mb250ZWxsb1JlZ3VsYXJmb250ZWxsb2ZvbnRlbGxvVmVyc2lvbiAxLjBmb250ZWxsb0dlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABDACkAIAAyADAAMQA5ACAAYgB5ACAAbwByAGkAZwBpAG4AYQBsACAAYQB1AHQAaABvAHIAcwAgAEAAIABmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQBmAG8AbgB0AGUAbABsAG8AUgBlAGcAdQBsAGEAcgBmAG8AbgB0AGUAbABsAG8AZgBvAG4AdABlAGwAbABvAFYAZQByAHMAaQBvAG4AIAAxAC4AMABmAG8AbgB0AGUAbABsAG8ARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALQECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXARgBGQEaARsBHAEdAR4BHwEgASEBIgEjASQBJQEmAScBKAEpASoBKwEsAS0BLgAGY2FuY2VsBnVwbG9hZARzdGFyCnN0YXItZW1wdHkHcmV0d2VldAdleWUtb2ZmBnNlYXJjaANjb2cGbG9nb3V0CWRvd24tb3BlbgZhdHRhY2gHcGljdHVyZQV2aWRlbwpyaWdodC1vcGVuCWxlZnQtb3Blbgd1cC1vcGVuDmJlbGwtcmluZ2luZy1vBGxvY2sFZ2xvYmUFYnJ1c2gJYXR0ZW50aW9uBHBsdXMGYWRqdXN0BGVkaXQGcGVuY2lsA3BpbgZ3cmVuY2gJY2hhcnQtYmFyBXNwaW4zBXNwaW40CGxpbmstZXh0DGxpbmstZXh0LWFsdARtZW51CG1haWwtYWx0DWNvbW1lbnQtZW1wdHkIYmVsbC1hbHQMcGx1cy1zcXVhcmVkBXJlcGx5DWxvY2stb3Blbi1hbHQIZWxsaXBzaXMMcGxheS1jaXJjbGVkDXRodW1icy11cC1hbHQKYmlub2N1bGFycwl1c2VyLXBsdXMAAAAAAQAB//8ADwAAAAAAAAAAAAAAAAAAAAAAGAAYABgAGANn/2IDZ/9isAAsILAAVVhFWSAgS7gADlFLsAZTWliwNBuwKFlgZiCKVViwAiVhuQgACABjYyNiGyEhsABZsABDI0SyAAEAQ2BCLbABLLAgYGYtsAIsIGQgsMBQsAQmWrIoAQpDRWNFUltYISMhG4pYILBQUFghsEBZGyCwOFBYIbA4WVkgsQEKQ0VjRWFksChQWCGxAQpDRWNFILAwUFghsDBZGyCwwFBYIGYgiophILAKUFhgGyCwIFBYIbAKYBsgsDZQWCGwNmAbYFlZWRuwAStZWSOwAFBYZVlZLbADLCBFILAEJWFkILAFQ1BYsAUjQrAGI0IbISFZsAFgLbAELCMhIyEgZLEFYkIgsAYjQrEBCkNFY7EBCkOwAWBFY7ADKiEgsAZDIIogirABK7EwBSWwBCZRWGBQG2FSWVgjWSEgsEBTWLABKxshsEBZI7AAUFhlWS2wBSywB0MrsgACAENgQi2wBiywByNCIyCwACNCYbACYmawAWOwAWCwBSotsAcsICBFILALQ2O4BABiILAAUFiwQGBZZrABY2BEsAFgLbAILLIHCwBDRUIqIbIAAQBDYEItsAkssABDI0SyAAEAQ2BCLbAKLCAgRSCwASsjsABDsAQlYCBFiiNhIGQgsCBQWCGwABuwMFBYsCAbsEBZWSOwAFBYZVmwAyUjYUREsAFgLbALLCAgRSCwASsjsABDsAQlYCBFiiNhIGSwJFBYsAAbsEBZI7AAUFhlWbADJSNhRESwAWAtsAwsILAAI0KyCwoDRVghGyMhWSohLbANLLECAkWwZGFELbAOLLABYCAgsAxDSrAAUFggsAwjQlmwDUNKsABSWCCwDSNCWS2wDywgsBBiZrABYyC4BABjiiNhsA5DYCCKYCCwDiNCIy2wECxLVFixBGREWSSwDWUjeC2wESxLUVhLU1ixBGREWRshWSSwE2UjeC2wEiyxAA9DVVixDw9DsAFhQrAPK1mwAEOwAiVCsQwCJUKxDQIlQrABFiMgsAMlUFixAQBDYLAEJUKKiiCKI2GwDiohI7ABYSCKI2GwDiohG7EBAENgsAIlQrACJWGwDiohWbAMQ0ewDUNHYLACYiCwAFBYsEBgWWawAWMgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLEAABMjRLABQ7AAPrIBAQFDYEItsBMsALEAAkVUWLAPI0IgRbALI0KwCiOwAWBCIGCwAWG1EBABAA4AQkKKYLESBiuwcisbIlktsBQssQATKy2wFSyxARMrLbAWLLECEystsBcssQMTKy2wGCyxBBMrLbAZLLEFEystsBossQYTKy2wGyyxBxMrLbAcLLEIEystsB0ssQkTKy2wHiwAsA0rsQACRVRYsA8jQiBFsAsjQrAKI7ABYEIgYLABYbUQEAEADgBCQopgsRIGK7ByKxsiWS2wHyyxAB4rLbAgLLEBHistsCEssQIeKy2wIiyxAx4rLbAjLLEEHistsCQssQUeKy2wJSyxBh4rLbAmLLEHHistsCcssQgeKy2wKCyxCR4rLbApLCA8sAFgLbAqLCBgsBBgIEMjsAFgQ7ACJWGwAWCwKSohLbArLLAqK7AqKi2wLCwgIEcgILALQ2O4BABiILAAUFiwQGBZZrABY2AjYTgjIIpVWCBHICCwC0NjuAQAYiCwAFBYsEBgWWawAWNgI2E4GyFZLbAtLACxAAJFVFiwARawLCqwARUwGyJZLbAuLACwDSuxAAJFVFiwARawLCqwARUwGyJZLbAvLCA1sAFgLbAwLACwAUVjuAQAYiCwAFBYsEBgWWawAWOwASuwC0NjuAQAYiCwAFBYsEBgWWawAWOwASuwABa0AAAAAABEPiM4sS8BFSotsDEsIDwgRyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsABDYTgtsDIsLhc8LbAzLCA8IEcgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLAAQ2GwAUNjOC2wNCyxAgAWJSAuIEewACNCsAIlSYqKRyNHI2EgWGIbIVmwASNCsjMBARUUKi2wNSywABawBCWwBCVHI0cjYbAJQytlii4jICA8ijgtsDYssAAWsAQlsAQlIC5HI0cjYSCwBCNCsAlDKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgsAhDIIojRyNHI2EjRmCwBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2EjICCwBCYjRmE4GyOwCENGsAIlsAhDRyNHI2FgILAEQ7ACYiCwAFBYsEBgWWawAWNgIyCwASsjsARDYLABK7AFJWGwBSWwAmIgsABQWLBAYFlmsAFjsAQmYSCwBCVgZCOwAyVgZFBYIRsjIVkjICCwBCYjRmE4WS2wNyywABYgICCwBSYgLkcjRyNhIzw4LbA4LLAAFiCwCCNCICAgRiNHsAErI2E4LbA5LLAAFrADJbACJUcjRyNhsABUWC4gPCMhG7ACJbACJUcjRyNhILAFJbAEJUcjRyNhsAYlsAUlSbACJWG5CAAIAGNjIyBYYhshWWO4BABiILAAUFiwQGBZZrABY2AjLiMgIDyKOCMhWS2wOiywABYgsAhDIC5HI0cjYSBgsCBgZrACYiCwAFBYsEBgWWawAWMjICA8ijgtsDssIyAuRrACJUZSWCA8WS6xKwEUKy2wPCwjIC5GsAIlRlBYIDxZLrErARQrLbA9LCMgLkawAiVGUlggPFkjIC5GsAIlRlBYIDxZLrErARQrLbA+LLA1KyMgLkawAiVGUlggPFkusSsBFCstsD8ssDYriiAgPLAEI0KKOCMgLkawAiVGUlggPFkusSsBFCuwBEMusCsrLbBALLAAFrAEJbAEJiAuRyNHI2GwCUMrIyA8IC4jOLErARQrLbBBLLEIBCVCsAAWsAQlsAQlIC5HI0cjYSCwBCNCsAlDKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgR7AEQ7ACYiCwAFBYsEBgWWawAWNgILABKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwAmIgsABQWLBAYFlmsAFjYbACJUZhOCMgPCM4GyEgIEYjR7ABKyNhOCFZsSsBFCstsEIssDUrLrErARQrLbBDLLA2KyEjICA8sAQjQiM4sSsBFCuwBEMusCsrLbBELLAAFSBHsAAjQrIAAQEVFBMusDEqLbBFLLAAFSBHsAAjQrIAAQEVFBMusDEqLbBGLLEAARQTsDIqLbBHLLA0Ki2wSCywABZFIyAuIEaKI2E4sSsBFCstsEkssAgjQrBIKy2wSiyyAABBKy2wSyyyAAFBKy2wTCyyAQBBKy2wTSyyAQFBKy2wTiyyAABCKy2wTyyyAAFCKy2wUCyyAQBCKy2wUSyyAQFCKy2wUiyyAAA+Ky2wUyyyAAE+Ky2wVCyyAQA+Ky2wVSyyAQE+Ky2wViyyAABAKy2wVyyyAAFAKy2wWCyyAQBAKy2wWSyyAQFAKy2wWiyyAABDKy2wWyyyAAFDKy2wXCyyAQBDKy2wXSyyAQFDKy2wXiyyAAA/Ky2wXyyyAAE/Ky2wYCyyAQA/Ky2wYSyyAQE/Ky2wYiywNysusSsBFCstsGMssDcrsDsrLbBkLLA3K7A8Ky2wZSywABawNyuwPSstsGYssDgrLrErARQrLbBnLLA4K7A7Ky2waCywOCuwPCstsGkssDgrsD0rLbBqLLA5Ky6xKwEUKy2wayywOSuwOystsGwssDkrsDwrLbBtLLA5K7A9Ky2wbiywOisusSsBFCstsG8ssDorsDsrLbBwLLA6K7A8Ky2wcSywOiuwPSstsHIsswkEAgNFWCEbIyFZQiuwCGWwAyRQeLABFTAtAEu4AMhSWLEBAY5ZsAG5CAAIAGNwsQAFQrIAAQAqsQAFQrMKAgEIKrEABUKzDgABCCqxAAZCugLAAAEACSqxAAdCugBAAAEACSqxAwBEsSQBiFFYsECIWLEDZESxJgGIUVi6CIAAAQRAiGNUWLEDAERZWVlZswwCAQwquAH/hbAEjbECAEQAAA==') format('truetype');
 }
 /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
 /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
@@ -17,7 +17,7 @@
 @media screen and (-webkit-min-device-pixel-ratio:0) {
   @font-face {
     font-family: 'fontello';
-    src: url('../font/fontello.svg?24909640#fontello') format('svg');
+    src: url('../font/fontello.svg?36125818#fontello') format('svg');
   }
 }
 */
@@ -68,7 +68,7 @@
 .icon-right-open:before { content: '\e80d'; } /* '' */
 .icon-left-open:before { content: '\e80e'; } /* '' */
 .icon-up-open:before { content: '\e80f'; } /* '' */
-.icon-bell:before { content: '\e810'; } /* '' */
+.icon-bell-ringing-o:before { content: '\e810'; } /* '' */
 .icon-lock:before { content: '\e811'; } /* '' */
 .icon-globe:before { content: '\e812'; } /* '' */
 .icon-brush:before { content: '\e813'; } /* '' */
@@ -77,7 +77,9 @@
 .icon-adjust:before { content: '\e816'; } /* '' */
 .icon-edit:before { content: '\e817'; } /* '' */
 .icon-pencil:before { content: '\e818'; } /* '' */
-.icon-emo-happy:before { content: '\e819'; } /* '' */
+.icon-pin:before { content: '\e819'; } /* '' */
+.icon-wrench:before { content: '\e81a'; } /* '' */
+.icon-chart-bar:before { content: '\e81b'; } /* '' */
 .icon-spin3:before { content: '\e832'; } /* '' */
 .icon-spin4:before { content: '\e834'; } /* '' */
 .icon-link-ext:before { content: '\f08e'; } /* '' */
@@ -88,8 +90,8 @@
 .icon-bell-alt:before { content: '\f0f3'; } /* '' */
 .icon-plus-squared:before { content: '\f0fe'; } /* '' */
 .icon-reply:before { content: '\f112'; } /* '' */
-.icon-smile:before { content: '\f118'; } /* '' */
 .icon-lock-open-alt:before { content: '\f13e'; } /* '' */
+.icon-ellipsis:before { content: '\f141'; } /* '' */
 .icon-play-circled:before { content: '\f144'; } /* '' */
 .icon-thumbs-up-alt:before { content: '\f164'; } /* '' */
 .icon-binoculars:before { content: '\f1e5'; } /* '' */
diff --git a/static/font/css/fontello-ie7-codes.css b/static/font/css/fontello-ie7-codes.css
index e6391508..5df45a1d 100755
--- a/static/font/css/fontello-ie7-codes.css
+++ b/static/font/css/fontello-ie7-codes.css
@@ -15,7 +15,7 @@
 .icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
 .icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
 .icon-up-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
-.icon-bell { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
+.icon-bell-ringing-o { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
 .icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }
 .icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }
 .icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }
@@ -24,7 +24,9 @@
 .icon-adjust { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe816;&nbsp;'); }
 .icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe817;&nbsp;'); }
 .icon-pencil { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe818;&nbsp;'); }
-.icon-emo-happy { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }
+.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }
+.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81a;&nbsp;'); }
+.icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81b;&nbsp;'); }
 .icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
 .icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
 .icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
@@ -35,8 +37,8 @@
 .icon-bell-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f3;&nbsp;'); }
 .icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0fe;&nbsp;'); }
 .icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf112;&nbsp;'); }
-.icon-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf118;&nbsp;'); }
 .icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf13e;&nbsp;'); }
+.icon-ellipsis { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf141;&nbsp;'); }
 .icon-play-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf144;&nbsp;'); }
 .icon-thumbs-up-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf164;&nbsp;'); }
 .icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1e5;&nbsp;'); }
diff --git a/static/font/css/fontello-ie7.css b/static/font/css/fontello-ie7.css
index 0cd48ac0..f700ae78 100755
--- a/static/font/css/fontello-ie7.css
+++ b/static/font/css/fontello-ie7.css
@@ -26,7 +26,7 @@
 .icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
 .icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
 .icon-up-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
-.icon-bell { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
+.icon-bell-ringing-o { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
 .icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }
 .icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }
 .icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }
@@ -35,7 +35,9 @@
 .icon-adjust { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe816;&nbsp;'); }
 .icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe817;&nbsp;'); }
 .icon-pencil { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe818;&nbsp;'); }
-.icon-emo-happy { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }
+.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }
+.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81a;&nbsp;'); }
+.icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81b;&nbsp;'); }
 .icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
 .icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
 .icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
@@ -46,8 +48,8 @@
 .icon-bell-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f3;&nbsp;'); }
 .icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0fe;&nbsp;'); }
 .icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf112;&nbsp;'); }
-.icon-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf118;&nbsp;'); }
 .icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf13e;&nbsp;'); }
+.icon-ellipsis { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf141;&nbsp;'); }
 .icon-play-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf144;&nbsp;'); }
 .icon-thumbs-up-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf164;&nbsp;'); }
 .icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1e5;&nbsp;'); }
diff --git a/static/font/css/fontello.css b/static/font/css/fontello.css
index 18e244c9..6c14be64 100755
--- a/static/font/css/fontello.css
+++ b/static/font/css/fontello.css
@@ -1,11 +1,11 @@
 @font-face {
   font-family: 'fontello';
-  src: url('../font/fontello.eot?20888173');
-  src: url('../font/fontello.eot?20888173#iefix') format('embedded-opentype'),
-       url('../font/fontello.woff2?20888173') format('woff2'),
-       url('../font/fontello.woff?20888173') format('woff'),
-       url('../font/fontello.ttf?20888173') format('truetype'),
-       url('../font/fontello.svg?20888173#fontello') format('svg');
+  src: url('../font/fontello.eot?91349539');
+  src: url('../font/fontello.eot?91349539#iefix') format('embedded-opentype'),
+       url('../font/fontello.woff2?91349539') format('woff2'),
+       url('../font/fontello.woff?91349539') format('woff'),
+       url('../font/fontello.ttf?91349539') format('truetype'),
+       url('../font/fontello.svg?91349539#fontello') format('svg');
   font-weight: normal;
   font-style: normal;
 }
@@ -15,7 +15,7 @@
 @media screen and (-webkit-min-device-pixel-ratio:0) {
   @font-face {
     font-family: 'fontello';
-    src: url('../font/fontello.svg?20888173#fontello') format('svg');
+    src: url('../font/fontello.svg?91349539#fontello') format('svg');
   }
 }
 */
@@ -71,7 +71,7 @@
 .icon-right-open:before { content: '\e80d'; } /* '' */
 .icon-left-open:before { content: '\e80e'; } /* '' */
 .icon-up-open:before { content: '\e80f'; } /* '' */
-.icon-bell:before { content: '\e810'; } /* '' */
+.icon-bell-ringing-o:before { content: '\e810'; } /* '' */
 .icon-lock:before { content: '\e811'; } /* '' */
 .icon-globe:before { content: '\e812'; } /* '' */
 .icon-brush:before { content: '\e813'; } /* '' */
@@ -80,7 +80,9 @@
 .icon-adjust:before { content: '\e816'; } /* '' */
 .icon-edit:before { content: '\e817'; } /* '' */
 .icon-pencil:before { content: '\e818'; } /* '' */
-.icon-emo-happy:before { content: '\e819'; } /* '' */
+.icon-pin:before { content: '\e819'; } /* '' */
+.icon-wrench:before { content: '\e81a'; } /* '' */
+.icon-chart-bar:before { content: '\e81b'; } /* '' */
 .icon-spin3:before { content: '\e832'; } /* '' */
 .icon-spin4:before { content: '\e834'; } /* '' */
 .icon-link-ext:before { content: '\f08e'; } /* '' */
@@ -91,8 +93,8 @@
 .icon-bell-alt:before { content: '\f0f3'; } /* '' */
 .icon-plus-squared:before { content: '\f0fe'; } /* '' */
 .icon-reply:before { content: '\f112'; } /* '' */
-.icon-smile:before { content: '\f118'; } /* '' */
 .icon-lock-open-alt:before { content: '\f13e'; } /* '' */
+.icon-ellipsis:before { content: '\f141'; } /* '' */
 .icon-play-circled:before { content: '\f144'; } /* '' */
 .icon-thumbs-up-alt:before { content: '\f164'; } /* '' */
 .icon-binoculars:before { content: '\f1e5'; } /* '' */
diff --git a/static/font/demo.html b/static/font/demo.html
index 21fb1408..61dfae76 100755
--- a/static/font/demo.html
+++ b/static/font/demo.html
@@ -229,11 +229,11 @@ body {
 }
 @font-face {
       font-family: 'fontello';
-      src: url('./font/fontello.eot?93986776');
-      src: url('./font/fontello.eot?93986776#iefix') format('embedded-opentype'),
-           url('./font/fontello.woff?93986776') format('woff'),
-           url('./font/fontello.ttf?93986776') format('truetype'),
-           url('./font/fontello.svg?93986776#fontello') format('svg');
+      src: url('./font/fontello.eot?82370835');
+      src: url('./font/fontello.eot?82370835#iefix') format('embedded-opentype'),
+           url('./font/fontello.woff?82370835') format('woff'),
+           url('./font/fontello.ttf?82370835') format('truetype'),
+           url('./font/fontello.svg?82370835#fontello') format('svg');
       font-weight: normal;
       font-style: normal;
     }
@@ -322,7 +322,7 @@ body {
         <div class="the-icons span3" title="Code: 0xe80f"><i class="demo-icon icon-up-open">&#xe80f;</i> <span class="i-name">icon-up-open</span><span class="i-code">0xe80f</span></div>
       </div>
       <div class="row">
-        <div class="the-icons span3" title="Code: 0xe810"><i class="demo-icon icon-bell">&#xe810;</i> <span class="i-name">icon-bell</span><span class="i-code">0xe810</span></div>
+        <div class="the-icons span3" title="Code: 0xe810"><i class="demo-icon icon-bell-ringing-o">&#xe810;</i> <span class="i-name">icon-bell-ringing-o</span><span class="i-code">0xe810</span></div>
         <div class="the-icons span3" title="Code: 0xe811"><i class="demo-icon icon-lock">&#xe811;</i> <span class="i-name">icon-lock</span><span class="i-code">0xe811</span></div>
         <div class="the-icons span3" title="Code: 0xe812"><i class="demo-icon icon-globe">&#xe812;</i> <span class="i-name">icon-globe</span><span class="i-code">0xe812</span></div>
         <div class="the-icons span3" title="Code: 0xe813"><i class="demo-icon icon-brush">&#xe813;</i> <span class="i-name">icon-brush</span><span class="i-code">0xe813</span></div>
@@ -335,29 +335,31 @@ body {
       </div>
       <div class="row">
         <div class="the-icons span3" title="Code: 0xe818"><i class="demo-icon icon-pencil">&#xe818;</i> <span class="i-name">icon-pencil</span><span class="i-code">0xe818</span></div>
-        <div class="the-icons span3" title="Code: 0xe819"><i class="demo-icon icon-emo-happy">&#xe819;</i> <span class="i-name">icon-emo-happy</span><span class="i-code">0xe819</span></div>
+        <div class="the-icons span3" title="Code: 0xe819"><i class="demo-icon icon-pin">&#xe819;</i> <span class="i-name">icon-pin</span><span class="i-code">0xe819</span></div>
+        <div class="the-icons span3" title="Code: 0xe81a"><i class="demo-icon icon-wrench">&#xe81a;</i> <span class="i-name">icon-wrench</span><span class="i-code">0xe81a</span></div>
+        <div class="the-icons span3" title="Code: 0xe81b"><i class="demo-icon icon-chart-bar">&#xe81b;</i> <span class="i-name">icon-chart-bar</span><span class="i-code">0xe81b</span></div>
+      </div>
+      <div class="row">
         <div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin">&#xe832;</i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div>
         <div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-spin4 animate-spin">&#xe834;</i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div>
-      </div>
-      <div class="row">
         <div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div>
         <div class="the-icons span3" title="Code: 0xf08f"><i class="demo-icon icon-link-ext-alt">&#xf08f;</i> <span class="i-name">icon-link-ext-alt</span><span class="i-code">0xf08f</span></div>
+      </div>
+      <div class="row">
         <div class="the-icons span3" title="Code: 0xf0c9"><i class="demo-icon icon-menu">&#xf0c9;</i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span></div>
         <div class="the-icons span3" title="Code: 0xf0e0"><i class="demo-icon icon-mail-alt">&#xf0e0;</i> <span class="i-name">icon-mail-alt</span><span class="i-code">0xf0e0</span></div>
-      </div>
-      <div class="row">
         <div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty">&#xf0e5;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div>
         <div class="the-icons span3" title="Code: 0xf0f3"><i class="demo-icon icon-bell-alt">&#xf0f3;</i> <span class="i-name">icon-bell-alt</span><span class="i-code">0xf0f3</span></div>
+      </div>
+      <div class="row">
         <div class="the-icons span3" title="Code: 0xf0fe"><i class="demo-icon icon-plus-squared">&#xf0fe;</i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xf0fe</span></div>
         <div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply">&#xf112;</i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div>
+        <div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div>
+        <div class="the-icons span3" title="Code: 0xf141"><i class="demo-icon icon-ellipsis">&#xf141;</i> <span class="i-name">icon-ellipsis</span><span class="i-code">0xf141</span></div>
       </div>
       <div class="row">
-        <div class="the-icons span3" title="Code: 0xf118"><i class="demo-icon icon-smile">&#xf118;</i> <span class="i-name">icon-smile</span><span class="i-code">0xf118</span></div>
-        <div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div>
         <div class="the-icons span3" title="Code: 0xf144"><i class="demo-icon icon-play-circled">&#xf144;</i> <span class="i-name">icon-play-circled</span><span class="i-code">0xf144</span></div>
         <div class="the-icons span3" title="Code: 0xf164"><i class="demo-icon icon-thumbs-up-alt">&#xf164;</i> <span class="i-name">icon-thumbs-up-alt</span><span class="i-code">0xf164</span></div>
-      </div>
-      <div class="row">
         <div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars">&#xf1e5;</i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div>
         <div class="the-icons span3" title="Code: 0xf234"><i class="demo-icon icon-user-plus">&#xf234;</i> <span class="i-name">icon-user-plus</span><span class="i-code">0xf234</span></div>
       </div>
diff --git a/static/font/font/fontello.eot b/static/font/font/fontello.eot
index 65d4f202..ca8d57f6 100755
Binary files a/static/font/font/fontello.eot and b/static/font/font/fontello.eot differ
diff --git a/static/font/font/fontello.svg b/static/font/font/fontello.svg
index 9f788daa..7849638e 100755
--- a/static/font/font/fontello.svg
+++ b/static/font/font/fontello.svg
@@ -38,7 +38,7 @@
 
 <glyph glyph-name="up-open" unicode="&#xe80f;" d="M939 114l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
 
-<glyph glyph-name="bell" unicode="&#xe810;" d="M509-89q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m-372 160h726q-149 168-149 465 0 28-13 58t-39 58-67 45-95 17-95-17-67-45-39-58-13-58q0-297-149-465z m827 0q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
+<glyph glyph-name="bell-ringing-o" unicode="&#xe810;" d="M498 857c-30 0-54-24-54-53 0-8 2-15 5-22-147-22-236-138-236-245 0-268-95-393-177-462 0-39 32-71 71-71h249c0-79 63-143 142-143s142 64 142 143h249c39 0 71 32 71 71-82 69-178 194-178 462 0 107-88 223-235 245 2 7 4 14 4 22 0 29-24 53-53 53z m-309-45c-81-74-118-170-118-275l71 0c0 89 28 162 95 223l-48 52z m617 0l-48-52c67-61 96-134 95-223l71 0c1 105-37 201-118 275z m-397-799c5 0 9-4 9-9 0-44 36-80 80-80 5 0 9-4 9-9s-4-9-9-9c-54 0-98 44-98 98 0 5 4 9 9 9z" horiz-adv-x="1000" />
 
 <glyph glyph-name="lock" unicode="&#xe811;" d="M179 428h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
 
@@ -56,7 +56,11 @@
 
 <glyph glyph-name="pencil" unicode="&#xe818;" d="M203 0l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
 
-<glyph glyph-name="emo-happy" unicode="&#xe819;" d="M261 807c-60 0-109-65-109-144 0-80 49-145 109-145s110 65 110 145c0 79-49 144-110 144z m477 0c-61 0-110-65-110-144 0-80 49-145 110-145 60 0 110 65 110 145 0 79-50 144-110 144z m208-599c-13 0-27-5-37-16-4-4-8-8-12-12-111-109-253-164-396-165-142-2-285 50-396 155l-3 3-12 12c-21 21-54 20-75-1-20-21-20-55 1-76 3-4 8-8 14-14l3-3c132-124 301-186 469-184 169 1 337 67 468 195 5 5 9 10 14 14 20 22 20 56-1 77-10 10-23 15-37 15z" horiz-adv-x="999" />
+<glyph glyph-name="pin" unicode="&#xe819;" d="M268 375v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="wrench" unicode="&#xe81a;" d="M214 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="chart-bar" unicode="&#xe81b;" d="M357 357v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" />
 
 <glyph glyph-name="spin3" unicode="&#xe832;" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
 
@@ -78,10 +82,10 @@
 
 <glyph glyph-name="reply" unicode="&#xf112;" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
 
-<glyph glyph-name="smile" unicode="&#xf118;" d="M633 257q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-
 <glyph glyph-name="lock-open-alt" unicode="&#xf13e;" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
 
+<glyph glyph-name="ellipsis" unicode="&#xf141;" d="M214 446v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-15 38v107q0 23 15 38t38 16h107q23 0 38-16t16-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-16 38v107q0 23 16 38t38 16h107q23 0 38-16t16-38z" horiz-adv-x="785.7" />
+
 <glyph glyph-name="play-circled" unicode="&#xf144;" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m214-460q18 10 18 31t-18 31l-304 178q-17 11-35 1-18-11-18-31v-358q0-20 18-31 9-4 17-4 10 0 18 5z" horiz-adv-x="857.1" />
 
 <glyph glyph-name="thumbs-up-alt" unicode="&#xf164;" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" />
diff --git a/static/font/font/fontello.ttf b/static/font/font/fontello.ttf
index d35a7ab5..e0fd784f 100755
Binary files a/static/font/font/fontello.ttf and b/static/font/font/fontello.ttf differ
diff --git a/static/font/font/fontello.woff b/static/font/font/fontello.woff
index ffd4b044..286c7d4d 100755
Binary files a/static/font/font/fontello.woff and b/static/font/font/fontello.woff differ
diff --git a/static/font/font/fontello.woff2 b/static/font/font/fontello.woff2
index b8f1c444..5e5a3a2c 100755
Binary files a/static/font/font/fontello.woff2 and b/static/font/font/fontello.woff2 differ
diff --git a/static/timeago-ca.json b/static/timeago-ca.json
deleted file mode 100644
index ef782caf..00000000
--- a/static/timeago-ca.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
-  "ara mateix",
-  ["fa %s s",     "fa %s s"],
-  ["fa %s min",   "fa %s min"],
-  ["fa %s h",     "fa %s h"],
-  ["fa %s dia",   "fa %s dies"],
-  ["fa %s setm.", "fa %s setm."],
-  ["fa %s mes",   "fa %s mesos"],
-  ["fa %s any",   "fa %s anys"]
-]
diff --git a/static/timeago-cs.json b/static/timeago-cs.json
deleted file mode 100644
index 697a0397..00000000
--- a/static/timeago-cs.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
-  "teď",
-  ["%s s", "%s s"],
-  ["%s min", "%s min"],
-  ["%s h", "%s h"],
-  ["%s d", "%s d"],
-  ["%s týd", "%s týd"],
-  ["%s měs", "%s měs"],
-  ["%s r", "%s l"]
-]
diff --git a/static/timeago-en.json b/static/timeago-en.json
deleted file mode 100644
index 073ece16..00000000
--- a/static/timeago-en.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
-  "now",
-  ["%ss", "%ss"],
-  ["%smin", "%smin"],
-  ["%sh", "%sh"],
-  ["%sd", "%sd"],
-  ["%sw", "%sw"],
-  ["%smo", "%smo"],
-  ["%sy", "%sy"]
-]
diff --git a/static/timeago-ga.json b/static/timeago-ga.json
deleted file mode 100644
index bdb7b6c4..00000000
--- a/static/timeago-ga.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
-  "Anois",
-  ["%s s", "%s s"],
-  ["%s n", "%s nóimeád"],
-  ["%s u", "%s uair"],
-  ["%s l", "%s lá"],
-  ["%s se", "%s seachtaine"],
-  ["%s m", "%s mí"],
-  ["%s b", "%s bliainta"]
-]
\ No newline at end of file
diff --git a/static/timeago-ja.json b/static/timeago-ja.json
deleted file mode 100644
index a0f975be..00000000
--- a/static/timeago-ja.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
-  "たった今",
-  "%s 秒前",
-  "%s 分前",
-  "%s 時間前",
-  "%s 日前",
-  "%s 週間前",
-  "%s ヶ月前",
-  "%s 年前"
-]
diff --git a/static/timeago-oc.json b/static/timeago-oc.json
deleted file mode 100644
index a6b3932f..00000000
--- a/static/timeago-oc.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
-  "ara meteis",
-  ["fa %s s",     "fa %s s"],
-  ["fa %s min",   "fa %s min"],
-  ["fa %s h",     "fa %s h"],
-  ["fa %s jorn",   "fa %s jorns"],
-  ["fa %s setm.", "fa %s setm."],
-  ["fa %s mes",   "fa %s meses"],
-  ["fa %s an",   "fa %s ans"]
-]
diff --git a/test/e2e/nightwatch.conf.js b/test/e2e/nightwatch.conf.js
index a5e55e90..2fc3af0b 100644
--- a/test/e2e/nightwatch.conf.js
+++ b/test/e2e/nightwatch.conf.js
@@ -3,43 +3,43 @@ var config = require('../../config')
 
 // http://nightwatchjs.org/guide#settings-file
 module.exports = {
-  "src_folders": ["test/e2e/specs"],
-  "output_folder": "test/e2e/reports",
-  "custom_assertions_path": ["test/e2e/custom-assertions"],
+  'src_folders': ['test/e2e/specs'],
+  'output_folder': 'test/e2e/reports',
+  'custom_assertions_path': ['test/e2e/custom-assertions'],
 
-  "selenium": {
-    "start_process": true,
-    "server_path": "node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.1.jar",
-    "host": "127.0.0.1",
-    "port": 4444,
-    "cli_args": {
-      "webdriver.chrome.driver": require('chromedriver').path
+  'selenium': {
+    'start_process': true,
+    'server_path': 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.1.jar',
+    'host': '127.0.0.1',
+    'port': 4444,
+    'cli_args': {
+      'webdriver.chrome.driver': require('chromedriver').path
     }
   },
 
-  "test_settings": {
-    "default": {
-      "selenium_port": 4444,
-      "selenium_host": "localhost",
-      "silent": true,
-      "globals": {
-        "devServerURL": "http://localhost:" + (process.env.PORT || config.dev.port)
+  'test_settings': {
+    'default': {
+      'selenium_port': 4444,
+      'selenium_host': 'localhost',
+      'silent': true,
+      'globals': {
+        'devServerURL': 'http://localhost:' + (process.env.PORT || config.dev.port)
       }
     },
 
-    "chrome": {
-      "desiredCapabilities": {
-        "browserName": "chrome",
-        "javascriptEnabled": true,
-        "acceptSslCerts": true
+    'chrome': {
+      'desiredCapabilities': {
+        'browserName': 'chrome',
+        'javascriptEnabled': true,
+        'acceptSslCerts': true
       }
     },
 
-    "firefox": {
-      "desiredCapabilities": {
-        "browserName": "firefox",
-        "javascriptEnabled": true,
-        "acceptSslCerts": true
+    'firefox': {
+      'desiredCapabilities': {
+        'browserName': 'firefox',
+        'javascriptEnabled': true,
+        'acceptSslCerts': true
       }
     }
   }
diff --git a/test/fixtures/mastoapi.json b/test/fixtures/mastoapi.json
index 858d7a0d..887fb83e 100644
--- a/test/fixtures/mastoapi.json
+++ b/test/fixtures/mastoapi.json
@@ -58,7 +58,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639",
     "url": "https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -127,7 +130,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/6634d32b-96a8-4852-a3db-ac8730715779",
     "url": "https://shigusegubu.club/objects/6634d32b-96a8-4852-a3db-ac8730715779",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -250,7 +256,10 @@
         "tags": [],
         "uri": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
         "url": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
-        "visibility": "public"
+        "visibility": "public",
+        "pleroma": {
+            "local": true
+        }
     },
     "reblogged": false,
     "reblogs_count": 0,
@@ -260,7 +269,10 @@
     "tags": [],
     "uri": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
     "url": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -329,7 +341,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/0f963ca1-a263-41ca-a43c-b5d26d0a08e9",
     "url": "https://shigusegubu.club/objects/0f963ca1-a263-41ca-a43c-b5d26d0a08e9",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -390,7 +405,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/3f809bd8-656f-4a29-81d4-80eed6916eb0",
     "url": "https://shigusegubu.club/objects/3f809bd8-656f-4a29-81d4-80eed6916eb0",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -516,7 +534,10 @@
         }],
         "uri": "tag:social.super-niche.club,2019-01-17:noticeId=2353002:objectType=note",
         "url": "https://social.super-niche.club/notice/2353002",
-        "visibility": "public"
+        "visibility": "public",
+        "pleroma": {
+            "local": true
+        }
     },
     "reblogged": false,
     "reblogs_count": 0,
@@ -529,7 +550,10 @@
     }],
     "uri": "tag:social.super-niche.club,2019-01-17:noticeId=2353002:objectType=note",
     "url": "tag:social.super-niche.club,2019-01-17:noticeId=2353002:objectType=note",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -657,7 +681,10 @@
         "tags": [],
         "uri": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
         "url": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
-        "visibility": "public"
+        "visibility": "public",
+        "pleroma": {
+            "local": true
+        }
     },
     "reblogged": false,
     "reblogs_count": 0,
@@ -667,7 +694,10 @@
     "tags": [],
     "uri": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
     "url": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -733,7 +763,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/38b1bc44-15d8-40dd-b1aa-937e0ff4a86d",
     "url": "https://shigusegubu.club/objects/38b1bc44-15d8-40dd-b1aa-937e0ff4a86d",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -794,7 +827,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/fbff5da4-a517-42a9-bca9-17cae8cf2542",
     "url": "https://shigusegubu.club/objects/fbff5da4-a517-42a9-bca9-17cae8cf2542",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -850,7 +886,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/4007d659-27c6-4577-be10-fd134f5e4e7e",
     "url": "https://shigusegubu.club/objects/4007d659-27c6-4577-be10-fd134f5e4e7e",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -906,7 +945,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/59912d51-1cc6-4dc7-828c-f167e6c8b391",
     "url": "https://shigusegubu.club/objects/59912d51-1cc6-4dc7-828c-f167e6c8b391",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -962,7 +1004,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/62690bce-3f49-4047-9c8e-8941f2f79e10",
     "url": "https://shigusegubu.club/objects/62690bce-3f49-4047-9c8e-8941f2f79e10",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -1023,7 +1068,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/818f3dd0-2ff8-4def-a170-e4d4c405f387",
     "url": "https://shigusegubu.club/objects/818f3dd0-2ff8-4def-a170-e4d4c405f387",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -1084,7 +1132,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/0783a193-c097-488d-8944-47df9372cd6e",
     "url": "https://shigusegubu.club/objects/0783a193-c097-488d-8944-47df9372cd6e",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -1145,7 +1196,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/145d5252-7b8e-467d-9f36-1db0818f452f",
     "url": "https://shigusegubu.club/objects/145d5252-7b8e-467d-9f36-1db0818f452f",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -1252,7 +1306,10 @@
         "tags": [],
         "uri": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
         "url": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
-        "visibility": "public"
+        "visibility": "public",
+        "pleroma": {
+            "local": true
+        }
     },
     "reblogged": false,
     "reblogs_count": 0,
@@ -1262,7 +1319,10 @@
     "tags": [],
     "uri": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
     "url": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -1323,7 +1383,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/d4eb7c46-02f9-4b1f-83af-926cefa21f33",
     "url": "https://shigusegubu.club/objects/d4eb7c46-02f9-4b1f-83af-926cefa21f33",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -1446,7 +1509,10 @@
         "tags": [],
         "uri": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
         "url": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
-        "visibility": "public"
+        "visibility": "public",
+        "pleroma": {
+            "local": true
+        }
     },
     "reblogged": false,
     "reblogs_count": 0,
@@ -1456,7 +1522,10 @@
     "tags": [],
     "uri": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
     "url": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -1517,7 +1586,10 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/f472f4ed-8b0b-492f-9d53-d69eda79629d",
     "url": "https://shigusegubu.club/objects/f472f4ed-8b0b-492f-9d53-d69eda79629d",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }, {
     "account": {
         "acct": "hj",
@@ -1578,5 +1650,8 @@
     "tags": [],
     "uri": "https://shigusegubu.club/objects/d6fb4fd2-1f6a-4446-a1a6-5edd34050096",
     "url": "https://shigusegubu.club/objects/d6fb4fd2-1f6a-4446-a1a6-5edd34050096",
-    "visibility": "public"
+    "visibility": "public",
+    "pleroma": {
+        "local": true
+    }
 }]
diff --git a/test/unit/index.js b/test/unit/index.js
index 03b19e32..83a2dcdb 100644
--- a/test/unit/index.js
+++ b/test/unit/index.js
@@ -1,7 +1,3 @@
-// Polyfill fn.bind() for PhantomJS
-/* eslint-disable no-extend-native */
-Function.prototype.bind = require('function-bind')
-
 // require all test files (files that ends with .spec.js)
 const testsContext = require.context('./specs', true, /\.spec$/)
 testsContext.keys().forEach(testsContext)
@@ -9,5 +5,5 @@ testsContext.keys().forEach(testsContext)
 // require all src files except main.js for coverage.
 // you can also change this to match only the subset of files that
 // you want coverage for.
-const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
-srcContext.keys().forEach(srcContext)
+// const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
+// srcContext.keys().forEach(srcContext)
diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js
index d19a2229..8af05c24 100644
--- a/test/unit/karma.conf.js
+++ b/test/unit/karma.conf.js
@@ -3,17 +3,17 @@
 // we are also using it with karma-webpack
 //   https://github.com/webpack/karma-webpack
 
-var path = require('path')
+// var path = require('path')
 var merge = require('webpack-merge')
 var baseConfig = require('../../build/webpack.base.conf')
 var utils = require('../../build/utils')
 var webpack = require('webpack')
-var projectRoot = path.resolve(__dirname, '../../')
+// var projectRoot = path.resolve(__dirname, '../../')
 
 var webpackConfig = merge(baseConfig, {
   // use inline sourcemap for karma-sourcemap-loader
   module: {
-    loaders: utils.styleLoaders()
+    rules: utils.styleLoaders()
   },
   devtool: '#inline-source-map',
   // vue: {
@@ -53,11 +53,18 @@ module.exports = function (config) {
     // 1. install corresponding karma launcher
     //    http://karma-runner.github.io/0.13/config/browsers.html
     // 2. add it to the `browsers` array below.
-    browsers: ['PhantomJS'],
+    browsers: ['FirefoxHeadless'],
     frameworks: ['mocha', 'sinon-chai'],
     reporters: ['mocha'],
+    customLaunchers: {
+      'FirefoxHeadless': {
+        base: 'Firefox',
+        flags: [
+          '-headless'
+        ]
+      }
+    },
     files: [
-      '../../node_modules/@babel/polyfill/dist/polyfill.js',
       './index.js'
     ],
     preprocessors: {
diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js
index 847481f3..6de9491a 100644
--- a/test/unit/specs/components/user_profile.spec.js
+++ b/test/unit/specs/components/user_profile.spec.js
@@ -39,6 +39,7 @@ const externalProfileStore = new Vuex.Store({
   getters: testGetters,
   state: {
     api: {
+      fetchers: {},
       backendInteractor: backendInteractorService('')
     },
     interface: {
@@ -106,6 +107,7 @@ const localProfileStore = new Vuex.Store({
   getters: testGetters,
   state: {
     api: {
+      fetchers: {},
       backendInteractor: backendInteractorService('')
     },
     interface: {
diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js
index 0bbcb25a..f794997b 100644
--- a/test/unit/specs/modules/statuses.spec.js
+++ b/test/unit/specs/modules/statuses.spec.js
@@ -1,10 +1,10 @@
 import { defaultState, mutations, prepareStatus } from '../../../../src/modules/statuses.js'
 
 // eslint-disable-next-line camelcase
-const makeMockStatus = ({id, text, type = 'status'}) => {
+const makeMockStatus = ({ id, text, type = 'status' }) => {
   return {
     id,
-    user: {id: '0'},
+    user: { id: '0' },
     name: 'status',
     text: text || `Text number ${id}`,
     fave_num: 0,
@@ -17,7 +17,7 @@ const makeMockStatus = ({id, text, type = 'status'}) => {
 describe('Statuses module', () => {
   describe('prepareStatus', () => {
     it('sets deleted flag to false', () => {
-      const aStatus = makeMockStatus({id: '1', text: 'Hello oniichan'})
+      const aStatus = makeMockStatus({ id: '1', text: 'Hello oniichan' })
       expect(prepareStatus(aStatus).deleted).to.eq(false)
     })
   })
@@ -25,7 +25,7 @@ describe('Statuses module', () => {
   describe('addNewStatuses', () => {
     it('adds the status to allStatuses and to the given timeline', () => {
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
+      const status = makeMockStatus({ id: '1' })
 
       mutations.addNewStatuses(state, { statuses: [status], timeline: 'public' })
 
@@ -37,7 +37,7 @@ describe('Statuses module', () => {
 
     it('counts the status as new if it has not been seen on this timeline', () => {
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
+      const status = makeMockStatus({ id: '1' })
 
       mutations.addNewStatuses(state, { statuses: [status], timeline: 'public' })
       mutations.addNewStatuses(state, { statuses: [status], timeline: 'friends' })
@@ -55,7 +55,7 @@ describe('Statuses module', () => {
 
     it('add the statuses to allStatuses if no timeline is given', () => {
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
+      const status = makeMockStatus({ id: '1' })
 
       mutations.addNewStatuses(state, { statuses: [status] })
 
@@ -67,7 +67,7 @@ describe('Statuses module', () => {
 
     it('adds the status to allStatuses and to the given timeline, directly visible', () => {
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
+      const status = makeMockStatus({ id: '1' })
 
       mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
 
@@ -79,10 +79,10 @@ describe('Statuses module', () => {
 
     it('removes statuses by tag on deletion', () => {
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
-      const otherStatus = makeMockStatus({id: '3'})
+      const status = makeMockStatus({ id: '1' })
+      const otherStatus = makeMockStatus({ id: '3' })
       status.uri = 'xxx'
-      const deletion = makeMockStatus({id: '2', type: 'deletion'})
+      const deletion = makeMockStatus({ id: '2', type: 'deletion' })
       deletion.text = 'Dolus deleted notice {{tag:gs.smuglo.li,2016-11-18:noticeId=1038007:objectType=note}}.'
       deletion.uri = 'xxx'
 
@@ -97,8 +97,8 @@ describe('Statuses module', () => {
 
     it('does not update the maxId when the noIdUpdate flag is set', () => {
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
-      const secondStatus = makeMockStatus({id: '2'})
+      const status = makeMockStatus({ id: '1' })
+      const secondStatus = makeMockStatus({ id: '2' })
 
       mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
       expect(state.timelines.public.maxId).to.eql('1')
@@ -111,10 +111,10 @@ describe('Statuses module', () => {
 
     it('keeps a descending by id order in timeline.visibleStatuses and timeline.statuses', () => {
       const state = defaultState()
-      const nonVisibleStatus = makeMockStatus({id: '1'})
-      const status = makeMockStatus({id: '3'})
-      const statusTwo = makeMockStatus({id: '2'})
-      const statusThree = makeMockStatus({id: '4'})
+      const nonVisibleStatus = makeMockStatus({ id: '1' })
+      const status = makeMockStatus({ id: '3' })
+      const statusTwo = makeMockStatus({ id: '2' })
+      const statusThree = makeMockStatus({ id: '4' })
 
       mutations.addNewStatuses(state, { statuses: [nonVisibleStatus], showImmediately: false, timeline: 'public' })
 
@@ -131,9 +131,9 @@ describe('Statuses module', () => {
 
     it('splits retweets from their status and links them', () => {
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
-      const retweet = makeMockStatus({id: '2', type: 'retweet'})
-      const modStatus = makeMockStatus({id: '1', text: 'something else'})
+      const status = makeMockStatus({ id: '1' })
+      const retweet = makeMockStatus({ id: '2', type: 'retweet' })
+      const modStatus = makeMockStatus({ id: '1', text: 'something else' })
 
       retweet.retweeted_status = status
 
@@ -156,8 +156,8 @@ describe('Statuses module', () => {
 
     it('replaces existing statuses with the same id', () => {
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
-      const modStatus = makeMockStatus({id: '1', text: 'something else'})
+      const status = makeMockStatus({ id: '1' })
+      const modStatus = makeMockStatus({ id: '1', text: 'something else' })
 
       // Add original status
       mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
@@ -173,9 +173,9 @@ describe('Statuses module', () => {
 
     it('replaces existing statuses with the same id, coming from a retweet', () => {
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
-      const modStatus = makeMockStatus({id: '1', text: 'something else'})
-      const retweet = makeMockStatus({id: '2', type: 'retweet'})
+      const status = makeMockStatus({ id: '1' })
+      const modStatus = makeMockStatus({ id: '1', text: 'something else' })
+      const retweet = makeMockStatus({ id: '2', type: 'retweet' })
       retweet.retweeted_status = modStatus
 
       // Add original status
@@ -194,7 +194,7 @@ describe('Statuses module', () => {
 
     it('handles favorite actions', () => {
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
+      const status = makeMockStatus({ id: '1' })
 
       const favorite = {
         id: '2',
@@ -258,11 +258,11 @@ describe('Statuses module', () => {
   })
 
   describe('clearTimeline', () => {
-    it('keeps userId when clearing user timeline', () => {
+    it('keeps userId when clearing user timeline when excludeUserId param is true', () => {
       const state = defaultState()
       state.timelines.user.userId = 123
 
-      mutations.clearTimeline(state, { timeline: 'user' })
+      mutations.clearTimeline(state, { timeline: 'user', excludeUserId: true })
 
       expect(state.timelines.user.userId).to.eql(123)
     })
@@ -272,14 +272,14 @@ describe('Statuses module', () => {
     it('removes a notification when the notice gets removed', () => {
       const user = { id: '1' }
       const state = defaultState()
-      const status = makeMockStatus({id: '1'})
-      const otherStatus = makeMockStatus({id: '3'})
-      const mentionedStatus = makeMockStatus({id: '2'})
+      const status = makeMockStatus({ id: '1' })
+      const otherStatus = makeMockStatus({ id: '3' })
+      const mentionedStatus = makeMockStatus({ id: '2' })
       mentionedStatus.attentions = [user]
       mentionedStatus.uri = 'xxx'
       otherStatus.attentions = [user]
 
-      const deletion = makeMockStatus({id: '4', type: 'deletion'})
+      const deletion = makeMockStatus({ id: '4', type: 'deletion' })
       deletion.text = 'Dolus deleted notice {{tag:gs.smuglo.li,2016-11-18:noticeId=1038007:objectType=note}}.'
       deletion.uri = 'xxx'
 
diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js
index c8bc0ae7..eeb7afef 100644
--- a/test/unit/specs/modules/users.spec.js
+++ b/test/unit/specs/modules/users.spec.js
@@ -24,11 +24,11 @@ describe('The users module', () => {
       const user = { id: '1', name: 'Guy' }
 
       mutations.addNewUsers(state, [user])
-      mutations.setMuted(state, {user, muted: true})
+      mutations.setMuted(state, { user, muted: true })
 
       expect(user.muted).to.eql(true)
 
-      mutations.setMuted(state, {user, muted: false})
+      mutations.setMuted(state, { user, muted: false })
 
       expect(user.muted).to.eql(false)
     })
diff --git a/test/unit/specs/services/date_utils/date_utils.spec.js b/test/unit/specs/services/date_utils/date_utils.spec.js
new file mode 100644
index 00000000..2d61dbac
--- /dev/null
+++ b/test/unit/specs/services/date_utils/date_utils.spec.js
@@ -0,0 +1,40 @@
+import * as DateUtils from 'src/services/date_utils/date_utils.js'
+
+describe('DateUtils', () => {
+  describe('relativeTime', () => {
+    it('returns now with low enough amount of seconds', () => {
+      const futureTime = Date.now() + 20 * DateUtils.SECOND
+      const pastTime = Date.now() - 20 * DateUtils.SECOND
+      expect(DateUtils.relativeTime(futureTime, 30)).to.eql({ num: 0, key: 'time.now' })
+      expect(DateUtils.relativeTime(pastTime, 30)).to.eql({ num: 0, key: 'time.now' })
+    })
+
+    it('rounds down for past', () => {
+      const time = Date.now() - 1.8 * DateUtils.HOUR
+      expect(DateUtils.relativeTime(time)).to.eql({ num: 1, key: 'time.hour' })
+    })
+
+    it('rounds up for future', () => {
+      const time = Date.now() + 1.8 * DateUtils.HOUR
+      expect(DateUtils.relativeTime(time)).to.eql({ num: 2, key: 'time.hours' })
+    })
+
+    it('uses plural when necessary', () => {
+      const time = Date.now() - 3.8 * DateUtils.WEEK
+      expect(DateUtils.relativeTime(time)).to.eql({ num: 3, key: 'time.weeks' })
+    })
+
+    it('works with date string', () => {
+      const time = Date.now() - 4 * DateUtils.MONTH
+      const dateString = new Date(time).toISOString()
+      expect(DateUtils.relativeTime(dateString)).to.eql({ num: 4, key: 'time.months' })
+    })
+  })
+
+  describe('relativeTimeShort', () => {
+    it('returns the short version of the same relative time', () => {
+      const time = Date.now() + 2 * DateUtils.YEAR
+      expect(DateUtils.relativeTimeShort(time)).to.eql({ num: 2, key: 'time.years_short' })
+    })
+  })
+})
diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
index 2b0b0d6d..20e03cb0 100644
--- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
+++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -129,7 +129,10 @@ const makeMockStatusMasto = (overrides = {}) => {
     tags: [],
     uri: 'https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639',
     url: 'https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639',
-    visibility: 'public'
+    visibility: 'public',
+    pleroma: {
+      local: true
+    }
   }, overrides)
 }
 
@@ -160,12 +163,6 @@ const makeMockEmojiMasto = (overrides = [{}]) => {
   ]
 }
 
-parseNotification
-parseUser
-parseStatus
-makeMockStatusQvitter
-makeMockUserQvitter
-
 describe('API Entities normalizer', () => {
   describe('parseStatus', () => {
     describe('QVitter preprocessing', () => {
@@ -203,15 +200,15 @@ describe('API Entities normalizer', () => {
       })
 
       it('sets nsfw for statuses with the #nsfw tag', () => {
-        const safe = makeMockStatusQvitter({id: '1', text: 'Hello oniichan'})
-        const nsfw = makeMockStatusQvitter({id: '1', text: 'Hello oniichan #nsfw'})
+        const safe = makeMockStatusQvitter({ id: '1', text: 'Hello oniichan' })
+        const nsfw = makeMockStatusQvitter({ id: '1', text: 'Hello oniichan #nsfw' })
 
         expect(parseStatus(safe).nsfw).to.eq(false)
         expect(parseStatus(nsfw).nsfw).to.eq(true)
       })
 
       it('leaves existing nsfw settings alone', () => {
-        const nsfw = makeMockStatusQvitter({id: '1', text: 'Hello oniichan #nsfw', nsfw: false})
+        const nsfw = makeMockStatusQvitter({ id: '1', text: 'Hello oniichan #nsfw', nsfw: false })
 
         expect(parseStatus(nsfw).nsfw).to.eq(false)
       })
@@ -279,6 +276,13 @@ describe('API Entities normalizer', () => {
 
       expect(parsedUser).to.have.property('description_html').that.contains('<img')
     })
+
+    it('adds hide_follows and hide_followers user settings', () => {
+      const user = makeMockUserMasto({ pleroma: { hide_followers: true, hide_follows: false } })
+
+      expect(parseUser(user)).to.have.property('hide_followers', true)
+      expect(parseUser(user)).to.have.property('hide_follows', false)
+    })
   })
 
   // We currently use QvitterAPI notifications only, and especially due to MastoAPI lacking is_seen, support for MastoAPI
@@ -319,10 +323,10 @@ describe('API Entities normalizer', () => {
 
   describe('MastoAPI emoji adder', () => {
     const emojis = makeMockEmojiMasto()
-    const imageHtml = '<img src="https://example.com/image.png" alt="image" class="emoji" />'
-          .replace(/"/g, '\'')
-    const thinkHtml = '<img src="https://example.com/think.png" alt="thinking" class="emoji" />'
-          .replace(/"/g, '\'')
+    const imageHtml = '<img src="https://example.com/image.png" alt="image" title="image" class="emoji" />'
+      .replace(/"/g, '\'')
+    const thinkHtml = '<img src="https://example.com/think.png" alt="thinking" title="thinking" class="emoji" />'
+      .replace(/"/g, '\'')
 
     it('correctly replaces shortcodes in supplied string', () => {
       const result = addEmojis('This post has :image: emoji and :thinking: emoji', emojis)
diff --git a/test/unit/specs/services/file_size_format/file_size_format.spec.js b/test/unit/specs/services/file_size_format/file_size_format.spec.js
index 0a5a82b7..e02ac379 100644
--- a/test/unit/specs/services/file_size_format/file_size_format.spec.js
+++ b/test/unit/specs/services/file_size_format/file_size_format.spec.js
@@ -1,34 +1,34 @@
- import fileSizeFormatService from '../../../../../src/services/file_size_format/file_size_format.js'
- describe('fileSizeFormat', () => {
-   it('Formats file size', () => {
-     const values = [1, 1024, 1048576, 1073741824, 1099511627776]
-     const expected = [
-       {
-         num: 1,
-         unit: 'B'
-       },
-       {
-         num: 1,
-         unit: 'KiB'
-       },
-       {
-         num: 1,
-         unit: 'MiB'
-       },
-       {
-         num: 1,
-         unit: 'GiB'
-       },
-       {
-         num: 1,
-         unit: 'TiB'
-       }
-     ]
+import fileSizeFormatService from '../../../../../src/services/file_size_format/file_size_format.js'
+describe('fileSizeFormat', () => {
+  it('Formats file size', () => {
+    const values = [1, 1024, 1048576, 1073741824, 1099511627776]
+    const expected = [
+      {
+        num: 1,
+        unit: 'B'
+      },
+      {
+        num: 1,
+        unit: 'KiB'
+      },
+      {
+        num: 1,
+        unit: 'MiB'
+      },
+      {
+        num: 1,
+        unit: 'GiB'
+      },
+      {
+        num: 1,
+        unit: 'TiB'
+      }
+    ]
 
-     var res = []
-     for (var value in values) {
-       res.push(fileSizeFormatService.fileSizeFormat(values[value]))
-     }
-     expect(res).to.eql(expected)
-   })
- })
+    var res = []
+    for (var value in values) {
+      res.push(fileSizeFormatService.fileSizeFormat(values[value]))
+    }
+    expect(res).to.eql(expected)
+  })
+})
diff --git a/test/unit/specs/services/gesture_service/gesture_service.spec.js b/test/unit/specs/services/gesture_service/gesture_service.spec.js
index 4a1b009a..a91f95db 100644
--- a/test/unit/specs/services/gesture_service/gesture_service.spec.js
+++ b/test/unit/specs/services/gesture_service/gesture_service.spec.js
@@ -9,7 +9,7 @@ const mockTouchEvent = (x, y) => ({
   ]
 })
 
-describe.only('GestureService', () => {
+describe('GestureService', () => {
   describe('swipeGesture', () => {
     it('calls the callback on a successful swipe', () => {
       let swiped = false
diff --git a/test/unit/specs/services/notification_utils/notification_utils.spec.js b/test/unit/specs/services/notification_utils/notification_utils.spec.js
index e945459e..1baa5fc9 100644
--- a/test/unit/specs/services/notification_utils/notification_utils.spec.js
+++ b/test/unit/specs/services/notification_utils/notification_utils.spec.js
@@ -9,14 +9,17 @@ describe('NotificationUtils', () => {
             notifications: {
               data: [
                 {
+                  id: 1,
                   action: { id: '1' },
                   type: 'like'
                 },
                 {
+                  id: 2,
                   action: { id: '2' },
                   type: 'mention'
                 },
                 {
+                  id: 3,
                   action: { id: '3' },
                   type: 'repeat'
                 }
@@ -35,10 +38,12 @@ describe('NotificationUtils', () => {
       const expected = [
         {
           action: { id: '3' },
+          id: 3,
           type: 'repeat'
         },
         {
           action: { id: '1' },
+          id: 1,
           type: 'like'
         }
       ]
diff --git a/test/unit/specs/services/status_parser/status_parses.spec.js b/test/unit/specs/services/status_parser/status_parses.spec.js
index 65808d84..7afd5042 100644
--- a/test/unit/specs/services/status_parser/status_parses.spec.js
+++ b/test/unit/specs/services/status_parser/status_parses.spec.js
@@ -1,7 +1,7 @@
-const example = '<div class="status-content">@<a href="https://sealion.club/user/4" class="h-card mention" title="dewoo">dwmatiz</a> <a href="https://social.heldscal.la/file/3deb764ada10ce64a61b7a070b75dac45f86d2d5bf213bf18873da71d8714d86.png" title="https://social.heldscal.la/file/3deb764ada10ce64a61b7a070b75dac45f86d2d5bf213bf18873da71d8714d86.png" class="attachment" id="attachment-159853" rel="nofollow external">https://social.heldscal.la/attachment/159853</a></div>'
-
 import { removeAttachmentLinks } from '../../../../../src/services/status_parser/status_parser.js'
 
+const example = '<div class="status-content">@<a href="https://sealion.club/user/4" class="h-card mention" title="dewoo">dwmatiz</a> <a href="https://social.heldscal.la/file/3deb764ada10ce64a61b7a070b75dac45f86d2d5bf213bf18873da71d8714d86.png" title="https://social.heldscal.la/file/3deb764ada10ce64a61b7a070b75dac45f86d2d5bf213bf18873da71d8714d86.png" class="attachment" id="attachment-159853" rel="nofollow external">https://social.heldscal.la/attachment/159853</a></div>'
+
 describe('statusParser.removeAttachmentLinks', () => {
   const exampleWithoutAttachmentLinks = '<div class="status-content">@<a href="https://sealion.club/user/4" class="h-card mention" title="dewoo">dwmatiz</a> </div>'
 
diff --git a/test/unit/specs/services/version/version.service.spec.js b/test/unit/specs/services/version/version.service.spec.js
new file mode 100644
index 00000000..519145ee
--- /dev/null
+++ b/test/unit/specs/services/version/version.service.spec.js
@@ -0,0 +1,11 @@
+import { extractCommit } from 'src/services/version/version.service.js'
+
+describe('extractCommit', () => {
+  it('return short commit hash following "-g" characters', () => {
+    expect(extractCommit('1.0.0-45-g5e7aeebc')).to.eql('5e7aeebc')
+  })
+
+  it('return short commit hash without branch name', () => {
+    expect(extractCommit('1.0.0-45-g5e7aeebc-branch')).to.eql('5e7aeebc')
+  })
+})
diff --git a/yarn.lock b/yarn.lock
index 0fe45479..13d94a23 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,12 +2,26 @@
 # yarn lockfile v1
 
 
+"@babel/code-frame@^7.0.0":
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
+  dependencies:
+    "@babel/highlight" "^7.0.0"
+
 "@babel/helper-module-imports@^7.0.0-beta.49":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d"
   dependencies:
     "@babel/types" "^7.0.0"
 
+"@babel/highlight@^7.0.0":
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
+  dependencies:
+    chalk "^2.0.0"
+    esutils "^2.0.2"
+    js-tokens "^4.0.0"
+
 "@babel/polyfill@^7.0.0":
   version "7.2.5"
   resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.2.5.tgz#6c54b964f71ad27edddc567d065e57e87ed7fa7d"
@@ -23,13 +37,11 @@
     lodash "^4.17.10"
     to-fast-properties "^2.0.0"
 
-"@types/node@^10.11.7":
-  version "10.12.18"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
-
-"@types/semver@^5.5.0":
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
+"@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"
+  dependencies:
+    qrcode "^1.3.0"
 
 "@vue/test-utils@^1.0.0-beta.26":
   version "1.0.0-beta.28"
@@ -38,16 +50,152 @@
     dom-event-types "^1.0.0"
     lodash "^4.17.4"
 
+"@webassemblyjs/ast@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
+  dependencies:
+    "@webassemblyjs/helper-module-context" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/wast-parser" "1.8.5"
+
+"@webassemblyjs/floating-point-hex-parser@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721"
+
+"@webassemblyjs/helper-api-error@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7"
+
+"@webassemblyjs/helper-buffer@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204"
+
+"@webassemblyjs/helper-code-frame@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e"
+  dependencies:
+    "@webassemblyjs/wast-printer" "1.8.5"
+
+"@webassemblyjs/helper-fsm@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452"
+
+"@webassemblyjs/helper-module-context@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245"
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    mamacro "^0.0.3"
+
+"@webassemblyjs/helper-wasm-bytecode@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61"
+
+"@webassemblyjs/helper-wasm-section@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf"
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-buffer" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/wasm-gen" "1.8.5"
+
+"@webassemblyjs/ieee754@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e"
+  dependencies:
+    "@xtuc/ieee754" "^1.2.0"
+
+"@webassemblyjs/leb128@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10"
+  dependencies:
+    "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/utf8@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc"
+
+"@webassemblyjs/wasm-edit@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a"
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-buffer" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/helper-wasm-section" "1.8.5"
+    "@webassemblyjs/wasm-gen" "1.8.5"
+    "@webassemblyjs/wasm-opt" "1.8.5"
+    "@webassemblyjs/wasm-parser" "1.8.5"
+    "@webassemblyjs/wast-printer" "1.8.5"
+
+"@webassemblyjs/wasm-gen@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc"
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/ieee754" "1.8.5"
+    "@webassemblyjs/leb128" "1.8.5"
+    "@webassemblyjs/utf8" "1.8.5"
+
+"@webassemblyjs/wasm-opt@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264"
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-buffer" "1.8.5"
+    "@webassemblyjs/wasm-gen" "1.8.5"
+    "@webassemblyjs/wasm-parser" "1.8.5"
+
+"@webassemblyjs/wasm-parser@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d"
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-api-error" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/ieee754" "1.8.5"
+    "@webassemblyjs/leb128" "1.8.5"
+    "@webassemblyjs/utf8" "1.8.5"
+
+"@webassemblyjs/wast-parser@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c"
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/floating-point-hex-parser" "1.8.5"
+    "@webassemblyjs/helper-api-error" "1.8.5"
+    "@webassemblyjs/helper-code-frame" "1.8.5"
+    "@webassemblyjs/helper-fsm" "1.8.5"
+    "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/wast-printer@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc"
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/wast-parser" "1.8.5"
+    "@xtuc/long" "4.2.2"
+
+"@xtuc/ieee754@^1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
+
+"@xtuc/long@4.2.2":
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
+
 abbrev@1, abbrev@1.0.x:
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
 
-accepts@1.3.3:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
+accepts@~1.3.4:
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
   dependencies:
-    mime-types "~2.1.11"
-    negotiator "0.6.1"
+    mime-types "~2.1.24"
+    negotiator "0.6.2"
 
 accepts@~1.3.5:
   version "1.3.5"
@@ -56,19 +204,17 @@ accepts@~1.3.5:
     mime-types "~2.1.18"
     negotiator "0.6.1"
 
-acorn-jsx@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
-  dependencies:
-    acorn "^3.0.4"
+acorn-dynamic-import@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948"
 
-acorn@^3.0.0, acorn@^3.0.4:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
+acorn-jsx@^5.0.0:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
 
-acorn@^5.5.0:
-  version "5.7.3"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
+acorn@^6.0.2, acorn@^6.0.5, acorn@^6.0.7:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f"
 
 after@0.8.2:
   version "0.8.2"
@@ -81,16 +227,22 @@ agent-base@2:
     extend "~3.0.0"
     semver "~5.0.1"
 
-ajv-keywords@^1.0.0:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
+ajv-errors@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
 
-ajv@^4.7.0:
-  version "4.11.8"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
+ajv-keywords@^3.1.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.0.tgz#4b831e7b531415a7cc518cd404e73f6193c6349d"
+
+ajv@^6.1.0, ajv@^6.9.1:
+  version "6.10.0"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
   dependencies:
-    co "^4.6.0"
-    json-stable-stringify "^1.0.1"
+    fast-deep-equal "^2.0.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
 
 ajv@^6.5.5:
   version "6.6.2"
@@ -101,14 +253,6 @@ ajv@^6.5.5:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
-align-text@^0.1.1, align-text@^0.1.3:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
-  dependencies:
-    kind-of "^3.0.2"
-    longest "^1.0.1"
-    repeat-string "^1.5.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"
@@ -117,9 +261,13 @@ amdefine@>=0.0.4:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
 
-ansi-escapes@^1.1.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
+ansi-colors@^3.0.0:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
+
+ansi-escapes@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
 
 ansi-html@0.0.7:
   version "0.0.7"
@@ -133,11 +281,15 @@ ansi-regex@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
 
+ansi-regex@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
+
 ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
 
-ansi-styles@^3.2.1:
+ansi-styles@^3.2.0, ansi-styles@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
   dependencies:
@@ -147,14 +299,14 @@ ansi-styles@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
 
-anymatch@^1.3.0:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
+anymatch@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
   dependencies:
-    micromatch "^2.1.5"
-    normalize-path "^2.0.0"
+    micromatch "^3.1.4"
+    normalize-path "^2.1.1"
 
-aproba@^1.0.3:
+aproba@^1.0.3, aproba@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
 
@@ -197,6 +349,13 @@ array-flatten@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
 
+array-includes@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
+  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"
@@ -219,9 +378,17 @@ array-unique@^0.3.2:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
 
-arraybuffer.slice@0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca"
+arraybuffer.slice@~0.0.7:
+  version "0.0.7"
+  resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
+
+asn1.js@^4.0.0:
+  version "4.10.1"
+  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
+  dependencies:
+    bn.js "^4.0.0"
+    inherits "^2.0.1"
+    minimalistic-assert "^1.0.0"
 
 asn1@~0.2.3:
   version "0.2.4"
@@ -255,32 +422,34 @@ ast-types@0.x.x:
   version "0.11.7"
   resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.7.tgz#f318bf44e339db6a320be0009ded64ec1471f46c"
 
-async-each@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
+astral-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
 
-async-foreach@^0.1.3:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
+async-each@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
 
-async@1.x, async@^1.3.0, async@^1.5.0:
+async-limiter@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
+
+async@1.x:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
-async@^0.9.0, async@~0.9.0:
-  version "0.9.2"
-  resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
+async@^2.0.0:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
+  dependencies:
+    lodash "^4.17.11"
 
-async@^2.0.1, async@^2.5.0:
+async@^2.5.0:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
   dependencies:
     lodash "^4.17.10"
 
-async@~0.2.6:
-  version "0.2.10"
-  resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
-
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -308,7 +477,7 @@ aws4@^1.8.0:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
 
-babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
+babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
   dependencies:
@@ -484,14 +653,13 @@ babel-helpers@^6.24.1:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-loader@^6.0.0:
-  version "6.4.1"
-  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-6.4.1.tgz#0b34112d5b0748a8dcdbf51acf6f9bd42d50b8ca"
+babel-loader@^7.0.0:
+  version "7.1.5"
+  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68"
   dependencies:
-    find-cache-dir "^0.1.1"
-    loader-utils "^0.2.16"
+    find-cache-dir "^1.0.0"
+    loader-utils "^1.0.2"
     mkdirp "^0.5.1"
-    object-assign "^4.0.1"
 
 babel-messages@^6.23.0:
   version "6.23.0"
@@ -997,20 +1165,22 @@ binary-extensions@^1.0.0:
   version "1.12.0"
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14"
 
-blob@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
+blob@0.0.5:
+  version "0.0.5"
+  resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
 
-block-stream@*:
-  version "0.0.9"
-  resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
-  dependencies:
-    inherits "~2.0.0"
-
-bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.4.7:
+bluebird@^3.1.1, bluebird@^3.3.0:
   version "3.5.3"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
 
+bluebird@^3.5.3:
+  version "3.5.4"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714"
+
+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"
+
 body-parser@1.18.3, body-parser@^1.16.1:
   version "1.18.3"
   resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4"
@@ -1051,7 +1221,7 @@ braces@^1.8.2:
     preserve "^0.2.0"
     repeat-element "^1.1.2"
 
-braces@^2.3.1:
+braces@^2.3.1, braces@^2.3.2:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
   dependencies:
@@ -1066,21 +1236,66 @@ braces@^2.3.1:
     split-string "^3.0.2"
     to-regex "^3.0.1"
 
+brorand@^1.0.1:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
+
 browser-stdout@1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
 
-browserify-aes@0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-0.4.0.tgz#067149b668df31c4b58533e02d01e806d8608e2c"
+browserify-aes@^1.0.0, browserify-aes@^1.0.4:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
   dependencies:
+    buffer-xor "^1.0.3"
+    cipher-base "^1.0.0"
+    create-hash "^1.1.0"
+    evp_bytestokey "^1.0.3"
     inherits "^2.0.1"
+    safe-buffer "^5.0.1"
 
-browserify-zlib@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
+browserify-cipher@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0"
   dependencies:
-    pako "~0.2.0"
+    browserify-aes "^1.0.4"
+    browserify-des "^1.0.0"
+    evp_bytestokey "^1.0.0"
+
+browserify-des@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c"
+  dependencies:
+    cipher-base "^1.0.1"
+    des.js "^1.0.0"
+    inherits "^2.0.1"
+    safe-buffer "^5.1.2"
+
+browserify-rsa@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
+  dependencies:
+    bn.js "^4.1.0"
+    randombytes "^2.0.1"
+
+browserify-sign@^4.0.0:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298"
+  dependencies:
+    bn.js "^4.1.1"
+    browserify-rsa "^4.0.0"
+    create-hash "^1.1.0"
+    create-hmac "^1.1.2"
+    elliptic "^6.0.0"
+    inherits "^2.0.1"
+    parse-asn1 "^5.0.0"
+
+browserify-zlib@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
+  dependencies:
+    pako "~1.0.5"
 
 browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
   version "1.7.7"
@@ -1115,7 +1330,11 @@ buffer-from@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
 
-buffer@^4.9.0:
+buffer-xor@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
+
+buffer@^4.3.0:
   version "4.9.1"
   resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
   dependencies:
@@ -1135,6 +1354,25 @@ bytes@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
 
+cacache@^11.3.2:
+  version "11.3.2"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa"
+  dependencies:
+    bluebird "^3.5.3"
+    chownr "^1.1.1"
+    figgy-pudding "^3.5.1"
+    glob "^7.1.3"
+    graceful-fs "^4.1.15"
+    lru-cache "^5.1.1"
+    mississippi "^3.0.0"
+    mkdirp "^0.5.1"
+    move-concurrently "^1.0.1"
+    promise-inflight "^1.0.1"
+    rimraf "^2.6.2"
+    ssri "^6.0.1"
+    unique-filename "^1.1.1"
+    y18n "^4.0.0"
+
 cache-base@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@@ -1149,19 +1387,13 @@ cache-base@^1.0.1:
     union-value "^1.0.0"
     unset-value "^1.0.0"
 
-caller-path@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
-  dependencies:
-    callsites "^0.2.0"
-
 callsite@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
 
-callsites@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
+callsites@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
 
 camel-case@3.0.x:
   version "3.0.0"
@@ -1177,17 +1409,19 @@ camelcase-keys@^2.0.0:
     camelcase "^2.0.0"
     map-obj "^1.0.0"
 
-camelcase@^1.0.2:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
-
 camelcase@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
 
-camelcase@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
+camelcase@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
+
+can-promise@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/can-promise/-/can-promise-0.0.1.tgz#7a7597ad801fb14c8b22341dfec314b6bd6ad8d3"
+  dependencies:
+    window-or-global "^1.0.1"
 
 caniuse-api@^1.5.2:
   version "1.6.1"
@@ -1210,13 +1444,6 @@ caseless@~0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
 
-center-align@^0.1.1:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
-  dependencies:
-    align-text "^0.1.3"
-    lazy-cache "^1.0.3"
-
 chai-nightwatch@~0.1.x:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/chai-nightwatch/-/chai-nightwatch-0.1.1.tgz#1ca56de768d3c0868fe7fc2f4d32c2fe894e6be9"
@@ -1242,7 +1469,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
-chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   dependencies:
@@ -1258,20 +1485,27 @@ chalk@~0.4.0:
     has-color "~0.1.0"
     strip-ansi "~0.1.0"
 
-chokidar@^1.0.0, chokidar@^1.4.1:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
+chardet@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
+
+chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3:
+  version "2.1.6"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5"
   dependencies:
-    anymatch "^1.3.0"
-    async-each "^1.0.0"
-    glob-parent "^2.0.0"
-    inherits "^2.0.1"
+    anymatch "^2.0.0"
+    async-each "^1.0.1"
+    braces "^2.3.2"
+    glob-parent "^3.1.0"
+    inherits "^2.0.3"
     is-binary-path "^1.0.0"
-    is-glob "^2.0.0"
+    is-glob "^4.0.0"
+    normalize-path "^3.0.0"
     path-is-absolute "^1.0.0"
-    readdirp "^2.0.0"
+    readdirp "^2.2.1"
+    upath "^1.1.1"
   optionalDependencies:
-    fsevents "^1.0.0"
+    fsevents "^1.2.7"
 
 chownr@^1.1.1:
   version "1.1.1"
@@ -1281,6 +1515,12 @@ chromatism@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/chromatism/-/chromatism-3.0.0.tgz#a7249d353c1e4f3577e444ac41171c4e2e624b12"
 
+chrome-trace-event@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48"
+  dependencies:
+    tslib "^1.9.0"
+
 chromedriver@^2.21.2:
   version "2.45.0"
   resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-2.45.0.tgz#8c1b158adbbd3e0ca3f7af19d459082245554378"
@@ -1291,9 +1531,16 @@ chromedriver@^2.21.2:
     request "^2.88.0"
     tcp-port-used "^1.0.1"
 
-circular-json@^0.3.1:
-  version "0.3.3"
-  resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
+cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
+  dependencies:
+    inherits "^2.0.1"
+    safe-buffer "^5.0.1"
+
+circular-json@^0.5.5:
+  version "0.5.9"
+  resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d"
 
 clap@^1.0.9:
   version "1.2.3"
@@ -1316,12 +1563,18 @@ clean-css@4.2.x:
   dependencies:
     source-map "~0.6.0"
 
-cli-cursor@^1.0.1, cli-cursor@^1.0.2:
+cli-cursor@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
   dependencies:
     restore-cursor "^1.0.1"
 
+cli-cursor@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
+  dependencies:
+    restore-cursor "^2.0.0"
+
 cli-spinners@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.2.0.tgz#85078737913b880f6ec9ffe7b65e83ec7776284f"
@@ -1330,30 +1583,26 @@ cli-width@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
 
-cliui@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
+cliui@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
   dependencies:
-    center-align "^0.1.1"
-    right-align "^0.1.1"
-    wordwrap "0.0.2"
-
-cliui@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
-  dependencies:
-    string-width "^1.0.1"
-    strip-ansi "^3.0.1"
+    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"
+  dependencies:
+    is-plain-object "^2.0.4"
+    kind-of "^6.0.2"
+    shallow-clone "^3.0.0"
+
 clone@^1.0.2:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
 
-co@^4.6.0:
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
-
 co@~3.0.6:
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/co/-/co-3.0.6.tgz#1445f226c5eb956138e68c9ac30167ea7d2e6bda"
@@ -1453,10 +1702,6 @@ component-bind@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
 
-component-emitter@1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3"
-
 component-emitter@1.2.1, component-emitter@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
@@ -1469,7 +1714,7 @@ concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
 
-concat-stream@1.6.2, concat-stream@^1.5.2:
+concat-stream@1.6.2, concat-stream@^1.5.0:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
   dependencies:
@@ -1478,13 +1723,6 @@ concat-stream@1.6.2, concat-stream@^1.5.2:
     readable-stream "^2.2.2"
     typedarray "^0.0.6"
 
-config-chain@^1.1.12:
-  version "1.1.12"
-  resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
-  dependencies:
-    ini "^1.3.4"
-    proto-list "~1.2.1"
-
 connect-history-api-fallback@^1.1.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
@@ -1518,6 +1756,10 @@ constants-browserify@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
 
+contains-path@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
+
 content-disposition@0.5.2:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
@@ -1540,6 +1782,17 @@ cookie@0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
 
+copy-concurrently@^1.0.0:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
+  dependencies:
+    aproba "^1.1.1"
+    fs-write-stream-atomic "^1.0.8"
+    iferr "^0.1.5"
+    mkdirp "^0.5.1"
+    rimraf "^2.5.4"
+    run-queue "^1.0.0"
+
 copy-descriptor@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
@@ -1564,17 +1817,38 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
     parse-json "^2.2.0"
     require-from-string "^1.1.0"
 
+create-ecdh@^4.0.0:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff"
+  dependencies:
+    bn.js "^4.1.0"
+    elliptic "^6.0.0"
+
+create-hash@^1.1.0, create-hash@^1.1.2:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
+  dependencies:
+    cipher-base "^1.0.1"
+    inherits "^2.0.1"
+    md5.js "^1.3.4"
+    ripemd160 "^2.0.1"
+    sha.js "^2.4.0"
+
+create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
+  dependencies:
+    cipher-base "^1.0.3"
+    create-hash "^1.1.0"
+    inherits "^2.0.1"
+    ripemd160 "^2.0.0"
+    safe-buffer "^5.0.1"
+    sha.js "^2.4.8"
+
 cropperjs@^1.4.3:
   version "1.4.3"
   resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.4.3.tgz#dc44d6c9e73269e7f96894c726ab91e8913f9e90"
 
-cross-spawn@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
-  dependencies:
-    lru-cache "^4.0.1"
-    which "^1.2.9"
-
 cross-spawn@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41"
@@ -1582,35 +1856,54 @@ cross-spawn@^4.0.2:
     lru-cache "^4.0.1"
     which "^1.2.9"
 
-crypto-browserify@3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.3.0.tgz#b9fc75bb4a0ed61dcf1cd5dae96eb30c9c3e506c"
+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"
   dependencies:
-    browserify-aes "0.4.0"
-    pbkdf2-compat "2.0.1"
-    ripemd160 "0.2.0"
-    sha.js "2.2.6"
+    nice-try "^1.0.4"
+    path-key "^2.0.1"
+    semver "^5.5.0"
+    shebang-command "^1.2.0"
+    which "^1.2.9"
+
+crypto-browserify@^3.11.0:
+  version "3.12.0"
+  resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
+  dependencies:
+    browserify-cipher "^1.0.0"
+    browserify-sign "^4.0.0"
+    create-ecdh "^4.0.0"
+    create-hash "^1.1.0"
+    create-hmac "^1.1.0"
+    diffie-hellman "^5.0.0"
+    inherits "^2.0.1"
+    pbkdf2 "^3.0.3"
+    public-encrypt "^4.0.0"
+    randombytes "^2.0.0"
+    randomfill "^1.0.3"
 
 css-color-names@0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
 
-css-loader@^0.25.0:
-  version "0.25.0"
-  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.25.0.tgz#c3febc8ce28f4c83576b6b13707f47f90c390223"
+css-loader@^0.28.0:
+  version "0.28.11"
+  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7"
   dependencies:
-    babel-code-frame "^6.11.0"
-    css-selector-tokenizer "^0.6.0"
-    cssnano ">=2.6.1 <4"
-    loader-utils "~0.2.2"
-    lodash.camelcase "^3.0.1"
-    object-assign "^4.0.1"
+    babel-code-frame "^6.26.0"
+    css-selector-tokenizer "^0.7.0"
+    cssnano "^3.10.0"
+    icss-utils "^2.1.0"
+    loader-utils "^1.0.2"
+    lodash.camelcase "^4.3.0"
+    object-assign "^4.1.1"
     postcss "^5.0.6"
-    postcss-modules-extract-imports "^1.0.0"
-    postcss-modules-local-by-default "^1.0.1"
-    postcss-modules-scope "^1.0.0"
-    postcss-modules-values "^1.1.0"
-    source-list-map "^0.1.4"
+    postcss-modules-extract-imports "^1.2.0"
+    postcss-modules-local-by-default "^1.2.0"
+    postcss-modules-scope "^1.1.0"
+    postcss-modules-values "^1.3.0"
+    postcss-value-parser "^3.3.0"
+    source-list-map "^2.0.0"
 
 css-select@^1.1.0:
   version "1.2.0"
@@ -1621,14 +1914,6 @@ css-select@^1.1.0:
     domutils "1.5.1"
     nth-check "~1.0.1"
 
-css-selector-tokenizer@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.6.0.tgz#6445f582c7930d241dcc5007a43d6fcb8f073152"
-  dependencies:
-    cssesc "^0.1.0"
-    fastparse "^1.1.1"
-    regexpu-core "^1.0.0"
-
 css-selector-tokenizer@^0.7.0:
   version "0.7.1"
   resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz#a177271a8bca5019172f4f891fc6eed9cbf68d5d"
@@ -1645,7 +1930,7 @@ cssesc@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
 
-"cssnano@>=2.6.1 <4":
+cssnano@^3.10.0:
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38"
   dependencies:
@@ -1699,11 +1984,9 @@ custom-event@~1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
 
-d@1:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
-  dependencies:
-    es5-ext "^0.10.9"
+cyclist@~0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
 
 dashdash@^1.12.0:
   version "1.14.1"
@@ -1715,6 +1998,10 @@ 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"
 
+date-format@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/date-format/-/date-format-1.2.0.tgz#615e828e233dd1ab9bb9ae0950e0ceccfa6ecad8"
+
 date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -1730,7 +2017,7 @@ de-indent@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
 
-debug@2, debug@2.6.9, debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
+debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   dependencies:
@@ -1742,12 +2029,6 @@ debug@2.2.0:
   dependencies:
     ms "0.7.1"
 
-debug@2.3.3:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c"
-  dependencies:
-    ms "0.7.2"
-
 debug@2.6.8:
   version "2.6.8"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
@@ -1760,13 +2041,25 @@ debug@4.1.0:
   dependencies:
     ms "^2.1.1"
 
-debug@=3.1.0:
+debug@=3.1.0, debug@~3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
   dependencies:
     ms "2.0.0"
 
-decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
+debug@^3.1.0:
+  version "3.2.6"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+  dependencies:
+    ms "^2.1.1"
+
+debug@^4.0.1, debug@^4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
+  dependencies:
+    ms "^2.1.1"
+
+decamelize@^1.1.2, decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
 
@@ -1788,6 +2081,12 @@ deep-is@^0.1.3, deep-is@~0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
 
+define-properties@^1.1.2:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
+  dependencies:
+    object-keys "^1.0.12"
+
 define-property@^0.2.5:
   version "0.2.5"
   resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
@@ -1842,6 +2141,13 @@ depd@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
 
+des.js@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
+  dependencies:
+    inherits "^2.0.1"
+    minimalistic-assert "^1.0.0"
+
 destroy@~1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
@@ -1868,13 +2174,32 @@ diff@3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
 
-diff@^3.0.1:
+diff@^3.0.1, diff@^3.1.0:
   version "3.5.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
 
-doctrine@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
+diffie-hellman@^5.0.0:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
+  dependencies:
+    bn.js "^4.1.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"
+
+doctrine@1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
+  dependencies:
+    esutils "^2.0.2"
+    isarray "^1.0.0"
+
+doctrine@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
   dependencies:
     esutils "^2.0.2"
 
@@ -1948,6 +2273,15 @@ domutils@^1.5.1:
     dom-serializer "0"
     domelementtype "1"
 
+duplexify@^3.4.2, duplexify@^3.6.0:
+  version "3.7.1"
+  resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
+  dependencies:
+    end-of-stream "^1.0.0"
+    inherits "^2.0.1"
+    readable-stream "^2.0.0"
+    stream-shift "^1.0.0"
+
 ecc-jsbn@~0.1.1:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@@ -1955,17 +2289,6 @@ ecc-jsbn@~0.1.1:
     jsbn "~0.1.0"
     safer-buffer "^2.1.0"
 
-editorconfig@^0.15.2:
-  version "0.15.2"
-  resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.2.tgz#047be983abb9ab3c2eefe5199cb2b7c5689f0702"
-  dependencies:
-    "@types/node" "^10.11.7"
-    "@types/semver" "^5.5.0"
-    commander "^2.19.0"
-    lru-cache "^4.1.3"
-    semver "^5.6.0"
-    sigmund "^1.0.1"
-
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -1978,6 +2301,22 @@ electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.47:
   version "1.3.100"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.100.tgz#899fb088def210aee6b838a47655bbb299190e13"
 
+elliptic@^6.0.0:
+  version "6.4.1"
+  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"
+  dependencies:
+    bn.js "^4.4.0"
+    brorand "^1.0.1"
+    hash.js "^1.0.0"
+    hmac-drbg "^1.0.0"
+    inherits "^2.0.1"
+    minimalistic-assert "^1.0.0"
+    minimalistic-crypto-utils "^1.0.0"
+
+emoji-regex@^7.0.1:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
+
 emojis-list@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@@ -1986,52 +2325,56 @@ encodeurl@~1.0.1, encodeurl@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
 
-engine.io-client@1.8.3:
-  version "1.8.3"
-  resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.3.tgz#1798ed93451246453d4c6f635d7a201fe940d5ab"
+end-of-stream@^1.0.0, end-of-stream@^1.1.0:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
+  dependencies:
+    once "^1.4.0"
+
+engine.io-client@~3.2.0:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36"
   dependencies:
     component-emitter "1.2.1"
     component-inherit "0.0.3"
-    debug "2.3.3"
-    engine.io-parser "1.3.2"
+    debug "~3.1.0"
+    engine.io-parser "~2.1.1"
     has-cors "1.1.0"
     indexof "0.0.1"
-    parsejson "0.0.3"
     parseqs "0.0.5"
     parseuri "0.0.5"
-    ws "1.1.2"
-    xmlhttprequest-ssl "1.5.3"
+    ws "~3.3.1"
+    xmlhttprequest-ssl "~1.5.4"
     yeast "0.1.2"
 
-engine.io-parser@1.3.2:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.2.tgz#937b079f0007d0893ec56d46cb220b8cb435220a"
+engine.io-parser@~2.1.0, engine.io-parser@~2.1.1:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.3.tgz#757ab970fbf2dfb32c7b74b033216d5739ef79a6"
   dependencies:
     after "0.8.2"
-    arraybuffer.slice "0.0.6"
+    arraybuffer.slice "~0.0.7"
     base64-arraybuffer "0.1.5"
-    blob "0.0.4"
-    has-binary "0.1.7"
-    wtf-8 "1.0.0"
+    blob "0.0.5"
+    has-binary2 "~1.0.2"
 
-engine.io@1.8.3:
-  version "1.8.3"
-  resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.3.tgz#8de7f97895d20d39b85f88eeee777b2bd42b13d4"
+engine.io@~3.2.0:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.2.1.tgz#b60281c35484a70ee0351ea0ebff83ec8c9522a2"
   dependencies:
-    accepts "1.3.3"
+    accepts "~1.3.4"
     base64id "1.0.0"
     cookie "0.3.1"
-    debug "2.3.3"
-    engine.io-parser "1.3.2"
-    ws "1.1.2"
+    debug "~3.1.0"
+    engine.io-parser "~2.1.0"
+    ws "~3.3.1"
 
-enhanced-resolve@~0.9.0:
-  version "0.9.1"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e"
+enhanced-resolve@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
   dependencies:
     graceful-fs "^4.1.2"
-    memory-fs "^0.2.0"
-    tapable "^0.1.8"
+    memory-fs "^0.4.0"
+    tapable "^1.0.0"
 
 ent@~2.2.0:
   version "2.2.0"
@@ -2041,7 +2384,7 @@ entities@^1.1.1, entities@~1.1.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
 
-errno@^0.1.3:
+errno@^0.1.3, errno@~0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
   dependencies:
@@ -2053,62 +2396,24 @@ error-ex@^1.2.0:
   dependencies:
     is-arrayish "^0.2.1"
 
-es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
-  version "0.10.46"
-  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.46.tgz#efd99f67c5a7ec789baa3daa7f79870388f7f572"
+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"
   dependencies:
-    es6-iterator "~2.0.3"
-    es6-symbol "~3.1.1"
-    next-tick "1"
+    es-to-primitive "^1.2.0"
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    is-callable "^1.1.4"
+    is-regex "^1.0.4"
+    object-keys "^1.0.12"
 
-es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
+es-to-primitive@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
   dependencies:
-    d "1"
-    es5-ext "^0.10.35"
-    es6-symbol "^3.1.1"
-
-es6-map@^0.1.3:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
-  dependencies:
-    d "1"
-    es5-ext "~0.10.14"
-    es6-iterator "~2.0.1"
-    es6-set "~0.1.5"
-    es6-symbol "~3.1.1"
-    event-emitter "~0.3.5"
-
-es6-promise@^4.0.3:
-  version "4.2.5"
-  resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
-
-es6-set@~0.1.5:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
-  dependencies:
-    d "1"
-    es5-ext "~0.10.14"
-    es6-iterator "~2.0.1"
-    es6-symbol "3.1.1"
-    event-emitter "~0.3.5"
-
-es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
-  dependencies:
-    d "1"
-    es5-ext "~0.10.14"
-
-es6-weak-map@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f"
-  dependencies:
-    d "1"
-    es5-ext "^0.10.14"
-    es6-iterator "^2.0.1"
-    es6-symbol "^3.1.1"
+    is-callable "^1.1.4"
+    is-date-object "^1.0.1"
+    is-symbol "^1.0.2"
 
 escape-html@~1.0.3:
   version "1.0.3"
@@ -2140,18 +2445,9 @@ escodegen@1.x.x, escodegen@^1.6.1:
   optionalDependencies:
     source-map "~0.6.1"
 
-escope@^3.6.0:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3"
-  dependencies:
-    es6-map "^0.1.3"
-    es6-weak-map "^2.0.1"
-    esrecurse "^4.1.0"
-    estraverse "^4.1.1"
-
-eslint-config-standard@^6.1.0:
-  version "6.2.1"
-  resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-6.2.1.tgz#d3a68aafc7191639e7ee441e7348739026354292"
+eslint-config-standard@^12.0.0:
+  version "12.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz#638b4c65db0bd5a41319f96bba1f15ddad2107d9"
 
 eslint-friendly-formatter@^2.0.5:
   version "2.0.7"
@@ -2162,9 +2458,16 @@ eslint-friendly-formatter@^2.0.5:
     minimist "^1.2.0"
     text-table "^0.2.0"
 
-eslint-loader@^1.5.0:
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-1.9.0.tgz#7e1be9feddca328d3dcfaef1ad49d5beffe83a13"
+eslint-import-resolver-node@^0.3.2:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a"
+  dependencies:
+    debug "^2.6.9"
+    resolve "^1.5.0"
+
+eslint-loader@^2.1.0:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.1.2.tgz#453542a1230d6ffac90e4e7cb9cadba9d851be68"
   dependencies:
     loader-fs-cache "^1.0.0"
     loader-utils "^1.0.2"
@@ -2172,66 +2475,132 @@ eslint-loader@^1.5.0:
     object-hash "^1.1.4"
     rimraf "^2.6.1"
 
-eslint-plugin-html@^1.5.5:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-1.7.0.tgz#2a5b03884d8d56adf9ad9864e9c036480fb629c9"
+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"
   dependencies:
-    htmlparser2 "^3.8.2"
+    debug "^2.6.8"
+    pkg-dir "^2.0.0"
 
-eslint-plugin-promise@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-2.0.1.tgz#a9759cefa5e38ab11bb2ef65a04ef042309aa0a4"
-
-eslint-plugin-standard@^2.0.1:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-2.3.1.tgz#6765bd2a6d9ecdc7bdf1b145ae4bb30e2b7b86f8"
-
-eslint@^3.7.1:
-  version "3.19.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc"
+eslint-plugin-es@^1.3.1:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.0.tgz#475f65bb20c993fc10e8c8fe77d1d60068072da6"
   dependencies:
-    babel-code-frame "^6.16.0"
-    chalk "^1.1.3"
-    concat-stream "^1.5.2"
-    debug "^2.1.1"
-    doctrine "^2.0.0"
-    escope "^3.6.0"
-    espree "^3.4.0"
-    esquery "^1.0.0"
-    estraverse "^4.2.0"
+    eslint-utils "^1.3.0"
+    regexpp "^2.0.1"
+
+eslint-plugin-import@^2.13.0:
+  version "2.17.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.2.tgz#d227d5c6dc67eca71eb590d2bb62fb38d86e9fcb"
+  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.4.0"
+    has "^1.0.3"
+    lodash "^4.17.11"
+    minimatch "^3.0.4"
+    read-pkg-up "^2.0.0"
+    resolve "^1.10.0"
+
+eslint-plugin-node@^7.0.0:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz#a6e054e50199b2edd85518b89b4e7b323c9f36db"
+  dependencies:
+    eslint-plugin-es "^1.3.1"
+    eslint-utils "^1.3.1"
+    ignore "^4.0.2"
+    minimatch "^3.0.4"
+    resolve "^1.8.1"
+    semver "^5.5.0"
+
+eslint-plugin-promise@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db"
+
+eslint-plugin-standard@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz#f845b45109c99cd90e77796940a344546c8f6b5c"
+
+eslint-plugin-vue@^5.2.2:
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.2.2.tgz#86601823b7721b70bc92d54f1728cfc03b36283c"
+  dependencies:
+    vue-eslint-parser "^5.0.0"
+
+eslint-scope@^4.0.0, eslint-scope@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
+  dependencies:
+    esrecurse "^4.1.0"
+    estraverse "^4.1.1"
+
+eslint-utils@^1.3.0, eslint-utils@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512"
+
+eslint-visitor-keys@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
+
+eslint@^5.16.0:
+  version "5.16.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea"
+  dependencies:
+    "@babel/code-frame" "^7.0.0"
+    ajv "^6.9.1"
+    chalk "^2.1.0"
+    cross-spawn "^6.0.5"
+    debug "^4.0.1"
+    doctrine "^3.0.0"
+    eslint-scope "^4.0.3"
+    eslint-utils "^1.3.1"
+    eslint-visitor-keys "^1.0.0"
+    espree "^5.0.1"
+    esquery "^1.0.1"
     esutils "^2.0.2"
-    file-entry-cache "^2.0.0"
-    glob "^7.0.3"
-    globals "^9.14.0"
-    ignore "^3.2.0"
+    file-entry-cache "^5.0.1"
+    functional-red-black-tree "^1.0.1"
+    glob "^7.1.2"
+    globals "^11.7.0"
+    ignore "^4.0.6"
+    import-fresh "^3.0.0"
     imurmurhash "^0.1.4"
-    inquirer "^0.12.0"
-    is-my-json-valid "^2.10.0"
-    is-resolvable "^1.0.0"
-    js-yaml "^3.5.1"
-    json-stable-stringify "^1.0.0"
+    inquirer "^6.2.2"
+    js-yaml "^3.13.0"
+    json-stable-stringify-without-jsonify "^1.0.1"
     levn "^0.3.0"
-    lodash "^4.0.0"
-    mkdirp "^0.5.0"
+    lodash "^4.17.11"
+    minimatch "^3.0.4"
+    mkdirp "^0.5.1"
     natural-compare "^1.4.0"
     optionator "^0.8.2"
-    path-is-inside "^1.0.1"
-    pluralize "^1.2.1"
-    progress "^1.1.8"
-    require-uncached "^1.0.2"
-    shelljs "^0.7.5"
-    strip-bom "^3.0.0"
-    strip-json-comments "~2.0.1"
-    table "^3.7.8"
-    text-table "~0.2.0"
-    user-home "^2.0.0"
+    path-is-inside "^1.0.2"
+    progress "^2.0.0"
+    regexpp "^2.0.1"
+    semver "^5.5.1"
+    strip-ansi "^4.0.0"
+    strip-json-comments "^2.0.1"
+    table "^5.2.3"
+    text-table "^0.2.0"
 
-espree@^3.4.0:
-  version "3.5.4"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
+espree@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-4.1.0.tgz#728d5451e0fd156c04384a7ad89ed51ff54eb25f"
   dependencies:
-    acorn "^5.5.0"
-    acorn-jsx "^3.0.0"
+    acorn "^6.0.2"
+    acorn-jsx "^5.0.0"
+    eslint-visitor-keys "^1.0.0"
+
+espree@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a"
+  dependencies:
+    acorn "^6.0.7"
+    acorn-jsx "^5.0.0"
+    eslint-visitor-keys "^1.0.0"
 
 esprima@2.7.x, esprima@^2.6.0, esprima@^2.7.1:
   version "2.7.3"
@@ -2245,7 +2614,7 @@ esprima@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
 
-esquery@^1.0.0:
+esquery@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
   dependencies:
@@ -2273,25 +2642,37 @@ etag@~1.8.1:
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
 
-event-emitter@~0.3.5:
-  version "0.3.5"
-  resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
-  dependencies:
-    d "1"
-    es5-ext "~0.10.14"
-
 eventemitter3@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
 
-events@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
+events@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
 
 eventsource-polyfill@^0.9.6:
   version "0.9.6"
   resolved "https://registry.yarnpkg.com/eventsource-polyfill/-/eventsource-polyfill-0.9.6.tgz#10e0d187f111b167f28fdab918843ce7d818f13c"
 
+evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
+  dependencies:
+    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"
+  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"
@@ -2387,6 +2768,14 @@ extend@3, extend@^3.0.0, extend@~3.0.0, extend@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
 
+external-editor@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27"
+  dependencies:
+    chardet "^0.7.0"
+    iconv-lite "^0.4.24"
+    tmp "^0.0.33"
+
 extglob@^0.3.1:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
@@ -2406,15 +2795,7 @@ extglob@^2.0.4:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
-extract-text-webpack-plugin@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-1.0.1.tgz#c95bf3cbaac49dc96f1dc6e072549fbb654ccd2c"
-  dependencies:
-    async "^1.5.0"
-    loader-utils "^0.2.3"
-    webpack-sources "^0.1.0"
-
-extract-zip@^1.6.5, extract-zip@^1.6.7:
+extract-zip@^1.6.7:
   version "1.6.7"
   resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9"
   dependencies:
@@ -2449,25 +2830,28 @@ fd-slicer@~1.0.1:
   dependencies:
     pend "~1.2.0"
 
-figures@^1.3.5:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
+figgy-pudding@^3.5.1:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
+
+figures@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
   dependencies:
     escape-string-regexp "^1.0.5"
-    object-assign "^4.1.0"
 
-file-entry-cache@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
+file-entry-cache@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
   dependencies:
-    flat-cache "^1.2.1"
-    object-assign "^4.0.1"
+    flat-cache "^2.0.1"
 
-file-loader@^0.9.0:
-  version "0.9.0"
-  resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.9.0.tgz#1d2daddd424ce6d1b07cfe3f79731bed3617ab42"
+file-loader@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa"
   dependencies:
-    loader-utils "~0.2.5"
+    loader-utils "^1.0.2"
+    schema-utils "^1.0.0"
 
 file-uri-to-path@1:
   version "1.0.0"
@@ -2528,6 +2912,22 @@ find-cache-dir@^0.1.1:
     mkdirp "^0.5.1"
     pkg-dir "^1.0.0"
 
+find-cache-dir@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
+  dependencies:
+    commondir "^1.0.1"
+    make-dir "^1.0.0"
+    pkg-dir "^2.0.0"
+
+find-cache-dir@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
+  dependencies:
+    commondir "^1.0.1"
+    make-dir "^2.0.0"
+    pkg-dir "^3.0.0"
+
 find-up@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
@@ -2535,19 +2935,41 @@ find-up@^1.0.0:
     path-exists "^2.0.0"
     pinkie-promise "^2.0.0"
 
-flat-cache@^1.2.1:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f"
+find-up@^2.0.0, find-up@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
   dependencies:
-    circular-json "^0.3.1"
-    graceful-fs "^4.1.2"
-    rimraf "~2.6.2"
-    write "^0.2.1"
+    locate-path "^2.0.0"
+
+find-up@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
+  dependencies:
+    locate-path "^3.0.0"
+
+flat-cache@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
+  dependencies:
+    flatted "^2.0.0"
+    rimraf "2.6.3"
+    write "1.0.3"
+
+flatted@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916"
 
 flatten@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
 
+flush-write-stream@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
+  dependencies:
+    inherits "^2.0.3"
+    readable-stream "^2.3.6"
+
 follow-redirects@^1.0.0:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb"
@@ -2576,11 +2998,11 @@ form-data@~2.3.2:
     combined-stream "^1.0.6"
     mime-types "^2.1.12"
 
-formatio@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9"
+formatio@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb"
   dependencies:
-    samsam "~1.1"
+    samsam "1.x"
 
 forwarded@~0.1.2:
   version "0.1.2"
@@ -2596,13 +3018,12 @@ fresh@0.5.2:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
 
-fs-extra@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950"
+from2@^2.1.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
   dependencies:
-    graceful-fs "^4.1.2"
-    jsonfile "^2.1.0"
-    klaw "^1.0.0"
+    inherits "^2.0.1"
+    readable-stream "^2.0.0"
 
 fs-minipass@^1.2.5:
   version "1.2.5"
@@ -2610,25 +3031,25 @@ fs-minipass@^1.2.5:
   dependencies:
     minipass "^2.2.1"
 
+fs-write-stream-atomic@^1.0.8:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
+  dependencies:
+    graceful-fs "^4.1.2"
+    iferr "^0.1.5"
+    imurmurhash "^0.1.4"
+    readable-stream "1 || 2"
+
 fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
 
-fsevents@^1.0.0:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426"
+fsevents@^1.2.7:
+  version "1.2.9"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
   dependencies:
-    nan "^2.9.2"
-    node-pre-gyp "^0.10.0"
-
-fstream@^1.0.0, fstream@^1.0.2:
-  version "1.0.11"
-  resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
-  dependencies:
-    graceful-fs "^4.1.2"
-    inherits "~2.0.0"
-    mkdirp ">=0.5 0"
-    rimraf "2"
+    nan "^2.12.1"
+    node-pre-gyp "^0.12.0"
 
 ftp@~0.3.10:
   version "0.3.10"
@@ -2641,6 +3062,10 @@ function-bind@^1.0.2, function-bind@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
 
+functional-red-black-tree@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
+
 gauge@~2.7.3:
   version "2.7.4"
   resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
@@ -2654,24 +3079,6 @@ gauge@~2.7.3:
     strip-ansi "^3.0.1"
     wide-align "^1.1.0"
 
-gaze@^1.0.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a"
-  dependencies:
-    globule "^1.0.0"
-
-generate-function@^2.0.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f"
-  dependencies:
-    is-property "^1.0.2"
-
-generate-object-property@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
-  dependencies:
-    is-property "^1.0.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"
@@ -2680,6 +3087,12 @@ get-stdin@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
 
+get-stream@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+  dependencies:
+    pump "^3.0.0"
+
 get-uri@2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.2.tgz#5c795e71326f6ca1286f2fc82575cd2bab2af578"
@@ -2714,6 +3127,13 @@ glob-parent@^2.0.0:
   dependencies:
     is-glob "^2.0.0"
 
+glob-parent@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
+  dependencies:
+    is-glob "^3.1.0"
+    path-dirname "^1.0.0"
+
 glob@7.0.5:
   version "7.0.5"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95"
@@ -2746,7 +3166,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.3, glob@~7.1.1:
+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"
   dependencies:
@@ -2757,7 +3177,22 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@~7.1.1:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-globals@^9.14.0, globals@^9.18.0:
+glob@^7.1.2:
+  version "7.1.4"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
+  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.12.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+
+globals@^9.18.0:
   version "9.18.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
 
@@ -2771,15 +3206,7 @@ globby@^6.1.0:
     pify "^2.0.0"
     pinkie-promise "^2.0.0"
 
-globule@^1.0.0:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d"
-  dependencies:
-    glob "~7.1.1"
-    lodash "~4.17.10"
-    minimatch "~3.0.2"
-
-graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
+graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
   version "4.1.15"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
 
@@ -2818,11 +3245,11 @@ has-ansi@^2.0.0:
   dependencies:
     ansi-regex "^2.0.0"
 
-has-binary@0.1.7:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.7.tgz#68e61eb16210c9545a0a5cce06a873912fe1e68c"
+has-binary2@~1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
   dependencies:
-    isarray "0.0.1"
+    isarray "2.0.1"
 
 has-color@~0.1.0:
   version "0.1.7"
@@ -2840,6 +3267,10 @@ has-flag@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
 
+has-symbols@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
+
 has-unicode@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -2871,22 +3302,29 @@ has-values@^1.0.0:
     is-number "^3.0.0"
     kind-of "^4.0.0"
 
-has@^1.0.1:
+has@^1.0.1, has@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
   dependencies:
     function-bind "^1.1.1"
 
+hash-base@^3.0.0:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
+  dependencies:
+    inherits "^2.0.1"
+    safe-buffer "^5.0.1"
+
 hash-sum@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
 
-hasha@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/hasha/-/hasha-2.2.0.tgz#78d7cbfc1e6d66303fe79837365984517b2f6ee1"
+hash.js@^1.0.0, hash.js@^1.0.3:
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
   dependencies:
-    is-stream "^1.0.1"
-    pinkie-promise "^2.0.0"
+    inherits "^2.0.3"
+    minimalistic-assert "^1.0.1"
 
 he@1.1.1:
   version "1.1.1"
@@ -2896,6 +3334,14 @@ he@1.2.x, he@^1.1.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
 
+hmac-drbg@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
+  dependencies:
+    hash.js "^1.0.3"
+    minimalistic-assert "^1.0.0"
+    minimalistic-crypto-utils "^1.0.1"
+
 home-or-tmp@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -2927,18 +3373,19 @@ html-minifier@^3.2.3:
     relateurl "0.2.x"
     uglify-js "3.4.x"
 
-html-webpack-plugin@^2.8.1:
-  version "2.30.1"
-  resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz#7f9c421b7ea91ec460f56527d78df484ee7537d5"
+html-webpack-plugin@^3.0.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b"
   dependencies:
-    bluebird "^3.4.7"
     html-minifier "^3.2.3"
     loader-utils "^0.2.16"
     lodash "^4.17.3"
     pretty-error "^2.0.2"
+    tapable "^1.0.0"
     toposort "^1.0.0"
+    util.promisify "1.0.0"
 
-htmlparser2@^3.10.0, htmlparser2@^3.8.2:
+htmlparser2@^3.10.0:
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"
   dependencies:
@@ -3000,9 +3447,9 @@ http-signature@~1.2.0:
     jsprim "^1.2.2"
     sshpk "^1.7.0"
 
-https-browserify@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
+https-browserify@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
 
 https-proxy-agent@1:
   version "1.0.0"
@@ -3018,36 +3465,55 @@ iconv-lite@0.4.23, iconv-lite@^0.4.4:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
+iconv-lite@^0.4.24:
+  version "0.4.24"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+  dependencies:
+    safer-buffer ">= 2.1.2 < 3"
+
 icss-replace-symbols@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
 
+icss-utils@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962"
+  dependencies:
+    postcss "^6.0.1"
+
 ieee754@^1.1.4:
   version "1.1.12"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
 
+iferr@^0.1.5:
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
+
 ignore-walk@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8"
   dependencies:
     minimatch "^3.0.4"
 
-ignore@^3.2.0:
-  version "3.3.10"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
+ignore@^4.0.2, ignore@^4.0.6:
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
 
 immediate@~3.0.5:
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
 
+import-fresh@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390"
+  dependencies:
+    parent-module "^1.0.0"
+    resolve-from "^4.0.0"
+
 imurmurhash@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
 
-in-publish@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51"
-
 indent-string@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
@@ -3069,7 +3535,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
 
@@ -3077,7 +3543,7 @@ inherits@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
 
-ini@^1.3.4, ini@~1.3.0:
+ini@~1.3.0:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
 
@@ -3087,28 +3553,24 @@ inject-loader@^2.0.1:
   dependencies:
     loader-utils "^0.2.3"
 
-inquirer@^0.12.0:
-  version "0.12.0"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
+inquirer@^6.2.2:
+  version "6.3.1"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7"
   dependencies:
-    ansi-escapes "^1.1.0"
-    ansi-regex "^2.0.0"
-    chalk "^1.0.0"
-    cli-cursor "^1.0.1"
+    ansi-escapes "^3.2.0"
+    chalk "^2.4.2"
+    cli-cursor "^2.1.0"
     cli-width "^2.0.0"
-    figures "^1.3.5"
-    lodash "^4.3.0"
-    readline2 "^1.0.1"
-    run-async "^0.1.0"
-    rx-lite "^3.1.2"
-    string-width "^1.0.1"
-    strip-ansi "^3.0.0"
+    external-editor "^3.0.3"
+    figures "^2.0.0"
+    lodash "^4.17.11"
+    mute-stream "0.0.7"
+    run-async "^2.2.0"
+    rxjs "^6.4.0"
+    string-width "^2.1.0"
+    strip-ansi "^5.1.0"
     through "^2.3.6"
 
-interpret@^0.6.4:
-  version "0.6.6"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-0.6.6.tgz#fecd7a18e7ce5ca6abfb953e1f86213a49f1625b"
-
 interpret@^1.0.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
@@ -3119,9 +3581,9 @@ invariant@^2.2.2:
   dependencies:
     loose-envify "^1.0.0"
 
-invert-kv@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
+invert-kv@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
 
 ip-regex@^2.1.0:
   version "2.1.0"
@@ -3175,6 +3637,10 @@ is-builtin-module@^1.0.0:
   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"
+
 is-data-descriptor@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -3187,6 +3653,10 @@ is-data-descriptor@^1.0.0:
   dependencies:
     kind-of "^6.0.0"
 
+is-date-object@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
+
 is-descriptor@^0.1.0:
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -3231,7 +3701,7 @@ is-extglob@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
 
-is-extglob@^2.1.0:
+is-extglob@^2.1.0, is-extglob@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
 
@@ -3263,19 +3733,11 @@ is-glob@^3.1.0:
   dependencies:
     is-extglob "^2.1.0"
 
-is-my-ip-valid@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
-
-is-my-json-valid@^2.10.0:
-  version "2.19.0"
-  resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz#8fd6e40363cd06b963fa877d444bfb5eddc62175"
+is-glob@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
   dependencies:
-    generate-function "^2.0.0"
-    generate-object-property "^1.1.0"
-    is-my-ip-valid "^1.0.0"
-    jsonpointer "^4.0.0"
-    xtend "^4.0.0"
+    is-extglob "^2.1.1"
 
 is-number@^0.1.1:
   version "0.1.1"
@@ -3331,15 +3793,17 @@ is-primitive@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
 
-is-property@^1.0.0, is-property@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
+is-promise@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
 
-is-resolvable@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
+is-regex@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
+  dependencies:
+    has "^1.0.1"
 
-is-stream@^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"
 
@@ -3349,6 +3813,12 @@ is-svg@^2.0.0:
   dependencies:
     html-comment-regex "^1.1.0"
 
+is-symbol@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
+  dependencies:
+    has-symbols "^1.0.0"
+
 is-typedarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -3365,6 +3835,10 @@ is-windows@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
 
+is-wsl@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
+
 is2@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/is2/-/is2-2.0.1.tgz#8ac355644840921ce435d94f05d3a94634d3481a"
@@ -3381,6 +3855,14 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
 
+isarray@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
+
+isarray@^2.0.1:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.4.tgz#38e7bcbb0f3ba1b7933c86ba1894ddfc3781bbb7"
+
 isbinaryfile@^3.0.0:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80"
@@ -3448,31 +3930,32 @@ istanbul@0.4.5, istanbul@^0.4.0:
     which "^1.1.1"
     wordwrap "^1.0.0"
 
-js-base64@^2.1.8, js-base64@^2.1.9:
+js-base64@^2.1.9:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.0.tgz#42255ba183ab67ce59a0dee640afdc00ab5ae93e"
 
-js-beautify@^1.6.3:
-  version "1.8.9"
-  resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.9.tgz#08e3c05ead3ecfbd4f512c3895b1cda76c87d523"
-  dependencies:
-    config-chain "^1.1.12"
-    editorconfig "^0.15.2"
-    glob "^7.1.3"
-    mkdirp "~0.5.0"
-    nopt "~4.0.1"
-
 "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"
 
-js-yaml@3.x, js-yaml@^3.4.3, js-yaml@^3.5.1:
+js-tokens@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+
+js-yaml@3.x, js-yaml@^3.4.3:
   version "3.12.1"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600"
   dependencies:
     argparse "^1.0.7"
     esprima "^4.0.0"
 
+js-yaml@^3.13.0:
+  version "3.13.1"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^4.0.0"
+
 js-yaml@~3.7.0:
   version "3.7.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
@@ -3496,6 +3979,10 @@ json-loader@^0.5.4:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
 
+json-parse-better-errors@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
+
 json-schema-traverse@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -3504,11 +3991,9 @@ json-schema@0.2.3:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
 
-json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
+json-stable-stringify-without-jsonify@^1.0.1:
   version "1.0.1"
-  resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
-  dependencies:
-    jsonify "~0.0.0"
+  resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
 
 json-stringify-safe@~5.0.1:
   version "5.0.1"
@@ -3528,20 +4013,6 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
-jsonfile@^2.1.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
-  optionalDependencies:
-    graceful-fs "^4.1.6"
-
-jsonify@~0.0.0:
-  version "0.0.0"
-  resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
-
-jsonpointer@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
-
 jsprim@^1.2.2:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -3561,6 +4032,10 @@ karma-coverage@^1.1.1:
     minimatch "^3.0.0"
     source-map "^0.5.1"
 
+karma-firefox-launcher@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz#2c47030452f04531eb7d13d4fc7669630bb93339"
+
 karma-mocha-reporter@^2.2.1:
   version "2.2.5"
   resolved "https://registry.yarnpkg.com/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz#15120095e8ed819186e47a0b012f3cd741895560"
@@ -3575,18 +4050,9 @@ karma-mocha@^1.2.0:
   dependencies:
     minimist "1.2.0"
 
-karma-phantomjs-launcher@^1.0.0:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.4.tgz#d23ca34801bda9863ad318e3bb4bd4062b13acd2"
-  dependencies:
-    lodash "^4.0.1"
-    phantomjs-prebuilt "^2.1.7"
-
-karma-sinon-chai@^1.2.0:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/karma-sinon-chai/-/karma-sinon-chai-1.3.4.tgz#56c82674a5618ee9a4063cfbd57fc01da37f1495"
-  dependencies:
-    lolex "^1.6.0"
+karma-sinon-chai@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/karma-sinon-chai/-/karma-sinon-chai-2.0.2.tgz#e28c109b989973abafc28a7c9f09ef24a05e07c2"
 
 karma-sourcemap-loader@^0.3.7:
   version "0.3.7"
@@ -3600,23 +4066,22 @@ karma-spec-reporter@0.0.26:
   dependencies:
     colors "~0.6.0"
 
-karma-webpack@^1.7.0:
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-1.8.1.tgz#39d5fd2edeea3cc3ef5b405989b37d5b0e6a3b4e"
+karma-webpack@^4.0.0-rc.3:
+  version "4.0.0-rc.6"
+  resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-4.0.0-rc.6.tgz#02ac6a47c7fc166c8b208446069a424698082405"
   dependencies:
-    async "~0.9.0"
-    loader-utils "^0.2.5"
-    lodash "^3.8.0"
-    source-map "^0.1.41"
-    webpack-dev-middleware "^1.0.11"
+    async "^2.0.0"
+    loader-utils "^1.1.0"
+    source-map "^0.5.6"
+    webpack-dev-middleware "^3.2.0"
 
-karma@^1.3.0:
-  version "1.7.1"
-  resolved "https://registry.yarnpkg.com/karma/-/karma-1.7.1.tgz#85cc08e9e0a22d7ce9cca37c4a1be824f6a2b1ae"
+karma@^3.0.0:
+  version "3.1.4"
+  resolved "https://registry.yarnpkg.com/karma/-/karma-3.1.4.tgz#3890ca9722b10d1d14b726e1335931455788499e"
   dependencies:
     bluebird "^3.3.0"
     body-parser "^1.16.1"
-    chokidar "^1.4.1"
+    chokidar "^2.0.3"
     colors "^1.1.0"
     combine-lists "^1.0.0"
     connect "^3.6.0"
@@ -3624,27 +4089,24 @@ karma@^1.3.0:
     di "^0.0.1"
     dom-serialize "^2.2.0"
     expand-braces "^0.1.1"
+    flatted "^2.0.0"
     glob "^7.1.1"
     graceful-fs "^4.1.2"
     http-proxy "^1.13.0"
     isbinaryfile "^3.0.0"
-    lodash "^3.8.0"
-    log4js "^0.6.31"
-    mime "^1.3.4"
+    lodash "^4.17.5"
+    log4js "^3.0.0"
+    mime "^2.3.1"
     minimatch "^3.0.2"
     optimist "^0.6.1"
     qjobs "^1.1.4"
     range-parser "^1.2.0"
     rimraf "^2.6.0"
     safe-buffer "^5.0.1"
-    socket.io "1.7.3"
-    source-map "^0.5.3"
-    tmp "0.0.31"
-    useragent "^2.1.12"
-
-kew@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/kew/-/kew-0.7.0.tgz#79d93d2d33363d6fdd2970b335d9141ad591d79b"
+    socket.io "2.1.1"
+    source-map "^0.6.1"
+    tmp "0.0.33"
+    useragent "2.3.0"
 
 kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
   version "3.2.2"
@@ -3666,21 +4128,11 @@ kind-of@^6.0.0, kind-of@^6.0.2:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
 
-klaw@^1.0.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
-  optionalDependencies:
-    graceful-fs "^4.1.9"
-
-lazy-cache@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
-
-lcid@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
+lcid@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
   dependencies:
-    invert-kv "^1.0.0"
+    invert-kv "^2.0.0"
 
 levn@^0.3.0, levn@~0.3.0:
   version "0.3.0"
@@ -3705,6 +4157,15 @@ load-json-file@^1.0.0:
     pinkie-promise "^2.0.0"
     strip-bom "^2.0.0"
 
+load-json-file@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
+  dependencies:
+    graceful-fs "^4.1.2"
+    parse-json "^2.2.0"
+    pify "^2.0.0"
+    strip-bom "^3.0.0"
+
 loader-fs-cache@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz#56e0bf08bd9708b26a765b68509840c8dec9fdbc"
@@ -3712,7 +4173,11 @@ loader-fs-cache@^1.0.0:
     find-cache-dir "^0.1.1"
     mkdirp "0.5.1"
 
-loader-utils@^0.2.11, loader-utils@^0.2.15, loader-utils@^0.2.16, loader-utils@^0.2.3, loader-utils@^0.2.5, loader-utils@~0.2.2, loader-utils@~0.2.5:
+loader-runner@^2.3.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
+
+loader-utils@^0.2.16, loader-utils@^0.2.3:
   version "0.2.17"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
   dependencies:
@@ -3721,7 +4186,7 @@ loader-utils@^0.2.11, loader-utils@^0.2.15, loader-utils@^0.2.16, loader-utils@^
     json5 "^0.5.0"
     object-assign "^4.0.1"
 
-loader-utils@^1.0.2, loader-utils@^1.1.0:
+loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
   dependencies:
@@ -3735,6 +4200,20 @@ localforage@^1.5.0:
   dependencies:
     lie "3.1.1"
 
+locate-path@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
+  dependencies:
+    p-locate "^2.0.0"
+    path-exists "^3.0.0"
+
+locate-path@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
+  dependencies:
+    p-locate "^3.0.0"
+    path-exists "^3.0.0"
+
 lodash._arraycopy@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1"
@@ -3820,13 +4299,6 @@ lodash._createassigner@^3.0.0:
     lodash._isiterateecall "^3.0.0"
     lodash.restparam "^3.0.0"
 
-lodash._createcompounder@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/lodash._createcompounder/-/lodash._createcompounder-3.0.0.tgz#5dd2cb55372d6e70e0e2392fb2304d6631091075"
-  dependencies:
-    lodash.deburr "^3.0.0"
-    lodash.words "^3.0.0"
-
 lodash._getnative@^3.0.0:
   version "3.9.1"
   resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
@@ -3835,23 +4307,13 @@ lodash._isiterateecall@^3.0.0:
   version "3.0.9"
   resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
 
-lodash._root@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
-
 lodash._stack@^4.0.0:
   version "4.1.3"
   resolved "https://registry.yarnpkg.com/lodash._stack/-/lodash._stack-4.1.3.tgz#751aa76c1b964b047e76d14fc72a093fcb5e2dd0"
 
-lodash.assign@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
-
-lodash.camelcase@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-3.0.1.tgz#932c8b87f8a4377897c67197533282f97aeac298"
-  dependencies:
-    lodash._createcompounder "^3.0.0"
+lodash.camelcase@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
 
 lodash.clone@3.0.3:
   version "3.0.3"
@@ -3861,7 +4323,7 @@ lodash.clone@3.0.3:
     lodash._bindcallback "^3.0.0"
     lodash._isiterateecall "^3.0.0"
 
-lodash.clonedeep@^4.3.2, lodash.clonedeep@^4.5.0:
+lodash.clonedeep@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
 
@@ -3873,12 +4335,6 @@ lodash.create@3.1.1:
     lodash._basecreate "^3.0.0"
     lodash._isiterateecall "^3.0.0"
 
-lodash.deburr@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-3.2.0.tgz#6da8f54334a366a7cf4c4c76ef8d80aa1b365ed5"
-  dependencies:
-    lodash._root "^3.0.0"
-
 lodash.defaultsdeep@4.3.2:
   version "4.3.2"
   resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.3.2.tgz#6c1a586e6c5647b0e64e2d798141b8836158be8a"
@@ -3994,6 +4450,10 @@ lodash.restparam@^3.0.0:
   version "3.6.1"
   resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
 
+lodash.tail@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
+
 lodash.toplainobject@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz#28790ad942d293d78aa663a07ecf7f52ca04198d"
@@ -4005,17 +4465,7 @@ lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
 
-lodash.words@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.words/-/lodash.words-3.2.0.tgz#4e2a8649bc08745b17c695b1a3ce8fee596623b3"
-  dependencies:
-    lodash._root "^3.0.0"
-
-lodash@^3.8.0:
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
-
-lodash@^4.0.0, lodash@^4.0.1, lodash@^4.16.4, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.0, lodash@~4.17.10:
+lodash@^4.16.4, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.5.0:
   version "4.17.11"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
 
@@ -4031,25 +4481,20 @@ log-symbols@^2.1.0:
   dependencies:
     chalk "^2.0.1"
 
-log4js@^0.6.31:
-  version "0.6.38"
-  resolved "https://registry.yarnpkg.com/log4js/-/log4js-0.6.38.tgz#2c494116695d6fb25480943d3fc872e662a522fd"
+log4js@^3.0.0:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/log4js/-/log4js-3.0.6.tgz#e6caced94967eeeb9ce399f9f8682a4b2b28c8ff"
   dependencies:
-    readable-stream "~1.0.2"
-    semver "~4.3.3"
-
-lolex@1.3.2:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31"
+    circular-json "^0.5.5"
+    date-format "^1.2.0"
+    debug "^3.1.0"
+    rfdc "^1.1.2"
+    streamroller "0.7.0"
 
 lolex@^1.4.0, lolex@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6"
 
-longest@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
-
 loose-envify@^1.0.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -4067,17 +4512,46 @@ lower-case@^1.1.1:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
 
-lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.3:
+lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.1:
   version "4.1.5"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
   dependencies:
     pseudomap "^1.0.2"
     yallist "^2.1.2"
 
+lru-cache@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+  dependencies:
+    yallist "^3.0.2"
+
 lru-cache@~2.6.5:
   version "2.6.5"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5"
 
+make-dir@^1.0.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
+  dependencies:
+    pify "^3.0.0"
+
+make-dir@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
+  dependencies:
+    pify "^4.0.1"
+    semver "^5.6.0"
+
+mamacro@^0.0.3:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4"
+
+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"
+  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"
@@ -4100,29 +4574,34 @@ math-random@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
 
+md5.js@^1.3.4:
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
+  dependencies:
+    hash-base "^3.0.0"
+    inherits "^2.0.1"
+    safe-buffer "^5.1.2"
+
 media-typer@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
 
-memory-fs@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290"
-
-memory-fs@~0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.3.0.tgz#7bcc6b629e3a43e871d7e29aca6ae8a7f15cbb20"
+mem@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/mem/-/mem-4.1.0.tgz#aeb9be2d21f47e78af29e4ac5978e8afa2ca5b8a"
   dependencies:
-    errno "^0.1.3"
-    readable-stream "^2.0.1"
+    map-age-cleaner "^0.1.1"
+    mimic-fn "^1.0.0"
+    p-is-promise "^2.0.0"
 
-memory-fs@~0.4.1:
+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"
   dependencies:
     errno "^0.1.3"
     readable-stream "^2.0.1"
 
-meow@^3.3.0, meow@^3.7.0:
+meow@^3.3.0:
   version "3.7.0"
   resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
   dependencies:
@@ -4145,7 +4624,7 @@ methods@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
 
-micromatch@^2.1.5, micromatch@^2.3.11:
+micromatch@^2.3.11:
   version "2.3.11"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
   dependencies:
@@ -4163,7 +4642,7 @@ micromatch@^2.1.5, micromatch@^2.3.11:
     parse-glob "^3.0.4"
     regex-cache "^0.4.2"
 
-micromatch@^3.1.10:
+micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8:
   version "3.1.10"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
   dependencies:
@@ -4181,29 +4660,62 @@ micromatch@^3.1.10:
     snapdragon "^0.8.1"
     to-regex "^3.0.2"
 
+miller-rabin@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
+  dependencies:
+    bn.js "^4.0.0"
+    brorand "^1.0.1"
+
+mime-db@1.40.0:
+  version "1.40.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
+
 mime-db@~1.37.0:
   version "1.37.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
 
-mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.18, mime-types@~2.1.19:
+mime-types@^2.1.12, mime-types@~2.1.18, mime-types@~2.1.19:
   version "2.1.21"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
   dependencies:
     mime-db "~1.37.0"
 
-mime@1.3.x:
-  version "1.3.6"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0"
+mime-types@~2.1.24:
+  version "2.1.24"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
+  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"
 
-mime@^1.3.4, mime@^1.5.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+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"
 
-"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
+mimic-fn@^1.0.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
+
+mini-css-extract-plugin@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0"
+  dependencies:
+    loader-utils "^1.1.0"
+    schema-utils "^1.0.0"
+    webpack-sources "^1.1.0"
+
+minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
+
+minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
+
+"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
   dependencies:
@@ -4236,6 +4748,21 @@ minizlib@^1.1.1:
   dependencies:
     minipass "^2.2.1"
 
+mississippi@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
+  dependencies:
+    concat-stream "^1.5.0"
+    duplexify "^3.4.2"
+    end-of-stream "^1.1.0"
+    flush-write-stream "^1.0.0"
+    from2 "^2.1.0"
+    parallel-transform "^1.1.0"
+    pump "^3.0.0"
+    pumpify "^1.3.3"
+    stream-each "^1.1.0"
+    through2 "^2.0.0"
+
 mixin-deep@^1.2.0:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
@@ -4243,7 +4770,7 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   dependencies:
@@ -4286,14 +4813,21 @@ mocha@^3.1.0:
     mkdirp "0.5.1"
     supports-color "3.1.2"
 
+move-concurrently@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
+  dependencies:
+    aproba "^1.1.1"
+    copy-concurrently "^1.0.0"
+    fs-write-stream-atomic "^1.0.8"
+    mkdirp "^0.5.1"
+    rimraf "^2.5.4"
+    run-queue "^1.0.3"
+
 ms@0.7.1:
   version "0.7.1"
   resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
 
-ms@0.7.2:
-  version "0.7.2"
-  resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
-
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -4302,13 +4836,13 @@ ms@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
 
-mute-stream@0.0.5:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
+mute-stream@0.0.7:
+  version "0.0.7"
+  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
 
-nan@^2.3.2, nan@^2.9.2:
-  version "2.12.1"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
+nan@^2.12.1:
+  version "2.14.0"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
 
 nanomatch@^1.2.9:
   version "1.2.13"
@@ -4326,6 +4860,10 @@ nanomatch@^1.2.9:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
+native-promise-only@^0.8.1:
+  version "0.8.1"
+  resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11"
+
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -4342,13 +4880,21 @@ negotiator@0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
 
+negotiator@0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
+
+neo-async@^2.5.0:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
+
 netmask@~1.0.4:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
 
-next-tick@1:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
+nice-try@^1.0.4:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
 
 nightwatch@^0.9.8:
   version "0.9.21"
@@ -4371,54 +4917,37 @@ no-case@^2.2.0:
   dependencies:
     lower-case "^1.1.1"
 
-node-gyp@^3.3.1:
-  version "3.8.0"
-  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
-  dependencies:
-    fstream "^1.0.0"
-    glob "^7.0.3"
-    graceful-fs "^4.1.2"
-    mkdirp "^0.5.0"
-    nopt "2 || 3"
-    npmlog "0 || 1 || 2 || 3 || 4"
-    osenv "0"
-    request "^2.87.0"
-    rimraf "2"
-    semver "~5.3.0"
-    tar "^2.0.0"
-    which "1"
-
-node-libs-browser@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-0.7.0.tgz#3e272c0819e308935e26674408d7af0e1491b83b"
+node-libs-browser@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.0.tgz#c72f60d9d46de08a940dedbb25f3ffa2f9bbaa77"
   dependencies:
     assert "^1.1.1"
-    browserify-zlib "^0.1.4"
-    buffer "^4.9.0"
+    browserify-zlib "^0.2.0"
+    buffer "^4.3.0"
     console-browserify "^1.1.0"
     constants-browserify "^1.0.0"
-    crypto-browserify "3.3.0"
+    crypto-browserify "^3.11.0"
     domain-browser "^1.1.1"
-    events "^1.0.0"
-    https-browserify "0.0.1"
-    os-browserify "^0.2.0"
+    events "^3.0.0"
+    https-browserify "^1.0.0"
+    os-browserify "^0.3.0"
     path-browserify "0.0.0"
-    process "^0.11.0"
+    process "^0.11.10"
     punycode "^1.2.4"
     querystring-es3 "^0.2.0"
-    readable-stream "^2.0.5"
+    readable-stream "^2.3.3"
     stream-browserify "^2.0.1"
-    stream-http "^2.3.1"
-    string_decoder "^0.10.25"
-    timers-browserify "^2.0.2"
+    stream-http "^2.7.2"
+    string_decoder "^1.0.0"
+    timers-browserify "^2.0.4"
     tty-browserify "0.0.0"
     url "^0.11.0"
-    util "^0.10.3"
+    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"
+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"
   dependencies:
     detect-libc "^1.0.2"
     mkdirp "^0.5.1"
@@ -4431,27 +4960,6 @@ node-pre-gyp@^0.10.0:
     semver "^5.3.0"
     tar "^4"
 
-node-sass@^3.10.1:
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-3.13.1.tgz#7240fbbff2396304b4223527ed3020589c004fc2"
-  dependencies:
-    async-foreach "^0.1.3"
-    chalk "^1.1.1"
-    cross-spawn "^3.0.0"
-    gaze "^1.0.0"
-    get-stdin "^4.0.1"
-    glob "^7.0.3"
-    in-publish "^2.0.0"
-    lodash.assign "^4.2.0"
-    lodash.clonedeep "^4.3.2"
-    meow "^3.7.0"
-    mkdirp "^0.5.1"
-    nan "^2.3.2"
-    node-gyp "^3.3.1"
-    npmlog "^4.0.0"
-    request "^2.61.0"
-    sass-graph "^2.1.1"
-
 nomnomnomnom@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/nomnomnomnom/-/nomnomnomnom-2.0.1.tgz#b2239f031c8d04da67e32836e1e3199e12f7a8e2"
@@ -4459,13 +4967,13 @@ nomnomnomnom@^2.0.0:
     chalk "~0.4.0"
     underscore "~1.6.0"
 
-"nopt@2 || 3", nopt@3.x:
+nopt@3.x:
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
   dependencies:
     abbrev "1"
 
-nopt@^4.0.1, nopt@~4.0.1:
+nopt@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
   dependencies:
@@ -4481,12 +4989,16 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
     semver "2 || 3 || 4 || 5"
     validate-npm-package-license "^3.0.1"
 
-normalize-path@^2.0.0, normalize-path@^2.0.1:
+normalize-path@^2.0.1, normalize-path@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
   dependencies:
     remove-trailing-separator "^1.0.1"
 
+normalize-path@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+
 normalize-range@^0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
@@ -4511,7 +5023,13 @@ npm-packlist@^1.1.6:
     ignore-walk "^3.0.1"
     npm-bundled "^1.0.1"
 
-"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2:
+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"
+  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"
   dependencies:
@@ -4538,11 +5056,7 @@ oauth-sign@~0.9.0:
   version "0.9.0"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
 
-object-assign@4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
-
-object-assign@^4.0.1, object-assign@^4.1.0:
+object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
 
@@ -4562,6 +5076,10 @@ object-hash@^1.1.4:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
 
+object-keys@^1.0.12:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+
 object-path@^0.11.3:
   version "0.11.4"
   resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949"
@@ -4572,6 +5090,13 @@ object-visit@^1.0.0:
   dependencies:
     isobject "^3.0.0"
 
+object.getownpropertydescriptors@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
+  dependencies:
+    define-properties "^1.1.2"
+    es-abstract "^1.5.1"
+
 object.omit@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
@@ -4591,7 +5116,7 @@ on-finished@~2.3.0:
   dependencies:
     ee-first "1.1.1"
 
-once@1.x, once@^1.3.0:
+once@1.x, once@^1.3.0, once@^1.3.1, once@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
   dependencies:
@@ -4601,6 +5126,12 @@ onetime@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
 
+onetime@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
+  dependencies:
+    mimic-fn "^1.0.0"
+
 opn@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95"
@@ -4608,7 +5139,7 @@ opn@^4.0.2:
     object-assign "^4.0.1"
     pinkie-promise "^2.0.0"
 
-optimist@0.6.1, optimist@^0.6.1, optimist@~0.6.0:
+optimist@0.6.1, optimist@^0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
   dependencies:
@@ -4626,10 +5157,6 @@ optionator@^0.8.1, optionator@^0.8.2:
     type-check "~0.3.2"
     wordwrap "~1.0.0"
 
-options@>=0.0.5:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
-
 ora@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/ora/-/ora-0.3.0.tgz#367a078ad25cfb096da501115eb5b401e07d7495"
@@ -4639,35 +5166,81 @@ ora@^0.3.0:
     cli-spinners "^0.2.0"
     log-symbols "^1.0.2"
 
-os-browserify@^0.2.0:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f"
+os-browserify@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
 
 os-homedir@^1.0.0, os-homedir@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
 
-os-locale@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
+os-locale@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
   dependencies:
-    lcid "^1.0.0"
+    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.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"
 
-osenv@0, osenv@^0.1.4:
+osenv@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
   dependencies:
     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"
+
+p-finally@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+
+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"
+
+p-limit@^1.1.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
+  dependencies:
+    p-try "^1.0.0"
+
+p-limit@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.1.0.tgz#1d5a0d20fb12707c758a655f6bbc4386b5930d68"
+  dependencies:
+    p-try "^2.0.0"
+
+p-locate@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+  dependencies:
+    p-limit "^1.1.0"
+
+p-locate@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
+  dependencies:
+    p-limit "^2.0.0"
+
 p-map@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
 
+p-try@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+
+p-try@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
+
 pac-proxy-agent@1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz#34a385dfdf61d2f0ecace08858c745d3e791fd4d"
@@ -4692,9 +5265,17 @@ pac-resolver@~2.0.0:
     netmask "~1.0.4"
     thunkify "~2.1.1"
 
-pako@~0.2.0:
-  version "0.2.9"
-  resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
+pako@~1.0.5:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
+
+parallel-transform@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
+  dependencies:
+    cyclist "~0.2.2"
+    inherits "^2.0.3"
+    readable-stream "^2.1.5"
 
 param-case@2.1.x:
   version "2.1.1"
@@ -4702,6 +5283,23 @@ param-case@2.1.x:
   dependencies:
     no-case "^2.2.0"
 
+parent-module@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+  dependencies:
+    callsites "^3.0.0"
+
+parse-asn1@^5.0.0:
+  version "5.1.4"
+  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc"
+  dependencies:
+    asn1.js "^4.0.0"
+    browserify-aes "^1.0.0"
+    create-hash "^1.1.0"
+    evp_bytestokey "^1.0.0"
+    pbkdf2 "^3.0.3"
+    safe-buffer "^5.1.1"
+
 parse-glob@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
@@ -4717,12 +5315,6 @@ parse-json@^2.2.0:
   dependencies:
     error-ex "^1.2.0"
 
-parsejson@0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab"
-  dependencies:
-    better-assert "~1.0.0"
-
 parseqs@0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
@@ -4747,24 +5339,46 @@ path-browserify@0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
 
+path-dirname@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
+
 path-exists@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
   dependencies:
     pinkie-promise "^2.0.0"
 
+path-exists@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+
 path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
 
-path-is-inside@^1.0.1:
+path-is-inside@^1.0.1, path-is-inside@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
 
+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"
+
+path-parse@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
+
 path-to-regexp@0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
 
+path-to-regexp@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
+  dependencies:
+    isarray "0.0.1"
+
 path-type@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@@ -4773,9 +5387,21 @@ path-type@^1.0.0:
     pify "^2.0.0"
     pinkie-promise "^2.0.0"
 
-pbkdf2-compat@2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz#b6e0c8fa99494d94e0511575802a59a5c142f288"
+path-type@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
+  dependencies:
+    pify "^2.0.0"
+
+pbkdf2@^3.0.3:
+  version "3.0.17"
+  resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6"
+  dependencies:
+    create-hash "^1.1.2"
+    create-hmac "^1.1.4"
+    ripemd160 "^2.0.1"
+    safe-buffer "^5.0.1"
+    sha.js "^2.4.8"
 
 pend@~1.2.0:
   version "1.2.0"
@@ -4785,20 +5411,6 @@ performance-now@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
 
-phantomjs-prebuilt@^2.1.3, phantomjs-prebuilt@^2.1.7:
-  version "2.1.16"
-  resolved "https://registry.yarnpkg.com/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz#efd212a4a3966d3647684ea8ba788549be2aefef"
-  dependencies:
-    es6-promise "^4.0.3"
-    extract-zip "^1.6.5"
-    fs-extra "^1.0.0"
-    hasha "^2.2.0"
-    kew "^0.7.0"
-    progress "^1.1.8"
-    request "^2.81.0"
-    request-progress "^2.0.1"
-    which "^1.2.10"
-
 phoenix@^1.3.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.4.0.tgz#9cec8dbd8cbc59ecd2147bc09ca8ceb56b860d75"
@@ -4811,6 +5423,10 @@ pify@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
 
+pify@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
+
 pinkie-promise@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
@@ -4827,9 +5443,30 @@ pkg-dir@^1.0.0:
   dependencies:
     find-up "^1.0.0"
 
-pluralize@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
+pkg-dir@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
+  dependencies:
+    find-up "^2.1.0"
+
+pkg-dir@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
+  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"
+
+popper.js@^1.15.0:
+  version "1.15.0"
+  resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"
+  integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==
+
+portal-vue@^2.1.4:
+  version "2.1.4"
+  resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.4.tgz#1fc679d77e294dc8d026f1eb84aa467de11b392e"
 
 posix-character-classes@^0.1.0:
   version "0.1.1"
@@ -4979,27 +5616,27 @@ postcss-minify-selectors@^2.0.4:
     postcss "^5.0.14"
     postcss-selector-parser "^2.0.0"
 
-postcss-modules-extract-imports@^1.0.0:
+postcss-modules-extract-imports@^1.2.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a"
   dependencies:
     postcss "^6.0.1"
 
-postcss-modules-local-by-default@^1.0.1:
+postcss-modules-local-by-default@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069"
   dependencies:
     css-selector-tokenizer "^0.7.0"
     postcss "^6.0.1"
 
-postcss-modules-scope@^1.0.0:
+postcss-modules-scope@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90"
   dependencies:
     css-selector-tokenizer "^0.7.0"
     postcss "^6.0.1"
 
-postcss-modules-values@^1.1.0:
+postcss-modules-values@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20"
   dependencies:
@@ -5086,7 +5723,7 @@ postcss-zindex@^2.0.1:
     postcss "^5.0.4"
     uniqs "^2.0.0"
 
-postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.21, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16:
+postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16:
   version "5.2.18"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5"
   dependencies:
@@ -5095,7 +5732,7 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0
     source-map "^0.5.6"
     supports-color "^3.2.3"
 
-postcss@^6.0.1:
+postcss@^6.0.1, postcss@^6.0.8:
   version "6.0.23"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
   dependencies:
@@ -5123,6 +5760,10 @@ preserve@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
 
+prettier@^1.16.0:
+  version "1.17.1"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.1.tgz#ed64b4e93e370cb8a25b9ef7fef3e4fd1c0995db"
+
 pretty-error@^2.0.2:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3"
@@ -5138,17 +5779,17 @@ process-nextick-args@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
 
-process@^0.11.0:
+process@^0.11.10:
   version "0.11.10"
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
 
-progress@^1.1.8:
-  version "1.1.8"
-  resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
+progress@^2.0.0:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
 
-proto-list@~1.2.1:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+promise-inflight@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
 
 proxy-addr@~2.0.4:
   version "2.0.4"
@@ -5182,6 +5823,39 @@ psl@^1.1.24:
   version "1.1.31"
   resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184"
 
+public-encrypt@^4.0.0:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
+  dependencies:
+    bn.js "^4.1.0"
+    browserify-rsa "^4.0.0"
+    create-hash "^1.1.0"
+    parse-asn1 "^5.0.0"
+    randombytes "^2.0.1"
+    safe-buffer "^5.1.2"
+
+pump@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
+  dependencies:
+    end-of-stream "^1.1.0"
+    once "^1.3.1"
+
+pump@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
+  dependencies:
+    end-of-stream "^1.1.0"
+    once "^1.3.1"
+
+pumpify@^1.3.3:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
+  dependencies:
+    duplexify "^3.6.0"
+    inherits "^2.0.3"
+    pump "^2.0.0"
+
 punycode@1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
@@ -5202,6 +5876,16 @@ qjobs@^1.1.4:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
 
+qrcode@^1.3.0:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.3.3.tgz#5ef50c0c890cffa1897f452070f0f094936993de"
+  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"
@@ -5229,10 +5913,27 @@ randomatic@^3.0.0:
     kind-of "^6.0.0"
     math-random "^1.0.1"
 
-range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0:
+randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+  dependencies:
+    safe-buffer "^5.1.0"
+
+randomfill@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458"
+  dependencies:
+    randombytes "^2.0.5"
+    safe-buffer "^5.1.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"
 
+range-parser@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+
 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"
@@ -5262,6 +5963,13 @@ read-pkg-up@^1.0.1:
     find-up "^1.0.0"
     read-pkg "^1.0.0"
 
+read-pkg-up@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
+  dependencies:
+    find-up "^2.0.0"
+    read-pkg "^2.0.0"
+
 read-pkg@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
@@ -5270,7 +5978,27 @@ read-pkg@^1.0.0:
     normalize-package-data "^2.3.2"
     path-type "^1.0.0"
 
-readable-stream@1.0, readable-stream@~1.0.2:
+read-pkg@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
+  dependencies:
+    load-json-file "^2.0.0"
+    normalize-package-data "^2.3.2"
+    path-type "^2.0.0"
+
+"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"
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.3"
+    isarray "~1.0.0"
+    process-nextick-args "~2.0.0"
+    safe-buffer "~5.1.1"
+    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"
   dependencies:
@@ -5288,18 +6016,6 @@ readable-stream@1.1.x:
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-readable-stream@2, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.6:
-  version "2.3.6"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.3"
-    isarray "~1.0.0"
-    process-nextick-args "~2.0.0"
-    safe-buffer "~5.1.1"
-    string_decoder "~1.1.1"
-    util-deprecate "~1.0.1"
-
 readable-stream@^3.0.6:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06"
@@ -5308,7 +6024,7 @@ readable-stream@^3.0.6:
     string_decoder "^1.1.1"
     util-deprecate "^1.0.1"
 
-readdirp@^2.0.0:
+readdirp@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
   dependencies:
@@ -5316,14 +6032,6 @@ readdirp@^2.0.0:
     micromatch "^3.1.10"
     readable-stream "^2.0.2"
 
-readline2@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35"
-  dependencies:
-    code-point-at "^1.0.0"
-    is-fullwidth-code-point "^1.0.0"
-    mute-stream "0.0.5"
-
 rechoir@^0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
@@ -5384,6 +6092,10 @@ regex-not@^1.0.0, regex-not@^1.0.2:
     extend-shallow "^3.0.2"
     safe-regex "^1.1.0"
 
+regexpp@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
+
 regexpu-core@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
@@ -5446,13 +6158,7 @@ repeating@^2.0.0:
   dependencies:
     is-finite "^1.0.0"
 
-request-progress@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-2.0.1.tgz#5d36bb57961c673aa5b788dbc8141fdf23b44e08"
-  dependencies:
-    throttleit "^1.0.0"
-
-request@^2.61.0, request@^2.81.0, request@^2.87.0, request@^2.88.0:
+request@^2.88.0:
   version "2.88.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
   dependencies:
@@ -5493,20 +6199,13 @@ 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"
 
-require-uncached@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
-  dependencies:
-    caller-path "^0.1.0"
-    resolve-from "^1.0.0"
-
 requires-port@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
 
-resolve-from@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
+resolve-from@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
 
 resolve-url@^0.2.1:
   version "0.2.1"
@@ -5516,6 +6215,12 @@ resolve@1.1.x, resolve@^1.1.6:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
 
+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"
+  dependencies:
+    path-parse "^1.0.6"
+
 restore-cursor@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
@@ -5523,37 +6228,53 @@ restore-cursor@^1.0.1:
     exit-hook "^1.0.0"
     onetime "^1.0.0"
 
+restore-cursor@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
+  dependencies:
+    onetime "^2.0.0"
+    signal-exit "^3.0.2"
+
 ret@~0.1.10:
   version "0.1.15"
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
 
-right-align@^0.1.1:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
-  dependencies:
-    align-text "^0.1.1"
+rfdc@^1.1.2:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
 
-rimraf@2, rimraf@^2.2.8, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@~2.6.2:
+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"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
   dependencies:
     glob "^7.1.3"
 
-ripemd160@0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-0.2.0.tgz#2bf198bde167cacfa51c0a928e84b68bbe171fce"
-
-run-async@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389"
+ripemd160@^2.0.0, ripemd160@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
   dependencies:
-    once "^1.3.0"
+    hash-base "^3.0.0"
+    inherits "^2.0.1"
 
-rx-lite@^3.1.2:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
+run-async@^2.2.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
+  dependencies:
+    is-promise "^2.1.0"
 
-safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+run-queue@^1.0.0, run-queue@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
+  dependencies:
+    aproba "^1.1.1"
+
+rxjs@^6.4.0:
+  version "6.5.2"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7"
+  dependencies:
+    tslib "^1.9.0"
+
+safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
 
@@ -5567,9 +6288,9 @@ safe-regex@^1.1.0:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
 
-samsam@1.1.2, samsam@~1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567"
+samsam@1.x, samsam@^1.1.3:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
 
 sanitize-html@^1.13.0:
   version "1.20.0"
@@ -5586,54 +6307,51 @@ sanitize-html@^1.13.0:
     srcset "^1.0.0"
     xtend "^4.0.1"
 
-sass-graph@^2.1.1:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
+"sass-loader@git://github.com/webpack-contrib/sass-loader":
+  version "7.1.0"
+  resolved "git://github.com/webpack-contrib/sass-loader#e279f2a129eee0bd0b624b5acd498f23a81ee35e"
   dependencies:
-    glob "^7.0.0"
-    lodash "^4.0.0"
-    scss-tokenizer "^0.2.3"
-    yargs "^7.0.0"
+    clone-deep "^4.0.1"
+    loader-utils "^1.0.1"
+    lodash.tail "^4.1.1"
+    neo-async "^2.5.0"
+    pify "^4.0.1"
+    semver "^5.5.0"
 
-sass-loader@^4.0.2:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-4.1.1.tgz#79ef9468cf0bf646c29529e1f2cba6bd6e51c7bc"
+sass@^1.17.3:
+  version "1.20.1"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.20.1.tgz#737b901fe072336da540b6d00ec155e2267420da"
   dependencies:
-    async "^2.0.1"
-    loader-utils "^0.2.15"
-    object-assign "^4.1.0"
+    chokidar "^2.0.0"
 
 sax@^1.2.4, sax@~1.2.1:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
 
-scss-tokenizer@^0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
+schema-utils@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
   dependencies:
-    js-base64 "^2.1.8"
-    source-map "^0.4.2"
+    ajv "^6.1.0"
+    ajv-errors "^1.0.0"
+    ajv-keywords "^3.1.0"
 
 selenium-server@2.53.1:
   version "2.53.1"
   resolved "https://registry.yarnpkg.com/selenium-server/-/selenium-server-2.53.1.tgz#d681528812f3c2e0531a6b7e613e23bb02cce8a6"
 
-"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.6.0:
+"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0:
   version "5.6.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
 
-semver@~4.3.3:
-  version "4.3.6"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"
+semver@^5.5.1:
+  version "5.7.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
 
 semver@~5.0.1:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
 
-semver@~5.3.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
-
 send@0.16.2:
   version "0.16.2"
   resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
@@ -5652,6 +6370,10 @@ send@0.16.2:
     range-parser "~1.2.0"
     statuses "~1.4.0"
 
+serialize-javascript@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65"
+
 serve-static@1.13.2:
   version "1.13.2"
   resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
@@ -5661,11 +6383,11 @@ serve-static@1.13.2:
     parseurl "~1.3.2"
     send "0.16.2"
 
-serviceworker-webpack-plugin@0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/serviceworker-webpack-plugin/-/serviceworker-webpack-plugin-0.2.3.tgz#1873ed6fc83c873ac8240fac443c615d374feeb2"
+serviceworker-webpack-plugin@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/serviceworker-webpack-plugin/-/serviceworker-webpack-plugin-1.0.1.tgz#481863288487e92da01d49745336c72ef8a6136b"
   dependencies:
-    minimatch "^3.0.3"
+    minimatch "^3.0.4"
 
 set-blocking@^2.0.0, set-blocking@~2.0.0:
   version "2.0.0"
@@ -5697,11 +6419,30 @@ setprototypeof@1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
 
-sha.js@2.2.6:
-  version "2.2.6"
-  resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba"
+sha.js@^2.4.0, sha.js@^2.4.8:
+  version "2.4.11"
+  resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
+  dependencies:
+    inherits "^2.0.1"
+    safe-buffer "^5.0.1"
 
-shelljs@^0.7.4, shelljs@^0.7.5:
+shallow-clone@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+  dependencies:
+    kind-of "^6.0.2"
+
+shebang-command@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+  dependencies:
+    shebang-regex "^1.0.0"
+
+shebang-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+
+shelljs@^0.7.4:
   version "0.7.8"
   resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
   dependencies:
@@ -5709,11 +6450,7 @@ shelljs@^0.7.4, shelljs@^0.7.5:
     interpret "^1.0.0"
     rechoir "^0.6.2"
 
-sigmund@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
-
-signal-exit@^3.0.0:
+signal-exit@^3.0.0, signal-exit@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
 
@@ -5721,22 +6458,30 @@ sinon-chai@^2.8.0:
   version "2.14.0"
   resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.14.0.tgz#da7dd4cc83cd6a260b67cca0f7a9fdae26a1205d"
 
-sinon@^1.17.3:
-  version "1.17.7"
-  resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf"
+sinon@^2.1.0:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.4.1.tgz#021fd64b54cb77d9d2fb0d43cdedfae7629c3a36"
   dependencies:
-    formatio "1.1.1"
-    lolex "1.3.2"
-    samsam "1.1.2"
-    util ">=0.10.3 <1"
+    diff "^3.1.0"
+    formatio "1.2.0"
+    lolex "^1.6.0"
+    native-promise-only "^0.8.1"
+    path-to-regexp "^1.7.0"
+    samsam "^1.1.3"
+    text-encoding "0.6.4"
+    type-detect "^4.0.0"
 
 slash@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
 
-slice-ansi@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
+slice-ansi@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
+  dependencies:
+    ansi-styles "^3.2.0"
+    astral-regex "^1.0.0"
+    is-fullwidth-code-point "^2.0.0"
 
 smart-buffer@^1.0.13:
   version "1.1.15"
@@ -5769,49 +6514,47 @@ snapdragon@^0.8.1:
     source-map-resolve "^0.5.0"
     use "^3.1.0"
 
-socket.io-adapter@0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b"
-  dependencies:
-    debug "2.3.3"
-    socket.io-parser "2.3.1"
+socket.io-adapter@~1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b"
 
-socket.io-client@1.7.3:
-  version "1.7.3"
-  resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.3.tgz#b30e86aa10d5ef3546601c09cde4765e381da377"
+socket.io-client@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.1.1.tgz#dcb38103436ab4578ddb026638ae2f21b623671f"
   dependencies:
     backo2 "1.0.2"
+    base64-arraybuffer "0.1.5"
     component-bind "1.0.0"
     component-emitter "1.2.1"
-    debug "2.3.3"
-    engine.io-client "1.8.3"
-    has-binary "0.1.7"
+    debug "~3.1.0"
+    engine.io-client "~3.2.0"
+    has-binary2 "~1.0.2"
+    has-cors "1.1.0"
     indexof "0.0.1"
     object-component "0.0.3"
+    parseqs "0.0.5"
     parseuri "0.0.5"
-    socket.io-parser "2.3.1"
+    socket.io-parser "~3.2.0"
     to-array "0.1.4"
 
-socket.io-parser@2.3.1:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0"
+socket.io-parser@~3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.2.0.tgz#e7c6228b6aa1f814e6148aea325b51aa9499e077"
   dependencies:
-    component-emitter "1.1.2"
-    debug "2.2.0"
-    isarray "0.0.1"
-    json3 "3.3.2"
+    component-emitter "1.2.1"
+    debug "~3.1.0"
+    isarray "2.0.1"
 
-socket.io@1.7.3:
-  version "1.7.3"
-  resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.3.tgz#b8af9caba00949e568e369f1327ea9be9ea2461b"
+socket.io@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.1.1.tgz#a069c5feabee3e6b214a75b40ce0652e1cfb9980"
   dependencies:
-    debug "2.3.3"
-    engine.io "1.8.3"
-    has-binary "0.1.7"
-    object-assign "4.1.0"
-    socket.io-adapter "0.5.0"
-    socket.io-client "1.7.3"
-    socket.io-parser "2.3.1"
+    debug "~3.1.0"
+    engine.io "~3.2.0"
+    has-binary2 "~1.0.2"
+    socket.io-adapter "~1.1.0"
+    socket.io-client "2.1.1"
+    socket.io-parser "~3.2.0"
 
 socks-proxy-agent@2:
   version "2.1.1"
@@ -5834,9 +6577,9 @@ sort-keys@^1.0.0:
   dependencies:
     is-plain-obj "^1.0.0"
 
-source-list-map@^0.1.4, source-list-map@~0.1.7:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106"
+source-list-map@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
 
 source-map-resolve@^0.5.0:
   version "0.5.2"
@@ -5854,27 +6597,22 @@ source-map-support@^0.4.15:
   dependencies:
     source-map "^0.5.6"
 
+source-map-support@~0.5.10:
+  version "0.5.12"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
 source-map-url@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
 
-source-map@^0.1.41:
-  version "0.1.43"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
-  dependencies:
-    amdefine ">=0.0.4"
-
-source-map@^0.4.2, source-map@~0.4.1:
-  version "0.4.4"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
-  dependencies:
-    amdefine ">=0.0.4"
-
-source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3:
+source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
 
-source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
+source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
 
@@ -5937,6 +6675,12 @@ sshpk@^1.7.0:
     safer-buffer "^2.0.2"
     tweetnacl "~0.14.0"
 
+ssri@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
+  dependencies:
+    figgy-pudding "^3.5.1"
+
 static-extend@^0.1.1:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@@ -5959,7 +6703,14 @@ stream-browserify@^2.0.1:
     inherits "~2.0.1"
     readable-stream "^2.0.2"
 
-stream-http@^2.3.1:
+stream-each@^1.1.0:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae"
+  dependencies:
+    end-of-stream "^1.1.0"
+    stream-shift "^1.0.0"
+
+stream-http@^2.7.2:
   version "2.8.3"
   resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc"
   dependencies:
@@ -5969,11 +6720,24 @@ stream-http@^2.3.1:
     to-arraybuffer "^1.0.0"
     xtend "^4.0.0"
 
+stream-shift@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
+
+streamroller@0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.7.0.tgz#a1d1b7cf83d39afb0d63049a5acbf93493bdf64b"
+  dependencies:
+    date-format "^1.2.0"
+    debug "^3.1.0"
+    mkdirp "^0.5.1"
+    readable-stream "^2.3.0"
+
 strict-uri-encode@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
 
-string-width@^1.0.1, string-width@^1.0.2:
+string-width@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
   dependencies:
@@ -5981,23 +6745,31 @@ string-width@^1.0.1, string-width@^1.0.2:
     is-fullwidth-code-point "^1.0.0"
     strip-ansi "^3.0.0"
 
-"string-width@^1.0.2 || 2", string-width@^2.0.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"
   dependencies:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
 
-string_decoder@^0.10.25, string_decoder@~0.10.x:
-  version "0.10.31"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+string-width@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
+  dependencies:
+    emoji-regex "^7.0.1"
+    is-fullwidth-code-point "^2.0.0"
+    strip-ansi "^5.1.0"
 
-string_decoder@^1.1.1:
+string_decoder@^1.0.0, string_decoder@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
   dependencies:
     safe-buffer "~5.1.0"
 
+string_decoder@~0.10.x:
+  version "0.10.31"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+
 string_decoder@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -6016,6 +6788,12 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
+strip-ansi@^5.1.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+  dependencies:
+    ansi-regex "^4.1.0"
+
 strip-ansi@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
@@ -6030,13 +6808,17 @@ strip-bom@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
 
+strip-eof@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
+
 strip-indent@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
   dependencies:
     get-stdin "^4.0.1"
 
-strip-json-comments@~2.0.1:
+strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 
@@ -6080,28 +6862,18 @@ svgo@^0.7.0:
     sax "~1.2.1"
     whet.extend "~0.9.9"
 
-table@^3.7.8:
-  version "3.8.3"
-  resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
+table@^5.2.3:
+  version "5.3.3"
+  resolved "https://registry.yarnpkg.com/table/-/table-5.3.3.tgz#eae560c90437331b74200e011487a33442bd28b4"
   dependencies:
-    ajv "^4.7.0"
-    ajv-keywords "^1.0.0"
-    chalk "^1.1.1"
-    lodash "^4.0.0"
-    slice-ansi "0.0.4"
-    string-width "^2.0.0"
+    ajv "^6.9.1"
+    lodash "^4.17.11"
+    slice-ansi "^2.1.0"
+    string-width "^3.0.0"
 
-tapable@^0.1.8, tapable@~0.1.8:
-  version "0.1.10"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4"
-
-tar@^2.0.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
-  dependencies:
-    block-stream "*"
-    fstream "^1.0.2"
-    inherits "2"
+tapable@^1.0.0, tapable@^1.1.0:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
 
 tar@^4:
   version "4.4.8"
@@ -6122,13 +6894,42 @@ tcp-port-used@^1.0.1:
     debug "4.1.0"
     is2 "2.0.1"
 
-text-table@^0.2.0, text-table@~0.2.0:
+terser-webpack-plugin@^1.1.0:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.4.tgz#56f87540c28dd5265753431009388f473b5abba3"
+  dependencies:
+    cacache "^11.3.2"
+    find-cache-dir "^2.0.0"
+    is-wsl "^1.1.0"
+    schema-utils "^1.0.0"
+    serialize-javascript "^1.7.0"
+    source-map "^0.6.1"
+    terser "^3.17.0"
+    webpack-sources "^1.3.0"
+    worker-farm "^1.7.0"
+
+terser@^3.17.0:
+  version "3.17.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2"
+  dependencies:
+    commander "^2.19.0"
+    source-map "~0.6.1"
+    source-map-support "~0.5.10"
+
+text-encoding@0.6.4:
+  version "0.6.4"
+  resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
+
+text-table@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
 
-throttleit@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c"
+through2@^2.0.0:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
+  dependencies:
+    readable-stream "~2.3.6"
+    xtend "~4.0.1"
 
 through@^2.3.6:
   version "2.3.8"
@@ -6138,17 +6939,19 @@ thunkify@~2.1.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d"
 
-time-stamp@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.2.0.tgz#917e0a66905688790ec7bbbde04046259af83f57"
-
-timers-browserify@^2.0.2:
+timers-browserify@^2.0.4:
   version "2.0.10"
   resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae"
   dependencies:
     setimmediate "^1.0.4"
 
-tmp@0.0.31, tmp@0.0.x:
+tmp@0.0.33, tmp@^0.0.33:
+  version "0.0.33"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+  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"
   dependencies:
@@ -6211,6 +7014,10 @@ trim-right@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
 
+tslib@^1.9.0:
+  version "1.9.3"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
+
 tty-browserify@0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@@ -6239,6 +7046,10 @@ type-detect@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2"
 
+type-detect@^4.0.0:
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
+
 type-is@~1.6.16:
   version "1.6.16"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
@@ -6257,22 +7068,9 @@ uglify-js@3.4.x, uglify-js@^3.1.4:
     commander "~2.17.1"
     source-map "~0.6.1"
 
-uglify-js@~2.7.3:
-  version "2.7.5"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
-  dependencies:
-    async "~0.2.6"
-    source-map "~0.5.1"
-    uglify-to-browserify "~1.0.0"
-    yargs "~3.10.0"
-
-uglify-to-browserify@~1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
-
-ultron@1.0.x:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
+ultron@~1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
 
 underscore@~1.6.0:
   version "1.6.0"
@@ -6295,6 +7093,18 @@ uniqs@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
 
+unique-filename@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
+  dependencies:
+    unique-slug "^2.0.0"
+
+unique-slug@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.1.tgz#5e9edc6d1ce8fb264db18a507ef9bd8544451ca6"
+  dependencies:
+    imurmurhash "^0.1.4"
+
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -6306,6 +7116,10 @@ unset-value@^1.0.0:
     has-value "^0.3.1"
     isobject "^3.0.0"
 
+upath@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068"
+
 upper-case@^1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
@@ -6320,12 +7134,13 @@ urix@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
 
-url-loader@^0.5.7:
-  version "0.5.9"
-  resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.9.tgz#cc8fea82c7b906e7777019250869e569e995c295"
+url-loader@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.1.2.tgz#b971d191b83af693c5e3fea4064be9e1f2d7f8d8"
   dependencies:
-    loader-utils "^1.0.2"
-    mime "1.3.x"
+    loader-utils "^1.1.0"
+    mime "^2.0.3"
+    schema-utils "^1.0.0"
 
 url@^0.11.0:
   version "0.11.0"
@@ -6338,13 +7153,7 @@ use@^3.1.0:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
 
-user-home@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
-  dependencies:
-    os-homedir "^1.0.0"
-
-useragent@^2.1.12:
+useragent@2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972"
   dependencies:
@@ -6355,24 +7164,25 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
 
+util.promisify@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030"
+  dependencies:
+    define-properties "^1.1.2"
+    object.getownpropertydescriptors "^2.0.3"
+
 util@0.10.3:
   version "0.10.3"
   resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
   dependencies:
     inherits "2.0.1"
 
-"util@>=0.10.3 <1":
+util@^0.11.0:
   version "0.11.1"
   resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61"
   dependencies:
     inherits "2.0.3"
 
-util@^0.10.3:
-  version "0.10.4"
-  resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
-  dependencies:
-    inherits "2.0.3"
-
 utila@^0.4.0, utila@~0.4:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
@@ -6385,6 +7195,19 @@ uuid@^3.3.2:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
 
+v-click-outside@^2.1.1:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/v-click-outside/-/v-click-outside-2.1.3.tgz#b7297abe833a439dc0895e6418a494381e64b5e7"
+
+v-tooltip@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.2.tgz#8610d9eece2cc44fd66c12ef2f12eec6435cab9b"
+  integrity sha512-xQ+qzOFfywkLdjHknRPgMMupQNS8yJtf9Utd5Dxiu/0n4HtrxqsgDtN2MLZ0LKbburtSAQgyypuE/snM8bBZhw==
+  dependencies:
+    lodash "^4.17.11"
+    popper.js "^1.15.0"
+    vue-resize "^0.4.5"
+
 validate-npm-package-license@^3.0.1:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
@@ -6422,48 +7245,55 @@ vue-chat-scroll@^1.2.1:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/vue-chat-scroll/-/vue-chat-scroll-1.3.5.tgz#a5ee5bae5058f614818a96eac5ee3be4394a2f68"
 
-vue-compose@^0.7.1:
-  version "0.7.1"
-  resolved "https://registry.yarnpkg.com/vue-compose/-/vue-compose-0.7.1.tgz#1c11c4cd5e2c8f2743b03fce8ab43d78aabc20b3"
+vue-eslint-parser@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz#00f4e4da94ec974b821a26ff0ed0f7a78402b8a1"
   dependencies:
-    vue-hoc "0.x.x"
+    debug "^4.1.0"
+    eslint-scope "^4.0.0"
+    eslint-visitor-keys "^1.0.0"
+    espree "^4.1.0"
+    esquery "^1.0.1"
+    lodash "^4.17.11"
 
-vue-hoc@0.x.x:
-  version "0.4.7"
-  resolved "https://registry.yarnpkg.com/vue-hoc/-/vue-hoc-0.4.7.tgz#4d3322ba89b8b0e42b19045ef536c21d948a4fac"
-
-vue-hot-reload-api@^2.0.11:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"
+vue-hot-reload-api@^2.2.0:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz#2756f46cb3258054c5f4723de8ae7e87302a1ccf"
 
 vue-i18n@^7.3.2:
   version "7.8.1"
   resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-7.8.1.tgz#2ce4b6efde679a1e05ddb5d907bfc1bc218803b2"
 
-vue-loader@^11.1.0:
-  version "11.3.4"
-  resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-11.3.4.tgz#65e10a44ce092d906e14bbc72981dec99eb090d2"
+vue-loader@^14.0.0:
+  version "14.2.4"
+  resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-14.2.4.tgz#d0a0e8236155fa7f9602cde65b0d38259e051ee2"
   dependencies:
     consolidate "^0.14.0"
     hash-sum "^1.0.2"
-    js-beautify "^1.6.3"
     loader-utils "^1.1.0"
-    lru-cache "^4.0.1"
-    postcss "^5.0.21"
+    lru-cache "^4.1.1"
+    postcss "^6.0.8"
     postcss-load-config "^1.1.0"
     postcss-selector-parser "^2.0.0"
-    source-map "^0.5.6"
-    vue-hot-reload-api "^2.0.11"
-    vue-style-loader "^2.0.0"
-    vue-template-es2015-compiler "^1.2.2"
+    prettier "^1.16.0"
+    resolve "^1.4.0"
+    source-map "^0.6.1"
+    vue-hot-reload-api "^2.2.0"
+    vue-style-loader "^4.0.1"
+    vue-template-es2015-compiler "^1.6.0"
+
+vue-resize@^0.4.5:
+  version "0.4.5"
+  resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-0.4.5.tgz#4777a23042e3c05620d9cbda01c0b3cc5e32dcea"
+  integrity sha512-bhP7MlgJQ8TIkZJXAfDf78uJO+mEI3CaLABLjv0WNzr4CcGRGPIAItyWYnP6LsPA4Oq0WE+suidNs6dgpO4RHg==
 
 vue-router@^3.0.1:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be"
 
-vue-style-loader@^2.0.0:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-2.0.5.tgz#f0efac992febe3f12e493e334edb13cd235a3d22"
+vue-style-loader@^4.0.0, vue-style-loader@^4.0.1:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
   dependencies:
     hash-sum "^1.0.2"
     loader-utils "^1.0.2"
@@ -6475,13 +7305,9 @@ vue-template-compiler@^2.3.4:
     de-indent "^1.0.2"
     he "^1.1.0"
 
-vue-template-es2015-compiler@^1.2.2:
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.8.1.tgz#e2ec4f42d16b6c712e61899c6b20bcdb1df128ca"
-
-vue-timeago@^3.1.2:
-  version "3.4.4"
-  resolved "https://registry.yarnpkg.com/vue-timeago/-/vue-timeago-3.4.4.tgz#a878c9ba5840816939a89659451902f84ebdf23f"
+vue-template-es2015-compiler@^1.6.0:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
 
 vue@^2.5.13:
   version "2.5.21"
@@ -6495,30 +7321,22 @@ vuex@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2"
 
-watchpack@^0.2.1:
-  version "0.2.9"
-  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-0.2.9.tgz#62eaa4ab5e5ba35fdfc018275626e3c0f5e3fb0b"
+watchpack@^1.5.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
   dependencies:
-    async "^0.9.0"
-    chokidar "^1.0.0"
+    chokidar "^2.0.2"
     graceful-fs "^4.1.2"
+    neo-async "^2.5.0"
 
-webpack-core@~0.6.9:
-  version "0.6.9"
-  resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2"
+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"
   dependencies:
-    source-list-map "~0.1.7"
-    source-map "~0.4.1"
-
-webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.8.3:
-  version "1.12.2"
-  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e"
-  dependencies:
-    memory-fs "~0.4.1"
-    mime "^1.5.0"
-    path-is-absolute "^1.0.0"
-    range-parser "^1.0.3"
-    time-stamp "^2.0.0"
+    memory-fs "^0.4.1"
+    mime "^2.4.2"
+    range-parser "^1.2.1"
+    webpack-log "^2.0.0"
 
 webpack-hot-middleware@^2.12.2:
   version "2.24.3"
@@ -6529,6 +7347,13 @@ webpack-hot-middleware@^2.12.2:
     querystring "^0.2.0"
     strip-ansi "^3.0.0"
 
+webpack-log@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f"
+  dependencies:
+    ansi-colors "^3.0.0"
+    uuid "^3.3.2"
+
 webpack-merge@^0.14.1:
   version "0.14.1"
   resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-0.14.1.tgz#d6bfe6d9360a024e1e7f8e6383ae735f1737cd23"
@@ -6538,32 +7363,41 @@ webpack-merge@^0.14.1:
     lodash.isplainobject "^3.2.0"
     lodash.merge "^3.3.2"
 
-webpack-sources@^0.1.0:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.5.tgz#aa1f3abf0f0d74db7111c40e500b84f966640750"
+webpack-sources@^1.1.0, webpack-sources@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85"
   dependencies:
-    source-list-map "~0.1.7"
-    source-map "~0.5.3"
+    source-list-map "^2.0.0"
+    source-map "~0.6.1"
 
-webpack@^1.13.2:
-  version "1.15.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-1.15.0.tgz#4ff31f53db03339e55164a9d468ee0324968fe98"
+webpack@^4.0.0:
+  version "4.32.1"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.32.1.tgz#afe0cc7dd2b196e5a58f8d1d385311cfbb5d68c0"
   dependencies:
-    acorn "^3.0.0"
-    async "^1.3.0"
-    clone "^1.0.2"
-    enhanced-resolve "~0.9.0"
-    interpret "^0.6.4"
-    loader-utils "^0.2.11"
-    memory-fs "~0.3.0"
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-module-context" "1.8.5"
+    "@webassemblyjs/wasm-edit" "1.8.5"
+    "@webassemblyjs/wasm-parser" "1.8.5"
+    acorn "^6.0.5"
+    acorn-dynamic-import "^4.0.0"
+    ajv "^6.1.0"
+    ajv-keywords "^3.1.0"
+    chrome-trace-event "^1.0.0"
+    enhanced-resolve "^4.1.0"
+    eslint-scope "^4.0.0"
+    json-parse-better-errors "^1.0.2"
+    loader-runner "^2.3.0"
+    loader-utils "^1.1.0"
+    memory-fs "~0.4.1"
+    micromatch "^3.1.8"
     mkdirp "~0.5.0"
-    node-libs-browser "^0.7.0"
-    optimist "~0.6.0"
-    supports-color "^3.1.0"
-    tapable "~0.1.8"
-    uglify-js "~2.7.3"
-    watchpack "^0.2.1"
-    webpack-core "~0.6.9"
+    neo-async "^2.5.0"
+    node-libs-browser "^2.0.0"
+    schema-utils "^1.0.0"
+    tapable "^1.1.0"
+    terser-webpack-plugin "^1.1.0"
+    watchpack "^1.5.0"
+    webpack-sources "^1.3.0"
 
 whatwg-fetch@^2.0.3:
   version "2.0.4"
@@ -6573,11 +7407,11 @@ whet.extend@~0.9.9:
   version "0.9.9"
   resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
 
-which-module@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
+which-module@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
 
-which@1, which@^1.0.9, which@^1.1.1, which@^1.2.10, which@^1.2.9:
+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"
   dependencies:
@@ -6589,13 +7423,9 @@ wide-align@^1.1.0:
   dependencies:
     string-width "^1.0.2 || 2"
 
-window-size@0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
-
-wordwrap@0.0.2:
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
+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"
 
 wordwrap@^1.0.0, wordwrap@~1.0.0:
   version "1.0.0"
@@ -6605,6 +7435,12 @@ wordwrap@~0.0.2:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
 
+worker-farm@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
+  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"
@@ -6616,38 +7452,35 @@ wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
 
-write@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
+write@1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
   dependencies:
     mkdirp "^0.5.1"
 
-ws@1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f"
+ws@~3.3.1:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
   dependencies:
-    options ">=0.0.5"
-    ultron "1.0.x"
+    async-limiter "~1.0.0"
+    safe-buffer "~5.1.0"
+    ultron "~1.1.0"
 
-wtf-8@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a"
-
-xmlhttprequest-ssl@1.5.3:
-  version "1.5.3"
-  resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"
+xmlhttprequest-ssl@~1.5.4:
+  version "1.5.5"
+  resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
 
 xregexp@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
 
-xtend@^4.0.0, xtend@^4.0.1:
+xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
 
-y18n@^3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
+"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"
 
 yallist@^2.1.2:
   version "2.1.2"
@@ -6657,38 +7490,29 @@ yallist@^3.0.0, yallist@^3.0.2:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
 
-yargs-parser@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
+yargs-parser@^11.1.1:
+  version "11.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
   dependencies:
-    camelcase "^3.0.0"
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
 
-yargs@^7.0.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
+yargs@^12.0.5:
+  version "12.0.5"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
   dependencies:
-    camelcase "^3.0.0"
-    cliui "^3.2.0"
-    decamelize "^1.1.1"
+    cliui "^4.0.0"
+    decamelize "^1.2.0"
+    find-up "^3.0.0"
     get-caller-file "^1.0.1"
-    os-locale "^1.4.0"
-    read-pkg-up "^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 "^1.0.2"
-    which-module "^1.0.0"
-    y18n "^3.2.1"
-    yargs-parser "^5.0.0"
-
-yargs@~3.10.0:
-  version "3.10.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
-  dependencies:
-    camelcase "^1.0.2"
-    cliui "^2.1.0"
-    decamelize "^1.0.0"
-    window-size "0.1.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"