From cb5f73148a2dc9341d16326ed606d74e818fb61d Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Mon, 11 Nov 2019 14:25:38 -0600
Subject: app: search API is not available in private mode so disable it

---
 src/App.vue | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/App.vue')

diff --git a/src/App.vue b/src/App.vue
index dbe842ec..1f244b56 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -43,6 +43,7 @@
             class="nav-icon mobile-hidden"
             @toggled="onSearchBarToggled"
             @click.stop.native
+            v-if="currentUser || !privateMode"
           />
           <router-link
             class="mobile-hidden"
-- 
cgit v1.2.3-70-g09d2


From d0c78989aa9e5f5142dbc09a8935c004a2050257 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sun, 1 Dec 2019 19:34:01 -0500
Subject: hide instance url/link/text in header using hideSitename option

---
 src/App.js                                 | 1 +
 src/App.vue                                | 1 +
 src/boot/after_store.js                    | 1 +
 src/components/mobile_nav/mobile_nav.js    | 1 +
 src/components/mobile_nav/mobile_nav.vue   | 1 +
 src/components/side_drawer/side_drawer.js  | 3 +++
 src/components/side_drawer/side_drawer.vue | 2 +-
 src/modules/instance.js                    | 1 +
 8 files changed, 10 insertions(+), 1 deletion(-)

(limited to 'src/App.vue')

diff --git a/src/App.js b/src/App.js
index 04a40e30..78ff29da 100644
--- a/src/App.js
+++ b/src/App.js
@@ -90,6 +90,7 @@ export default {
     },
     sitename () { return this.$store.state.instance.name },
     chat () { return this.$store.state.chat.channel.state === 'joined' },
+    hideSitename () { return this.$store.state.instance.hideSitename },
     suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
     showInstanceSpecificPanel () {
       return this.$store.state.instance.showInstanceSpecificPanel &&
diff --git a/src/App.vue b/src/App.vue
index dbe842ec..c66df843 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -31,6 +31,7 @@
         </div>
         <div class="item">
           <router-link
+            v-if="!hideSitename"
             class="site-name"
             :to="{ name: 'root' }"
             active-class="home"
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 226b67d8..e18cd657 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -108,6 +108,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
   copyInstanceOption('alwaysShowSubjectInput')
   copyInstanceOption('noAttachmentLinks')
   copyInstanceOption('showFeaturesPanel')
+  copyInstanceOption('hideSitename')
 
   return store.dispatch('setTheme', config['theme'])
 }
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index 5a90c31f..c1166a0c 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -29,6 +29,7 @@ const MobileNav = {
     unseenNotificationsCount () {
       return this.unseenNotifications.length
     },
+    hideSitename () { return this.$store.state.instance.hideSitename },
     sitename () { return this.$store.state.instance.name }
   },
   methods: {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index d1c24e56..51f1d636 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -17,6 +17,7 @@
             <i class="button-icon icon-menu" />
           </a>
           <router-link
+            v-if="!hideSitename"
             class="site-name"
             :to="{ name: 'root' }"
             active-class="home"
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 0188cf3e..65c96e47 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -33,6 +33,9 @@ const SideDrawer = {
     logo () {
       return this.$store.state.instance.logo
     },
+    hideSitename () {
+      return this.$store.state.instance.hideSitename
+    },
     sitename () {
       return this.$store.state.instance.name
     },
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 214b8e0c..eb429f29 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -27,7 +27,7 @@
           class="side-drawer-logo-wrapper"
         >
           <img :src="logo">
-          <span>{{ sitename }}</span>
+          <span v-if="!hideSitename">{{ sitename }}</span>
         </div>
       </div>
       <ul>
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 96f14ed5..625323b9 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -27,6 +27,7 @@ const defaultState = {
   scopeCopy: true,
   subjectLineBehavior: 'email',
   postContentType: 'text/plain',
+  hideSitename: false,
   nsfwCensorImage: undefined,
   vapidPublicKey: undefined,
   noAttachmentLinks: false,
-- 
cgit v1.2.3-70-g09d2


From 3eeb3dc57296cba13e3760a79b635fba5a332be0 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 12 Dec 2019 13:37:32 -0600
Subject: Lint

---
 src/App.vue                           | 2 +-
 src/components/nav_panel/nav_panel.js | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

(limited to 'src/App.vue')

diff --git a/src/App.vue b/src/App.vue
index d455e9ed..5f8517d4 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -43,8 +43,8 @@
           <search-bar
             class="nav-icon mobile-hidden"
             @toggled="onSearchBarToggled"
-            @click.stop.native
             v-if="currentUser || !privateMode"
+            @click.stop.native
           />
           <router-link
             class="mobile-hidden"
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index dbbfb096..f27727fc 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -1,4 +1,3 @@
-import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
 import { mapState } from 'vuex'
 
 const NavPanel = {
-- 
cgit v1.2.3-70-g09d2


From 7ddd5af0812a73d6a898331e38603067e0cd219b Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 12 Dec 2019 13:46:07 -0600
Subject: Finally trust eslint

---
 src/App.vue                                |  2 +-
 src/components/side_drawer/side_drawer.vue | 18 ++++++++++++------
 2 files changed, 13 insertions(+), 7 deletions(-)

(limited to 'src/App.vue')

diff --git a/src/App.vue b/src/App.vue
index 5f8517d4..1b1c2648 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -41,9 +41,9 @@
         </div>
         <div class="item right">
           <search-bar
+            v-if="currentUser || !privateMode"
             class="nav-icon mobile-hidden"
             @toggled="onSearchBarToggled"
-            v-if="currentUser || !privateMode"
             @click.stop.native
           />
           <router-link
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index bf61d38c..49c25bba 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -79,14 +79,18 @@
             </span>
           </router-link>
         </li>
-        <li v-if="currentUser || !privateMode"
-          @click="toggleDrawer">
+        <li
+          v-if="currentUser || !privateMode"
+          @click="toggleDrawer"
+        >
           <router-link to="/main/public">
             <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
           </router-link>
         </li>
-        <li v-if="(currentUser || !privateMode) && federating"
-          @click="toggleDrawer">
+        <li
+          v-if="(currentUser || !privateMode) && federating"
+          @click="toggleDrawer"
+        >
           <router-link to="/main/all">
             <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
           </router-link>
@@ -101,8 +105,10 @@
         </li>
       </ul>
       <ul>
-        <li v-if="currentUser || !privateMode"
-          @click="toggleDrawer">
+        <li
+          v-if="currentUser || !privateMode"
+          @click="toggleDrawer"
+        >
           <router-link :to="{ name: 'search' }">
             <i class="button-icon icon-search" /> {{ $t("nav.search") }}
           </router-link>
-- 
cgit v1.2.3-70-g09d2


From e46bb942260d192beb601727519ed90df5c62494 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 28 Jan 2020 23:45:14 +0200
Subject: updated preview to account for underlay

---
 src/App.scss                                     |   4 +-
 src/App.vue                                      |   2 +-
 src/components/style_switcher/preview.vue        | 188 ++++++++++++-----------
 src/components/style_switcher/style_switcher.vue |   4 +-
 4 files changed, 107 insertions(+), 91 deletions(-)

(limited to 'src/App.vue')

diff --git a/src/App.scss b/src/App.scss
index 1fabaaa9..85fd0b47 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -31,9 +31,11 @@ h4 {
   margin: auto;
   min-height: 100vh;
   max-width: 980px;
+  align-content: flex-start;
+}
+.underlay {
   background-color: rgba(0,0,0,0.15);
   background-color: var(--underlay, rgba(0,0,0,0.15));
-  align-content: flex-start;
 }
 
 .text-center {
diff --git a/src/App.vue b/src/App.vue
index 1b1c2648..ff62fc51 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -78,7 +78,7 @@
     </nav>
     <div
       id="content"
-      class="container"
+      class="container underlay"
     >
       <div class="sidebar-flexer mobile-hidden">
         <div class="sidebar-bounds">
diff --git a/src/components/style_switcher/preview.vue b/src/components/style_switcher/preview.vue
index 101a32bd..5b8c3839 100644
--- a/src/components/style_switcher/preview.vue
+++ b/src/components/style_switcher/preview.vue
@@ -1,101 +1,117 @@
 <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="preview-container">
+    <div class="underlay underlay-preview"/>
+    <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>
+        <span class="alert error">
+          {{ $t('settings.style.preview.error') }}
         </span>
+        <button class="btn">
+          {{ $t('settings.style.preview.button') }}
+        </button>
       </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>
+      <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>
+            <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 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>
 
-      <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 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>
-      <div class="separator" />
+        <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 class="alert error">
+          {{ $t('settings.style.preview.error') }}
         </span>
-        <button class="btn">
-          {{ $t('settings.style.preview.button') }}
-        </button>
+        <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>
+
+<style lang="scss">
+.preview-container {
+  position: relative;
+}
+.underlay-preview {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 10px;
+  right: 10px;
+}
+</style>
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 5a42bba7..a3f9b488 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -116,9 +116,7 @@
       </div>
     </div>
 
-    <div class="preview-container">
-      <preview :style="previewRules" />
-    </div>
+    <preview :style="previewRules" />
 
     <keep-alive>
       <tab-switcher key="style-tweak">
-- 
cgit v1.2.3-70-g09d2


From 2e35289c3376881ca17b9330113c816a3327f245 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 3 May 2020 17:36:12 +0300
Subject: initial work on settings modal

---
 src/App.js                                         |   5 +
 src/App.scss                                       |   3 +
 src/App.vue                                        |   7 +-
 .../mobile_post_status_button.js                   |   2 +-
 .../post_status_modal/post_status_modal.js         |   3 -
 .../post_status_modal/post_status_modal.vue        |   1 -
 src/components/settings_modal/settings_modal.js    |  39 ++
 src/components/settings_modal/settings_modal.scss  |  59 ++
 src/components/settings_modal/settings_modal.vue   |  31 ++
 .../settings_modal/tabs/data_import_export.js      |  65 +++
 .../settings_modal/tabs/data_import_export.vue     |  43 ++
 .../settings_modal/tabs/mutes_and_blocks.js        | 124 +++++
 .../settings_modal/tabs/mutes_and_blocks.vue       | 173 ++++++
 .../settings_modal/tabs/notifications.js           |  27 +
 .../settings_modal/tabs/notifications.vue          |  42 ++
 src/components/settings_modal/tabs/profile.js      | 179 ++++++
 src/components/settings_modal/tabs/profile.scss    |  82 +++
 src/components/settings_modal/tabs/profile.vue     | 213 ++++++++
 src/components/settings_modal/tabs/security.js     | 106 ++++
 src/components/settings_modal/tabs/security.vue    | 143 +++++
 src/components/tab_switcher/tab_switcher.js        |   7 +-
 src/components/tab_switcher/tab_switcher.scss      | 220 +++++---
 src/components/user_settings/user_settings.js      | 257 +--------
 src/components/user_settings/user_settings.vue     | 597 ---------------------
 src/i18n/en.json                                   |   1 +
 src/modules/interface.js                           |  28 +
 26 files changed, 1530 insertions(+), 927 deletions(-)
 create mode 100644 src/components/settings_modal/settings_modal.js
 create mode 100644 src/components/settings_modal/settings_modal.scss
 create mode 100644 src/components/settings_modal/settings_modal.vue
 create mode 100644 src/components/settings_modal/tabs/data_import_export.js
 create mode 100644 src/components/settings_modal/tabs/data_import_export.vue
 create mode 100644 src/components/settings_modal/tabs/mutes_and_blocks.js
 create mode 100644 src/components/settings_modal/tabs/mutes_and_blocks.vue
 create mode 100644 src/components/settings_modal/tabs/notifications.js
 create mode 100644 src/components/settings_modal/tabs/notifications.vue
 create mode 100644 src/components/settings_modal/tabs/profile.js
 create mode 100644 src/components/settings_modal/tabs/profile.scss
 create mode 100644 src/components/settings_modal/tabs/profile.vue
 create mode 100644 src/components/settings_modal/tabs/security.js
 create mode 100644 src/components/settings_modal/tabs/security.vue

(limited to 'src/App.vue')

diff --git a/src/App.js b/src/App.js
index 61b5eec1..4d9d50d4 100644
--- a/src/App.js
+++ b/src/App.js
@@ -6,6 +6,7 @@ import InstanceSpecificPanel from './components/instance_specific_panel/instance
 import FeaturesPanel from './components/features_panel/features_panel.vue'
 import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
 import ChatPanel from './components/chat_panel/chat_panel.vue'
+import SettingsModal from './components/settings_modal/settings_modal.vue'
 import MediaModal from './components/media_modal/media_modal.vue'
 import SideDrawer from './components/side_drawer/side_drawer.vue'
 import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
@@ -29,6 +30,7 @@ export default {
     SideDrawer,
     MobilePostStatusButton,
     MobileNav,
+    SettingsModal,
     UserReportingModal,
     PostStatusModal
   },
@@ -112,6 +114,9 @@ export default {
     onSearchBarToggled (hidden) {
       this.searchBarHidden = hidden
     },
+    openSettingsModal () {
+      this.$store.dispatch('openSettingsModal')
+    },
     updateMobileState () {
       const mobileLayout = windowWidth() <= 800
       const changed = mobileLayout !== this.isMobileLayout
diff --git a/src/App.scss b/src/App.scss
index 89aa3215..7db9461c 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -860,6 +860,7 @@ nav {
   }
 }
 
+// DELETE
 .setting-item {
   border-bottom: 2px solid var(--fg, $fallback--fg);
   margin: 1em 1em 1.4em;
@@ -905,6 +906,8 @@ nav {
     max-width: 6em;
   }
 }
+// DELETE
+
 .select-multiple {
   display: flex;
   .option-list {
diff --git a/src/App.vue b/src/App.vue
index ff62fc51..db3f981f 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -46,15 +46,15 @@
             @toggled="onSearchBarToggled"
             @click.stop.native
           />
-          <router-link
+          <a
             class="mobile-hidden"
-            :to="{ name: 'settings'}"
+            @click.stop="openSettingsModal"
           >
             <i
               class="button-icon icon-cog nav-icon"
               :title="$t('nav.preferences')"
             />
-          </router-link>
+          </a>
           <a
             v-if="currentUser && currentUser.role === 'admin'"
             href="/pleroma/admin/#/login-pleroma"
@@ -122,6 +122,7 @@
     <MobilePostStatusButton />
     <UserReportingModal />
     <PostStatusModal />
+    <SettingsModal />
     <portal-target name="modal" />
   </div>
 </template>
diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js
index 0ad12bb1..ff2d4eaa 100644
--- a/src/components/mobile_post_status_button/mobile_post_status_button.js
+++ b/src/components/mobile_post_status_button/mobile_post_status_button.js
@@ -52,7 +52,7 @@ const MobilePostStatusButton = {
       window.removeEventListener('scroll', this.handleScrollEnd)
     },
     openPostForm () {
-      this.$store.dispatch('openPostStatusModal')
+      this.$store.dispatch('openSettingsModal')
     },
     handleOSK () {
       // This is a big hack: we're guessing from changed window sizes if the
diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js
index b44354db..be945400 100644
--- a/src/components/post_status_modal/post_status_modal.js
+++ b/src/components/post_status_modal/post_status_modal.js
@@ -13,9 +13,6 @@ const PostStatusModal = {
     }
   },
   computed: {
-    isLoggedIn () {
-      return !!this.$store.state.users.currentUser
-    },
     modalActivated () {
       return this.$store.state.postStatus.modalActivated
     },
diff --git a/src/components/post_status_modal/post_status_modal.vue b/src/components/post_status_modal/post_status_modal.vue
index dbcd321e..07c58f74 100644
--- a/src/components/post_status_modal/post_status_modal.vue
+++ b/src/components/post_status_modal/post_status_modal.vue
@@ -1,6 +1,5 @@
 <template>
   <Modal
-    v-if="isLoggedIn && !resettingForm"
     :is-open="modalActivated"
     class="post-form-modal-view"
     @backdropClicked="closeModal"
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
new file mode 100644
index 00000000..1f4c038f
--- /dev/null
+++ b/src/components/settings_modal/settings_modal.js
@@ -0,0 +1,39 @@
+import Modal from '../modal/modal.vue'
+import TabSwitcher from '../tab_switcher/tab_switcher.js'
+
+import Profile from './tabs/profile.vue'
+import Security from './tabs/security.vue'
+import Notifications from './tabs/notifications.vue'
+import DataImportExport from './tabs/data_import_export.vue'
+import MutesAndBlocks from './tabs/mutes_and_blocks.vue'
+
+const SettingsModal = {
+  components: {
+    Modal,
+    TabSwitcher,
+    Profile,
+    Security,
+    Notifications,
+    DataImportExport,
+    MutesAndBlocks
+  },
+  data () {
+    return {
+      resettingForm: false
+    }
+  },
+  computed: {
+    isLoggedIn () {
+      return !!this.$store.state.users.currentUser
+    },
+    modalActivated () {
+      return this.$store.state.interface.settingsModalState !== 'hidden'
+    }
+  },
+  watch: {
+  },
+  methods: {
+  }
+}
+
+export default SettingsModal
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
new file mode 100644
index 00000000..8cea52d2
--- /dev/null
+++ b/src/components/settings_modal/settings_modal.scss
@@ -0,0 +1,59 @@
+@import '../../_variables.scss';
+.settings-modal {
+  .settings_tab-switcher {
+    height: 100%;
+  }
+  .settings-modal-panel {
+    width: 1000px;
+    max-width: 90vw;
+    height: 90vh;
+  }
+  .panel-body {
+    overflow-y: hidden;
+  }
+  .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;
+    }
+  }
+}
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
new file mode 100644
index 00000000..9e35d3f6
--- /dev/null
+++ b/src/components/settings_modal/settings_modal.vue
@@ -0,0 +1,31 @@
+<template>
+<Modal
+  v-if="isLoggedIn && !resettingForm"
+  :is-open="modalActivated"
+  class="settings-modal"
+  >
+  <div class="settings-modal-panel panel">
+    <div class="panel-heading">
+      {{ $t('settings.settings') }}
+    </div>
+    <div class="panel-body">
+      <tab-switcher
+        class="settings_tab-switcher"
+        :sideTabBar="true"
+        :scrollableTabs="true"
+        ref="tabSwitcher"
+        >
+        <div :label="$t('settings.profile_tab')"><Profile /></div>
+        <div :label="$t('settings.security_tab')"><Security /></div>
+        <div :label="$t('settings.notifications')"><Notifications /></div>
+        <div :label="$t('settings.data_import_export_tab')"><DataImportExport /></div>
+        <div :label="$t('settings.mutes_and_blocks')"><MutesAndBlocks /></div>
+      </tab-switcher>
+    </div>
+  </div>
+</Modal>
+</template>
+
+<script src="./settings_modal.js"></script>
+
+<style src="./settings_modal.scss" lang="scss"></style>
diff --git a/src/components/settings_modal/tabs/data_import_export.js b/src/components/settings_modal/tabs/data_import_export.js
new file mode 100644
index 00000000..f68d12e9
--- /dev/null
+++ b/src/components/settings_modal/tabs/data_import_export.js
@@ -0,0 +1,65 @@
+import Importer from '../../importer/importer.vue'
+import Exporter from '../../exporter/exporter.vue'
+import Checkbox from '../../checkbox/checkbox.vue'
+
+const DataImportExport = {
+  data () {
+    return {
+      activeTab: 'profile',
+      newDomainToMute: ''
+    }
+  },
+  created () {
+    this.$store.dispatch('fetchTokens')
+  },
+  components: {
+    Importer,
+    Exporter,
+    Checkbox
+  },
+  computed: {
+    user () {
+      return this.$store.state.users.currentUser
+    }
+  },
+  methods: {
+    getFollowsContent () {
+      return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
+        .then(this.generateExportableUsersContent)
+    },
+    getBlocksContent () {
+      return this.$store.state.api.backendInteractor.fetchBlocks()
+        .then(this.generateExportableUsersContent)
+    },
+    importFollows (file) {
+      return this.$store.state.api.backendInteractor.importFollows({ file })
+        .then((status) => {
+          if (!status) {
+            throw new Error('failed')
+          }
+        })
+    },
+    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
+          return user.screen_name + '@' + location.hostname
+        }
+        return user.screen_name
+      }).join('\n')
+    }
+  }
+}
+
+export default DataImportExport
diff --git a/src/components/settings_modal/tabs/data_import_export.vue b/src/components/settings_modal/tabs/data_import_export.vue
new file mode 100644
index 00000000..464df6d3
--- /dev/null
+++ b/src/components/settings_modal/tabs/data_import_export.vue
@@ -0,0 +1,43 @@
+<template>
+<div
+  :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">
+    <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>
+</template>
+
+<script src="./data_import_export.js"></script>
+<!-- <style lang="scss" src="./profile.scss"></style> -->
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks.js b/src/components/settings_modal/tabs/mutes_and_blocks.js
new file mode 100644
index 00000000..51895ddc
--- /dev/null
+++ b/src/components/settings_modal/tabs/mutes_and_blocks.js
@@ -0,0 +1,124 @@
+import get from 'lodash/get'
+import map from 'lodash/map'
+import reject from 'lodash/reject'
+import Autosuggest from '../../autosuggest/autosuggest.vue'
+import TabSwitcher from '../../tab_switcher/tab_switcher.js'
+import BlockCard from '../../block_card/block_card.vue'
+import MuteCard from '../../mute_card/mute_card.vue'
+import DomainMuteCard from '../../domain_mute_card/domain_mute_card.vue'
+import SelectableList from '../../selectable_list/selectable_list.vue'
+import ProgressButton from '../../progress_button/progress_button.vue'
+import withSubscription from '../../../hocs/with_subscription/with_subscription'
+import Checkbox from '../../checkbox/checkbox.vue'
+
+const BlockList = withSubscription({
+  fetch: (props, $store) => $store.dispatch('fetchBlocks'),
+  select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
+  childPropName: 'items'
+})(SelectableList)
+
+const MuteList = withSubscription({
+  fetch: (props, $store) => $store.dispatch('fetchMutes'),
+  select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
+  childPropName: 'items'
+})(SelectableList)
+
+const DomainMuteList = withSubscription({
+  fetch: (props, $store) => $store.dispatch('fetchDomainMutes'),
+  select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []),
+  childPropName: 'items'
+})(SelectableList)
+
+const MutesAndBlocks = {
+  data () {
+    return {
+      activeTab: 'profile',
+      newDomainToMute: ''
+    }
+  },
+  created () {
+    this.$store.dispatch('fetchTokens')
+  },
+  components: {
+    TabSwitcher,
+    BlockList,
+    MuteList,
+    DomainMuteList,
+    BlockCard,
+    MuteCard,
+    DomainMuteCard,
+    ProgressButton,
+    Autosuggest,
+    Checkbox
+  },
+  methods: {
+    importFollows (file) {
+      return this.$store.state.api.backendInteractor.importFollows({ file })
+        .then((status) => {
+          if (!status) {
+            throw new Error('failed')
+          }
+        })
+    },
+    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
+          return user.screen_name + '@' + location.hostname
+        }
+        return user.screen_name
+      }).join('\n')
+    },
+    activateTab (tabName) {
+      this.activeTab = tabName
+    },
+    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)
+    },
+    unmuteDomains (domains) {
+      return this.$store.dispatch('unmuteDomains', domains)
+    },
+    muteDomain () {
+      return this.$store.dispatch('muteDomain', this.newDomainToMute)
+        .then(() => { this.newDomainToMute = '' })
+    }
+  }
+}
+
+export default MutesAndBlocks
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks.vue b/src/components/settings_modal/tabs/mutes_and_blocks.vue
new file mode 100644
index 00000000..3aff47a0
--- /dev/null
+++ b/src/components/settings_modal/tabs/mutes_and_blocks.vue
@@ -0,0 +1,173 @@
+<template>
+  <tab-switcher>
+    <div :label="$t('settings.blocks_tab')">
+      <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')">
+      <tab-switcher>
+        <div label="Users">
+          <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>
+
+        <div :label="$t('settings.domain_mutes')">
+          <div class="profile-edit-domain-mute-form">
+            <input
+              v-model="newDomainToMute"
+              :placeholder="$t('settings.type_domains_to_mute')"
+              type="text"
+              @keyup.enter="muteDomain"
+              >
+            <ProgressButton
+              class="btn btn-default"
+              :click="muteDomain"
+              >
+              {{ $t('domain_mute_card.mute') }}
+              <template slot="progress">
+                {{ $t('domain_mute_card.mute_progress') }}
+              </template>
+            </ProgressButton>
+          </div>
+          <DomainMuteList
+            :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="() => unmuteDomains(selected)"
+                  >
+                  {{ $t('domain_mute_card.unmute') }}
+                  <template slot="progress">
+                    {{ $t('domain_mute_card.unmute_progress') }}
+                  </template>
+                </ProgressButton>
+              </div>
+            </template>
+            <template
+              slot="item"
+              slot-scope="{item}"
+              >
+              <DomainMuteCard :domain="item" />
+            </template>
+            <template slot="empty">
+              {{ $t('settings.no_mutes') }}
+            </template>
+          </DomainMuteList>
+        </div>
+      </tab-switcher>
+    </div>
+  </tab-switcher>
+</template>
+
+<script src="./mutes_and_blocks.js"></script>
+<!-- <style lang="scss" src="./profile.scss"></style> -->
diff --git a/src/components/settings_modal/tabs/notifications.js b/src/components/settings_modal/tabs/notifications.js
new file mode 100644
index 00000000..0a870b3f
--- /dev/null
+++ b/src/components/settings_modal/tabs/notifications.js
@@ -0,0 +1,27 @@
+import Checkbox from '../../checkbox/checkbox.vue'
+
+const Notifications = {
+  data () {
+    return {
+      activeTab: 'profile',
+      notificationSettings: this.$store.state.users.currentUser.notification_settings,
+      newDomainToMute: ''
+    }
+  },
+  components: {
+    Checkbox
+  },
+  computed: {
+    user () {
+      return this.$store.state.users.currentUser
+    }
+  },
+  methods: {
+    updateNotificationSettings () {
+      this.$store.state.api.backendInteractor
+        .updateNotificationSettings({ settings: this.notificationSettings })
+    }
+  }
+}
+
+export default Notifications
diff --git a/src/components/settings_modal/tabs/notifications.vue b/src/components/settings_modal/tabs/notifications.vue
new file mode 100644
index 00000000..f9a7c17b
--- /dev/null
+++ b/src/components/settings_modal/tabs/notifications.vue
@@ -0,0 +1,42 @@
+<template>
+<div :label="$t('settings.notifications')">
+  <div class="setting-item">
+    <div class="select-multiple">
+      <span class="label">{{ $t('settings.notification_setting') }}</span>
+      <ul class="option-list">
+        <li>
+          <Checkbox v-model="notificationSettings.follows">
+            {{ $t('settings.notification_setting_follows') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="notificationSettings.followers">
+            {{ $t('settings.notification_setting_followers') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="notificationSettings.non_follows">
+            {{ $t('settings.notification_setting_non_follows') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="notificationSettings.non_followers">
+            {{ $t('settings.notification_setting_non_followers') }}
+          </Checkbox>
+        </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>
+</template>
+
+<script src="./notifications.js"></script>
+<!-- <style lang="scss" src="./profile.scss"></style> -->
diff --git a/src/components/settings_modal/tabs/profile.js b/src/components/settings_modal/tabs/profile.js
new file mode 100644
index 00000000..18c44024
--- /dev/null
+++ b/src/components/settings_modal/tabs/profile.js
@@ -0,0 +1,179 @@
+import unescape from 'lodash/unescape'
+import ImageCropper from '../../image_cropper/image_cropper.vue'
+import ScopeSelector from '../../scope_selector/scope_selector.vue'
+import fileSizeFormatService from '../../../services/file_size_format/file_size_format.js'
+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 Checkbox from '../../checkbox/checkbox.vue'
+
+const ProfileTab = {
+  data () {
+    return {
+      newName: this.$store.state.users.currentUser.name,
+      newBio: unescape(this.$store.state.users.currentUser.description),
+      newLocked: this.$store.state.users.currentUser.locked,
+      newNoRichText: this.$store.state.users.currentUser.no_rich_text,
+      newDefaultScope: this.$store.state.users.currentUser.default_scope,
+      hideFollows: this.$store.state.users.currentUser.hide_follows,
+      hideFollowers: this.$store.state.users.currentUser.hide_followers,
+      hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count,
+      hideFollowersCount: this.$store.state.users.currentUser.hide_followers_count,
+      showRole: this.$store.state.users.currentUser.show_role,
+      role: this.$store.state.users.currentUser.role,
+      discoverable: this.$store.state.users.currentUser.discoverable,
+      allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
+      pickAvatarBtnVisible: true,
+      bannerUploading: false,
+      backgroundUploading: false,
+      banner: null,
+      bannerPreview: null,
+      background: null,
+      backgroundPreview: null,
+      bannerUploadError: null,
+      backgroundUploadError: null,
+    }
+  },
+  components: {
+    ScopeSelector,
+    ImageCropper,
+    EmojiInput,
+    Autosuggest,
+    ProgressButton,
+    Checkbox
+  },
+  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
+      ] })
+    }
+  },
+  methods: {
+    updateProfile () {
+      this.$store.state.api.backendInteractor
+        .updateProfile({
+          params: {
+            note: this.newBio,
+            locked: this.newLocked,
+            // Backend notation.
+            /* eslint-disable camelcase */
+            display_name: this.newName,
+            default_scope: this.newDefaultScope,
+            no_rich_text: this.newNoRichText,
+            hide_follows: this.hideFollows,
+            hide_followers: this.hideFollowers,
+            discoverable: this.discoverable,
+            allow_following_move: this.allowFollowingMove,
+            hide_follows_count: this.hideFollowsCount,
+            hide_followers_count: this.hideFollowersCount,
+            show_role: this.showRole
+            /* eslint-enable camelcase */
+          } }).then((user) => {
+          this.$store.commit('addNewUsers', [user])
+          this.$store.commit('setCurrentUser', user)
+        })
+    },
+    changeVis (visibility) {
+      this.newDefaultScope = visibility
+    },
+    uploadFile (slot, e) {
+      const file = e.target.files[0]
+      if (!file) { return }
+      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
+            }
+          )
+        ].join(' ')
+        return
+      }
+      // eslint-disable-next-line no-undef
+      const reader = new FileReader()
+      reader.onload = ({ target }) => {
+        const img = target.result
+        this[slot + 'Preview'] = img
+        this[slot] = file
+      }
+      reader.readAsDataURL(file)
+    },
+    submitAvatar (cropper, 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))
+            })
+        }
+
+        if (cropper) {
+          cropper.getCroppedCanvas().toBlob(updateAvatar, file.type)
+        } else {
+          updateAvatar(file)
+        }
+      })
+    },
+    submitBanner () {
+      if (!this.bannerPreview) { return }
+
+      this.bannerUploading = true
+      this.$store.state.api.backendInteractor.updateBanner({ banner: this.banner })
+        .then((user) => {
+          this.$store.commit('addNewUsers', [user])
+          this.$store.commit('setCurrentUser', user)
+          this.bannerPreview = null
+        })
+        .catch((err) => {
+          this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message
+        })
+        .then(() => { this.bannerUploading = false })
+    },
+    submitBg () {
+      if (!this.backgroundPreview) { return }
+      let background = this.background
+      this.backgroundUploading = true
+      this.$store.state.api.backendInteractor.updateBg({ background }).then((data) => {
+        if (!data.error) {
+          this.$store.commit('addNewUsers', [data])
+          this.$store.commit('setCurrentUser', data)
+          this.backgroundPreview = null
+        } else {
+          this.backgroundUploadError = this.$t('upload.error.base') + data.error
+        }
+        this.backgroundUploading = false
+      })
+    }
+  }
+}
+
+export default ProfileTab
diff --git a/src/components/settings_modal/tabs/profile.scss b/src/components/settings_modal/tabs/profile.scss
new file mode 100644
index 00000000..4aab81eb
--- /dev/null
+++ b/src/components/settings_modal/tabs/profile.scss
@@ -0,0 +1,82 @@
+@import '../../../_variables.scss';
+.profile-tab {
+  .bio {
+    margin: 0;
+  }
+
+  .visibility-tray {
+    padding-top: 5px;
+  }
+
+  input[type=file] {
+    padding: 5px;
+    height: auto;
+  }
+
+  .banner {
+    max-width: 100%;
+  }
+
+  .uploading {
+    font-size: 1.5em;
+    margin: 0.25em;
+  }
+
+  .name-changer {
+    width: 100%;
+  }
+
+  .bg {
+    max-width: 100%;
+  }
+
+  .current-avatar {
+    display: block;
+    width: 150px;
+    height: 150px;
+    border-radius: $fallback--avatarRadius;
+    border-radius: var(--avatarRadius, $fallback--avatarRadius);
+  }
+
+  .oauth-tokens {
+    width: 100%;
+
+    th {
+      text-align: left;
+    }
+
+    .actions {
+      text-align: right;
+    }
+  }
+
+  &-usersearch-wrapper {
+    padding: 1em;
+  }
+
+  &-bulk-actions {
+    text-align: right;
+    padding: 0 1em;
+    min-height: 28px;
+
+    button {
+      width: 10em;
+    }
+  }
+
+  &-domain-mute-form {
+    padding: 1em;
+    display: flex;
+    flex-direction: column;
+
+    button {
+      align-self: flex-end;
+      margin-top: 1em;
+      width: 10em;
+    }
+  }
+
+  .setting-subitem {
+    margin-left: 1.75em;
+  }
+}
diff --git a/src/components/settings_modal/tabs/profile.vue b/src/components/settings_modal/tabs/profile.vue
new file mode 100644
index 00000000..335fc12e
--- /dev/null
+++ b/src/components/settings_modal/tabs/profile.vue
@@ -0,0 +1,213 @@
+<template>
+<div class="profile-tab">
+  <div class="setting-item">
+    <h2>{{ $t('settings.name_bio') }}</h2>
+    <p>{{ $t('settings.name') }}</p>
+    <EmojiInput
+      v-model="newName"
+      enable-emoji-picker
+      :suggest="emojiSuggestor"
+      >
+      <input
+        id="username"
+        v-model="newName"
+        classname="name-changer"
+        >
+    </EmojiInput>
+    <p>{{ $t('settings.bio') }}</p>
+    <EmojiInput
+      v-model="newBio"
+      enable-emoji-picker
+      :suggest="emojiUserSuggestor"
+      >
+      <textarea
+        v-model="newBio"
+        classname="bio"
+        />
+    </EmojiInput>
+    <p>
+      <Checkbox v-model="newLocked">
+        {{ $t('settings.lock_account_description') }}
+      </Checkbox>
+    </p>
+    <div>
+      <label for="default-vis">{{ $t('settings.default_vis') }}</label>
+      <div
+        id="default-vis"
+        class="visibility-tray"
+        >
+        <scope-selector
+          :show-all="true"
+          :user-default="newDefaultScope"
+          :initial-scope="newDefaultScope"
+          :on-scope-change="changeVis"
+          />
+      </div>
+    </div>
+    <p>
+      <Checkbox v-model="newNoRichText">
+        {{ $t('settings.no_rich_text_description') }}
+      </Checkbox>
+    </p>
+    <p>
+      <Checkbox v-model="hideFollows">
+        {{ $t('settings.hide_follows_description') }}
+      </Checkbox>
+    </p>
+    <p class="setting-subitem">
+      <Checkbox
+        v-model="hideFollowsCount"
+        :disabled="!hideFollows"
+        >
+        {{ $t('settings.hide_follows_count_description') }}
+      </Checkbox>
+    </p>
+    <p>
+      <Checkbox v-model="hideFollowers">
+        {{ $t('settings.hide_followers_description') }}
+      </Checkbox>
+    </p>
+    <p class="setting-subitem">
+      <Checkbox
+        v-model="hideFollowersCount"
+        :disabled="!hideFollowers"
+        >
+        {{ $t('settings.hide_followers_count_description') }}
+      </Checkbox>
+    </p>
+    <p>
+      <Checkbox v-model="allowFollowingMove">
+        {{ $t('settings.allow_following_move') }}
+      </Checkbox>
+    </p>
+    <p v-if="role === 'admin' || role === 'moderator'">
+      <Checkbox v-model="showRole">
+        <template v-if="role === 'admin'">
+          {{ $t('settings.show_admin_badge') }}
+        </template>
+        <template v-if="role === 'moderator'">
+          {{ $t('settings.show_moderator_badge') }}
+        </template>
+      </Checkbox>
+    </p>
+    <p>
+      <Checkbox v-model="discoverable">
+        {{ $t('settings.discoverable') }}
+      </Checkbox>
+    </p>
+    <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
+      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
+      v-if="bannerPreview"
+      class="banner"
+      :src="bannerPreview"
+      >
+    <div>
+      <input
+        type="file"
+        @change="uploadFile('banner', $event)"
+        >
+    </div>
+    <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')"
+        />
+    </div>
+  </div>
+  <div class="setting-item">
+    <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)"
+        >
+    </div>
+    <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')"
+        />
+    </div>
+  </div>
+</div>
+</template>
+
+<script src="./profile.js"></script>
+<style lang="scss" src="./profile.scss"></style>
diff --git a/src/components/settings_modal/tabs/security.js b/src/components/settings_modal/tabs/security.js
new file mode 100644
index 00000000..cc791b7a
--- /dev/null
+++ b/src/components/settings_modal/tabs/security.js
@@ -0,0 +1,106 @@
+import ProgressButton from '../../progress_button/progress_button.vue'
+import Checkbox from '../../checkbox/checkbox.vue'
+import Mfa from '../../user_settings/mfa.vue'
+
+const Security = {
+  data () {
+    return {
+      newEmail: '',
+      changeEmailError: false,
+      changeEmailPassword: '',
+      changedEmail: false,
+      deletingAccount: false,
+      deleteAccountConfirmPasswordInput: '',
+      deleteAccountError: false,
+      changePasswordInputs: [ '', '', '' ],
+      changedPassword: false,
+      changePasswordError: false
+    }
+  },
+  created () {
+    this.$store.dispatch('fetchTokens')
+  },
+  components: {
+    ProgressButton,
+    Mfa,
+    Checkbox
+  },
+  computed: {
+    user () {
+      return this.$store.state.users.currentUser
+    },
+    pleromaBackend () {
+      return this.$store.state.instance.pleromaBackend
+    },
+    oauthTokens () {
+      return this.$store.state.oauthTokens.tokens.map(oauthToken => {
+        return {
+          id: oauthToken.id,
+          appName: oauthToken.app_name,
+          validUntil: new Date(oauthToken.valid_until).toLocaleDateString()
+        }
+      })
+    }
+  },
+  methods: {
+    confirmDelete () {
+      this.deletingAccount = true
+    },
+    deleteAccount () {
+      this.$store.state.api.backendInteractor.deleteAccount({ password: this.deleteAccountConfirmPasswordInput })
+        .then((res) => {
+          if (res.status === 'success') {
+            this.$store.dispatch('logout')
+            this.$router.push({ name: 'root' })
+          } else {
+            this.deleteAccountError = res.error
+          }
+        })
+    },
+    changePassword () {
+      const params = {
+        password: this.changePasswordInputs[0],
+        newPassword: this.changePasswordInputs[1],
+        newPasswordConfirmation: this.changePasswordInputs[2]
+      }
+      this.$store.state.api.backendInteractor.changePassword(params)
+        .then((res) => {
+          if (res.status === 'success') {
+            this.changedPassword = true
+            this.changePasswordError = false
+            this.logout()
+          } else {
+            this.changedPassword = false
+            this.changePasswordError = res.error
+          }
+        })
+    },
+    changeEmail () {
+      const params = {
+        email: this.newEmail,
+        password: this.changeEmailPassword
+      }
+      this.$store.state.api.backendInteractor.changeEmail(params)
+        .then((res) => {
+          if (res.status === 'success') {
+            this.changedEmail = true
+            this.changeEmailError = false
+          } else {
+            this.changedEmail = false
+            this.changeEmailError = res.error
+          }
+        })
+    },
+    logout () {
+      this.$store.dispatch('logout')
+      this.$router.replace('/')
+    },
+    revokeToken (id) {
+      if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) {
+        this.$store.dispatch('revokeToken', id)
+      }
+    }
+  }
+}
+
+export default Security
diff --git a/src/components/settings_modal/tabs/security.vue b/src/components/settings_modal/tabs/security.vue
new file mode 100644
index 00000000..603c9a04
--- /dev/null
+++ b/src/components/settings_modal/tabs/security.vue
@@ -0,0 +1,143 @@
+<template>
+<div :label="$t('settings.security_tab')">
+  <div class="setting-item">
+    <h2>{{ $t('settings.change_email') }}</h2>
+    <div>
+      <p>{{ $t('settings.new_email') }}</p>
+      <input
+        v-model="newEmail"
+        type="email"
+        autocomplete="email"
+        >
+    </div>
+    <div>
+      <p>{{ $t('settings.current_password') }}</p>
+      <input
+        v-model="changeEmailPassword"
+        type="password"
+        autocomplete="current-password"
+        >
+    </div>
+    <button
+      class="btn btn-default"
+      @click="changeEmail"
+      >
+      {{ $t('general.submit') }}
+    </button>
+    <p v-if="changedEmail">
+      {{ $t('settings.changed_email') }}
+    </p>
+    <template v-if="changeEmailError !== false">
+      <p>{{ $t('settings.change_email_error') }}</p>
+      <p>{{ changeEmailError }}</p>
+    </template>
+  </div>
+
+  <div class="setting-item">
+    <h2>{{ $t('settings.change_password') }}</h2>
+    <div>
+      <p>{{ $t('settings.current_password') }}</p>
+      <input
+        v-model="changePasswordInputs[0]"
+        type="password"
+        >
+    </div>
+    <div>
+      <p>{{ $t('settings.new_password') }}</p>
+      <input
+        v-model="changePasswordInputs[1]"
+        type="password"
+        >
+    </div>
+    <div>
+      <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>
+  </div>
+
+  <div class="setting-item">
+    <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 />
+        </tr>
+      </thead>
+      <tbody>
+        <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>
+          </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>
+    <div v-if="deletingAccount">
+      <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
+      v-if="!deletingAccount"
+      class="btn btn-default"
+      @click="confirmDelete"
+      >
+      {{ $t('general.submit') }}
+    </button>
+  </div>
+</div>
+</template>
+
+<script src="./security.js"></script>
+<!-- <style lang="scss" src="./profile.scss"></style> -->
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index 008e1e95..97791de3 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -24,6 +24,11 @@ export default Vue.component('tab-switcher', {
       required: false,
       type: Boolean,
       default: false
+    },
+    sideTabBar: {
+      required: false,
+      type: Boolean,
+      default: false
     }
   },
   data () {
@@ -105,7 +110,7 @@ export default Vue.component('tab-switcher', {
     })
 
     return (
-      <div class="tab-switcher">
+      <div class={'tab-switcher ' + (this.sideTabBar ? 'side-tabs' : 'top-tabs')}>
         <div class="tabs">
           {tabs}
         </div>
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index df585faa..a443531e 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -2,7 +2,116 @@
 
 .tab-switcher {
   display: flex;
-  flex-direction: column;
+
+  &.top-tabs {
+    flex-direction: column;
+    > .tabs {
+      width: 100%;
+      overflow-y: hidden;
+      overflow-x: auto;
+      padding-top: 5px;
+      flex-direction: row;
+      &::after, &::before {
+        content: '';
+        flex: 1 1 auto;
+        border-bottom: 1px solid;
+        border-bottom-color: $fallback--border;
+        border-bottom-color: var(--border, $fallback--border);
+      }
+      .tab-wrapper {
+        height: 28px;
+
+        &:not(.active)::after {
+          left: 0;
+          right: 0;
+          bottom: 0;
+          border-bottom: 1px solid;
+          border-bottom-color: $fallback--border;
+          border-bottom-color: var(--border, $fallback--border);
+        }
+      }
+      .tab {
+        width: 100%;
+        min-width: 1px;
+        border-bottom-left-radius: 0;
+        border-bottom-right-radius: 0;
+        padding-bottom: 99px;
+        margin-bottom: 6px - 99px;
+      }
+    }
+    .contents.scrollable-tabs {
+      flex-basis: 0;
+    }
+  }
+
+  &.side-tabs {
+    flex-direction: row;
+    > .contents {
+      flex: 0 1 80%;
+    }
+    > .tabs {
+      flex: 1 0 auto;
+      overflow-y: auto;
+      overflow-x: hidden;
+      padding-top: 5px;
+      flex-direction: column;
+      &::after {
+        content: '';
+        flex: 1 1 auto;
+        border-right: 1px solid;
+        border-right-color: $fallback--border;
+        border-right-color: var(--border, $fallback--border);
+      }
+      .tab-wrapper {
+        min-width: 10em;
+        &:not(.active)::after {
+          top: 0;
+          right: 0;
+          bottom: 0;
+          border-right: 1px solid;
+          border-right-color: $fallback--border;
+          border-right-color: var(--border, $fallback--border);
+        }
+      }
+      .tab {
+        box-sizing: content-box;
+        width: 100%;
+        margin-bottom: 5px;
+        min-width: 10em;
+        min-width: 1px;
+        border-top-right-radius: 0;
+        border-bottom-right-radius: 0;
+        // padding-right: 200px;
+        // margin-right: 6px - 200px;
+        margin-left: 6px;
+      }
+
+      .tab-wrapper {
+        min-width: 10em;
+        &:not(.active)::after {
+          top: 0;
+          right: 0;
+          bottom: 0;
+          border-right: 1px solid;
+          border-right-color: $fallback--border;
+          border-right-color: var(--border, $fallback--border);
+        }
+      }
+      .tab {
+        box-sizing: content-box;
+        width: 100%;
+        margin-bottom: 5px;
+        min-width: 10em;
+        min-width: 1px;
+        border-top-right-radius: 0;
+        border-bottom-right-radius: 0;
+        // padding-right: 200px;
+        // margin-right: 6px - 200px;
+        margin-left: 6px;
+      }
+    }
+  }
+
 
   .contents {
     flex: 1 0 auto;
@@ -13,86 +122,65 @@
     }
 
     &.scrollable-tabs {
-      flex-basis: 0;
       overflow-y: auto;
     }
   }
+
+  .tab {
+    position: relative;
+    white-space: nowrap;
+
+    padding: 6px 1em;
+    color: $fallback--text;
+    color: var(--tabText, $fallback--text);
+    background-color: $fallback--fg;
+    background-color: var(--tab, $fallback--fg);
+
+    &:not(.active) {
+      z-index: 4;
+
+      &:hover {
+        z-index: 6;
+      }
+    }
+
+    &.active {
+      background: transparent;
+      z-index: 5;
+      color: $fallback--text;
+      color: var(--tabActiveText, $fallback--text);
+    }
+
+    img {
+      max-height: 26px;
+      vertical-align: top;
+      margin-top: -5px;
+    }
+  }
+
+
   .tabs {
     display: flex;
     position: relative;
-    width: 100%;
-    overflow-y: hidden;
-    overflow-x: auto;
-    padding-top: 5px;
     box-sizing: border-box;
 
     &::after, &::before {
       display: block;
-      content: '';
       flex: 1 1 auto;
-      border-bottom: 1px solid;
-      border-bottom-color: $fallback--border;
-      border-bottom-color: var(--border, $fallback--border);
     }
+  }
 
-    .tab-wrapper {
-      height: 28px;
-      position: relative;
-      display: flex;
-      flex: 0 0 auto;
-
-      .tab {
-        width: 100%;
-        min-width: 1px;
-        position: relative;
-        border-bottom-left-radius: 0;
-        border-bottom-right-radius: 0;
-        padding: 6px 1em;
-        padding-bottom: 99px;
-        margin-bottom: 6px - 99px;
-        white-space: nowrap;
-
-        color: $fallback--text;
-        color: var(--tabText, $fallback--text);
-        background-color: $fallback--fg;
-        background-color: var(--tab, $fallback--fg);
-
-        &:not(.active) {
-          z-index: 4;
-
-          &:hover {
-            z-index: 6;
-          }
-        }
-
-        &.active {
-          background: transparent;
-          z-index: 5;
-          color: $fallback--text;
-          color: var(--tabActiveText, $fallback--text);
-        }
-
-        img {
-          max-height: 26px;
-          vertical-align: top;
-          margin-top: -5px;
-        }
-      }
+  .tab-wrapper {
+    position: relative;
+    display: flex;
+    flex: 0 0 auto;
 
-      &:not(.active) {
-        &::after {
-          content: '';
-          position: absolute;
-          left: 0;
-          right: 0;
-          bottom: 0;
-          z-index: 7;
-          border-bottom: 1px solid;
-          border-bottom-color: $fallback--border;
-          border-bottom-color: var(--border, $fallback--border);
-        }
+    &:not(.active) {
+      &::after {
+        content: '';
+        position: absolute;
+        z-index: 7;
       }
     }
-
   }
 }
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index eca6f9b1..e07d4e56 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -1,25 +1,17 @@
-import unescape from 'lodash/unescape'
 import get from 'lodash/get'
 import map from 'lodash/map'
 import reject from 'lodash/reject'
+import Autosuggest from '../autosuggest/autosuggest.vue'
 import TabSwitcher from '../tab_switcher/tab_switcher.js'
-import ImageCropper from '../image_cropper/image_cropper.vue'
-import StyleSwitcher from '../style_switcher/style_switcher.vue'
-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 DomainMuteCard from '../domain_mute_card/domain_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 Checkbox from '../checkbox/checkbox.vue'
-import Mfa from './mfa.vue'
 
 const BlockList = withSubscription({
   fetch: (props, $store) => $store.dispatch('fetchBlocks'),
@@ -42,40 +34,7 @@ const DomainMuteList = withSubscription({
 const UserSettings = {
   data () {
     return {
-      newEmail: '',
-      newName: this.$store.state.users.currentUser.name,
-      newBio: unescape(this.$store.state.users.currentUser.description),
-      newLocked: this.$store.state.users.currentUser.locked,
-      newNoRichText: this.$store.state.users.currentUser.no_rich_text,
-      newDefaultScope: this.$store.state.users.currentUser.default_scope,
-      hideFollows: this.$store.state.users.currentUser.hide_follows,
-      hideFollowers: this.$store.state.users.currentUser.hide_followers,
-      hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count,
-      hideFollowersCount: this.$store.state.users.currentUser.hide_followers_count,
-      showRole: this.$store.state.users.currentUser.show_role,
-      role: this.$store.state.users.currentUser.role,
-      discoverable: this.$store.state.users.currentUser.discoverable,
-      allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
-      pickAvatarBtnVisible: true,
-      bannerUploading: false,
-      backgroundUploading: false,
-      banner: null,
-      bannerPreview: null,
-      background: null,
-      backgroundPreview: null,
-      bannerUploadError: null,
-      backgroundUploadError: null,
-      changeEmailError: false,
-      changeEmailPassword: '',
-      changedEmail: false,
-      deletingAccount: false,
-      deleteAccountConfirmPasswordInput: '',
-      deleteAccountError: false,
-      changePasswordInputs: [ '', '', '' ],
-      changedPassword: false,
-      changePasswordError: false,
       activeTab: 'profile',
-      notificationSettings: this.$store.state.users.currentUser.notification_settings,
       newDomainToMute: ''
     }
   },
@@ -83,176 +42,29 @@ const UserSettings = {
     this.$store.dispatch('fetchTokens')
   },
   components: {
-    StyleSwitcher,
-    ScopeSelector,
     TabSwitcher,
-    ImageCropper,
     BlockList,
     MuteList,
     DomainMuteList,
-    EmojiInput,
-    Autosuggest,
     BlockCard,
     MuteCard,
     DomainMuteCard,
     ProgressButton,
-    Importer,
-    Exporter,
-    Mfa,
+    Autosuggest,
     Checkbox
   },
   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
     },
-    minimalScopesMode () {
-      return this.$store.state.instance.minimalScopesMode
-    },
-    vis () {
-      return {
-        public: { selected: this.newDefaultScope === 'public' },
-        unlisted: { selected: this.newDefaultScope === 'unlisted' },
-        private: { selected: this.newDefaultScope === 'private' },
-        direct: { selected: this.newDefaultScope === 'direct' }
-      }
-    },
     currentSaveStateNotice () {
       return this.$store.state.interface.settings.currentSaveStateNotice
-    },
-    oauthTokens () {
-      return this.$store.state.oauthTokens.tokens.map(oauthToken => {
-        return {
-          id: oauthToken.id,
-          appName: oauthToken.app_name,
-          validUntil: new Date(oauthToken.valid_until).toLocaleDateString()
-        }
-      })
     }
   },
   methods: {
-    updateProfile () {
-      this.$store.state.api.backendInteractor
-        .updateProfile({
-          params: {
-            note: this.newBio,
-            locked: this.newLocked,
-            // Backend notation.
-            /* eslint-disable camelcase */
-            display_name: this.newName,
-            default_scope: this.newDefaultScope,
-            no_rich_text: this.newNoRichText,
-            hide_follows: this.hideFollows,
-            hide_followers: this.hideFollowers,
-            discoverable: this.discoverable,
-            allow_following_move: this.allowFollowingMove,
-            hide_follows_count: this.hideFollowsCount,
-            hide_followers_count: this.hideFollowersCount,
-            show_role: this.showRole
-            /* eslint-enable camelcase */
-          } }).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
-    },
-    uploadFile (slot, e) {
-      const file = e.target.files[0]
-      if (!file) { return }
-      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 })
-        return
-      }
-      // eslint-disable-next-line no-undef
-      const reader = new FileReader()
-      reader.onload = ({ target }) => {
-        const img = target.result
-        this[slot + 'Preview'] = img
-        this[slot] = file
-      }
-      reader.readAsDataURL(file)
-    },
-    submitAvatar (cropper, 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))
-            })
-        }
-
-        if (cropper) {
-          cropper.getCroppedCanvas().toBlob(updateAvatar, file.type)
-        } else {
-          updateAvatar(file)
-        }
-      })
-    },
-    clearUploadError (slot) {
-      this[slot + 'UploadError'] = null
-    },
-    submitBanner () {
-      if (!this.bannerPreview) { return }
-
-      this.bannerUploading = true
-      this.$store.state.api.backendInteractor.updateBanner({ banner: this.banner })
-        .then((user) => {
-          this.$store.commit('addNewUsers', [user])
-          this.$store.commit('setCurrentUser', user)
-          this.bannerPreview = null
-        })
-        .catch((err) => {
-          this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message
-        })
-        .then(() => { this.bannerUploading = false })
-    },
-    submitBg () {
-      if (!this.backgroundPreview) { return }
-      let background = this.background
-      this.backgroundUploading = true
-      this.$store.state.api.backendInteractor.updateBg({ background }).then((data) => {
-        if (!data.error) {
-          this.$store.commit('addNewUsers', [data])
-          this.$store.commit('setCurrentUser', data)
-          this.backgroundPreview = null
-        } else {
-          this.backgroundUploadError = this.$t('upload.error.base') + data.error
-        }
-        this.backgroundUploading = false
-      })
-    },
     importFollows (file) {
       return this.$store.state.api.backendInteractor.importFollows({ file })
         .then((status) => {
@@ -281,74 +93,9 @@ const UserSettings = {
         return user.screen_name
       }).join('\n')
     },
-    getFollowsContent () {
-      return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
-        .then(this.generateExportableUsersContent)
-    },
-    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 })
-        .then((res) => {
-          if (res.status === 'success') {
-            this.$store.dispatch('logout')
-            this.$router.push({ name: 'root' })
-          } else {
-            this.deleteAccountError = res.error
-          }
-        })
-    },
-    changePassword () {
-      const params = {
-        password: this.changePasswordInputs[0],
-        newPassword: this.changePasswordInputs[1],
-        newPasswordConfirmation: this.changePasswordInputs[2]
-      }
-      this.$store.state.api.backendInteractor.changePassword(params)
-        .then((res) => {
-          if (res.status === 'success') {
-            this.changedPassword = true
-            this.changePasswordError = false
-            this.logout()
-          } else {
-            this.changedPassword = false
-            this.changePasswordError = res.error
-          }
-        })
-    },
-    changeEmail () {
-      const params = {
-        email: this.newEmail,
-        password: this.changeEmailPassword
-      }
-      this.$store.state.api.backendInteractor.changeEmail(params)
-        .then((res) => {
-          if (res.status === 'success') {
-            this.changedEmail = true
-            this.changeEmailError = false
-          } else {
-            this.changedEmail = false
-            this.changeEmailError = res.error
-          }
-        })
-    },
     activateTab (tabName) {
       this.activeTab = tabName
     },
-    logout () {
-      this.$store.dispatch('logout')
-      this.$router.replace('/')
-    },
-    revokeToken (id) {
-      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)
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 8b2336b4..2a88714f 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -25,603 +25,6 @@
       </transition>
     </div>
     <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
-              v-model="newName"
-              enable-emoji-picker
-              :suggest="emojiSuggestor"
-            >
-              <input
-                id="username"
-                v-model="newName"
-                classname="name-changer"
-              >
-            </EmojiInput>
-            <p>{{ $t('settings.bio') }}</p>
-            <EmojiInput
-              v-model="newBio"
-              enable-emoji-picker
-              :suggest="emojiUserSuggestor"
-            >
-              <textarea
-                v-model="newBio"
-                classname="bio"
-              />
-            </EmojiInput>
-            <p>
-              <Checkbox v-model="newLocked">
-                {{ $t('settings.lock_account_description') }}
-              </Checkbox>
-            </p>
-            <div>
-              <label for="default-vis">{{ $t('settings.default_vis') }}</label>
-              <div
-                id="default-vis"
-                class="visibility-tray"
-              >
-                <scope-selector
-                  :show-all="true"
-                  :user-default="newDefaultScope"
-                  :initial-scope="newDefaultScope"
-                  :on-scope-change="changeVis"
-                />
-              </div>
-            </div>
-            <p>
-              <Checkbox v-model="newNoRichText">
-                {{ $t('settings.no_rich_text_description') }}
-              </Checkbox>
-            </p>
-            <p>
-              <Checkbox v-model="hideFollows">
-                {{ $t('settings.hide_follows_description') }}
-              </Checkbox>
-            </p>
-            <p class="setting-subitem">
-              <Checkbox
-                v-model="hideFollowsCount"
-                :disabled="!hideFollows"
-              >
-                {{ $t('settings.hide_follows_count_description') }}
-              </Checkbox>
-            </p>
-            <p>
-              <Checkbox v-model="hideFollowers">
-                {{ $t('settings.hide_followers_description') }}
-              </Checkbox>
-            </p>
-            <p class="setting-subitem">
-              <Checkbox
-                v-model="hideFollowersCount"
-                :disabled="!hideFollowers"
-              >
-                {{ $t('settings.hide_followers_count_description') }}
-              </Checkbox>
-            </p>
-            <p>
-              <Checkbox v-model="allowFollowingMove">
-                {{ $t('settings.allow_following_move') }}
-              </Checkbox>
-            </p>
-            <p v-if="role === 'admin' || role === 'moderator'">
-              <Checkbox v-model="showRole">
-                <template v-if="role === 'admin'">
-                  {{ $t('settings.show_admin_badge') }}
-                </template>
-                <template v-if="role === 'moderator'">
-                  {{ $t('settings.show_moderator_badge') }}
-                </template>
-              </Checkbox>
-            </p>
-            <p>
-              <Checkbox v-model="discoverable">
-                {{ $t('settings.discoverable') }}
-              </Checkbox>
-            </p>
-            <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
-              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
-              v-if="bannerPreview"
-              class="banner"
-              :src="bannerPreview"
-            >
-            <div>
-              <input
-                type="file"
-                @change="uploadFile('banner', $event)"
-              >
-            </div>
-            <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')"
-              />
-            </div>
-          </div>
-          <div class="setting-item">
-            <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)"
-              >
-            </div>
-            <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')"
-              />
-            </div>
-          </div>
-        </div>
-
-        <div :label="$t('settings.security_tab')">
-          <div class="setting-item">
-            <h2>{{ $t('settings.change_email') }}</h2>
-            <div>
-              <p>{{ $t('settings.new_email') }}</p>
-              <input
-                v-model="newEmail"
-                type="email"
-                autocomplete="email"
-              >
-            </div>
-            <div>
-              <p>{{ $t('settings.current_password') }}</p>
-              <input
-                v-model="changeEmailPassword"
-                type="password"
-                autocomplete="current-password"
-              >
-            </div>
-            <button
-              class="btn btn-default"
-              @click="changeEmail"
-            >
-              {{ $t('general.submit') }}
-            </button>
-            <p v-if="changedEmail">
-              {{ $t('settings.changed_email') }}
-            </p>
-            <template v-if="changeEmailError !== false">
-              <p>{{ $t('settings.change_email_error') }}</p>
-              <p>{{ changeEmailError }}</p>
-            </template>
-          </div>
-
-          <div class="setting-item">
-            <h2>{{ $t('settings.change_password') }}</h2>
-            <div>
-              <p>{{ $t('settings.current_password') }}</p>
-              <input
-                v-model="changePasswordInputs[0]"
-                type="password"
-              >
-            </div>
-            <div>
-              <p>{{ $t('settings.new_password') }}</p>
-              <input
-                v-model="changePasswordInputs[1]"
-                type="password"
-              >
-            </div>
-            <div>
-              <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>
-          </div>
-
-          <div class="setting-item">
-            <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 />
-                </tr>
-              </thead>
-              <tbody>
-                <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>
-                  </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>
-            <div v-if="deletingAccount">
-              <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
-              v-if="!deletingAccount"
-              class="btn btn-default"
-              @click="confirmDelete"
-            >
-              {{ $t('general.submit') }}
-            </button>
-          </div>
-        </div>
-
-        <div
-          v-if="pleromaBackend"
-          :label="$t('settings.notifications')"
-        >
-          <div class="setting-item">
-            <div class="select-multiple">
-              <span class="label">{{ $t('settings.notification_setting') }}</span>
-              <ul class="option-list">
-                <li>
-                  <Checkbox v-model="notificationSettings.follows">
-                    {{ $t('settings.notification_setting_follows') }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="notificationSettings.followers">
-                    {{ $t('settings.notification_setting_followers') }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="notificationSettings.non_follows">
-                    {{ $t('settings.notification_setting_non_follows') }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="notificationSettings.non_followers">
-                    {{ $t('settings.notification_setting_non_followers') }}
-                  </Checkbox>
-                </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>
-
-        <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">
-            <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')">
-          <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')">
-          <tab-switcher>
-            <div label="Users">
-              <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>
-
-            <div :label="$t('settings.domain_mutes')">
-              <div class="profile-edit-domain-mute-form">
-                <input
-                  v-model="newDomainToMute"
-                  :placeholder="$t('settings.type_domains_to_mute')"
-                  type="text"
-                  @keyup.enter="muteDomain"
-                >
-                <ProgressButton
-                  class="btn btn-default"
-                  :click="muteDomain"
-                >
-                  {{ $t('domain_mute_card.mute') }}
-                  <template slot="progress">
-                    {{ $t('domain_mute_card.mute_progress') }}
-                  </template>
-                </ProgressButton>
-              </div>
-              <DomainMuteList
-                :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="() => unmuteDomains(selected)"
-                    >
-                      {{ $t('domain_mute_card.unmute') }}
-                      <template slot="progress">
-                        {{ $t('domain_mute_card.unmute_progress') }}
-                      </template>
-                    </ProgressButton>
-                  </div>
-                </template>
-                <template
-                  slot="item"
-                  slot-scope="{item}"
-                >
-                  <DomainMuteCard :domain="item" />
-                </template>
-                <template slot="empty">
-                  {{ $t('settings.no_mutes') }}
-                </template>
-              </DomainMuteList>
-            </div>
-          </tab-switcher>
-        </div>
-      </tab-switcher>
     </div>
   </div>
 </template>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 37d9591c..312f7283 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -278,6 +278,7 @@
     "current_avatar": "Your current avatar",
     "current_password": "Current password",
     "current_profile_banner": "Your current profile banner",
+    "mutes_and_blocks": "Mutes and Blocks",
     "data_import_export_tab": "Data Import / Export",
     "default_vis": "Default visibility scope",
     "delete_account": "Delete Account",
diff --git a/src/modules/interface.js b/src/modules/interface.js
index 5b2762e5..e55b7290 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -1,6 +1,7 @@
 import { set, delete as del } from 'vue'
 
 const defaultState = {
+  settingsModalState: 'hidden',
   settings: {
     currentSaveStateNotice: null,
     noticeClearTimeout: null,
@@ -35,6 +36,24 @@ const interfaceMod = {
     },
     setMobileLayout (state, value) {
       state.mobileLayout = value
+    },
+    closeSettingsModal (state) {
+      state.settingsModalState = 'hidden'
+    },
+    togglePeekSettingsModal (state) {
+      switch (state.settingsModalState) {
+        case 'minimized':
+          state.settingsModalState = 'visible'
+          return
+        case 'visible':
+          state.settingsModalState = 'minimized'
+          return
+        default:
+          throw new Error('Illegal minimization state of settings modal')
+      }
+    },
+    openSettingsModal (state) {
+      state.settingsModalState = 'visible'
     }
   },
   actions: {
@@ -49,6 +68,15 @@ const interfaceMod = {
     },
     setMobileLayout ({ commit }, value) {
       commit('setMobileLayout', value)
+    },
+    closeSettingsModal ({ commit }) {
+      commit('closeSettingsModal')
+    },
+    openSettingsModal ({ commit }) {
+      commit('openSettingsModal')
+    },
+    togglePeekSettingsModal ({ commit }) {
+      commit('togglePeekSettingsModal')
     }
   }
 }
-- 
cgit v1.2.3-70-g09d2


From bc5005b3ddddeb47d5160a1b79d2edb39e887b4b Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Tue, 12 May 2020 13:59:52 -0500
Subject: Permit sidebar alignment with instance configuration option

---
 src/App.js              | 7 ++++++-
 src/App.vue             | 5 ++++-
 src/modules/instance.js | 1 +
 static/config.json      | 3 ++-
 4 files changed, 13 insertions(+), 3 deletions(-)

(limited to 'src/App.vue')

diff --git a/src/App.js b/src/App.js
index 61b5eec1..bbb41409 100644
--- a/src/App.js
+++ b/src/App.js
@@ -99,7 +99,12 @@ export default {
     },
     showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
     isMobileLayout () { return this.$store.state.interface.mobileLayout },
-    privateMode () { return this.$store.state.instance.private }
+    privateMode () { return this.$store.state.instance.private },
+    sidebarAlign () {
+      return {
+        'order': this.$store.state.instance.sidebarRight ? 99 : 0
+      }
+    }
   },
   methods: {
     scrollToTop () {
diff --git a/src/App.vue b/src/App.vue
index ff62fc51..7018a5a4 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -80,7 +80,10 @@
       id="content"
       class="container underlay"
     >
-      <div class="sidebar-flexer mobile-hidden">
+      <div
+        class="sidebar-flexer mobile-hidden"
+        :style="sidebarAlign"
+      >
         <div class="sidebar-bounds">
           <div class="sidebar-scroller">
             <div class="sidebar">
diff --git a/src/modules/instance.js b/src/modules/instance.js
index ffece311..869ceebd 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -36,6 +36,7 @@ const defaultState = {
   showFeaturesPanel: true,
   minimalScopesMode: false,
   greentext: false,
+  sidebarRight: false,
 
   // Nasty stuff
   pleromaBackend: true,
diff --git a/static/config.json b/static/config.json
index c8267869..a87ed853 100644
--- a/static/config.json
+++ b/static/config.json
@@ -19,5 +19,6 @@
   "noAttachmentLinks": false,
   "nsfwCensorImage": "",
   "showFeaturesPanel": true,
-  "minimalScopesMode": false
+  "minimalScopesMode": false,
+  "sidebarRight": false
 }
-- 
cgit v1.2.3-70-g09d2


From 0286e1024c9e9f9f1794a870bd7a0680aa29cee2 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 16:16:30 +0300
Subject: fix cursor on desktop, add modal link on mobile

---
 src/App.vue                                | 1 +
 src/components/side_drawer/side_drawer.js  | 5 ++++-
 src/components/side_drawer/side_drawer.vue | 7 +++++--
 3 files changed, 10 insertions(+), 3 deletions(-)

(limited to 'src/App.vue')

diff --git a/src/App.vue b/src/App.vue
index 40278cb4..7b9ad3dc 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -47,6 +47,7 @@
             @click.stop.native
           />
           <a
+            href="#"
             class="mobile-hidden"
             @click.stop="openSettingsModal"
           >
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 2181ecc7..e0d118a1 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -62,7 +62,10 @@ const SideDrawer = {
     },
     touchMove (e) {
       GestureService.updateSwipe(e, this.closeGesture)
-    }
+    },
+    openSettingsModal () {
+      this.$store.dispatch('openSettingsModal')
+    },
   }
 }
 
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 2958a386..aa39bc6f 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -122,9 +122,12 @@
           </router-link>
         </li>
         <li @click="toggleDrawer">
-          <router-link :to="{ name: 'settings' }">
+          <a
+            href="#"
+            @click.stop="openSettingsModal"
+          >
             <i class="button-icon icon-cog" /> {{ $t("settings.settings") }}
-          </router-link>
+          </a>
         </li>
         <li @click="toggleDrawer">
           <router-link :to="{ name: 'about'}">
-- 
cgit v1.2.3-70-g09d2

