From 6cede475bec01755d168586b4a2a2241a8bc18ab Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Fri, 8 Nov 2019 21:53:53 -0600
Subject: [PATCH 001/483] nav panel: add link to about page

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

diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 614fadf4..28589bb1 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -38,6 +38,11 @@
             {{ $t("nav.twkn") }}
           </router-link>
         </li>
+        <li>
+          <router-link :to="{ name: 'about' }">
+            {{ $t("nav.about") }}
+          </router-link>
+        </li>
       </ul>
     </div>
   </div>

From 5db77c839bcf2ec433c8618c5b2ef1ff850df613 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Fri, 8 Nov 2019 21:57:09 -0600
Subject: [PATCH 002/483] about page: fix hiding of instance-specific panel,
 flow ToS and ISP better

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

diff --git a/src/components/about/about.js b/src/components/about/about.js
index ae1cb182..92856b21 100644
--- a/src/components/about/about.js
+++ b/src/components/about/about.js
@@ -9,7 +9,12 @@ const About = {
     TermsOfServicePanel
   },
   computed: {
-    showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }
+    showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
+    showInstanceSpecificPanel () {
+      return this.$store.state.instance.showInstanceSpecificPanel &&
+        !this.$store.getters.mergedConfig.hideISP &&
+        this.$store.state.instance.instanceSpecificPanelContent
+    }
   }
 }
 
diff --git a/src/components/about/about.vue b/src/components/about/about.vue
index 62ae16ea..10dad4bb 100644
--- a/src/components/about/about.vue
+++ b/src/components/about/about.vue
@@ -1,8 +1,8 @@
 <template>
   <div class="sidebar">
-    <instance-specific-panel />
-    <features-panel v-if="showFeaturesPanel" />
+    <instance-specific-panel v-if="showInstanceSpecificPanel" />
     <terms-of-service-panel />
+    <features-panel v-if="showFeaturesPanel" />
   </div>
 </template>
 

From 20ccd93a176f911a43b3db3d595f3fdb3491934f Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Fri, 8 Nov 2019 23:21:07 -0600
Subject: [PATCH 003/483] about: add staff panel

---
 src/boot/after_store.js                    | 17 +++++++++++++++++
 src/components/about/about.js              |  4 +++-
 src/components/about/about.vue             |  1 +
 src/components/staff_panel/staff_panel.js  | 15 +++++++++++++++
 src/components/staff_panel/staff_panel.vue | 22 ++++++++++++++++++++++
 src/i18n/en.json                           |  3 +++
 6 files changed, 61 insertions(+), 1 deletion(-)
 create mode 100644 src/components/staff_panel/staff_panel.js
 create mode 100644 src/components/staff_panel/staff_panel.vue

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 80a55849..e96baaf0 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -184,6 +184,20 @@ const getAppSecret = async ({ store }) => {
     })
 }
 
+const resolveStaffAccounts = async ({ store, accounts }) => {
+  let nicknames = accounts.map(uri => uri.split('/').pop())
+  const backendInteractor = store.state.api.backendInteractor
+
+  nicknames = nicknames.map(id => {
+    console.log('resolving staff account:', id)
+    return backendInteractor.fetchUser({ id })
+  })
+
+  nicknames = await Promise.all(nicknames)
+
+  store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
+}
+
 const getNodeInfo = async ({ store }) => {
   try {
     const res = await window.fetch('/nodeinfo/2.0.json')
@@ -212,6 +226,9 @@ const getNodeInfo = async ({ store }) => {
       const frontendVersion = window.___pleromafe_commit_hash
       store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
       store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') })
+
+      const accounts = metadata.staffAccounts
+      await resolveStaffAccounts({ store, accounts })
     } else {
       throw (res)
     }
diff --git a/src/components/about/about.js b/src/components/about/about.js
index 92856b21..5c95c079 100644
--- a/src/components/about/about.js
+++ b/src/components/about/about.js
@@ -1,12 +1,14 @@
 import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue'
 import FeaturesPanel from '../features_panel/features_panel.vue'
 import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
+import StaffPanel from '../staff_panel/staff_panel.vue'
 
 const About = {
   components: {
     InstanceSpecificPanel,
     FeaturesPanel,
-    TermsOfServicePanel
+    TermsOfServicePanel,
+    StaffPanel
   },
   computed: {
     showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
diff --git a/src/components/about/about.vue b/src/components/about/about.vue
index 10dad4bb..ad520d11 100644
--- a/src/components/about/about.vue
+++ b/src/components/about/about.vue
@@ -1,6 +1,7 @@
 <template>
   <div class="sidebar">
     <instance-specific-panel v-if="showInstanceSpecificPanel" />
+    <staff-panel />
     <terms-of-service-panel />
     <features-panel v-if="showFeaturesPanel" />
   </div>
diff --git a/src/components/staff_panel/staff_panel.js b/src/components/staff_panel/staff_panel.js
new file mode 100644
index 00000000..b4d23079
--- /dev/null
+++ b/src/components/staff_panel/staff_panel.js
@@ -0,0 +1,15 @@
+import BasicUserCard from '../basic_user_card/basic_user_card.vue'
+
+const StaffPanel = {
+  components: {
+    BasicUserCard
+  },
+  computed: {
+    staffAccounts() {
+      return this.$store.state.instance.staffAccounts
+    }
+  }
+}
+
+export default StaffPanel
+
diff --git a/src/components/staff_panel/staff_panel.vue b/src/components/staff_panel/staff_panel.vue
new file mode 100644
index 00000000..a74872d2
--- /dev/null
+++ b/src/components/staff_panel/staff_panel.vue
@@ -0,0 +1,22 @@
+<template>
+  <div class="staff-panel">
+    <div class="panel panel-default base01-background">
+      <div class="panel-heading timeline-heading base02-background">
+        <div class="title">
+          {{ $t("about.staff") }}
+        </div>
+      </div>
+      <div class="panel-body">
+        <basic-user-card
+          v-for="user in staffAccounts"
+          :user="user"
+          v-bind:key="user.screen_name" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script src="./staff_panel.js" ></script>
+
+<style lang="scss">
+</style>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 483432ff..4cd66177 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1,4 +1,7 @@
 {
+  "about": {
+    "staff": "Staff"
+  },
   "chat": {
     "title": "Chat"
   },

From 90f764224d7518da08653db285c5343ca5d078ac Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Sat, 9 Nov 2019 00:09:32 -0600
Subject: [PATCH 004/483] about: add MRF transparency panel

---
 src/boot/after_store.js                       |  3 +
 src/components/about/about.js                 |  4 +-
 src/components/about/about.vue                |  1 +
 .../mrf_transparency_panel.js                 | 33 +++++++
 .../mrf_transparency_panel.vue                | 91 +++++++++++++++++++
 src/i18n/en.json                              | 18 +++-
 6 files changed, 148 insertions(+), 2 deletions(-)
 create mode 100644 src/components/mrf_transparency_panel/mrf_transparency_panel.js
 create mode 100644 src/components/mrf_transparency_panel/mrf_transparency_panel.vue

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index e96baaf0..bba3288c 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -227,6 +227,9 @@ const getNodeInfo = async ({ store }) => {
       store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
       store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') })
 
+      const federation = metadata.federation
+      store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
+
       const accounts = metadata.staffAccounts
       await resolveStaffAccounts({ store, accounts })
     } else {
diff --git a/src/components/about/about.js b/src/components/about/about.js
index 5c95c079..1df25845 100644
--- a/src/components/about/about.js
+++ b/src/components/about/about.js
@@ -2,13 +2,15 @@ import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_
 import FeaturesPanel from '../features_panel/features_panel.vue'
 import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
 import StaffPanel from '../staff_panel/staff_panel.vue'
+import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue'
 
 const About = {
   components: {
     InstanceSpecificPanel,
     FeaturesPanel,
     TermsOfServicePanel,
-    StaffPanel
+    StaffPanel,
+    MRFTransparencyPanel
   },
   computed: {
     showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
diff --git a/src/components/about/about.vue b/src/components/about/about.vue
index ad520d11..518f6184 100644
--- a/src/components/about/about.vue
+++ b/src/components/about/about.vue
@@ -3,6 +3,7 @@
     <instance-specific-panel v-if="showInstanceSpecificPanel" />
     <staff-panel />
     <terms-of-service-panel />
+    <MRFTransparencyPanel />
     <features-panel v-if="showFeaturesPanel" />
   </div>
 </template>
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
new file mode 100644
index 00000000..3791dd12
--- /dev/null
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
@@ -0,0 +1,33 @@
+const MRFTransparencyPanel = {
+  components: {
+  },
+  computed: {
+    federationPolicy() {
+      return this.$store.state.instance.federationPolicy
+    },
+    mrfPolicies() {
+      return this.$store.state.instance.federationPolicy.mrf_policies
+    },
+    acceptInstances() {
+      return this.$store.state.instance.federationPolicy.mrf_simple.accept
+    },
+    rejectInstances() {
+      return this.$store.state.instance.federationPolicy.mrf_simple.reject
+    },
+    quarantineInstances() {
+      return this.$store.state.instance.federationPolicy.quarantined_instances
+    },
+    ftlRemovalInstances() {
+      return this.$store.state.instance.federationPolicy.mrf_simple.federated_timeline_removal
+    },
+    mediaNsfwInstances() {
+      return this.$store.state.instance.federationPolicy.mrf_simple.media_nsfw
+    },
+    mediaRemovalInstances() {
+      return this.$store.state.instance.federationPolicy.mrf_simple.media_removal
+    }
+  }
+}
+
+export default MRFTransparencyPanel
+
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
new file mode 100644
index 00000000..f3507591
--- /dev/null
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
@@ -0,0 +1,91 @@
+<template>
+  <div class="mrf-transparency-panel" v-if="federationPolicy">
+    <div class="panel panel-default base01-background">
+      <div class="panel-heading timeline-heading base02-background">
+        <div class="title">
+          {{ $t("about.federation") }}
+        </div>
+      </div>
+      <div class="panel-body">
+        <div class="mrf-section">
+          <h2>{{ $t("about.mrf_policies") }}</h2>
+          <p>{{ $t("about.mrf_policies_desc") }}</p>
+
+          <ul>
+            <li v-for="policy in mrfPolicies" v-bind:key="policy" v-text="policy" />
+          </ul>
+
+          <h2>{{ $t("about.mrf_policy_simple") }}</h2>
+
+          <div v-if="acceptInstances.length">
+            <h4>{{ $t("about.mrf_policy_simple_accept") }}</h4>
+
+            <p>{{ $t("about.mrf_policy_simple_accept_desc") }}</p>
+
+            <ul>
+              <li v-for="instance in acceptInstances" v-bind:key="instance" v-text="instance" />
+            </ul>
+          </div>
+
+          <div v-if="rejectInstances.length">
+            <h4>{{ $t("about.mrf_policy_simple_reject") }}</h4>
+
+            <p>{{ $t("about.mrf_policy_simple_reject_desc") }}</p>
+
+            <ul>
+              <li v-for="instance in rejectInstances" v-bind:key="instance" v-text="instance" />
+            </ul>
+          </div>
+
+          <div v-if="quarantineInstances.length">
+            <h4>{{ $t("about.mrf_policy_simple_quarantine") }}</h4>
+
+            <p>{{ $t("about.mrf_policy_simple_quarantine_desc") }}</p>
+
+            <ul>
+              <li v-for="instance in quarantineInstances" v-bind:key="instance" v-text="instance" />
+            </ul>
+          </div>
+
+          <div v-if="ftlRemovalInstances.length">
+            <h4>{{ $t("about.mrf_policy_simple_ftl_removal") }}</h4>
+
+            <p>{{ $t("about.mrf_policy_simple_ftl_removal_desc") }}</p>
+
+            <ul>
+              <li v-for="instance in ftlRemovalInstances" v-bind:key="instance" v-text="instance" />
+            </ul>
+          </div>
+
+          <div v-if="mediaNsfwInstances.length">
+            <h4>{{ $t("about.mrf_policy_simple_media_nsfw") }}</h4>
+
+            <p>{{ $t("about.mrf_policy_simple_media_nsfw_desc") }}</p>
+
+            <ul>
+              <li v-for="instance in mediaNsfwInstances" v-bind:key="instance" v-text="instance" />
+            </ul>
+          </div>
+
+          <div v-if="mediaRemovalInstances.length">
+            <h4>{{ $t("about.mrf_policy_simple_media_removal") }}</h4>
+
+            <p>{{ $t("about.mrf_policy_simple_media_removal_desc") }}</p>
+
+            <ul>
+              <li v-for="instance in mediaRemovalInstances" v-bind:key="instance" v-text="instance" />
+            </ul>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script src="./mrf_transparency_panel.js"></script>
+
+<style lang="scss">
+.mrf-section {
+  margin: 1em;
+}
+</style>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 4cd66177..ead333c1 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1,6 +1,22 @@
 {
   "about": {
-    "staff": "Staff"
+    "staff": "Staff",
+    "federation": "Federation",
+    "mrf_policies": "Enabled MRF Policies",
+    "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance.  The following policies are enabled:",
+    "mrf_policy_simple": "Instance-specific Policies",
+    "mrf_policy_simple_accept": "Accept",
+    "mrf_policy_simple_accept_desc": "This instance only accepts messages from the following instances:",
+    "mrf_policy_simple_reject": "Reject",
+    "mrf_policy_simple_reject_desc": "This instance will not accept messages from the following instances:",
+    "mrf_policy_simple_quarantine": "Quarantine",
+    "mrf_policy_simple_quarantine_desc": "This instance will send only public posts to the following instances:",
+    "mrf_policy_simple_ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+    "mrf_policy_simple_ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
+    "mrf_policy_simple_media_removal": "Media Removal",
+    "mrf_policy_simple_media_removal_desc": "This instance removes media from posts on the following instances:",
+    "mrf_policy_simple_media_nsfw": "Media Force-set As Sensitive",
+    "mrf_policy_simple_media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
   },
   "chat": {
     "title": "Chat"

From 6dfe3cc911e748767368cc920cac2d91b4805992 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Sat, 9 Nov 2019 00:23:22 -0600
Subject: [PATCH 005/483] lint

---
 .../mrf_transparency_panel.js                 | 17 ++++---
 .../mrf_transparency_panel.vue                | 47 +++++++++++++++----
 src/components/staff_panel/staff_panel.js     |  3 +-
 src/components/staff_panel/staff_panel.vue    |  3 +-
 4 files changed, 50 insertions(+), 20 deletions(-)

diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
index 3791dd12..d4e583b0 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
@@ -2,32 +2,31 @@ const MRFTransparencyPanel = {
   components: {
   },
   computed: {
-    federationPolicy() {
+    federationPolicy () {
       return this.$store.state.instance.federationPolicy
     },
-    mrfPolicies() {
+    mrfPolicies () {
       return this.$store.state.instance.federationPolicy.mrf_policies
     },
-    acceptInstances() {
+    acceptInstances () {
       return this.$store.state.instance.federationPolicy.mrf_simple.accept
     },
-    rejectInstances() {
+    rejectInstances () {
       return this.$store.state.instance.federationPolicy.mrf_simple.reject
     },
-    quarantineInstances() {
+    quarantineInstances () {
       return this.$store.state.instance.federationPolicy.quarantined_instances
     },
-    ftlRemovalInstances() {
+    ftlRemovalInstances () {
       return this.$store.state.instance.federationPolicy.mrf_simple.federated_timeline_removal
     },
-    mediaNsfwInstances() {
+    mediaNsfwInstances () {
       return this.$store.state.instance.federationPolicy.mrf_simple.media_nsfw
     },
-    mediaRemovalInstances() {
+    mediaRemovalInstances () {
       return this.$store.state.instance.federationPolicy.mrf_simple.media_removal
     }
   }
 }
 
 export default MRFTransparencyPanel
-
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
index f3507591..2640d68c 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
@@ -1,5 +1,8 @@
 <template>
-  <div class="mrf-transparency-panel" v-if="federationPolicy">
+  <div
+    v-if="federationPolicy"
+    class="mrf-transparency-panel"
+  >
     <div class="panel panel-default base01-background">
       <div class="panel-heading timeline-heading base02-background">
         <div class="title">
@@ -12,7 +15,11 @@
           <p>{{ $t("about.mrf_policies_desc") }}</p>
 
           <ul>
-            <li v-for="policy in mrfPolicies" v-bind:key="policy" v-text="policy" />
+            <li
+              v-for="policy in mrfPolicies"
+              :key="policy"
+              v-text="policy"
+            />
           </ul>
 
           <h2>{{ $t("about.mrf_policy_simple") }}</h2>
@@ -23,7 +30,11 @@
             <p>{{ $t("about.mrf_policy_simple_accept_desc") }}</p>
 
             <ul>
-              <li v-for="instance in acceptInstances" v-bind:key="instance" v-text="instance" />
+              <li
+                v-for="instance in acceptInstances"
+                :key="instance"
+                v-text="instance"
+              />
             </ul>
           </div>
 
@@ -33,7 +44,11 @@
             <p>{{ $t("about.mrf_policy_simple_reject_desc") }}</p>
 
             <ul>
-              <li v-for="instance in rejectInstances" v-bind:key="instance" v-text="instance" />
+              <li
+                v-for="instance in rejectInstances"
+                :key="instance"
+                v-text="instance"
+              />
             </ul>
           </div>
 
@@ -43,7 +58,11 @@
             <p>{{ $t("about.mrf_policy_simple_quarantine_desc") }}</p>
 
             <ul>
-              <li v-for="instance in quarantineInstances" v-bind:key="instance" v-text="instance" />
+              <li
+                v-for="instance in quarantineInstances"
+                :key="instance"
+                v-text="instance"
+              />
             </ul>
           </div>
 
@@ -53,7 +72,11 @@
             <p>{{ $t("about.mrf_policy_simple_ftl_removal_desc") }}</p>
 
             <ul>
-              <li v-for="instance in ftlRemovalInstances" v-bind:key="instance" v-text="instance" />
+              <li
+                v-for="instance in ftlRemovalInstances"
+                :key="instance"
+                v-text="instance"
+              />
             </ul>
           </div>
 
@@ -63,7 +86,11 @@
             <p>{{ $t("about.mrf_policy_simple_media_nsfw_desc") }}</p>
 
             <ul>
-              <li v-for="instance in mediaNsfwInstances" v-bind:key="instance" v-text="instance" />
+              <li
+                v-for="instance in mediaNsfwInstances"
+                :key="instance"
+                v-text="instance"
+              />
             </ul>
           </div>
 
@@ -73,7 +100,11 @@
             <p>{{ $t("about.mrf_policy_simple_media_removal_desc") }}</p>
 
             <ul>
-              <li v-for="instance in mediaRemovalInstances" v-bind:key="instance" v-text="instance" />
+              <li
+                v-for="instance in mediaRemovalInstances"
+                :key="instance"
+                v-text="instance"
+              />
             </ul>
           </div>
         </div>
diff --git a/src/components/staff_panel/staff_panel.js b/src/components/staff_panel/staff_panel.js
index b4d23079..93e950ad 100644
--- a/src/components/staff_panel/staff_panel.js
+++ b/src/components/staff_panel/staff_panel.js
@@ -5,11 +5,10 @@ const StaffPanel = {
     BasicUserCard
   },
   computed: {
-    staffAccounts() {
+    staffAccounts () {
       return this.$store.state.instance.staffAccounts
     }
   }
 }
 
 export default StaffPanel
-
diff --git a/src/components/staff_panel/staff_panel.vue b/src/components/staff_panel/staff_panel.vue
index a74872d2..1d13003d 100644
--- a/src/components/staff_panel/staff_panel.vue
+++ b/src/components/staff_panel/staff_panel.vue
@@ -9,8 +9,9 @@
       <div class="panel-body">
         <basic-user-card
           v-for="user in staffAccounts"
+          :key="user.screen_name"
           :user="user"
-          v-bind:key="user.screen_name" />
+        />
       </div>
     </div>
   </div>

From a4ae956a62f7d82db9da91c4f6516219599954f5 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Sat, 9 Nov 2019 00:34:05 -0600
Subject: [PATCH 006/483] boot: cleanup resolveStaffAccounts

---
 src/boot/after_store.js | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index bba3288c..226b67d8 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -185,14 +185,9 @@ const getAppSecret = async ({ store }) => {
 }
 
 const resolveStaffAccounts = async ({ store, accounts }) => {
-  let nicknames = accounts.map(uri => uri.split('/').pop())
   const backendInteractor = store.state.api.backendInteractor
-
-  nicknames = nicknames.map(id => {
-    console.log('resolving staff account:', id)
-    return backendInteractor.fetchUser({ id })
-  })
-
+  let nicknames = accounts.map(uri => uri.split('/').pop())
+    .map(id => backendInteractor.fetchUser({ id }))
   nicknames = await Promise.all(nicknames)
 
   store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })

From 6d6b5f3dbbb2b0e2f47e93cbc3c19758726ef875 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Sat, 9 Nov 2019 11:16:19 -0600
Subject: [PATCH 007/483] mrf transparency panel: remove unneeded components{}

---
 src/components/mrf_transparency_panel/mrf_transparency_panel.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
index d4e583b0..28ecec38 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
@@ -1,6 +1,4 @@
 const MRFTransparencyPanel = {
-  components: {
-  },
   computed: {
     federationPolicy () {
       return this.$store.state.instance.federationPolicy

From c24f95b49840c1d3c8d0362f21abfbf2e227110f Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Sat, 9 Nov 2019 11:32:18 -0600
Subject: [PATCH 008/483] mrf transparency panel: refactor to use vuex mapState

---
 .../mrf_transparency_panel.js                 | 38 ++++++-------------
 1 file changed, 12 insertions(+), 26 deletions(-)

diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
index 28ecec38..20f8a08a 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
@@ -1,30 +1,16 @@
+import { mapState } from 'vuex'
+
 const MRFTransparencyPanel = {
-  computed: {
-    federationPolicy () {
-      return this.$store.state.instance.federationPolicy
-    },
-    mrfPolicies () {
-      return this.$store.state.instance.federationPolicy.mrf_policies
-    },
-    acceptInstances () {
-      return this.$store.state.instance.federationPolicy.mrf_simple.accept
-    },
-    rejectInstances () {
-      return this.$store.state.instance.federationPolicy.mrf_simple.reject
-    },
-    quarantineInstances () {
-      return this.$store.state.instance.federationPolicy.quarantined_instances
-    },
-    ftlRemovalInstances () {
-      return this.$store.state.instance.federationPolicy.mrf_simple.federated_timeline_removal
-    },
-    mediaNsfwInstances () {
-      return this.$store.state.instance.federationPolicy.mrf_simple.media_nsfw
-    },
-    mediaRemovalInstances () {
-      return this.$store.state.instance.federationPolicy.mrf_simple.media_removal
-    }
-  }
+  computed: mapState({
+    federationPolicy: state => state.instance.federationPolicy,
+    mrfPolicies: state => state.instance.federationPolicy.mrf_policies,
+    acceptInstances: state => state.instance.federationPolicy.mrf_simple.accept,
+    rejectInstances: state => state.instance.federationPolicy.mrf_simple.reject,
+    quarantineInstances: state => state.instance.federationPolicy.quarantined_instances,
+    ftlRemovalInstances: state => state.instance.federationPolicy.mrf_simple.federated_timeline_removal,
+    mediaNsfwInstances: state => state.instance.federationPolicy.mrf_simple.media_nsfw,
+    mediaRemovalInstances: state => state.instance.federationPolicy.mrf_simple.media_removal
+  })
 }
 
 export default MRFTransparencyPanel

From a2a94c4b7b33611d97c61761de0cef85eb892bb1 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Sat, 9 Nov 2019 11:33:47 -0600
Subject: [PATCH 009/483] add changelog entry

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2719edcf..0d6ef1a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Emoji picker
 - Started changelog anew
 - Ability to change user's email
+- About page
 ### Changed
 - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes
 ### Fixed

From 3c3f2c654ea9e6083ec855b5c795a303b3711f3c Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sun, 10 Nov 2019 14:42:01 -0500
Subject: [PATCH 010/483] add hideISP to defaultState of config module

---
 src/modules/config.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/modules/config.js b/src/modules/config.js
index 78314118..d4819ee8 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -5,6 +5,7 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0]
 
 export const defaultState = {
   colors: {},
+  hideISP: false,
   // bad name: actually hides posts of muted USERS
   hideMutedPosts: undefined, // instance default
   collapseMessageWithSubject: undefined, // instance default

From 9e774fffbf5d7e3a506ae377d790c7bc4c057aca Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 11 Nov 2019 21:45:40 +0200
Subject: [PATCH 011/483] achieve the perfect speeeeen in media-upload

---
 src/components/media_upload/media_upload.vue  | 27 ++++++++++++-------
 .../post_status_form/post_status_form.vue     |  5 ----
 2 files changed, 18 insertions(+), 14 deletions(-)

diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue
index 1dda7bc1..0fc305ac 100644
--- a/src/components/media_upload/media_upload.vue
+++ b/src/components/media_upload/media_upload.vue
@@ -6,16 +6,16 @@
     @drop="fileDrop"
   >
     <label
-      class="btn btn-default"
+      class="label"
       :title="$t('tool_tip.media_upload')"
     >
       <i
         v-if="uploading"
-        class="icon-spin4 animate-spin"
+        class="progress-icon icon-spin4 animate-spin"
       />
       <i
         v-if="!uploading"
-        class="icon-upload"
+        class="new-icon icon-upload"
       />
       <input
         v-if="uploadReady"
@@ -30,15 +30,24 @@
 
 <script src="./media_upload.js" ></script>
 
-<style>
+<style lang="scss">
 .media-upload {
-  .icon-upload {
+  .label {
+    display: inline-block;
+  }
+
+  .new-icon {
     cursor: pointer;
   }
 
-  label {
-    display: block;
-    width: 100%;
+  .progress-icon {
+    display: inline-block;
+    line-height: 0;
+    &::before {
+      /* Overriding fontello to achieve the perfect speeeen */
+      margin: 0;
+      line-height: 0;
+    }
   }
 }
-</style>
+ </style>
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 0094b1aa..9789a481 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -339,11 +339,6 @@
     font-size: 26px;
     flex: 1;
 
-    i {
-      display: block;
-      width: 100%;
-    }
-
     &.selected, &:hover {
       // needs to be specific to override icon default color
       i, label {

From 621ac0bdc76ef1f38b227ede9200a947bbbc8073 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Sat, 9 Nov 2019 19:49:50 -0600
Subject: [PATCH 012/483] docs: document FE interaction with the BE private
 setting

---
 docs/CONFIGURATION.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 35363537..f7397a55 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -96,3 +96,6 @@ Setting this will change the warning text that is displayed for direct messages.
 ATTENTION: If you actually want the behavior to change. You will need to set the appropriate option at the backend. See the backend documentation for information about that.
 
 DO NOT activate this without checking the backend configuration first!
+
+### Private Mode
+If the `private` instance setting is enabled in the backend, features that are not accessible without authentication, such as the timelines and search will be disabled for unauthenticated users.

From 99fd096ddd1cc657a86c41e7e96344b8bb1dc4de Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Sat, 9 Nov 2019 19:53:03 -0600
Subject: [PATCH 013/483] boot: track whether private mode is enabled or not

---
 src/boot/after_store.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 226b67d8..cbe0c330 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -218,6 +218,9 @@ const getNodeInfo = async ({ store }) => {
       store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
       store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
 
+      const priv = metadata.private
+      store.dispatch('setInstanceOption', { name: 'private', value: priv })
+
       const frontendVersion = window.___pleromafe_commit_hash
       store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
       store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') })

From 21f1637e437398ec56b6078cf28b58bd4a0299ba Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Mon, 11 Nov 2019 14:14:44 -0600
Subject: [PATCH 014/483] nav panel: refactor to use vuex mapState

---
 src/components/nav_panel/nav_panel.js | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index aa3f7605..bfcab62e 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -1,4 +1,5 @@
 import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
+import { mapState } from 'vuex'
 
 const NavPanel = {
   created () {
@@ -9,17 +10,11 @@ const NavPanel = {
       followRequestFetcher.startFetching({ store, credentials })
     }
   },
-  computed: {
-    currentUser () {
-      return this.$store.state.users.currentUser
-    },
-    chat () {
-      return this.$store.state.chat.channel
-    },
-    followRequestCount () {
-      return this.$store.state.api.followRequests.length
-    }
-  }
+  computed: mapState({
+    currentUser: state => state.users.currentUser,
+    chat: state => state.chat.channel,
+    followRequestCount: state => state.api.followRequests.length
+  })
 }
 
 export default NavPanel

From 1f9674350cdf7455fe5540d377eb327edf1336ce Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Mon, 11 Nov 2019 14:18:36 -0600
Subject: [PATCH 015/483] nav panel: disable TWKN if federation disabled,
 disable Public and TWKN if privateMode is enabled

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

diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index bfcab62e..a6426d13 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -13,7 +13,9 @@ const NavPanel = {
   computed: mapState({
     currentUser: state => state.users.currentUser,
     chat: state => state.chat.channel,
-    followRequestCount: state => state.api.followRequests.length
+    followRequestCount: state => state.api.followRequests.length,
+    privateMode: state => state.instance.private,
+    federating: state => state.instance.federationPolicy.federating || true
   })
 }
 
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 28589bb1..d85c28bd 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -28,12 +28,12 @@
             </span>
           </router-link>
         </li>
-        <li>
+        <li v-if="currentUser || !privateMode">
           <router-link :to="{ name: 'public-timeline' }">
             {{ $t("nav.public_tl") }}
           </router-link>
         </li>
-        <li>
+        <li v-if="(currentUser || !privateMode) && federating">
           <router-link :to="{ name: 'public-external-timeline' }">
             {{ $t("nav.twkn") }}
           </router-link>

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: [PATCH 016/483] app: search API is not available in private mode so
 disable it

---
 src/App.js  | 3 ++-
 src/App.vue | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/App.js b/src/App.js
index 04a40e30..e2b0e6db 100644
--- a/src/App.js
+++ b/src/App.js
@@ -97,7 +97,8 @@ export default {
         this.$store.state.instance.instanceSpecificPanelContent
     },
     showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
-    isMobileLayout () { return this.$store.state.interface.mobileLayout }
+    isMobileLayout () { return this.$store.state.interface.mobileLayout },
+    privateMode () { return this.$store.state.instance.private }
   },
   methods: {
     scrollToTop () {
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"

From 43d4d17b41863dd5784f1d611857a9064759946b Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Mon, 11 Nov 2019 14:37:14 -0600
Subject: [PATCH 017/483] side drawer: same treatment

---
 src/components/side_drawer/side_drawer.js  | 6 ++++++
 src/components/side_drawer/side_drawer.vue | 6 +++---
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 567d2e5e..2725d43a 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -34,6 +34,12 @@ const SideDrawer = {
     },
     followRequestCount () {
       return this.$store.state.api.followRequests.length
+    },
+    privateMode () {
+      return this.$store.state.instance.private
+    },
+    federating () {
+      return this.$store.state.instance.federationPolicy.federating || true
     }
   },
   methods: {
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 214b8e0c..be18a5d7 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -79,12 +79,12 @@
             </span>
           </router-link>
         </li>
-        <li @click="toggleDrawer">
+        <li @click="toggleDrawer" v-if="currentUser || !privateMode">
           <router-link to="/main/public">
             {{ $t("nav.public_tl") }}
           </router-link>
         </li>
-        <li @click="toggleDrawer">
+        <li @click="toggleDrawer" v-if="(currentUser || !privateMode) && federating">
           <router-link to="/main/all">
             {{ $t("nav.twkn") }}
           </router-link>
@@ -99,7 +99,7 @@
         </li>
       </ul>
       <ul>
-        <li @click="toggleDrawer">
+        <li @click="toggleDrawer" v-if="currentUser || !privateMode">
           <router-link :to="{ name: 'search' }">
             {{ $t("nav.search") }}
           </router-link>

From 949f47063b0114cd72630d3862df96429835f925 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 12 Nov 2019 10:40:36 -0500
Subject: [PATCH 018/483] show N/A when count is hidden

---
 src/components/user_card/user_card.js  | 6 ++++++
 src/components/user_card/user_card.vue | 4 ++--
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index cc8a1ed6..a9278200 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -93,6 +93,12 @@ export default {
       const roleTitle = rights.admin ? 'admin' : 'moderator'
       return validRole && roleTitle
     },
+    hideFollowsCount () {
+      return this.isOtherUser && this.user.hide_follows_count
+    },
+    hideFollowersCount () {
+      return this.isOtherUser && this.user.hide_followers_count
+    },
     ...mapGetters(['mergedConfig'])
   },
   components: {
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 6f3c958e..e54d30d4 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -208,14 +208,14 @@
           @click.prevent="setProfileView('friends')"
         >
           <h5>{{ $t('user_card.followees') }}</h5>
-          <span>{{ user.friends_count }}</span>
+          <span>{{ hideFollowsCount ? 'N/A' : user.friends_count }}</span>
         </div>
         <div
           class="user-count"
           @click.prevent="setProfileView('followers')"
         >
           <h5>{{ $t('user_card.followers') }}</h5>
-          <span>{{ user.followers_count }}</span>
+          <span>{{ hideFollowersCount ? 'N/A' : user.followers_count }}</span>
         </div>
       </div>
       <!-- eslint-disable vue/no-v-html -->

From 85685d64788193f3f40ce14d3066c48c4523dde0 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 12 Nov 2019 10:47:49 -0500
Subject: [PATCH 019/483] add a translation

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

diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index e54d30d4..421a1236 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -208,14 +208,14 @@
           @click.prevent="setProfileView('friends')"
         >
           <h5>{{ $t('user_card.followees') }}</h5>
-          <span>{{ hideFollowsCount ? 'N/A' : user.friends_count }}</span>
+          <span>{{ hideFollowsCount ? $t('user_card.na') : user.friends_count }}</span>
         </div>
         <div
           class="user-count"
           @click.prevent="setProfileView('followers')"
         >
           <h5>{{ $t('user_card.followers') }}</h5>
-          <span>{{ hideFollowersCount ? 'N/A' : user.followers_count }}</span>
+          <span>{{ hideFollowersCount ? $t('user_card.na') : user.followers_count }}</span>
         </div>
       </div>
       <!-- eslint-disable vue/no-v-html -->
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ead333c1..b9519617 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -574,6 +574,7 @@
     "mention": "Mention",
     "mute": "Mute",
     "muted": "Muted",
+    "na": "N/A",
     "per_day": "per day",
     "remote_follow": "Remote follow",
     "report": "Report",

From 58839ecef895bbf7cf5fd94ed048a0736ee951fe Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 12 Nov 2019 13:31:30 -0500
Subject: [PATCH 020/483] change N/A to Hidden

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

diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 421a1236..96acf610 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -208,14 +208,14 @@
           @click.prevent="setProfileView('friends')"
         >
           <h5>{{ $t('user_card.followees') }}</h5>
-          <span>{{ hideFollowsCount ? $t('user_card.na') : user.friends_count }}</span>
+          <span>{{ hideFollowsCount ? $t('user_card.hidden') : user.friends_count }}</span>
         </div>
         <div
           class="user-count"
           @click.prevent="setProfileView('followers')"
         >
           <h5>{{ $t('user_card.followers') }}</h5>
-          <span>{{ hideFollowersCount ? $t('user_card.na') : user.followers_count }}</span>
+          <span>{{ hideFollowersCount ? $t('user_card.hidden') : user.followers_count }}</span>
         </div>
       </div>
       <!-- eslint-disable vue/no-v-html -->
diff --git a/src/i18n/en.json b/src/i18n/en.json
index b9519617..ad3e671d 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -569,12 +569,12 @@
     "followers": "Followers",
     "following": "Following!",
     "follows_you": "Follows you!",
+    "hidden": "Hidden",
     "its_you": "It's you!",
     "media": "Media",
     "mention": "Mention",
     "mute": "Mute",
     "muted": "Muted",
-    "na": "N/A",
     "per_day": "per day",
     "remote_follow": "Remote follow",
     "report": "Report",

From 50dc9df8a44d408dd83ae4b17c407fa36c85cf8e Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 14 Nov 2019 00:18:14 +0200
Subject: [PATCH 021/483] adds greentext, also small fixes

---
 src/components/status/status.js               | 49 +++++++++--
 .../tiny_post_html_processor.service.js       | 84 +++++++++++++++++++
 2 files changed, 124 insertions(+), 9 deletions(-)
 create mode 100644 src/services/tiny_post_html_processor/tiny_post_html_processor.service.js

diff --git a/src/components/status/status.js b/src/components/status/status.js
index 4fbd5ac3..6dbb2199 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -13,10 +13,11 @@ import Timeago from '../timeago/timeago.vue'
 import StatusPopover from '../status_popover/status_popover.vue'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 import fileType from 'src/services/file_type/file_type.service'
+import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
 import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
 import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
 import { filter, unescape, uniqBy } from 'lodash'
-import { mapGetters } from 'vuex'
+import { mapGetters, mapState } from 'vuex'
 
 const Status = {
   name: 'Status',
@@ -42,8 +43,8 @@ const Status = {
       showingTall: this.inConversation && this.focused,
       showingLongSubject: false,
       error: null,
+      // Initial state
       expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject,
-      betterShadow: this.$store.state.interface.browserSupport.cssFilter
     }
   },
   computed: {
@@ -103,7 +104,7 @@ const Status = {
       return this.$store.state.statuses.allStatusesObject[this.status.id]
     },
     loggedIn () {
-      return !!this.$store.state.users.currentUser
+      return !!this.currentUser
     },
     muteWordHits () {
       const statusText = this.status.text.toLowerCase()
@@ -163,7 +164,7 @@ const Status = {
       if (this.inConversation || !this.isReply) {
         return false
       }
-      if (this.status.user.id === this.$store.state.users.currentUser.id) {
+      if (this.status.user.id === this.currentUser.id) {
         return false
       }
       if (this.status.type === 'retweet') {
@@ -178,7 +179,7 @@ const Status = {
         if (checkFollowing && taggedUser && taggedUser.following) {
           return false
         }
-        if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) {
+        if (this.status.attentions[i].id === this.currentUser.id) {
           return false
         }
       }
@@ -255,11 +256,37 @@ const Status = {
     maxThumbnails () {
       return this.mergedConfig.maxThumbnails
     },
+    postBodyHtml () {
+      const html = this.status.statusnet_html
+
+      try {
+        if (html.includes('&gt;')) {
+          // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works
+          return processHtml(html, (string) => {
+            if (string.includes('&gt;') &&
+                string
+                .replace(/<[^>]+?>/gi, '') // remove all tags
+                .replace(/@\w+/gi, '') // remove mentions (even failed ones)
+                .trim()
+                .startsWith('&gt;')) {
+              return `<span class='greentext'>${string}</span>`
+            } else {
+              return string
+            }
+          })
+        } else {
+          return html
+        }
+      } catch (e) {
+        console.err('Failed to process status html', e)
+        return html
+      }
+    },
     contentHtml () {
       if (!this.status.summary_html) {
-        return this.status.statusnet_html
+        return this.postBodyHtml
       }
-      return this.status.summary_html + '<br />' + this.status.statusnet_html
+      return this.status.summary_html + '<br />' + this.postBodyHtml
     },
     combinedFavsAndRepeatsUsers () {
       // Use the status from the global status repository since favs and repeats are saved in it
@@ -270,7 +297,7 @@ const Status = {
       return uniqBy(combinedUsers, 'id')
     },
     ownStatus () {
-      return this.status.user.id === this.$store.state.users.currentUser.id
+      return this.status.user.id === this.currentUser.id
     },
     tags () {
       return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')
@@ -278,7 +305,11 @@ const Status = {
     hidePostStats () {
       return this.mergedConfig.hidePostStats
     },
-    ...mapGetters(['mergedConfig'])
+    ...mapGetters(['mergedConfig']),
+    ...mapState({
+      betterShadow: state => state.interface.browserSupport.cssFilter,
+      currentUser: state => state.users.currentUser
+    })
   },
   components: {
     Attachment,
diff --git a/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
new file mode 100644
index 00000000..c9ff81e1
--- /dev/null
+++ b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
@@ -0,0 +1,84 @@
+/**
+ * This is a tiny purpose-built HTML parser/processor. This basically detects any type of visual newline and
+ * allows it to be processed, useful for greentexting, mostly
+ *
+ * @param {Object} input - input data
+ * @param {(string) => string} processor - function that will be called on every line
+ * @return {string} processed html
+ */
+export const processHtml = (html, processor) => {
+  const handledTags = new Set(['p', 'br', 'div'])
+  const openCloseTags = new Set(['p', 'div'])
+  const tagRegex = /(?:<\/(\w+)>|<(\w+)\s?[^/]*?\/?>)/gi
+
+  let buffer = '' // Current output buffer
+  const level = [] // How deep we are in tags and which tags were there
+  let textBuffer = '' // Current line content
+  let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag
+
+  // Extracts tagname from tag, i.e. <span a="b"> => span
+  const getTagName = (tag) => {
+    // eslint-disable-next-line no-unused-vars
+    const result = tagRegex.exec(tag)
+    return result && (result[1] || result[2])
+  }
+
+  const flush = () => { // Processes current line buffer, adds it to output buffer and clears line buffer
+    buffer += processor(textBuffer)
+    textBuffer = ''
+  }
+
+  const handleBr = (tag) => { // handles single newlines/linebreaks
+    flush()
+    buffer += tag
+  }
+
+  const handleOpen = (tag) => { // handles opening tags
+    flush()
+    buffer += tag
+    level.push(tag)
+  }
+
+  const handleClose = (tag) => { // handles closing tags
+    flush()
+    buffer += tag
+    if (level[level.length - 1] === tag) {
+      level.pop()
+    }
+  }
+
+  for (let i = 0; i < html.length; i++) {
+    const char = html[i]
+    if (char === '<' && tagBuffer !== null) {
+      tagBuffer = char
+    } else if (char !== '>' && tagBuffer !== null) {
+      tagBuffer += char
+    } else if (char === '>' && tagBuffer !== null) {
+      tagBuffer += char
+      const tagName = getTagName(tagBuffer)
+      if (handledTags.has(tagName)) {
+        if (tagName === 'br') {
+          handleBr(tagBuffer)
+        }
+        if (openCloseTags.has(tagBuffer)) {
+          if (tagBuffer[1] === '/') {
+            handleClose(tagBuffer)
+          } else {
+            handleOpen(tagBuffer)
+          }
+        }
+      } else {
+        textBuffer += tagBuffer
+      }
+      tagBuffer = null
+    } else if (char === '\n') {
+      handleBr(char)
+    } else {
+      textBuffer += char
+    }
+  }
+
+  flush()
+
+  return buffer
+}

From 692ee0e95a852b1f803b7ae92d65cbf4f3ce3445 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 14 Nov 2019 00:41:14 +0200
Subject: [PATCH 022/483] Fix regex, tag detector condition

---
 src/components/status/status.js               |  2 +-
 .../tiny_post_html_processor.service.js       | 25 +++++++++----------
 2 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/src/components/status/status.js b/src/components/status/status.js
index 6dbb2199..416aa36a 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -43,7 +43,7 @@ const Status = {
       showingTall: this.inConversation && this.focused,
       showingLongSubject: false,
       error: null,
-      // Initial state
+      // not as computed because it sets the initial state which will be changed later
       expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject,
     }
   },
diff --git a/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
index c9ff81e1..b96c1ccf 100644
--- a/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
+++ b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
@@ -9,17 +9,15 @@
 export const processHtml = (html, processor) => {
   const handledTags = new Set(['p', 'br', 'div'])
   const openCloseTags = new Set(['p', 'div'])
-  const tagRegex = /(?:<\/(\w+)>|<(\w+)\s?[^/]*?\/?>)/gi
 
   let buffer = '' // Current output buffer
   const level = [] // How deep we are in tags and which tags were there
   let textBuffer = '' // Current line content
   let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag
 
-  // Extracts tagname from tag, i.e. <span a="b"> => span
+  // Extracts tag name from tag, i.e. <span a="b"> => span
   const getTagName = (tag) => {
-    // eslint-disable-next-line no-unused-vars
-    const result = tagRegex.exec(tag)
+    const result = /(?:<\/(\w+)>|<(\w+)\s?[^/]*?\/?>)/gi.exec(tag)
     return result && (result[1] || result[2])
   }
 
@@ -49,28 +47,29 @@ export const processHtml = (html, processor) => {
 
   for (let i = 0; i < html.length; i++) {
     const char = html[i]
-    if (char === '<' && tagBuffer !== null) {
+    if (char === '<' && tagBuffer === null) {
       tagBuffer = char
     } else if (char !== '>' && tagBuffer !== null) {
       tagBuffer += char
     } else if (char === '>' && tagBuffer !== null) {
       tagBuffer += char
-      const tagName = getTagName(tagBuffer)
+      const tagFull = tagBuffer
+      tagBuffer = null
+      const tagName = getTagName(tagFull)
       if (handledTags.has(tagName)) {
         if (tagName === 'br') {
-          handleBr(tagBuffer)
+          handleBr(tagFull)
         }
-        if (openCloseTags.has(tagBuffer)) {
-          if (tagBuffer[1] === '/') {
-            handleClose(tagBuffer)
+        if (openCloseTags.has(tagFull)) {
+          if (tagFull[1] === '/') {
+            handleClose(tagFull)
           } else {
-            handleOpen(tagBuffer)
+            handleOpen(tagFull)
           }
         }
       } else {
-        textBuffer += tagBuffer
+        textBuffer += tagFull
       }
-      tagBuffer = null
     } else if (char === '\n') {
       handleBr(char)
     } else {

From 897131572f62c36277e06e464f328f37cf6acad2 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 14 Nov 2019 00:47:20 +0200
Subject: [PATCH 023/483] Made it optional

---
 src/components/settings/settings.vue | 11 ++++++++
 src/components/status/status.js      | 40 +++++++++++++++-------------
 src/components/status/status.vue     |  5 ++--
 src/i18n/en.json                     |  2 ++
 src/modules/config.js                |  1 +
 src/modules/instance.js              |  1 +
 6 files changed, 40 insertions(+), 20 deletions(-)

diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index a83489d2..c4021137 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -270,6 +270,17 @@
                 </li>
               </ul>
             </div>
+
+            <div class="setting-item">
+              <h2>{{ $t('settings.fun') }}</h2>
+              <ul class="setting-list">
+                <li>
+                  <Checkbox v-model="greentext">
+                    {{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
+                  </Checkbox>
+                </li>
+              </ul>
+            </div>
           </div>
 
           <div :label="$t('settings.theme')">
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 416aa36a..dd0ce3ab 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -259,26 +259,30 @@ const Status = {
     postBodyHtml () {
       const html = this.status.statusnet_html
 
-      try {
-        if (html.includes('&gt;')) {
-          // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works
-          return processHtml(html, (string) => {
-            if (string.includes('&gt;') &&
-                string
-                .replace(/<[^>]+?>/gi, '') // remove all tags
-                .replace(/@\w+/gi, '') // remove mentions (even failed ones)
-                .trim()
-                .startsWith('&gt;')) {
-              return `<span class='greentext'>${string}</span>`
-            } else {
-              return string
-            }
-          })
-        } else {
+      if (this.mergedConfig.greentext) {
+        try {
+          if (html.includes('&gt;')) {
+            // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works
+            return processHtml(html, (string) => {
+              if (string.includes('&gt;') &&
+                  string
+                  .replace(/<[^>]+?>/gi, '') // remove all tags
+                  .replace(/@\w+/gi, '') // remove mentions (even failed ones)
+                  .trim()
+                  .startsWith('&gt;')) {
+                return `<span class='greentext'>${string}</span>`
+              } else {
+                return string
+              }
+            })
+          } else {
+            return html
+          }
+        } catch (e) {
+          console.err('Failed to process status html', e)
           return html
         }
-      } catch (e) {
-        console.err('Failed to process status html', e)
+      } else {
         return html
       }
     },
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 65778b2e..d291e762 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -606,7 +606,7 @@ $status-margin: 0.75em;
       height: 100%;
       mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
             linear-gradient(to top, white, white);
-      // Autoprefixed seem to ignore this one, and also syntax is different
+      /* Autoprefixed seem to ignore this one, and also syntax is different */
       -webkit-mask-composite: xor;
       mask-composite: exclude;
     }
@@ -752,7 +752,8 @@ $status-margin: 0.75em;
 }
 
 .greentext {
-  color: green;
+  color: $fallback--cGreen;
+  color: var(--cGreen, $fallback--cGreen);
 }
 
 .status-conversation {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ead333c1..7b0b3b94 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -370,6 +370,8 @@
       "false": "no",
       "true": "yes"
     },
+    "fun": "Fun",
+    "greentext": "Meme arrows",
     "notifications": "Notifications",
     "notification_setting": "Receive notifications from:",
     "notification_setting_follows": "Users you follow",
diff --git a/src/modules/config.js b/src/modules/config.js
index d4819ee8..329b4091 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -45,6 +45,7 @@ export const defaultState = {
   playVideosInModal: false,
   useOneClickNsfw: false,
   useContainFit: false,
+  greentext: undefined, // instance default
   hidePostStats: undefined, // instance default
   hideUserStats: undefined // instance default
 }
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 7b0e0da4..96f14ed5 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -32,6 +32,7 @@ const defaultState = {
   noAttachmentLinks: false,
   showFeaturesPanel: true,
   minimalScopesMode: false,
+  greentext: false,
 
   // Nasty stuff
   pleromaBackend: true,

From bce750c5717419b8f2f2de6d8a4380a4e664f986 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 14 Nov 2019 00:49:31 +0200
Subject: [PATCH 024/483] making dtluna proud or disgusted

---
 src/i18n/ru.json | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index f8bcd996..19e10f1e 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -174,6 +174,8 @@
     "name_bio": "Имя и описание",
     "new_email": "Новый email",
     "new_password": "Новый пароль",
+    "fun": "Потешное",
+    "greentext": "Мемные стрелочки",
     "notification_visibility": "Показывать уведомления",
     "notification_visibility_follows": "Подписки",
     "notification_visibility_likes": "Лайки",

From 51ea295704c52b1f9a922868aedf264e53a5ec92 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 14 Nov 2019 00:52:38 +0200
Subject: [PATCH 025/483] eslint

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

diff --git a/src/components/status/status.js b/src/components/status/status.js
index dd0ce3ab..714ea6d2 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -44,7 +44,7 @@ const Status = {
       showingLongSubject: false,
       error: null,
       // not as computed because it sets the initial state which will be changed later
-      expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject,
+      expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject
     }
   },
   computed: {
@@ -266,10 +266,10 @@ const Status = {
             return processHtml(html, (string) => {
               if (string.includes('&gt;') &&
                   string
-                  .replace(/<[^>]+?>/gi, '') // remove all tags
-                  .replace(/@\w+/gi, '') // remove mentions (even failed ones)
-                  .trim()
-                  .startsWith('&gt;')) {
+                    .replace(/<[^>]+?>/gi, '') // remove all tags
+                    .replace(/@\w+/gi, '') // remove mentions (even failed ones)
+                    .trim()
+                    .startsWith('&gt;')) {
                 return `<span class='greentext'>${string}</span>`
               } else {
                 return string

From 90fc6b07743c87cbb08f07d9cf640b14efab1c8e Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Thu, 14 Nov 2019 14:07:05 -0500
Subject: [PATCH 026/483] close image modal by clicking image

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

diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 49e3143e..80d2a8b9 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -10,13 +10,13 @@
       :src="currentMedia.url"
       @touchstart.stop="mediaTouchStart"
       @touchmove.stop="mediaTouchMove"
+      @click="hide"
     >
     <VideoAttachment
       v-if="type === 'video'"
       class="modal-image"
       :attachment="currentMedia"
       :controls="true"
-      @click.stop.native=""
     />
     <button
       v-if="canNavigate"

From bd2a682b83743311645241fe644e853e1a359b67 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 14 Nov 2019 22:40:20 +0200
Subject: [PATCH 027/483] tests + updates

---
 .../tiny_post_html_processor.service.js       | 19 +++-
 .../tiny_post_html_processor.spec.js          | 96 +++++++++++++++++++
 2 files changed, 111 insertions(+), 4 deletions(-)
 create mode 100644 test/unit/specs/services/tiny_post_html_processor/tiny_post_html_processor.spec.js

diff --git a/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
index b96c1ccf..de6f20ef 100644
--- a/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
+++ b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
@@ -2,6 +2,8 @@
  * This is a tiny purpose-built HTML parser/processor. This basically detects any type of visual newline and
  * allows it to be processed, useful for greentexting, mostly
  *
+ * known issue: doesn't handle CDATA so nested CDATA might not work well
+ *
  * @param {Object} input - input data
  * @param {(string) => string} processor - function that will be called on every line
  * @return {string} processed html
@@ -22,11 +24,15 @@ export const processHtml = (html, processor) => {
   }
 
   const flush = () => { // Processes current line buffer, adds it to output buffer and clears line buffer
-    buffer += processor(textBuffer)
+    if (textBuffer.trim().length > 0) {
+      buffer += processor(textBuffer)
+    } else {
+      buffer += textBuffer
+    }
     textBuffer = ''
   }
 
-  const handleBr = (tag) => { // handles single newlines/linebreaks
+  const handleBr = (tag) => { // handles single newlines/linebreaks/selfclosing
     flush()
     buffer += tag
   }
@@ -59,10 +65,12 @@ export const processHtml = (html, processor) => {
       if (handledTags.has(tagName)) {
         if (tagName === 'br') {
           handleBr(tagFull)
-        }
-        if (openCloseTags.has(tagFull)) {
+        } else if (openCloseTags.has(tagName)) {
           if (tagFull[1] === '/') {
             handleClose(tagFull)
+          } else if (tagFull[tagFull.length - 2] === '/') {
+            // self-closing
+            handleBr(tagFull)
           } else {
             handleOpen(tagFull)
           }
@@ -76,6 +84,9 @@ export const processHtml = (html, processor) => {
       textBuffer += char
     }
   }
+  if (tagBuffer) {
+    textBuffer += tagBuffer
+  }
 
   flush()
 
diff --git a/test/unit/specs/services/tiny_post_html_processor/tiny_post_html_processor.spec.js b/test/unit/specs/services/tiny_post_html_processor/tiny_post_html_processor.spec.js
new file mode 100644
index 00000000..f301429d
--- /dev/null
+++ b/test/unit/specs/services/tiny_post_html_processor/tiny_post_html_processor.spec.js
@@ -0,0 +1,96 @@
+import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
+
+describe('TinyPostHTMLProcessor', () => {
+  describe('with processor that keeps original line should not make any changes to HTML when', () => {
+    const processorKeep = (line) => line
+    it('fed with regular HTML with newlines', () => {
+      const inputOutput = '1<br/>2<p class="lol">3 4</p> 5 \n 6 <p > 7 <br> 8 </p> <br>\n<br/>'
+      expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+    })
+
+    it('fed with possibly broken HTML with invalid tags/composition', () => {
+      const inputOutput = '<feeee dwdwddddddw> <i>ayy<b>lm</i>ao</b> </section>'
+      expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+    })
+
+    it('fed with very broken HTML with broken composition', () => {
+      const inputOutput = '</p> lmao what </div> whats going on <div> wha <p>'
+      expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+    })
+
+    it('fed with sorta valid HTML but tags aren\'t closed', () => {
+      const inputOutput = 'just leaving a <div> hanging'
+      expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+    })
+
+    it('fed with not really HTML at this point... tags that aren\'t finished', () => {
+      const inputOutput = 'do you expect me to finish this <div class='
+      expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+    })
+
+    it('fed with dubiously valid HTML (p within p and also div inside p)', () => {
+      const inputOutput = 'look ma <p> p \nwithin <p> p! </p> and a <br/><div>div!</div></p>'
+      expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+    })
+
+    it('fed with maybe valid HTML? self-closing divs and ps', () => {
+      const inputOutput = 'a <div class="what"/> what now <p aria-label="wtf"/> ?'
+      expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+    })
+
+    it('fed with valid XHTML containing a CDATA', () => {
+      const inputOutput = 'Yes, it is me, <![CDATA[DIO]]>'
+      expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+    })
+  })
+  describe('with processor that replaces lines with word "_" should match expected line when', () => {
+    const processorReplace = (line) => '_'
+    it('fed with regular HTML with newlines', () => {
+      const input = '1<br/>2<p class="lol">3 4</p> 5 \n 6 <p > 7 <br> 8 </p> <br>\n<br/>'
+      const output = '_<br/>_<p class="lol">_</p>_\n_<p >_<br>_</p> <br>\n<br/>'
+      expect(processHtml(input, processorReplace)).to.eql(output)
+    })
+
+    it('fed with possibly broken HTML with invalid tags/composition', () => {
+      const input = '<feeee dwdwddddddw> <i>ayy<b>lm</i>ao</b> </section>'
+      const output = '_'
+      expect(processHtml(input, processorReplace)).to.eql(output)
+    })
+
+    it('fed with very broken HTML with broken composition', () => {
+      const input = '</p> lmao what </div> whats going on <div> wha <p>'
+      const output = '</p>_</div>_<div>_<p>'
+      expect(processHtml(input, processorReplace)).to.eql(output)
+    })
+
+    it('fed with sorta valid HTML but tags aren\'t closed', () => {
+      const input = 'just leaving a <div> hanging'
+      const output = '_<div>_'
+      expect(processHtml(input, processorReplace)).to.eql(output)
+    })
+
+    it('fed with not really HTML at this point... tags that aren\'t finished', () => {
+      const input = 'do you expect me to finish this <div class='
+      const output = '_'
+      expect(processHtml(input, processorReplace)).to.eql(output)
+    })
+
+    it('fed with dubiously valid HTML (p within p and also div inside p)', () => {
+      const input = 'look ma <p> p \nwithin <p> p! </p> and a <br/><div>div!</div></p>'
+      const output = '_<p>_\n_<p>_</p>_<br/><div>_</div></p>'
+      expect(processHtml(input, processorReplace)).to.eql(output)
+    })
+
+    it('fed with maybe valid HTML? self-closing divs and ps', () => {
+      const input = 'a <div class="what"/> what now <p aria-label="wtf"/> ?'
+      const output = '_<div class="what"/>_<p aria-label="wtf"/>_'
+      expect(processHtml(input, processorReplace)).to.eql(output)
+    })
+
+    it('fed with valid XHTML containing a CDATA', () => {
+      const input = 'Yes, it is me, <![CDATA[DIO]]>'
+      const output = '_'
+      expect(processHtml(input, processorReplace)).to.eql(output)
+    })
+  })
+})

From 28efd7d862d3fb890969448479d3034af7201170 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 14 Nov 2019 23:46:19 +0200
Subject: [PATCH 028/483] use yarn, try to restart pipeline

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9d42288e..7f6d3c92 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,7 +24,7 @@ test:
     - apt install firefox-esr -y --no-install-recommends
     - firefox --version
     - yarn
-    - npm run unit
+    - yarn unit
 
 build:
   stage: build

From 3ecfbc8ea2b60aa0fe40f2969d27fcb916d6b639 Mon Sep 17 00:00:00 2001
From: Mew Mew <alexafediverse+plgitlab@gmail.com>
Date: Fri, 15 Nov 2019 05:09:01 +0000
Subject: [PATCH 029/483] Fix translation
 (https://blob.cat/notice/9oyYO1RzcNbJXxKxeq)

---
 src/i18n/zh.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index 80c4e0d8..8d9462ab 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -111,7 +111,7 @@
   },
   "interactions": {
     "favs_repeats": "转发和收藏",
-    "follows": "新的关注着",
+    "follows": "新的关注者",
     "load_older": "加载更早的互动"
   },
   "post_status": {

From d0075026290c90d8406c7ac81413259a8ae58ec7 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Fri, 15 Nov 2019 08:39:21 +0200
Subject: [PATCH 030/483] add fetching for emoji reactions, draft design

---
 src/components/conversation/conversation.js   |  1 +
 src/components/status/status.js               |  6 ++++
 src/components/status/status.vue              | 28 +++++++++++++++++++
 src/modules/statuses.js                       | 14 +++++++++-
 src/services/api/api.service.js               |  6 ++++
 .../backend_interactor_service.js             |  2 ++
 6 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 72ee9c39..715804ff 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -149,6 +149,7 @@ const conversation = {
       if (!id) return
       this.highlight = id
       this.$store.dispatch('fetchFavsAndRepeats', id)
+      this.$store.dispatch('fetchEmojiReactions', id)
     },
     getHighlight () {
       return this.isExpanded ? this.highlight : null
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 4fbd5ac3..8268e615 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -278,6 +278,12 @@ const Status = {
     hidePostStats () {
       return this.mergedConfig.hidePostStats
     },
+    emojiReactions () {
+      return {
+        '🤔': [{ 'id': 'xyz..' }, { 'id': 'zyx...' }],
+        '🐻': [{ 'id': 'abc...' }]
+      }
+    },
     ...mapGetters(['mergedConfig'])
   },
   components: {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 65778b2e..aae58a5e 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -354,6 +354,17 @@
             </div>
           </transition>
 
+          <div class="emoji-reactions">
+            <button
+              class="emoji-reaction btn btn-default"
+              v-for="(users, emoji) in emojiReactions"
+              :key="emoji"
+            >
+              <span>{{users.length}}</span>
+              <span>{{emoji}}</span>
+            </button>
+          </div>
+
           <div
             v-if="!noHeading && !isPreview"
             class="status-actions media-body"
@@ -771,6 +782,23 @@ $status-margin: 0.75em;
   }
 }
 
+.emoji-reactions {
+  display: flex;
+  margin-top: 0.75em;
+}
+
+.emoji-reaction {
+  padding: 0 0.5em;
+  margin-right: 0.5em;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  :first-child {
+    margin-right: 0.25em;
+  }
+}
+
 .button-icon.icon-reply {
   &:not(.button-icon-disabled):hover,
   &.button-icon-active {
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index f11ffdcd..c285b452 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -1,4 +1,4 @@
-import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
+import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy, findKey } from 'lodash'
 import { set } from 'vue'
 import apiService from '../services/api/api.service.js'
 // import parse from '../services/status_parser/status_parser.js'
@@ -510,6 +510,11 @@ export const mutations = {
     newStatus.fave_num = newStatus.favoritedBy.length
     newStatus.favorited = !!newStatus.favoritedBy.find(({ id }) => currentUser.id === id)
   },
+  addEmojiReactions (state, { id, emojiReactions, currentUser }) {
+    const status = state.allStatusesObject[id]
+    status.emojiReactions = emojiReactions
+    status.reactedWithEmoji = findKey(emojiReactions, { id: currentUser.id })
+  },
   updateStatusWithPoll (state, { id, poll }) {
     const status = state.allStatusesObject[id]
     status.poll = poll
@@ -611,6 +616,13 @@ const statuses = {
         commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
       })
     },
+    fetchEmojiReactions ({ rootState, commit }, id) {
+      rootState.api.backendInteractor.fetchEmojiReactions(id).then(
+        emojiReactions => {
+          commit('addEmojiReactions', { id, emojiReactions, currentUser: rootState.users.currentUser })
+        }
+      )
+    },
     fetchFavs ({ rootState, commit }, id) {
       rootState.api.backendInteractor.fetchFavoritedByUsers(id)
         .then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 8f5eb416..7ef4b74a 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -71,6 +71,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
 const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
 const MASTODON_SEARCH_2 = `/api/v2/search`
 const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
+const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by`
 
 const oldfetch = window.fetch
 
@@ -864,6 +865,10 @@ const fetchRebloggedByUsers = ({ id }) => {
   return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser))
 }
 
+const fetchEmojiReactions = ({ id }) => {
+  return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) })
+}
+
 const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
   return promisedRequest({
     url: MASTODON_REPORT_USER_URL,
@@ -997,6 +1002,7 @@ const apiService = {
   fetchPoll,
   fetchFavoritedByUsers,
   fetchRebloggedByUsers,
+  fetchEmojiReactions,
   reportUser,
   updateNotificationSettings,
   search2,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index d6617276..52234fcc 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -143,6 +143,7 @@ const backendInteractorService = credentials => {
 
   const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id })
   const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id })
+  const fetchEmojiReactions = (id) => apiService.fetchEmojiReactions({ id })
   const reportUser = (params) => apiService.reportUser({ credentials, ...params })
 
   const favorite = (id) => apiService.favorite({ id, credentials })
@@ -210,6 +211,7 @@ const backendInteractorService = credentials => {
     fetchPoll,
     fetchFavoritedByUsers,
     fetchRebloggedByUsers,
+    fetchEmojiReactions,
     reportUser,
     favorite,
     unfavorite,

From de945ba3e9470b28dd010fb32f658b42053f19d3 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Fri, 15 Nov 2019 16:29:25 +0200
Subject: [PATCH 031/483] wip commit, add basic popover for emoji reaction
 select

---
 src/components/react_button/react_button.js  | 50 +++++++++++++
 src/components/react_button/react_button.vue | 78 ++++++++++++++++++++
 src/components/status/status.js              |  2 +
 src/components/status/status.vue             | 10 ++-
 src/i18n/en.json                             |  1 +
 5 files changed, 138 insertions(+), 3 deletions(-)
 create mode 100644 src/components/react_button/react_button.js
 create mode 100644 src/components/react_button/react_button.vue

diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
new file mode 100644
index 00000000..d1d15d93
--- /dev/null
+++ b/src/components/react_button/react_button.js
@@ -0,0 +1,50 @@
+import { mapGetters } from 'vuex'
+
+const ReactButton = {
+  props: ['status', 'loggedIn'],
+  data () {
+    return {
+      animated: false,
+      showTooltip: false,
+      popperOptions: {
+        modifiers: {
+          preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
+        }
+      }
+    }
+  },
+  methods: {
+    openReactionSelect () {
+      console.log('test')
+      this.showTooltip = true
+    },
+    closeReactionSelect () {
+      this.showTooltip = false
+    },
+    favorite () {
+      if (!this.status.favorited) {
+        this.$store.dispatch('favorite', { id: this.status.id })
+      } else {
+        this.$store.dispatch('unfavorite', { id: this.status.id })
+      }
+      this.animated = true
+      setTimeout(() => {
+        this.animated = false
+      }, 500)
+    }
+  },
+  computed: {
+    emojis () {
+      return this.$store.state.instance.emoji || []
+    },
+    classes () {
+      return {
+        'icon-smile': true,
+        'animate-spin': this.animated
+      }
+    },
+    ...mapGetters(['mergedConfig'])
+  }
+}
+
+export default ReactButton
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
new file mode 100644
index 00000000..93638770
--- /dev/null
+++ b/src/components/react_button/react_button.vue
@@ -0,0 +1,78 @@
+<template>
+  <v-popover
+    :popper-options="popperOptions"
+    :open="showTooltip"
+    trigger="manual"
+    placement="top"
+    class="react-button-popover"
+    @close-group="closeReactionSelect"
+  >
+    <div slot="popover">
+      <div class="reaction-picker">
+        <span
+          v-for="(emoji, key) in emojis"
+          :key="key"
+          class="emoji-reaction-button"
+        >
+          {{ emoji.replacement }}
+        </span>
+        <div class="reaction-bottom-fader" />
+      </div>
+    </div>
+    <div @click.prevent="openReactionSelect" v-if="loggedIn">
+      <i
+        :class="classes"
+        class="button-icon favorite-button fav-active"
+        :title="$t('tool_tip.add_reaction')"
+      />
+      <span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span>
+    </div>
+  </v-popover>
+</template>
+
+<script src="./react_button.js" ></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.reaction-picker {
+  width: 10em;
+  height: 8em;
+  font-size: 1.5em;
+  overflow-y: scroll;
+  display: flex;
+  flex-wrap: wrap;
+  padding: 0.5em;
+  text-align:center;
+
+  mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
+    linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
+    linear-gradient(to top, white, white);
+  transition: mask-size 150ms;
+  mask-size: 100% 20px, 100% 20px, auto;
+  // Autoprefixed seem to ignore this one, and also syntax is different
+  -webkit-mask-composite: xor;
+  mask-composite: exclude;
+}
+
+.emoji-reaction-button {
+  flex-basis: 20%;
+  line-height: 1.5em;
+  align-content: center;
+}
+
+.fav-active {
+  cursor: pointer;
+  animation-duration: 0.6s;
+
+  &:hover {
+    color: $fallback--cOrange;
+    color: var(--cOrange, $fallback--cOrange);
+  }
+}
+
+.favorite-button.icon-star {
+  color: $fallback--cOrange;
+  color: var(--cOrange, $fallback--cOrange);
+}
+</style>
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 8268e615..8c6fc0cf 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -1,5 +1,6 @@
 import Attachment from '../attachment/attachment.vue'
 import FavoriteButton from '../favorite_button/favorite_button.vue'
+import ReactButton from '../react_button/react_button.vue'
 import RetweetButton from '../retweet_button/retweet_button.vue'
 import Poll from '../poll/poll.vue'
 import ExtraButtons from '../extra_buttons/extra_buttons.vue'
@@ -289,6 +290,7 @@ const Status = {
   components: {
     Attachment,
     FavoriteButton,
+    ReactButton,
     RetweetButton,
     ExtraButtons,
     PostStatusForm,
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index aae58a5e..d455ccf6 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -356,12 +356,12 @@
 
           <div class="emoji-reactions">
             <button
-              class="emoji-reaction btn btn-default"
               v-for="(users, emoji) in emojiReactions"
               :key="emoji"
+              class="emoji-reaction btn btn-default"
             >
-              <span>{{users.length}}</span>
-              <span>{{emoji}}</span>
+              <span>{{ users.length }}</span>
+              <span>{{ emoji }}</span>
             </button>
           </div>
 
@@ -393,6 +393,10 @@
               :logged-in="loggedIn"
               :status="status"
             />
+            <ReactButton
+              :logged-in="loggedIn"
+              :status="status"
+            />
             <extra-buttons
               :status="status"
               @onError="showError"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ad3e671d..febbf2ea 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -632,6 +632,7 @@
     "repeat": "Repeat",
     "reply": "Reply",
     "favorite": "Favorite",
+    "add_reaction": "Add Reaction",
     "user_settings": "User Settings"
   },
   "upload":{

From a9022d0c32e2fcd6735f02c680312d93e215fd0f Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Fri, 15 Nov 2019 22:56:16 +0300
Subject: [PATCH 032/483] Remove outdated changelog file

This hasn't been updated in 2 years and we have CHANGELOG.md now.
---
 CHANGELOG | 35 -----------------------------------
 1 file changed, 35 deletions(-)
 delete mode 100644 CHANGELOG

diff --git a/CHANGELOG b/CHANGELOG
deleted file mode 100644
index ca2ebcfa..00000000
--- a/CHANGELOG
+++ /dev/null
@@ -1,35 +0,0 @@
-## 2017-02-20
-
-- Overall CSS styling fixes
-- Current theme is displayed in theme selector
-- Theme selector is moved to the settings page
-- Oembed attachments will now display correctly
-- Styling changes to the user info cards
-- Notification count in title
-- Better Notification handling (persistance, mark as read)
-- Post statuses with ctrl+enter
-- Links in statuses open in a new tab
-- Optimized mobile view
-- Fix crash on persistance failure
-- Compress persisted state
-- Sync mutes with backend (SEE NOTE BELOW)
-
-Pleroma will now try to get the current mutes from the backend. Sadly, a bug in
-Qvitter will not allow getting the mutes from the endpoint, because it will
-ignore HTTP Basic authentication. Mutes will still persist in Pleroma through
-localstorage, but the mutes from Qvitter won't be picked up if the call fails.
-
-The patch for Qvitter:
-
---- a/actions/apiqvittermutes.php
-+++ b/actions/apiqvittermutes.php
-@@ -74,7 +74,7 @@ class ApiQvitterMutesAction extends ApiPrivateAuthAction
-     {
-         parent::handle();
-
--        $this->target = Profile::current();
-+        $this->target = $this->scoped;
-
-                if(!$this->target instanceof Profile) {
-                        $this->clientError(_('You have to be logged in to view your mutes.'), 403);
-

From 67c95c9d061ebb2754d56b85969a08c786d34424 Mon Sep 17 00:00:00 2001
From: AkiraFukushima <h3.poteto@gmail.com>
Date: Sun, 17 Nov 2019 01:11:19 +0900
Subject: [PATCH 033/483] Use kana+kanji as default for Japanese translation

---
 .../interface_language_switcher.vue           |   4 +-
 src/i18n/ja.json                              | 658 +++++++++---------
 src/i18n/ja_easy.json                         | 639 +++++++++++++++++
 src/i18n/ja_pedantic.json                     | 639 -----------------
 src/i18n/messages.js                          |   2 +-
 5 files changed, 971 insertions(+), 971 deletions(-)
 create mode 100644 src/i18n/ja_easy.json
 delete mode 100644 src/i18n/ja_pedantic.json

diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index 1ca22001..f5ace0cc 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -51,8 +51,8 @@ export default {
   methods: {
     getLanguageName (code) {
       const specialLanguageNames = {
-        'ja': 'Japanese (やさしいにほんご)',
-        'ja_pedantic': 'Japanese (日本語)',
+        'ja': 'Japanese (日本語)',
+        'ja_easy': 'Japanese (やさしいにほんご)',
         'zh': 'Chinese (简体中文)'
       }
       return specialLanguageNames[code] || ISO6391.getName(code)
diff --git a/src/i18n/ja.json b/src/i18n/ja.json
index 592a7257..2ca7dca8 100644
--- a/src/i18n/ja.json
+++ b/src/i18n/ja.json
@@ -4,128 +4,128 @@
   },
   "exporter": {
     "export": "エクスポート",
-    "processing": "おまちください。しばらくすると、あなたのファイルをダウンロードするように、メッセージがでます。"
+    "processing": "処理中です。処理が完了すると、ファイルをダウンロードするよう指示があります。"
   },
   "features_panel": {
     "chat": "チャット",
     "gopher": "Gopher",
     "media_proxy": "メディアプロクシ",
-    "scope_options": "こうかいはんいせんたく",
-    "text_limit": "もじのかず",
-    "title": "ゆうこうなきのう",
+    "scope_options": "公開範囲選択",
+    "text_limit": "文字の数",
+    "title": "有効な機能",
     "who_to_follow": "おすすめユーザー"
   },
   "finder": {
-    "error_fetching_user": "ユーザーけんさくがエラーになりました。",
-    "find_user": "ユーザーをさがす"
+    "error_fetching_user": "ユーザー検索がエラーになりました。",
+    "find_user": "ユーザーを探す"
   },
   "general": {
-    "apply": "てきよう",
-    "submit": "そうしん",
-    "more": "つづき",
+    "apply": "適用",
+    "submit": "送信",
+    "more": "続き",
     "generic_error": "エラーになりました",
-    "optional": "かかなくてもよい",
-    "show_more": "つづきをみる",
+    "optional": "省略可",
+    "show_more": "もっと見る",
     "show_less": "たたむ",
     "cancel": "キャンセル",
-    "disable": "なし",
-    "enable": "あり",
-    "confirm": "たしかめる",
-    "verify": "たしかめる"
+    "disable": "無効",
+    "enable": "有効",
+    "confirm": "確認",
+    "verify": "検査"
   },
   "image_cropper": {
-    "crop_picture": "がぞうをきりぬく",
-    "save": "セーブ",
-    "save_without_cropping": "きりぬかずにセーブ",
+    "crop_picture": "画像を切り抜く",
+    "save": "保存",
+    "save_without_cropping": "切り抜かずに保存",
     "cancel": "キャンセル"
   },
   "importer": {
-    "submit": "そうしん",
-    "success": "インポートできました。",
-    "error": "インポートがエラーになりました。"
+    "submit": "送信",
+    "success": "正常にインポートされました。",
+    "error": "このファイルをインポートするとき、エラーが発生しました。"
   },
   "login": {
     "login": "ログイン",
     "description": "OAuthでログイン",
     "logout": "ログアウト",
     "password": "パスワード",
-    "placeholder": "れい: lain",
-    "register": "はじめる",
-    "username": "ユーザーめい",
-    "hint": "はなしあいにくわわるには、ログインしてください",
-    "authentication_code": "にんしょうコード",
-    "enter_recovery_code": "リカバリーコードをいれてください",
-    "enter_two_factor_code": "2-ファクターコードをいれてください",
+    "placeholder": "例: lain",
+    "register": "登録",
+    "username": "ユーザー名",
+    "hint": "会話に加わるには、ログインしてください",
+    "authentication_code": "認証コード",
+    "enter_recovery_code": "リカバリーコードを入力してください",
+    "enter_two_factor_code": "2段階認証コードを入力してください",
     "recovery_code": "リカバリーコード",
     "heading" : {
-      "totp" : "2-ファクターにんしょう",
-      "recovery" : "2-ファクターリカバリー"
+      "totp" : "2段階認証",
+      "recovery" : "2段階リカバリー"
     }
   },
   "media_modal": {
-    "previous": "まえ",
-    "next": "つぎ"
+    "previous": "前",
+    "next": "次"
   },
   "nav": {
-    "about": "これはなに?",
-    "back": "もどる",
+    "about": "このインスタンスについて",
+    "back": "戻る",
     "chat": "ローカルチャット",
     "friend_requests": "フォローリクエスト",
-    "mentions": "メンション",
-    "interactions": "やりとり",
+    "mentions": "通知",
+    "interactions": "インタラクション",
     "dms": "ダイレクトメッセージ",
     "public_tl": "パブリックタイムライン",
     "timeline": "タイムライン",
-    "twkn": "つながっているすべてのネットワーク",
-    "user_search": "ユーザーをさがす",
-    "search": "さがす",
+    "twkn": "接続しているすべてのネットワーク",
+    "user_search": "ユーザーを探す",
+    "search": "検索",
     "who_to_follow": "おすすめユーザー",
-    "preferences": "せってい"
+    "preferences": "設定"
   },
   "notifications": {
-    "broken_favorite": "ステータスがみつかりません。さがしています...",
-    "favorited_you": "あなたのステータスがおきにいりされました",
+    "broken_favorite": "ステータスが見つかりません。探しています...",
+    "favorited_you": "あなたのステータスがお気に入りされました",
     "followed_you": "フォローされました",
-    "load_older": "ふるいつうちをみる",
-    "notifications": "つうち",
-    "read": "よんだ!",
+    "load_older": "古い通知をみる",
+    "notifications": "通知",
+    "read": "読んだ!",
     "repeated_you": "あなたのステータスがリピートされました",
-    "no_more_notifications": "つうちはありません"
+    "no_more_notifications": "通知はありません"
   },
   "polls": {
-    "add_poll": "いれふだをはじめる",
-    "add_option": "オプションをふやす",
-    "option": "オプション",
-    "votes": "いれふだ",
-    "vote": "ふだをいれる",
-    "type": "いれふだのかた",
-    "single_choice": "ひとつえらぶ",
-    "multiple_choices": "いくつでもえらべる",
-    "expiry": "いれふだのながさ",
-    "expires_in": "いれふだは {0} で、おわります",
-    "expired": "いれふだは {0} まえに、おわりました",
-    "not_enough_options": "ユニークなオプションが、たりません"
+    "add_poll": "投票を追加",
+    "add_option": "選択肢を追加",
+    "option": "選択肢",
+    "votes": "票",
+    "vote": "投票",
+    "type": "投票の形式",
+    "single_choice": "択一式",
+    "multiple_choices": "複数選択式",
+    "expiry": "投票期間",
+    "expires_in": "投票は {0} で終了します",
+    "expired": "投票は {0} 前に終了しました",
+    "not_enough_options": "相異なる選択肢が不足しています"
   },
   "emoji": {
     "stickers": "ステッカー",
-    "emoji": "えもじ",
-    "keep_open": "ピッカーをあけたままにする",
-    "search_emoji": "えもじをさがす",
-    "add_emoji": "えもじをうちこむ",
-    "custom": "カスタムえもじ",
-    "unicode": "ユニコードえもじ"
+    "emoji": "絵文字",
+    "keep_open": "ピッカーを開いたままにする",
+    "search_emoji": "絵文字を検索",
+    "add_emoji": "絵文字を挿入",
+    "custom": "カスタム絵文字",
+    "unicode": "Unicode絵文字"
   },
   "stickers": {
-    "add_sticker": "ステッカーをふやす"
+    "add_sticker": "ステッカーを追加"
   },
   "interactions": {
-    "favs_repeats": "リピートとおきにいり",
-    "follows": "あたらしいフォロー",
-    "load_older": "ふるいやりとりをみる"
+    "favs_repeats": "リピートとお気に入り",
+    "follows": "新しいフォロワー",
+    "load_older": "古いインタラクションを見る"
   },
   "post_status": {
-    "new_status": "とうこうする",
-    "account_not_locked_warning": "あなたのアカウントは {0} ではありません。あなたをフォローすれば、だれでも、フォロワーげんていのステータスをよむことができます。",
+    "new_status": "投稿する",
+    "account_not_locked_warning": "あなたのアカウントは {0} ではありません。あなたをフォローすれば、誰でも、フォロワー限定のステータスを読むことができます。",
     "account_not_locked_warning_link": "ロックされたアカウント",
     "attachments_sensitive": "ファイルをNSFWにする",
     "content_type": {
@@ -134,131 +134,131 @@
       "text/markdown": "Markdown",
       "text/bbcode": "BBCode"
     },
-    "content_warning": "せつめい (かかなくてもよい)",
-    "default": "はねだくうこうに、つきました。",
-    "direct_warning_to_all": "このとうこうは、メンションされたすべてのユーザーが、みることができます。",
-    "direct_warning_to_first_only": "このとうこうは、メッセージのはじめでメンションされたユーザーだけが、みることができます。",
-    "direct_warning": "このステータスは、メンションされたユーザーだけが、よむことができます。",
-    "posting": "とうこう",
+    "content_warning": "説明 (省略可)",
+    "default": "羽田空港に着きました。",
+    "direct_warning_to_all": "この投稿は、メンションされたすべてのユーザーが、見ることができます。",
+    "direct_warning_to_first_only": "この投稿は、メッセージの冒頭でメンションされたユーザーだけが、見ることができます。",
+    "direct_warning": "このステータスは、メンションされたユーザーだけが、読むことができます。",
+    "posting": "投稿",
     "scope_notice": {
-      "public": "このとうこうは、だれでもみることができます",
-      "private": "このとうこうは、あなたのフォロワーだけが、みることができます",
-      "unlisted": "このとうこうは、パブリックタイムラインと、つながっているすべてのネットワークでは、みることができません"
+      "public": "この投稿は、誰でも見ることができます",
+      "private": "この投稿は、あなたのフォロワーだけが、見ることができます。",
+      "unlisted": "この投稿は、パブリックタイムラインと、接続しているすべてのネットワークには、表示されません。"
     },
     "scope": {
-      "direct": "ダイレクト: メンションされたユーザーのみにとどきます。",
-      "private": "フォロワーげんてい: フォロワーのみにとどきます。",
-      "public": "パブリック: パブリックタイムラインにとどきます。",
-      "unlisted": "アンリステッド: パブリックタイムラインにとどきません。"
+      "direct": "ダイレクト: メンションされたユーザーのみに届きます。",
+      "private": "フォロワーげんてい: フォロワーのみに届きます。",
+      "public": "パブリック: パブリックタイムラインに届きます。",
+      "unlisted": "アンリステッド: パブリックタイムラインに届きません。"
     }
   },
   "registration": {
     "bio": "プロフィール",
     "email": "Eメール",
     "fullname": "スクリーンネーム",
-    "password_confirm": "パスワードのかくにん",
-    "registration": "はじめる",
-    "token": "しょうたいトークン",
+    "password_confirm": "パスワードの確認",
+    "registration": "登録",
+    "token": "招待トークン",
     "captcha": "CAPTCHA",
-    "new_captcha": "もじがよめないときは、がぞうをクリックすると、あたらしいがぞうになります",
-    "username_placeholder": "れい: lain",
-    "fullname_placeholder": "れい: いわくら れいん",
-    "bio_placeholder": "れい:\nごきげんよう。わたしはれいん。\nわたしはアニメのおんなのこで、にほんのベッドタウンにすんでいます。ワイヤードで、わたしにあったことが、あるかもしれませんね。",
+    "new_captcha": "文字が読めないときは、画像をクリックすると、新しい画像になります",
+    "username_placeholder": "例: lain",
+    "fullname_placeholder": "例: 岩倉玲音",
+    "bio_placeholder": "例:\nこんにちは。私は玲音。\n私はアニメのキャラクターで、日本の郊外に住んでいます。私をWiredで見たことがあるかもしれません。",
     "validations": {
-      "username_required": "なにかかいてください",
-      "fullname_required": "なにかかいてください",
-      "email_required": "なにかかいてください",
-      "password_required": "なにかかいてください",
-      "password_confirmation_required": "なにかかいてください",
-      "password_confirmation_match": "パスワードがちがいます"
+      "username_required": "必須",
+      "fullname_required": "必須",
+      "email_required": "必須",
+      "password_required": "必須",
+      "password_confirmation_required": "必須",
+      "password_confirmation_match": "パスワードが違います"
     }
   },
   "selectable_list": {
-    "select_all": "すべてえらぶ"
+    "select_all": "すべて選択"
   },
   "settings": {
-    "app_name": "アプリのなまえ",
+    "app_name": "アプリの名称",
     "security": "セキュリティ",
-    "enter_current_password_to_confirm": "あなたのアイデンティティをたしかめるため、あなたのいまのパスワードをかいてください",
+    "enter_current_password_to_confirm": "あなたのアイデンティティを証明するため、現在のパスワードを入力してください",
     "mfa": {
       "otp" : "OTP",
-      "setup_otp" : "OTPをつくる",
-      "wait_pre_setup_otp" : "OTPをよういしています",
-      "confirm_and_enable" : "OTPをたしかめて、ゆうこうにする",
-      "title": "2-ファクターにんしょう",
-      "generate_new_recovery_codes" : "あたらしいリカバリーコードをつくる",
-      "warning_of_generate_new_codes" : "あたらしいリカバリーコードをつくったら、ふるいコードはつかえなくなります。",
+      "setup_otp" : "OTPのセットアップ",
+      "wait_pre_setup_otp" : "OTPのプリセット",
+      "confirm_and_enable" : "OTPの確認と有効化",
+      "title": "2段階認証",
+      "generate_new_recovery_codes" : "新しいリカバリーコードを生成",
+      "warning_of_generate_new_codes" : "新しいリカバリーコードを生成すると、古いコードは使用できなくなります。",
       "recovery_codes" : "リカバリーコード。",
-      "waiting_a_recovery_codes": "バックアップコードをうけとっています...",
-      "recovery_codes_warning" : "コードをかきうつすか、ひとにみられないところにセーブしてください。そうでなければ、あなたはこのコードをふたたびみることはできません。もしあなたが、2FAアプリのアクセスをうしなって、なおかつ、リカバリーコードもおもいだせないならば、あなたはあなたのアカウントから、しめだされます。",
-      "authentication_methods" : "にんしょうメソッド",
+      "waiting_a_recovery_codes": "バックアップコードを受信しています...",
+      "recovery_codes_warning" : "コードを紙に書くか、安全な場所に保存してください。そうでなければ、あなたはコードを再び見ることはできません。もし2段階認証アプリのアクセスを喪失し、なおかつ、リカバリーコードもないならば、あなたは自分のアカウントから閉め出されます。",
+      "authentication_methods" : "認証方法",
       "scan": {
         "title": "スキャン",
-        "desc": "あなたの2-ファクターアプリをつかって、このQRコードをスキャンするか、テキストキーをうちこんでください:",
+        "desc": "あなたの2段階認証アプリを使って、このQRコードをスキャンするか、テキストキーを入力してください:",
         "secret_code": "キー"
       },
       "verify": {
-        "desc": "2-ファクターにんしょうをつかうには、あなたの2-ファクターアプリのコードをいれてください:"
+        "desc": "2段階認証を有効にするには、あなたの2段階認証アプリのコードを入力してください:"
       }
     },
     "attachmentRadius": "ファイル",
     "attachments": "ファイル",
-    "autoload": "したにスクロールしたとき、じどうてきによみこむ。",
+    "autoload": "下にスクロールしたとき、自動的に読み込む。",
     "avatar": "アバター",
-    "avatarAltRadius": "つうちのアバター",
+    "avatarAltRadius": "通知のアバター",
     "avatarRadius": "アバター",
     "background": "バックグラウンド",
     "bio": "プロフィール",
     "block_export": "ブロックのエクスポート",
-    "block_export_button": "ブロックをCSVファイルにエクスポート",
+    "block_export_button": "ブロックをCSVファイルにエクスポートする",
     "block_import": "ブロックのインポート",
-    "block_import_error": "ブロックのインポートがエラーになりました",
-    "blocks_imported": "ブロックをインポートしました! じっさいにブロックするまでには、もうしばらくかかります。",
+    "block_import_error": "ブロックのインポートに失敗しました",
+    "blocks_imported": "ブロックをインポートしました! 実際に処理されるまでに、しばらく時間がかかります。",
     "blocks_tab": "ブロック",
     "btnRadius": "ボタン",
-    "cBlue": "リプライとフォロー",
+    "cBlue": "返信とフォロー",
     "cGreen": "リピート",
-    "cOrange": "おきにいり",
+    "cOrange": "お気に入り",
     "cRed": "キャンセル",
-    "change_password": "パスワードをかえる",
-    "change_password_error": "パスワードをかえることが、できなかったかもしれません。",
-    "changed_password": "パスワードが、かわりました!",
-    "collapse_subject": "せつめいのあるとうこうをたたむ",
-    "composing": "とうこう",
-    "confirm_new_password": "あたらしいパスワードのかくにん",
-    "current_avatar": "いまのアバター",
-    "current_password": "いまのパスワード",
-    "current_profile_banner": "いまのプロフィールバナー",
+    "change_password": "パスワードを変える",
+    "change_password_error": "パスワードを変えることが、できなかったかもしれません。",
+    "changed_password": "パスワードが、変わりました!",
+    "collapse_subject": "説明のある投稿をたたむ",
+    "composing": "投稿",
+    "confirm_new_password": "新しいパスワードの確認",
+    "current_avatar": "現在のアバター",
+    "current_password": "現在のパスワード",
+    "current_profile_banner": "現在のプロフィールバナー",
     "data_import_export_tab": "インポートとエクスポート",
-    "default_vis": "デフォルトのこうかいはんい",
-    "delete_account": "アカウントをけす",
-    "delete_account_description": "あなたのアカウントとメッセージが、きえます。",
-    "delete_account_error": "アカウントをけすことが、できなかったかもしれません。インスタンスのアドミニストレーターに、おといあわせください。",
-    "delete_account_instructions": "ほんとうにアカウントをけしてもいいなら、パスワードをかいてください。",
-    "discoverable": "けんさくなどのサービスで、このアカウントをみつけてもよい",
-    "avatar_size_instruction": "アバターのおおきさは、150×150ピクセルか、それよりもおおきくするといいです。",
-    "pad_emoji": "えもじをピッカーでえらんだとき、えもじのまわりにスペースをいれる",
-    "export_theme": "セーブ",
+    "default_vis": "デフォルトの公開範囲",
+    "delete_account": "アカウントを消す",
+    "delete_account_description": "あなたのアカウントとメッセージが、消えます。",
+    "delete_account_error": "アカウントを消すことが、できなかったかもしれません。インスタンスの管理者に、連絡してください。",
+    "delete_account_instructions": "本当にアカウントを消してもいいなら、パスワードを入力してください。",
+    "discoverable": "検索などのサービスでこのアカウントを見つけることを許可する",
+    "avatar_size_instruction": "アバターの大きさは、150×150ピクセルか、それよりも大きくするといいです。",
+    "pad_emoji": "ピッカーから絵文字を挿入するとき、絵文字の両側にスペースを入れる",
+    "export_theme": "保存",
     "filtering": "フィルタリング",
-    "filtering_explanation": "これらのことばをふくむすべてのものがミュートされます。1ぎょうに1つのことばをかいてください。",
+    "filtering_explanation": "これらの言葉を含むすべてのものがミュートされます。1行に1つの言葉を書いてください。",
     "follow_export": "フォローのエクスポート",
     "follow_export_button": "エクスポート",
-    "follow_export_processing": "おまちください。まもなくファイルをダウンロードできます。",
-    "follow_import": "フォローインポート",
+    "follow_export_processing": "お待ちください。まもなくファイルをダウンロードできます。",
+    "follow_import": "フォローのインポート",
     "follow_import_error": "フォローのインポートがエラーになりました。",
-    "follows_imported": "フォローがインポートされました! すこしじかんがかかるかもしれません。",
+    "follows_imported": "フォローがインポートされました! 少し時間がかかるかもしれません。",
     "foreground": "フォアグラウンド",
-    "general": "ぜんぱん",
-    "hide_attachments_in_convo": "スレッドのファイルをかくす",
-    "hide_attachments_in_tl": "タイムラインのファイルをかくす",
-    "hide_muted_posts": "ミュートしたユーザーのとうこうをかくす",
-    "max_thumbnails": "ひとつのとうこうにいれられるサムネイルのかず",
-    "hide_isp": "インスタンススペシフィックパネルをかくす",
-    "preload_images": "がぞうをさきよみする",
-    "use_one_click_nsfw": "NSFWなファイルを1クリックでひらく",
-    "hide_post_stats": "とうこうのとうけいをかくす (れい: おきにいりのかず)",
-    "hide_user_stats": "ユーザーのとうけいをかくす (れい: フォロワーのかず)",
-    "hide_filtered_statuses": "フィルターされたとうこうをかくす",
+    "general": "全般",
+    "hide_attachments_in_convo": "スレッドのファイルを隠す",
+    "hide_attachments_in_tl": "タイムラインのファイルを隠す",
+    "hide_muted_posts": "ミュートしているユーザーの投稿を隠す",
+    "max_thumbnails": "投稿に含まれるサムネイルの最大数",
+    "hide_isp": "インスタンス固有パネルを隠す",
+    "preload_images": "画像を先読みする",
+    "use_one_click_nsfw": "NSFWなファイルを1クリックで開く",
+    "hide_post_stats": "投稿の統計を隠す (例: お気に入りの数)",
+    "hide_user_stats": "ユーザーの統計を隠す (例: フォロワーの数)",
+    "hide_filtered_statuses": "フィルターされた投稿を隠す",
     "import_blocks_from_a_csv_file": "CSVファイルからブロックをインポートする",
     "import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
     "import_theme": "ロード",
@@ -267,198 +267,198 @@
     "instance_default": "(デフォルト: {value})",
     "instance_default_simple": "(デフォルト)",
     "interface": "インターフェース",
-    "interfaceLanguage": "インターフェースのことば",
-    "invalid_theme_imported": "このファイルはPleromaのテーマではありません。テーマはへんこうされませんでした。",
+    "interfaceLanguage": "インターフェースの言語",
+    "invalid_theme_imported": "このファイルはPleromaのテーマではありません。テーマは変更されませんでした。",
     "limited_availability": "あなたのブラウザではできません",
     "links": "リンク",
-    "lock_account_description": "あなたがみとめたひとだけ、あなたのアカウントをフォローできる",
-    "loop_video": "ビデオをくりかえす",
-    "loop_video_silent_only": "おとのないビデオだけくりかえす",
+    "lock_account_description": "あなたが認めた人だけ、あなたのアカウントをフォローできる",
+    "loop_video": "ビデオを繰り返す",
+    "loop_video_silent_only": "音のないビデオだけ繰り返す",
     "mutes_tab": "ミュート",
-    "play_videos_in_modal": "ビデオをメディアビューアーでみる",
-    "use_contain_fit": "がぞうのサムネイルを、きりぬかない",
-    "name": "なまえ",
-    "name_bio": "なまえとプロフィール",
-    "new_password": "あたらしいパスワード",
-    "notification_visibility": "ひょうじするつうち",
+    "play_videos_in_modal": "ビデオをメディアビューアーで見る",
+    "use_contain_fit": "画像のサムネイルを、切り抜かない",
+    "name": "名前",
+    "name_bio": "名前とプロフィール",
+    "new_password": "新しいパスワード",
+    "notification_visibility": "表示する通知",
     "notification_visibility_follows": "フォロー",
-    "notification_visibility_likes": "おきにいり",
+    "notification_visibility_likes": "お気に入り",
     "notification_visibility_mentions": "メンション",
     "notification_visibility_repeats": "リピート",
-    "no_rich_text_description": "リッチテキストをつかわない",
-    "no_blocks": "ブロックしていません",
-    "no_mutes": "ミュートしていません",
-    "hide_follows_description": "フォローしているひとをみせない",
-    "hide_followers_description": "フォロワーをみせない",
-    "hide_follows_count_description": "フォローしているひとのかずをみせない",
-    "hide_followers_count_description": "フォロワーのかずをみせない",
-    "show_admin_badge": "アドミンのしるしをみせる",
-    "show_moderator_badge": "モデレーターのしるしをみせる",
-    "nsfw_clickthrough": "NSFWなファイルをかくす",
+    "no_rich_text_description": "リッチテキストを使わない",
+    "no_blocks": "ブロックはありません",
+    "no_mutes": "ミュートはありません",
+    "hide_follows_description": "フォローしている人を見せない",
+    "hide_followers_description": "フォロワーを見せない",
+    "hide_follows_count_description": "フォローしている人の数を見せない",
+    "hide_followers_count_description": "フォロワーの数を見せない",
+    "show_admin_badge": "管理者のバッジを見せる",
+    "show_moderator_badge": "モデレーターのバッジを見せる",
+    "nsfw_clickthrough": "NSFWなファイルを隠す",
     "oauth_tokens": "OAuthトークン",
     "token": "トークン",
-    "refresh_token": "トークンをリフレッシュ",
-    "valid_until": "おわりのとき",
-    "revoke_token": "とりけす",
+    "refresh_token": "トークンを更新",
+    "valid_until": "まで有効",
+    "revoke_token": "取り消す",
     "panelRadius": "パネル",
-    "pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる",
+    "pause_on_unfocused": "タブにフォーカスがないときストリーミングを止める",
     "presets": "プリセット",
     "profile_background": "プロフィールのバックグラウンド",
     "profile_banner": "プロフィールバナー",
     "profile_tab": "プロフィール",
-    "radii_help": "インターフェースのまるさをせっていする。",
+    "radii_help": "インターフェースの丸さを設定する。",
     "replies_in_timeline": "タイムラインのリプライ",
-    "reply_link_preview": "カーソルをかさねたとき、リプライのプレビューをみる",
-    "reply_visibility_all": "すべてのリプライをみる",
-    "reply_visibility_following": "わたしにあてられたリプライと、フォローしているひとからのリプライをみる",
-    "reply_visibility_self": "わたしにあてられたリプライをみる",
-    "autohide_floating_post_button": "あたらしいとうこうのボタンを、じどうてきにかくす (モバイル)",
-    "saving_err": "せっていをセーブできませんでした",
-    "saving_ok": "せっていをセーブしました",
-    "search_user_to_block": "ブロックしたいひとを、ここでけんさくできます",
-    "search_user_to_mute": "ミュートしたいひとを、ここでけんさくできます",
+    "reply_link_preview": "カーソルを重ねたとき、リプライのプレビューを見る",
+    "reply_visibility_all": "すべてのリプライを見る",
+    "reply_visibility_following": "私に宛てられたリプライと、フォローしている人からのリプライを見る",
+    "reply_visibility_self": "私に宛てられたリプライを見る",
+    "autohide_floating_post_button": "新しい投稿ボタンを自動的に隠す (モバイル)",
+    "saving_err": "設定を保存できませんでした",
+    "saving_ok": "設定を保存しました",
+    "search_user_to_block": "ブロックしたいユーザーを検索",
+    "search_user_to_mute": "ミュートしたいユーザーを検索",
     "security_tab": "セキュリティ",
-    "scope_copy": "リプライするとき、こうかいはんいをコピーする (DMのこうかいはんいは、つねにコピーされます)",
-    "minimal_scopes_mode": "こうかいはんいせんたくオプションを、ちいさくする",
-    "set_new_avatar": "あたらしいアバターをせっていする",
-    "set_new_profile_background": "あたらしいプロフィールのバックグラウンドをせっていする",
-    "set_new_profile_banner": "あたらしいプロフィールバナーを設定する",
-    "settings": "せってい",
-    "subject_input_always_show": "サブジェクトフィールドをいつでもひょうじする",
-    "subject_line_behavior": "リプライするときサブジェクトをコピーする",
-    "subject_line_email": "メールふう: \"re: サブジェクト\"",
-    "subject_line_mastodon": "マストドンふう: そのままコピー",
+    "scope_copy": "返信するとき、公開範囲をコピーする (DMの公開範囲は、常にコピーされます)",
+    "minimal_scopes_mode": "公開範囲選択オプションを最小にする",
+    "set_new_avatar": "新しいアバターを設定する",
+    "set_new_profile_background": "新しいプロフィールのバックグラウンドを設定する",
+    "set_new_profile_banner": "新しいプロフィールバナーを設定する",
+    "settings": "設定",
+    "subject_input_always_show": "サブジェクトフィールドをいつでも表示する",
+    "subject_line_behavior": "返信するときサブジェクトをコピーする",
+    "subject_line_email": "メール風: \"re: サブジェクト\"",
+    "subject_line_mastodon": "マストドン風: そのままコピー",
     "subject_line_noop": "コピーしない",
-    "post_status_content_type": "とうこうのコンテントタイプ",
-    "stop_gifs": "カーソルをかさねたとき、GIFをうごかす",
-    "streaming": "うえまでスクロールしたとき、じどうてきにストリーミングする",
-    "text": "もじ",
+    "post_status_content_type": "投稿のコンテントタイプ",
+    "stop_gifs": "カーソルを重ねたとき、GIFを動かす",
+    "streaming": "上までスクロールしたとき、自動的にストリーミングする",
+    "text": "文字",
     "theme": "テーマ",
     "theme_help": "カラーテーマをカスタマイズできます",
-    "theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、いろと、とうめいどを、オーバーライドできます。「すべてクリア」ボタンをおすと、すべてのオーバーライドを、やめます。",
-    "theme_help_v2_2": "バックグラウンドとテキストのコントラストをあらわすアイコンがあります。マウスをホバーすると、くわしいせつめいがでます。とうめいないろをつかっているときは、もっともわるいばあいのコントラストがしめされます。",
-    "upload_a_photo": "がぞうをアップロード",
+    "theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、色と透明度をオーバーライドできます。「すべてクリア」ボタンを押すと、すべてのオーバーライドをやめます。",
+    "theme_help_v2_2": "バックグラウンドとテキストのコントラストを表すアイコンがあります。マウスをホバーすると、詳しい説明が出ます。透明な色を使っているときは、最悪の場合のコントラストが示されます。",
     "tooltipRadius": "ツールチップとアラート",
-    "user_settings": "ユーザーせってい",
+    "upload_a_photo": "画像をアップロード",
+    "user_settings": "ユーザー設定",
     "values": {
       "false": "いいえ",
       "true": "はい"
     },
-    "notifications": "つうち",
-    "notification_setting": "つうちをうけとる:",
-    "notification_setting_follows": "あなたがフォローしているひとから",
-    "notification_setting_non_follows": "あなたがフォローしていないひとから",
-    "notification_setting_followers": "あなたをフォローしているひとから",
-    "notification_setting_non_followers": "あなたをフォローしていないひとから",
-    "notification_mutes": "あるユーザーからのつうちをとめるには、ミュートしてください。",
-    "notification_blocks": "ブロックしているユーザーからのつうちは、すべてとまります。",
-    "enable_web_push_notifications": "ウェブプッシュつうちをゆるす",
+    "notifications": "通知",
+    "notification_setting": "通知を受け取る:",
+    "notification_setting_follows": "あなたがフォローしているユーザーから",
+    "notification_setting_non_follows": "あなたがフォローしていないユーザーから",
+    "notification_setting_followers": "あなたをフォローしているユーザーから",
+    "notification_setting_non_followers": "あなたをフォローしていないユーザーから",
+    "notification_mutes": "特定のユーザーからの通知を止めるには、ミュートしてください。",
+    "notification_blocks": "ブロックしているユーザーからの通知は、すべて止まります。",
+    "enable_web_push_notifications": "ウェブプッシュ通知を許可する",
     "style": {
       "switcher": {
-        "keep_color": "いろをのこす",
-        "keep_shadows": "かげをのこす",
-        "keep_opacity": "とうめいどをのこす",
-        "keep_roundness": "まるさをのこす",
-        "keep_fonts": "フォントをのこす",
-        "save_load_hint": "「のこす」オプションをONにすると、テーマをえらんだときとロードしたとき、いまのせっていをのこします。また、テーマをエクスポートするとき、これらのオプションをストアします。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべてのせっていをセーブします。",
+        "keep_color": "色を残す",
+        "keep_shadows": "影を残す",
+        "keep_opacity": "透明度を残す",
+        "keep_roundness": "丸さを残す",
+        "keep_fonts": "フォントを残す",
+        "save_load_hint": "「残す」オプションをONにすると、テーマを選んだときとロードしたとき、現在の設定を残します。また、テーマをエクスポートするとき、これらのオプションを維持します。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべての設定を保存します。",
         "reset": "リセット",
         "clear_all": "すべてクリア",
-        "clear_opacity": "とうめいどをクリア"
+        "clear_opacity": "透明度をクリア"
       },
       "common": {
-        "color": "いろ",
-        "opacity": "とうめいど",
+        "color": "色",
+        "opacity": "透明度",
         "contrast": {
           "hint": "コントラストは {ratio} です。{level}。({context})",
           "level": {
-            "aa": "AAレベルガイドライン (ミニマル) をみたします",
-            "aaa": "AAAレベルガイドライン (レコメンデッド) をみたします。",
-            "bad": "ガイドラインをみたしません。"
+            "aa": "AAレベルガイドライン (ミニマル) を満たします",
+            "aaa": "AAAレベルガイドライン (レコメンデッド) を満たします。",
+            "bad": "ガイドラインを満たしません。"
           },
           "context": {
-            "18pt": "おおきい (18ポイントいじょう) テキスト",
+            "18pt": "大きい (18ポイント以上) テキスト",
             "text": "テキスト"
           }
         }
       },
       "common_colors": {
-        "_tab_label": "きょうつう",
-        "main": "きょうつうのいろ",
-        "foreground_hint": "「くわしく」タブで、もっとこまかくせっていできます",
+        "_tab_label": "共通",
+        "main": "共通の色",
+        "foreground_hint": "「詳細」タブで、もっと細かく設定できます",
         "rgbo": "アイコンとアクセントとバッジ"
       },
       "advanced_colors": {
-        "_tab_label": "くわしく",
+        "_tab_label": "詳細",
         "alert": "アラートのバックグラウンド",
         "alert_error": "エラー",
         "badge": "バッジのバックグラウンド",
-        "badge_notification": "つうち",
+        "badge_notification": "通知",
         "panel_header": "パネルヘッダー",
         "top_bar": "トップバー",
-        "borders": "さかいめ",
+        "borders": "境界",
         "buttons": "ボタン",
         "inputs": "インプットフィールド",
-        "faint_text": "うすいテキスト"
+        "faint_text": "薄いテキスト"
       },
       "radii": {
-        "_tab_label": "まるさ"
+        "_tab_label": "丸さ"
       },
       "shadows": {
-        "_tab_label": "ひかりとかげ",
+        "_tab_label": "光と影",
         "component": "コンポーネント",
         "override": "オーバーライド",
-        "shadow_id": "かげ #{value}",
+        "shadow_id": "影 #{value}",
         "blur": "ぼかし",
-        "spread": "ひろがり",
-        "inset": "うちがわ",
-        "hint": "かげのせっていでは、いろのあたいとして --variable をつかうことができます。これはCSS3へんすうです。ただし、とうめいどのせっていは、きかなくなります。",
+        "spread": "広がり",
+        "inset": "内側",
+        "hint": "影の設定では、色の値として --variable を使うことができます。これはCSS3変数です。ただし、透明度の設定は、効かなくなります。",
         "filter_hint": {
-          "always_drop_shadow": "ブラウザーがサポートしていれば、つねに {0} がつかわれます。",
+          "always_drop_shadow": "ブラウザーがサポートしていれば、常に {0} が使われます。",
           "drop_shadow_syntax": "{0} は、{1} パラメーターと {2} キーワードをサポートしていません。",
-          "avatar_inset": "うちがわのかげと、そとがわのかげを、いっしょにつかうと、とうめいなアバターが、へんなみためになります。",
-          "spread_zero": "ひろがりが 0 よりもおおきなかげは、0 とおなじです。",
-          "inset_classic": "うちがわのかげは {0} をつかいます。"
+          "avatar_inset": "内側の影と外側の影を同時に使うと、透明なアバターの表示が乱れます。",
+          "spread_zero": "広がりが 0 よりも大きな影は、0 と同じです。",
+          "inset_classic": "内側の影は {0} を使います。"
         },
         "components": {
           "panel": "パネル",
           "panelHeader": "パネルヘッダー",
           "topBar": "トップバー",
           "avatar": "ユーザーアバター (プロフィール)",
-          "avatarStatus": "ユーザーアバター (とうこう)",
+          "avatarStatus": "ユーザーアバター (投稿)",
           "popup": "ポップアップとツールチップ",
           "button": "ボタン",
           "buttonHover": "ボタン (ホバー)",
-          "buttonPressed": "ボタン (おされているとき)",
-          "buttonPressedHover": "ボタン (ホバー、かつ、おされているとき)",
+          "buttonPressed": "ボタン (押されているとき)",
+          "buttonPressedHover": "ボタン (ホバー、かつ、押されているとき)",
           "input": "インプットフィールド"
         }
       },
       "fonts": {
         "_tab_label": "フォント",
-        "help": "「カスタム」をえらんだときは、システムにあるフォントのなまえを、ただしくにゅうりょくしてください。",
+        "help": "「カスタム」を選んだときは、システムにあるフォントの名前を、正しく入力してください。",
         "components": {
           "interface": "インターフェース",
           "input": "インプットフィールド",
-          "post": "とうこう",
-          "postCode": "モノスペース (とうこうがリッチテキストであるとき)"
+          "post": "投稿",
+          "postCode": "等幅 (投稿がリッチテキストであるとき)"
         },
-        "family": "フォントめい",
-        "size": "おおきさ (px)",
-        "weight": "ふとさ",
+        "family": "フォント名",
+        "size": "大きさ (px)",
+        "weight": "太さ",
         "custom": "カスタム"
       },
       "preview": {
         "header": "プレビュー",
-        "content": "ほんぶん",
-        "error": "エラーのれい",
+        "content": "本文",
+        "error": "エラーの例",
         "button": "ボタン",
-        "text": "これは{0}と{1}のれいです。",
+        "text": "これは{0}と{1}の例です。",
         "mono": "monospace",
-        "input": "はねだくうこうに、つきました。",
-        "faint_link": "とてもたすけになるマニュアル",
-        "fine_print": "わたしたちの{0}を、よまないでください!",
+        "input": "羽田空港に着きました。",
+        "faint_link": "とても助けになるマニュアル",
+        "fine_print": "私たちの{0}を、読まないでください!",
         "header_faint": "エラーではありません",
-        "checkbox": "りようきやくを、よみました",
+        "checkbox": "利用規約を読みました",
         "link": "ハイパーリンク"
       }
     },
@@ -505,38 +505,38 @@
   "timeline": {
     "collapse": "たたむ",
     "conversation": "スレッド",
-    "error_fetching": "よみこみがエラーになりました",
-    "load_older": "ふるいステータス",
-    "no_retweet_hint": "とうこうを「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります",
+    "error_fetching": "読み込みがエラーになりました",
+    "load_older": "古いステータス",
+    "no_retweet_hint": "投稿を「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります",
     "repeated": "リピート",
-    "show_new": "よみこみ",
-    "up_to_date": "さいしん",
-    "no_more_statuses": "これでおわりです",
-    "no_statuses": "ありません"
+    "show_new": "読み込み",
+    "up_to_date": "最新",
+    "no_more_statuses": "これで終わりです",
+    "no_statuses": "ステータスはありません"
   },
   "status": {
-    "favorites": "おきにいり",
+    "favorites": "お気に入り",
     "repeats": "リピート",
-    "delete": "ステータスをけす",
-    "pin": "プロフィールにピンどめする",
-    "unpin": "プロフィールにピンどめするのをやめる",
-    "pinned": "ピンどめ",
-    "delete_confirm": "ほんとうに、このステータスを、けしてもいいですか?",
-    "reply_to": "へんしん:",
-    "replies_list": "へんしん:",
-    "mute_conversation": "スレッドをミュートする",
-    "unmute_conversation": "スレッドをミュートするのをやめる"
+    "delete": "ステータスを削除",
+    "pin": "プロフィールにピン留め",
+    "unpin": "プロフィールのピン留めを外す",
+    "pinned": "ピン留め",
+    "delete_confirm": "本当にこのステータスを削除してもよろしいですか?",
+    "reply_to": "返信",
+    "replies_list": "返信:",
+    "mute_conversation": "スレッドをミュート",
+    "unmute_conversation": "スレッドのミュートを解除"
   },
   "user_card": {
-    "approve": "うけいれ",
+    "approve": "受け入れ",
     "block": "ブロック",
     "blocked": "ブロックしています!",
-    "deny": "おことわり",
-    "favorites": "おきにいり",
+    "deny": "お断り",
+    "favorites": "お気に入り",
     "follow": "フォロー",
-    "follow_sent": "リクエストを、おくりました!",
+    "follow_sent": "リクエストを送りました!",
     "follow_progress": "リクエストしています…",
-    "follow_again": "ふたたびリクエストをおくりますか?",
+    "follow_again": "再びリクエストを送りますか?",
     "follow_unfollow": "フォローをやめる",
     "followees": "フォロー",
     "followers": "フォロワー",
@@ -549,66 +549,66 @@
     "muted": "ミュートしています!",
     "per_day": "/日",
     "remote_follow": "リモートフォロー",
-    "report": "つうほう",
+    "report": "通報",
     "statuses": "ステータス",
-    "subscribe": "サブスクライブ",
-    "unsubscribe": "サブスクライブをやめる",
-    "unblock": "ブロックをやめる",
-    "unblock_progress": "ブロックをとりけしています...",
+    "subscribe": "購読",
+    "unsubscribe": "購読を解除",
+    "unblock": "ブロック解除",
+    "unblock_progress": "ブロックを解除しています...",
     "block_progress": "ブロックしています...",
-    "unmute": "ミュートをやめる",
-    "unmute_progress": "ミュートをとりけしています...",
+    "unmute": "ミュート解除",
+    "unmute_progress": "ミュートを解除しています...",
     "mute_progress": "ミュートしています...",
     "admin_menu": {
       "moderation": "モデレーション",
-      "grant_admin": "アドミンにする",
-      "revoke_admin": "アドミンをやめさせる",
-      "grant_moderator": "モデレーターにする",
-      "revoke_moderator": "モデレーターをやめさせる",
+      "grant_admin": "管理者権限を付与",
+      "revoke_admin": "管理者権限を解除",
+      "grant_moderator": "モデレーター権限を付与",
+      "revoke_moderator": "モデレーター権限を解除",
       "activate_account": "アカウントをアクティブにする",
       "deactivate_account": "アカウントをアクティブでなくする",
-      "delete_account": "アカウントをけす",
-      "force_nsfw": "すべてのとうこうをNSFWにする",
-      "strip_media": "とうこうからメディアをなくす",
-      "force_unlisted": "とうこうをアンリステッドにする",
-      "sandbox": "とうこうをフォロワーのみにする",
-      "disable_remote_subscription": "ほかのインスタンスからフォローされないようにする",
+      "delete_account": "アカウントを削除",
+      "force_nsfw": "すべての投稿をNSFWにする",
+      "strip_media": "投稿からメディアを除去する",
+      "force_unlisted": "投稿を未収載にする",
+      "sandbox": "投稿をフォロワーのみにする",
+      "disable_remote_subscription": "他のインスタンスからフォローされないようにする",
       "disable_any_subscription": "フォローされないようにする",
-      "quarantine": "ほかのインスタンスのユーザーのとうこうをとめる",
-      "delete_user": "ユーザーをけす",
-      "delete_user_confirmation": "あなたは、ほんとうに、きはたしかですか? これは、とりけすことが、できません。"
+      "quarantine": "他のインスタンスからの投稿を止める",
+      "delete_user": "ユーザーを削除",
+      "delete_user_confirmation": "あなたの精神状態に何か問題はございませんか? この操作を取り消すことはできません。"
     }
   },
   "user_profile": {
     "timeline_title": "ユーザータイムライン",
-    "profile_does_not_exist": "ごめんなさい。このプロフィールは、そんざいしません。",
-    "profile_loading_error": "ごめんなさい。プロフィールのロードがエラーになりました。"
+    "profile_does_not_exist": "申し訳ない。このプロフィールは存在しません。",
+    "profile_loading_error": "申し訳ない。プロフィールの読み込みがエラーになりました。"
   },
   "user_reporting": {
-    "title": "つうほうする: {0}",
-    "add_comment_description": "このつうほうは、あなたのインスタンスのモデレーターに、おくられます。このアカウントを、つうほうするりゆうを、せつめいすることができます:",
-    "additional_comments": "ついかのコメント",
-    "forward_description": "このアカウントは、ほかのインスタンスのものです。そのインスタンスにも、このつうほうのコピーを、おくりますか?",
-    "forward_to": "コピーをおくる: {0}",
-    "submit": "そうしん",
-    "generic_error": "あなたのリクエストをうけつけようとしましたが、エラーになってしまいました。"
+    "title": "通報する: {0}",
+    "add_comment_description": "この通報は、あなたのインスタンスのモデレーターに送られます。このアカウントを通報する理由を説明することができます:",
+    "additional_comments": "追加のコメント",
+    "forward_description": "このアカウントは他のサーバーに置かれています。この通報のコピーをリモートのサーバーに送りますか?",
+    "forward_to": "転送する: {0}",
+    "submit": "送信",
+    "generic_error": "あなたのリクエストを処理しようとしましたが、エラーになりました。"
   },
   "who_to_follow": {
-    "more": "くわしく",
+    "more": "詳細",
     "who_to_follow": "おすすめユーザー"
   },
   "tool_tip": {
     "media_upload": "メディアをアップロード",
     "repeat": "リピート",
-    "reply": "リプライ",
-    "favorite": "おきにいり",
-    "user_settings": "ユーザーせってい"
+    "reply": "返信",
+    "favorite": "お気に入り",
+    "user_settings": "ユーザー設定"
   },
   "upload":{
     "error": {
-    "base": "アップロードにしっぱいしました。",
-    "file_too_big": "ファイルがおおきすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
-    "default": "しばらくしてから、ためしてください"
+    "base": "アップロードに失敗しました。",
+    "file_too_big": "ファイルが大きすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
+    "default": "しばらくしてから試してください"
     },
     "file_size_units": {
       "B": "B",
@@ -619,21 +619,21 @@
     }
   },
   "search": {
-    "people": "ひとびと",
+    "people": "人々",
     "hashtags": "ハッシュタグ",
-    "person_talking": "{count} にんが、はなしています",
-    "people_talking": "{count} にんが、はなしています",
-    "no_results": "みつかりませんでした"
+    "person_talking": "{count} 人が話しています",
+    "people_talking": "{count} 人が話しています",
+    "no_results": "見つかりませんでした"
   },
   "password_reset": {
-    "forgot_password": "パスワードを、わすれましたか?",
+    "forgot_password": "パスワードを忘れましたか?",
     "password_reset": "パスワードリセット",
-    "instruction": "あなたのメールアドレスかユーザーめいをいれてください。パスワードをリセットするためのリンクをおくります。",
-    "placeholder": "あなたのメールアドレスかユーザーめい",
-    "check_email": "パスワードをリセットするためのリンクがかかれたメールが、とどいているかどうか、みてください。",
-    "return_home": "ホームページにもどる",
-    "not_found": "そのメールアドレスまたはユーザーめいを、みつけることができませんでした。",
-    "too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。",
-    "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。"
+    "instruction": "メールアドレスまたはユーザー名を入力してください。パスワードをリセットするためのリンクを送信します。",
+    "placeholder": "メールアドレスまたはユーザー名",
+    "check_email": "パスワードをリセットするためのリンクが記載されたメールが届いているか確認してください。",
+    "return_home": "ホームページに戻る",
+    "not_found": "メールアドレスまたはユーザー名が見つかりませんでした。",
+    "too_many_requests": "試行回数の制限に達しました。しばらく時間を置いてから再試行してください。",
+    "password_reset_disabled": "このインスタンスではパスワードリセットは無効になっています。インスタンスの管理者に連絡してください。"
   }
 }
diff --git a/src/i18n/ja_easy.json b/src/i18n/ja_easy.json
new file mode 100644
index 00000000..592a7257
--- /dev/null
+++ b/src/i18n/ja_easy.json
@@ -0,0 +1,639 @@
+{
+  "chat": {
+    "title": "チャット"
+  },
+  "exporter": {
+    "export": "エクスポート",
+    "processing": "おまちください。しばらくすると、あなたのファイルをダウンロードするように、メッセージがでます。"
+  },
+  "features_panel": {
+    "chat": "チャット",
+    "gopher": "Gopher",
+    "media_proxy": "メディアプロクシ",
+    "scope_options": "こうかいはんいせんたく",
+    "text_limit": "もじのかず",
+    "title": "ゆうこうなきのう",
+    "who_to_follow": "おすすめユーザー"
+  },
+  "finder": {
+    "error_fetching_user": "ユーザーけんさくがエラーになりました。",
+    "find_user": "ユーザーをさがす"
+  },
+  "general": {
+    "apply": "てきよう",
+    "submit": "そうしん",
+    "more": "つづき",
+    "generic_error": "エラーになりました",
+    "optional": "かかなくてもよい",
+    "show_more": "つづきをみる",
+    "show_less": "たたむ",
+    "cancel": "キャンセル",
+    "disable": "なし",
+    "enable": "あり",
+    "confirm": "たしかめる",
+    "verify": "たしかめる"
+  },
+  "image_cropper": {
+    "crop_picture": "がぞうをきりぬく",
+    "save": "セーブ",
+    "save_without_cropping": "きりぬかずにセーブ",
+    "cancel": "キャンセル"
+  },
+  "importer": {
+    "submit": "そうしん",
+    "success": "インポートできました。",
+    "error": "インポートがエラーになりました。"
+  },
+  "login": {
+    "login": "ログイン",
+    "description": "OAuthでログイン",
+    "logout": "ログアウト",
+    "password": "パスワード",
+    "placeholder": "れい: lain",
+    "register": "はじめる",
+    "username": "ユーザーめい",
+    "hint": "はなしあいにくわわるには、ログインしてください",
+    "authentication_code": "にんしょうコード",
+    "enter_recovery_code": "リカバリーコードをいれてください",
+    "enter_two_factor_code": "2-ファクターコードをいれてください",
+    "recovery_code": "リカバリーコード",
+    "heading" : {
+      "totp" : "2-ファクターにんしょう",
+      "recovery" : "2-ファクターリカバリー"
+    }
+  },
+  "media_modal": {
+    "previous": "まえ",
+    "next": "つぎ"
+  },
+  "nav": {
+    "about": "これはなに?",
+    "back": "もどる",
+    "chat": "ローカルチャット",
+    "friend_requests": "フォローリクエスト",
+    "mentions": "メンション",
+    "interactions": "やりとり",
+    "dms": "ダイレクトメッセージ",
+    "public_tl": "パブリックタイムライン",
+    "timeline": "タイムライン",
+    "twkn": "つながっているすべてのネットワーク",
+    "user_search": "ユーザーをさがす",
+    "search": "さがす",
+    "who_to_follow": "おすすめユーザー",
+    "preferences": "せってい"
+  },
+  "notifications": {
+    "broken_favorite": "ステータスがみつかりません。さがしています...",
+    "favorited_you": "あなたのステータスがおきにいりされました",
+    "followed_you": "フォローされました",
+    "load_older": "ふるいつうちをみる",
+    "notifications": "つうち",
+    "read": "よんだ!",
+    "repeated_you": "あなたのステータスがリピートされました",
+    "no_more_notifications": "つうちはありません"
+  },
+  "polls": {
+    "add_poll": "いれふだをはじめる",
+    "add_option": "オプションをふやす",
+    "option": "オプション",
+    "votes": "いれふだ",
+    "vote": "ふだをいれる",
+    "type": "いれふだのかた",
+    "single_choice": "ひとつえらぶ",
+    "multiple_choices": "いくつでもえらべる",
+    "expiry": "いれふだのながさ",
+    "expires_in": "いれふだは {0} で、おわります",
+    "expired": "いれふだは {0} まえに、おわりました",
+    "not_enough_options": "ユニークなオプションが、たりません"
+  },
+  "emoji": {
+    "stickers": "ステッカー",
+    "emoji": "えもじ",
+    "keep_open": "ピッカーをあけたままにする",
+    "search_emoji": "えもじをさがす",
+    "add_emoji": "えもじをうちこむ",
+    "custom": "カスタムえもじ",
+    "unicode": "ユニコードえもじ"
+  },
+  "stickers": {
+    "add_sticker": "ステッカーをふやす"
+  },
+  "interactions": {
+    "favs_repeats": "リピートとおきにいり",
+    "follows": "あたらしいフォロー",
+    "load_older": "ふるいやりとりをみる"
+  },
+  "post_status": {
+    "new_status": "とうこうする",
+    "account_not_locked_warning": "あなたのアカウントは {0} ではありません。あなたをフォローすれば、だれでも、フォロワーげんていのステータスをよむことができます。",
+    "account_not_locked_warning_link": "ロックされたアカウント",
+    "attachments_sensitive": "ファイルをNSFWにする",
+    "content_type": {
+      "text/plain": "プレーンテキスト",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
+    },
+    "content_warning": "せつめい (かかなくてもよい)",
+    "default": "はねだくうこうに、つきました。",
+    "direct_warning_to_all": "このとうこうは、メンションされたすべてのユーザーが、みることができます。",
+    "direct_warning_to_first_only": "このとうこうは、メッセージのはじめでメンションされたユーザーだけが、みることができます。",
+    "direct_warning": "このステータスは、メンションされたユーザーだけが、よむことができます。",
+    "posting": "とうこう",
+    "scope_notice": {
+      "public": "このとうこうは、だれでもみることができます",
+      "private": "このとうこうは、あなたのフォロワーだけが、みることができます",
+      "unlisted": "このとうこうは、パブリックタイムラインと、つながっているすべてのネットワークでは、みることができません"
+    },
+    "scope": {
+      "direct": "ダイレクト: メンションされたユーザーのみにとどきます。",
+      "private": "フォロワーげんてい: フォロワーのみにとどきます。",
+      "public": "パブリック: パブリックタイムラインにとどきます。",
+      "unlisted": "アンリステッド: パブリックタイムラインにとどきません。"
+    }
+  },
+  "registration": {
+    "bio": "プロフィール",
+    "email": "Eメール",
+    "fullname": "スクリーンネーム",
+    "password_confirm": "パスワードのかくにん",
+    "registration": "はじめる",
+    "token": "しょうたいトークン",
+    "captcha": "CAPTCHA",
+    "new_captcha": "もじがよめないときは、がぞうをクリックすると、あたらしいがぞうになります",
+    "username_placeholder": "れい: lain",
+    "fullname_placeholder": "れい: いわくら れいん",
+    "bio_placeholder": "れい:\nごきげんよう。わたしはれいん。\nわたしはアニメのおんなのこで、にほんのベッドタウンにすんでいます。ワイヤードで、わたしにあったことが、あるかもしれませんね。",
+    "validations": {
+      "username_required": "なにかかいてください",
+      "fullname_required": "なにかかいてください",
+      "email_required": "なにかかいてください",
+      "password_required": "なにかかいてください",
+      "password_confirmation_required": "なにかかいてください",
+      "password_confirmation_match": "パスワードがちがいます"
+    }
+  },
+  "selectable_list": {
+    "select_all": "すべてえらぶ"
+  },
+  "settings": {
+    "app_name": "アプリのなまえ",
+    "security": "セキュリティ",
+    "enter_current_password_to_confirm": "あなたのアイデンティティをたしかめるため、あなたのいまのパスワードをかいてください",
+    "mfa": {
+      "otp" : "OTP",
+      "setup_otp" : "OTPをつくる",
+      "wait_pre_setup_otp" : "OTPをよういしています",
+      "confirm_and_enable" : "OTPをたしかめて、ゆうこうにする",
+      "title": "2-ファクターにんしょう",
+      "generate_new_recovery_codes" : "あたらしいリカバリーコードをつくる",
+      "warning_of_generate_new_codes" : "あたらしいリカバリーコードをつくったら、ふるいコードはつかえなくなります。",
+      "recovery_codes" : "リカバリーコード。",
+      "waiting_a_recovery_codes": "バックアップコードをうけとっています...",
+      "recovery_codes_warning" : "コードをかきうつすか、ひとにみられないところにセーブしてください。そうでなければ、あなたはこのコードをふたたびみることはできません。もしあなたが、2FAアプリのアクセスをうしなって、なおかつ、リカバリーコードもおもいだせないならば、あなたはあなたのアカウントから、しめだされます。",
+      "authentication_methods" : "にんしょうメソッド",
+      "scan": {
+        "title": "スキャン",
+        "desc": "あなたの2-ファクターアプリをつかって、このQRコードをスキャンするか、テキストキーをうちこんでください:",
+        "secret_code": "キー"
+      },
+      "verify": {
+        "desc": "2-ファクターにんしょうをつかうには、あなたの2-ファクターアプリのコードをいれてください:"
+      }
+    },
+    "attachmentRadius": "ファイル",
+    "attachments": "ファイル",
+    "autoload": "したにスクロールしたとき、じどうてきによみこむ。",
+    "avatar": "アバター",
+    "avatarAltRadius": "つうちのアバター",
+    "avatarRadius": "アバター",
+    "background": "バックグラウンド",
+    "bio": "プロフィール",
+    "block_export": "ブロックのエクスポート",
+    "block_export_button": "ブロックをCSVファイルにエクスポート",
+    "block_import": "ブロックのインポート",
+    "block_import_error": "ブロックのインポートがエラーになりました",
+    "blocks_imported": "ブロックをインポートしました! じっさいにブロックするまでには、もうしばらくかかります。",
+    "blocks_tab": "ブロック",
+    "btnRadius": "ボタン",
+    "cBlue": "リプライとフォロー",
+    "cGreen": "リピート",
+    "cOrange": "おきにいり",
+    "cRed": "キャンセル",
+    "change_password": "パスワードをかえる",
+    "change_password_error": "パスワードをかえることが、できなかったかもしれません。",
+    "changed_password": "パスワードが、かわりました!",
+    "collapse_subject": "せつめいのあるとうこうをたたむ",
+    "composing": "とうこう",
+    "confirm_new_password": "あたらしいパスワードのかくにん",
+    "current_avatar": "いまのアバター",
+    "current_password": "いまのパスワード",
+    "current_profile_banner": "いまのプロフィールバナー",
+    "data_import_export_tab": "インポートとエクスポート",
+    "default_vis": "デフォルトのこうかいはんい",
+    "delete_account": "アカウントをけす",
+    "delete_account_description": "あなたのアカウントとメッセージが、きえます。",
+    "delete_account_error": "アカウントをけすことが、できなかったかもしれません。インスタンスのアドミニストレーターに、おといあわせください。",
+    "delete_account_instructions": "ほんとうにアカウントをけしてもいいなら、パスワードをかいてください。",
+    "discoverable": "けんさくなどのサービスで、このアカウントをみつけてもよい",
+    "avatar_size_instruction": "アバターのおおきさは、150×150ピクセルか、それよりもおおきくするといいです。",
+    "pad_emoji": "えもじをピッカーでえらんだとき、えもじのまわりにスペースをいれる",
+    "export_theme": "セーブ",
+    "filtering": "フィルタリング",
+    "filtering_explanation": "これらのことばをふくむすべてのものがミュートされます。1ぎょうに1つのことばをかいてください。",
+    "follow_export": "フォローのエクスポート",
+    "follow_export_button": "エクスポート",
+    "follow_export_processing": "おまちください。まもなくファイルをダウンロードできます。",
+    "follow_import": "フォローインポート",
+    "follow_import_error": "フォローのインポートがエラーになりました。",
+    "follows_imported": "フォローがインポートされました! すこしじかんがかかるかもしれません。",
+    "foreground": "フォアグラウンド",
+    "general": "ぜんぱん",
+    "hide_attachments_in_convo": "スレッドのファイルをかくす",
+    "hide_attachments_in_tl": "タイムラインのファイルをかくす",
+    "hide_muted_posts": "ミュートしたユーザーのとうこうをかくす",
+    "max_thumbnails": "ひとつのとうこうにいれられるサムネイルのかず",
+    "hide_isp": "インスタンススペシフィックパネルをかくす",
+    "preload_images": "がぞうをさきよみする",
+    "use_one_click_nsfw": "NSFWなファイルを1クリックでひらく",
+    "hide_post_stats": "とうこうのとうけいをかくす (れい: おきにいりのかず)",
+    "hide_user_stats": "ユーザーのとうけいをかくす (れい: フォロワーのかず)",
+    "hide_filtered_statuses": "フィルターされたとうこうをかくす",
+    "import_blocks_from_a_csv_file": "CSVファイルからブロックをインポートする",
+    "import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
+    "import_theme": "ロード",
+    "inputRadius": "インプットフィールド",
+    "checkboxRadius": "チェックボックス",
+    "instance_default": "(デフォルト: {value})",
+    "instance_default_simple": "(デフォルト)",
+    "interface": "インターフェース",
+    "interfaceLanguage": "インターフェースのことば",
+    "invalid_theme_imported": "このファイルはPleromaのテーマではありません。テーマはへんこうされませんでした。",
+    "limited_availability": "あなたのブラウザではできません",
+    "links": "リンク",
+    "lock_account_description": "あなたがみとめたひとだけ、あなたのアカウントをフォローできる",
+    "loop_video": "ビデオをくりかえす",
+    "loop_video_silent_only": "おとのないビデオだけくりかえす",
+    "mutes_tab": "ミュート",
+    "play_videos_in_modal": "ビデオをメディアビューアーでみる",
+    "use_contain_fit": "がぞうのサムネイルを、きりぬかない",
+    "name": "なまえ",
+    "name_bio": "なまえとプロフィール",
+    "new_password": "あたらしいパスワード",
+    "notification_visibility": "ひょうじするつうち",
+    "notification_visibility_follows": "フォロー",
+    "notification_visibility_likes": "おきにいり",
+    "notification_visibility_mentions": "メンション",
+    "notification_visibility_repeats": "リピート",
+    "no_rich_text_description": "リッチテキストをつかわない",
+    "no_blocks": "ブロックしていません",
+    "no_mutes": "ミュートしていません",
+    "hide_follows_description": "フォローしているひとをみせない",
+    "hide_followers_description": "フォロワーをみせない",
+    "hide_follows_count_description": "フォローしているひとのかずをみせない",
+    "hide_followers_count_description": "フォロワーのかずをみせない",
+    "show_admin_badge": "アドミンのしるしをみせる",
+    "show_moderator_badge": "モデレーターのしるしをみせる",
+    "nsfw_clickthrough": "NSFWなファイルをかくす",
+    "oauth_tokens": "OAuthトークン",
+    "token": "トークン",
+    "refresh_token": "トークンをリフレッシュ",
+    "valid_until": "おわりのとき",
+    "revoke_token": "とりけす",
+    "panelRadius": "パネル",
+    "pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる",
+    "presets": "プリセット",
+    "profile_background": "プロフィールのバックグラウンド",
+    "profile_banner": "プロフィールバナー",
+    "profile_tab": "プロフィール",
+    "radii_help": "インターフェースのまるさをせっていする。",
+    "replies_in_timeline": "タイムラインのリプライ",
+    "reply_link_preview": "カーソルをかさねたとき、リプライのプレビューをみる",
+    "reply_visibility_all": "すべてのリプライをみる",
+    "reply_visibility_following": "わたしにあてられたリプライと、フォローしているひとからのリプライをみる",
+    "reply_visibility_self": "わたしにあてられたリプライをみる",
+    "autohide_floating_post_button": "あたらしいとうこうのボタンを、じどうてきにかくす (モバイル)",
+    "saving_err": "せっていをセーブできませんでした",
+    "saving_ok": "せっていをセーブしました",
+    "search_user_to_block": "ブロックしたいひとを、ここでけんさくできます",
+    "search_user_to_mute": "ミュートしたいひとを、ここでけんさくできます",
+    "security_tab": "セキュリティ",
+    "scope_copy": "リプライするとき、こうかいはんいをコピーする (DMのこうかいはんいは、つねにコピーされます)",
+    "minimal_scopes_mode": "こうかいはんいせんたくオプションを、ちいさくする",
+    "set_new_avatar": "あたらしいアバターをせっていする",
+    "set_new_profile_background": "あたらしいプロフィールのバックグラウンドをせっていする",
+    "set_new_profile_banner": "あたらしいプロフィールバナーを設定する",
+    "settings": "せってい",
+    "subject_input_always_show": "サブジェクトフィールドをいつでもひょうじする",
+    "subject_line_behavior": "リプライするときサブジェクトをコピーする",
+    "subject_line_email": "メールふう: \"re: サブジェクト\"",
+    "subject_line_mastodon": "マストドンふう: そのままコピー",
+    "subject_line_noop": "コピーしない",
+    "post_status_content_type": "とうこうのコンテントタイプ",
+    "stop_gifs": "カーソルをかさねたとき、GIFをうごかす",
+    "streaming": "うえまでスクロールしたとき、じどうてきにストリーミングする",
+    "text": "もじ",
+    "theme": "テーマ",
+    "theme_help": "カラーテーマをカスタマイズできます",
+    "theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、いろと、とうめいどを、オーバーライドできます。「すべてクリア」ボタンをおすと、すべてのオーバーライドを、やめます。",
+    "theme_help_v2_2": "バックグラウンドとテキストのコントラストをあらわすアイコンがあります。マウスをホバーすると、くわしいせつめいがでます。とうめいないろをつかっているときは、もっともわるいばあいのコントラストがしめされます。",
+    "upload_a_photo": "がぞうをアップロード",
+    "tooltipRadius": "ツールチップとアラート",
+    "user_settings": "ユーザーせってい",
+    "values": {
+      "false": "いいえ",
+      "true": "はい"
+    },
+    "notifications": "つうち",
+    "notification_setting": "つうちをうけとる:",
+    "notification_setting_follows": "あなたがフォローしているひとから",
+    "notification_setting_non_follows": "あなたがフォローしていないひとから",
+    "notification_setting_followers": "あなたをフォローしているひとから",
+    "notification_setting_non_followers": "あなたをフォローしていないひとから",
+    "notification_mutes": "あるユーザーからのつうちをとめるには、ミュートしてください。",
+    "notification_blocks": "ブロックしているユーザーからのつうちは、すべてとまります。",
+    "enable_web_push_notifications": "ウェブプッシュつうちをゆるす",
+    "style": {
+      "switcher": {
+        "keep_color": "いろをのこす",
+        "keep_shadows": "かげをのこす",
+        "keep_opacity": "とうめいどをのこす",
+        "keep_roundness": "まるさをのこす",
+        "keep_fonts": "フォントをのこす",
+        "save_load_hint": "「のこす」オプションをONにすると、テーマをえらんだときとロードしたとき、いまのせっていをのこします。また、テーマをエクスポートするとき、これらのオプションをストアします。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべてのせっていをセーブします。",
+        "reset": "リセット",
+        "clear_all": "すべてクリア",
+        "clear_opacity": "とうめいどをクリア"
+      },
+      "common": {
+        "color": "いろ",
+        "opacity": "とうめいど",
+        "contrast": {
+          "hint": "コントラストは {ratio} です。{level}。({context})",
+          "level": {
+            "aa": "AAレベルガイドライン (ミニマル) をみたします",
+            "aaa": "AAAレベルガイドライン (レコメンデッド) をみたします。",
+            "bad": "ガイドラインをみたしません。"
+          },
+          "context": {
+            "18pt": "おおきい (18ポイントいじょう) テキスト",
+            "text": "テキスト"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "きょうつう",
+        "main": "きょうつうのいろ",
+        "foreground_hint": "「くわしく」タブで、もっとこまかくせっていできます",
+        "rgbo": "アイコンとアクセントとバッジ"
+      },
+      "advanced_colors": {
+        "_tab_label": "くわしく",
+        "alert": "アラートのバックグラウンド",
+        "alert_error": "エラー",
+        "badge": "バッジのバックグラウンド",
+        "badge_notification": "つうち",
+        "panel_header": "パネルヘッダー",
+        "top_bar": "トップバー",
+        "borders": "さかいめ",
+        "buttons": "ボタン",
+        "inputs": "インプットフィールド",
+        "faint_text": "うすいテキスト"
+      },
+      "radii": {
+        "_tab_label": "まるさ"
+      },
+      "shadows": {
+        "_tab_label": "ひかりとかげ",
+        "component": "コンポーネント",
+        "override": "オーバーライド",
+        "shadow_id": "かげ #{value}",
+        "blur": "ぼかし",
+        "spread": "ひろがり",
+        "inset": "うちがわ",
+        "hint": "かげのせっていでは、いろのあたいとして --variable をつかうことができます。これはCSS3へんすうです。ただし、とうめいどのせっていは、きかなくなります。",
+        "filter_hint": {
+          "always_drop_shadow": "ブラウザーがサポートしていれば、つねに {0} がつかわれます。",
+          "drop_shadow_syntax": "{0} は、{1} パラメーターと {2} キーワードをサポートしていません。",
+          "avatar_inset": "うちがわのかげと、そとがわのかげを、いっしょにつかうと、とうめいなアバターが、へんなみためになります。",
+          "spread_zero": "ひろがりが 0 よりもおおきなかげは、0 とおなじです。",
+          "inset_classic": "うちがわのかげは {0} をつかいます。"
+        },
+        "components": {
+          "panel": "パネル",
+          "panelHeader": "パネルヘッダー",
+          "topBar": "トップバー",
+          "avatar": "ユーザーアバター (プロフィール)",
+          "avatarStatus": "ユーザーアバター (とうこう)",
+          "popup": "ポップアップとツールチップ",
+          "button": "ボタン",
+          "buttonHover": "ボタン (ホバー)",
+          "buttonPressed": "ボタン (おされているとき)",
+          "buttonPressedHover": "ボタン (ホバー、かつ、おされているとき)",
+          "input": "インプットフィールド"
+        }
+      },
+      "fonts": {
+        "_tab_label": "フォント",
+        "help": "「カスタム」をえらんだときは、システムにあるフォントのなまえを、ただしくにゅうりょくしてください。",
+        "components": {
+          "interface": "インターフェース",
+          "input": "インプットフィールド",
+          "post": "とうこう",
+          "postCode": "モノスペース (とうこうがリッチテキストであるとき)"
+        },
+        "family": "フォントめい",
+        "size": "おおきさ (px)",
+        "weight": "ふとさ",
+        "custom": "カスタム"
+      },
+      "preview": {
+        "header": "プレビュー",
+        "content": "ほんぶん",
+        "error": "エラーのれい",
+        "button": "ボタン",
+        "text": "これは{0}と{1}のれいです。",
+        "mono": "monospace",
+        "input": "はねだくうこうに、つきました。",
+        "faint_link": "とてもたすけになるマニュアル",
+        "fine_print": "わたしたちの{0}を、よまないでください!",
+        "header_faint": "エラーではありません",
+        "checkbox": "りようきやくを、よみました",
+        "link": "ハイパーリンク"
+      }
+    },
+    "version": {
+      "title": "バージョン",
+      "backend_version": "バックエンドのバージョン",
+      "frontend_version": "フロントエンドのバージョン"
+    }
+  },
+  "time": {
+    "day": "{0}日",
+    "days": "{0}日",
+    "day_short": "{0}日",
+    "days_short": "{0}日",
+    "hour": "{0}時間",
+    "hours": "{0}時間",
+    "hour_short": "{0}時間",
+    "hours_short": "{0}時間",
+    "in_future": "{0}で",
+    "in_past": "{0}前",
+    "minute": "{0}分",
+    "minutes": "{0}分",
+    "minute_short": "{0}分",
+    "minutes_short": "{0}分",
+    "month": "{0}ヶ月前",
+    "months": "{0}ヶ月前",
+    "month_short": "{0}ヶ月前",
+    "months_short": "{0}ヶ月前",
+    "now": "たった今",
+    "now_short": "たった今",
+    "second": "{0}秒",
+    "seconds": "{0}秒",
+    "second_short": "{0}秒",
+    "seconds_short": "{0}秒",
+    "week": "{0}週間",
+    "weeks": "{0}週間",
+    "week_short": "{0}週間",
+    "weeks_short": "{0}週間",
+    "year": "{0}年",
+    "years": "{0}年",
+    "year_short": "{0}年",
+    "years_short": "{0}年"
+  },
+  "timeline": {
+    "collapse": "たたむ",
+    "conversation": "スレッド",
+    "error_fetching": "よみこみがエラーになりました",
+    "load_older": "ふるいステータス",
+    "no_retweet_hint": "とうこうを「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります",
+    "repeated": "リピート",
+    "show_new": "よみこみ",
+    "up_to_date": "さいしん",
+    "no_more_statuses": "これでおわりです",
+    "no_statuses": "ありません"
+  },
+  "status": {
+    "favorites": "おきにいり",
+    "repeats": "リピート",
+    "delete": "ステータスをけす",
+    "pin": "プロフィールにピンどめする",
+    "unpin": "プロフィールにピンどめするのをやめる",
+    "pinned": "ピンどめ",
+    "delete_confirm": "ほんとうに、このステータスを、けしてもいいですか?",
+    "reply_to": "へんしん:",
+    "replies_list": "へんしん:",
+    "mute_conversation": "スレッドをミュートする",
+    "unmute_conversation": "スレッドをミュートするのをやめる"
+  },
+  "user_card": {
+    "approve": "うけいれ",
+    "block": "ブロック",
+    "blocked": "ブロックしています!",
+    "deny": "おことわり",
+    "favorites": "おきにいり",
+    "follow": "フォロー",
+    "follow_sent": "リクエストを、おくりました!",
+    "follow_progress": "リクエストしています…",
+    "follow_again": "ふたたびリクエストをおくりますか?",
+    "follow_unfollow": "フォローをやめる",
+    "followees": "フォロー",
+    "followers": "フォロワー",
+    "following": "フォローしています!",
+    "follows_you": "フォローされました!",
+    "its_you": "これはあなたです!",
+    "media": "メディア",
+    "mention": "メンション",
+    "mute": "ミュート",
+    "muted": "ミュートしています!",
+    "per_day": "/日",
+    "remote_follow": "リモートフォロー",
+    "report": "つうほう",
+    "statuses": "ステータス",
+    "subscribe": "サブスクライブ",
+    "unsubscribe": "サブスクライブをやめる",
+    "unblock": "ブロックをやめる",
+    "unblock_progress": "ブロックをとりけしています...",
+    "block_progress": "ブロックしています...",
+    "unmute": "ミュートをやめる",
+    "unmute_progress": "ミュートをとりけしています...",
+    "mute_progress": "ミュートしています...",
+    "admin_menu": {
+      "moderation": "モデレーション",
+      "grant_admin": "アドミンにする",
+      "revoke_admin": "アドミンをやめさせる",
+      "grant_moderator": "モデレーターにする",
+      "revoke_moderator": "モデレーターをやめさせる",
+      "activate_account": "アカウントをアクティブにする",
+      "deactivate_account": "アカウントをアクティブでなくする",
+      "delete_account": "アカウントをけす",
+      "force_nsfw": "すべてのとうこうをNSFWにする",
+      "strip_media": "とうこうからメディアをなくす",
+      "force_unlisted": "とうこうをアンリステッドにする",
+      "sandbox": "とうこうをフォロワーのみにする",
+      "disable_remote_subscription": "ほかのインスタンスからフォローされないようにする",
+      "disable_any_subscription": "フォローされないようにする",
+      "quarantine": "ほかのインスタンスのユーザーのとうこうをとめる",
+      "delete_user": "ユーザーをけす",
+      "delete_user_confirmation": "あなたは、ほんとうに、きはたしかですか? これは、とりけすことが、できません。"
+    }
+  },
+  "user_profile": {
+    "timeline_title": "ユーザータイムライン",
+    "profile_does_not_exist": "ごめんなさい。このプロフィールは、そんざいしません。",
+    "profile_loading_error": "ごめんなさい。プロフィールのロードがエラーになりました。"
+  },
+  "user_reporting": {
+    "title": "つうほうする: {0}",
+    "add_comment_description": "このつうほうは、あなたのインスタンスのモデレーターに、おくられます。このアカウントを、つうほうするりゆうを、せつめいすることができます:",
+    "additional_comments": "ついかのコメント",
+    "forward_description": "このアカウントは、ほかのインスタンスのものです。そのインスタンスにも、このつうほうのコピーを、おくりますか?",
+    "forward_to": "コピーをおくる: {0}",
+    "submit": "そうしん",
+    "generic_error": "あなたのリクエストをうけつけようとしましたが、エラーになってしまいました。"
+  },
+  "who_to_follow": {
+    "more": "くわしく",
+    "who_to_follow": "おすすめユーザー"
+  },
+  "tool_tip": {
+    "media_upload": "メディアをアップロード",
+    "repeat": "リピート",
+    "reply": "リプライ",
+    "favorite": "おきにいり",
+    "user_settings": "ユーザーせってい"
+  },
+  "upload":{
+    "error": {
+    "base": "アップロードにしっぱいしました。",
+    "file_too_big": "ファイルがおおきすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
+    "default": "しばらくしてから、ためしてください"
+    },
+    "file_size_units": {
+      "B": "B",
+      "KiB": "KiB",
+      "MiB": "MiB",
+      "GiB": "GiB",
+      "TiB": "TiB"
+    }
+  },
+  "search": {
+    "people": "ひとびと",
+    "hashtags": "ハッシュタグ",
+    "person_talking": "{count} にんが、はなしています",
+    "people_talking": "{count} にんが、はなしています",
+    "no_results": "みつかりませんでした"
+  },
+  "password_reset": {
+    "forgot_password": "パスワードを、わすれましたか?",
+    "password_reset": "パスワードリセット",
+    "instruction": "あなたのメールアドレスかユーザーめいをいれてください。パスワードをリセットするためのリンクをおくります。",
+    "placeholder": "あなたのメールアドレスかユーザーめい",
+    "check_email": "パスワードをリセットするためのリンクがかかれたメールが、とどいているかどうか、みてください。",
+    "return_home": "ホームページにもどる",
+    "not_found": "そのメールアドレスまたはユーザーめいを、みつけることができませんでした。",
+    "too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。",
+    "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。"
+  }
+}
diff --git a/src/i18n/ja_pedantic.json b/src/i18n/ja_pedantic.json
deleted file mode 100644
index 2ca7dca8..00000000
--- a/src/i18n/ja_pedantic.json
+++ /dev/null
@@ -1,639 +0,0 @@
-{
-  "chat": {
-    "title": "チャット"
-  },
-  "exporter": {
-    "export": "エクスポート",
-    "processing": "処理中です。処理が完了すると、ファイルをダウンロードするよう指示があります。"
-  },
-  "features_panel": {
-    "chat": "チャット",
-    "gopher": "Gopher",
-    "media_proxy": "メディアプロクシ",
-    "scope_options": "公開範囲選択",
-    "text_limit": "文字の数",
-    "title": "有効な機能",
-    "who_to_follow": "おすすめユーザー"
-  },
-  "finder": {
-    "error_fetching_user": "ユーザー検索がエラーになりました。",
-    "find_user": "ユーザーを探す"
-  },
-  "general": {
-    "apply": "適用",
-    "submit": "送信",
-    "more": "続き",
-    "generic_error": "エラーになりました",
-    "optional": "省略可",
-    "show_more": "もっと見る",
-    "show_less": "たたむ",
-    "cancel": "キャンセル",
-    "disable": "無効",
-    "enable": "有効",
-    "confirm": "確認",
-    "verify": "検査"
-  },
-  "image_cropper": {
-    "crop_picture": "画像を切り抜く",
-    "save": "保存",
-    "save_without_cropping": "切り抜かずに保存",
-    "cancel": "キャンセル"
-  },
-  "importer": {
-    "submit": "送信",
-    "success": "正常にインポートされました。",
-    "error": "このファイルをインポートするとき、エラーが発生しました。"
-  },
-  "login": {
-    "login": "ログイン",
-    "description": "OAuthでログイン",
-    "logout": "ログアウト",
-    "password": "パスワード",
-    "placeholder": "例: lain",
-    "register": "登録",
-    "username": "ユーザー名",
-    "hint": "会話に加わるには、ログインしてください",
-    "authentication_code": "認証コード",
-    "enter_recovery_code": "リカバリーコードを入力してください",
-    "enter_two_factor_code": "2段階認証コードを入力してください",
-    "recovery_code": "リカバリーコード",
-    "heading" : {
-      "totp" : "2段階認証",
-      "recovery" : "2段階リカバリー"
-    }
-  },
-  "media_modal": {
-    "previous": "前",
-    "next": "次"
-  },
-  "nav": {
-    "about": "このインスタンスについて",
-    "back": "戻る",
-    "chat": "ローカルチャット",
-    "friend_requests": "フォローリクエスト",
-    "mentions": "通知",
-    "interactions": "インタラクション",
-    "dms": "ダイレクトメッセージ",
-    "public_tl": "パブリックタイムライン",
-    "timeline": "タイムライン",
-    "twkn": "接続しているすべてのネットワーク",
-    "user_search": "ユーザーを探す",
-    "search": "検索",
-    "who_to_follow": "おすすめユーザー",
-    "preferences": "設定"
-  },
-  "notifications": {
-    "broken_favorite": "ステータスが見つかりません。探しています...",
-    "favorited_you": "あなたのステータスがお気に入りされました",
-    "followed_you": "フォローされました",
-    "load_older": "古い通知をみる",
-    "notifications": "通知",
-    "read": "読んだ!",
-    "repeated_you": "あなたのステータスがリピートされました",
-    "no_more_notifications": "通知はありません"
-  },
-  "polls": {
-    "add_poll": "投票を追加",
-    "add_option": "選択肢を追加",
-    "option": "選択肢",
-    "votes": "票",
-    "vote": "投票",
-    "type": "投票の形式",
-    "single_choice": "択一式",
-    "multiple_choices": "複数選択式",
-    "expiry": "投票期間",
-    "expires_in": "投票は {0} で終了します",
-    "expired": "投票は {0} 前に終了しました",
-    "not_enough_options": "相異なる選択肢が不足しています"
-  },
-  "emoji": {
-    "stickers": "ステッカー",
-    "emoji": "絵文字",
-    "keep_open": "ピッカーを開いたままにする",
-    "search_emoji": "絵文字を検索",
-    "add_emoji": "絵文字を挿入",
-    "custom": "カスタム絵文字",
-    "unicode": "Unicode絵文字"
-  },
-  "stickers": {
-    "add_sticker": "ステッカーを追加"
-  },
-  "interactions": {
-    "favs_repeats": "リピートとお気に入り",
-    "follows": "新しいフォロワー",
-    "load_older": "古いインタラクションを見る"
-  },
-  "post_status": {
-    "new_status": "投稿する",
-    "account_not_locked_warning": "あなたのアカウントは {0} ではありません。あなたをフォローすれば、誰でも、フォロワー限定のステータスを読むことができます。",
-    "account_not_locked_warning_link": "ロックされたアカウント",
-    "attachments_sensitive": "ファイルをNSFWにする",
-    "content_type": {
-      "text/plain": "プレーンテキスト",
-      "text/html": "HTML",
-      "text/markdown": "Markdown",
-      "text/bbcode": "BBCode"
-    },
-    "content_warning": "説明 (省略可)",
-    "default": "羽田空港に着きました。",
-    "direct_warning_to_all": "この投稿は、メンションされたすべてのユーザーが、見ることができます。",
-    "direct_warning_to_first_only": "この投稿は、メッセージの冒頭でメンションされたユーザーだけが、見ることができます。",
-    "direct_warning": "このステータスは、メンションされたユーザーだけが、読むことができます。",
-    "posting": "投稿",
-    "scope_notice": {
-      "public": "この投稿は、誰でも見ることができます",
-      "private": "この投稿は、あなたのフォロワーだけが、見ることができます。",
-      "unlisted": "この投稿は、パブリックタイムラインと、接続しているすべてのネットワークには、表示されません。"
-    },
-    "scope": {
-      "direct": "ダイレクト: メンションされたユーザーのみに届きます。",
-      "private": "フォロワーげんてい: フォロワーのみに届きます。",
-      "public": "パブリック: パブリックタイムラインに届きます。",
-      "unlisted": "アンリステッド: パブリックタイムラインに届きません。"
-    }
-  },
-  "registration": {
-    "bio": "プロフィール",
-    "email": "Eメール",
-    "fullname": "スクリーンネーム",
-    "password_confirm": "パスワードの確認",
-    "registration": "登録",
-    "token": "招待トークン",
-    "captcha": "CAPTCHA",
-    "new_captcha": "文字が読めないときは、画像をクリックすると、新しい画像になります",
-    "username_placeholder": "例: lain",
-    "fullname_placeholder": "例: 岩倉玲音",
-    "bio_placeholder": "例:\nこんにちは。私は玲音。\n私はアニメのキャラクターで、日本の郊外に住んでいます。私をWiredで見たことがあるかもしれません。",
-    "validations": {
-      "username_required": "必須",
-      "fullname_required": "必須",
-      "email_required": "必須",
-      "password_required": "必須",
-      "password_confirmation_required": "必須",
-      "password_confirmation_match": "パスワードが違います"
-    }
-  },
-  "selectable_list": {
-    "select_all": "すべて選択"
-  },
-  "settings": {
-    "app_name": "アプリの名称",
-    "security": "セキュリティ",
-    "enter_current_password_to_confirm": "あなたのアイデンティティを証明するため、現在のパスワードを入力してください",
-    "mfa": {
-      "otp" : "OTP",
-      "setup_otp" : "OTPのセットアップ",
-      "wait_pre_setup_otp" : "OTPのプリセット",
-      "confirm_and_enable" : "OTPの確認と有効化",
-      "title": "2段階認証",
-      "generate_new_recovery_codes" : "新しいリカバリーコードを生成",
-      "warning_of_generate_new_codes" : "新しいリカバリーコードを生成すると、古いコードは使用できなくなります。",
-      "recovery_codes" : "リカバリーコード。",
-      "waiting_a_recovery_codes": "バックアップコードを受信しています...",
-      "recovery_codes_warning" : "コードを紙に書くか、安全な場所に保存してください。そうでなければ、あなたはコードを再び見ることはできません。もし2段階認証アプリのアクセスを喪失し、なおかつ、リカバリーコードもないならば、あなたは自分のアカウントから閉め出されます。",
-      "authentication_methods" : "認証方法",
-      "scan": {
-        "title": "スキャン",
-        "desc": "あなたの2段階認証アプリを使って、このQRコードをスキャンするか、テキストキーを入力してください:",
-        "secret_code": "キー"
-      },
-      "verify": {
-        "desc": "2段階認証を有効にするには、あなたの2段階認証アプリのコードを入力してください:"
-      }
-    },
-    "attachmentRadius": "ファイル",
-    "attachments": "ファイル",
-    "autoload": "下にスクロールしたとき、自動的に読み込む。",
-    "avatar": "アバター",
-    "avatarAltRadius": "通知のアバター",
-    "avatarRadius": "アバター",
-    "background": "バックグラウンド",
-    "bio": "プロフィール",
-    "block_export": "ブロックのエクスポート",
-    "block_export_button": "ブロックをCSVファイルにエクスポートする",
-    "block_import": "ブロックのインポート",
-    "block_import_error": "ブロックのインポートに失敗しました",
-    "blocks_imported": "ブロックをインポートしました! 実際に処理されるまでに、しばらく時間がかかります。",
-    "blocks_tab": "ブロック",
-    "btnRadius": "ボタン",
-    "cBlue": "返信とフォロー",
-    "cGreen": "リピート",
-    "cOrange": "お気に入り",
-    "cRed": "キャンセル",
-    "change_password": "パスワードを変える",
-    "change_password_error": "パスワードを変えることが、できなかったかもしれません。",
-    "changed_password": "パスワードが、変わりました!",
-    "collapse_subject": "説明のある投稿をたたむ",
-    "composing": "投稿",
-    "confirm_new_password": "新しいパスワードの確認",
-    "current_avatar": "現在のアバター",
-    "current_password": "現在のパスワード",
-    "current_profile_banner": "現在のプロフィールバナー",
-    "data_import_export_tab": "インポートとエクスポート",
-    "default_vis": "デフォルトの公開範囲",
-    "delete_account": "アカウントを消す",
-    "delete_account_description": "あなたのアカウントとメッセージが、消えます。",
-    "delete_account_error": "アカウントを消すことが、できなかったかもしれません。インスタンスの管理者に、連絡してください。",
-    "delete_account_instructions": "本当にアカウントを消してもいいなら、パスワードを入力してください。",
-    "discoverable": "検索などのサービスでこのアカウントを見つけることを許可する",
-    "avatar_size_instruction": "アバターの大きさは、150×150ピクセルか、それよりも大きくするといいです。",
-    "pad_emoji": "ピッカーから絵文字を挿入するとき、絵文字の両側にスペースを入れる",
-    "export_theme": "保存",
-    "filtering": "フィルタリング",
-    "filtering_explanation": "これらの言葉を含むすべてのものがミュートされます。1行に1つの言葉を書いてください。",
-    "follow_export": "フォローのエクスポート",
-    "follow_export_button": "エクスポート",
-    "follow_export_processing": "お待ちください。まもなくファイルをダウンロードできます。",
-    "follow_import": "フォローのインポート",
-    "follow_import_error": "フォローのインポートがエラーになりました。",
-    "follows_imported": "フォローがインポートされました! 少し時間がかかるかもしれません。",
-    "foreground": "フォアグラウンド",
-    "general": "全般",
-    "hide_attachments_in_convo": "スレッドのファイルを隠す",
-    "hide_attachments_in_tl": "タイムラインのファイルを隠す",
-    "hide_muted_posts": "ミュートしているユーザーの投稿を隠す",
-    "max_thumbnails": "投稿に含まれるサムネイルの最大数",
-    "hide_isp": "インスタンス固有パネルを隠す",
-    "preload_images": "画像を先読みする",
-    "use_one_click_nsfw": "NSFWなファイルを1クリックで開く",
-    "hide_post_stats": "投稿の統計を隠す (例: お気に入りの数)",
-    "hide_user_stats": "ユーザーの統計を隠す (例: フォロワーの数)",
-    "hide_filtered_statuses": "フィルターされた投稿を隠す",
-    "import_blocks_from_a_csv_file": "CSVファイルからブロックをインポートする",
-    "import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
-    "import_theme": "ロード",
-    "inputRadius": "インプットフィールド",
-    "checkboxRadius": "チェックボックス",
-    "instance_default": "(デフォルト: {value})",
-    "instance_default_simple": "(デフォルト)",
-    "interface": "インターフェース",
-    "interfaceLanguage": "インターフェースの言語",
-    "invalid_theme_imported": "このファイルはPleromaのテーマではありません。テーマは変更されませんでした。",
-    "limited_availability": "あなたのブラウザではできません",
-    "links": "リンク",
-    "lock_account_description": "あなたが認めた人だけ、あなたのアカウントをフォローできる",
-    "loop_video": "ビデオを繰り返す",
-    "loop_video_silent_only": "音のないビデオだけ繰り返す",
-    "mutes_tab": "ミュート",
-    "play_videos_in_modal": "ビデオをメディアビューアーで見る",
-    "use_contain_fit": "画像のサムネイルを、切り抜かない",
-    "name": "名前",
-    "name_bio": "名前とプロフィール",
-    "new_password": "新しいパスワード",
-    "notification_visibility": "表示する通知",
-    "notification_visibility_follows": "フォロー",
-    "notification_visibility_likes": "お気に入り",
-    "notification_visibility_mentions": "メンション",
-    "notification_visibility_repeats": "リピート",
-    "no_rich_text_description": "リッチテキストを使わない",
-    "no_blocks": "ブロックはありません",
-    "no_mutes": "ミュートはありません",
-    "hide_follows_description": "フォローしている人を見せない",
-    "hide_followers_description": "フォロワーを見せない",
-    "hide_follows_count_description": "フォローしている人の数を見せない",
-    "hide_followers_count_description": "フォロワーの数を見せない",
-    "show_admin_badge": "管理者のバッジを見せる",
-    "show_moderator_badge": "モデレーターのバッジを見せる",
-    "nsfw_clickthrough": "NSFWなファイルを隠す",
-    "oauth_tokens": "OAuthトークン",
-    "token": "トークン",
-    "refresh_token": "トークンを更新",
-    "valid_until": "まで有効",
-    "revoke_token": "取り消す",
-    "panelRadius": "パネル",
-    "pause_on_unfocused": "タブにフォーカスがないときストリーミングを止める",
-    "presets": "プリセット",
-    "profile_background": "プロフィールのバックグラウンド",
-    "profile_banner": "プロフィールバナー",
-    "profile_tab": "プロフィール",
-    "radii_help": "インターフェースの丸さを設定する。",
-    "replies_in_timeline": "タイムラインのリプライ",
-    "reply_link_preview": "カーソルを重ねたとき、リプライのプレビューを見る",
-    "reply_visibility_all": "すべてのリプライを見る",
-    "reply_visibility_following": "私に宛てられたリプライと、フォローしている人からのリプライを見る",
-    "reply_visibility_self": "私に宛てられたリプライを見る",
-    "autohide_floating_post_button": "新しい投稿ボタンを自動的に隠す (モバイル)",
-    "saving_err": "設定を保存できませんでした",
-    "saving_ok": "設定を保存しました",
-    "search_user_to_block": "ブロックしたいユーザーを検索",
-    "search_user_to_mute": "ミュートしたいユーザーを検索",
-    "security_tab": "セキュリティ",
-    "scope_copy": "返信するとき、公開範囲をコピーする (DMの公開範囲は、常にコピーされます)",
-    "minimal_scopes_mode": "公開範囲選択オプションを最小にする",
-    "set_new_avatar": "新しいアバターを設定する",
-    "set_new_profile_background": "新しいプロフィールのバックグラウンドを設定する",
-    "set_new_profile_banner": "新しいプロフィールバナーを設定する",
-    "settings": "設定",
-    "subject_input_always_show": "サブジェクトフィールドをいつでも表示する",
-    "subject_line_behavior": "返信するときサブジェクトをコピーする",
-    "subject_line_email": "メール風: \"re: サブジェクト\"",
-    "subject_line_mastodon": "マストドン風: そのままコピー",
-    "subject_line_noop": "コピーしない",
-    "post_status_content_type": "投稿のコンテントタイプ",
-    "stop_gifs": "カーソルを重ねたとき、GIFを動かす",
-    "streaming": "上までスクロールしたとき、自動的にストリーミングする",
-    "text": "文字",
-    "theme": "テーマ",
-    "theme_help": "カラーテーマをカスタマイズできます",
-    "theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、色と透明度をオーバーライドできます。「すべてクリア」ボタンを押すと、すべてのオーバーライドをやめます。",
-    "theme_help_v2_2": "バックグラウンドとテキストのコントラストを表すアイコンがあります。マウスをホバーすると、詳しい説明が出ます。透明な色を使っているときは、最悪の場合のコントラストが示されます。",
-    "tooltipRadius": "ツールチップとアラート",
-    "upload_a_photo": "画像をアップロード",
-    "user_settings": "ユーザー設定",
-    "values": {
-      "false": "いいえ",
-      "true": "はい"
-    },
-    "notifications": "通知",
-    "notification_setting": "通知を受け取る:",
-    "notification_setting_follows": "あなたがフォローしているユーザーから",
-    "notification_setting_non_follows": "あなたがフォローしていないユーザーから",
-    "notification_setting_followers": "あなたをフォローしているユーザーから",
-    "notification_setting_non_followers": "あなたをフォローしていないユーザーから",
-    "notification_mutes": "特定のユーザーからの通知を止めるには、ミュートしてください。",
-    "notification_blocks": "ブロックしているユーザーからの通知は、すべて止まります。",
-    "enable_web_push_notifications": "ウェブプッシュ通知を許可する",
-    "style": {
-      "switcher": {
-        "keep_color": "色を残す",
-        "keep_shadows": "影を残す",
-        "keep_opacity": "透明度を残す",
-        "keep_roundness": "丸さを残す",
-        "keep_fonts": "フォントを残す",
-        "save_load_hint": "「残す」オプションをONにすると、テーマを選んだときとロードしたとき、現在の設定を残します。また、テーマをエクスポートするとき、これらのオプションを維持します。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべての設定を保存します。",
-        "reset": "リセット",
-        "clear_all": "すべてクリア",
-        "clear_opacity": "透明度をクリア"
-      },
-      "common": {
-        "color": "色",
-        "opacity": "透明度",
-        "contrast": {
-          "hint": "コントラストは {ratio} です。{level}。({context})",
-          "level": {
-            "aa": "AAレベルガイドライン (ミニマル) を満たします",
-            "aaa": "AAAレベルガイドライン (レコメンデッド) を満たします。",
-            "bad": "ガイドラインを満たしません。"
-          },
-          "context": {
-            "18pt": "大きい (18ポイント以上) テキスト",
-            "text": "テキスト"
-          }
-        }
-      },
-      "common_colors": {
-        "_tab_label": "共通",
-        "main": "共通の色",
-        "foreground_hint": "「詳細」タブで、もっと細かく設定できます",
-        "rgbo": "アイコンとアクセントとバッジ"
-      },
-      "advanced_colors": {
-        "_tab_label": "詳細",
-        "alert": "アラートのバックグラウンド",
-        "alert_error": "エラー",
-        "badge": "バッジのバックグラウンド",
-        "badge_notification": "通知",
-        "panel_header": "パネルヘッダー",
-        "top_bar": "トップバー",
-        "borders": "境界",
-        "buttons": "ボタン",
-        "inputs": "インプットフィールド",
-        "faint_text": "薄いテキスト"
-      },
-      "radii": {
-        "_tab_label": "丸さ"
-      },
-      "shadows": {
-        "_tab_label": "光と影",
-        "component": "コンポーネント",
-        "override": "オーバーライド",
-        "shadow_id": "影 #{value}",
-        "blur": "ぼかし",
-        "spread": "広がり",
-        "inset": "内側",
-        "hint": "影の設定では、色の値として --variable を使うことができます。これはCSS3変数です。ただし、透明度の設定は、効かなくなります。",
-        "filter_hint": {
-          "always_drop_shadow": "ブラウザーがサポートしていれば、常に {0} が使われます。",
-          "drop_shadow_syntax": "{0} は、{1} パラメーターと {2} キーワードをサポートしていません。",
-          "avatar_inset": "内側の影と外側の影を同時に使うと、透明なアバターの表示が乱れます。",
-          "spread_zero": "広がりが 0 よりも大きな影は、0 と同じです。",
-          "inset_classic": "内側の影は {0} を使います。"
-        },
-        "components": {
-          "panel": "パネル",
-          "panelHeader": "パネルヘッダー",
-          "topBar": "トップバー",
-          "avatar": "ユーザーアバター (プロフィール)",
-          "avatarStatus": "ユーザーアバター (投稿)",
-          "popup": "ポップアップとツールチップ",
-          "button": "ボタン",
-          "buttonHover": "ボタン (ホバー)",
-          "buttonPressed": "ボタン (押されているとき)",
-          "buttonPressedHover": "ボタン (ホバー、かつ、押されているとき)",
-          "input": "インプットフィールド"
-        }
-      },
-      "fonts": {
-        "_tab_label": "フォント",
-        "help": "「カスタム」を選んだときは、システムにあるフォントの名前を、正しく入力してください。",
-        "components": {
-          "interface": "インターフェース",
-          "input": "インプットフィールド",
-          "post": "投稿",
-          "postCode": "等幅 (投稿がリッチテキストであるとき)"
-        },
-        "family": "フォント名",
-        "size": "大きさ (px)",
-        "weight": "太さ",
-        "custom": "カスタム"
-      },
-      "preview": {
-        "header": "プレビュー",
-        "content": "本文",
-        "error": "エラーの例",
-        "button": "ボタン",
-        "text": "これは{0}と{1}の例です。",
-        "mono": "monospace",
-        "input": "羽田空港に着きました。",
-        "faint_link": "とても助けになるマニュアル",
-        "fine_print": "私たちの{0}を、読まないでください!",
-        "header_faint": "エラーではありません",
-        "checkbox": "利用規約を読みました",
-        "link": "ハイパーリンク"
-      }
-    },
-    "version": {
-      "title": "バージョン",
-      "backend_version": "バックエンドのバージョン",
-      "frontend_version": "フロントエンドのバージョン"
-    }
-  },
-  "time": {
-    "day": "{0}日",
-    "days": "{0}日",
-    "day_short": "{0}日",
-    "days_short": "{0}日",
-    "hour": "{0}時間",
-    "hours": "{0}時間",
-    "hour_short": "{0}時間",
-    "hours_short": "{0}時間",
-    "in_future": "{0}で",
-    "in_past": "{0}前",
-    "minute": "{0}分",
-    "minutes": "{0}分",
-    "minute_short": "{0}分",
-    "minutes_short": "{0}分",
-    "month": "{0}ヶ月前",
-    "months": "{0}ヶ月前",
-    "month_short": "{0}ヶ月前",
-    "months_short": "{0}ヶ月前",
-    "now": "たった今",
-    "now_short": "たった今",
-    "second": "{0}秒",
-    "seconds": "{0}秒",
-    "second_short": "{0}秒",
-    "seconds_short": "{0}秒",
-    "week": "{0}週間",
-    "weeks": "{0}週間",
-    "week_short": "{0}週間",
-    "weeks_short": "{0}週間",
-    "year": "{0}年",
-    "years": "{0}年",
-    "year_short": "{0}年",
-    "years_short": "{0}年"
-  },
-  "timeline": {
-    "collapse": "たたむ",
-    "conversation": "スレッド",
-    "error_fetching": "読み込みがエラーになりました",
-    "load_older": "古いステータス",
-    "no_retweet_hint": "投稿を「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります",
-    "repeated": "リピート",
-    "show_new": "読み込み",
-    "up_to_date": "最新",
-    "no_more_statuses": "これで終わりです",
-    "no_statuses": "ステータスはありません"
-  },
-  "status": {
-    "favorites": "お気に入り",
-    "repeats": "リピート",
-    "delete": "ステータスを削除",
-    "pin": "プロフィールにピン留め",
-    "unpin": "プロフィールのピン留めを外す",
-    "pinned": "ピン留め",
-    "delete_confirm": "本当にこのステータスを削除してもよろしいですか?",
-    "reply_to": "返信",
-    "replies_list": "返信:",
-    "mute_conversation": "スレッドをミュート",
-    "unmute_conversation": "スレッドのミュートを解除"
-  },
-  "user_card": {
-    "approve": "受け入れ",
-    "block": "ブロック",
-    "blocked": "ブロックしています!",
-    "deny": "お断り",
-    "favorites": "お気に入り",
-    "follow": "フォロー",
-    "follow_sent": "リクエストを送りました!",
-    "follow_progress": "リクエストしています…",
-    "follow_again": "再びリクエストを送りますか?",
-    "follow_unfollow": "フォローをやめる",
-    "followees": "フォロー",
-    "followers": "フォロワー",
-    "following": "フォローしています!",
-    "follows_you": "フォローされました!",
-    "its_you": "これはあなたです!",
-    "media": "メディア",
-    "mention": "メンション",
-    "mute": "ミュート",
-    "muted": "ミュートしています!",
-    "per_day": "/日",
-    "remote_follow": "リモートフォロー",
-    "report": "通報",
-    "statuses": "ステータス",
-    "subscribe": "購読",
-    "unsubscribe": "購読を解除",
-    "unblock": "ブロック解除",
-    "unblock_progress": "ブロックを解除しています...",
-    "block_progress": "ブロックしています...",
-    "unmute": "ミュート解除",
-    "unmute_progress": "ミュートを解除しています...",
-    "mute_progress": "ミュートしています...",
-    "admin_menu": {
-      "moderation": "モデレーション",
-      "grant_admin": "管理者権限を付与",
-      "revoke_admin": "管理者権限を解除",
-      "grant_moderator": "モデレーター権限を付与",
-      "revoke_moderator": "モデレーター権限を解除",
-      "activate_account": "アカウントをアクティブにする",
-      "deactivate_account": "アカウントをアクティブでなくする",
-      "delete_account": "アカウントを削除",
-      "force_nsfw": "すべての投稿をNSFWにする",
-      "strip_media": "投稿からメディアを除去する",
-      "force_unlisted": "投稿を未収載にする",
-      "sandbox": "投稿をフォロワーのみにする",
-      "disable_remote_subscription": "他のインスタンスからフォローされないようにする",
-      "disable_any_subscription": "フォローされないようにする",
-      "quarantine": "他のインスタンスからの投稿を止める",
-      "delete_user": "ユーザーを削除",
-      "delete_user_confirmation": "あなたの精神状態に何か問題はございませんか? この操作を取り消すことはできません。"
-    }
-  },
-  "user_profile": {
-    "timeline_title": "ユーザータイムライン",
-    "profile_does_not_exist": "申し訳ない。このプロフィールは存在しません。",
-    "profile_loading_error": "申し訳ない。プロフィールの読み込みがエラーになりました。"
-  },
-  "user_reporting": {
-    "title": "通報する: {0}",
-    "add_comment_description": "この通報は、あなたのインスタンスのモデレーターに送られます。このアカウントを通報する理由を説明することができます:",
-    "additional_comments": "追加のコメント",
-    "forward_description": "このアカウントは他のサーバーに置かれています。この通報のコピーをリモートのサーバーに送りますか?",
-    "forward_to": "転送する: {0}",
-    "submit": "送信",
-    "generic_error": "あなたのリクエストを処理しようとしましたが、エラーになりました。"
-  },
-  "who_to_follow": {
-    "more": "詳細",
-    "who_to_follow": "おすすめユーザー"
-  },
-  "tool_tip": {
-    "media_upload": "メディアをアップロード",
-    "repeat": "リピート",
-    "reply": "返信",
-    "favorite": "お気に入り",
-    "user_settings": "ユーザー設定"
-  },
-  "upload":{
-    "error": {
-    "base": "アップロードに失敗しました。",
-    "file_too_big": "ファイルが大きすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
-    "default": "しばらくしてから試してください"
-    },
-    "file_size_units": {
-      "B": "B",
-      "KiB": "KiB",
-      "MiB": "MiB",
-      "GiB": "GiB",
-      "TiB": "TiB"
-    }
-  },
-  "search": {
-    "people": "人々",
-    "hashtags": "ハッシュタグ",
-    "person_talking": "{count} 人が話しています",
-    "people_talking": "{count} 人が話しています",
-    "no_results": "見つかりませんでした"
-  },
-  "password_reset": {
-    "forgot_password": "パスワードを忘れましたか?",
-    "password_reset": "パスワードリセット",
-    "instruction": "メールアドレスまたはユーザー名を入力してください。パスワードをリセットするためのリンクを送信します。",
-    "placeholder": "メールアドレスまたはユーザー名",
-    "check_email": "パスワードをリセットするためのリンクが記載されたメールが届いているか確認してください。",
-    "return_home": "ホームページに戻る",
-    "not_found": "メールアドレスまたはユーザー名が見つかりませんでした。",
-    "too_many_requests": "試行回数の制限に達しました。しばらく時間を置いてから再試行してください。",
-    "password_reset_disabled": "このインスタンスではパスワードリセットは無効になっています。インスタンスの管理者に連絡してください。"
-  }
-}
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index 89c8a8c8..774a48e0 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -24,7 +24,7 @@ const messages = {
   hu: require('./hu.json'),
   it: require('./it.json'),
   ja: require('./ja.json'),
-  ja_pedantic: require('./ja_pedantic.json'),
+  ja_easy: require('./ja_easy.json'),
   ko: require('./ko.json'),
   nb: require('./nb.json'),
   nl: require('./nl.json'),

From 44cd5ef8145b6799f7956401bb7cdd847e34c878 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Fri, 15 Nov 2019 12:52:29 -0500
Subject: [PATCH 034/483] show badge visibility user setting checkbox only if
 needed

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

diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 8c18cf49..3f1982a6 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -104,7 +104,7 @@
                 {{ $t('settings.hide_followers_count_description') }}
               </Checkbox>
             </p>
-            <p>
+            <p v-if="role === 'admin' || role === 'moderator'">
               <Checkbox v-model="showRole">
                 <template v-if="role === 'admin'">
                   {{ $t('settings.show_admin_badge') }}

From 0995658757b89eeb38b78e997bec2d85b96296af Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Tue, 19 Nov 2019 14:07:15 +0000
Subject: [PATCH 035/483] backend interactor service: implement
 startFetchingFollowRequest

backend interactor service: remove unused fetchFollowRequests
---
 src/components/nav_panel/nav_panel.js                     | 7 +------
 src/components/side_drawer/side_drawer.js                 | 4 ++++
 src/modules/api.js                                        | 7 +++++++
 src/modules/users.js                                      | 1 +
 .../backend_interactor_service.js                         | 8 ++++++--
 5 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index aa3f7605..7f783acb 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -1,12 +1,7 @@
-import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
-
 const NavPanel = {
   created () {
     if (this.currentUser && this.currentUser.locked) {
-      const store = this.$store
-      const credentials = store.state.users.currentUser.credentials
-
-      followRequestFetcher.startFetching({ store, credentials })
+      this.$store.dispatch('startFetchingFollowRequest')
     }
   },
   computed: {
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 567d2e5e..0188cf3e 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -10,6 +10,10 @@ const SideDrawer = {
   }),
   created () {
     this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, this.toggleDrawer)
+
+    if (this.currentUser && this.currentUser.locked) {
+      this.$store.dispatch('startFetchingFollowRequest')
+    }
   },
   components: { UserCard },
   computed: {
diff --git a/src/modules/api.js b/src/modules/api.js
index eb6a7980..1293e3c8 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -43,6 +43,13 @@ const api = {
       const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
       store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
     },
+    startFetchingFollowRequest (store) {
+      // Don't start fetching if we already are.
+      if (store.state.fetchers['followRequest']) return
+
+      const fetcher = store.state.backendInteractor.startFetchingFollowRequest({ store })
+      store.commit('addFetcher', { fetcherName: 'followRequest', fetcher })
+    },
     stopFetching (store, fetcherName) {
       const fetcher = store.state.fetchers[fetcherName]
       window.clearInterval(fetcher)
diff --git a/src/modules/users.js b/src/modules/users.js
index 1c9ff5e8..14b2d8b5 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -434,6 +434,7 @@ const users = {
           store.dispatch('stopFetching', 'friends')
           store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
           store.dispatch('stopFetching', 'notifications')
+          store.dispatch('stopFetching', 'followRequest')
           store.commit('clearNotifications')
           store.commit('resetStatuses')
         })
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index d6617276..c16bd1f1 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -1,6 +1,7 @@
 import apiService from '../api/api.service.js'
 import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
 import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
+import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
 
 const backendInteractorService = credentials => {
   const fetchStatus = ({ id }) => {
@@ -63,6 +64,10 @@ const backendInteractorService = credentials => {
     return notificationsFetcher.startFetching({ store, credentials })
   }
 
+  const startFetchingFollowRequest = ({ store }) => {
+    return followRequestFetcher.startFetching({ store, credentials })
+  }
+
   // eslint-disable-next-line camelcase
   const tagUser = ({ screen_name }, tag) => {
     return apiService.tagUser({ screen_name, tag, credentials })
@@ -111,7 +116,6 @@ const backendInteractorService = credentials => {
   const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
   const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
   const fetchBlocks = () => apiService.fetchBlocks({ credentials })
-  const fetchFollowRequests = () => apiService.fetchFollowRequests({ credentials })
   const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
   const revokeOAuthToken = (id) => apiService.revokeOAuthToken({ id, credentials })
   const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
@@ -168,6 +172,7 @@ const backendInteractorService = credentials => {
     verifyCredentials: apiService.verifyCredentials,
     startFetchingTimeline,
     startFetchingNotifications,
+    startFetchingFollowRequest,
     fetchMutes,
     muteUser,
     unmuteUser,
@@ -203,7 +208,6 @@ const backendInteractorService = credentials => {
     mfaSetupOTP,
     mfaConfirmOTP,
     mfaDisableOTP,
-    fetchFollowRequests,
     approveUser,
     denyUser,
     vote,

From a55486f8d78161166e84b2b69709a49b3845c14e Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Tue, 19 Nov 2019 14:15:41 +0000
Subject: [PATCH 036/483] Normalize profile fields

---
 .../entity_normalizer/entity_normalizer.service.js  |  9 +++++++++
 .../entity_normalizer/entity_normalizer.spec.js     | 13 +++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 5f45660d..ca79df6f 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -46,6 +46,14 @@ export const parseUser = (data) => {
     output.description = data.note
     output.description_html = addEmojis(data.note, data.emojis)
 
+    output.fields = data.fields
+    output.fields_html = data.fields.map(field => {
+      return {
+        name: addEmojis(field.name, data.emojis),
+        value: addEmojis(field.value, data.emojis)
+      }
+    })
+
     // Utilize avatar_static for gif avatars?
     output.profile_image_url = data.avatar
     output.profile_image_url_original = data.avatar
@@ -95,6 +103,7 @@ export const parseUser = (data) => {
     if (data.source) {
       output.description = data.source.note
       output.default_scope = data.source.privacy
+      output.fields = data.source.fields
       if (data.source.pleroma) {
         output.no_rich_text = data.source.pleroma.no_rich_text
         output.show_role = data.source.pleroma.show_role
diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
index 49f378e2..cfb380ba 100644
--- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
+++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -277,6 +277,19 @@ describe('API Entities normalizer', () => {
       expect(parsedUser).to.have.property('description_html').that.contains('<img')
     })
 
+    it('adds emojis to user profile fields', () => {
+      const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), fields: [{ name: ':thinking:', value: ':image:' }] })
+
+      const parsedUser = parseUser(user)
+
+      expect(parsedUser).to.have.property('fields_html').to.be.an('array')
+
+      const field = parsedUser.fields_html[0]
+
+      expect(field).to.have.property('name').that.contains('<img')
+      expect(field).to.have.property('value').that.contains('<img')
+    })
+
     it('adds hide_follows and hide_followers user settings', () => {
       const user = makeMockUserMasto({ pleroma: { hide_followers: true, hide_follows: false, hide_followers_count: false, hide_follows_count: true } })
 

From 55da4462a4934f00486fdcaba5d43b3a092860e9 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo <hakabahitoyo@yahoo.co.jp>
Date: Tue, 19 Nov 2019 17:02:45 +0000
Subject: [PATCH 037/483] [i18n] Improve easy/pedantic Japanese switching

---
 src/i18n/{ja.json => ja_pedantic.json} | 0
 src/i18n/messages.js                   | 2 +-
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename src/i18n/{ja.json => ja_pedantic.json} (100%)

diff --git a/src/i18n/ja.json b/src/i18n/ja_pedantic.json
similarity index 100%
rename from src/i18n/ja.json
rename to src/i18n/ja_pedantic.json
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index 774a48e0..c56ae205 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -23,7 +23,7 @@ const messages = {
   he: require('./he.json'),
   hu: require('./hu.json'),
   it: require('./it.json'),
-  ja: require('./ja.json'),
+  ja: require('./ja_pedantic.json'),
   ja_easy: require('./ja_easy.json'),
   ko: require('./ko.json'),
   nb: require('./nb.json'),

From ddb6fb9217789e90490a4ec1ce7a2dd9ced67631 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 24 Nov 2019 13:57:46 +0200
Subject: [PATCH 038/483] Backend Interactor service overhaul, removed the need
 for copypasting

---
 .../follow_request_card.js                    |   4 +-
 .../moderation_tools/moderation_tools.js      |  16 +-
 .../user_reporting_modal.js                   |   2 +-
 src/components/user_settings/user_settings.js |   8 +-
 src/modules/oauth_tokens.js                   |   2 +-
 src/modules/polls.js                          |   4 +-
 src/modules/statuses.js                       |  26 +-
 src/modules/users.js                          |  16 +-
 .../backend_interactor_service.js             | 231 ++----------------
 .../follow_manipulate/follow_manipulate.js    |   2 +-
 10 files changed, 55 insertions(+), 256 deletions(-)

diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
index 1a00a1c1..a8931787 100644
--- a/src/components/follow_request_card/follow_request_card.js
+++ b/src/components/follow_request_card/follow_request_card.js
@@ -7,11 +7,11 @@ const FollowRequestCard = {
   },
   methods: {
     approveUser () {
-      this.$store.state.api.backendInteractor.approveUser(this.user.id)
+      this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
       this.$store.dispatch('removeFollowRequest', this.user)
     },
     denyUser () {
-      this.$store.state.api.backendInteractor.denyUser(this.user.id)
+      this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
       this.$store.dispatch('removeFollowRequest', this.user)
     }
   }
diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
index 8aadc8c5..5bb76497 100644
--- a/src/components/moderation_tools/moderation_tools.js
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -45,12 +45,12 @@ const ModerationTools = {
     toggleTag (tag) {
       const store = this.$store
       if (this.tagsSet.has(tag)) {
-        store.state.api.backendInteractor.untagUser(this.user, tag).then(response => {
+        store.state.api.backendInteractor.untagUser({ user: this.user, tag }).then(response => {
           if (!response.ok) { return }
           store.commit('untagUser', { user: this.user, tag })
         })
       } else {
-        store.state.api.backendInteractor.tagUser(this.user, tag).then(response => {
+        store.state.api.backendInteractor.tagUser({ user: this.user, tag }).then(response => {
           if (!response.ok) { return }
           store.commit('tagUser', { user: this.user, tag })
         })
@@ -59,21 +59,21 @@ const ModerationTools = {
     toggleRight (right) {
       const store = this.$store
       if (this.user.rights[right]) {
-        store.state.api.backendInteractor.deleteRight(this.user, right).then(response => {
+        store.state.api.backendInteractor.deleteRight({ user: this.user, right }).then(response => {
           if (!response.ok) { return }
-          store.commit('updateRight', { user: this.user, right: right, value: false })
+          store.commit('updateRight', { user: this.user, right, value: false })
         })
       } else {
-        store.state.api.backendInteractor.addRight(this.user, right).then(response => {
+        store.state.api.backendInteractor.addRight({ user: this.user, right }).then(response => {
           if (!response.ok) { return }
-          store.commit('updateRight', { user: this.user, right: right, value: true })
+          store.commit('updateRight', { user: this.user, right, value: true })
         })
       }
     },
     toggleActivationStatus () {
       const store = this.$store
       const status = !!this.user.deactivated
-      store.state.api.backendInteractor.setActivationStatus(this.user, status).then(response => {
+      store.state.api.backendInteractor.setActivationStatus({ user: this.user, status }).then(response => {
         if (!response.ok) { return }
         store.commit('updateActivationStatus', { user: this.user, status: status })
       })
@@ -85,7 +85,7 @@ const ModerationTools = {
       const store = this.$store
       const user = this.user
       const { id, name } = user
-      store.state.api.backendInteractor.deleteUser(user)
+      store.state.api.backendInteractor.deleteUser({ user })
         .then(e => {
           this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id)
           const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile'
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
index 833fa98a..38cf117b 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.js
+++ b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -64,7 +64,7 @@ const UserReportingModal = {
         forward: this.forward,
         statusIds: this.statusIdsToReport
       }
-      this.$store.state.api.backendInteractor.reportUser(params)
+      this.$store.state.api.backendInteractor.reportUser({ ...params })
         .then(() => {
           this.processing = false
           this.resetState()
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 3fdc5340..d5d671e4 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -242,7 +242,7 @@ const UserSettings = {
       })
     },
     importFollows (file) {
-      return this.$store.state.api.backendInteractor.importFollows(file)
+      return this.$store.state.api.backendInteractor.importFollows({ file })
         .then((status) => {
           if (!status) {
             throw new Error('failed')
@@ -250,7 +250,7 @@ const UserSettings = {
         })
     },
     importBlocks (file) {
-      return this.$store.state.api.backendInteractor.importBlocks(file)
+      return this.$store.state.api.backendInteractor.importBlocks({ file })
         .then((status) => {
           if (!status) {
             throw new Error('failed')
@@ -297,7 +297,7 @@ const UserSettings = {
         newPassword: this.changePasswordInputs[1],
         newPasswordConfirmation: this.changePasswordInputs[2]
       }
-      this.$store.state.api.backendInteractor.changePassword(params)
+      this.$store.state.api.backendInteractor.changePassword({ params })
         .then((res) => {
           if (res.status === 'success') {
             this.changedPassword = true
@@ -314,7 +314,7 @@ const UserSettings = {
         email: this.newEmail,
         password: this.changeEmailPassword
       }
-      this.$store.state.api.backendInteractor.changeEmail(params)
+      this.$store.state.api.backendInteractor.changeEmail({ params })
         .then((res) => {
           if (res.status === 'success') {
             this.changedEmail = true
diff --git a/src/modules/oauth_tokens.js b/src/modules/oauth_tokens.js
index 0159a3f1..907cae4a 100644
--- a/src/modules/oauth_tokens.js
+++ b/src/modules/oauth_tokens.js
@@ -9,7 +9,7 @@ const oauthTokens = {
       })
     },
     revokeToken ({ rootState, commit, state }, id) {
-      rootState.api.backendInteractor.revokeOAuthToken(id).then((response) => {
+      rootState.api.backendInteractor.revokeOAuthToken({ id }).then((response) => {
         if (response.status === 201) {
           commit('swapTokens', state.tokens.filter(token => token.id !== id))
         }
diff --git a/src/modules/polls.js b/src/modules/polls.js
index e6158b63..92b89a06 100644
--- a/src/modules/polls.js
+++ b/src/modules/polls.js
@@ -40,7 +40,7 @@ const polls = {
       commit('mergeOrAddPoll', poll)
     },
     updateTrackedPoll ({ rootState, dispatch, commit }, pollId) {
-      rootState.api.backendInteractor.fetchPoll(pollId).then(poll => {
+      rootState.api.backendInteractor.fetchPoll({ pollId }).then(poll => {
         setTimeout(() => {
           if (rootState.polls.trackedPolls[pollId]) {
             dispatch('updateTrackedPoll', pollId)
@@ -59,7 +59,7 @@ const polls = {
       commit('untrackPoll', pollId)
     },
     votePoll ({ rootState, commit }, { id, pollId, choices }) {
-      return rootState.api.backendInteractor.vote(pollId, choices).then(poll => {
+      return rootState.api.backendInteractor.vote({ pollId, choices }).then(poll => {
         commit('mergeOrAddPoll', poll)
         return poll
       })
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index f11ffdcd..6a743a4a 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -551,45 +551,45 @@ const statuses = {
     favorite ({ rootState, commit }, status) {
       // Optimistic favoriting...
       commit('setFavorited', { status, value: true })
-      rootState.api.backendInteractor.favorite(status.id)
+      rootState.api.backendInteractor.favorite({ id: status.id })
         .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
     },
     unfavorite ({ rootState, commit }, status) {
       // Optimistic unfavoriting...
       commit('setFavorited', { status, value: false })
-      rootState.api.backendInteractor.unfavorite(status.id)
+      rootState.api.backendInteractor.unfavorite({ id: status.id })
         .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
     },
     fetchPinnedStatuses ({ rootState, dispatch }, userId) {
-      rootState.api.backendInteractor.fetchPinnedStatuses(userId)
+      rootState.api.backendInteractor.fetchPinnedStatuses({ id: userId })
         .then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true }))
     },
     pinStatus ({ rootState, dispatch }, statusId) {
-      return rootState.api.backendInteractor.pinOwnStatus(statusId)
+      return rootState.api.backendInteractor.pinOwnStatus({ id: statusId })
         .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
     },
     unpinStatus ({ rootState, dispatch }, statusId) {
-      rootState.api.backendInteractor.unpinOwnStatus(statusId)
+      rootState.api.backendInteractor.unpinOwnStatus({ id: statusId })
         .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
     },
     muteConversation ({ rootState, commit }, statusId) {
-      return rootState.api.backendInteractor.muteConversation(statusId)
+      return rootState.api.backendInteractor.muteConversation({ id: statusId })
         .then((status) => commit('setMutedStatus', status))
     },
     unmuteConversation ({ rootState, commit }, statusId) {
-      return rootState.api.backendInteractor.unmuteConversation(statusId)
+      return rootState.api.backendInteractor.unmuteConversation({ id: statusId })
         .then((status) => commit('setMutedStatus', status))
     },
     retweet ({ rootState, commit }, status) {
       // Optimistic retweeting...
       commit('setRetweeted', { status, value: true })
-      rootState.api.backendInteractor.retweet(status.id)
+      rootState.api.backendInteractor.retweet({ id: status.id })
         .then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser }))
     },
     unretweet ({ rootState, commit }, status) {
       // Optimistic unretweeting...
       commit('setRetweeted', { status, value: false })
-      rootState.api.backendInteractor.unretweet(status.id)
+      rootState.api.backendInteractor.unretweet({ id: status.id })
         .then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser }))
     },
     queueFlush ({ rootState, commit }, { timeline, id }) {
@@ -604,19 +604,19 @@ const statuses = {
     },
     fetchFavsAndRepeats ({ rootState, commit }, id) {
       Promise.all([
-        rootState.api.backendInteractor.fetchFavoritedByUsers(id),
-        rootState.api.backendInteractor.fetchRebloggedByUsers(id)
+        rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
+        rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
       ]).then(([favoritedByUsers, rebloggedByUsers]) => {
         commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser })
         commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
       })
     },
     fetchFavs ({ rootState, commit }, id) {
-      rootState.api.backendInteractor.fetchFavoritedByUsers(id)
+      rootState.api.backendInteractor.fetchFavoritedByUsers({ id })
         .then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
     },
     fetchRepeats ({ rootState, commit }, id) {
-      rootState.api.backendInteractor.fetchRebloggedByUsers(id)
+      rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
         .then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
     },
     search (store, { q, resolve, limit, offset, following }) {
diff --git a/src/modules/users.js b/src/modules/users.js
index 14b2d8b5..e1373220 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -32,7 +32,7 @@ const getNotificationPermission = () => {
 }
 
 const blockUser = (store, id) => {
-  return store.rootState.api.backendInteractor.blockUser(id)
+  return store.rootState.api.backendInteractor.blockUser({ id })
     .then((relationship) => {
       store.commit('updateUserRelationship', [relationship])
       store.commit('addBlockId', id)
@@ -43,12 +43,12 @@ const blockUser = (store, id) => {
 }
 
 const unblockUser = (store, id) => {
-  return store.rootState.api.backendInteractor.unblockUser(id)
+  return store.rootState.api.backendInteractor.unblockUser({ id })
     .then((relationship) => store.commit('updateUserRelationship', [relationship]))
 }
 
 const muteUser = (store, id) => {
-  return store.rootState.api.backendInteractor.muteUser(id)
+  return store.rootState.api.backendInteractor.muteUser({ id })
     .then((relationship) => {
       store.commit('updateUserRelationship', [relationship])
       store.commit('addMuteId', id)
@@ -56,7 +56,7 @@ const muteUser = (store, id) => {
 }
 
 const unmuteUser = (store, id) => {
-  return store.rootState.api.backendInteractor.unmuteUser(id)
+  return store.rootState.api.backendInteractor.unmuteUser({ id })
     .then((relationship) => store.commit('updateUserRelationship', [relationship]))
 }
 
@@ -324,11 +324,11 @@ const users = {
       commit('clearFollowers', userId)
     },
     subscribeUser ({ rootState, commit }, id) {
-      return rootState.api.backendInteractor.subscribeUser(id)
+      return rootState.api.backendInteractor.subscribeUser({ id })
         .then((relationship) => commit('updateUserRelationship', [relationship]))
     },
     unsubscribeUser ({ rootState, commit }, id) {
-      return rootState.api.backendInteractor.unsubscribeUser(id)
+      return rootState.api.backendInteractor.unsubscribeUser({ id })
         .then((relationship) => commit('updateUserRelationship', [relationship]))
     },
     registerPushNotifications (store) {
@@ -382,7 +382,7 @@ const users = {
       })
     },
     searchUsers (store, query) {
-      return store.rootState.api.backendInteractor.searchUsers(query)
+      return store.rootState.api.backendInteractor.searchUsers({ query })
         .then((users) => {
           store.commit('addNewUsers', users)
           return users
@@ -394,7 +394,7 @@ const users = {
       let rootState = store.rootState
 
       try {
-        let data = await rootState.api.backendInteractor.register(userInfo)
+        let data = await rootState.api.backendInteractor.register({ ...userInfo })
         store.commit('signUpSuccess')
         store.commit('setToken', data.access_token)
         store.dispatch('loginUser', data.access_token)
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index c16bd1f1..57fdccde 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -3,228 +3,27 @@ import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service
 import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
 import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
 
-const backendInteractorService = credentials => {
-  const fetchStatus = ({ id }) => {
-    return apiService.fetchStatus({ id, credentials })
-  }
-
-  const fetchConversation = ({ id }) => {
-    return apiService.fetchConversation({ id, credentials })
-  }
-
-  const fetchFriends = ({ id, maxId, sinceId, limit }) => {
-    return apiService.fetchFriends({ id, maxId, sinceId, limit, credentials })
-  }
-
-  const exportFriends = ({ id }) => {
-    return apiService.exportFriends({ id, credentials })
-  }
-
-  const fetchFollowers = ({ id, maxId, sinceId, limit }) => {
-    return apiService.fetchFollowers({ id, maxId, sinceId, limit, credentials })
-  }
-
-  const fetchUser = ({ id }) => {
-    return apiService.fetchUser({ id, credentials })
-  }
-
-  const fetchUserRelationship = ({ id }) => {
-    return apiService.fetchUserRelationship({ id, credentials })
-  }
-
-  const followUser = ({ id, reblogs }) => {
-    return apiService.followUser({ credentials, id, reblogs })
-  }
-
-  const unfollowUser = (id) => {
-    return apiService.unfollowUser({ credentials, id })
-  }
-
-  const blockUser = (id) => {
-    return apiService.blockUser({ credentials, id })
-  }
-
-  const unblockUser = (id) => {
-    return apiService.unblockUser({ credentials, id })
-  }
-
-  const approveUser = (id) => {
-    return apiService.approveUser({ credentials, id })
-  }
-
-  const denyUser = (id) => {
-    return apiService.denyUser({ credentials, id })
-  }
-
-  const startFetchingTimeline = ({ timeline, store, userId = false, tag }) => {
+const backendInteractorService = credentials => ({
+  startFetchingTimeline ({ timeline, store, userId = false, tag }) {
     return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag })
-  }
+  },
 
-  const startFetchingNotifications = ({ store }) => {
+  startFetchingNotifications ({ store }) {
     return notificationsFetcher.startFetching({ store, credentials })
-  }
+  },
 
-  const startFetchingFollowRequest = ({ store }) => {
+  startFetchingFollowRequest ({ store }) {
     return followRequestFetcher.startFetching({ store, credentials })
-  }
+  },
 
-  // eslint-disable-next-line camelcase
-  const tagUser = ({ screen_name }, tag) => {
-    return apiService.tagUser({ screen_name, tag, credentials })
-  }
+  ...Object.entries(apiService).reduce((acc, [key, func]) => {
+    return {
+      ...acc,
+      [key]: (args) => func({ credentials, ...args })
+    }
+  }, {}),
 
-  // eslint-disable-next-line camelcase
-  const untagUser = ({ screen_name }, tag) => {
-    return apiService.untagUser({ screen_name, tag, credentials })
-  }
-
-  // eslint-disable-next-line camelcase
-  const addRight = ({ screen_name }, right) => {
-    return apiService.addRight({ screen_name, right, credentials })
-  }
-
-  // eslint-disable-next-line camelcase
-  const deleteRight = ({ screen_name }, right) => {
-    return apiService.deleteRight({ screen_name, right, credentials })
-  }
-
-  // eslint-disable-next-line camelcase
-  const setActivationStatus = ({ screen_name }, status) => {
-    return apiService.setActivationStatus({ screen_name, status, credentials })
-  }
-
-  // eslint-disable-next-line camelcase
-  const deleteUser = ({ screen_name }) => {
-    return apiService.deleteUser({ screen_name, credentials })
-  }
-
-  const vote = (pollId, choices) => {
-    return apiService.vote({ credentials, pollId, choices })
-  }
-
-  const fetchPoll = (pollId) => {
-    return apiService.fetchPoll({ credentials, pollId })
-  }
-
-  const updateNotificationSettings = ({ settings }) => {
-    return apiService.updateNotificationSettings({ credentials, settings })
-  }
-
-  const fetchMutes = () => apiService.fetchMutes({ credentials })
-  const muteUser = (id) => apiService.muteUser({ credentials, id })
-  const unmuteUser = (id) => apiService.unmuteUser({ credentials, id })
-  const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
-  const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
-  const fetchBlocks = () => apiService.fetchBlocks({ credentials })
-  const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
-  const revokeOAuthToken = (id) => apiService.revokeOAuthToken({ id, credentials })
-  const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
-  const pinOwnStatus = (id) => apiService.pinOwnStatus({ credentials, id })
-  const unpinOwnStatus = (id) => apiService.unpinOwnStatus({ credentials, id })
-  const muteConversation = (id) => apiService.muteConversation({ credentials, id })
-  const unmuteConversation = (id) => apiService.unmuteConversation({ credentials, id })
-
-  const getCaptcha = () => apiService.getCaptcha()
-  const register = (params) => apiService.register({ credentials, params })
-  const updateAvatar = ({ avatar }) => apiService.updateAvatar({ credentials, avatar })
-  const updateBg = ({ background }) => apiService.updateBg({ credentials, background })
-  const updateBanner = ({ banner }) => apiService.updateBanner({ credentials, banner })
-  const updateProfile = ({ params }) => apiService.updateProfile({ credentials, params })
-
-  const importBlocks = (file) => apiService.importBlocks({ file, credentials })
-  const importFollows = (file) => apiService.importFollows({ file, credentials })
-
-  const deleteAccount = ({ password }) => apiService.deleteAccount({ credentials, password })
-  const changeEmail = ({ email, password }) => apiService.changeEmail({ credentials, email, password })
-  const changePassword = ({ password, newPassword, newPasswordConfirmation }) =>
-    apiService.changePassword({ credentials, password, newPassword, newPasswordConfirmation })
-
-  const fetchSettingsMFA = () => apiService.settingsMFA({ credentials })
-  const generateMfaBackupCodes = () => apiService.generateMfaBackupCodes({ credentials })
-  const mfaSetupOTP = () => apiService.mfaSetupOTP({ credentials })
-  const mfaConfirmOTP = ({ password, token }) => apiService.mfaConfirmOTP({ credentials, password, token })
-  const mfaDisableOTP = ({ password }) => apiService.mfaDisableOTP({ credentials, password })
-
-  const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id })
-  const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id })
-  const reportUser = (params) => apiService.reportUser({ credentials, ...params })
-
-  const favorite = (id) => apiService.favorite({ id, credentials })
-  const unfavorite = (id) => apiService.unfavorite({ id, credentials })
-  const retweet = (id) => apiService.retweet({ id, credentials })
-  const unretweet = (id) => apiService.unretweet({ id, credentials })
-  const search2 = ({ q, resolve, limit, offset, following }) =>
-    apiService.search2({ credentials, q, resolve, limit, offset, following })
-  const searchUsers = (query) => apiService.searchUsers({ query, credentials })
-
-  const backendInteractorServiceInstance = {
-    fetchStatus,
-    fetchConversation,
-    fetchFriends,
-    exportFriends,
-    fetchFollowers,
-    followUser,
-    unfollowUser,
-    blockUser,
-    unblockUser,
-    fetchUser,
-    fetchUserRelationship,
-    verifyCredentials: apiService.verifyCredentials,
-    startFetchingTimeline,
-    startFetchingNotifications,
-    startFetchingFollowRequest,
-    fetchMutes,
-    muteUser,
-    unmuteUser,
-    subscribeUser,
-    unsubscribeUser,
-    fetchBlocks,
-    fetchOAuthTokens,
-    revokeOAuthToken,
-    fetchPinnedStatuses,
-    pinOwnStatus,
-    unpinOwnStatus,
-    muteConversation,
-    unmuteConversation,
-    tagUser,
-    untagUser,
-    addRight,
-    deleteRight,
-    deleteUser,
-    setActivationStatus,
-    register,
-    getCaptcha,
-    updateAvatar,
-    updateBg,
-    updateBanner,
-    updateProfile,
-    importBlocks,
-    importFollows,
-    deleteAccount,
-    changeEmail,
-    changePassword,
-    fetchSettingsMFA,
-    generateMfaBackupCodes,
-    mfaSetupOTP,
-    mfaConfirmOTP,
-    mfaDisableOTP,
-    approveUser,
-    denyUser,
-    vote,
-    fetchPoll,
-    fetchFavoritedByUsers,
-    fetchRebloggedByUsers,
-    reportUser,
-    favorite,
-    unfavorite,
-    retweet,
-    unretweet,
-    updateNotificationSettings,
-    search2,
-    searchUsers
-  }
-
-  return backendInteractorServiceInstance
-}
+  verifyCredentials: apiService.verifyCredentials
+})
 
 export default backendInteractorService
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index 598cb5f7..29b38a0f 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -39,7 +39,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
 })
 
 export const requestUnfollow = (user, store) => new Promise((resolve, reject) => {
-  store.state.api.backendInteractor.unfollowUser(user.id)
+  store.state.api.backendInteractor.unfollowUser({ id: user.id })
     .then((updated) => {
       store.commit('updateUserRelationship', [updated])
       resolve({

From 319bb4ac2895b8eb62da42e3f95addc9bb67b1a0 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 24 Nov 2019 18:50:28 +0200
Subject: [PATCH 039/483] initial streaming work

---
 src/modules/api.js                            | 15 +++++++
 src/modules/users.js                          | 11 +++--
 src/services/api/api.service.js               | 40 +++++++++++++++++++
 .../backend_interactor_service.js             | 15 ++++++-
 4 files changed, 76 insertions(+), 5 deletions(-)

diff --git a/src/modules/api.js b/src/modules/api.js
index 1293e3c8..1bf65db5 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -6,6 +6,7 @@ const api = {
     backendInteractor: backendInteractorService(),
     fetchers: {},
     socket: null,
+    mastoSocket: null,
     followRequests: []
   },
   mutations: {
@@ -29,6 +30,20 @@ const api = {
     }
   },
   actions: {
+    startMastoSocket (store) {
+      store.state.mastoSocket = store.state.backendInteractor
+        .startUserSocket({
+          store,
+          onMessage: (message) => {
+            if (!message) return
+            if (message.event === 'notification') {
+              store.dispatch('addNewNotifications', { notifications: [message.notification], older: false })
+            } else if (message.event === 'update') {
+              store.dispatch('addNewStatuses', { statuses: [message.status], userId: false, showImmediately: false, timeline: 'friends' })
+            }
+          }
+        })
+    },
     startFetchingTimeline (store, { timeline = 'friends', tag = false, userId = false }) {
       // Don't start fetching if we already are.
       if (store.state.fetchers[timeline]) return
diff --git a/src/modules/users.js b/src/modules/users.js
index e1373220..861a2f4f 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -469,11 +469,14 @@ const users = {
                 store.dispatch('initializeSocket')
               }
 
-              // Start getting fresh posts.
-              store.dispatch('startFetchingTimeline', { timeline: 'friends' })
+              store.dispatch('startMastoSocket').catch((error) => {
+                console.error(error)
+                // Start getting fresh posts.
+                store.dispatch('startFetchingTimeline', { timeline: 'friends' })
 
-              // Start fetching notifications
-              store.dispatch('startFetchingNotifications')
+                // Start fetching notifications
+                store.dispatch('startFetchingNotifications')
+              })
 
               // Get user mutes
               store.dispatch('fetchMutes')
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 8f5eb416..7f27564f 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -71,6 +71,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
 const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
 const MASTODON_SEARCH_2 = `/api/v2/search`
 const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
+const MASTODON_STREAMING = '/api/v1/streaming'
 
 const oldfetch = window.fetch
 
@@ -932,6 +933,45 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
     })
 }
 
+export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
+  return Object.entries({
+    ...(credentials
+      ? { access_token: credentials }
+      : {}
+    ),
+    stream,
+    ...args
+  }).reduce((acc, [key, val]) => {
+    return acc + `${key}=${val}&`
+  }, MASTODON_STREAMING + '?')
+}
+
+const MASTODON_STREAMING_EVENTS = new Set([
+  'update',
+  'notification',
+  'delete',
+  'filters_changed'
+])
+
+export const handleMastoWS = (wsEvent) => {
+  console.debug('Event', wsEvent)
+  const { data } = wsEvent
+  if (!data) return
+  const parsedEvent = JSON.parse(data)
+  const { event, payload } = parsedEvent
+  if (MASTODON_STREAMING_EVENTS.has(event)) {
+    const data = payload ? JSON.parse(payload) : null
+    if (event === 'update') {
+      return { event, status: parseStatus(data) }
+    } else if (event === 'notification') {
+      return { event, notification: parseNotification(data) }
+    }
+  } else {
+    console.warn('Unknown event', wsEvent)
+    return null
+  }
+}
+
 const apiService = {
   verifyCredentials,
   fetchTimeline,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 57fdccde..0cef4640 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -1,4 +1,4 @@
-import apiService from '../api/api.service.js'
+import apiService, { getMastodonSocketURI, handleMastoWS } from '../api/api.service.js'
 import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
 import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
 import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
@@ -16,6 +16,19 @@ const backendInteractorService = credentials => ({
     return followRequestFetcher.startFetching({ store, credentials })
   },
 
+  startUserSocket ({ store, onMessage }) {
+    const serv = store.rootState.instance.server.replace('https', 'wss')
+    // const serb = 'ws://localhost:8080/'
+    const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })
+    const socket = new WebSocket(url)
+    console.log(socket)
+    if (socket) {
+      socket.addEventListener('message', (wsEvent) => onMessage(handleMastoWS(wsEvent)))
+    } else {
+      throw new Error('failed to connect to socket')
+    }
+  },
+
   ...Object.entries(apiService).reduce((acc, [key, func]) => {
     return {
       ...acc,

From 172ebaf4e67358852bfaafd8f069763ca5e602b1 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 24 Nov 2019 22:01:12 +0200
Subject: [PATCH 040/483] improved initial notifications fetching

---
 src/components/notifications/notifications.js |  5 ++++
 src/modules/api.js                            | 23 ++++++++++++++++---
 src/modules/users.js                          |  2 +-
 .../backend_interactor_service.js             |  9 ++++++--
 4 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index 6c4054fd..a89c0cdc 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -47,6 +47,11 @@ const Notifications = {
   components: {
     Notification
   },
+  created () {
+    const { dispatch } = this.$store
+
+    dispatch('fetchAndUpdateNotifications')
+  },
   watch: {
     unseenCount (count) {
       if (count > 0) {
diff --git a/src/modules/api.js b/src/modules/api.js
index 1bf65db5..0e7e5e19 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -31,18 +31,32 @@ const api = {
   },
   actions: {
     startMastoSocket (store) {
-      store.state.mastoSocket = store.state.backendInteractor
+      const { state, dispatch } = store
+      state.mastoSocket = state.backendInteractor
         .startUserSocket({
           store,
           onMessage: (message) => {
             if (!message) return
             if (message.event === 'notification') {
-              store.dispatch('addNewNotifications', { notifications: [message.notification], older: false })
+              dispatch('addNewNotifications', {
+                notifications: [message.notification],
+                older: false
+              })
             } else if (message.event === 'update') {
-              store.dispatch('addNewStatuses', { statuses: [message.status], userId: false, showImmediately: false, timeline: 'friends' })
+              dispatch('addNewStatuses', {
+                statuses: [message.status],
+                userId: false,
+                showImmediately: false,
+                timeline: 'friends'
+              })
             }
           }
         })
+      state.mastoSocket.addEventListener('error', error => {
+        console.error('Error with MastoAPI websocket:', error)
+        dispatch('startFetchingTimeline', { timeline: 'friends' })
+        dispatch('startFetchingNotifications')
+      })
     },
     startFetchingTimeline (store, { timeline = 'friends', tag = false, userId = false }) {
       // Don't start fetching if we already are.
@@ -58,6 +72,9 @@ const api = {
       const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
       store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
     },
+    fetchAndUpdateNotifications (store) {
+      store.state.backendInteractor.fetchAndUpdateNotifications({ store })
+    },
     startFetchingFollowRequest (store) {
       // Don't start fetching if we already are.
       if (store.state.fetchers['followRequest']) return
diff --git a/src/modules/users.js b/src/modules/users.js
index 861a2f4f..eff0c5d5 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -470,7 +470,7 @@ const users = {
               }
 
               store.dispatch('startMastoSocket').catch((error) => {
-                console.error(error)
+                console.error('Failed initializing MastoAPI Streaming socket', error)
                 // Start getting fresh posts.
                 store.dispatch('startFetchingTimeline', { timeline: 'friends' })
 
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 0cef4640..850b7867 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -12,18 +12,23 @@ const backendInteractorService = credentials => ({
     return notificationsFetcher.startFetching({ store, credentials })
   },
 
+  fetchAndUpdateNotifications ({ store }) {
+    return notificationsFetcher.fetchAndUpdate({ store, credentials })
+  },
+
   startFetchingFollowRequest ({ store }) {
     return followRequestFetcher.startFetching({ store, credentials })
   },
 
   startUserSocket ({ store, onMessage }) {
-    const serv = store.rootState.instance.server.replace('https', 'wss')
-    // const serb = 'ws://localhost:8080/'
+    const serv = store.rootState.instance.server.replace('http', 'ws')
     const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })
     const socket = new WebSocket(url)
     console.log(socket)
     if (socket) {
       socket.addEventListener('message', (wsEvent) => onMessage(handleMastoWS(wsEvent)))
+      socket.addEventListener('error', (error) => console.error('WebSocket Error:', error))
+      return socket
     } else {
       throw new Error('failed to connect to socket')
     }

From 40e774e05abfce6da3c558c09ce1750c132a580f Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Mon, 25 Nov 2019 12:25:01 -0500
Subject: [PATCH 041/483] =?UTF-8?q?restore=20muted=20users=20collapsing=20?=
 =?UTF-8?q?logic=20on=20other=20user=E2=80=99s=20profiles?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/conversation/conversation.js  | 3 ++-
 src/components/conversation/conversation.vue | 1 +
 src/components/status/status.js              | 5 +++--
 src/components/timeline/timeline.vue         | 2 ++
 4 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 72ee9c39..08283fff 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -43,7 +43,8 @@ const conversation = {
     'collapsable',
     'isPage',
     'pinnedStatusIdsObject',
-    'inProfile'
+    'inProfile',
+    'profileUserId'
   ],
   created () {
     if (this.isPage) {
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index 0f1de55f..2e48240a 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -27,6 +27,7 @@
       :highlight="getHighlight()"
       :replies="getReplies(status.id)"
       :in-profile="inProfile"
+      :profile-user-id="profileUserId"
       class="status-fadein panel-body"
       @goto="setHighlight"
       @toggleExpanded="toggleExpanded"
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 714ea6d2..c49e729c 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -33,7 +33,8 @@ const Status = {
     'noHeading',
     'inlineExpanded',
     'showPinned',
-    'inProfile'
+    'inProfile',
+    'profileUserId'
   ],
   data () {
     return {
@@ -115,7 +116,7 @@ const Status = {
 
       return hits
     },
-    muted () { return !this.unmuted && ((!this.inProfile && this.status.user.muted) || (!this.inConversation && this.status.thread_muted) || this.muteWordHits.length > 0) },
+    muted () { return !this.unmuted && ((!(this.inProfile && this.status.user.id === this.profileUserId) && this.status.user.muted) || (!this.inConversation && this.status.thread_muted) || this.muteWordHits.length > 0) },
     hideFilteredStatuses () {
       return this.mergedConfig.hideFilteredStatuses
     },
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index f1d3903a..93f6f570 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -37,6 +37,7 @@
             :collapsable="true"
             :pinned-status-ids-object="pinnedStatusIdsObject"
             :in-profile="inProfile"
+            :profile-user-id="userId"
           />
         </template>
         <template v-for="status in timeline.visibleStatuses">
@@ -47,6 +48,7 @@
             :status-id="status.id"
             :collapsable="true"
             :in-profile="inProfile"
+            :profile-user-id="userId"
           />
         </template>
       </div>

From 7ebf3602d5d9a8630ffbe239bfe4431655046821 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 26 Nov 2019 19:57:27 -0500
Subject: [PATCH 042/483] move mention button right next to mute button

---
 .../account_actions/account_actions.js         |  3 ---
 .../account_actions/account_actions.vue        | 18 ++++--------------
 src/components/user_card/user_card.js          |  3 +++
 src/components/user_card/user_card.vue         |  8 ++++++++
 4 files changed, 15 insertions(+), 17 deletions(-)

diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
index 204d506a..d2153680 100644
--- a/src/components/account_actions/account_actions.js
+++ b/src/components/account_actions/account_actions.js
@@ -25,9 +25,6 @@ const AccountActions = {
     },
     reportUser () {
       this.$store.dispatch('openUserReportingModal', this.user.id)
-    },
-    mentionUser () {
-      this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
     }
   }
 }
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index 046cba93..d3235be1 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -9,17 +9,7 @@
     >
       <div slot="popover">
         <div class="dropdown-menu">
-          <button
-            class="btn btn-default btn-block dropdown-item"
-            @click="mentionUser"
-          >
-            {{ $t('user_card.mention') }}
-          </button>
           <template v-if="user.following">
-            <div
-              role="separator"
-              class="dropdown-divider"
-            />
             <button
               v-if="user.showing_reblogs"
               class="btn btn-default dropdown-item"
@@ -34,11 +24,11 @@
             >
               {{ $t('user_card.show_repeats') }}
             </button>
+            <div
+              role="separator"
+              class="dropdown-divider"
+            />
           </template>
-          <div
-            role="separator"
-            class="dropdown-divider"
-          />
           <button
             v-if="user.statusnet_blocking"
             class="btn btn-default btn-block dropdown-item"
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index a9278200..2f649910 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -149,6 +149,9 @@ export default {
       }
       this.$store.dispatch('setMedia', [attachment])
       this.$store.dispatch('setCurrent', attachment)
+    },
+    mentionUser () {
+      this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
     }
   }
 }
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 96acf610..93d55fff 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -175,6 +175,14 @@
               {{ $t('user_card.mute') }}
             </button>
           </div>
+          <div>
+            <button
+              class="btn btn-default btn-block"
+              @click="mentionUser"
+            >
+              {{ $t('user_card.mention') }}
+            </button>
+          </div>
           <ModerationTools
             v-if="loggedIn.role === &quot;admin&quot;"
             :user="user"

From fa38a41e42dbbbd5aa9d1aa2efb107d99ce8cd7d Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Mon, 2 Dec 2019 12:20:24 +0000
Subject: [PATCH 043/483] fix "can't find property of undefined" errors in mrf
 transparency panel

---
 .../mrf_transparency_panel.js                 | 31 +++++++++++++------
 .../mrf_transparency_panel.vue                |  4 ++-
 2 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
index 20f8a08a..6a1baec8 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
@@ -1,16 +1,27 @@
 import { mapState } from 'vuex'
+import { get } from 'lodash'
 
 const MRFTransparencyPanel = {
-  computed: mapState({
-    federationPolicy: state => state.instance.federationPolicy,
-    mrfPolicies: state => state.instance.federationPolicy.mrf_policies,
-    acceptInstances: state => state.instance.federationPolicy.mrf_simple.accept,
-    rejectInstances: state => state.instance.federationPolicy.mrf_simple.reject,
-    quarantineInstances: state => state.instance.federationPolicy.quarantined_instances,
-    ftlRemovalInstances: state => state.instance.federationPolicy.mrf_simple.federated_timeline_removal,
-    mediaNsfwInstances: state => state.instance.federationPolicy.mrf_simple.media_nsfw,
-    mediaRemovalInstances: state => state.instance.federationPolicy.mrf_simple.media_removal
-  })
+  computed: {
+    ...mapState({
+      federationPolicy: state => get(state, 'instance.federationPolicy'),
+      mrfPolicies: state => get(state, 'instance.federationPolicy.mrf_policies', []),
+      quarantineInstances: state => get(state, 'instance.federationPolicy.quarantined_instances', []),
+      acceptInstances: state => get(state, 'instance.federationPolicy.mrf_simple.accept', []),
+      rejectInstances: state => get(state, 'instance.federationPolicy.mrf_simple.reject', []),
+      ftlRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.federated_timeline_removal', []),
+      mediaNsfwInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_nsfw', []),
+      mediaRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_removal', [])
+    }),
+    hasInstanceSpecificPolicies () {
+      return this.quarantineInstances.length ||
+        this.acceptInstances.length ||
+        this.rejectInstances.length ||
+        this.ftlRemovalInstances.length ||
+        this.mediaNsfwInstances.length ||
+        this.mediaRemovalInstances.length
+    }
+  }
 }
 
 export default MRFTransparencyPanel
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
index 2640d68c..d6495dc6 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
@@ -22,7 +22,9 @@
             />
           </ul>
 
-          <h2>{{ $t("about.mrf_policy_simple") }}</h2>
+          <h2 v-if="hasInstanceSpecificPolicies">
+            {{ $t("about.mrf_policy_simple") }}
+          </h2>
 
           <div v-if="acceptInstances.length">
             <h4>{{ $t("about.mrf_policy_simple_accept") }}</h4>

From 0082ed837ed0b4b9a047520460782562bad0d8aa Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sun, 1 Dec 2019 12:56:53 -0500
Subject: [PATCH 044/483] versioning the font resources through webpack

---
 index.html                                      |   2 --
 {static => src}/font/LICENSE.txt                |   0
 {static => src}/font/README.txt                 |   0
 {static => src}/font/config.json                |   0
 {static => src}/font/css/animation.css          |   0
 {static => src}/font/css/fontello-codes.css     |   0
 {static => src}/font/css/fontello-embedded.css  |   0
 {static => src}/font/css/fontello-ie7-codes.css |   0
 {static => src}/font/css/fontello-ie7.css       |   0
 {static => src}/font/css/fontello.css           |   0
 {static => src}/font/demo.html                  |   0
 {static => src}/font/font/fontello.eot          | Bin
 {static => src}/font/font/fontello.svg          |   0
 {static => src}/font/font/fontello.ttf          | Bin
 {static => src}/font/font/fontello.woff         | Bin
 {static => src}/font/font/fontello.woff2        | Bin
 src/main.js                                     |   3 +++
 17 files changed, 3 insertions(+), 2 deletions(-)
 rename {static => src}/font/LICENSE.txt (100%)
 rename {static => src}/font/README.txt (100%)
 rename {static => src}/font/config.json (100%)
 rename {static => src}/font/css/animation.css (100%)
 rename {static => src}/font/css/fontello-codes.css (100%)
 rename {static => src}/font/css/fontello-embedded.css (100%)
 rename {static => src}/font/css/fontello-ie7-codes.css (100%)
 rename {static => src}/font/css/fontello-ie7.css (100%)
 rename {static => src}/font/css/fontello.css (100%)
 rename {static => src}/font/demo.html (100%)
 rename {static => src}/font/font/fontello.eot (100%)
 rename {static => src}/font/font/fontello.svg (100%)
 rename {static => src}/font/font/fontello.ttf (100%)
 rename {static => src}/font/font/fontello.woff (100%)
 rename {static => src}/font/font/fontello.woff2 (100%)

diff --git a/index.html b/index.html
index fd4e795e..1ff944d9 100644
--- a/index.html
+++ b/index.html
@@ -6,8 +6,6 @@
     <title>Pleroma</title>
     <!--server-generated-meta-->
     <link rel="icon" type="image/png" href="/favicon.png">
-    <link rel="stylesheet" href="/static/font/css/fontello.css">
-    <link rel="stylesheet" href="/static/font/css/animation.css">
   </head>
   <body class="hidden">
     <noscript>To use Pleroma, please enable JavaScript.</noscript>
diff --git a/static/font/LICENSE.txt b/src/font/LICENSE.txt
similarity index 100%
rename from static/font/LICENSE.txt
rename to src/font/LICENSE.txt
diff --git a/static/font/README.txt b/src/font/README.txt
similarity index 100%
rename from static/font/README.txt
rename to src/font/README.txt
diff --git a/static/font/config.json b/src/font/config.json
similarity index 100%
rename from static/font/config.json
rename to src/font/config.json
diff --git a/static/font/css/animation.css b/src/font/css/animation.css
similarity index 100%
rename from static/font/css/animation.css
rename to src/font/css/animation.css
diff --git a/static/font/css/fontello-codes.css b/src/font/css/fontello-codes.css
similarity index 100%
rename from static/font/css/fontello-codes.css
rename to src/font/css/fontello-codes.css
diff --git a/static/font/css/fontello-embedded.css b/src/font/css/fontello-embedded.css
similarity index 100%
rename from static/font/css/fontello-embedded.css
rename to src/font/css/fontello-embedded.css
diff --git a/static/font/css/fontello-ie7-codes.css b/src/font/css/fontello-ie7-codes.css
similarity index 100%
rename from static/font/css/fontello-ie7-codes.css
rename to src/font/css/fontello-ie7-codes.css
diff --git a/static/font/css/fontello-ie7.css b/src/font/css/fontello-ie7.css
similarity index 100%
rename from static/font/css/fontello-ie7.css
rename to src/font/css/fontello-ie7.css
diff --git a/static/font/css/fontello.css b/src/font/css/fontello.css
similarity index 100%
rename from static/font/css/fontello.css
rename to src/font/css/fontello.css
diff --git a/static/font/demo.html b/src/font/demo.html
similarity index 100%
rename from static/font/demo.html
rename to src/font/demo.html
diff --git a/static/font/font/fontello.eot b/src/font/font/fontello.eot
similarity index 100%
rename from static/font/font/fontello.eot
rename to src/font/font/fontello.eot
diff --git a/static/font/font/fontello.svg b/src/font/font/fontello.svg
similarity index 100%
rename from static/font/font/fontello.svg
rename to src/font/font/fontello.svg
diff --git a/static/font/font/fontello.ttf b/src/font/font/fontello.ttf
similarity index 100%
rename from static/font/font/fontello.ttf
rename to src/font/font/fontello.ttf
diff --git a/static/font/font/fontello.woff b/src/font/font/fontello.woff
similarity index 100%
rename from static/font/font/fontello.woff
rename to src/font/font/fontello.woff
diff --git a/static/font/font/fontello.woff2 b/src/font/font/fontello.woff2
similarity index 100%
rename from static/font/font/fontello.woff2
rename to src/font/font/fontello.woff2
diff --git a/src/main.js b/src/main.js
index a9db1cff..6469ba5c 100644
--- a/src/main.js
+++ b/src/main.js
@@ -32,6 +32,9 @@ import VTooltip from 'v-tooltip'
 
 import afterStoreSetup from './boot/after_store.js'
 
+import './font/css/fontello.css'
+import './font/css/animation.css'
+
 const currentLocale = (window.navigator.language || 'en').split('-')[0]
 
 Vue.use(Vuex)

From afd4524c3920f8426051e0673b42f022cb3627fe Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 3 Dec 2019 10:32:46 -0500
Subject: [PATCH 045/483] use another approach for versioning font files

---
 build/webpack.base.conf.js                   |  11 +
 package.json                                 |   1 +
 src/font/LICENSE.txt                         |  39 --
 src/font/README.txt                          |  75 ----
 src/font/css/animation.css                   |  85 -----
 src/font/css/fontello-codes.css              |  48 ---
 src/font/css/fontello-embedded.css           | 101 -----
 src/font/css/fontello-ie7-codes.css          |  48 ---
 src/font/css/fontello-ie7.css                |  59 ---
 src/font/css/fontello.css                    | 104 ------
 src/font/demo.html                           | 374 -------------------
 src/font/font/fontello.eot                   | Bin 20152 -> 0 bytes
 src/font/font/fontello.svg                   | 104 ------
 src/font/font/fontello.ttf                   | Bin 19984 -> 0 bytes
 src/font/font/fontello.woff                  | Bin 12248 -> 0 bytes
 src/font/font/fontello.woff2                 | Bin 10392 -> 0 bytes
 src/main.js                                  |   4 +-
 src/font/config.json => static/fontello.json |   0
 yarn.lock                                    | 191 +++++++++-
 19 files changed, 198 insertions(+), 1046 deletions(-)
 delete mode 100755 src/font/LICENSE.txt
 delete mode 100755 src/font/README.txt
 delete mode 100755 src/font/css/animation.css
 delete mode 100755 src/font/css/fontello-codes.css
 delete mode 100755 src/font/css/fontello-embedded.css
 delete mode 100755 src/font/css/fontello-ie7-codes.css
 delete mode 100755 src/font/css/fontello-ie7.css
 delete mode 100755 src/font/css/fontello.css
 delete mode 100755 src/font/demo.html
 delete mode 100755 src/font/font/fontello.eot
 delete mode 100755 src/font/font/fontello.svg
 delete mode 100755 src/font/font/fontello.ttf
 delete mode 100755 src/font/font/fontello.woff
 delete mode 100755 src/font/font/fontello.woff2
 rename src/font/config.json => static/fontello.json (100%)

diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
index f8968966..9313ec20 100644
--- a/build/webpack.base.conf.js
+++ b/build/webpack.base.conf.js
@@ -3,6 +3,7 @@ var config = require('../config')
 var utils = require('./utils')
 var projectRoot = path.resolve(__dirname, '../')
 var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
+var FontelloPlugin = require("fontello-webpack-plugin")
 
 var env = process.env.NODE_ENV
 // check env & config/index.js to decide weither to enable CSS Sourcemaps for the
@@ -11,6 +12,8 @@ var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
 var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
 var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
 
+var now = Date.now()
+
 module.exports = {
   entry: {
     app: './src/main.js'
@@ -90,6 +93,14 @@ module.exports = {
     new ServiceWorkerWebpackPlugin({
       entry: path.join(__dirname, '..', 'src/sw.js'),
       filename: 'sw-pleroma.js'
+    }),
+    new FontelloPlugin({
+      config: require('../static/fontello.json'),
+      name: 'fontello',
+      output: {
+        css: '[name].' + now + '.css',  // [hash] is not supported. Use the current timestamp instead for versioning.
+        font: 'font/[name].' + now + '.[ext]'
+      }
     })
   ]
 }
diff --git a/package.json b/package.json
index f039d412..648ffbdb 100644
--- a/package.json
+++ b/package.json
@@ -72,6 +72,7 @@
     "eventsource-polyfill": "^0.9.6",
     "express": "^4.13.3",
     "file-loader": "^3.0.1",
+    "fontello-webpack-plugin": "https://github.com/sypl/fontello-webpack-plugin.git#35dac8cfd851bc1b3be19fd97e361516a1be6633",
     "function-bind": "^1.0.2",
     "html-webpack-plugin": "^3.0.0",
     "http-proxy-middleware": "^0.17.2",
diff --git a/src/font/LICENSE.txt b/src/font/LICENSE.txt
deleted file mode 100755
index 95966f00..00000000
--- a/src/font/LICENSE.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-Font license info
-
-
-## Font Awesome
-
-   Copyright (C) 2016 by Dave Gandy
-
-   Author:    Dave Gandy
-   License:   SIL ()
-   Homepage:  http://fortawesome.github.com/Font-Awesome/
-
-
-## Entypo
-
-   Copyright (C) 2012 by Daniel Bruce
-
-   Author:    Daniel Bruce
-   License:   SIL (http://scripts.sil.org/OFL)
-   Homepage:  http://www.entypo.com
-
-
-## Iconic
-
-   Copyright (C) 2012 by P.J. Onori
-
-   Author:    P.J. Onori
-   License:   SIL (http://scripts.sil.org/OFL)
-   Homepage:  http://somerandomdude.com/work/iconic/
-
-
-## Fontelico
-
-   Copyright (C) 2012 by Fontello project
-
-   Author:    Crowdsourced, for Fontello project
-   License:   SIL (http://scripts.sil.org/OFL)
-   Homepage:  http://fontello.com
-
-
diff --git a/src/font/README.txt b/src/font/README.txt
deleted file mode 100755
index beaab336..00000000
--- a/src/font/README.txt
+++ /dev/null
@@ -1,75 +0,0 @@
-This webfont is generated by http://fontello.com open source project.
-
-
-================================================================================
-Please, note, that you should obey original font licenses, used to make this
-webfont pack. Details available in LICENSE.txt file.
-
-- Usually, it's enough to publish content of LICENSE.txt file somewhere on your
-  site in "About" section.
-
-- If your project is open-source, usually, it will be ok to make LICENSE.txt
-  file publicly available in your repository.
-
-- Fonts, used in Fontello, don't require a clickable link on your site.
-  But any kind of additional authors crediting is welcome.
-================================================================================
-
-
-Comments on archive content
----------------------------
-
-- /font/* - fonts in different formats
-
-- /css/*  - different kinds of css, for all situations. Should be ok with 
-  twitter bootstrap. Also, you can skip <i> style and assign icon classes
-  directly to text elements, if you don't mind about IE7.
-
-- demo.html - demo file, to show your webfont content
-
-- LICENSE.txt - license info about source fonts, used to build your one.
-
-- config.json - keeps your settings. You can import it back into fontello
-  anytime, to continue your work
-
-
-Why so many CSS files ?
------------------------
-
-Because we like to fit all your needs :)
-
-- basic file, <your_font_name>.css - is usually enough, it contains @font-face
-  and character code definitions
-
-- *-ie7.css - if you need IE7 support, but still don't wish to put char codes
-  directly into html
-
-- *-codes.css and *-ie7-codes.css - if you like to use your own @font-face
-  rules, but still wish to benefit from css generation. That can be very
-  convenient for automated asset build systems. When you need to update font -
-  no need to manually edit files, just override old version with archive
-  content. See fontello source code for examples.
-
-- *-embedded.css - basic css file, but with embedded WOFF font, to avoid
-  CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain.
-  We strongly recommend to resolve this issue by `Access-Control-Allow-Origin`
-  server headers. But if you ok with dirty hack - this file is for you. Note,
-  that data url moved to separate @font-face to avoid problems with <IE9, when
-  string is too long.
-
-- animate.css - use it to get ideas about spinner rotation animation.
-
-
-Attention for server setup
---------------------------
-
-You MUST setup server to reply with proper `mime-types` for font files -
-otherwise some browsers will fail to show fonts.
-
-Usually, `apache` already has necessary settings, but `nginx` and other
-webservers should be tuned. Here is list of mime types for our file extensions:
-
-- `application/vnd.ms-fontobject` - eot
-- `application/x-font-woff` - woff
-- `application/x-font-ttf` - ttf
-- `image/svg+xml` - svg
diff --git a/src/font/css/animation.css b/src/font/css/animation.css
deleted file mode 100755
index ac5a9562..00000000
--- a/src/font/css/animation.css
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
-   Animation example, for spinners
-*/
-.animate-spin {
-  -moz-animation: spin 2s infinite linear;
-  -o-animation: spin 2s infinite linear;
-  -webkit-animation: spin 2s infinite linear;
-  animation: spin 2s infinite linear;
-  display: inline-block;
-}
-@-moz-keyframes spin {
-  0% {
-    -moz-transform: rotate(0deg);
-    -o-transform: rotate(0deg);
-    -webkit-transform: rotate(0deg);
-    transform: rotate(0deg);
-  }
-
-  100% {
-    -moz-transform: rotate(359deg);
-    -o-transform: rotate(359deg);
-    -webkit-transform: rotate(359deg);
-    transform: rotate(359deg);
-  }
-}
-@-webkit-keyframes spin {
-  0% {
-    -moz-transform: rotate(0deg);
-    -o-transform: rotate(0deg);
-    -webkit-transform: rotate(0deg);
-    transform: rotate(0deg);
-  }
-
-  100% {
-    -moz-transform: rotate(359deg);
-    -o-transform: rotate(359deg);
-    -webkit-transform: rotate(359deg);
-    transform: rotate(359deg);
-  }
-}
-@-o-keyframes spin {
-  0% {
-    -moz-transform: rotate(0deg);
-    -o-transform: rotate(0deg);
-    -webkit-transform: rotate(0deg);
-    transform: rotate(0deg);
-  }
-
-  100% {
-    -moz-transform: rotate(359deg);
-    -o-transform: rotate(359deg);
-    -webkit-transform: rotate(359deg);
-    transform: rotate(359deg);
-  }
-}
-@-ms-keyframes spin {
-  0% {
-    -moz-transform: rotate(0deg);
-    -o-transform: rotate(0deg);
-    -webkit-transform: rotate(0deg);
-    transform: rotate(0deg);
-  }
-
-  100% {
-    -moz-transform: rotate(359deg);
-    -o-transform: rotate(359deg);
-    -webkit-transform: rotate(359deg);
-    transform: rotate(359deg);
-  }
-}
-@keyframes spin {
-  0% {
-    -moz-transform: rotate(0deg);
-    -o-transform: rotate(0deg);
-    -webkit-transform: rotate(0deg);
-    transform: rotate(0deg);
-  }
-
-  100% {
-    -moz-transform: rotate(359deg);
-    -o-transform: rotate(359deg);
-    -webkit-transform: rotate(359deg);
-    transform: rotate(359deg);
-  }
-}
diff --git a/src/font/css/fontello-codes.css b/src/font/css/fontello-codes.css
deleted file mode 100755
index 87b4930e..00000000
--- a/src/font/css/fontello-codes.css
+++ /dev/null
@@ -1,48 +0,0 @@
-
-.icon-cancel:before { content: '\e800'; } /* '' */
-.icon-upload:before { content: '\e801'; } /* '' */
-.icon-star:before { content: '\e802'; } /* '' */
-.icon-star-empty:before { content: '\e803'; } /* '' */
-.icon-retweet:before { content: '\e804'; } /* '' */
-.icon-eye-off:before { content: '\e805'; } /* '' */
-.icon-search:before { content: '\e806'; } /* '' */
-.icon-cog:before { content: '\e807'; } /* '' */
-.icon-logout:before { content: '\e808'; } /* '' */
-.icon-down-open:before { content: '\e809'; } /* '' */
-.icon-attach:before { content: '\e80a'; } /* '' */
-.icon-picture:before { content: '\e80b'; } /* '' */
-.icon-video:before { content: '\e80c'; } /* '' */
-.icon-right-open:before { content: '\e80d'; } /* '' */
-.icon-left-open:before { content: '\e80e'; } /* '' */
-.icon-up-open:before { content: '\e80f'; } /* '' */
-.icon-bell-ringing-o:before { content: '\e810'; } /* '' */
-.icon-lock:before { content: '\e811'; } /* '' */
-.icon-globe:before { content: '\e812'; } /* '' */
-.icon-brush:before { content: '\e813'; } /* '' */
-.icon-attention:before { content: '\e814'; } /* '' */
-.icon-plus:before { content: '\e815'; } /* '' */
-.icon-adjust:before { content: '\e816'; } /* '' */
-.icon-edit:before { content: '\e817'; } /* '' */
-.icon-pencil:before { content: '\e818'; } /* '' */
-.icon-pin:before { content: '\e819'; } /* '' */
-.icon-wrench:before { content: '\e81a'; } /* '' */
-.icon-chart-bar:before { content: '\e81b'; } /* '' */
-.icon-zoom-in:before { content: '\e81c'; } /* '' */
-.icon-spin3:before { content: '\e832'; } /* '' */
-.icon-spin4:before { content: '\e834'; } /* '' */
-.icon-link-ext:before { content: '\f08e'; } /* '' */
-.icon-link-ext-alt:before { content: '\f08f'; } /* '' */
-.icon-menu:before { content: '\f0c9'; } /* '' */
-.icon-mail-alt:before { content: '\f0e0'; } /* '' */
-.icon-gauge:before { content: '\f0e4'; } /* '' */
-.icon-comment-empty:before { content: '\f0e5'; } /* '' */
-.icon-bell-alt:before { content: '\f0f3'; } /* '' */
-.icon-plus-squared:before { content: '\f0fe'; } /* '' */
-.icon-reply:before { content: '\f112'; } /* '' */
-.icon-smile:before { content: '\f118'; } /* '' */
-.icon-lock-open-alt:before { content: '\f13e'; } /* '' */
-.icon-ellipsis:before { content: '\f141'; } /* '' */
-.icon-play-circled:before { content: '\f144'; } /* '' */
-.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */
-.icon-binoculars:before { content: '\f1e5'; } /* '' */
-.icon-user-plus:before { content: '\f234'; } /* '' */
\ No newline at end of file
diff --git a/src/font/css/fontello-embedded.css b/src/font/css/fontello-embedded.css
deleted file mode 100755
index 861ef86e..00000000
--- a/src/font/css/fontello-embedded.css
+++ /dev/null
@@ -1,101 +0,0 @@
-@font-face {
-  font-family: 'fontello';
-  src: url('../font/fontello.eot?899037');
-  src: url('../font/fontello.eot?899037#iefix') format('embedded-opentype'),
-       url('../font/fontello.svg?899037#fontello') format('svg');
-  font-weight: normal;
-  font-style: normal;
-}
-@font-face {
-  font-family: 'fontello';
-  src: url('data:application/octet-stream;base64,d09GRgABAAAAAC/YAA8AAAAAThAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABWAAAADsAAABUIIslek9TLzIAAAGUAAAAQwAAAFY+L1N4Y21hcAAAAdgAAAGHAAAEdO/gU91jdnQgAAADYAAAABMAAAAgBv/+9GZwZ20AAAN0AAAFkAAAC3CKkZBZZ2FzcAAACQQAAAAIAAAACAAAABBnbHlmAAAJDAAAIi4AADVSSwKKUmhlYWQAACs8AAAAMgAAADYW8cayaGhlYQAAK3AAAAAgAAAAJAfJBAtobXR4AAArkAAAAGMAAADArL//3mxvY2EAACv0AAAAYgAAAGJHzDggbWF4cAAALFgAAAAgAAAAIAGFDaZuYW1lAAAseAAAAXcAAALNzJ0fIXBvc3QAAC3wAAABbAAAAg3WuIl7cHJlcAAAL1wAAAB6AAAAhuVBK7x4nGNgZGBg4GIwYLBjYHJx8wlh4MtJLMljkGJgYYAAkDwymzEnMz2RgQPGA8qxgGkOIGaDiAIAJjsFSAB4nGNgZJ7JOIGBlYGBqYppDwMDQw+EZnzAYMjIBBRlYGVmwAoC0lxTGBxeMHwyYY78X8gQxZzOMA8ozAiSAwD3dAwvAHic3dTJThtBFIXh38aQCTI4IXGAQBgyMcRR5DUSkh8jirLgefBz8RYssHSWVbA3OdX3LhOFdbr1We1Wy92t+5eBZWDJjm0A/W/0fETv0Gd73fklHnfnByz8/YD3PhpoS981KbNyWa7KvNyWRR3WUT2tZ3Vaz+v8ZnJ3B6K76qK76vpvV/1z6/muP7r9Z7f/+sPerur7WQd+oxUe8JBHfu4nrLLGU57xnBcMeckr1nnNG0a8ZYNNtnjHNjt+q1322PedPvCRT3zmC4cc+f1P+MrYP79yjyf937fV9tGf5rdxm3BorSh5Dii1tpRaX0qtOyXPCyVPDiXPECVPE6XWo5InjFJ7OiVPHSXPHyWXgJKbQMl1oOROUHIxKLkdlFwRSu4JJZeFkhtDybWh5O5QcoEouUWUXKXXTnCfaBJcKuUiuFnKLLheymVwx5Sr4KIp18FtU+bBlVNug3unLILLpw6D1wB1FLwaqKfB64J6FrxCqNPQ/kPqefCqoc6D1w83k8D4NzQIsWEAeJxjYEADEhDInP4/CYQBEw4D9wB4nK1WaXfTRhQdeUmchCwlCy1qYcTEabBGJmzBgAlBsmMgXZytlaCLFDvpvvGJ3+Bf82Tac+g3flrvGy8kkLTncJqTo3fnzdXM22USWpLYC+uRlJsvxdTWJo3sPAnphk3LUXwoO3shZYrJ3wVREK2W2rcdh0REIlC1rrBEEPseWZpkfOhRRsu2pFdNyi096S5b40G9Vd9+GjrKsTuhpGYzdGg9siVVGFWiSKY9UtKmZaj6K0krvL/CzFfNUMKITiJpvBnG0EjeG2e0ymg1tuMoimyy3ChSJJrhQRR5lNUS5+SKCQzKB82Q8sqnEeXD/Iis2KOcVrBLttP8vi95p3c5P7Ffb1G25EAfyI7s4Ox0JV+EW1th3LST7ShUEXbXd0Js2exU/2aP8ppGA7crMr3QjGCpfIUQKz+hzP4hWS2cT/mSR6NaspETQetlTuxLPoHW44gpcc0YWdDd0QkR1P2SMwz2mD4e/PHeKZYLEwJ4HMt6RyWcCBMpYXM0SdowcmAlZYsqqfWumDjldVrEW8J+7drRl85o41B3YjxbDx1bOVHJ8WhSp5lMndpJzaMpDaKUdCZ4zK8DKD+iSV5tYzWJlUfTOGbGhEQiAi3cS1NBLDuxpCkEzaMZvbkbprl2LVqkyQP13KP39OZWuLnTU9oO9LNGf1anYjrYC9PpaeQv8Wna5SJF6frpGX5M4kHWAjKRLTbDlIMHb/0O0svXlhyF1wbY7u3zK6h91kTwpAH7G9AeT9UpCUyFmFWIVkBirWtZlsnVrBapyNR3Q5pWvqzTBIpyHBfHvoxx/V8zM5aYEr7fidOzIy49c+1LCNMcfJt1PZrXqcVyAXFmeU6nWZbv6zTH8gOd5lme1+kIS1unoyw/1GmB5Uc6HWN5QQuadN/BkIsw5AIOkDCEpQNDWF6CISwVDGG5CENYFmEIyyUYwvJjGMJyGYawvKxl1dRTSePamVgGbEJgYo4eucxF5WoquVRCu2hUakOeEm6VVBTPqn9loF488oY5sBZIl8iaXzHOlY9G5fjWFS1vGjtXwLHqbx+O9jnxUtaLhT8F/9XWVCW9Ys3Dk6vwG4aebCeqNql4dE2Xz1U9uv5fVFRYC/QbSIVYKMqybHBnIoSPOp2GaqCVQ8xszDy063XLmp/D/TcxQhZQ/fg3FBoL3INOWUlZ7eCs1dfbstw7g3I4EyxJMTfz+lb4IiOz0n6RWcqej3wecAWMSmXYagOtFbzZJzEPmd4kzwRxW1E2SNrYzgSJDRzzgHnznQQmYeqqDeRO4YYN+AVhbsF5J1yieqMsh+5F7PMopPxbp+JE9qhojMCz2Rthr+9Cym9xDCQ0+aV+DFQVoakYNRXQNFJuqAZfxtm6bULGDvQjKnbDsqziw8cW95WSbRmEfKSI1aOjn9Zeok6q3H5mFJfvnb4FwSA1MX9733RxkMq7WskyR20DU7calVPXmkPjVYfq5lH1vePsEzlrmm66Jx56X9Oq28HFXCyw9m0O0lImF9T1YYUNosvFpVDqZTRJ77gHGBYY0O9Qio3/q/rYfJ4rVYXRcSTfTtS30edgDPwP2H9H9QPQ92Pocg0uz/eaE59u9OFsma6iF+un6Dcwa625WboG3NB0A+IhR62OuMoNfKcGcXqkuRzpIeBj3RXiAcAmgMXgE921jOZTAKP5jDk+wOfMYdBkDoMt5jDYZs4awA5zGOwyh8Eecxh8wZx1gC+ZwyBkDoOIOQyeMCcAeMocBl8xh8HXzGHwDXPuA3zLHAYxcxgkzGGwr+nWMMwtXtBdoLZBVaADU09Y3MPiUFNlyP6OF4b9vUHM/sEgpv6o6faQ+hMvDPVng5j6i0FM/VXTnSH1N14Y6u8GMfUPg5j6TL8Yy2UGv4x8lwoHlF1sPufvifcP28VAuQABAAH//wAPeJzFewtwVNeZ5vnPOffZt9+3b7ek7larW90tWkKIfoIQonkKgwABMpYwKDIGHCODbI8TOzbKZALjwhsHPIzL601qnLDjuGrXccYDicNuHk5l7CSLd6ucyYSkMjtbm8emcLLrSWU9mR0Gmv3/2y0hYnuyO1VbK6R777nncc/5z//4/v8/MGDsxm/4X/FPsm6WrHek2wKaZBxGBHDGZwGrD9tx25ZKrDdr+0FNLwONLrnyGsjTpVrshBpdHKyOOvyv/KOBvsDzz+NlNED3wM2y3//88/6HHXr43Of8727o76cGTOKcLopzosJ0FmRLWJ1tqq+v4HcNxnFWI8xQjVkdVE2dZZrQZrEDl+MKCJwuF2yaSckn8BUfXT2UKWXSxezKWMhUEr3Zcs7Hk1Ctzd8jtprpSufylXI1WkrCKihWa6WiI9RewCotQ1V4aa7S4ZfspM1j7bFP2qkQd+KxTSnn2hvRJKSct61q5ky66n3bSb1ixM7Y/jN+G85Ew8GrZtK8Gur2OTyUCsl2a/7hiQtOKuXgBTp7ejqTsMu5ij0c39U+7GJeDTL8ob35LtJhhHWyRL095DelUGhz2MLeJOyoUKK9gLQPR2wfuLuTq5Rr4Txds+7OKI445780YEWsf7xqORYMvOHrhNhHPSlrDmIp+JXlf73xluUJgHbypBYypQ7R1/1WROlpRKONHvziwjwM3I18vTveZvu8hq6pigDr1gllu6NOKCAUuxdqywA5QqtFw83ZZdLvMzv+2L/95ZF7//uLS773vQbOM2q+9zyXvJD+/vfTL/xydhbON6ccf58J4w/N+YYc4CdYF1vP1tXXpEGqxNY4BQ3UowaoUlPljI58rgHXponr5DiyDptQAAuj6+pOVzbW5UR6wi7v2GoeWWUZ9EMpmEn3Q4spiE0iXfQ0Lx+5cnUVVLqaT7WuotMJSYgEka/4JVO//paicpQumMH91i/g4s4bjm8G1hrKpIRD+nkr5bmg45vG1+mNqfOYdDvM+JyQZgEXEizY4XSYly3rshm34bJ6n/ITr3nZ671sdjiXtRnFa2Izheuicd5BWiBBblwSV/hF3L92Nsw2sDvYHfXxcgdncreKIrVrPQc+tm5JHoVKBTnCFKnMIglRnOAoAxV/Z5gq8HeGCXFsEakYUWp0a7i/rdtOaEp7b7bWD7VyTdUcKOe0tBqxnWIVxauEkmVHVI4kyqTd3e8n/VEbhlIxWsNqpJKjOWEkZ9iJ2rhJPshgbS2XryVRr0C1d2AFpD92+xQcCXg2HQw4gQ0DnsClVb9YFVdMbYPRNvZ40ePZc+1fFoudiil8nm4PGJGJ2/5EXvU4+fH/fHzJw9/ZuHZ/pnIg5bl/R+bI6vWDa08+Bfcg2x/c6AkEPAMbAh+ScG9j771FI6+aWqH7kW3BQujEs2bVUFVbBaVxffvHOiDWNhUOdy+dPrLFPHnvwfqa7gPVMPLbjRs3HkAZsVFndbHxutmJ4uBDlcRHtr7cNTZRd4hqIFE7ARMcxDTqMi+/raOeQJ3F779ZKwSMMwAxwQSI0ckvRzN2OKQobb1Q7gfVdoYBbGIzpGM/H5ZJjtRCvXvi7Jtn8ReSfYP2qwcfGzv7wTofuu/086fvG4KNr0bgiXvO8mcuPas+2fhUohB5dePwkaf+9eljg3Ld4We2PXbw1UhLZi6KvSKEazjCNtbXHZocXSuZXGVyYOWejoDEGTWZA7mEyVlSUbM4Y5jFJQnkGH543527d962ubeQToVDmuLgpHNpHyAPZFGh4uZrTtSxcW/ztALcZdS0qBHyuTxqBry6HFFzpYyUMspYLTfPJp1YwH+opIlXkGmK0dZgmitifNWuR3bxPQ/tgbiufdD0hHtUxT/m1bRtbe2GJgPHdSvQEd2hBtRNjlT0HtOvH9Z0MJUP6r5ottlW3xZrN3QRPI6S5o9Hdyh+bbMtpdFsbMKhVePjHx4ff4TqA8lIR1H1qZExUIa8+mg8YGr3GNaQotaTik+1iv54hx8szW3b1p5aqlmaPbaoqWeVoqyPt5q2B1CVunvAmJjhl1ge7R7qLRtVBSpYlStcVY4yRXAFpVAKJiQ7ShKqAp+mghhH2SSJFGzUybRlC9m8pnSg3nL8gCRCM1YJll3qRaLuq3wmrWpB24mWikkONmrFdG41ZOiCequE5Hei4MAh1A2g6+fXT02tP6+bAM1itgzV7i+qHPWH6ml81xN3rvoctF9O3APLPBUeUnwGF1Pr4dT6KVP3GCoSF5mh8Qh2lFyHPp+n8aZp+885vsuoCc+hYTTwxbzde1u8wgeYzdrqjhdQHEaQCgz5jPBINEQ2DzV4Og9NCBI1WspYvNg4gBa3ccDj2Y936IEeT9za54FnGnd7PPAnnqS5z+Np/BBfe/Z54vitG40bj4mL4h62nHXW4/RtV/zYBPI8sFFghSVsOSwnixZN51G5QTVKCkkjCuaqWMRHNerU6BF5FQtJ3nr7zuENm+Ue+NXYVN8mq32skeuZTiXVPhiNldsbX+yLWVbMgR8VU0PVaiO0Th58fAv8iqoCu/5w86ZvTGHHdmtT3zR1NFOxgwXY3l6OYcd2nUvq+OGAr9gIjT5+QNbh7Vg/dST6SdRFF+UyV479iB2WsdvruxI2Kh0/rsnntQzJWTyCAEmS5Sb0dJShVRSgClJPqPUJRCmKq+SVCZL40WCgvzeXaYsGOoOd4XBId1GHj0xcEiDSValFIdvVZCi0edV8sJyLBlGbo82sBZt2EA4N7x3GXz507e3zeyEByWsnUKYsVcyhiJg7y9lrJ7qrUM6KuWyZx5YO83V71snBxtWrMxcmIXEOjedeaqjzF3QzdH2vy4L8BboxD+lfd83NFa9hO9gHUDn9PjvDPsP+jL1Wb3uqzg391MemU1KRj6xApTs2gCqWyZaCrrFIyOK6EdGnw2AEQCqGnA56OconJ2s67QdhIv0QYHo05EZ7gtm210YNPvx/19O2YXxhBLBHJ+u5L7z4wp8+9+lnnj795OMnP3r8w793bObwwak794xv31qpVHL4r1JyEINEK2hTUWoTYDuEVVFF5lB/umXErW4536pHqa4CbgLiWxU3winhpsBv9Z8va5FmWWB7rdU+iu2jrfGpnsavtcancrRVXty/Fmzi6fkNv2z7N5NSwAu85yNf5fgau91X8JLfvv76zSoRdHwjLiTG6/dvafaDRTXvd918CzN13/zsz25O4+eL+jTugiRVNH6CV/5HI36sD4zg8/VP3OwLX4GEW9H4KfX5D+891M9udr7neihbLmf52y6Pkl77Nn9IbEW9Fq3bhqvX2Lxai4c42kujBSFrxrxqQ63Gj6BCi3v2owbrafywpdqeM+H+xl2muR9rYAnpOWpADed16Lf5s/Pfglu/FY263+KOC1lJi9ZaCpSfbvwAljRHJS2Kn0ma+03+540fNn7gPprwGffz7jToO2hxXuHbmrpagVvdgajt6uosWfeFpbVWJV7ch+oYx/1ha23P0Uqe8zy4D7+xBL9mUj1OwGwtivzCD4lviElWYGW2mm1nX64Hq2UuWcLH0aiMjKJx2rT15XaU5iUKIhQUbjiKkL8FYDX8RcDPmLWZaRq5iC4C2/pyN3bIvV8HpjGu8X1uP4bSjiAZZzKLzUHOUlskadMV5Vwbbw2s8dHJyXoU2JbNG9YPrhxY1tHmREIBVoCCQb4RAeMoYdwIQV2SHjsJxWGooFRVcoSP8ZJH4JxTcOxIqYzmJaslQStV8zky4SjQRfQ9yKqXq/BrJ6WMrCgNDha2FRL/kFu7fW3uHxKFbb2DK8u1EdnZ+NKqZGLEiS3nKweK/TACiWSjJpVlXaTjewYVMdnpmJYn331v9Utzd65NFPLrcrl1+UJi7Z1zX6re2533+HSnc2l184adG6orlpbLS49tGNs4cj2myK5liq4M9khlHrvwE6iDNdTBhXoewQpDtMD4USQ1ug0ArkWBCYS6MJoJZ6vhgIpOQ7gLF+qDqNIyGGg80B/HhRUdLYLrvAAJRF0Ab6ac62+5/nbw6f/4DA/h4+fvWzXOx1afa3zdwfcRWIce9X2Hn3768H1JJm5cRzw7ifOx4Gvw9/zhrS8bYxNrh9jX2FfYRTQLz7BTTEW8xNBI4Czx6Ufse4iqJtlOtg4dpRJLsTZmEgfAc/AsPANPwifgUfgQHIK7UZ3/mP1X5AkVHcjdsA16sL/OVHgH/hq+C2/AN+DrsAJK+A7oPRtBTjPx++tbXz+FbERk+xp5BPj0/34OGhvBNQN+C9imjv9/hJicdHeiXkHXRxNcO8o0VWgkcbpQ9Rmmg9BhBjXWMdSRCGrH8cbEhCI5wt7RJhnrgxLQtiriEEqbwklOVaU5htIcQ7k5hqI0x1D24NqVLR3/zC9PTq5tcxHiD+Ey/Dv4MtwBe9i32evsS+yL7M/ZF9hH2IeRRiojVQH4Z+LnUNqLSYJK5K4BQXGSc/RyqtEcOThrQM1VbK2cUyv9kvQjRUnsAthpNa2htGcQVZb6OUJPfI0qWkUtQJ4U+T5qGh9IU+Q0+ivmtGHI0KB5h1wnlJ+SU84X3QZqlBrjB/I4LI6az1EZtQ5iWfyU6mjoczlk3tERq5WjeVUr0lDRWhQ7a46GM8Cuqpbkds3RXOdLy+dUp0TjdOKEamqnQHdUpfEq2Aoxcb6fV8hzQ0xcwnkXk7JTOEUcFTvX0m5gBMF0tYKj4IVWn6tGi1VcLi7LViOZKhlBfK+lNZ/I4RSonKd5IeAo4zqcKo6EE3ZqSY7UqdYc1ArDgD5lpZ8ifS41itgijbNBN9Kha82p5oYhUqtmaI5E4GIFCSLQ20QTVUX/k379gCuLIL36cdf8kKvmiO5VNYJqGx0B1wtA7Ry1VQdeeuhbDz74rSvfOaY++lUIcx0dfymCkTDCW66rArdMSlNRJeioEIWQ+KOCiqBRkSq2BN0CJS4FR98KP8Y1A5ugScOOJpeKVwjbF5Y6+nrAFYND2FAlV1RT6BKZX6gGjoaoUxHoHkrwaR6/DAgcVeqg0w0HFgj3Q4qwLPw8t9o6hKooYUV4pNeDH1KlLg25syjJzRQQM3EOiqR5kv8J3NS0kNQMiR/kPixzH7oP3K8LHFqgLUSTjSMolsaFLgzNUVVF1wPSxnFwcOETEh1tPWhy/AGFY4kLS6AfSKRCQfTgd7huC3QwyZSjZANFSUDGhIFGFoSX+4gcEmtUnAPSSUpNVzRLYgGdYMWdiCV5CLtzcj65qSOpVFVTDMu89/fGwAIv9o+Q2iBCKxbKPP4AzdzEHeJIamyEE5EeP3DDBBF66LVfvPaQe2n8DeicwmO6UDzYDIdAX0Rz6QpctRQV6YomTrgv8JnrRFbAleNea0LXTE0qqmIRa+DSLAOJouASRJALn07vhYHbKlTwSROHVHBZptQ0DQxF13QkkiBaIjuYQvioWpHoRpi6nwtSZj4kgFTxH05i6Q5Juy5Vv4lzQP/NZ9geDmo7RysrVfRmhQggjaWu6BI8Ma9i4aqlpfukD0yPjb66giTHvQgJU0qDYpamS2Ae0EPEvzgPE/ELbSXSO6D4SRdzDy4aizLmM3yKQSFXJDUSHcVE4X7kEaBQpkDvUXIdCenjpqlQTNNjKMQauAe4ZokCgSRQAZeHHWnf8dLwRm6nNVMQkeQASc1NgS4WQiSBrha1IX6icZS4HjR8hsVlQHPjWp8TZ0Q3auQoS9c7EQ7zoELYBKEq8NkFXNzhpF24aqv5NKqJPCEQRKoahaMQtcKbX3hs54YNu2Di0Ql4LtXV+Ka9awUMp6a+e/xl6Mn/i12rJybg71JTqcY3a+M2VqDtuPF3iEH+J2JWP+tCO3qwbnXgfnPDxUUjTeyZYEg7ZMqjZILJTXSBqNyHdstSEHKmGRJ3luKEszdb4DZToEJOUFN0Io3oijxaC8JT2UV+Iflp+XA5Ty8QSUabnpwAh+JmhLVyNVSXRXTFTO0+zXQvKKqa9igaUsPS7tEtHT5vR4x06NoLobQRseElI51L7z2im6aOF7B+gCBYqqhBbqDJVXng2tuZTDCELlAmI0JB2275I0iMEGKxDFtSzzHkTZfs9yu4GiYoeyPcmItgo5lSNlNyF0JZmHymlYqpVTLNVI0bm6JAVpTAoQilnCspZwaB3xUXG15JRmfwgQpfp7dvucjwrdZbSrtcSTHuYsMPuFjVZr31nnnCSpJ/GJek9CaIO0YZCwUtD7bTgooS6c0G05Q8WrDoaNzhpd1PjvHxUxzmTcA7//6jFT6968nnn9wFAx9qaZCHXnNj8bjcn+F3VeSIlYitNoNWj9SHVwVQUDQmy5TEGlmLkrKpiXMGmIZKVpNHGQFodggnKpmQ0zoWVAXUDzDSBOPIKzRfhRM+8pAH1Gqvwf9Bh9g/60P15Yu7oPt09Hf2mUS3yGFsw/rVQ8v7l+SSHU4YKaHaBlG2lke4HyHDqxJ+CbfSc5VmOAF3DyvybkRDa0UoVrt4wBFRyFRAy7cSkfCr+u31CkQM4zUjhH/dU+sbAxS/hDczSUNoHbrptRoDbvwI3syWlW49VjvXeOIcny2dKwX6ArcHXlt7+9rOKpydH6Lx9SPNAdZNoXoOq3HUruVsa4xNGo6gw5nPNp74LPSXz5X9/tsDfa085jaB6yPvmHWzx+u4CK4kHJ+G5qpNEKpERnNjUCHcgvwtgi4owA7sbrJQEnbhDeQdROitHShB72rJZt/dcLIeYqwrFYsG/IbuElpDQpdahC5n0hqgJ1oqok+ed2lo+6FFxTeKJ0qb4QOWIhvflV5EFctE8kpj4IrYZu+/st9e5ZywSydKQyNo+mTjLyVeoV8+cKWx7C34VCKy/619kcgJx9W9D7g+oYf1s7tf0UjvQSvk1sE0XUU1rLFpg4yRaxymEXd7BWq9bkoq3H9rExJNuqNsEjBBfqr7lvaFS0E7XYpE3EQdBbWaqq9YVUh9CDf6nRekPcrVUjBNuLCLvOd8ENW7ZQZM/IWHHd/fJsBUwc97f+y3YaKWKPD+DjiUKBQStQl4+CopPLp8zedA49doIv3gt/0bj0GyMFiAvhV90PjJMRJvXPOc+Kwbf7Vx1aX6QA5tYE8+hAacdlSMoIzgniFQmHVjJa1EChyOZ5bG3GTPfIo6uxA0SUK2mTihrGKR0D+C0yjBWwq0NRPTcLhxIjLorIpE4LgzDv/K2/HxHR88e/aDqU1thvGn9/HC1i6/uZCM/k3jhG2vxp2E47Xxv3GyW6fg7JtPcTughrSpuSHettR24/GUP7woH8b1CNSCXhZkEXa4fjdTTGXWD6YwZxEN6WLWh9hVn/W4gXoDgRHy26wXTYNUd+FNlXsYgpMtoZDPpyNKQLUaCUXssC/oCwb8ulf3Wh7TkJpEi08sHQwgp0IwE3T/Il3BkvuUpdKhM3DoNL/0j0+M8DfOuKXGT5BAyeuv8aHrc5vF3mtvwzuN3fDS5etz/ISrchfycxnWxzayc/X2zhjijnAI1aPwInpg60GDIhia0uLOAZyuQDxGWRYNOfAounOqNNVphaBp0wprzNA1Y5rpOH9k2dq7uuiGfv979TMMN1ZkTDBDM5CLA6uHKiWbsuQ5O5PxEB/P2778Ytu3Chbbvug/mRJEiz5nqd2aBXNumH3O1LvxAQub55OFhaH3TBZ+utmJYu3NTnPE+3Nm2+9MIpJdfUD8EvFOno2xl+u+bge9Hz66rkyeQouyOebCQnEUbROCOzlD5zom1GZkrplxULzKfGQu867WkjIS+252ojTqkt9upTYTGfxdmQwKzC3pATaycfmynrElY3bIMlke8robmCOPTrOdTiCXkPKNRGZNpZTjMFBeEh3BfA7SETd7Sbls1JfkLfqA3Nk1gJKJ+pS6lvE1vH3igaPrN+IM5HhYqZR233H3jtPlQYNbf++xTTnIQ8baDXv3Qcmt3HP32OaNlVU69/yvVq1Z37B3/+GPP3BsnTuGmKwPzxz7Ax1dwNCB3TuXLR9esdIIi6IwnMBPdY86tCnX05DNqlTy3XXU++O6zl04RjnrA+IXuFedbC27rU5OB+rm5QAbmpQP3zwDAMfEfADULyhENNtCznB4su4FtDF2mHVCp5wn4nKkAIUIog65yEmgvG6VHHginktk1WnWI6WWYynvxjGq1CgHv75j5/iGPfcduefIjnVdXWrW1x4oBYXJM5DNPTV1Z0OJ+cmB7ubduc13PvbwR+buosYz2DilZHXVFxKTieTKjRE7mdqxbs/uCzuXdAQgKPzq3r+Y3P9ULtt4OyBV3S1tvrM7HWvbuahtpMsXYgu52CsuL69hc/VwDwLdIJqdWj86Yl3ob8gWgO9GVY6e0M38LFJIuqaMrME+pqqWipTrY+jzz/5TbRflcCfr5mBHtlLNliiNC7diIQftm/pbQMhVC+GgewpowWrk0VqUugglL2CgJwjlNx5eQD8o4Lp52Yl7DjSeVgKyjj7ufQc8jg8SaAV3n1+APW67BcxzHjUC/IgSIdzCjqpaV3xux7jjLJwloNhrjPWwYn1ZCB1y5qYGmhyF+p+/x3mHSs1uK7mnHYLlXB4X2IlrobAXQpSmYIVbQeJ5SvBLtr/xTiwcGmtc9nhWUg6hsNP0q3rkzNT662/R9Hl0/RTsBgvXszRAzZLmSpx9YcwjTKhcv4KLm17LY3RjTfuNl738jVYusVYv94BUdNb0GBV0XhRJ+0UxiulFKWMy46OUrsuVXDcs0gpnV5ozFZHmGSL3uFmrnFlcnrQD1/7WTSSJoJtDet/SzKJ8EwQWMlRgg4/yTj43BTUv4xfFW/wS4s8VbGm9QGfoBO5D88BG0/m9Zf6otroHS5LObizkb0lOkxwdRxJi/IfvVT/4OJkm0okUVewnd9lltSvl7LVL3VVo67w0mspt6ODxdT2dH/hyKlZd8pflipVOermVDCa9afWPp0OZIejvFVVs/p8aG5s8+ZUO54larD0O7fHohuPOq31jibOZvBFCc2SG9Lg4tM4X3dXdO1jG9Wkun13B9dFZQbLtFfSpxtDPvmspcLVqcKF1kx0QIzqWNdWN+QpVEzMG0IkmNiPBPeI0gwhV5R4y0/gdQI8U7xMMneHRbaMb160eWrmiXBpY1lvI58J2OGQpMQJpaTXJq2H3kEuSYqVVNMjueQuVzhdWc9n5Klhos/Bga7T9lOwZ5sUkv1wY7OM9w7mj+Cf7qwO/XCkC7XGxoquqe/omtniNdthTGOrhfSv6G8+22vymdf+zHaZ97de2uWPs5Fe/89WTY5eTBbcpOK2HX33f165age+Lvq7e0Vh/b1c9O9/kD1oPO1r3//JHx4PBx84evnhq+/ZTF1uy/IBL4yhbzQ7UrTIZkJyHomgtix5FIwvzx5tuQfHJJoq/WU0Qni0415P1ILBVg93pREcowKIQVV0DggQkG4xWAgEuyf0w73ddbjS4ZDtcNJSr5tz4+TBfQwGa8jCk0Bd/58FvPQRjtw34ve23b4ylcmks80e+CR97/Oen8oVjf9zRLXQfRwUuLOm1NTug+ScOwuM/h8DPH+cntp8cHX5wSbxS6u8eighl+8lnT25v/OSu56flXTldWgZwdPz8is/R4/FwoXh2HKumn5+n0WtiL9rIMnuu7mn3cuIqzuZJlEcuEyoCFKXFd9hJZXyGjMNmFEoK5EivnEc93e/XnJPbsGBSsk2TQmewqCE5gq5tlpKPtwblkiCP2ZkOZTJ0KKujVykTpCynKa1fzNpqL8HHHEHMyrCsobIilgQnYmtJiNpJAdeNrgEIDfYY8O1C4ur4h8evJgrx/oHuED95r5LqSylHPg5OemBgUh/oMowlg/BvEoVV4+OrColYcXzq1LbxswHTgxokHfGYgbPj2x+f3lVebCMyrMTW1ofppE8nEE50cTIKLa2eQqiUhUVIjfqZAk0E5tQJBNXqaKWSKTmZ7oyuxHubxxkWzihk5g8uzJ9OoOTqe1mPC01Fet5VqeebhQs+5/Qt5mMtqdcLbpMLTV17gRTtBRvW/LYBgaZOEhXWS7Eu8v1g3uMDCntxmHf72OFMNLw6SgaPvDvePB+LiqNS7ldco7+QnadUaCqK2nUETKloQROtj51eMbxnT23OThmNn3o8kPDEY3wOTu9NXtn/GRkKSNNCVCtynSv21geSIfWMz/FAktL3SdP2n/nxVqYsxMHCLM6yrNo8LZuwm1bCQNYzXM+F3O2WtdPoeJCm664Ho08wXdNH19ajUQf1Ylube9IaNSAhPWQihH5I+mHXfcXFDXMHnXJ0wamq3M8pliaGRW1RNI1/JHL/6Qdt6Yu3S9/o1Ba/jLcHGj9vaUdItXTgJwenHj99at8QXzF98uzHD1RvWxR7A1g/zsc2+toNK9C3YkWf32e2/7eWeou37i/ednJ6BfU9OTUky4ePb1scpmvGK/kJlOcMO9yUyBQqMXQg2FFOAQmFNfOE6KoQdBFuoDb73k0EtiFkRw6IQAekHgaW6uyI2eGAz1BZBjIaaT10NH47AZ5EPdg8IkmmJhrh29wQ5+IsuO1Hh95Nj38+GV2cBr9wz9P8qXuJSUkUL8zjmhC808I16XqnQh4pmn+6IwCQhNEYChX+uBAm3PI/m2HXoqMs+KO3lptHBovO5WaUtRmOTTRvzVedbgEv82HZxS3f45mxVozWzd+zdparZxYcD3CVG50j4DAKrC2mqcwCy3U+Fidbq+V8NqdGgnaU8OMtObqxUKjxZrA7ZIT0W7I8ZbNgnvdE7ManbI9U3FjORTHnxnJi7Ha2vb51C+haZwclyZFky4M4DzmC4qDNMl3osyoshONI9d6/yOApbsRqWV+ke02uEg4ZKCe1MuUWEQy4eikzv/NIWFtzIo7muJlXqiFxyrv1wxwRGF6GRamYlGoUV0uaAzudcRKonxL2XjvwWVdTfdbv8Kc7DWgzDMNRUt1bNmf3FAsbw1hpdwzGcyHTpypSDUT8bYWYraucW7pFiaNP99bpfLo7HvQ1Pu2OBodcnDmQaQsV0p2ZzshwvhdCPn9svq6eWR4y03bMiaUdK9QeS4W8kT7HlpZPrbfiSA+4/lSA2ahrlrGV7K/r0dISrunoTfFExGuhay9GJCgU/SaruVS1hEeCxjWgUySKxpUZHEhTGOI30j4m6Lq1GTEc87J529n3/p2o4bFFPTWU2uLvaI4Nsf04tdf2YF9d24IGNRUMMlarFJcv7e3Jd6c7kx1tQTuIyBBX56953eCVTf5ZS6LDCI7daNb8C/orFaPZSKYV0lEWnuAJx9c6ZfYJ6YXPnHYPylERf/+HVzaGXzf1c7oJDzfv/IXGONY0Xm3uUwKuWI1H4FTDah5U88Fa/Pu89dJcM45D11ac8qJ8RISRt/vYDvZw/aG+LDe1VKdPCF4Mc6mLEQYaWivN1GZ9wEyvybxHmcfLvR5+FPE483pM77QKHC2EzsU006XUx5muywmD0qoonNu2btm8ccPaNdXS8mVLerrT8Y5oJBQwDVSTOuh+F+6hoUhyVSmRkbNv/ucb9zTyQsaHxCLqnnSMNF3h8rASLTbtSxUJifY+Ak9MfpQ/+soj6kn4i9fc+NVrljqjm6+7ATAk1gw+NA4VEmdyKxux9bukFUrmBrs8nr7xg+N9Hs9tA3OJAhz66Msf48e/+Oht7+7bHLTxaqIP/jC+fX1yxbrqinQ7N9P4Y1YLCfa/AQUT96AAAHicY2BkYGAAYu14F754fpuvDNzML4AiDDc3HWyC0f+//k9iqWBOB3I5GJhAogBW9Q1VAAB4nGNgZGBgjvxfyMDAUvb/6//PLBUMQBEUYAAAo0gG23icTY3REYAgDEN7wAJMwjws4gBOwr+bOAkO4CfGppTTj3e5JJDGLhKz0j5SUWXegFg9pz/wpAKQcIoQy5Wwq1aM+Ra3bdKzr95ze/Pdrt1Fnb3R3S/qUgz7n/835tYLSYU3FQAAAAAAAEoAzgESAWwB8gKkAwYDyARKBIAE6gVkBrYG7AcgB1YIJghuDHIMsA00DXwNuA6uDzAPqhASEHQRKBH+Eo4TLBOKE/AUYBUSFaQWPhaqFwAXjBf2GE4YkBk2Gf4aqQAAAAEAAAAwAfgACwAAAAAAAgAsADwAcwAAAKoLcAAAAAB4nHWQy07CQBSG/5GLCokaTdw6KwMxlkviAhISEgxsdEMMW1NKaUtKh0wHEl7Dd/BhfAmfxZ92MAZim+l855szZ04HwDW+IZA/Txw5C5wxyvkEp+hZLtA/Wy6SXyyXUMWb5TL9u+UKHhBYruIGH6wgiueMFvi0LHAlLi2f4ELcWS7QP1ouknuWS7gVr5bL9J7lCiYitVzFvfgaqNVWR0FoZG1Ql+1mqyOnW6moosSNpbs2odKp7Mu5Sowfx8rx1HLPYz9Yx67eh/t54us0UolsOc29GvmJr13jz3bV003QNmYu51ot5dBmyJVWC98zTmjMqtto/D0PAyissIVGxKsKYSBRo61zbqOJFjqkKTMkM/OsCAlcxDQu1twRZisp4z7HnFFC6zMjJjvw+F0e+TEp4P6YVfTR6mE8Ie3OiDIv2ZfD7g6zRqQky3QzO/vtPcWGp7VpDXftutRZVxLDgxqS97FbW9B49E52K4a2iwbff/7vB+x4hFUAeJxtUGmz1CAQTL9NyOHu877v+0SfpX+IkNkEHwHkcF1/vSRbfnNqauihm6keirPiFF3x/7jAGTYoUYGhRoMWHa5gix3OcRXXcB03cBO3cBt3cBf3cB8P8BCP8BhP8BTP8Bwv8BKv8Bpv8Bbv8B4f8BGfwPEZX3CBrwWTwkjSLDltxVCGKHy3FE6zi8faUzwQxZqOxO1+zwIJL6eNtCPTdrQptoM9GG4dGSZiFHKqnZIxeap+qYFs59U4xZVvNe1PqE5uPc970pp7Zcac3Jbaystq1LanqvcpTG2eSCYqa0qnU2Bi+JFCLGlQkeXnUumNU4YdfMZTKyfhI++Fr/9YO3NlqpDZb2v93mhlLjn9jtt/gAsdy5lMamah9NJVo0gj7aSd83U8fUCzWszkdnHAw88kPA2VJ6ePVZiVpt3iel1nkTVZrlxQIevFkUvlpaZhF6c094HnvbOm65WxMmnhQ5sCeb6MLoq/SB+SJXicY/DewXAiKGIjI2Nf5AbGnRwMHAzJBRsZWJ02MTAyaIEYm7mYGDkgLD4GMIvNaRfTAaA0J5DN7rSLwQHCZmZw2ajC2BEYscGhI2Ijc4rLRjUQbxdHAwMji0NHckgESEkkEGzmYWLk0drB+L91A0vvRiYGFwAMdiP0AAA=') format('woff'),
-       url('data:application/octet-stream;base64,AAEAAAAPAIAAAwBwR1NVQiCLJXoAAAD8AAAAVE9TLzI+L1N4AAABUAAAAFZjbWFw7+BT3QAAAagAAAR0Y3Z0IAb//vQAAEH4AAAAIGZwZ22KkZBZAABCGAAAC3BnYXNwAAAAEAAAQfAAAAAIZ2x5ZksCilIAAAYcAAA1UmhlYWQW8cayAAA7cAAAADZoaGVhB8kECwAAO6gAAAAkaG10eKy//94AADvMAAAAwGxvY2FHzDggAAA8jAAAAGJtYXhwAYUNpgAAPPAAAAAgbmFtZcydHyEAAD0QAAACzXBvc3TWuIl7AAA/4AAAAg1wcmVw5UErvAAATYgAAACGAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEDmQGQAAUAAAJ6ArwAAACMAnoCvAAAAeAAMQECAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOgA8jQDWf9xAFoDZwCeAAAAAQAAAAAAAAAAAAUAAAADAAAALAAAAAQAAAIwAAEAAAAAASoAAwABAAAALAADAAoAAAIwAAQA/gAAACQAIAAEAAToHOgy6DTwj/DJ8ODw5fDz8P7xEvEY8T7xQfFE8WTx5fI0//8AAOgA6DLoNPCO8Mnw4PDk8PPw/vES8RjxPvFB8UTxZPHl8jT//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAkAFwAXABcAF4AXgBeAGAAYABgAGAAYABgAGAAYABgAGAAAAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAJAAlACYAJwAoACkAKgArACwALQAuAC8AAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAkQAAAAAAAAALwAA6AAAAOgAAAAAAQAA6AEAAOgBAAAAAgAA6AIAAOgCAAAAAwAA6AMAAOgDAAAABAAA6AQAAOgEAAAABQAA6AUAAOgFAAAABgAA6AYAAOgGAAAABwAA6AcAAOgHAAAACAAA6AgAAOgIAAAACQAA6AkAAOgJAAAACgAA6AoAAOgKAAAACwAA6AsAAOgLAAAADAAA6AwAAOgMAAAADQAA6A0AAOgNAAAADgAA6A4AAOgOAAAADwAA6A8AAOgPAAAAEAAA6BAAAOgQAAAAEQAA6BEAAOgRAAAAEgAA6BIAAOgSAAAAEwAA6BMAAOgTAAAAFAAA6BQAAOgUAAAAFQAA6BUAAOgVAAAAFgAA6BYAAOgWAAAAFwAA6BcAAOgXAAAAGAAA6BgAAOgYAAAAGQAA6BkAAOgZAAAAGgAA6BoAAOgaAAAAGwAA6BsAAOgbAAAAHAAA6BwAAOgcAAAAHQAA6DIAAOgyAAAAHgAA6DQAAOg0AAAAHwAA8I4AAPCOAAAAIAAA8I8AAPCPAAAAIQAA8MkAAPDJAAAAIgAA8OAAAPDgAAAAIwAA8OQAAPDkAAAAJAAA8OUAAPDlAAAAJQAA8PMAAPDzAAAAJgAA8P4AAPD+AAAAJwAA8RIAAPESAAAAKAAA8RgAAPEYAAAAKQAA8T4AAPE+AAAAKgAA8UEAAPFBAAAAKwAA8UQAAPFEAAAALAAA8WQAAPFkAAAALQAA8eUAAPHlAAAALgAA8jQAAPI0AAAALwABAAD/9gLUAo0AJAAeQBsiGRAHBAACAUcDAQIAAm8BAQAAZhQcFBQEBRgrJRQPAQYiLwEHBiIvASY0PwEnJjQ/ATYyHwE3NjIfARYUDwEXFgLUD0wQLBCkpBAsEEwQEKSkEBBMECwQpKQQLBBMDw+kpA93FhBMDw+lpQ8PTBAsEKSkECwQTBAQpKQQEEwPLg+kpA8ABAAA/7gDoQM1AAgAEQApAEAARkBDNQEHBgkAAgIAAkcACQYJbwgBBgcGbwAHAwdvAAQAAgRUBQEDAQEAAgMAYAAEBAJYAAIEAkw9PCMzIyIyJTkYEgoFHSslNCYOAh4BNjc0Jg4CHgE2NxUUBiMhIiYnNTQ2FzMeATsBMjY3MzIWAwYrARUUBgcjIiYnNSMiJj8BNjIfARYCyhQeFAIYGhiNFCASAhYcGEYgFvzLFx4BIBbuDDYjjyI2De4WILYJGI8UD48PFAGPFxMR+goeCvoSJA4WAhIgEgQaDA4WAhIgEgQaibMWICAWsxYgAR8oKB8eAVIW+g8UARYO+iwR+goK+hEAAAAAAQAA/9EDoQNHAB8AHUAaEg8KBAMFAAIBRwACAAJvAQEAAGYdFBcDBRcrARQPARMVFA4BLwEHBiImNTQ3EycmNTQ3JTc2Mh8BBRYDoQ/KMAwVDPv6DBYMATDLDh8BGH4LIAx9ARggAfAMD8X+6QwLEAEHhIQHEgoECAEXxQ8MFQUo/hcX/igFAAIAAP/RA6EDRwAJACkAJ0AkHBkUDg0JCAcGBQMBDAACAUcAAgACbwEBAABmJSQXFhIQAwUUKwE3LwEPARcHNxcTFA8BExUUIyIvAQcGIiY1NDcTJyY1NDclNzYyHwEFFgJ7qutqaeyrKdPT/g/KMBcKDPv6DBYMATDLDh8BGH4LIAx9ARggASmmItXVIqbrb28BsgwPxf7pDBwHhIQHEgoECAEXxQ8MFQUo/hcX/igFAAAAAAIAAP//BDACgwAhAEMAQkA/IgEEBgFHAwEBBwYHAQZtCQEGBAcGBGsIAQIABwECB2AABAAABFQABAQAWAUBAAQATEJAFiElGCEWFSgTCgUdKyUUBichIiYvAS4BMxEjIi4BPwE2Mh8BFhQGByMVITIfARYlFA8BBiIvASY0NjsBNSEiLwEmNDY3ITIWHwEeARURMzIWAsoKCP3pBQYCAwECAWsPFAEIswsgDLIJFg5rAUEJBVkEAWUIsgwgC7MIFg5r/r4JBVkECggCGAQGAgMBAmsOFhIHDAECAwQBDAFPFhsK1gwM1gocFAHWBmwF4g0K1g0N1gobFtYHawUNCgECAwUCCAP+shYAAAAFAAD/ygPoArgACQAaAD4ARABXAFdAVDQbAgAEUwYCAgBSQwIBAlBCKScIAQYGAQRHAAUEBW8AAgABAAIBbQABBgABBmsABgMABgNrAAMDbgAEAAAEVAAEBABYAAAEAExMSxMuGSQUHQcFGislNy4BNzQ3BgcWATQmByIGFRQWMjY1NDYzMjY3FBUGAg8BBiMiJyY1NDcuAScmNDc+ATMyFzc2MzIWHwEWBxYTFAYHExYXFAcGBw4BIzc+ATcmJzceARcWATYrMDgBIoBVXgFqEAtGZBAWEEQwCxDKO+o7HAUKB0QJGVCGMgsLVvyXMjIfBQoDDgskCwEJFVhJnQT6CxYnVNx8KXfIRUFdIzViIAtwTyNqPUM6QYSQAWcLEAFkRQsQEAswRBB1BAFp/lppMgknBgoHKiR4TREqEoOYCjYJBgYUBgEF/v1OgBsBGBleExMkLWBqSgqEaWRAPyRiNhMAAAL///9xA6EDFAAIACEAVEAKHwEBAA4BAwECR0uwIVBYQBYABAAAAQQAYAABAAMCAQNgAAICDQJJG0AdAAIDAnAABAAAAQQAYAABAwMBVAABAQNYAAMBA0xZtxcjFBMSBQUZKwE0LgEGFBY+AQEUBiIvAQYjIi4CPgQeAhcUBxcWAoOS0JKS0JIBHiw6FL9ke1CSaEACPGyOpI5sPAFFvxUBiWeSApbKmAaM/podKhW/RT5qkKKObjoEQmaWTXtkvxUAAAACAAD/uANaAxIACABqAEVAQmVZTEEEAAQ7CgIBADQoGxAEAwEDRwAFBAVvBgEEAARvAAABAG8AAQMBbwADAgNvAAICZlxbU1FJSCsqIiATEgcFFisBNCYiDgEWMjYlFRQGDwEGBxYXFhQHDgEnIi8BBgcGBwYrASImNScmJwcGIicmJyY0Nz4BNyYvAS4BJzU0Nj8BNjcmJyY0Nz4BMzIfATY3Njc2OwEyFh8BFhc3NjIXFhcWFAcOAQcWHwEeAQI7UnhSAlZ0VgEcCAdoCgsTKAYFD1ANBwdNGRoJBwQQfAgMEBsXTwYQBkYWBAUIKAoPCGYHCAEKBWgIDhclBgUPUA0HCE0YGgkIAxF8BwwBDxwXTwUPB0gUBAQJKAoPCGYHCgFlO1RUdlRUeHwHDAEQHhUbMgYOBhVQAQU8DQhMHBAKB2cJDDwFBkAeBQ4GDDIPHBsPAQwHfAcMARAZGiAtBwwHFFAFPA0ITBwQCgdnCQs7BQVDHAUOBgwyDxwaEAEMAAAAAgAAAAADawLKACcAQABCQD8UAQIBAUcABgIFAgYFbQAFAwIFA2sABAMAAwQAbQABAAIGAQJgAAMEAANUAAMDAFgAAAMATBYjGSUqJScHBRsrJRQWDwEOAQcjIiY1ETQ2OwEyFhUXFg8BDgEnIyIGBxEUFhczMh4CARQHAQYiJj0BIyImPQE0NjczNTQ2FhcBFgFlAgECAQgIskNeXkOyCAoBAQECAQgIsiU0ATYktAYCBgICBgv+0QscFvoOFhYO+hYcCwEvCzUCEgUOCQIDXkMBiENeCggLCQYNBwgBNCb+eCU0AQQCCAEsDgv+0AoUD6EWDtYPFAGhDhYCCf7QCgAAAAABAAD/7gO2AjAAFAAZQBYNAQABAUcCAQEAAW8AAABmFBcSAwUXKwkBBiInASY0PwE2MhcJATYyHwEWFAOr/mIKHgr+YgsLXQoeCgEoASgLHAxcCwGW/mMLCwGdCx4KXAsL/tgBKAsLXAscAAAB//7/ewO4A2cAMQAfQBwAAQAAAVQAAQEAWAIBAAEATAEAKikAMQExAwUUKxciJy4BNwE2Fx4BFxYHAQ4BJyY2NwE2FgcBBhcWNzY3ATYmJyYHAQYeAjcBNhYHAQb0ZkRIBFYB8FBeLEYMGlD+JihgIB4GLAFMGDQa/rQsGAwMGBYB2jIgPDY2/hJCBGSGSgHwGDQa/hBShUhGwF4B8FAaDEYsYFD+JigKIBhkKgFOGjQY/rQsGggCBBYB2jJ2EA4y/hJMhmIEQAHuGC4a/hBSAAAAAAT///+4BC8DEgAIAA8AHwAvAFVAUh0UAgEDDwEAAQ4NDAkEAgAcFQIEAgRHAAIABAACBG0ABgcBAwEGA2AAAQAAAgEAYAAEBQUEVAAEBAVYAAUEBUwREC4rJiMZFxAfER8TExIIBRcrARQOASY0Nh4BARUhNTcXASUhIgYHERQWNyEyNicRNCYXERQGByEiJjcRNDY3ITIWAWU+Wj4+Wj4CPPzusloBHQEe/IMHCgEMBgN9BwwBClE0JfyDJDYBNCUDfSU0AhgtPgJCVkIEOv76+muzWQEdoQoI/VoHDAEKCAKmCAoS/VolNAE2JAKmJTQBNgAL////cQQvAxIADwAfAC8APwBPAF8AbwB/AI8AnwCvAMRAGZBAAgkIiIBgIAQFBHg4AgMCUDAAAwEABEdLsCFQWEA3ABUSDAIICRUIYBMBCRABBAUJBGARDQIFDgYCAgMFAmAPAQMKAQABAwBgCwcCAQEUWAAUFA0USRtAPgAVEgwCCAkVCGATAQkQAQQFCQRgEQ0CBQ4GAgIDBQJgDwEDCgEAAQMAYAsHAgEUFAFUCwcCAQEUWAAUARRMWUAmrqumo56blpSOjIaEfnx2c25rZmReW1ZUTks1NTUmNSY1NTMWBR0rFzU0JgcjIgYdARQWOwEyNic1NCYrASIGHQEUFjczMjYnNTQmJyMiBh0BFBYXMzI2ARE0JiMhIgYXERQWMyEyNgE1NCYHIyIGHQEUFjsBMjYBNTQmByMiBgcVFBY7ATI2AxE0JgchIgYXERQWFyEyNhc1NCYrASIGBxUUFjczMjY3NTQmJyMiBgcVFBYXMzI2NzU0JgcjIgYHFRQWOwEyNjcRFAYjISImNxE0NjchMhbWFA9IDhYWDkgOFgEUD0gOFhYOSA4WARQPSA4WFg5IDhYCOxYO/lMOFgEUDwGtDxT9xRQPSA4WFg5IDhYDERYORw8UARYORw8U1RYO/lMOFgEUDwGtDxTXFg5HDxQBFg5HDxQBFg5HDxQBFg5HDxQBFg5HDxQBFg5HDxRINCX8gyQ2ATQlA30lNCRIDhYBFA9IDhYW5EgOFhYOSA4WARTmRw8UARYORw8UARb+YQEeDhYWDv7iDhYWApFHDxYBFBBHDhYW/YtIDhYBFA9IDhYWAbsBHQ8WARQQ/uMPFAEWyUgOFhYOSA4WARTmRw8UARYORw8UARbkRw8WARQQRw4WFmf9EiU0NCUC7iU0ATYAAQAA/8cCdANLABQAF0AUCQEAAQFHAAEAAW8AAABmHBICBRYrCQEGIi8BJjQ3CQEmND8BNjIXARYUAmr+YgscC10LCwEo/tgLC10KHgoBngoBcP5hCgpdCxwLASkBKAscC10LC/5iCxwAAAAAAQAA/8cCmANLABQAF0AUAQEAAQFHAAEAAW8AAABmFxcCBRYrCQIWFA8BBiInASY0NwE2Mh8BFhQCjv7XASkKCl0LHAv+YgsLAZ4KHgpdCgKx/tj+1woeCl0KCgGfCh4KAZ4LC10KHgABAAAAAAO2Ak0AFAAZQBYFAQACAUcAAgACbwEBAABmFxQSAwUXKyUHBiInCQEGIi8BJjQ3ATYyFwEWFAOrXAseCv7Y/tgLHAtdCwsBngscCwGeC3JcCgoBKf7XCgpcCx4KAZ4KCv5iCxwAAAAEAAD/dQPAA1kAKgA0AD0ATgC3QBE2NAIEAB0OAgEEAkdMAQEBRkuwGlBYQCkFAQQAAQAEAW0DAQEGAAEGawAGBwAGB2sIAQAADEgABwcCWAACAg0CSRtLsCRQWEAmBQEEAAEABAFtAwEBBgABBmsABgcABgdrAAcAAgcCXAgBAAAMAEkbQCcIAQAEAG8FAQQBBG8DAQEGAW8ABgcGbwAHAgIHVAAHBwJYAAIHAkxZWUAXAQBKSERDOjkwLxsZFhUSEAAqASoJBRQrASIGFRQXBgcOARUUBwYHFBY7ARQeATI+ATUzMjY1JicmNTQmJyYnNjU0JgUGBwYVMzQ3NjclBx4BBzM2JyYBMhYVFBYzMhYUBiMiJjU0NgHyFiAFRzgzOjoqTSod+SZBTkEm+R0qTSs6OTQ3RwQf/rU7Hh1HFhgxAjkwMi4BRwEdHv43BAUvIQQFBQQoOgUDWR8WCgwLJyRpNrV9W0EdKidCJiZCJyodQVt9tTZpJCcLDggWHy02SERRRDY4LTQ0LW5EUEVH/RgFBCEvBQgFOigEBQAAAAIAAAAAAoMDEgAHAB8AKkAnBQMCAAECAQACbQACAm4ABAEBBFQABAQBWAABBAFMIxMlNhMQBgUaKxMhNTQmDgEXBREUBgchIiYnETQ2FzM1NDYyFgcVMzIWswEdVHZUAQHQIBb96RceASAWEZTMlgISFx4BrGw7VAJQPaH+vhYeASAVAUIWIAFsZpSUZmweAAP//f+4A1kDEgAMAb0B9wJ3S7AJUFhBPAC9ALsAuACfAJYAiAAGAAMAAACPAAEAAgADANoA0wBtAFkAUQBCAD4AMwAgABkACgAHAAIBngGYAZYBjAGLAXoBdQFlAWMBAwDhAOAADAAGAAcBUwFNASgAAwAIAAYB9AHbAdEBywHAAb4BOAEzAAgAAQAIAAYARxtLsApQWEFDALsAuACfAIgABAAFAAAAvQABAAMABQCPAAEAAgADANoA0wBtAFkAUQBCAD4AMwAgABkACgAHAAIBngGYAZYBjAGLAXoBdQFlAWMBAwDhAOAADAAGAAcBUwFNASgAAwAIAAYB9AHbAdEBywHAAb4BOAEzAAgAAQAIAAcARwCWAAEABQABAEYbQTwAvQC7ALgAnwCWAIgABgADAAAAjwABAAIAAwDaANMAbQBZAFEAQgA+ADMAIAAZAAoABwACAZ4BmAGWAYwBiwF6AXUBZQFjAQMA4QDgAAwABgAHAVMBTQEoAAMACAAGAfQB2wHRAcsBwAG+ATgBMwAIAAEACAAGAEdZWUuwCVBYQDUAAgMHAwIHbQAHBgMHBmsABggDBghrAAgBAwgBawABAW4JAQADAwBUCQEAAANYBQQCAwADTBtLsApQWEA6BAEDBQIFA2UAAgcFAgdrAAcGBQcGawAGCAUGCGsACAEFCAFrAAEBbgkBAAUFAFQJAQAABVYABQAFShtANQACAwcDAgdtAAcGAwcGawAGCAMGCGsACAEDCAFrAAEBbgkBAAMDAFQJAQAAA1gFBAIDAANMWVlBGQABAAAB2AHWAbkBtwFXAVYAxwDFALUAtACxAK4AeQB2AAcABgAAAAwAAQAMAAoABQAUKwEyHgEUDgEiLgI+AQEOAQcyPgE1PgE3NhcmNj8BNj8BBiY1FAc0JgY1LgQvASY0LwEHBhQqARQiBiIHNicmIzYmJzMuAicuAQcGFB8BFgYeAQcGDwEGFhcWFAYiDwEGJicmJyYHJicmBzImBz4BIzY/ATYnFj8BNjc2MhYzFjQnMicmJyYHBhciDwEGLwEmJyIHNiYjNicmIg8BBh4BMhcWByIGIgYWBy4BJxYnIyIGIicmNzQXJwYHMjY/ATYXNxcmBwYHFgcnLgEnIgcGBx4CFDcWBzIXFhcWBycmBhYzIg8BBh8BBhY3Bh8DHgIXBhYHIgY1HgIUFjc2Jy4CNTMyHwEGHgIzHgEHMh4EHwMWMj8BNhYXFjciHwEeARUeARc2NQYWMzY1Bi8BJjQmNhcyNi4CJwYmJxQGFSM2ND8BNi8BJgciBw4DJicuATQ/ATYnNj8BNjsBMjQ2JiMWNhcWNycmNxY3HgIfARY2NxYXHgE+ASY1JzUuATY3NDY/ATYnMjcnJiI3Nic+ATMWNic+ATcWNiY+ARU3NiMWNzYnNiYnMzI1NicmAzY3JiIvATYmLwEmLwEmDwEiDwEVJiciLgEOAQ8BJjYmBg8BBjYGFQ4BFS4BNx4BFxYHBgcGFxQGFgGtdMZycsboyG4GerwBEwIIAwECBAMRFRMKAQwCCAYDAQcGBAQKBQYEAQgBAgEDAwQEBAQGAQYCCAkFBAYCBAMBCAwBBRwEAwICAQgBDgECBwkDBAQBBAIDAQcKAgQFDQMDFA4TBAgGAQIBAgUJAgETCQYEAgUGCgMIBAcFAgMGCQQGAQUJBAUDAwIFBAEOBwsPBBADAwEIBAgBCAMBCAQDAgIDBAIEEgUDDAwBAwMCDBkbAwYFBRMFAwsEDQsBBAIGBAgECQRRMgQFAgYFAwEYCgECBwUEAwQEBAECAQEBAgoHBxIEBwkEAwgEAg4BAQICDgIEAgIPCAMEAwIDBQEECgoBBAgEBQwHAgMIAwkHFgYGBQgIEAQUCgECBAIGAw4DBAEKBQgRCgICAgIBBQIEAQoCAwwDAggBAggDAQMCBwsEAQICCBQDCAoBAgEEAgMFAgEDAgEDAQQYAwkDAQEBAw0CDgQCAwEEAwUCBggEAgIBCAQEBwgFBwwEBAICAgYBBQQDAgMFDAQCEgEEAgIFDgkCAgoIBQkCBgYHBQkMCmlzUAEMAQ0BBAMVAQMFAgMCAgEFDAgDBgYGBgEBBAgECgEHBgIKAgQBDAEBAgIECw8BAgkKAQMSdMTqxHR0xOrEdP7dAQgCBgYBBAgDBQsBDAEDAgIMAQoHAgMEAgQBAgYMBQYDAwIEAQEDAwQCBAEDAwICCAQCBgQBAwQBBAQGBwMIBwoHBAUGBQwDAQIEAgEDDAkOAwQFBwgFAxECAw4IBQwDAQMJCQYEAwYBDgQKBAECBQICBgoEBwcHAQkFCAcIAwIHAwIEAgYCBAUKAwMOAgUCAgUEBwIBCggPAgMDBwMCDgMCAwQGBAYEBAEBLU8EAQgEAwQGDwoCBgQFBAUOCRQLAgEGGgIBFwUEBgMFFAMDEAUCAQQIBQgEAQsYDQUMAgIEBAwIDgQOAQoLFAcIAQUDDQIBAgESAwoEBAkFBgIDCgMCAwUMAhAIEgMDBAQGAgQKBw4BBQIEAQQCAhAFDwUCBQMCCwIIBAQCAgQYDgkOBQkBBAYBAgMCAQQDBgcGBQIPCgEEAQIDAQIDCAUXBAIICAMFDgIKCgUBAgMECwkFAgICAgYCCgYKBAQEAwEECgQGAQcCAQcGBQQCAwEFBAL+DRVVAgIFBAYCDwEBAgECAQEDAgoDBgICBQYHAw4GAgEFBAIIAQIIAgICAgUcCBEJDgkMAgQQBwACAAD/pQOPAyQADAAXACJAHxQBAQIRBQIAAQJHAAIBAm8AAQABbwAAAGYbFiIDBRcrJRQGJyInPgEnNDYyFgEWFAcBLgEnATYyAdCue1FERFIBWHpYAZ4gIf7CFFI4AT4gXtF8sAEoJ4pSPVhYAfUgXiD+wjdUFAE+IAAAA//1/7gD8wNZAA8AIQAzAGRADBsRAgMCCQECAQACR0uwJFBYQB0AAgUDBQIDbQADAAABAwBgAAEABAEEXAAFBQwFSRtAIgAFAgVvAAIDAm8AAwAAAQMAYAABBAQBVAABAQRYAAQBBExZQAkXOCcnJiMGBRorJTU0JisBIgYdARQWFzMyNicTNCcmKwEiBwYVFxQWNzMyNgMBFgcOAQchIiYnJjcBPgEyFgI7CgdsBwoKB2wHCgEKBQcHegYIBQkMB2cIDAgBrBQVCSIS/KYSIgkVFAGtCSImIlpqCAoKCGoICgEM1wEBBgQGBgQI/wUIAQYCEPzuIyMREgEUECMjAxIRFBQAAAAAAQAAAAADEgMSACMAKUAmAAQDBG8AAQABcAUBAwAAA1QFAQMDAFgCAQADAEwjMyUjMyMGBRorARUUBicjFRQGByMiJjc1IyImJzU0NjczNTQ2OwEyFhcVMzIWAxIgFuggFmsWIAHoFx4BIBboHhdrFx4B6BceAb5rFiAB6RYeASAV6R4XaxceAegWICAW6CAAAv/9/7gDXwMSAAcAFAArQCgAAwAAAQMAYAQBAQICAVQEAQEBAlgAAgECTAAAEhEMCwAHAAcRBQUVKyURIg4CHgEBFA4BIi4CPgEyHgEBrVOMUAJUiAIBcsboyG4Gerz0un41AmBSjKSMUgEwdcR0dMTqxHR0xAAABQAAAAAD5AMSAAYADwA5AD4ASAEHQBVAPjsQAwIBBwAENAEBAAJHQQEEAUZLsApQWEAwAAcDBAMHBG0AAAQBAQBlAAMABAADBGAIAQEABgUBBl8ABQICBVQABQUCWAACBQJMG0uwC1BYQCkAAAQBAQBlBwEDAAQAAwRgCAEBAAYFAQZfAAUCAgVUAAUFAlgAAgUCTBtLsBhQWEAwAAcDBAMHBG0AAAQBAQBlAAMABAADBGAIAQEABgUBBl8ABQICBVQABQUCWAACBQJMG0AxAAcDBAMHBG0AAAQBBAABbQADAAQAAwRgCAEBAAYFAQZfAAUCAgVUAAUFAlgAAgUCTFlZWUAWAABEQz08MS4pJh4bFhMABgAGFAkFFSslNycHFTMVASYPAQYWPwE2ExUUBiMhIiY1ETQ2NyEyFx4BDwEGJyYjISIGBxEUFhchMjY9ATQ/ATYWAxcBIzUBByc3NjIfARYUAfBAVUA1ARUJCcQJEgnECSReQ/4wQ15eQwHQIx4JAwcbCAoNDP4wJTQBNiQB0CU0BSQIGDeh/omhAm8zoTMQLBBVEMRBVUEfNgGSCQnECRIJxAn+vmpDXl5DAdBCXgEOBBMGHAgEAzQl/jAlNAE2JEYHBSQICAGPoP6JoAEuNKE0Dw9VECwABAAA/7gDTQMGAAYAFAAZACQAhkAXHgECBR0WDgcEAwIZAwIDAAMBAQEABEdLsBJQWEAnAAUCBW8AAgMCbwADAANvAAABAQBjBgEBBAQBUgYBAQEEVwAEAQRLG0AmAAUCBW8AAgMCbwADAANvAAABAG8GAQEEBAFSBgEBAQRXAAQBBEtZQBIAACEgGBcQDwkIAAYABhQHBRUrMzcnBxUzFQE0IyIHAQYVFDMyNwE2JxcBIzUBFA8BJzc2Mh8BFssygzNIAV8MBQT+0QQNBQQBLwMe6P4w6ANNFF3oXRQ7FoMUM4MzPEcCBgwE/tIEBgwEAS4Ecej+L+kBmh0VXelcFRWDFgACAAD/cQKDAxIACwAuAGO2BwECAQABR0uwIVBYQBsABwgGAgABBwBgCQUCAQQBAgMBAmAAAwMNA0kbQCQAAwIDcAAHCAYCAAEHAGAJBQIBAgIBVAkFAgEBAlgEAQIBAkxZQA4tLBMzERQiMxUVEwoFHSsBNTQmIgYdARQWMjYFFAYnIwMOAQcjIicDIyImJzQ2MxEiLgE2NyEyFhQGJxEyFgEMChAKChAKAXcWDu8dAQoGAQ8CK+EPFAFYNx0qAi4bAWUdKiodN1gBd/oICgoI+ggKCr0OFgH+8gcIAQ8BDxQPRW4BHio6KgEsOCwB/uJuAAAAAwAA/30DoAMSAAgAFAAuADNAMCYBBAMoJxIDAgQAAQEAA0cAAwQDbwAEAgRvAAIAAm8AAAEAbwABAWYcIy0YEgUFGSs3NCYOAh4BNiUBBiIvASY0NwEeASUUBw4BJyImNDY3MhYXFhQPARUXNj8BNjIW1hQeFAIYGhgBZv6DFToWOxUVAXwWVAGZDRuCT2iSkmggRhkJCaNsAipLIQ8KJA4WAhIgEgQa9v6DFBQ9FDsWAXw3VN0WJUteAZLQkAIUEAYSB159PAIZLRQKAAAAAAUAAP+4BHcDEgADAAcADQARABUAZkBjAAUKBW8PAQoDCm8MAQMIA28OAQgBCG8LAQEAAW8JBwIDAAYAbw0BBgQEBlINAQYGBFYABAYEShISDg4ICAQEAAASFRIVFBMOEQ4REA8IDQgNDAsKCQQHBAcGBQADAAMREAUVKwERIxEBESMRARUhETMRAREjESURIxEBZY8BZY4CyvuJRwLLjwFljwFl/uIBHgEe/cQCPP19SANa/O4B9P5TAa3W/X0CgwAAAAAD////cQOhAxQAIwAsAEUAoUAaHxgCAwQTEgEDAAMNBgIBAEMBBwEyAQkHBUdLsCFQWEAwAAQGAwYEA20AAQAHAAEHbQAKAAYECgZgBQEDAgEAAQMAYAAHAAkIBwlgAAgIDQhJG0A3AAQGAwYEA20AAQAHAAEHbQAICQhwAAoABgQKBmAFAQMCAQABAwBgAAcJCQdUAAcHCVgACQcJTFlAED08NTMUExUUIyYUIyMLBR0rARUUBicjFRQGJyMiJjc1IyImJzU0NjsBNTQ2OwEyFhcVMzIWFzQuAQYUFj4BARQGIi8BBiMiLgI+BB4CFxQHFxYCOwoHfQwGJAcMAX0HCgEMBn0KCCQHCgF9BwpIktCSktCSAR4qPBS/ZHtQkmhAAjxsjqSObDwBRb8VAZskBwwBfQcMAQoIfQoIJAcKfQgKCgh9ChlnkgKWypgGjP6aHSoVv0U+apCijm46BEJmlk17ZL8VAAAC//3/cQPrA1kAJwBQALBADiQWBgMBAkxCNAMEAwJHS7AhUFhAJgABAgMCAQNtBwEDBAIDBGsAAgIAWAYBAAAMSAAEBAVYAAUFDQVJG0uwJFBYQCMAAQIDAgEDbQcBAwQCAwRrAAQABQQFXAACAgBYBgEAAAwCSRtAKQABAgMCAQNtBwEDBAIDBGsGAQAAAgEAAmAABAUFBFQABAQFWAAFBAVMWVlAFykoAQBHRTEvKFApUBQSDAoAJwEnCAUUKwEiBwYHBgcUFh8BMzI1Njc2NzYzMhYXBwYWHwEWPgEvAS4BDwEmJyYBIhUGBwYHBiMiJyYnNzYmLwEmDgEfAR4BPwEWFxYzMjc2NzY3NCYvAQHug3FtQ0UFBQQEVBMFNTNTV2NPjjQ6CQIM9wsUCgQ6AhIJQURaXAEzEwU1M1NWY1BIRTU7CAIL+AsUCgQ6AhIKQERaXWaCcW5CRQUFBAQDWUA+a26BCAkCARJiU1EvMT44OQkTAzIDCRYQ4wgLBjxGJij+BBJiU1EvMSAeODkJEwMyAwkWEOMICwY8RiYoQD5rboIICAIBAAAAAAL///9iA+oDWQAfAEEASUAKBAECAAFHMQEBREuwJFBYQBMAAgABAAIBbQABAW4DAQAADABJG0APAwEAAgBvAAIBAm8AAQFmWUANAQAhIBQTAB8BHwQFFCsBIgcGBzE2NzYXFhcWFxYGBwYXHgE3PgE3NiYnLgEnJgEiBwYHBgcGFhcWFxYXFjc2NzEGBwYnJicmJyY2NzYmJyYB8ldRVERWbGpnak9CISEGJQ4aEDMRAwoCIwElJpBeW/4FGA8EBAYBJAIkJkhbe3d5fWFWbGpna09CISAFJQgGDhIDWR0eOUUVFB4gT0JWU7NRKRsQAREDDwZaw1ldkCYl/u4QBAYIBlrDWV1IWyQiGBlRRRUUHiBPQlZTs1EVIQ4SAAAAAAIAAAAAA+gDWQAnAD8AfUATKAEBBhEBAgE3LgIEAiEBBQQER0uwJFBYQCQABAIFAgQFbQAFAwIFA2sAAQACBAECYAADAAADAFwABgYMBkkbQCwABgEGbwAEAgUCBAVtAAUDAgUDawABAAIEAQJgAAMAAANUAAMDAFgAAAMATFlACjobJTU2JTMHBRsrARUUBiMhIiY1ETQ2NyEyFh0BFAYjISIGBxEUFhchMjY9ATQ2OwEyFhMRFA4BLwEBBiIvASY0NwEnJjQ2MyEyFgMSXkP+MENeXkMBiQcKCgf+dyU0ATYkAdAlNAoIJAgK1hYcC2L+lAUQBEAGBgFsYgsWDgEdDxQBU7JDXl5DAdBCXgEKCCQICjQl/jAlNAE2JLIICgoB2v7jDxQCDGL+lAYGQAUOBgFsYgscFhYAAAACAAD/uANZAxIAGAAoADJALxIJAgIAAUcAAgABAAIBbQAEAAACBABgAAEDAwFUAAEBA1gAAwEDTDU3FBkzBQUZKwERNCYnISIGHwEBBhQfARYyNwEXFjMyNzYTERQGByEiJjURNDY3ITIWAsoUD/70GBMSUP7WCws5CxwLASpRCg8GCBWPXkP96UNeXkMCF0NeAVMBDA8UAS0QUP7WCx4KOQoKASpQCwMKATX96EJeAWBBAhhCXgFgAAAAAAMAAAAAA1oCywAPAB8ALwA3QDQoAQQFCAACAAECRwAFAAQDBQRgAAMAAgEDAmAAAQAAAVQAAQEAWAAAAQBMJjUmNSYzBgUaKyUVFAYHISImJzU0NjchMhYDFRQGJyEiJic1NDYXITIWAxUUBiMhIiYnNTQ2FyEyFgNZFBD87w8UARYOAxEPFgEUEPzvDxQBFg4DEQ8WARQQ/O8PFAEWDgMRDxZrRw8UARYORw8UARYBEEgOFgEUD0gOFgEUAQ5HDhYWDkcPFgEUAAAAAAL///+4A+kCygAZADgALUAqCQACAgMBRwADAgNvAAIBAm8AAQAAAVQAAQEAWAAAAQBMNzQmJDozBAUWKwERFAYHISImNxEWFxYXHgI3MzI+ATc2NzY3FAYHBg8BDgInIyImLwEuAS8BJicuASc0NjMhMhYD6DQl/MokNgEZH8pMICZEGwIcQigfX7cgGDYp0jQ1DCIeDQIMHhEeDSIGk2ASIzwBLisDNiQ2Ac3+RSU0ATYkAbsbFok3GBocARocF0R8Fr8sUB2SIycJEgwBCgoSCBwDZUIOF1IkKzo0AAAABwAA/7gD6ALKAAgAEQAjACwANQA+AFAAZEBhLQECBjYJAgMHJAACAQADRwgBAgYHBgIHbQAHAwYHA2sJAQMABgMAawQBAAEGAAFrAAsABgILBmAFAQEKCgFUBQEBAQpYAAoBCkxNTEVCPTw5ODQzMC8rKicmExQTEgwFGCs3NCYiBh4CNhM0JiIOAR4BNhc3Ni4BBg8BDgEHBh4BNjc2JiU0JiIOAR4BNgE0JiIOAR4BNhc0JiIOAR4BNhcUBwYjISInJjU0PgIyHgLWKjosAig+Jm0oPiYELjYw6zkDEBocAzghNggLLFhKDQkaAVYqPCgCLDgu/pgoPiYELjYw9ig+JgQuNjCvTwoU/PIUCk9QhLzIvIRQ1h4qKjwoAiwBFh4qKjwoAizw1Q4aBgwQ1QMsIStMGC4rIUAlHioqPCgCLAGBHioqPCgCLE8eKio8KAIs3pF8ERF7kma4iE5OiLgAAAACAAD/cQPoAsoAFwA9AGJADDQIAgEAJgsCAwICR0uwIVBYQBcABAUBAAEEAGAAAQACAwECYAADAw0DSRtAHgADAgNwAAQFAQABBABgAAECAgFUAAEBAlgAAgECTFlAEQEAOzokIh0bEhAAFwEXBgUUKwEiDgEHFBYfAQcGBzY/ARcWMzI+Ai4BARQOASMiJwYHBgcjIiYnNSY2Jj8BNj8BPgI/AS4BJzQ+ASAeAQH0csZ0AVBJMA8NGlVFGCAmInLGdAJ4wgGAhuaIJypukxskAwgOAgIEAgMMBA0UBxQQBw9YZAGG5gEQ5oYCg06ETD5yKRw1My4kPBUDBU6EmIRO/uJhpGAEYSYIBAwJAQIIBAMPBQ4WCBwcEyoyklRhpGBgpAAAAgAA/3EDxANaAAwANACeQAsaDQIBBgABAgACR0uwIVBYQCcAAQYDBgEDbQUBAwAGAwBrAAACBgACawAGBgxIAAICBFgABAQNBEkbS7AkUFhAJAABBgMGAQNtBQEDAAYDAGsAAAIGAAJrAAIABAIEXAAGBgwGSRtAJQAGAQZvAAEDAW8FAQMAA28AAAIAbwACBAQCVAACAgRYAAQCBExZWUAKHyISIyMTEgcFGysFNCMiJjc0IhUUFjcyJRQGKwEUBiImNSMiJjU+BDc0NjcmNTQ+ARYVFAceARcUHgMB/QkhMAESOigJAccqHfpUdlT6HSocLjAkEgKEaQUgLCAFaoIBFiIwMFkIMCEJCSk6AakdKjtUVDsqHRgyVF6ITVSSEAoLFx4CIhULChCSVE6GYFI0AAACAAD/uANZAxIAIwAzAEFAPg0BAAEfAQQDAkcCAQABAwEAA20FAQMEAQMEawAHAAEABwFgAAQGBgRUAAQEBlgABgQGTDU1IzMWIyQjCAUcKwE1NCYHIzU0JicjIgYHFSMiBgcVFBY3MxUUFjsBMjY3NTMyNhMRFAYHISImNRE0NjchMhYCyhQPsxYORw8UAbIPFAEWDrIWDkcPFAGzDhaOXkP96UNeXkMCF0NeAUFIDhYBsw8UARYOsxQPSA4WAbMOFhYOsxQBP/3oQl4BYEECGEJeAWAAAAABAAD/uAPoAzUAKwApQCYmAQQDAUcAAwQDbwAEAQRvAAECAW8AAgACbwAAAGYjFxM9FwUFGSslFAcOAgcGIiY1NDY3NjU0LgUrARUUBiInASY0NwE2MhYHFTMgFxYD6EcBCgQFBxEKAgEDFCI4PlZWN30UIAn+4wsLAR0LHBgCfQGOWh7oXZ8EEhAECgwIBRQDJh84WkAwHhIGjw4WCwEeCh4KAR4KFA+P4UsABf/9/7gDXwMSABMAHAAlADYAQwBCQD8dFAICAwFHAAkABgMJBmAFAQMEAQIBAwJgAAEAAAcBAGAABwgIB1QABwcIWAAIBwhMQUAXFxYTFBMZGRIKBR0rAQ4BLgEnJj4BFhceATI2Nz4BHgElFAYiJj4CFgUUBiIuAT4BFhc0LgIiDgIeAz4DNxQOASIuAj4BMh4BAnkVcI5yFAQOHBoEDkxeSg8EHBoQ/uYqOiwCKD4mASAqPCgCLDgujTpeho6IXDwCOGCEkoJiNklyxujIbgZ6vPS6fgEBQ1QCUEUOGgkMECw4OCwPDgoa5R4qKjwoAiwcHioqPCgCLKtJhGA4OGCEkoRePAQ0ZnxNdcR0dMTqxHR0xAAAAAEAAAAAAoMDWgAjAGZLsCRQWEAgAAQFAAUEAG0CBgIAAQUAAWsAAQFuAAUFA1gAAwMMBUkbQCUABAUABQQAbQIGAgABBQABawABAW4AAwUFA1QAAwMFWAAFAwVMWUATAQAgHxsYFBMQDgkGACMBIwcFFCsBMhYXERQGByEiJicRNDYXMzU0Nh4BBxQGKwEiJjU0JiIGFxUCTRceASAW/ekXHgEgFhGUzJYCFA8kDhZUdlQBAaweF/6+Fh4BIBUBQhYgAbNnlAKQaQ4WFg47VFQ7swAAAwAAAAADEgH0AA8AHwAvACJAHwUDAgEAAAFUBQMCAQEAWAQCAgABAEw1NTU1NTMGBRorExUUBicjIiYnNTQ2NzMyFgUVFAYnIyImNzU0NjczMhYFFRQGJyMiJj0BNDY3MzIW1h4XaxceASAWaxYgAR0gFmsWIAEeF2sXHgEfIBZrFiAgFmsXHgG+axYgAR4XaxceASAWaxYgAR4XaxceASAWaxYgAR4XaxceASAAAAAC//3/uANZAxIADAAaACZAIwMBAAIAbwACAQECVAACAgFYAAECAUwBABkYBwYADAEMBAUUKwEyHgEUDgEiLgI+AQE2NCclJgYVERQXFjI3Aa10xnJyxujIbgZ6vAFQEhL+0BEkEgkSCAMSdMTqxHR0xOrEdP40CioKsgsVFP6aFAsEBQADAAD/uAN9AxIACAAYAFUATkBLSgEIBx8bAgADAAEBADERAgIBBEcABwgHbwAIAwhvBgEDAANvAAABAG8ABAIEcAABAgIBVAABAQJYBQECAQJMLywVJD8mNRMSCQUdKzc0LgEOAR4BNhMRFAYHIyImJxE0NhczMhYFFAcWFRYHFgcGBxYHBgcjIi4BJyYnIiYnETQ+Ajc2Nz4CNz4DMzIeBAYXFA4BBw4CBzMyFo8WHRQBFh0UWhQQoA8UARYOoA8WApQfCQEZCQkJFgUgJEpIJVYyKkUTDxQBFBs6HCYSCg4GBQQGEBUPGSoYFAgGAgIMCAwBCAQDmytAaw8UARYdFAEWASz+mw8UARYOAWUOFgEUDzAjGRIqIh8jHxU+JysBEg4PGAEWDgFlDhYBQCMxEgoiFBgWGCIWDBIaGCASDRUsFhQEDA4GQAAAAAUAAP9xA+gDWQAQABQAJQAvADkA20AXMykCBwghAQUCHRUNDAQABQNHBAEFAUZLsCFQWEAtBgwDCwQBBwIHAQJtAAIFBwIFawAFAAcFAGsJAQcHCFgKAQgIDEgEAQAADQBJG0uwJFBYQCwGDAMLBAEHAgcBAm0AAgUHAgVrAAUABwUAawQBAABuCQEHBwhYCgEICAwHSRtAMgYMAwsEAQcCBwECbQACBQcCBWsABQAHBQBrBAEAAG4KAQgHBwhUCgEICAdWCQEHCAdKWVlAIBERAAA3NTIxLSsoJyQiHx4bGREUERQTEgAQAA83DQUVKwERFAYHERQGByEiJicREzYzIREjEQERFAYHISImJxEiJicRMzIXJRUjNTQ2OwEyFgUVIzU0NjsBMhYBiRYOFBD+4w8UAYsEDQGfjgI7Fg7+4w8UAQ8UAe0NBP4+xQoIoQgKAXfFCgihCAoCpv5UDxQB/r8PFAEWDgEdAegM/ngBiP4M/uMPFAEWDgFBFg4BrAytfX0ICgoIfX0ICgoAAAADAAD/uAR4AxMACAAsAE8Ad0B0LCUCCgcgHw4DAwIyEwIECANHAAEHAW8ABwoHbw4BAAoNCgANbQALDQINCwJtDAEKAA0LCg1gBgECBQEDCAIDYAAIBAQIVAAICARYCQEECARMAQBNS0pIRURBPzYzMS8pKCQiHBsXFRIQCgkFBAAIAQgPBRQrASImPgEeAgYFMzIWBxUUBisBFRQGByMiJj0BIyImJzU0NjczNTQ2FzMyFhcBFBY3MxUGIyEiJjU0PgUXMhceATI2NzYzMhcjIgYVAYlZfgJ6tngGhAHDxAcMAQoIxAwGawgKxQcKAQwGxQoIawcKAf5lKh2PJjn+GENSBAwSHiY6IQsLLFRkVCwLC0kwfR0qAWV+sIACfLR6SQwGawgKxQcKAQwGxQoIawcKAcQHDAEKCP6/HSwBhRxOQx44QjY4IhoCCiIiIiIKNiodAAAAAAEAAAABAAArX0QOXw889QALA+gAAAAA2bLBggAAAADZssGC//X/YgR4A2cAAAAIAAIAAAAAAAAAAQAAA1n/cQAABHb/9f/zBHgAAQAAAAAAAAAAAAAAAAAAADAD6AAAAxEAAAOgAAADoAAAA6AAAAQvAAAD6AAAA6D//wNZAAADoAAAA+gAAAOr//4EL///BC///wLKAAACygAAA+gAAAPoAAACggAAA1n//QOgAAAD6P/1AxEAAANZ//0D6AAAA1kAAAKCAAADoAAABHYAAAOg//8D6P/9A+n//wPoAAADWQAAA1kAAAPo//8D6AAAA+gAAAPoAAADWQAAA+gAAANZ//0CggAAAxEAAANZ//0DoAAAA+gAAAR2AAAAAAAAAEoAzgESAWwB8gKkAwYDyARKBIAE6gVkBrYG7AcgB1YIJghuDHIMsA00DXwNuA6uDzAPqhASEHQRKBH+Eo4TLBOKE/AUYBUSFaQWPhaqFwAXjBf2GE4YkBk2Gf4aqQAAAAEAAAAwAfgACwAAAAAAAgAsADwAcwAAAKoLcAAAAAAAAAASAN4AAQAAAAAAAAA1AAAAAQAAAAAAAQAIADUAAQAAAAAAAgAHAD0AAQAAAAAAAwAIAEQAAQAAAAAABAAIAEwAAQAAAAAABQALAFQAAQAAAAAABgAIAF8AAQAAAAAACgArAGcAAQAAAAAACwATAJIAAwABBAkAAABqAKUAAwABBAkAAQAQAQ8AAwABBAkAAgAOAR8AAwABBAkAAwAQAS0AAwABBAkABAAQAT0AAwABBAkABQAWAU0AAwABBAkABgAQAWMAAwABBAkACgBWAXMAAwABBAkACwAmAclDb3B5cmlnaHQgKEMpIDIwMTkgYnkgb3JpZ2luYWwgYXV0aG9ycyBAIGZvbnRlbGxvLmNvbWZvbnRlbGxvUmVndWxhcmZvbnRlbGxvZm9udGVsbG9WZXJzaW9uIDEuMGZvbnRlbGxvR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwBvAHAAeQByAGkAZwBoAHQAIAAoAEMAKQAgADIAMAAxADkAIABiAHkAIABvAHIAaQBnAGkAbgBhAGwAIABhAHUAdABoAG8AcgBzACAAQAAgAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAGYAbwBuAHQAZQBsAGwAbwBSAGUAZwB1AGwAYQByAGYAbwBuAHQAZQBsAGwAbwBmAG8AbgB0AGUAbABsAG8AVgBlAHIAcwBpAG8AbgAgADEALgAwAGYAbwBuAHQAZQBsAGwAbwBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABlAGwAbABvAC4AYwBvAG0AAAAAAgAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExAAZjYW5jZWwGdXBsb2FkBHN0YXIKc3Rhci1lbXB0eQdyZXR3ZWV0B2V5ZS1vZmYGc2VhcmNoA2NvZwZsb2dvdXQJZG93bi1vcGVuBmF0dGFjaAdwaWN0dXJlBXZpZGVvCnJpZ2h0LW9wZW4JbGVmdC1vcGVuB3VwLW9wZW4OYmVsbC1yaW5naW5nLW8EbG9jawVnbG9iZQVicnVzaAlhdHRlbnRpb24EcGx1cwZhZGp1c3QEZWRpdAZwZW5jaWwDcGluBndyZW5jaAljaGFydC1iYXIHem9vbS1pbgVzcGluMwVzcGluNAhsaW5rLWV4dAxsaW5rLWV4dC1hbHQEbWVudQhtYWlsLWFsdAVnYXVnZQ1jb21tZW50LWVtcHR5CGJlbGwtYWx0DHBsdXMtc3F1YXJlZAVyZXBseQVzbWlsZQ1sb2NrLW9wZW4tYWx0CGVsbGlwc2lzDHBsYXktY2lyY2xlZA10aHVtYnMtdXAtYWx0CmJpbm9jdWxhcnMJdXNlci1wbHVzAAAAAAAAAQAB//8ADwAAAAAAAAAAAAAAAAAAAAAAGAAYABgAGANn/2IDZ/9isAAsILAAVVhFWSAgS7gADlFLsAZTWliwNBuwKFlgZiCKVViwAiVhuQgACABjYyNiGyEhsABZsABDI0SyAAEAQ2BCLbABLLAgYGYtsAIsIGQgsMBQsAQmWrIoAQpDRWNFUltYISMhG4pYILBQUFghsEBZGyCwOFBYIbA4WVkgsQEKQ0VjRWFksChQWCGxAQpDRWNFILAwUFghsDBZGyCwwFBYIGYgiophILAKUFhgGyCwIFBYIbAKYBsgsDZQWCGwNmAbYFlZWRuwAStZWSOwAFBYZVlZLbADLCBFILAEJWFkILAFQ1BYsAUjQrAGI0IbISFZsAFgLbAELCMhIyEgZLEFYkIgsAYjQrEBCkNFY7EBCkOwAWBFY7ADKiEgsAZDIIogirABK7EwBSWwBCZRWGBQG2FSWVgjWSEgsEBTWLABKxshsEBZI7AAUFhlWS2wBSywB0MrsgACAENgQi2wBiywByNCIyCwACNCYbACYmawAWOwAWCwBSotsAcsICBFILALQ2O4BABiILAAUFiwQGBZZrABY2BEsAFgLbAILLIHCwBDRUIqIbIAAQBDYEItsAkssABDI0SyAAEAQ2BCLbAKLCAgRSCwASsjsABDsAQlYCBFiiNhIGQgsCBQWCGwABuwMFBYsCAbsEBZWSOwAFBYZVmwAyUjYUREsAFgLbALLCAgRSCwASsjsABDsAQlYCBFiiNhIGSwJFBYsAAbsEBZI7AAUFhlWbADJSNhRESwAWAtsAwsILAAI0KyCwoDRVghGyMhWSohLbANLLECAkWwZGFELbAOLLABYCAgsAxDSrAAUFggsAwjQlmwDUNKsABSWCCwDSNCWS2wDywgsBBiZrABYyC4BABjiiNhsA5DYCCKYCCwDiNCIy2wECxLVFixBGREWSSwDWUjeC2wESxLUVhLU1ixBGREWRshWSSwE2UjeC2wEiyxAA9DVVixDw9DsAFhQrAPK1mwAEOwAiVCsQwCJUKxDQIlQrABFiMgsAMlUFixAQBDYLAEJUKKiiCKI2GwDiohI7ABYSCKI2GwDiohG7EBAENgsAIlQrACJWGwDiohWbAMQ0ewDUNHYLACYiCwAFBYsEBgWWawAWMgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLEAABMjRLABQ7AAPrIBAQFDYEItsBMsALEAAkVUWLAPI0IgRbALI0KwCiOwAWBCIGCwAWG1EBABAA4AQkKKYLESBiuwcisbIlktsBQssQATKy2wFSyxARMrLbAWLLECEystsBcssQMTKy2wGCyxBBMrLbAZLLEFEystsBossQYTKy2wGyyxBxMrLbAcLLEIEystsB0ssQkTKy2wHiwAsA0rsQACRVRYsA8jQiBFsAsjQrAKI7ABYEIgYLABYbUQEAEADgBCQopgsRIGK7ByKxsiWS2wHyyxAB4rLbAgLLEBHistsCEssQIeKy2wIiyxAx4rLbAjLLEEHistsCQssQUeKy2wJSyxBh4rLbAmLLEHHistsCcssQgeKy2wKCyxCR4rLbApLCA8sAFgLbAqLCBgsBBgIEMjsAFgQ7ACJWGwAWCwKSohLbArLLAqK7AqKi2wLCwgIEcgILALQ2O4BABiILAAUFiwQGBZZrABY2AjYTgjIIpVWCBHICCwC0NjuAQAYiCwAFBYsEBgWWawAWNgI2E4GyFZLbAtLACxAAJFVFiwARawLCqwARUwGyJZLbAuLACwDSuxAAJFVFiwARawLCqwARUwGyJZLbAvLCA1sAFgLbAwLACwAUVjuAQAYiCwAFBYsEBgWWawAWOwASuwC0NjuAQAYiCwAFBYsEBgWWawAWOwASuwABa0AAAAAABEPiM4sS8BFSotsDEsIDwgRyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsABDYTgtsDIsLhc8LbAzLCA8IEcgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLAAQ2GwAUNjOC2wNCyxAgAWJSAuIEewACNCsAIlSYqKRyNHI2EgWGIbIVmwASNCsjMBARUUKi2wNSywABawBCWwBCVHI0cjYbAJQytlii4jICA8ijgtsDYssAAWsAQlsAQlIC5HI0cjYSCwBCNCsAlDKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgsAhDIIojRyNHI2EjRmCwBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2EjICCwBCYjRmE4GyOwCENGsAIlsAhDRyNHI2FgILAEQ7ACYiCwAFBYsEBgWWawAWNgIyCwASsjsARDYLABK7AFJWGwBSWwAmIgsABQWLBAYFlmsAFjsAQmYSCwBCVgZCOwAyVgZFBYIRsjIVkjICCwBCYjRmE4WS2wNyywABYgICCwBSYgLkcjRyNhIzw4LbA4LLAAFiCwCCNCICAgRiNHsAErI2E4LbA5LLAAFrADJbACJUcjRyNhsABUWC4gPCMhG7ACJbACJUcjRyNhILAFJbAEJUcjRyNhsAYlsAUlSbACJWG5CAAIAGNjIyBYYhshWWO4BABiILAAUFiwQGBZZrABY2AjLiMgIDyKOCMhWS2wOiywABYgsAhDIC5HI0cjYSBgsCBgZrACYiCwAFBYsEBgWWawAWMjICA8ijgtsDssIyAuRrACJUZSWCA8WS6xKwEUKy2wPCwjIC5GsAIlRlBYIDxZLrErARQrLbA9LCMgLkawAiVGUlggPFkjIC5GsAIlRlBYIDxZLrErARQrLbA+LLA1KyMgLkawAiVGUlggPFkusSsBFCstsD8ssDYriiAgPLAEI0KKOCMgLkawAiVGUlggPFkusSsBFCuwBEMusCsrLbBALLAAFrAEJbAEJiAuRyNHI2GwCUMrIyA8IC4jOLErARQrLbBBLLEIBCVCsAAWsAQlsAQlIC5HI0cjYSCwBCNCsAlDKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgR7AEQ7ACYiCwAFBYsEBgWWawAWNgILABKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwAmIgsABQWLBAYFlmsAFjYbACJUZhOCMgPCM4GyEgIEYjR7ABKyNhOCFZsSsBFCstsEIssDUrLrErARQrLbBDLLA2KyEjICA8sAQjQiM4sSsBFCuwBEMusCsrLbBELLAAFSBHsAAjQrIAAQEVFBMusDEqLbBFLLAAFSBHsAAjQrIAAQEVFBMusDEqLbBGLLEAARQTsDIqLbBHLLA0Ki2wSCywABZFIyAuIEaKI2E4sSsBFCstsEkssAgjQrBIKy2wSiyyAABBKy2wSyyyAAFBKy2wTCyyAQBBKy2wTSyyAQFBKy2wTiyyAABCKy2wTyyyAAFCKy2wUCyyAQBCKy2wUSyyAQFCKy2wUiyyAAA+Ky2wUyyyAAE+Ky2wVCyyAQA+Ky2wVSyyAQE+Ky2wViyyAABAKy2wVyyyAAFAKy2wWCyyAQBAKy2wWSyyAQFAKy2wWiyyAABDKy2wWyyyAAFDKy2wXCyyAQBDKy2wXSyyAQFDKy2wXiyyAAA/Ky2wXyyyAAE/Ky2wYCyyAQA/Ky2wYSyyAQE/Ky2wYiywNysusSsBFCstsGMssDcrsDsrLbBkLLA3K7A8Ky2wZSywABawNyuwPSstsGYssDgrLrErARQrLbBnLLA4K7A7Ky2waCywOCuwPCstsGkssDgrsD0rLbBqLLA5Ky6xKwEUKy2wayywOSuwOystsGwssDkrsDwrLbBtLLA5K7A9Ky2wbiywOisusSsBFCstsG8ssDorsDsrLbBwLLA6K7A8Ky2wcSywOiuwPSstsHIsswkEAgNFWCEbIyFZQiuwCGWwAyRQeLABFTAtAEu4AMhSWLEBAY5ZsAG5CAAIAGNwsQAFQrIAAQAqsQAFQrMKAgEIKrEABUKzDgABCCqxAAZCugLAAAEACSqxAAdCugBAAAEACSqxAwBEsSQBiFFYsECIWLEDZESxJgGIUVi6CIAAAQRAiGNUWLEDAERZWVlZswwCAQwquAH/hbAEjbECAEQAAA==') format('truetype');
-}
-/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
-/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
-/*
-@media screen and (-webkit-min-device-pixel-ratio:0) {
-  @font-face {
-    font-family: 'fontello';
-    src: url('../font/fontello.svg?899037#fontello') format('svg');
-  }
-}
-*/
- 
- [class^="icon-"]:before, [class*=" icon-"]:before {
-  font-family: "fontello";
-  font-style: normal;
-  font-weight: normal;
-  speak: none;
- 
-  display: inline-block;
-  text-decoration: inherit;
-  width: 1em;
-  margin-right: .2em;
-  text-align: center;
-  /* opacity: .8; */
- 
-  /* For safety - reset parent styles, that can break glyph codes*/
-  font-variant: normal;
-  text-transform: none;
-     
-  /* fix buttons height, for twitter bootstrap */
-  line-height: 1em;
- 
-  /* Animation center compensation - margins should be symmetric */
-  /* remove if not needed */
-  margin-left: .2em;
- 
-  /* you can be more comfortable with increased icons size */
-  /* font-size: 120%; */
- 
-  /* Uncomment for 3D effect */
-  /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
-}
-.icon-cancel:before { content: '\e800'; } /* '' */
-.icon-upload:before { content: '\e801'; } /* '' */
-.icon-star:before { content: '\e802'; } /* '' */
-.icon-star-empty:before { content: '\e803'; } /* '' */
-.icon-retweet:before { content: '\e804'; } /* '' */
-.icon-eye-off:before { content: '\e805'; } /* '' */
-.icon-search:before { content: '\e806'; } /* '' */
-.icon-cog:before { content: '\e807'; } /* '' */
-.icon-logout:before { content: '\e808'; } /* '' */
-.icon-down-open:before { content: '\e809'; } /* '' */
-.icon-attach:before { content: '\e80a'; } /* '' */
-.icon-picture:before { content: '\e80b'; } /* '' */
-.icon-video:before { content: '\e80c'; } /* '' */
-.icon-right-open:before { content: '\e80d'; } /* '' */
-.icon-left-open:before { content: '\e80e'; } /* '' */
-.icon-up-open:before { content: '\e80f'; } /* '' */
-.icon-bell-ringing-o:before { content: '\e810'; } /* '' */
-.icon-lock:before { content: '\e811'; } /* '' */
-.icon-globe:before { content: '\e812'; } /* '' */
-.icon-brush:before { content: '\e813'; } /* '' */
-.icon-attention:before { content: '\e814'; } /* '' */
-.icon-plus:before { content: '\e815'; } /* '' */
-.icon-adjust:before { content: '\e816'; } /* '' */
-.icon-edit:before { content: '\e817'; } /* '' */
-.icon-pencil:before { content: '\e818'; } /* '' */
-.icon-pin:before { content: '\e819'; } /* '' */
-.icon-wrench:before { content: '\e81a'; } /* '' */
-.icon-chart-bar:before { content: '\e81b'; } /* '' */
-.icon-zoom-in:before { content: '\e81c'; } /* '' */
-.icon-spin3:before { content: '\e832'; } /* '' */
-.icon-spin4:before { content: '\e834'; } /* '' */
-.icon-link-ext:before { content: '\f08e'; } /* '' */
-.icon-link-ext-alt:before { content: '\f08f'; } /* '' */
-.icon-menu:before { content: '\f0c9'; } /* '' */
-.icon-mail-alt:before { content: '\f0e0'; } /* '' */
-.icon-gauge:before { content: '\f0e4'; } /* '' */
-.icon-comment-empty:before { content: '\f0e5'; } /* '' */
-.icon-bell-alt:before { content: '\f0f3'; } /* '' */
-.icon-plus-squared:before { content: '\f0fe'; } /* '' */
-.icon-reply:before { content: '\f112'; } /* '' */
-.icon-smile:before { content: '\f118'; } /* '' */
-.icon-lock-open-alt:before { content: '\f13e'; } /* '' */
-.icon-ellipsis:before { content: '\f141'; } /* '' */
-.icon-play-circled:before { content: '\f144'; } /* '' */
-.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */
-.icon-binoculars:before { content: '\f1e5'; } /* '' */
-.icon-user-plus:before { content: '\f234'; } /* '' */
\ No newline at end of file
diff --git a/src/font/css/fontello-ie7-codes.css b/src/font/css/fontello-ie7-codes.css
deleted file mode 100755
index 11c8c10a..00000000
--- a/src/font/css/fontello-ie7-codes.css
+++ /dev/null
@@ -1,48 +0,0 @@
-
-.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
-.icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
-.icon-star { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
-.icon-star-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
-.icon-retweet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
-.icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
-.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
-.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
-.icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
-.icon-down-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }
-.icon-attach { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
-.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }
-.icon-video { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }
-.icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
-.icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
-.icon-up-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
-.icon-bell-ringing-o { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
-.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }
-.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }
-.icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }
-.icon-attention { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe814;&nbsp;'); }
-.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe815;&nbsp;'); }
-.icon-adjust { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe816;&nbsp;'); }
-.icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe817;&nbsp;'); }
-.icon-pencil { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe818;&nbsp;'); }
-.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }
-.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81a;&nbsp;'); }
-.icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81b;&nbsp;'); }
-.icon-zoom-in { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81c;&nbsp;'); }
-.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
-.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
-.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
-.icon-link-ext-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08f;&nbsp;'); }
-.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0c9;&nbsp;'); }
-.icon-mail-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0e0;&nbsp;'); }
-.icon-gauge { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0e4;&nbsp;'); }
-.icon-comment-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0e5;&nbsp;'); }
-.icon-bell-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f3;&nbsp;'); }
-.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0fe;&nbsp;'); }
-.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf112;&nbsp;'); }
-.icon-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf118;&nbsp;'); }
-.icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf13e;&nbsp;'); }
-.icon-ellipsis { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf141;&nbsp;'); }
-.icon-play-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf144;&nbsp;'); }
-.icon-thumbs-up-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf164;&nbsp;'); }
-.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1e5;&nbsp;'); }
-.icon-user-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf234;&nbsp;'); }
\ No newline at end of file
diff --git a/src/font/css/fontello-ie7.css b/src/font/css/fontello-ie7.css
deleted file mode 100755
index edf83afe..00000000
--- a/src/font/css/fontello-ie7.css
+++ /dev/null
@@ -1,59 +0,0 @@
-[class^="icon-"], [class*=" icon-"] {
-  font-family: 'fontello';
-  font-style: normal;
-  font-weight: normal;
- 
-  /* fix buttons height */
-  line-height: 1em;
- 
-  /* you can be more comfortable with increased icons size */
-  /* font-size: 120%; */
-}
- 
-.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
-.icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
-.icon-star { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
-.icon-star-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
-.icon-retweet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
-.icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
-.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
-.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
-.icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
-.icon-down-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }
-.icon-attach { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
-.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }
-.icon-video { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }
-.icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
-.icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
-.icon-up-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
-.icon-bell-ringing-o { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
-.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }
-.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }
-.icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }
-.icon-attention { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe814;&nbsp;'); }
-.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe815;&nbsp;'); }
-.icon-adjust { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe816;&nbsp;'); }
-.icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe817;&nbsp;'); }
-.icon-pencil { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe818;&nbsp;'); }
-.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }
-.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81a;&nbsp;'); }
-.icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81b;&nbsp;'); }
-.icon-zoom-in { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81c;&nbsp;'); }
-.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
-.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
-.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
-.icon-link-ext-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08f;&nbsp;'); }
-.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0c9;&nbsp;'); }
-.icon-mail-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0e0;&nbsp;'); }
-.icon-gauge { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0e4;&nbsp;'); }
-.icon-comment-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0e5;&nbsp;'); }
-.icon-bell-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f3;&nbsp;'); }
-.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0fe;&nbsp;'); }
-.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf112;&nbsp;'); }
-.icon-smile { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf118;&nbsp;'); }
-.icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf13e;&nbsp;'); }
-.icon-ellipsis { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf141;&nbsp;'); }
-.icon-play-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf144;&nbsp;'); }
-.icon-thumbs-up-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf164;&nbsp;'); }
-.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1e5;&nbsp;'); }
-.icon-user-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf234;&nbsp;'); }
\ No newline at end of file
diff --git a/src/font/css/fontello.css b/src/font/css/fontello.css
deleted file mode 100755
index a6b3c919..00000000
--- a/src/font/css/fontello.css
+++ /dev/null
@@ -1,104 +0,0 @@
-@font-face {
-  font-family: 'fontello';
-  src: url('../font/fontello.eot?70867224');
-  src: url('../font/fontello.eot?70867224#iefix') format('embedded-opentype'),
-       url('../font/fontello.woff2?70867224') format('woff2'),
-       url('../font/fontello.woff?70867224') format('woff'),
-       url('../font/fontello.ttf?70867224') format('truetype'),
-       url('../font/fontello.svg?70867224#fontello') format('svg');
-  font-weight: normal;
-  font-style: normal;
-}
-/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
-/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
-/*
-@media screen and (-webkit-min-device-pixel-ratio:0) {
-  @font-face {
-    font-family: 'fontello';
-    src: url('../font/fontello.svg?70867224#fontello') format('svg');
-  }
-}
-*/
- 
- [class^="icon-"]:before, [class*=" icon-"]:before {
-  font-family: "fontello";
-  font-style: normal;
-  font-weight: normal;
-  speak: none;
- 
-  display: inline-block;
-  text-decoration: inherit;
-  width: 1em;
-  margin-right: .2em;
-  text-align: center;
-  /* opacity: .8; */
- 
-  /* For safety - reset parent styles, that can break glyph codes*/
-  font-variant: normal;
-  text-transform: none;
- 
-  /* fix buttons height, for twitter bootstrap */
-  line-height: 1em;
- 
-  /* Animation center compensation - margins should be symmetric */
-  /* remove if not needed */
-  margin-left: .2em;
- 
-  /* you can be more comfortable with increased icons size */
-  /* font-size: 120%; */
- 
-  /* Font smoothing. That was taken from TWBS */
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
- 
-  /* Uncomment for 3D effect */
-  /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
-}
- 
-.icon-cancel:before { content: '\e800'; } /* '' */
-.icon-upload:before { content: '\e801'; } /* '' */
-.icon-star:before { content: '\e802'; } /* '' */
-.icon-star-empty:before { content: '\e803'; } /* '' */
-.icon-retweet:before { content: '\e804'; } /* '' */
-.icon-eye-off:before { content: '\e805'; } /* '' */
-.icon-search:before { content: '\e806'; } /* '' */
-.icon-cog:before { content: '\e807'; } /* '' */
-.icon-logout:before { content: '\e808'; } /* '' */
-.icon-down-open:before { content: '\e809'; } /* '' */
-.icon-attach:before { content: '\e80a'; } /* '' */
-.icon-picture:before { content: '\e80b'; } /* '' */
-.icon-video:before { content: '\e80c'; } /* '' */
-.icon-right-open:before { content: '\e80d'; } /* '' */
-.icon-left-open:before { content: '\e80e'; } /* '' */
-.icon-up-open:before { content: '\e80f'; } /* '' */
-.icon-bell-ringing-o:before { content: '\e810'; } /* '' */
-.icon-lock:before { content: '\e811'; } /* '' */
-.icon-globe:before { content: '\e812'; } /* '' */
-.icon-brush:before { content: '\e813'; } /* '' */
-.icon-attention:before { content: '\e814'; } /* '' */
-.icon-plus:before { content: '\e815'; } /* '' */
-.icon-adjust:before { content: '\e816'; } /* '' */
-.icon-edit:before { content: '\e817'; } /* '' */
-.icon-pencil:before { content: '\e818'; } /* '' */
-.icon-pin:before { content: '\e819'; } /* '' */
-.icon-wrench:before { content: '\e81a'; } /* '' */
-.icon-chart-bar:before { content: '\e81b'; } /* '' */
-.icon-zoom-in:before { content: '\e81c'; } /* '' */
-.icon-spin3:before { content: '\e832'; } /* '' */
-.icon-spin4:before { content: '\e834'; } /* '' */
-.icon-link-ext:before { content: '\f08e'; } /* '' */
-.icon-link-ext-alt:before { content: '\f08f'; } /* '' */
-.icon-menu:before { content: '\f0c9'; } /* '' */
-.icon-mail-alt:before { content: '\f0e0'; } /* '' */
-.icon-gauge:before { content: '\f0e4'; } /* '' */
-.icon-comment-empty:before { content: '\f0e5'; } /* '' */
-.icon-bell-alt:before { content: '\f0f3'; } /* '' */
-.icon-plus-squared:before { content: '\f0fe'; } /* '' */
-.icon-reply:before { content: '\f112'; } /* '' */
-.icon-smile:before { content: '\f118'; } /* '' */
-.icon-lock-open-alt:before { content: '\f13e'; } /* '' */
-.icon-ellipsis:before { content: '\f141'; } /* '' */
-.icon-play-circled:before { content: '\f144'; } /* '' */
-.icon-thumbs-up-alt:before { content: '\f164'; } /* '' */
-.icon-binoculars:before { content: '\f1e5'; } /* '' */
-.icon-user-plus:before { content: '\f234'; } /* '' */
\ No newline at end of file
diff --git a/src/font/demo.html b/src/font/demo.html
deleted file mode 100755
index afae72fa..00000000
--- a/src/font/demo.html
+++ /dev/null
@@ -1,374 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head><!--[if lt IE 9]><script language="javascript" type="text/javascript" src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
-    <meta charset="UTF-8"><style>/*
- * Bootstrap v2.2.1
- *
- * Copyright 2012 Twitter, Inc
- * Licensed under the Apache License v2.0
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Designed and built with all the love in the world @twitter by @mdo and @fat.
- */
-.clearfix {
-  *zoom: 1;
-}
-.clearfix:before,
-.clearfix:after {
-  display: table;
-  content: "";
-  line-height: 0;
-}
-.clearfix:after {
-  clear: both;
-}
-html {
-  font-size: 100%;
-  -webkit-text-size-adjust: 100%;
-  -ms-text-size-adjust: 100%;
-}
-a:focus {
-  outline: thin dotted #333;
-  outline: 5px auto -webkit-focus-ring-color;
-  outline-offset: -2px;
-}
-a:hover,
-a:active {
-  outline: 0;
-}
-button,
-input,
-select,
-textarea {
-  margin: 0;
-  font-size: 100%;
-  vertical-align: middle;
-}
-button,
-input {
-  *overflow: visible;
-  line-height: normal;
-}
-button::-moz-focus-inner,
-input::-moz-focus-inner {
-  padding: 0;
-  border: 0;
-}
-body {
-  margin: 0;
-  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-  font-size: 14px;
-  line-height: 20px;
-  color: #333;
-  background-color: #fff;
-}
-a {
-  color: #08c;
-  text-decoration: none;
-}
-a:hover {
-  color: #005580;
-  text-decoration: underline;
-}
-.row {
-  margin-left: -20px;
-  *zoom: 1;
-}
-.row:before,
-.row:after {
-  display: table;
-  content: "";
-  line-height: 0;
-}
-.row:after {
-  clear: both;
-}
-[class*="span"] {
-  float: left;
-  min-height: 1px;
-  margin-left: 20px;
-}
-.container,
-.navbar-static-top .container,
-.navbar-fixed-top .container,
-.navbar-fixed-bottom .container {
-  width: 940px;
-}
-.span12 {
-  width: 940px;
-}
-.span11 {
-  width: 860px;
-}
-.span10 {
-  width: 780px;
-}
-.span9 {
-  width: 700px;
-}
-.span8 {
-  width: 620px;
-}
-.span7 {
-  width: 540px;
-}
-.span6 {
-  width: 460px;
-}
-.span5 {
-  width: 380px;
-}
-.span4 {
-  width: 300px;
-}
-.span3 {
-  width: 220px;
-}
-.span2 {
-  width: 140px;
-}
-.span1 {
-  width: 60px;
-}
-[class*="span"].pull-right,
-.row-fluid [class*="span"].pull-right {
-  float: right;
-}
-.container {
-  margin-right: auto;
-  margin-left: auto;
-  *zoom: 1;
-}
-.container:before,
-.container:after {
-  display: table;
-  content: "";
-  line-height: 0;
-}
-.container:after {
-  clear: both;
-}
-p {
-  margin: 0 0 10px;
-}
-.lead {
-  margin-bottom: 20px;
-  font-size: 21px;
-  font-weight: 200;
-  line-height: 30px;
-}
-small {
-  font-size: 85%;
-}
-h1 {
-  margin: 10px 0;
-  font-family: inherit;
-  font-weight: bold;
-  line-height: 20px;
-  color: inherit;
-  text-rendering: optimizelegibility;
-}
-h1 small {
-  font-weight: normal;
-  line-height: 1;
-  color: #999;
-}
-h1 {
-  line-height: 40px;
-}
-h1 {
-  font-size: 38.5px;
-}
-h1 small {
-  font-size: 24.5px;
-}
-body {
-  margin-top: 90px;
-}
-.header {
-  position: fixed;
-  top: 0;
-  left: 50%;
-  margin-left: -480px;
-  background-color: #fff;
-  border-bottom: 1px solid #ddd;
-  padding-top: 10px;
-  z-index: 10;
-}
-.footer {
-  color: #ddd;
-  font-size: 12px;
-  text-align: center;
-  margin-top: 20px;
-}
-.footer a {
-  color: #ccc;
-  text-decoration: underline;
-}
-.the-icons {
-  font-size: 14px;
-  line-height: 24px;
-}
-.switch {
-  position: absolute;
-  right: 0;
-  bottom: 10px;
-  color: #666;
-}
-.switch input {
-  margin-right: 0.3em;
-}
-.codesOn .i-name {
-  display: none;
-}
-.codesOn .i-code {
-  display: inline;
-}
-.i-code {
-  display: none;
-}
-@font-face {
-      font-family: 'fontello';
-      src: url('./font/fontello.eot?56851497');
-      src: url('./font/fontello.eot?56851497#iefix') format('embedded-opentype'),
-           url('./font/fontello.woff?56851497') format('woff'),
-           url('./font/fontello.ttf?56851497') format('truetype'),
-           url('./font/fontello.svg?56851497#fontello') format('svg');
-      font-weight: normal;
-      font-style: normal;
-    }
-     
-     
-    .demo-icon
-    {
-      font-family: "fontello";
-      font-style: normal;
-      font-weight: normal;
-      speak: none;
-     
-      display: inline-block;
-      text-decoration: inherit;
-      width: 1em;
-      margin-right: .2em;
-      text-align: center;
-      /* opacity: .8; */
-     
-      /* For safety - reset parent styles, that can break glyph codes*/
-      font-variant: normal;
-      text-transform: none;
-     
-      /* fix buttons height, for twitter bootstrap */
-      line-height: 1em;
-     
-      /* Animation center compensation - margins should be symmetric */
-      /* remove if not needed */
-      margin-left: .2em;
-     
-      /* You can be more comfortable with increased icons size */
-      /* font-size: 120%; */
-     
-      /* Font smoothing. That was taken from TWBS */
-      -webkit-font-smoothing: antialiased;
-      -moz-osx-font-smoothing: grayscale;
-     
-      /* Uncomment for 3D effect */
-      /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
-    }
-     </style>
-    <link rel="stylesheet" href="css/animation.css"><!--[if IE 7]><link rel="stylesheet" href="css/" + font.fontname + "-ie7.css"><![endif]-->
-    <script>
-      function toggleCodes(on) {
-        var obj = document.getElementById('icons');
-      
-        if (on) {
-          obj.className += ' codesOn';
-        } else {
-          obj.className = obj.className.replace(' codesOn', '');
-        }
-      }
-      
-    </script>
-  </head>
-  <body>
-    <div class="container header">
-      <h1>fontello <small>font demo</small></h1>
-      <label class="switch">
-        <input type="checkbox" onclick="toggleCodes(this.checked)">show codes
-      </label>
-    </div>
-    <div class="container" id="icons">
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xe800"><i class="demo-icon icon-cancel">&#xe800;</i> <span class="i-name">icon-cancel</span><span class="i-code">0xe800</span></div>
-        <div class="the-icons span3" title="Code: 0xe801"><i class="demo-icon icon-upload">&#xe801;</i> <span class="i-name">icon-upload</span><span class="i-code">0xe801</span></div>
-        <div class="the-icons span3" title="Code: 0xe802"><i class="demo-icon icon-star">&#xe802;</i> <span class="i-name">icon-star</span><span class="i-code">0xe802</span></div>
-        <div class="the-icons span3" title="Code: 0xe803"><i class="demo-icon icon-star-empty">&#xe803;</i> <span class="i-name">icon-star-empty</span><span class="i-code">0xe803</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xe804"><i class="demo-icon icon-retweet">&#xe804;</i> <span class="i-name">icon-retweet</span><span class="i-code">0xe804</span></div>
-        <div class="the-icons span3" title="Code: 0xe805"><i class="demo-icon icon-eye-off">&#xe805;</i> <span class="i-name">icon-eye-off</span><span class="i-code">0xe805</span></div>
-        <div class="the-icons span3" title="Code: 0xe806"><i class="demo-icon icon-search">&#xe806;</i> <span class="i-name">icon-search</span><span class="i-code">0xe806</span></div>
-        <div class="the-icons span3" title="Code: 0xe807"><i class="demo-icon icon-cog">&#xe807;</i> <span class="i-name">icon-cog</span><span class="i-code">0xe807</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xe808"><i class="demo-icon icon-logout">&#xe808;</i> <span class="i-name">icon-logout</span><span class="i-code">0xe808</span></div>
-        <div class="the-icons span3" title="Code: 0xe809"><i class="demo-icon icon-down-open">&#xe809;</i> <span class="i-name">icon-down-open</span><span class="i-code">0xe809</span></div>
-        <div class="the-icons span3" title="Code: 0xe80a"><i class="demo-icon icon-attach">&#xe80a;</i> <span class="i-name">icon-attach</span><span class="i-code">0xe80a</span></div>
-        <div class="the-icons span3" title="Code: 0xe80b"><i class="demo-icon icon-picture">&#xe80b;</i> <span class="i-name">icon-picture</span><span class="i-code">0xe80b</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xe80c"><i class="demo-icon icon-video">&#xe80c;</i> <span class="i-name">icon-video</span><span class="i-code">0xe80c</span></div>
-        <div class="the-icons span3" title="Code: 0xe80d"><i class="demo-icon icon-right-open">&#xe80d;</i> <span class="i-name">icon-right-open</span><span class="i-code">0xe80d</span></div>
-        <div class="the-icons span3" title="Code: 0xe80e"><i class="demo-icon icon-left-open">&#xe80e;</i> <span class="i-name">icon-left-open</span><span class="i-code">0xe80e</span></div>
-        <div class="the-icons span3" title="Code: 0xe80f"><i class="demo-icon icon-up-open">&#xe80f;</i> <span class="i-name">icon-up-open</span><span class="i-code">0xe80f</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xe810"><i class="demo-icon icon-bell-ringing-o">&#xe810;</i> <span class="i-name">icon-bell-ringing-o</span><span class="i-code">0xe810</span></div>
-        <div class="the-icons span3" title="Code: 0xe811"><i class="demo-icon icon-lock">&#xe811;</i> <span class="i-name">icon-lock</span><span class="i-code">0xe811</span></div>
-        <div class="the-icons span3" title="Code: 0xe812"><i class="demo-icon icon-globe">&#xe812;</i> <span class="i-name">icon-globe</span><span class="i-code">0xe812</span></div>
-        <div class="the-icons span3" title="Code: 0xe813"><i class="demo-icon icon-brush">&#xe813;</i> <span class="i-name">icon-brush</span><span class="i-code">0xe813</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xe814"><i class="demo-icon icon-attention">&#xe814;</i> <span class="i-name">icon-attention</span><span class="i-code">0xe814</span></div>
-        <div class="the-icons span3" title="Code: 0xe815"><i class="demo-icon icon-plus">&#xe815;</i> <span class="i-name">icon-plus</span><span class="i-code">0xe815</span></div>
-        <div class="the-icons span3" title="Code: 0xe816"><i class="demo-icon icon-adjust">&#xe816;</i> <span class="i-name">icon-adjust</span><span class="i-code">0xe816</span></div>
-        <div class="the-icons span3" title="Code: 0xe817"><i class="demo-icon icon-edit">&#xe817;</i> <span class="i-name">icon-edit</span><span class="i-code">0xe817</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xe818"><i class="demo-icon icon-pencil">&#xe818;</i> <span class="i-name">icon-pencil</span><span class="i-code">0xe818</span></div>
-        <div class="the-icons span3" title="Code: 0xe819"><i class="demo-icon icon-pin">&#xe819;</i> <span class="i-name">icon-pin</span><span class="i-code">0xe819</span></div>
-        <div class="the-icons span3" title="Code: 0xe81a"><i class="demo-icon icon-wrench">&#xe81a;</i> <span class="i-name">icon-wrench</span><span class="i-code">0xe81a</span></div>
-        <div class="the-icons span3" title="Code: 0xe81b"><i class="demo-icon icon-chart-bar">&#xe81b;</i> <span class="i-name">icon-chart-bar</span><span class="i-code">0xe81b</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xe81c"><i class="demo-icon icon-zoom-in">&#xe81c;</i> <span class="i-name">icon-zoom-in</span><span class="i-code">0xe81c</span></div>
-        <div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin">&#xe832;</i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div>
-        <div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-spin4 animate-spin">&#xe834;</i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div>
-        <div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xf08f"><i class="demo-icon icon-link-ext-alt">&#xf08f;</i> <span class="i-name">icon-link-ext-alt</span><span class="i-code">0xf08f</span></div>
-        <div class="the-icons span3" title="Code: 0xf0c9"><i class="demo-icon icon-menu">&#xf0c9;</i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span></div>
-        <div class="the-icons span3" title="Code: 0xf0e0"><i class="demo-icon icon-mail-alt">&#xf0e0;</i> <span class="i-name">icon-mail-alt</span><span class="i-code">0xf0e0</span></div>
-        <div class="the-icons span3" title="Code: 0xf0e4"><i class="demo-icon icon-gauge">&#xf0e4;</i> <span class="i-name">icon-gauge</span><span class="i-code">0xf0e4</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty">&#xf0e5;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div>
-        <div class="the-icons span3" title="Code: 0xf0f3"><i class="demo-icon icon-bell-alt">&#xf0f3;</i> <span class="i-name">icon-bell-alt</span><span class="i-code">0xf0f3</span></div>
-        <div class="the-icons span3" title="Code: 0xf0fe"><i class="demo-icon icon-plus-squared">&#xf0fe;</i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xf0fe</span></div>
-        <div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply">&#xf112;</i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xf118"><i class="demo-icon icon-smile">&#xf118;</i> <span class="i-name">icon-smile</span><span class="i-code">0xf118</span></div>
-        <div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div>
-        <div class="the-icons span3" title="Code: 0xf141"><i class="demo-icon icon-ellipsis">&#xf141;</i> <span class="i-name">icon-ellipsis</span><span class="i-code">0xf141</span></div>
-        <div class="the-icons span3" title="Code: 0xf144"><i class="demo-icon icon-play-circled">&#xf144;</i> <span class="i-name">icon-play-circled</span><span class="i-code">0xf144</span></div>
-      </div>
-      <div class="row">
-        <div class="the-icons span3" title="Code: 0xf164"><i class="demo-icon icon-thumbs-up-alt">&#xf164;</i> <span class="i-name">icon-thumbs-up-alt</span><span class="i-code">0xf164</span></div>
-        <div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars">&#xf1e5;</i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div>
-        <div class="the-icons span3" title="Code: 0xf234"><i class="demo-icon icon-user-plus">&#xf234;</i> <span class="i-name">icon-user-plus</span><span class="i-code">0xf234</span></div>
-      </div>
-    </div>
-    <div class="container footer">Generated by <a href="http://fontello.com">fontello.com</a></div>
-  </body>
-</html>
\ No newline at end of file
diff --git a/src/font/font/fontello.eot b/src/font/font/fontello.eot
deleted file mode 100755
index 1703fd97fce7f856264563cd89df72928771f153..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 20152
zcmd^ndvsLyedq6Y-*e}wnLBqzuhG?vG$W*u5YlKQgb;%s2!j9tGL|sL5_*6@FAHJg
z2U#}|A50u@s82R+PMvt`_-ym)0C~_fiM_3pCQiD?NgI-Fw>@c^<j6_ag~Uly*9`ml
z-Wdt7adI~2?Abqd#P8nU`|<sKf8Xcta(O#rJ_ODLMn8ecq8&lb)0|@c1NrpLr=5-D
zcYeLU?q=?C%*Q6#9Ghi}Y=TYWZw>_>whO6)>=@FcYymkY+k>11c9c!owJo-WHLymM
z+^IdXX;3eFoQZ6{Z};6@*88g;LC#wUgFAMwZt7gU`#37mT=$I4jL!e-4|e|p(*Kn)
zX>shqMN7GndxNo!zec$=Ie&2G%m<$yW~?jBm~;N%=+SxP`%r!rZ|&gp!;@QuGrJg5
z${1_eb!cLA+`RU+i;S(Kdg(*RP`@ELkw*Wl>d?&M@lU;Y;~$a!7Sb<G&y9`teXGr4
ztm7%956p}npXcv$zkoFLZ_SR*OnmE)Dk>RUk1>V6o}W9qc;)hwk21F52S~f;7bfO^
zys_>}jBPuO`cK$v*8!YPCiHCTA7q~C(ZyL-!X%W^k6?3aoKH_39A#Regrkf~IdV8t
zmUG2F;7>Cd<2@o^B=%JP&40kwaG=6XJV4oPzk$i#ah6$PKTnCnHy&Vn#e?iwR9Hp|
zMtx$J7)v5xT%j>v#%qyFwUT)2cD}@NNLMk7X1G+g)U=ekdhY5suKwWakFWmXYVMl<
zTKHP$wT;($u8m*&@y}B?ZZNjQ>{@5-T0eTXTIlsR|2Rh7XaDVI^fz*cKU$N3xk^}(
z!c;)dfq6Tbi@AZ&Uf@d+^RoamSP<wOVqsQ{Jt<{nteizy1y)tbqAZ4ys#%;RSPfgn
zYFQoDUk_AYjqYhL{(t|u2mnG)0Sti35~36cSC$Hah|^LbU`N<eArO8o6$0ViQXvqQ
zFBJlIjx7}eor$GFz;3doLLhuxDg^8>TPg(XHCrkK>^fU21nfUsDg<BxTPg&iAxnh-
z>|jfU06bw!g#e6UON9X3VM~PoEMiNA0DNLgg#gTAON9U&V@rhqY-3A>0K8*Mg#Zj>
zON9VjWJ`qrti;*L2jC}LO63DEm0dlX55QS=^;|vxd)d`*<OA@SUHw5m0HfK}ALRpZ
zn_c~JJ^;(v)nDWT@SR=7*`@%@XV<WH3OEDo8s<*{r-5DT%m<tcc5P!m;DoSiJ^4U1
z<l1;X;MA~d7>feV59m)m;3NS{Zv0C4Tj2wkPb5<sE%vDr6L_BpN+!&46i*suh9Sw}
zx@yDAmFQ}&(j%VQz!UUHH&yUfdYC8<nZn<C`+Z5@^Uvea@1r+f%W1Fo`RBb4nZACn
z_v0V;zO$mYfhrJY-ncA2C$<3Rim=uU+mz{U!MF}4U{-z1p*ZF=uBgf!Q$=+S4FqXW
z<|3vmh#*IjBn%--=wIIvYmP;ms@I17x?EmYor-&e2v4^zzk`MntBl4IEva;<Il|ZR
zrgUp_lPM~797Sr3itxy<(iC1XB8Cty34g$_{DN5)-ej3?eKQo{mif9X9Xl6IyI(i0
z&pX2B4DUIw!Ow*PMQ`d6{Y`(B#}xdQUn+4eN1nW7T9%0iuc)c1i11zJn`mKr-b|v2
z{w9#$KE*e_4LI}x&&o3;ey=WxGHnjdbYXAGjgTmZ>NsE@2pS%4?@qiW)f!09v)bMn
z*%Y7izS8Ily8h!$m+9h--}F@Q@Ozw=>jV#5{Hn|Q)!b_?r;n?rPN{xf(s<~rURO}A
z$%R6>8jOhj$j`%p-AH7r%8Ct-+o7q7EOOWF6RNHXnSP%r8+E*OH3vqht)W1EcCkF}
zymNNKqo4TM;i-T5<f^~<n;hmD((gRaRWC%p``zdZKbxE57xPmtdzT3Vb~NW3QloGZ
zXR(`gWi~{)q!3yIja078IJhFIigZK+o~v9?fi#RsgFqcNBoo{3@5-2!)!|ArSQD^u
z$50YLmDRj~Hy6dC4ZMI9geJjC!vE#-5l^Mp@s>*akfd8Ho6HIx;lU!{mGFwLU4Knh
z1e`8@1pBRB!X{jFn4TkiqeC8+_=I-RWjQZt$jN<$a&%1yOLl`J9@DS7xFAa0#dnyc
z`W2V!ie6^$E6TL|L$`j#?Y^Rynpe~#vRg+rS<uAXMNmSb?l)c$mxRlp+a-{Mde}Ye
zp3GpXRAAC>1xIREx4?ygu2l&fDuqjZOqS$1fDp%t&k(QW$`PiB_;-Yf;_NM;0?b%{
z|JFc5ag|Z7$|ZHxtqr_2)vBl_PsP=!5;V-FG>%s@PL>f=1dxze)W*#Qq93iDytygV
zief;?RLuau88AZzHp#<dC~b`=S|gw<JYCn=#-oqly`LZUIX8{_OkYo<)A!1{pROyD
zb+yM)Jn%%5)4AuZ|J>A6A?u>YS>@!8;Lu%vB)#c06NCTru~iR!y?5h%v6cgtbACtc
z@cQodji;XG2Z8qEy-uIc+1TSdCh@7<-l-->LebUQs^i;=YW*iatfw7{VklhBUEltA
zDGwL#4+N^}M-Fe%Pfd+yHdGx*gCz+!ZrpePXVn1Bs$_#1y#mC@1Ck{4ZOv8=3}sA$
z8kazwI1>dfj)0cAg}X{K<)B%@{LNBP<b#ZJafpdr>>vI@C}srwvRqupQw>}(%udb?
zqQHP-gU~5O1V9UP_vHCk&*LADB-@P_#~&Rye<&k#OrL%J>~sh3eKE+NJa}IC&?_HS
zp341Ud2R5;-p<2M|Ch6~?NZm|hqgUB{vx=ieP%9;dqqFiaG3RGx+aGEH$uEzrwg2=
zYD#?)W|T(`U<s4vh(ygXEOHJ@5`i(o<i2}%@4RbsU2W6~_*L1&q~lQ!2M$z&pknh?
zGh`ar+XSruy9`PjjkhG?32+rWZH#EOPZ<$qoG~Ck0Zl59z#oV-;R=wZDOBi8wU3yv
zZrAZ$!k)!Fyi8LM>CQlnB6|nis=BSX#Gy*Q$26C(G_*tUDVt15)@pRGHmPb{mk(*4
zP<6hpwk=%Z(8Qw0aMHYGp&hbU-E2sbBVSqP6YB;C9~>M!PUXHxu(V0>D8T_Pceu6w
zGM}y<bhtWXB@>Z7imS<6R_f(0)vjAyV%4iI)fl*?s&kzzcb65amiRzQ?LB}Ba71_o
zygx&9%K)hW#a9GbP~;h?n1YN`D2Ysz7|tdW6fTTVLL9`|WJ9Dt^qaBb>e}jrDwl$5
zn_lkW#LHTWh%z;q!H}Jmh(#5($S_0AO%Z_`po-D>dX9SQc?xV0;5S3u<P)F`T+=Rg
z@892jQPVlkC#zFDUG-T-0R2#$xo<nm%r`wI_?KDc<g1-6f?xJH1aW^iKi$1w*PIT;
z4Opk*x#MUk2^vp&oVizZ!~2}+xdKY~92lbm82|wm#csSVejeJ7!HP4co8vh20SXLw
zM)F9=PdpD4JesiO5FmoXmP1TK{ABI`IBxEM(|I4>yoT2}%Ut`M{6o1hr<4DYGotTv
zI&<GgwsW5o;vK({yYZ-a8JgG{R*@;AvF+o>hJf>&^>bFc3ToXN;%K300>q7{!SO?;
z3ZN$9X(SO<LMBj?r-7?TM1(@_8<Rbor9J%W!2aYWSIIywUNd4vlqByDr%H04O@>{r
zu*v_f$?8a_bN()A{E036Di!&5y>IiTm-eG!iEC4Ggc|Brc)XTxFG+>butXCiYWSef
z)0Fe~KXE|H@Yll))R5pOffiksR@>*;3)x{cyF0V1+yK$>Voe^m%OMG@EC>M!q#ir~
zqS6clR}{G-jt~T}bnru2w)vqvMC_oy$k$L8j}?b}6-5<+fM2tv4iDg;j&L5VY-tVg
z>dHJqf#;<YMX7kG2-F-rvlVzpe2`Cc?(M`sq2sOBFYe{#Jo45_Rp%~6Jb~k+?@U#{
zb+RhWQ`O>$>XZ<!?-aWBbV=>GH{U#RX_%Kk2j;n#Dr&+Dn(n{8*G56%1$r?j(O-Mb
z`4w+qJJ|goPXCFWV;^OI%3jVCKb;XA+UdtfEJ>D*w}I#mG=ju3sX(AxS<vqiG)GVy
z32=vxOR_^6DRK)qp8|2y5ib{Yzz+hZQw25}L(DMT1}J#v|5;1J;DakYaHD@X6aUO7
zU-;u^Kk}hx&OY_TsrNkg;L+J5ljHmE-7~m-YfDQ@9Dgm%Cgj&p3wSC{Z8<kgl2t(>
z<Dj2*8gi?hrc+JjIMr#6O@Mr>U?a?CYzlu{`{lF>E=~Q3sE-Y^>xWRE#-skJoceD?
zn);_S^-pP<&vN@#Y&gl&%Nu^h@NOoefCnVhJ4A(brYE=CF6E#08rQ#ivzAz7dirdU
z7|-wCR_%MYlt5%&sda07^KHmfb#n$kx;c%X+|n{P$|E$@+z;^-KG^5QjD3BGUw{A2
zw*1d|xm}R^XPVhJ?%dUnZZ<x6-CvzbRSU1%I7)QwuY^T$E9hD%V>p0JpzL_sWUb6E
zfL{_-Bkiiyu}s>aSP6$g(8`?mfr8cKzF#11&+2?WH>&IRp@gp@I!6_#BGG{SSRY;<
zi-_kfV}(NYSOV#6c`6EuW$SRl+1&T|s(i0RMftNJqxb2;h1~aZ-?O7S|0q$`XYFaw
zIN&&+7q;bTHALsz^plWb)9PyC?kh{fP+%;e)%!q>(eL*QYkQVf@~m@VpRV&&7!9PF
z2GWU~)1pW(JSM&*4nyZnvGr^_`$DEDof0Hg?h(LL`uf3~Hf_z8K-^j-Lqfsf<}(oa
z3j(YPA_sw=G1q3Ms-z;?64=&k6&l3ftpSW6f-3B@+cBJW2+5eq9IA6^jylKo<yBEZ
zPzMVgt3v<qa3;jrmd!og?Q0uXmlm5rzmL`OS_kPyBp8QC#to9FOQ+9>@TN}Qf>YH3
zE<y(_o`B#Sm(h2yIR*Aotwy-ooKD1vh2uasnK*6W>ZvsUxoOFLZO!fNwcBdT|2@8O
z`^Nacm)CBqYhRmc?UO2UpIa9x?=!<|gtd)L4ZM$+M{=!_yt<NTd`-J74p*4E%bBQ}
zN`LOey&KDG6J7CmSE9Cj<Gm+7m!7IhI6azKQJ>!2v$H4NR-a1M&-M)T_FWIlQsrt{
zliO<~=olnF;&BpuM+MiZ%_JbKFh~nbm;tDvHgIlp6g~t&nD@s5)#-pwfzlAD#6mqh
zBo`P4m<RN6EUd{?gIMP!UJj{?^H(kN`fK36mRa=7w>~8J5&hKkx<O%J{d2jmn8*+E
zE@;%#lg~UeIUQl*jq9)@3}Zen{$>7e!b4lLj)9?#9qh~O&)H?Lg%7dQFn5WV^*P`Q
z87%*f{SBn5VYZWXK~ZXE7EDDtVE})Yf0%!WKgHkAAK}ON1Rnz<_$T%Qn7Lr!<h%Jc
zUW4`;Q}`SF@A<d+H~CBaE4+<2Bah6-3hOJ~n$<CWcVYC?KqCU%mr*Vv_J4m4D(l0V
zImY5_Q|bRZKnxGtJCSLD>Z6K+I>S^&R7uCwL`6HoG%jlV2q@MpXe5N%LA;naBulXF
zi2ZpmX4)kVt}KfaIG(bg9zh>+z7ILyhkUaSS<d$%@4?#TEv3KX$iu@Mivc>uzt6Am
zKjUBE_wYUJuh>`F=h$c21@;;CFnbWl0_!Qzf+Oe{4yU~-!eN^sb&ZoKPUjq&Z8{Vu
z1!n_S;w^@niYqM*5|Mtg;TW~th$>MPr#%L#vbjNk=!tA7J_@J+X;P&7C{aX+Qp8mX
zO>wo8$EeeUNvadhPqUdyG}#rD5LLzi33Q8I6LCs|>OhvoP>QKSBR7e?LlJ9Dg%XO|
zL|uhiLvhe3Q^gF>R8b>>(Q2x;@}(x?irGy4RA54_N`(k@6g@?i7Sw`FoM;e0(oh%?
zU|&<4B2tBDHlbI{zBOtaMj%Y5ThI$0wEB2D)Re|@u`ELg#?oYBM7|nTJz^YFpmc)f
z1%V}nb(m@NgbACi5dqLix0;{|og6x4O9NT@Y-lv0MijGwwZWuktC@~>@?dK^Msp+p
zH?;sDBD76#-!$}3`sd|XSrG7Qz^-|DJRK*vr<EYcHWUF{3jlQv8H&k2z4*0-g|97r
zeO7toOFRJ63l}7@C>VfvD`<*{Et4c&RwNFC6Bk7ZKZPq0H)U9NpjT@yE|*E7096Zv
z3918Cz|zoA7bMv&iiRg3Y0&k!AUgyea43=>E4rvjKz>nipf|{?vIzB1;vUuMm3%NH
zXt3pBDjIbxLfrSuqRWNh1y^yYsK|0a7M+sYiGdVJlN{2{CW+Kck%wVHQe}zei6No#
z>8k3NRELBC1rO4K2kL|1)kJhHf)|2iqX*fg3Zf=DR8vu8P4h_x`a$QS2UaUx){1li
zKQ0SM38G7c9!Q`932<T@K{G@sn8eC)vXKjoL<)-zFj+3T1rLD(7EnRK8~`UtQZ-q1
z!PqN63C3j6$|VVYG>6dc5CmNVs1!w&9WH(9=m5-SZnO^)*&sm6E>Ii%z?C#f#|{Yq
zH7a2ulGDotht5U+;>$mMdC`94{()=g4V6XNiE8Kqx(L<_xN<>p$qL{K7KhEn3vmI{
z#D-(#*lShPR9%&1MRpNtU}-J~fFffFVv!(vG|Cqp*j7>D9!W=+GM1%Fs;Y8_tf?A+
zg#Mv#OE7PV9x9e4C?dM%6+|MV9sod6@P|p%?*OJ@`xLK^IY58&I1H!2l@bA5R#KqN
zilPrNmNZ$DxHIgQU0Ai`(maxf>rMl@x(twGFZ`ko`v=)9bsNBfPxBN0Vjeo=7urg|
z+b4U877I=+8c8Yaad>10nOp&C03F9f7QDa_r11~hBWwTwq({(o85xq(f%Ra)f{ryy
zI1m7V!m&Iwq<zOD=MLUYYbGNiodbYc&_xAVfG(6Yszdlh{m5lnk;CI~36c-Y8*<~v
z#dBg6C~}BJGZhBf7s(`>K>y_eOjrdOywr@6X`vdn-YCdKf+P?~sw#}W2}rH{)z3V-
zv!`blA9`enKWkOy{=(SR#yhS3-+nC1YZ7O6tsffVzqIySxxZ)~G*AGpb>o+iL4E<5
z)ypa&P>g3>rPy!5f%(Igk(WJT0+b0UXGj(y1rrkjSobkmcF7PqqYS`>c}>JrS88CJ
z$xbK@QB^3Bj!;_y+=Aq(YKT+IdLij)i9jkr8IUAHc}-4)u@#&Mx*A9)$R%vk>vVNm
z)$Iq4s;WMs;P|-IgPKd@pE81usQ;}O{82~H;GcFx<I%l`HC@;6;I8jM2$mF3kQ-pR
zir{<e^;oRP5B(_?6a7U7oOHH60$}(di^bTgOdJL*m=6K=dAMy*K;9b|uK*KRuVT&B
zaNE#A$P1H*6<jf`<ccAyplzEXLsp1{L(y-UOO|;Ag5Q!Y8!kmcM-ZXJS19*2TQYpD
zkV|ftB@6aik`3>-WmSXKWk`6lL65@&+AxS0Ck=rM{fzmGTu$(HwMdqOb=5^taw4s8
zbTD`R>D^Ba2!p2uzRUvN_~Lt7gpplOJ^$1$-gvA)K^9-md-$37BaEv6Bi2I5+|1QX
zFw?othl8RrDFszc=-Y^s0!;?uVx|#3E=g2jq9FkqH$TX&E=nUB5{k@~`+*#SJP2H&
z`N{&xM^4g!3iV;v`RxtDztcdOHMh<U`W7MZZ=T#R)FSvKdb-zltZ7&kkCd7LKteGb
z1liUE#Qh*KUWMcrc(iPD0@*ELKf->Z04NR%g>AQlg#xBZR5Zm9kHJBhC^#bd)y&<Q
z79MmsUUv8qs`hv18p(diUyVf^qFSoyZdb0+wjc3Vt5b577H)km_vCZJT=R3ya97^#
zdwJvC8!OWM{Bjq$uN=;I(6t}rIiQq*x~8fNeQZ+EgT~K&EcfKccth&Bl-GN=F9}GH
zA9I_C^^tbWs@M}5EJBdWO^*t8RSbd(3l#Q|Y*2n|LE^T94vRVtI2$7lCGlN&ap@j9
z?ORK6f^H=Vc1<?-&WghsxQ;8WaLDI%kPf04DgfPF0CXxAg}E|lG&e#0PuMVoL7)J|
zZ#JE5-pucJ$x`myk{eRRYB92uYg`hy8TT#SXRI?%8qFu0JNm%-q}+ETJb8okz*27Y
zYy1z(gZI6*FBm*&+O+opTPJkF^E~!>_}Xyl3nsTxrfLevu*yb2{tL`u1QNen1l6k|
zr^5X0Rp@9^Rh&(*5**IqjHf;sXf86M%|S?8Fp`l`D{lg8O3Oq(MBDnE5Q%!g5mf|n
z&NfG)tO(@YrTcXJ;}4mhe=X;_!o5P>Kf$~+)LLFEG?em*^4i++)*=4Tn?&&F@nsmS
zaz6))^m4D^?VaV3+V)zWY)kUo4`)H^KuK<#5I=@9u3_IBSaYT^4jx#O@PmQVj*5La
zz}Pbg3gl)3RR<{qIpmXNvHGy>>{)hES1;Rx;6tp=J5FG7Z3XQk+f6V;;%$?8rQn$6
zlev?@c5_`Y$R9HY`5(AT-@D_``SXXYO~nq!A5ROlTPwZ#iZl9Gxs!&m9y`e&YaRRt
zvwG`(e*V>`1;eNK)%_<rgyMR`w#SqI@Urv})+2%ny5W-#vdPRClXZE{%XLwogEXXx
za~{a9+ME*$<Z})PM!>f@H&}zD?7~Zt_Am%1Tl{{HM}w3AvKsUUjew`fQ{?k%Zq4m-
z>JCYTTUN$EVv!G+%8Oz}b_iA$HQTXj%9%LFC(a75{Ku1h!Z#7czuXT2pvd)?g^ued
zHj8`TdY!+K+s!|H<@yP@fN4)w{BtqfjOb<0WlAc-kUIi?kVnxC>49}~l{ayRDi^S`
z5z`eRkCB%}1(MD{ol_)T83C^%BqlzsGPuDVBTU2mfwHadXrei^`8&09IBZ*yV~9Ca
z2e8<;zN4kt0L6>NjabY{c)ZN}ppwN`cpp5=c_)s|`$E6bB?yjw!lhKfWPZXnv!Bql
zDnyXjyzDHj?YN_}@FV#~WNXhiIzhO9LNETUPDJ9X4~Rd5ypv!9ESvFEnb3ZO{;m{h
z3I)i<A$gH>KLg$b;Z8aN^AH<SpjpE*CrRXSklhe`^3qZ49kpRMk>q_h8<87z)!S=P
zci1N39i9v_%&n^7tgm;?>Y9O71BTzFvjk6QwqXtmE*a!1;FoTJ25q~hL#je9$4*?Y
zXuzR|%*3NXs%QJMK`%*L^l(yVH^4Iq(;PL#5nj#t>n9(W>F$M{O&ScyEzP^{8QXC-
z)$R~n|K>Dwsa^0pHumh@$D8fKJ!1o#dt26Ng7epfQa#hN_rA&ZJ}}#5_aP2vI*-i$
zo(2`re_;2{)oVK2);a=Wljtyg|ExKcj!p5JoRlxJBJV7t*6-Cczy&|@SsxI8ioL2}
z8`)hMQaV7J*KppGhkO9U2@s?^0DKm%63EYhv==b}<SH*Y^U2{1Jam<o5nvU(LRtpm
z8bE;jvmp~;0@ov7J``;T(g2<fWU^q;A0V{`b;w9T4mEP3@t@zbbFgR6^x=btcXU-&
zD%GA6UvrVD3o%|De|rDDIXUbFS>aVeRebZkk3RJ9iBYP21eGnhT2nlJakxCPwl`=*
ztQ}o@c3;}Ls?^7eM6a^<FNg1YI$oW7-6tuUo!WeFRWw|@^Om~7N)KJQBHjhUv1H@&
z26iG7sDYqc1h&)K07a}4`UdfBG9FifltUA`>7NCJ08Fql;s*PeqPP@5E(z-(?kxR=
zy0`dnhckM6X?07wx|!U#L=DKbQjmpAxOgais~|)c5i?L^-vV0U8gQPc;rfA;ydncV
zNm739p%v)|R6*0Pz;AmX_l)e5GSG3S4>(N^d>sbgZTo%ss}OtadMh#yT){g3J92~w
zE;Lk>3>;<Du*{@8C!}-bZLeY6Tx-g#_Cp~Da;;z#=s%d@2*~Jd_Z?bVjpAnewgY@o
z2`muzC~UJAM3nr&!U`2qT$Uk$TUp-R8{vR|Aa}*-TuVNL+MT*r(SqmpcVB;vq&6Yc
zy&oo87uHknqiPX-EoNUk;Dkr7<@yp9H?mO()63p+`>|K}CM0z7p0;LEHE;)NG<N~_
zAE3O*674P7D6j%>Z_>ob_YRLSx$EN1wi-r?3#nM7v(O$8MK7e`UbEA*r&MmIhYjCb
z|4N=Ek62{;ncg*Z<QCTz_pNxSxWPT-w1Q6z{wrDro$<@!Yq(EP%-UFerk3u|iP#6o
zR?A2D_W6TsRkb%uba$fw>DD4Tr|@bw!*Xd;HmnP#Fpog2iD)9oAD!$1sx86y_6i)i
z#TBpgTk)P!p{%Q>;{GpK;q<ESq*`3jh+A+)iX!f)^7|wHSO;&Y6Vs^v*SX$2PX2kR
z`DAOjq>Pu8g?b({UrY{^pN}ORey}6mua$`tU7pacs=9XEVgv&wJX^y0=&m5~_7><=
z1JJWa>tU5i;|_vaMSMW)gKYrQwyn*uM~Fup;NZB_bVLF#hll70Zm%kWlh`}P;m}C&
z9%4Gz`?vM?cCGJN+m>o>TwPb2hzD>Z$Aue_096#0&2+%N<bsQZxRioBI`(}FSXPOa
z;mRTIzaXB!2t#$2bLf^6wx66modT|4T&ZnO3N@Ya83d^z-T1S$qOYV(Y^zLb&g9S*
zx1)sbsqLr{l5GvS4;SkEs__2P9lG(>&kcRYz^N~N{Y$3?u0(3>s@yC@u71~3qPTqD
z6_b^9{o#hX%1rfgmES8wb`;+K_`%1DiXJ^bdHMAA?WZpj4gtmw*f0*k>vkaHN)ZEz
zJHf-3IeG}!1pvnU^(Hd$;CSSvc(`FmioaP*B0Nq@LAf5z6mhn$y((H>3inuuhZLKE
zKx8K#3_bw?m(G2s&|s_FxIBhURBcND46=}ZH}Fnj0~u&io!o*h{>H-B7Wu$kjb3-j
z-MwKe9z|L>{uli5Cw_7|Q9JwlrB$Nlfr$$CN6BrdhEMeljq@jd!hJt^LO8koRDb8f
zs<M{mhN_OBC~rUY;ZxglKOB92L>i52k_%=VD1KhqV`^n(f!e0?gD4t#ei=qDi+jN%
zQ|#G{vjo;NV3lASdFTRI0$2vyo;X7m55hY%QkVdzhXSbw6M`W}a!bonPu06s$At|6
zrlA5$sm`+$x`Rdik|JpHW?10{3!MtE4uP3eME$WCUCJu0lcD&*#2+QMeiMvcxIc=!
zKV)8M>6BnHZzYuEa5BQ<7&0OvzwW4P<o@;=2mh<u@;Bj&d$YW@tf8^WFPxf^t)wL%
zelItpjg7-vW2M8fs-6Eyc`a^At*b2$Hx2GTy>0NkPj`ZXM1xM<cYbjD6C=BDcyIEE
z7%XEOGo5tPrvjRi&<Fg&l9&xFGNHn|1KO&BK9iw|u4yVm5XP1MmX=tv8LNtEa+$69
zsWGU3AWOKDU_Z#*g=K;@;<f@zVRzylm-2TAF4~m$Vm@)nW1hW@eQYEmd&#bH$-bq)
zQN{FJGWdqKGY~KYqDi>FTt~Jp(*20w+8Q5x#y|?VjDZ-o+dmcxtPc^xBkfLrpPU@J
zt?>Ld$Trir?>Lcno?M|;$P}0QxDLB9OzB)SqHUdf_OzZbEJyC2;jAxrmW72A{OsPy
z(tRJ5{60x{L8=ww6>WPnjS;_c&SN@xgxvQLT)jH?Pg|M1Vh0PbGTcK?vu=i4XSgk7
za|s79)nRKqq;40uoyx(tRZX)s7;T8bINiT76AIz>W}vv(zG)2~21$BAB8YwfeJ4mD
z5G@+-6ig`PP{FAv)gX{9P3#m~m;Ga~rU?%R=g%$}lBcXh^7QZD;+4uue7T<#s3W%u
zH1GrM`=2;_dS8doHgf9xdk>`Vy2bX!c{jYmy`B<>%a?3xOL{$e$&U-<t*r3=<Xxvm
z+NkZR{T)(j^095Vn(2tC13^#XJjd8%UdXdR#GrVv8Ca<qhY!{EP(oFqtD2&1sI9(p
z6%lnvvLnStl%eefII}8B!$!d8aVRXtV=7TSDD`i5fWv1-$CHo(tR@r`wgH~Ed%+Da
z6nfh|{;5dlRyX*igU<+0PZ5&RQM^QwD1M@!zfq8DU<rrG-WFfz4FLwGdlF%A=?FKD
zY|p%{gf5$1n@qWo#?2p?VQ;4MR|a8-$Xi+S=GgN5E1y+iC-A@mY_sgGHSZ87@Za>`
z<KA*S6I)R{xJ}&X9)ZAdMXxxlD)8~UY~|xtXKOl@sE#YaA|qr%e!R^`H{kc@UM;Hf
zJN(*hUY?X*t6y{mjocp^PV6^ehO>Oaw&jG`-E4bi>lUu56}UT%PqH{$Qv`m7YjmoH
z?+`RmgAFcEg~^};LOqWuE;BqhI-m)!P6n$s#9IP>2bBC)DDbv;VACbyYWCF!0M<~=
zATC6M*HNGg=TMLA@=gIV7#^J>E^bP2KZ03;a$q;kndJ~3%Z<H;?_)Nl`k2=go~dwf
zSbrR*Y*lU9T)n5Mwl{!6qqMy&?$<qVuPDBtx41TJz!oRC;D`i=`$%1e?x)-R@nr5J
zc5i&bzNyt1EB4n$D`FMF&O{yed%R%?tae!@w#KhVjj$Pxnl67y*z&uBNz;&A9wmeQ
zBs&u12<THj*u|<LS+8Y(p9wXu5>yS^lu#aYyP(>OeG-?+`b}K3UU9)i2}ckNhqy%|
ztAcz49jP*d{HChfkdC{luFa6)V3=Cwoym7^L=|zXV5OxBwXEs4RL2mgKS(2~d(c)>
zw}7EqMfgagwWVoIeO*nWDq0aKEiN*OAQ@tH-d4A5yn>v(a-sv!!)$9-K`AJr&BW#R
z>L6H_t-Hx9G5)0KF?{wdv-eAG{?W7cMLD{3hJXLdE#*4Ds%y`|_wrEwExeE$L`m+&
z{63ZQC0Fh^Kb>>sFRFR?M(oh1T%SIXx8u;0NCDxtbX*KTc245H<U^UoWHmg!R)q)d
z%cg(;S7#p#U*J~oUBbM_bhpmjGtB80+)iNz@;Gxlb@vFod=Ne~L4+YplC(jX!=xd2
z|1=4QbKBM}n|pgUZb&z;S-q;JDq2<=!c8b0j&I!a(7d*|2SyQr-x(;3tAwC;c#w!^
z-$q{X_`s8e3&}P^&#S_zPB}zg0q_@mKoA1I$2Iri_Xv-C{<w0A|K-c}UD20a$`MWf
zs%-!Ru#O;-o2V^67hjtTckhy1{z$yN(&<bNjt?fC&bt~<l-Kf!_hcUz9{cPgcfGT1
zzSG=`<w^d&vhCfGwytzrv_#OO`043dd?-drpdIS=|N1-kdpmx~oTB|v-v7Ax-`-2b
z^oCdP03d%5Nm48GKeV&I3uV%SDF20YJkNy+rSxi~o<!iC__3QoT8%j6f9%E$oUMEr
zr9XKiC#}X;FbL3(7zMi?$^^^=V_moVS-SB{yH83}JM>|fV+`p*q^aK}l)Xmpe7QZ&
zQXx%w%lY>E#&x?r&2wdpd|$K(wuSw7?&s6|=fd-%B7R-kB0VnsR32A8ul$Q@se81z
zHtSk&W!)+FWA4kI&v+ZXpYZv8i$yg>Ise%}GH@nv)fj>8_<6I_{6vU_o(lacygmGM
zak@BH@+at@;NQr9O<yD8CjhrQ@Ub*wpK#9GIj}sle_VlW3-YK1JvC^L`8d7`g07Q~
z)3vUiLL5lT`U`OxBS4k0+bYPvzYy0U_Z=+6o%m+)Jh2MNfqIA8#|v@JeB4`z3k<fy
zLR>_CeIYI(zP=EbnaQ^m;tKM|3UQt7;YSN`CyVoMbkEH{yfAg}(4tk-y~=88T(j0X
z@US(9tf|@2X>0V@;-R^PqgKY6oSR*on4X?%7@M0}j_;Z{cx-xfVL7?{-ZQaqbZTza
zTGP<DoYglmJFzgjI5AGWAARs()8gW!HMuZ1V{Iz*Yt1jr9iA9lY&f*IIN!c{^>RxZ
z4?^Y~o5yvj1$@JPkR4)+%z~My8=se1tO=iMuK{bY*a6hAP%~d^3SY*L;wyLy&tqtD
z$SygGbOzz=U(UnXgz}kp<lp`^{o7l;J$>gd?cZLj4_?_>`<whx%xi*;V?O!0AH{{R
zgILib{wD2NF4(iO*e1Mh{nFn8JgDq2+K-`jgAJ8M<hFxRu71Zlme&WYTBaXF)9>_a
zB+(t-H8_hw%r)-dI;R^fZu?pj?4mf00ho@0_}U}H!@QW6@KRnz7xbZyS-g@*d5l-_
zY97Zmh#FiruH|(+$?I*)Pa|K$l(Es-v59Hr*!=X|=(u!radbhaNBzXi{Nlsv!o=c3
z6BCQ-#KRNybCZ+G(TUN8u|wk6+(Bh}?%>?9MaTHuL$mdB^Aoem=;Gq&*dcX(YHab?
z!i4<b)cC}lZX>i^?wFpKT(o2Av3dLLIRGrKUznN&f*-7(lki^%9FY%B&mEYM4=fxz
zddPtuCuSFczta5lv7^f9_~Bzm7o~~usYL|?j!jLA^Ha0RLkox>a*Q1sU0AF?FuI^V
zGB-C<KQ${KMQOABOli|ovq$PDjxV~FBlV-xi_*-*>@jU-bZVMX^1;z#2PfP>a%3;&
z0ifAaM4^jjP=EA+W1|Zb<1+rQg6W6lqcc;}6K<Ndy&UR8!-}Tnk4_y${n3Z($EFs>
zrYFYTi-(TQ95`B!b)$}cU}|=5j85xO$FZXm3-#1>e)pj}<9mPaJ5V9a>_71!to{gD
zmb9|$?xEgc%i4OGd3J8iD!ca%WmBcun&FX2>&)FlS)qFL&oq#>v9Z{J(#py#8_u%s
zSkFaJx9*Xy`Ycaot&z$4tdO+Et?Ww!St-8vVhz{3d&hcr-8)nnt1LY;WMu~ihAOj}
z;ZiHxMzOZxVe3M^(&%`$23duam2IT*MymPJz>tMWoEf#~8pH^4EGp9}mZn&Gq;v$=
z2}-lPZg@DBWdlPK!^8DiF=_Q$S*dyy6Oy|JhO%<3E33r1F#X{yAF0nu$r$EkjbD%t
zbXioAADo^j?;XpEwUx;4w$4~*(D#K#xf;vbIW#g*I=X9kC^n4J%<dr+mC~XLBiCo;
zWLE92yNKgxgGNCz))fPYV_l<J;lN~;kD>ppTw9-2lNL?V**$hyVh2zK)69$vQ<ae(
zdy-o6qUvPby<N4HE5LRnZwJ1f?}gW43f)-oh}C;0HcA^|LyDCWG_zJICb>MztXLfz
z?a2?}e3$0r(a56l%F=Fa<RYAjbzOAoV((C8X{>U%wz59!PF@g%-t72jPkq*t!~!fU
z>+0S@>p~>fHJo)*Y8O&&r0TO?bmOx@VgUkUn0D6FJz||1v9cZjtv>5ZZXF!DAdUA7
zS7qH3vE%jGqU6?{LtA&}GfOLxAF%WN$qUTeefQ7>uNV6_+LiUz5t0IRyDqrs;l?A&
zP3(kNJuq~EFc-k;I)m+{VQVX6Xto?L&6iUX;69ZOV<ml<f8Pi;@wTmcmkqqYSRe)v
zcV}7WMHrE7YzQQAL0IS=9LjoQT~=?_38d69@JN?6g5f{s^Wk$**41@p<bq$R%P!QF
zMgb-RYYWuXXM@QLoZcqjOz%+gf=KUh@`6O~;^YOH-X+Nk3cX8{7gTzeB`;|7E>B)?
z&^wZ3S$Ey<Fp&yOB!UhsOoZN*m<YY2m<YXNm<YYAFcErJV<Pm9V<PlUU?TLc!9?i2
zDrt4tcwU>t`h6o-H^G87Z4~e9DnhEdWVW_0TZ`k81g7=@rS5oMVxw&_qE)}K2Jodm
zTfedg+{`9xvpm>nL%HErsN7z(I%&1o^K3*N-uuqu3Mz2NX;D5iKWo$Ap3Yd?h1EQW
zm8`+KG0{6V83ERgw$*2wk`1Ab`fT%Ws|pMoL-iE4f|=D;gVjeT6Ck|n%$dGeA1J{P
zXf|jbj%zc%(K9f93rL6w?8iU5O4iX`H*uyRW?3C)&{z6qvDJ|8BP*d#)UvW8beJ<c
zhb{@0Xq8?P;$q2g7ZErOq!Qy{#MmYrE$rg$=LCnE*X1d>&^<C9%ZlBj<DfA@_h>2N
zBf~hDZ*Mb-DS_(7HjTEG#xTMrtP3xD6!dq8K`;@bgc43SU@rraK)2sE6ZANW#?|&D
z@B|wn5_WSeU`Z>%z{2Qqya0n(2SC$iXJ<8>Ud!4P>!ZPFx7OMqqXiTo$_9rTtPU_f
znsFf$o!tbvQjOGIC|Y5y`Hgxfa*f0aJA*maXWI)i=w9BM5#qgXU*hssu1m(O27=lq
zP~DE<h6^zA<KT6yWMemPX0Kn#wrejpy{%4XGTTzOTyk5d8<N>{-5KBzA<G#Y$9D{j
zZE48XVf>5@E<(U%=n`_pa4H&bNb+;s2x0*Oy@ILB>;3Jh*mozy_+7A%;6`+%Bi2@0
zdCPuR4j1OwMZj1FYB#OFk|0l0EUaT?$vs$dFn>6~%5e?@4cRp~i@ooXzX`OK8-Z*S
z>hvYEDZDll)OrC|YZG|ZGMw&85;|r#1FTz;7a7}#$W}x+Mf#H$xt+5O5j$r))#*af
z4yr?u0jfihom7V+yQoelBD<*$MFy!3Mee3L6xl;{GKkzmbtp1Kbtp1Sbttlz>U1M=
zFV&&QKB_~J`=|~@_EVh=h}=(gC^ABIC^AZQC~_c~ZCwHL7$vgnkUeh4IuM(%@d?@M
zk(f+o+g55Hq=a4jkR7AiQ+AB19!_T0u2elj3A^gF9iyr<c8sddCbR7;Rp%&SSDm+G
zRP_NnMpYM*m+)=%vSid%m(?b+V%5NLVm0*)LiX2pK>*=rL6q}i_51}UcVYFcmLiwn
z)6w!V53^%R*B6DCQ0zdi+Vw@2$;U<3bD@f#-U+Gk^w0%yyyrrkQeV^_hoK>JdJLiy
asz5j%zT^_PtM)R#@xH9|0WgXl#{M^F&HPgU

diff --git a/src/font/font/fontello.svg b/src/font/font/fontello.svg
deleted file mode 100755
index f5e497ce..00000000
--- a/src/font/font/fontello.svg
+++ /dev/null
@@ -1,104 +0,0 @@
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg xmlns="http://www.w3.org/2000/svg">
-<metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata>
-<defs>
-<font id="fontello" horiz-adv-x="1000" >
-<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="857" descent="-143" />
-<missing-glyph horiz-adv-x="1000" />
-<glyph glyph-name="cancel" unicode="&#xe800;" d="M724 119q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
-
-<glyph glyph-name="upload" unicode="&#xe801;" d="M714 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m143 0q0 14-10 25t-26 10-25-10-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-38t-38-16h-821q-23 0-38 16t-16 38v179q0 22 16 38t38 15h238q12-31 39-51t62-20h143q34 0 61 20t40 51h238q22 0 38-15t16-38z m-182 361q-9-22-33-22h-143v-250q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v250h-143q-23 0-33 22-9 22 8 39l250 250q10 10 25 10t25-10l250-250q18-17 8-39z" horiz-adv-x="928.6" />
-
-<glyph glyph-name="star" unicode="&#xe802;" d="M929 496q0-12-15-27l-202-197 48-279q0-4 0-12 0-11-6-19t-17-9q-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
-
-<glyph glyph-name="star-empty" unicode="&#xe803;" d="M635 297l170 166-235 34-106 213-105-213-236-34 171-166-41-235 211 111 211-111z m294 199q0-12-15-27l-202-197 48-279q0-4 0-12 0-28-23-28-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
-
-<glyph glyph-name="retweet" unicode="&#xe804;" d="M714 18q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" />
-
-<glyph glyph-name="eye-off" unicode="&#xe805;" d="M310 112l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
-
-<glyph glyph-name="search" unicode="&#xe806;" d="M643 393q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
-
-<glyph glyph-name="cog" unicode="&#xe807;" d="M571 357q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
-
-<glyph glyph-name="logout" unicode="&#xe808;" d="M357 53q0-2 1-11t0-14-2-14-5-10-12-4h-178q-67 0-114 47t-47 114v392q0 67 47 114t114 47h178q8 0 13-5t5-13q0-2 1-11t0-15-2-13-5-11-12-3h-178q-37 0-63-26t-27-64v-392q0-37 27-63t63-27h174t6 0 7-2 4-3 4-5 1-8z m518 304q0-14-11-25l-303-304q-11-10-25-10t-25 10-11 25v161h-250q-14 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 11 25t25 10 25-10l303-304q11-10 11-25z" horiz-adv-x="928.6" />
-
-<glyph glyph-name="down-open" unicode="&#xe809;" d="M939 406l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l93 92q10 11 25 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
-
-<glyph glyph-name="attach" unicode="&#xe80a;" d="M244-133q-102 0-170 72-72 70-74 166t84 190l496 496q80 80 174 54 44-12 79-47t47-79q26-96-54-176l-474-474q-40-40-88-46-48-4-80 28-30 24-27 74t47 92l332 334q24 26 50 0t0-50l-332-332q-44-44-20-70 12-8 24-6 24 4 46 26l474 474q50 50 34 108-16 60-76 76-54 14-108-36l-494-494q-66-76-64-143t52-117q50-48 117-50t141 62l496 494q24 24 50 0 26-22 0-48l-496-496q-82-82-186-82z" horiz-adv-x="939" />
-
-<glyph glyph-name="picture" unicode="&#xe80b;" d="M357 536q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
-
-<glyph glyph-name="video" unicode="&#xe80c;" d="M214-36v72q0 14-10 25t-25 10h-72q-14 0-25-10t-11-25v-72q0-14 11-25t25-11h72q14 0 25 11t10 25z m0 214v72q0 14-10 25t-25 11h-72q-14 0-25-11t-11-25v-72q0-14 11-25t25-10h72q14 0 25 10t10 25z m0 215v71q0 15-10 25t-25 11h-72q-14 0-25-11t-11-25v-71q0-15 11-25t25-11h72q14 0 25 11t10 25z m572-429v286q0 14-11 25t-25 11h-429q-14 0-25-11t-10-25v-286q0-14 10-25t25-11h429q15 0 25 11t11 25z m-572 643v71q0 15-10 26t-25 10h-72q-14 0-25-10t-11-26v-71q0-14 11-25t25-11h72q14 0 25 11t10 25z m786-643v72q0 14-11 25t-25 10h-71q-15 0-25-10t-11-25v-72q0-14 11-25t25-11h71q15 0 25 11t11 25z m-214 429v285q0 15-11 26t-25 10h-429q-14 0-25-10t-10-26v-285q0-15 10-25t25-11h429q15 0 25 11t11 25z m214-215v72q0 14-11 25t-25 11h-71q-15 0-25-11t-11-25v-72q0-14 11-25t25-10h71q15 0 25 10t11 25z m0 215v71q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-71q0-15 11-25t25-11h71q15 0 25 11t11 25z m0 214v71q0 15-11 26t-25 10h-71q-15 0-25-10t-11-26v-71q0-14 11-25t25-11h71q15 0 25 11t11 25z m71 89v-750q0-37-26-63t-63-26h-893q-36 0-63 26t-26 63v750q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
-
-<glyph glyph-name="right-open" unicode="&#xe80d;" d="M618 368l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
-
-<glyph glyph-name="left-open" unicode="&#xe80e;" d="M654 689l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-25z" horiz-adv-x="714.3" />
-
-<glyph glyph-name="up-open" unicode="&#xe80f;" d="M939 114l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
-
-<glyph glyph-name="bell-ringing-o" unicode="&#xe810;" d="M498 857c-30 0-54-24-54-53 0-8 2-15 5-22-147-22-236-138-236-245 0-268-95-393-177-462 0-39 32-71 71-71h249c0-79 63-143 142-143s142 64 142 143h249c39 0 71 32 71 71-82 69-178 194-178 462 0 107-88 223-235 245 2 7 4 14 4 22 0 29-24 53-53 53z m-309-45c-81-74-118-170-118-275l71 0c0 89 28 162 95 223l-48 52z m617 0l-48-52c67-61 96-134 95-223l71 0c1 105-37 201-118 275z m-397-799c5 0 9-4 9-9 0-44 36-80 80-80 5 0 9-4 9-9s-4-9-9-9c-54 0-98 44-98 98 0 5 4 9 9 9z" horiz-adv-x="1000" />
-
-<glyph glyph-name="lock" unicode="&#xe811;" d="M179 428h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
-
-<glyph glyph-name="globe" unicode="&#xe812;" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" />
-
-<glyph glyph-name="brush" unicode="&#xe813;" d="M464 209q0-124-87-212t-210-87q-81 0-149 40 68 39 109 108t40 151q0 61 44 105t105 44 105-44 43-105z m415 562q32-32 32-79t-33-79l-318-318q-20 55-61 97t-97 62l318 318q32 32 79 32t80-33z" horiz-adv-x="928" />
-
-<glyph glyph-name="attention" unicode="&#xe814;" d="M571 90v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-6 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 14 3t5 9z m-7 522l428-786q20-35-1-70-9-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" />
-
-<glyph glyph-name="plus" unicode="&#xe815;" d="M786 446v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" />
-
-<glyph glyph-name="adjust" unicode="&#xe816;" d="M429 53v608q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41z m428 304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-
-<glyph glyph-name="edit" unicode="&#xe817;" d="M496 196l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 52q16 15 38 15t38-15l85-85q16-16 16-38t-16-38z" horiz-adv-x="1000" />
-
-<glyph glyph-name="pencil" unicode="&#xe818;" d="M203 0l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
-
-<glyph glyph-name="pin" unicode="&#xe819;" d="M268 375v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" />
-
-<glyph glyph-name="wrench" unicode="&#xe81a;" d="M214 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
-
-<glyph glyph-name="chart-bar" unicode="&#xe81b;" d="M357 357v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" />
-
-<glyph glyph-name="zoom-in" unicode="&#xe81c;" d="M571 411v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
-
-<glyph glyph-name="spin3" unicode="&#xe832;" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
-
-<glyph glyph-name="spin4" unicode="&#xe834;" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
-
-<glyph glyph-name="link-ext" unicode="&#xf08e;" d="M786 339v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
-
-<glyph glyph-name="link-ext-alt" unicode="&#xf08f;" d="M714 339v268q0 15-10 25t-25 11h-268q-24 0-33-22-10-23 8-39l80-80-298-298q-11-11-11-26t11-25l57-57q11-10 25-10t25 10l298 298 81-80q10-11 25-11 6 0 14 3 21 10 21 33z m143 286v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
-
-<glyph glyph-name="menu" unicode="&#xf0c9;" d="M857 107v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
-
-<glyph glyph-name="mail-alt" unicode="&#xf0e0;" d="M1000 461v-443q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v443q25-27 56-49 202-137 278-192 32-24 51-37t53-27 61-13h2q28 0 61 13t53 27 51 37q95 68 278 192 32 22 56 49z m0 164q0-44-27-84t-68-69q-210-146-262-181-5-4-23-17t-30-22-29-18-32-15-28-5h-2q-12 0-27 5t-32 15-30 18-30 22-23 17q-51 35-147 101t-114 80q-35 23-65 64t-31 77q0 43 23 72t66 29h822q36 0 63-26t26-63z" horiz-adv-x="1000" />
-
-<glyph glyph-name="gauge" unicode="&#xf0e4;" d="M214 214q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" />
-
-<glyph glyph-name="comment-empty" unicode="&#xf0e5;" d="M500 643q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" />
-
-<glyph glyph-name="bell-alt" unicode="&#xf0f3;" d="M509-89q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m455 160q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
-
-<glyph glyph-name="plus-squared" unicode="&#xf0fe;" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
-
-<glyph glyph-name="reply" unicode="&#xf112;" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
-
-<glyph glyph-name="smile" unicode="&#xf118;" d="M633 257q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
-
-<glyph glyph-name="lock-open-alt" unicode="&#xf13e;" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
-
-<glyph glyph-name="ellipsis" unicode="&#xf141;" d="M214 446v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-15 38v107q0 23 15 38t38 16h107q23 0 38-16t16-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-16 38v107q0 23 16 38t38 16h107q23 0 38-16t16-38z" horiz-adv-x="785.7" />
-
-<glyph glyph-name="play-circled" unicode="&#xf144;" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m214-460q18 10 18 31t-18 31l-304 178q-17 11-35 1-18-11-18-31v-358q0-20 18-31 9-4 17-4 10 0 18 5z" horiz-adv-x="857.1" />
-
-<glyph glyph-name="thumbs-up-alt" unicode="&#xf164;" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" />
-
-<glyph glyph-name="binoculars" unicode="&#xf1e5;" d="M393 678v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" />
-
-<glyph glyph-name="user-plus" unicode="&#xf234;" d="M393 357q-89 0-152 63t-62 151 62 152 152 63 151-63 63-152-63-151-151-63z m536-71h196q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-196v-197q0-7-6-12t-12-6h-107q-8 0-13 6t-5 12v197h-197q-7 0-12 5t-6 13v107q0 7 6 12t12 6h197v196q0 7 5 13t13 5h107q7 0 12-5t6-13v-196z m-411-125q0-29 21-51t50-21h143v-133q-38-28-95-28h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q11 0 22-10 44-34 86-51t92-17 92 17 86 51q11 10 22 10 73 0 121-54h-125q-29 0-50-21t-21-50v-107z" horiz-adv-x="1142.9" />
-</font>
-</defs>
-</svg>
\ No newline at end of file
diff --git a/src/font/font/fontello.ttf b/src/font/font/fontello.ttf
deleted file mode 100755
index e9ed780311d58bedd808d37acfe47495fbb9ba0a..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 19984
zcmd^ndvsLSdGFrmJ!hVpIdf+8KAO?Y2x%mQG#UvZ#GnVlAV7eOC5*9z9w5-mLfH60
zt{aFKCJwkXD>rRc9sAbty3MNt<U!LU&TXAEanie-wjsIgZC9EmS#r|rLgJ*UYli##
z&Wr>YoUEI*?z(?miG9v~d}lwt{q66$1>=k{FME=SY`$;zy<OJ(V~?TcZKS~+yH_`L
zuHJo|F+PCup0SzH`G5WX?teh}zcMB*jy=3+DK~O&GPdzoXtyTk56+zV-Digx>k2dG
zoIf~vbRP9Sv|q(rJ2?I5<W}L#F2<BH##(kAniw57uYK(zW9tYmeFzomHzX&@=$};|
znpr&l$(L{ZBg)@G`76_NW21fFYO@&Ycn0MIGo#1n`TN{2p-lZ-v!gQ;-}=MKD#q4h
zOyRHQ=Z-F3x%~9wjBWTn%I^7viTNLGtotHk+fD=iDMoA8StILYLeHlDLFSnrU7Tel
zOhP^VQ2VWUK0S4Clxc+)jy7uLsNqamt`&cuKg(o{_n3f@*faSz|2|v81=KQGMD1OE
z1}1yQS!Rj-EF})#c!=#453=U~Msvvjpik@)V@V{8D>UZYcr9uPDv7sl*Gnviay7GP
zhD&8jO-re(=dOO^>i4hy=<3g}=C1j#g|Bs9+jy<#+W55}{Va9k24hRihB|9Q{qWtO
z(Ce@N;TUzF{kxyh@5o(#X-xv=DgjFhQ-M7P=IvxI=4KwyqK_3ZKMOE}1)0f0EX<0r
zC#9^6m9q*~iB(mx2#aE*7>lz6t6{5HEvv)&>sbR^jqYhL{(t{*5dwss0vdpoC1fcP
ztt=H1_JJ)G5_W_w6%x_cQXvs7FBK9|`BEWa=h#vq(V18(B<v<zDkP%Er9#3Ev!z19
zUbCe_!mhKWLc;#Dr9uKFu%$vG9<o$OpboZFNT3t8R7jv0wp2)<9kx_Rpdz+ZNT4US
zR7jvKwp2)<F}74lpf<KtNT55mR7jvewp2)<MYdE(ph}#bd;)#4rBptFQrXqB`2?C}
zSI^}WsFz*+Mm~X#+12mo6DXQp{b4?Vw%OGm<rAo!UHy4Jf!^6woNY>=e0B|Mr-U=W
zu3`R^a2nXP&V0hTVAnS06HW-b){{@fL#~bI6HX1ghOsE&{DA-D6HXGS<i;<BzZE`!
z`BY>|BgH;dVgm0I!O4U<j^;_D%rGQ5To*IET#2maDm~(<4Lm`QbW<g7rH6^;kSYAF
zx8Ilaz4#&?{XTl*wOsakUwqN~i0SM1dO!9t@120&1_B_;ym484L2Lod6=AIzwkgxy
zf^i*8z^wY1LvhS$Tv3%dri$tuJ`kiqnTwdNAc7r9k}!lSp?`fxv^g4SimeU%b-BDQ
zmWq3X3Z8CVeg_RDS`~>WT2kpya|K_=o6@b#O{S>SaWtt>YQiJGN>g~%s4#?ZN%#YX
z<rmDd@FvTA`<tN(Zkcbm($RB~wEGRy`kW(t&hVb|8vI-+Q1q5wp}*y?_Lzd-@=GPI
z<;>HUOv^Iy;FUEsl@)xK`4+w~J#QuPiT)Od-#*1Rz70I|fzHY^C4R3ii85^t&U9gK
z%8igHhw3<R9|#&AZtqUKCDj^8&@*Q5jBJW8cwcRF1zrE~mdkYU#&3EmdH6j}%XNZ>
zEq>MI{c7%Ym($19Q>RqFE@?dURj(^3*W^N>Tn$FVe&pxjz-}Zm)n&zo$L-KmMHad1
z_6fzRL#E#+%0?Y;UClueYHKKvpItOhJ9o}bc>Lo(Jv{X<pIG%bf0M&JL;Bt4x$33J
zcfJ#O>8EpZ{9=B}W$!X!;Ev{eLuwRG;w*NvuFQrAmlPsvkdexj83$J+RgsQppmUWA
zDu{+LX%M8thGY`^{aqQeDi*FXgEax0b_^u}Qd!L#cym!S(!dKuL1YrFBKluGAMsRr
z9dD_!4@tVUs>!V66+Bo3x)NU1wd=3Tih$F_k6^#GOW1^q4%2glZ*<7R5}(j6x-91<
z4K=whQ;n_(VafjBh{yD+E-r`?ckvx&seZ-fx}ujE{E9Ly|G=$Zal5bRrREj&i0sw@
zCJUOFy9iE5-2KL@;*xL~e7gjiP!GG0-Ip0ml?qJSt>8%Q>K3>#(6uUoL#1%3kI9le
z2NdEs@fnh}TsguN5&w=bQJlR6RlphR@823|D6TfjRk@@t*4n^ZQ>}_>@>E=nC_%$)
zO5=Dn<762@MF0zlMr_(_ApX(X$(x%(t!M_8Ow|kkodGjsV3RyNiq_V6qO}5ig{SKp
z+j!*3d-wChKIf)!pXuvqboyRh_mg#Hvaa?xiU*!*ays|C{hynfDrH^tIIEr95gfYb
z52d%9W@7MvKC$YNulH_zAlh=ka?bCF9$w$wzVXzv{2<7Fyw~aTIU9R?$0R<L+dI|d
zNGQ5mTYY?6QLX>vhxN2WQ4EF4x$E1XEal<i{eeJr{m9`h`l+e$%!cX%X^14@#*G^f
z;j9|qSygN>qgR3%dBBo{zOC7+fuW2^SmP476KA5p#S!o_w{TBsrW`y=n7`R7ihPi9
zE)Fq~i~Yl&4@HfDUzUsOc&dRbhS|xvK^z!(Y!Eu73IW&x-#vN$we$GLE0XQT%j1s^
zoIjKiI;PLQcy_vj_r4tDPaixleCX8=E6?Qqpu9Hta&PD1XaCFD*><UG@<ZDmAAcFr
z(>^no#l4~*YdFk$GhGwI{TrcPuG0n1QZ=PM2{XzQ2dIQebHt+N7#2B)C5fOIVRGO7
zyLaBRxvn;11^lXPV$$)5hl2)UV5r!9)eM;i_BKH)z%GN+M&d1rcmh%dPn#lI?Ndff
z8D|UxP#}{^6z~fsO|$}{X$loOQ|%)rtlM>bm#}AX4=>ZyL%K6iqsZO?x2kR{E^(-m
z?+MN2D-G>Xe99(MlC>J$t4*pJ*X2W+Clt%WYTLpk4oxh20w>K|7TO_u)y;+^Ir6|d
zpIA3I`0(K1accKf1WTI~j}jc<a)(>%FZ1c@L5HhDRx%Z`M{zZI%Syf6rP{E?C04!a
zQjLLIK%MJkxx1_YTH*sMwf6ufz!Bk9$o>rREd#6q9A6P+L6K)*S_m>up(HX<VmO;j
zP`EHc1#u8(lMRsp(QihJW3{n_DwjfPn_lkWB+FWgh%+^r!H`{*h(;8($S_0AO%(z+
zz!f9$^&D{Pc?x0>=r=>$<P+cxT+=Rg@892jQPVlk7h@@&uKtW7fPW~?+_#-&=35>U
z^2;o9^3~23!7qCpg1En%pYGnTYfgva2Ch@_+;My;2^vp&oVnL@!~25ixdKl30tBN2
z6+i(N#csSIeh$`;!HP4co8vh20SgRtM*2v|Pcjc2Jd&{W5D<dH)<aA~{6y{mByR42
z)A<12yoT2}%Ut`M{6o1hr<4DovqInJbmqQ?YUe&D)H{A7cjIyKGAyw*tTIzZW824%
z4T0u4>*uU?70kLdB+){V1ehC7L*j=_6-Z6Q(<maVgiMeoPlHxbs1ORZZ%+1XmiF+g
z1N)PkTqOg!c+H4ap(J^KI8~DSOfu|pg-!l<O;$%bo%44|<4<kjSE<Rj>wTLyePusB
zEOBj0j?jm?6&|nU+e=bmd|09h5`Flv&(oCi_dj(&%J4VB4fG-5PXaHxEUmWBu@}0-
zYIbjCSGfVE<HedhZkIz6SXmGX5?DQC092(J2B|1=MI0dvVCj&DvTVykd5FY8f03`D
zE*>oo`6`Pl0|CEgYaJfoKV89ju&Sjs#A8)?f`ZITCyG+>P!YH}WM(Vqj^rSp=-k_h
ze?rIGZ(Q8V%X!7yCsm!h6!8R(lfE+*d;4T{nx|soiC9Vq*LMnCd%C3d+*@xQxirkn
zUx4u3OMsg2lBWBw@3l!#c!^%jN&MGdbAH7e*bep}nA3k^=h#QtpRiXl#m{C0hj#kO
z5lfP#<85HN1C3y@Oe!$wRu=TT1kDlDMgrX7<C5%<MvB}5&Zj`qbi~U=9r%NS=~O|D
z#t<_Mw*d~``G5AMVer9~9=OpzoQZ$>6EFSIb07K82hTq9)T#G8@$k{vBa`F%@82`H
zeQQfgOB}x~%_j8MPzz)#PHj0iOwv`sBIDqnb{TrBU8Yk_?KstGj!l4mt6(F{W^4+7
z$M?%+6;hh|69JD6v*AO4r}3zNYN!5NQKtT>O#M@t=Ck~LD>j_;>E#W-Vt6+bQ@{fn
z>RqzJI@6QeZMX7Id5!B|y$K~2nVvpdCC2kRw}E~4mKLbYD^R!QH{V7~)i-DG!<*Ci
z@hxBGMtKEIHTMHNh2QP-V#dBc<gdT~=C}OMc)8t>`)8WjH}2lm4{v^a@VY;iO2vdX
zY#JrL_Lst<xD|XYlrbD2CUAB<ZMIhC7a%W*tC4lp>R4uNFsy{bU}$B|2f)E<a^EYk
zw&!#{pBvTn2hhS-5uYOf0wf-gAM3-*V-fSbWvo!h9!nsbEzd>4v1}VoIGg(}UzP8b
zxF~-PZ1e$LxRCo^?z?tY=N~1``kXxt8V3^RbHcVfuZHS;n|%^8Y+fBB>Atcw3<bdg
zUcC?O82x^)u(s!DCC@n*_USrbh0(yOX&{~0IW3Cp!eioB#9`RHDYl+%XP?g$rBi~$
z$~^*vN?$*O)26N25~y3NWN0Wj+<XQqe?fs&!Q`OuGv?aNRFzCbTLasgt;PrOcl!WN
z5J468+21job|}f1$sEABG)J9d`|_r!AgF_dj#Z(5csLW{Y|G}J?)J5ft4oW`px?)8
zd98zNBT|e*q~iuj)uq#CRPd%w-hxxr0x3cVEuMhl9GB5|usH?s5>qR<+MG_rNrdA-
zH<>tXkm{*4|CwpYeQnL{?X}x#%l|#Tar?&jzn9l;t7~7IYVDIMbDv#TQQl{U*9dDH
zn;Li@FR#e8O7iL|;_)@@vN&96>Mm!ZdMf?d6ZdZ{uT6Bt<6Vi`@{RYO_-uNrI^pza
zW@UYPbI;D6bX$EYRX^J^(A#%CEK60ZWle6ckziwx{)op($Q>0@r#6#-vcjM(FkuF$
zhS|WmEm8Oo6k*;U4aCv`p8}&HP=$qhct|b?3<wX{<5*aesRpsmOS~Lf7w4~8=JnSh
zeJ!)-gWvj);79h8)9VI>f%PxszHFjC$h%-sPfvdEgOk%0OuTU&euQDn$Hl+I|4n#g
zYt}I^w6TMIiTxS746*Pbb{g(35wkuAS|Nw!-?6`eRyE9avMv}(&CG(UNGA&5&+!lQ
z5AkRC`}t%17@y!{5Cs3kz7IDS9GrYN-^Oe3y~Y&&CjWc>ZT?OE75-)3#+y+`?qh}Z
zm2S=I7{9wP`e~36q3ug(7m@qFKL?fdVa*(4aki=S{~aQRhwYunw7~RHMM0fmsv@dn
zV``$J9bp<5HGTvfYZg2bO6?$COdOIWcz4A9JQ_3Y5{Fcl#R(iwSx}Fl4>{k5obN-v
z*@rCW`;hlw?edn=-*V*P;f=*W9pm5QSNNau&-45E9`={)tL(GvGwcHUG<%di3}S)z
z6lB4XbPR{n-c-Tin;~<JlPXT<9F}c56ej~`16SfLhMI~iEe#T}e)8cMwcLm(5f!IB
z3azraL4fLsY8XBWxByvFWcw%)WQbG5RZ2~9wUbAw(}YQ;6V6YwnMyR-07{5}F+c*{
zqSr*6%HTTCWigaus<6mS67Mj?T2rBfqBc=iq1I3wJjzrt1AMBe6@t-fs<!c^CgO_O
zO#M`1Laj=r2y+xYMU)mmK_^Z$2w-VwtPtQ|Q=2NJO3`dWub6#n#CD88nNGK$7d&Y7
z@pPyujpbrlh7ydX$;F6zHKKaNIHo}51kDQyOA6~S)948kHd`wMU?<&bf-7`#*pw{|
z<mt1q(FBMHW&>}7NzYa@9q;79)^wESNC<9f0YXGrn~=U~*q`*z%dxT`@YR4_^YVB)
zPIyl%L9lHY0=5+Z?i?}{lYeUQYYPisTl)H}^4J%70InA<NMcbi0QFYT6cJk{NxG~^
z91bTgiV}Vlu0Y+C;oX5<t+}{dCW!(}Ees~84uC+U;X_@JWVa|9o`9sm*5iWg5O~0$
zNP?{Bq9%d(Ma6;Mps&gz%tMKLRHs++!I7ZBmxrln)UgP4-!F?U7ls#H#igPm%K=$*
zN^U0xQY1}sNIRP(GBZUUh6hQNC7LIOgvqC?s$Wta5(X4JC<`8#4}w<{(X|L!2$78*
zWS1(4n&?nXMUgelCmHAmor@lLt#nx{(gpl*SwKk;T_Ws2LKRqm6XOV)A;Q2UQI3<1
zT394fSad+ha?ve#2p#Z%3JT@`JV}zO$*K#^UI9ihCX26JlHkYZP}&`Wpld*tqNuXN
zrB59lfZNQC?}NlP2+^_&+y+08N}8l&hXkM+K$wW+^m4(WbJ4%}%1>Tdv>&;D;2L@Z
zuqZnLhAv=>V7<UA7ZjJQ0Iv{n*j&7j7cfn1I986mRz*$KRY_K47m)^*=5hcjGL|3~
z38F`%deMPx6&3E0baW|WS-PaEDtE}5ssTypANIBc_m=3PW?6zEqHA73BsS^+0we{$
zm_+>!P#U&R@#>fZ>^G0Ya0*;05g=tH1=g%6`ha6elQoGu!*1DyRZA|-BYC*)G+?XC
zKsolpFY54rkk3-L5iIyLKhZDdp+kS6tpvV(vX^+V;KZU)l)@f|M|P0Q6{rT%aZF^v
z3mQQg|6o1B2LMER1YMU=Avqmb4;C!wShIu!0Td`4%fpAX?|9_g!Fy@V<V2)%08|UQ
zsGthig^@-uL{HR@T&5K{JPwy2`5?TZH-1b!Csu<ahgc+2Y2f=JnRFA_zg&O|tDu9I
znh|m>#Ng|VfK4Pw1A(Th!s(lU*2-V|^y52wdUo-l$A<WGR#ooLja_ZL)7t;-C$hXI
zac0;0p&|YYYrmEI^VUHF4Uk$legPfi=g?WbtO^Rnc*a$V{T3XUKSCLK-4iZAnTT?R
zbP+NzF(IIJACqO543#s&fLyrOL`-!B0^3Y}LTQLVVMIDYZ3##V(x+ljr<UzPveOcQ
zRDvp?Nrv*4oCs$tBoS;iuuiZ`_@>wC>a?oc4;)oheN4geaj6G2m&QM71RW9o+b{Ve
zj-bIm<%q;1dk<^6uHnI5--Qw^Dc~SCAaWJK_x2mnXptZGQ#302iwp$mY<mRA@Ix1i
zvQ?Ql99VE40`2n%+n|A>H*j77C-7cHn_~#u&_XB*lZX~VF|8DeA+MnAn<7V6h?GOo
zZ<$M$c?62zlC2vqRfLWpLxnF>?d!H?_<Et1!Y)e|{I#STK4|Nz2CK`E@@AtRhX=f2
z5HC&^0vGxj^B1|Ckn3uZEC=giMG*=jtw?kTcmAo}&kP8Irv<(&0^a<>ds>8%UC+Gu
z%r4$|tiVAQU$K4hSndxot^$f!3ng<iS2Mv(=Q<w_ipr!EOf{izBTfn|8K{eyM#Q)z
zQH6_!6lgYqiNe_@jc6z+GFKi1aR~AtXoco03#1=8$pR|C!>{w3KM4O;17+6SIycx`
zM8Lm(a>Fo-5R>TXUf;2%VO6}M)C>R<is2y4wkDwN2TAZMq`x4dWxEr|Zwdbq_7e>t
zad;?fza=~ra8&}(6hl0U0AZpKh~!r@_hwpn(BXK+;YX_8-<@kD|0RDdTHz4YQcZWe
za*ekCh`$y~$<<o8^@ZHiF9>tZFEk@ud9Ux4jrVS>O!M>0UF5!eINw3nez50&QU>mt
ziWU0Uq@o9npL;&{^z*zS^+L+)z1NonCMb@%O~m@hI%d`EsSFk&$mOO-g}5pPLxl$l
z`$#@0Keixo+d+p%9S59^k%W@?F1)yOAD#BCr8q&ivIHBF&D{w&oI&Wg$_j^kUI*DA
zilGA0%>_iKq7k?&gGO@`%>RUqLpTHqSo~(w$>z=cL6<D$zAd?-Rjd{(mU4|t;x^-f
zr3Z|4=1HUZWOGL!M4y!VYY9)@AU(8{Tm3r!gYw`5ukQ;6PntIGeaN;6orpY-eGaiU
zoce;xt(2*n0yeC&5wQP)a2SEc?-s%Jswt>2e>(^rO#;Q)gebw`9L{*^lY!<UBhnm%
zwgo2{IkoaGu%@(3>_fD@-wBbp2Le$=Q0HuSB-)C=-d(y+$3Omv>G{`kt}EOt)cq6O
zJ43DIwL(KFpD3@bEpHv-kGw?;j~-uw!z%YPh)6H@8s6SnUQyd#%ad(Mp8LTpcpW&&
zjT7SYIO7`jy@54n8sm_GH3>fiIPIv|hXaf~gQ7rTHgI*YLa;+VSr)Ai+rggY5Or+X
zAA}fUEFU<5%e57}k9;@55UICK=9NNVnos6V2HVYb!61La9OS?6E`9HgL+8&QvNjbv
z9Dg(|)NZZv>MOzMU*=94#(L}|f1-8pAI#X+{rvoE&kBZ5@vHk!bO^=uhV751_~B*g
z5v)f97jz>gA7qo6F(&KsoR{mOJ_l_`6X!h8U9~wU7RcuuP>eutb8d(RN!f*$BJE*N
zPPX{{9*+hs0c<ts4;leak*CP#)!drf<<uRLim<GVfy5#oD3upQi|iDvDr&ZKF{+t3
z$0yDTul~o=eZn`9#lPGSfS`)&uLvF2Piz+VzWoM&Gq;<6>dN&K2m#Zcti<P{xEayQ
zUdWVGhM{)^{9uow8`=Zw<|=RE4plBtXCtO7LLZ|jiwYv0fjOs0x-tS;MMO+;T4e}>
zJ4Tp>`GaIz@AyP>X!Cdb&f&0qMUElnP#vIR-};W0W&<2A8aJX*C(-e;?1M=bUy*%?
zEa!tbw(JZ2+K?b5`U#g(4VU=|+s%GL*Q$|0Ve@jZu(so_!NQN^KO$dy{-YB_`zQ3`
z-xx$Bx%!a!Q|LPhHo&qOPqhi_N9gZLk)=?;Y#f>wY4<abO;GNnBXAF~AqAE-Jadvn
z5eL}~#V4;FMel%y-$au4-Ta8csH@%yMcrYWggYV`<d|Dk!&zVNn$<M}s|F0eOJ@n5
z&}_#X3|w-^RU$6k0t?y>O@~y4LXMrdUeSO<51olef&^#BvcWIOTJ&%-XEz`+3D+Eb
zh$Fn3^EXaDG}GM+KbtfdkXxE}-#517Y^vQMxc<#)=u*4jcWmt0yN@^9jeEuhHutuy
z(*)<Q3axskXYT`(?|o>t%kD!Q&U7A`{T&S^p#Q+`ovYV$wykvp#3s>U`u<sSDjl2R
zH90BYWL4bRMqj^I(|{NJP|W&(_!I0^CELjE$&k?j-n@qMo;>CQU{1gw-2vdU2$ev8
z2By8p31C-w&6!UQXAq&QvWx($<dxDg64w9&6rT;5Kog`M#qwcjLy-pZY$THhgMNXj
zHGm-}1qIY7h{k_*-_F6FJ=2E|9^TPaRi(r{CBEh&Q5T{-7JqjC{W&@81zX|OLUnxe
z{f|HL=!sDRJ_2A%j%kX=FAkSitnCdN71oZfJ-aXMTvh7hMWR>P`xnCxJR6VY-tb9^
zW|ubKUmXb-@4N*zSmmJ$S0uZ@IF@W$-oQ>|0yR){iy(Gd8(@f4!QLRbP0r(LuyR;J
zH{-Lw5ReH`M$%v(Qxum1%q8I+#GR#I1A9vhcQ~WBm&RJsv1SV65;vgGN<kMg5#pip
zt%?wNM9e^ueG6zsY9M%?M(77x@`?`hG->&{M^>~Sa0N}jg1GI0+y`Z!l!1*qeZXmY
z5bH4bZaePFUxV6X!>#B%2nFl>?<f!=xbUH(WDqE$56eutb3!&(-uD{D&9$b?YCjBe
z5Z4Mtf&W7oj)0BccHg0;)hKSZZ#y6smB0dVkHU6)K}E?QEUZu=!(|y0xRvG2y%`Sp
z2Xa@O&b1UnsNJc16)kvffA{s*Nox~A-TUF9bzwdAJ_4)I*JAdy15QNrTCOi)aU&ar
zFum+8w;y|jZ$d+-=xJ*vRfBM#MspW%{{hB}EYaSQj{+-z^d?J;V(*9;Q@AePY@1<Z
zxR8lOHVf?mar8nN;WfKVdrIwgdD!s1{jU^R@`y!toatRlM{WsSao<XWiW}TRK`X?>
z5Wk{j&>6ohzK;6@#jK6hXKLvVorryaZnb=bZ=XNdR&{%`M0Ym|m~JhibBd^TGd!0z
zXT!SS3iAjgnusTY{n5!TklGSrZ?7VdTU_~SzZLH(70SA5Dj)p36;7}E>r{&?QsEX{
z6-5>9i1K?Q{%8kps1wrw|EpYYo+kgS)O@-%TvEnM%0fL)m@g*>%Fjm=4nM?^?$^r1
zi7roQS9M)GZZSdt6P+z#eRNllWP1y2ssY&9qxJC0q;Us9ttL4j_Q5xRY1`gr_#?z4
z4oGlZYC0l8mLo!R1h-ce!AasB<8WA{cn>k1>;2pMd%M<mtZhp*H?FR$O~eDZk>kRR
zNT4bL&t^JcUvk04LR?D09Uc3=1w5<7%W&lo_g|3DUxXo;<r=!>gzcwbPp5z@7*}fB
zlR`~rd<IEsNH_j;t>`N$6WglNnlm}H#qB8Jdulssgk)Po?!yI`Ul!hfvO_oC{+Xfg
z7&!ICuYd8>z?F(x8<d-c%+>FBN)(sxJ7Th`u0PyRSCxq^gZxe*v!n3-$KQRTsOa(Y
zlb26#-+uZs(GYO_kd5OIqHYH=t`rH7xDzsbS)zw<T>xmzUvDB84~|D(i$@rSwD_CN
zq{8F06pZWPOc7`6+N&ewr3jCOcu27Y2vl~G!H^SBaOvE43Jtc&jmu-$MAh~Lz#$9m
zcLVPfHjslR)yXZ`;%_c|ZIKV$)97`V+}j(r;t`aE<A2VdeCo%i6ScFyS6VG<9=NFB
zf0W#YYWP&|&^UkU$K3bhr-YN+PxW^$tSW11Zm8}Eit_eTA3n7`_k+<FN2JlXCb{6Y
zf#K(sJ*HMx7N~7HKZvH07ngDLintdtGR2<DI7{F?162vOlZP&VB|v5H?TItw@gTax
zM+y@V^iZJnU_x-@NN#Ca>#2S>a9r3B;2J83lvrM*&>bx5mkdE$Hp2@ySm;!McL>6y
zGUAU$=~7l{oeaYdF8&CG^_$@A!u?U){UP^COQ!^vc`K15N01Q_$B<DW^6QSOM(%H~
zaqz#aEq@EKxVOq{%NiQ1{lcj!*-BdS;rDVg(%3kxHC8zstJ?V=m)GK^)VkX8aMR%a
z)7u8m`*bHbNF?agedh<aKQ*!ohxew8h{7|rG1EymeJWuo34Ne1EQ#63A{Q#6JK(J<
z_%k`0=$fW71Z7<5Z)u4(o6+j1CYRZkpBjbv2eyPe3HF2BU05bWBW^3e6?Ql2aVdX?
z;G)fWFXjuEJm%Tk#K%TrvX^X-OZF`V4iM9G$>1B_DL^0)h$rFxavk})$o3<KYg>GX
z8G|SgG6rGTVgG0-us%crkE}ZZadHaiwj%P|AlpLUzT-sEc?yMEAyZuH<2wAtaHVt6
zh_rR?+0%N$upGI6MzFrzSr!&f@Uwd>mLB-1<o8Ls3tFuhuWZ|!X{_)o=RBs9S5WxA
z0#~oj{nJ(^ulT_NtPJ<i)2y4})){UK*;2v*N_E&451HEqX{U0?ZB^533q~7aa8CDc
z%!ER?y%{Jjwr^S^hC!MhhzP15P~Qm_2ttd*I|UO)IZSYBN;L@NOA|ZA*5&vZylKLt
z!TGZbhU6(Lkv#qTw|J$p5?}7e1@6eL0uTH^`~IiSp5E6Xw2hoP|K0=Xdv5W)ao&xn
zaIdGt;qoQh+LB(6Uh<;?dn+rvKXK2gkv97F)cy`BHTlH0TitXd)Ip#pah{`WGOy%W
zU}7*l*bKbXj3b6>M<`*c&{a**cGSl14k7}EG&?eEL>bmzfHSMIG;9QX9*4rBJgO4c
zgHiv^061c1bUcYDAZkKEVH@yydlcO8!l1Xq<DaYu-5LhJbnt`1vr|N*bQCX<CW;@?
z&)+QQHSmPPWpAr5^o9Zh*FC8)xO9XYM|Nc1HbR%(u1%&~DC6c2+^{#x`748PMC84!
zd3S7i{*|w)v<rA(0k&KAR>-^L3H>+Y_qewl&qP-Y4{kFzx<?>zT+u5Is|sTLF5CFH
zHQ1U?C1P<USY(7u=#RI>=mz}$+-pVEeurPXEy|P9YxRrHppp9n!-@R{&Ty7b*uI=F
zyO(XxY~8{&wGwxy@h4fFtto;$!!<fp!`~rjq6Qyao(q#h2aI|iQ(P8!2z0;_UY!h9
zZ-}=9{0<oTtuWwi^}yy!B-QMz4?wJ;nn7HMhODDR7tUcG+3lSIbTB+RMO@sJ5PpQP
z1n0nRoHNUzJeC`K4d3%Nr+VIN3LmU=aCm<lrfgMj*&N%`RNEUsqfy#k7WeBOgjW<_
z&|6#^HsFgBTnI!$!hNJJL-*6|{&+I?5xX}&Vc*nhj28QABbCw0U}vI^`#s(;6jr+}
z6J6uiBSzQ^M@*N$By9QJ!K7(OE{~GIev%&vdIan#AN*o5Xx3}l-)BP2s{~bpH6@e>
z-7c8+VxPoi@_v)ltXEtJQ6dlo$02T!$f_V8K}V|0puefAHl*Wjs%tZJI5?)3WoPo;
zKO#WfDp>hag<0108^AFH;0I|Wbq~JP)GZLGRuTS4qqU`JO?_QWqB>GpQCeJN6hSk@
z>b$LP+j#{&dF4b0V29b>tb$QcM4O4r@3A06mTkMqD>?qO=`novEwlGaZvN4;_C-0m
zbcTQb%Pr+Pzp865AolV|{w=(e8$?U)<@`RC^CegAI6s|p<u9sv_(tr|CtaU9k@w@!
zlUM=Kwsc$!KzB~!zT_jB#bgYTUaQiB@MTj#K&Z11jxR_n#4h39W4c>s?iuEE3vQ<{
z1AUyiow|DjQ9dXinjpdvCP~^L++orXqJNr%!?|timd(9A8#knz*Q{PuQynQQ4dEt~
zj=(qWd1zi+-GiX0K-?K5jH`s;cX*JBXWvF%iTEIrg$v2HK+l`PsZKdWQ31#o`~g7-
z@*daRhu<SS_POKADgGC)*mp%=aVbYM{j0VE49GfyOm3pK{9Jr(F5JCKa``Lb?Nv@^
za&UYw>2%)Hc%r<PPrN7lr0~RN9=qqxZ}XkzUM^4a_myq$u4wB@w?#?>J%S%SU5h^y
zBO}mGb^Cwy*AIF-e!-lg{YQEK<KlmNFE!H}QNaVi{6Q4Stjz!5JNxfKne;H)e=Z%*
zOQAw5y&9<}5p*X$e=|v|k*E6SZ`{Dy%C}MZ6E||wYWx)p66_;J$%aFlfSF*d>vlg&
zH-2IFNoD#Dec0_7LwXox>URlkuhTo<ZjZB6C{x{Xz5Tv%-Tt2DxiUt+FIoiK!u~t=
z^J)Gw;YCpqzb<W&o|Jwfk1L;3{zbLaJz89wbuGBE?v(op_hrwgy^Y?F`~1GeqMD+d
z|7;)`I1{*PjKFvNqS<MFJj6oJgnk*`9)7ksU7RcVWAsn>Z{)wCzedE50NU<gN0Ixu
zbKb6j=b8QE3T|6aM_<rWgY}rt<FA5X>*Vuvt*fVy2hp<rLSDuQFlFp-71Tdi$m`Ji
z4i@rG{LSKd5*3mIaEIB)3VF_a+*`;C48FrcUPOI;Aul1nzL1xh$+s2q3hKuSd7bUy
zM+<o;i}P=E&&@x&Fm>?IqE*wq%4%v{v(`HBs5OVGsoBwKYxLOSp}B>lR>qo~n_Zlk
zo}OzMo10nA@0vJxY<hHIxw!n^GqG@VYHrqA)6lqF)i*Iau`s$gF;2Z7efVJ0;^L$=
zxiB|lZ7TF@%`eOyo)}wfIJCGp-@bbF@|QFol*~CckLywkYznA4#1@$aH&HkKyv$-v
z_;c+w5DgYP00;|^d8jEi%SPEWYDU>Hd~wKbIf`-yX%eMb)J_0m8hHep@SM4$eitAP
z;`?dzvT#T1JA2s!=mikZl-;|9c{N~m?}X|D)T|9TYR_OC^U2TsC@zE@#EKU2J892y
z!Jd`HHrYMioUa9ZP}^bjGY04e8!L;bZHJ&-eaD!~>jPCS^AF<bcl&81)g93_1dGAU
zHSXX#ryDG8`&twHqBxBKxQ>GOYmX2Q^I~4YOL-Yx(1$r@@hTqSQC`hsJdSG+HMneC
z%j<ZO*V~?-M!tqAW23WU6VuAE`RTdQap~yd=z>m<`iYtO#YfeJiN!}ICKlC+M<?p%
zCMT7n6Qc`bhs3eDgUa;W!MS6Lj`6uiX6xtXCuWt=#l_LFL+bq0*y6E;3Hjlv@rgOz
zCTP3eF+DN4Xy?>p^Y+_w090JRFf|JTKUhB};lB_#A|ITdJ1`+1SU7g{kOMtV%r1g{
zrTOV&N0rg>!^e&;N)zK#iwXuDo0=Bqr)HH$7LY&W7&|n&uvmX!bU}S=Zf>T2YF0jq
z)@J*e(x#_okJL{bUvw>J>PM#+rJ0G@W7^E<)HIdkgQLd|PPjqjs9ww?K(nWaMi<SX
z{^&!;Mi(Z=W&B?S(~rtWXQrkn+%#)@In;-S6-~__ojMBm(MRjYrWVGgC&t~2hmOr0
zI9iW&14chEH9I#(r}e1g*wKlFdg?mA`>>ty_x|2HNFmJZU-2Nk{zzGtw6g5pq26K3
z+IpFJc5cloyY~)dQ>EFO;gLz}%)LWdAvXG_8d%%dSoA<?RaKS^XIXc&=OVaU_efWL
zmM62;$Ygz1NLu4o_A3KfDZck&4cEJS$9i|&KU5X1Dm^n~Wd{a^s<N5kQY+g=xwhe9
z>p~uAbUa&wszS-iHd1>dL4IXm$igJfj9PRJVgxl7wds^gQ!YJHI)dv2rCDA#JRHrk
zfuV`v;rgtYw0f<q6dT2a<nDo?tQ_siD$y=Xe>lrW>a$WZig{V%7vuw77PaIDrzhHb
z$FgE=73#aKGu9dOeW6i~VOcweMg~epcMT6khtZnZJ%pxGT2x`=`mCJHs@-)LaU5;b
zC@4m|qCj!9Ycwkyn9TAq^q-Y$>$7UoqDeZt$1Y3k06;L!%*Zf-jP%%()RGrfC+qI*
zs;yc<wj+5v^7VW#ybe?7#)?O*-ZRlr+6Ws{tdy{swMsF`<ymIMSah@}KZNsLKBtIA
z79X!H?beT6L^IK@i%wna9jYpgRt?uy)o0zw3xd#_9Utwf&w7$rfMsP}-CJl~$V9t_
zvu-NwLdlI%eb$R^d^SofU|<Z>&U(5>tTQ84)&r!~XMM@7gF_dj@t)!8ta~DQygpl$
z+`4mU>+XDIX%*@NcD+A&fqA>{9lGH4V*f_Fvfer(Qjl)f1s6Tscx1VWoe*OKLl=m0
zfvm1G*j^g8wknFxmh+|gcKQUgPp!jPNgw9lH-b&PZL8j811~TZhyumkS=M<GPGp-J
z0!dsD7J3JVvfgNy)thyKD0K`x(q)Zc_|N)$_;XR#)pcg%f?ui2F4UDqfF=WL3)IzT
zgUJh=-X`!&?@;oBNbhj+f<*7)<OP}DCCLj4y-Sl9RC<>sFKF~GPhN1)yCTW5?z-P%
zB9)j(1v;=W5qei)BJ_@6BJ_@8BJ{4tMCcvEMCcvIMChHsMCe_EiO_ph((173yf%sT
z`$nv8!Ub*GDBjssL{xRjY;9e(7RMzCO6>zl-SxagN86&rt9~s6=%qeezp@A1%qDBI
zJlJSsx#3o<+}^Z0X|>q%Yy=GNz4N$&3*2>DRL{)M*gUwWGun1xH4kDXYp`xi^sb+b
zfNDqE>a$JBhEPX+w)r=Kg2KiCp2AizGiEhdeRMK`!h6n~>5KM(6AXc8gXiJ6Hsf#f
z42<6b7Gi?>@y`ayI=bs7&NM_VtK$s%O5bd@8uEQ)CG-g>D?37mIkR)<l3<Be=_Mg9
zmJD|hgVVq&F&;*YZo<*RF5Z4laHx4*o|6mRBjeGm*gZN99wT&*mLfkgjDz{kZ$>dC
zaNX#p(YDekM%aXP;bo74{_ZjeCPJK0!s!O?We^hh_B&>R9!K$U%$@|E5F^CGZjJ>i
zX(b$37+sDRa1iYPYTE4TtcKHTS(~DLG#KsHS{r4wfC5I@;826r0l`NzE>xnkn<!Ue
zDBXjm715gCs5^;kBwE-R%&|V(UYJ4m^45%y?0x4Fm$!0VGHNvt);59bb__RMfRi5w
zuVbYeyLq#E{YteBz5MBIFrCS4OWktIZJlmNX47?NKtn_<XK)<v7#Q2qkgdb`85><h
zfXmn=;)>!_G~kfr=eQBf0t|YEQkU2Jn@O?nZiMmMP#@uq_)15#t+eWv{jM4=%(07*
zv5eGiT7MN`o~Bq>$I6m>u;gI=a6**h90nS)Yj75O-=%&Ncr7;q*(SjBC9^5KHWSu*
zfmdr2WY#jC?nx3kW;X+^Tap(U+lb6oWH@E|lNY&Nvke)$W;?-jp=k%fP-cK&D6^Ab
zD6@-TI+58;Fq9c27|PsBFqGLtFd1a-BN)mI5e#L935GIz38ovF`w50J`v`_I4-gDx
z_7ltoWF90K%8U>UWkv~xG6#~`))h35Q6aky)#G-q1Gx#Co>09Wg~?>LZ3X%u6>R83
zc8;K@>>Pm}PG;AxfF7ZO4LWV-2z18I5$J3(+r9!iM+F;n-p&!|Lw1fp7m}Cox7Ev<
zQCD47o5+gQ1IJ0!)H5jAU*81<gr5ad&a2h)7nt0I)w5d4TtZAo%jZ1I&M94A5Wa$D
z2Wr)>FR)BLFS4Es)%^5MXpN_bE{Nkj7vfa<g7zdF4VlwpP@Mn*<#_m#OW>~B%lyXs
MveE}2D0&$C-+|xh?*IS*

diff --git a/src/font/font/fontello.woff b/src/font/font/fontello.woff
deleted file mode 100755
index 1d5025d3cccf300b56cdcea3fb71b091fdbf3bdb..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 12248
zcmY+K18^oy*skB$wrxAvH@0otwvCOkv2AC=jkB@sjcq$Q`TqaZsXASA*K}X=+}$%h
zRb4Y(kB5S|H~<9jU6@t?NdKMXk^i^-U;Y0t5-MuK004;QHwAu6l{lKhTS0}1^_wMs
zw?)3C&d;RcX<}#O_{~<n6#)Q%yIyUpY?`>c5(5BWh5!Hn?OVi<U!V8pjuv*`%oPBD
zNB{uf9HSBwv@DEVzP&Ku-!ahtSI8E&UgqBn9snR^1OPacWk92pEzOKf0RTGQ@0joL
z0i2jOLyeZ-@SA=60mR=ThZ+Tkv$S*d{AS<%15CcvTix^Z&(^`@zqtGFw()<GnBXS1
zGxGfQ(f{{s#QzB-458HC$j<DWefvQ?007X*i5wylM+cYh`(?QPX13o9VW}<B$I;pB
z+smZ;?eqRt_<;~zr)Rc_p{b$awlT9|y@{c-(;bY__P7-Ns7Zpcp-~_pftNLlg^G<Y
zF$io3Y^)*B3X#}6<R>VA5(ps)_#bcXD0fg$N>ETmlob*fm<Up+xo@xG$Ot&PnW33^
zFD&Sst_n83KC>^YQQW)12=ZX|6f+X_1auM@;K>!9>AR23g;9AtA@s1Ft)T=MR&Elf
zKtXI)mcL5-9H>b|WKoUVUe_)+nDq6oscQ9HJd00^Xkl(04_X)M<C`^aX+dU&qj7eC
z4~KhS(W@B?pUoEvLXRUT`uC5(Cw^Q@nJo^QCTFeMvCfTDF@hx}z8VWR?dh|3b7v2z
z!at|q$E$<Jhs)VZtw6_zMeEn=z`xqlH!UdtA`HUo1PT93s_MoemE4<bEO@U`x+Skk
zJn>Gm5V;o5VNRfbmEPmZQx5-iqJr-sTm=5hhr;vNia?KZJk`fm>*C{V-eId#mZN12
zwHG3Fb4#+efpiU!Vj7M@0g+-FnL+`rWT_~LDn{Ao2ZaIw#WWE`T3}L^;HX>vzPzF{
z3w1j|vM!4BB@9JcSQ4JdXmi2-rJ{2Xb-P%yZppH|W1c0dF6{wno7Nb8oBo)Y<8X4-
zb|KAjpPkBKUy6!-h))@P@OT+w$TGkUy%V>*tnVz?8?#fme17Ir<kaiBvS2D?8Hwvx
z?=u%Wbc50Vyh}kaROFHDPXU-v5Hv)vr*BrHZsjpAj!xhul^se?0moo%^!Kk-y*Q<9
zUm%Q7qY1HIc1=otG&=C4`zCU0+u+W0&E=TOU;7G=i9F%=kz?ksB9EiV#ffz@RPCe{
zxU<CMET+MQCCb=yUYL_`xp5_5YM>8^S=8tjgc5R1>RKy7gs3Q~VsmZ1DDoSjR;H=%
zv6A?BOR<LB7z5u4qt2dCm)e#;?vJqsU_r4to2w;XBZV5aTA^eqpYmL(S@|oS6kTUe
z|AfY*V*gKhQmam!p&7}4cox6MEIC^CsH1EfH>j1#Gk1j0y^<GEkM^Tr;K!gQ6K==K
zP!0y(e^##)WvkcAw9LPEGkKMH@CY;*I4HI33lfdQoo}DJQW%8l=ooGM{&hjEisHWP
zA#A&PuKHyjf14=|)=J0PH4|f4;TwibCr~i)NutG&t?-g0QKa3Nh$eJqCVwu&AGEah
zlg4QhTWe->76xVEBZ&?EF`b|A{pKG{DjXW9C;qs%1VuJ98jX<)yVM%9vmu39G;PI0
zZ94aXoAzIwe*cTL*|aIkJw*>=UL7O?9Ui688%yP~4B4DDsmUT5gu-N3O3#TpFlvE9
zDSbN=j>r^=d2aKeP*GA)h7B1NA$nj#F%9@+5l&Y-UTM2KgLd&K*uzHA)BT}(Tl>7q
z8sdGEcz}AD@sAa_`4g*srW>oZ15&Y**ApCn*<GO}P}W2S&Vl3*D2ETkx$)&x{IF%n
zmCK7ex^llI(d%N6H@q1_g+&?|*|2oApX!3fH8xtQql?v;6(b8CBAM#l)$`y{&iGiv
z-7!ud25fAp>8RaXk@HPtjC?K^GSKt*zFBU3nadS}&LB?aUh-vHsaI$0!?$2Lhg!MI
zC(trFWkvJ?3syk~5<dt`yoBH~UAOuYqM7$W5fhk0OTa`l^@B<1G2o5bp+z(?jiARV
zu={UKu=|~H!s|P$%@!9_r1sV_G$Cz;4UOXn+b6^htZCa-h5uNpT8reO+NY?ZPge$*
z<?Hj_hV#^8N~Vou8n8^I#ETz1FJUk^-~u&z8%`aF;vRW!l$WBz1R%Z^msBabj3)<v
zRbAtS=h{+La#nb{%B4-K@pXI}sH$qh-Q!7yX;P0h+B#a0hQ|NM3AfHq5t*=^;Qf8(
zHlCR`@WI6*jH&qX%!Q5%w-F+*C8c$-UA?%x-nan_app#&mtx_%dsTlXC2N}VPt+RA
zjrS#Vgou$gTd@YK!RTD2VnNFIM?MYiq@%<sxwbL|9*KZ|7wC1D15RmlT8XePzY~t|
z3}Iy7=S8Jk=v_n~aVd6wM_v4`Q?@JmSZ*29+q8;jRCgsj=HLWR&i+#353h<*P=U>X
zF~hEGUk+(7wAx`OJcM{pM(aM%SiL(ijQNz3WQP;#Tl{mes3>nqzfit%!`~b|?JB1m
zqK}w^CBSFP)hS`@kM=05gdLblD{hL)a;C_hTEyw0(nHtYIU0{mrhNw$p|`<&<$B+s
zE`9x@9paoaCDy`&Q5Ztu_+W|RsTsS}RT~sGCicvgVpQx`)$59HE}dG~tP}Z`{6g#O
zks<s$(n^5D1K7-y$+0s6<MdM|TAuy=j%`M5A=X*hF&DE<O}kbH<i?CE*e;1gO<da;
zf^Qk5lXr1D{t3yp$tK1wNH1mJ;5OQ3bIhR%py(xrhrjn|!f3`6F@)iedBuDRw-?a`
z`<ykRkI)6%w=df*kSSw;`1|Tl9{5~lDuCx0KBNIlMBnm+(KUk0C7ACxJ|v3^Hu;26
zecj0t^8|zbj9z|=kXk;Nih}0Gz^<Z-*~n)cR`^Gk(8OmSap`CI$z#GR8Wa43MM(N<
zw2<tF+I$Yl1D8Jb=@lf)1JXB??ZF;5gY1d%O@oC>*0be5x{7>4S|1c_2Y>)TzP<oR
zp4o#waE_|W8HX>^JWs2wTdfYQ#h2<;I-3mI!ax0kkqf<GLjWMGsm#=@koaUFw()2{
z&M{Nay?#6>-k9+-IqU5Dy*BG*ry;P&0eeDuvvcxjkE-m^&|+4gPS$X#W8;~W8D9ng
zWah`$Is=KZPa{xGhpyP?s{P1m`epj-`SVMF+3<YG;o0Cuq0v6I+M{?KG}9Dc9Nclp
z0;CFQ-Dy2lx|+-8O-POjES9nT7I$R6MEg7xR}a5B?#FxGdr?q#@7?sC8N1+9?_}fF
z*ZbXP1cCSS#?kwg{qa>9r0i}sW0Xr_zbmGxjAo8qWnJC22f~n(I@yF`tQLW)T#i}h
zjCliC+8!))HY1j8rqn3aH3pF$Uowr^+~E0`=7=h4*Q)c>@Gr%h$t|*$%oFLO+Z1OG
zZRvhmYIU2ELb3(@URnkbHBYPBxt+B0;z^T>28Fc>WRKkjIhvz2yjk+%?A99VL)nf~
zzjdiD<G)iYpKShkQ=$WXRpxV@xTlXiyveYm?lzx0k#O>~2ip)oe7t}BsvmEU;=|us
z<UcpoM__#~=z3wCGYWNU6z$^M=PFUObXn>DM}A(eSlAW#8%e^JsO`_Ms$`oTUcIKv
zDe5@xqaJocvFd<MYaUGa&YiQ+6$w5X8$@H8=ahqhuA9^Ym_Ya2VocT&#M$K!b<q<m
z(V|$w_uYP8_>EZKF5o}&x6bg|iYUO@5{%iOB6(_J^gmeE;{YDvDzSmdRkOJ(HsI$<
z&&NTc&(~YDO!re4<P@8A_Y-egXTs;_(>&9=vpeP#R~ddl9?Txn<V!gIHkBrp&h*ma
za<Ne5-z9DcgoPr=a%Gi+m^j~Bkmb_IvwiMH#4JSsT+!L7yawmefv9&s<JF2ap_UoW
zy$DUZ)RRaw%}+T}y?b^hqIPFD4AqWIh34AAc^Ijm;E8q56e>NRsfEtTjOl$JOHA9l
zQ+RO>kW08@#i1gvUUUcT`FmMI%O|lFcj>OciRbJDa}X?4`js8m^fZREMx552M(?~^
zv^uZVvo2eA?zCo{K^tSXu(Lv^e?r>CLiW0-8qZ=IJ?@+nob|vJf`=PtC=2J-DAHyd
zJ}CYcdnv*&X$YQv^oj{m7^UoOYA}odi*(v+k8l_#Ta*fHC2Lokww{`Gc}r;@hn6Lz
zW@ouYeCng$zi}IKj4=kQLX*rSRO86UiZ?f}s6LeN!j)OH2ds;eig=93Emk7e+NAhL
z4DA@w>L`@z1e-k!A&Ldu-_Y@4JUI#Axq}eEa(heEdNeUKWbHy`IUhY|_f}ml3^1c}
zqG)_`?UAq(BiBDg^&hxjZoYhdM+df6+uQ?6orw?H10)VdP9H*S6!;;Yh}VfWj$y!g
z!SFzdk{lgOuDZm?>N2$p-V*KlgpdC=nJ+4;=|Ge?`MnMQAYS6cK=feB;cpz|^}6bN
z&AbF#WWHUG#p5Cr#A#5naBH~fSdHA>7D@Rl(bB!a!Kvi&)0$Wb2!e+Hm;IELsi&qW
zS1@Prsz-|sJcbRx(g*%?c9cni&7HfL+OA9S5^B*s9K_a`t=L3>C{-$MLAc9j_dLtu
z1}n)g<gp5h2oLzy_k*b8>i(|m3NJXePJBDkXX@61k*|t>^;xwL*~@M3I<f5Y;lw1w
zd2`32h0pzZQBua7F{+C+)P}pCb7`3%6JL)mL)XmN@E|uX@GlNW23ULOsFt!)6SqnF
zAO*}mw!i0A&L59|E$yud@?j#GyW7PsRz$=|rb-37hc@yH4NeYszd<+57Jiqb+_v*t
zLw0uHE4h@!B(sibGH^{(=4kc?lov;8Bv?hwE*WOh8T&5QO1R(fAzcKu)%wlchx(`v
z3LGx}wzInkrvw{zqnkn5EPz~)i&MQPoUbEVa%E0II_<V>SykV{wBd3^;f+f=jF<4B
z_P1+dD4Dgj?JrQ<+an?*3J5&~8<mt$dZ<WbQ4H^Vwd6Ile!^Z#C|vp#rO+Jp=@~&p
zKWVqVl&f8-mC4sqUSW|^SRLP**#f1Q(&5vap1zZ&Nq9kc%7CW^L93ZUr>hpnD&`1L
zq6kY#4wOc%1kEcA#+Fzmk3_d!82xPqx7u0in*Y!FJ&;_OO&umx?^LYtvdWjT##(4S
zGBrvHk<)t6)t<xjIM}-?bl;<JyO(4EA^+@XSwXHwpe_qAY1{(8c*#_W{}6F%<-3gL
zQTvMj+dzH;Y@8Q^P}vs}OXX~5;gv?^37ZxbE2H2B+C5n}f<Le9rqt?fnx}ib9A>(}
zi2Zu7bJItsvmYvba_uw@OA%i@0T%ux=30_nloN@=^9Pa~G%APom}ch+cOYh~Q}5nt
zyvK16p~A-6QIG|$hY#;HRc>+n^q$o_vVqT|*Y0x-UMyaGWb`g!OSaq6-o2g1`wI9i
zZ@rRX7=<klJykTO+>uhd5Qj9Z6dCMcrRHA=j?;x>6fX=osej&YQ#%M*2ZYayE2>+R
zResfpXBqMoi~QKJDeHI!=h0f~6G226Dk6*4WWVJJeSP8HqbfoFd^+BhI>;IgyW6O2
zvAeOWG=xB`{L_#7201dP&pUy2E6}n0{(?(87n%ZY<gZc-Vps>P8gU;#2~r!@&;Fot
z(h>;Njvh6`k81G-1Ei7;DDPd93)Il_VU-oiA7DgjqN?^&V3M9^4fA`r(yXE(e}v@h
zI7)gbMa0YMq7u)kRfv(SFPc*S_FD9<&yI#v$|{<yhRAY9Mnt$G7&72@y|}pZApse+
zp;9cRSDPsKnGPIZwV~>$Qk;rvx6K$%MZ9zKSsLvdTC8Hjq9k!bj6Kq~F2BQaWpnkb
z8xMYm=wLxQG4Pn2#Hcd@u0SdBeDDJY_y;~?y3`$fDyI-W7KTezloR8t1xtXOfwZZk
zsll}kk|VnvD6?e)hTwv<;n|4ZJY!T;Ha{+)j7UF_4ZYd-#eITTTb9KU_}>GT=Ua??
zjlk<AsLtA%I-mRV_@7QJa``{@_oxKC-v@d;Y!9#dDu7Qh4qMt4s?N`K%Ff6kqI8xj
zO4ewGz)&?Mu|vgx4zs^C_B>ma5*4+e_Mp2u7+n1{JEgr24|z;BiRM~{J~fN{Q5Quu
zHx;=<b<IO%&cD_4H+nE_cTZjrdTiggeku3Dbsmu!96aOx+0?jB+Uov%ouox^^(A8@
zA3A$-uzCy;<GAwJ^o3({+j>8rwwK>KihgCk3?^4&_Yc<ilpyo3MvB?Kdr;UohcgHY
z)tP;!d3Zs(;#-)ucke#(rq*G!cv)wRPtt$m43M-H>%y#A53gEY>C}#n;9ip{f*`RO
z>a-TA8H&#rj`n#hf;_W&uP<54W!w22(kpEzvN41?;jtD(IC48~eQCPe3fAeti0#<g
zjf2yYieA!&P0q7_2c)t~P%uR0NoBsteICUUar$rbXI|qx-CeRD11+8Och)mIw1adu
zgUT;XPwBOu!b+OeDfBlYPO*nSjBM<tXQgxRc-xSA8o+zZmHFg*Z5g88`;j6YeoCnu
znzdxwSs2tpRhcnr^$*XM$HWvmt^IyDa8Ev@fF(sox95&Z=z%cgco*xPPH)bz!fpS_
z2)h32%;i4PWdK#mQhVcPFAzQ#M~q{^8a*jArL044HIH$ii|2q)eKR{c>W#jVI*GT0
zREw{<G<zs245A(wEYytxvPW&S0WGALL<oc#G%C+mRdWuFze$qV&Q@_em#m06q^P10
zqEox$Vcnsg5xeIbsZ6W7wxKGCsisCi#B9Mp{YudJ<7anCgc@eo6+$=OjIz4=JXdLI
z{5O)}hz=_1gBdi^w1_grOs{y)2NHv2^&~x&YRk{zLB_?SRu294%(>m|McDWv$Hk!X
z-+Io|vul})mD^o-4qhc)YU;R5ydCyBw=-YqNUd2^E&QXT8R4I6wDJ&+g}yLv!ai7$
z-@VUD<QyFcfLgI2n9vU9Oc;x*oxmu0kv2H2(~$BxR%}b&8KU7Z3<8GWvrYhvs2;$f
zH2ch<{nP#W<m(w8X=m@~N{3@=SUu0iceC#aMKgl%!7n1u|Ml8Wiqc+Jf)^Nh*RXAK
z{q(+5!-QvoW)rcpyrQup#tt`KuOchf`FBQgoBrlR;@szFCFQDo3nD=Ys!Ri?IVx1I
z%QZbOm_k3*w$d{)e()cqoUil`jeQslf|1R71n>HuDOFFXPuy-ehhDDjvwBeJXT)PZ
zJRy0uo3}F^1A#DGQPVeEFYxF4FMmW_ygm*vf4G&>xUYcF^lVp;LVclS2{c{c7`vJ!
zxl@PY15Rnkj1eTq63ap&wDs`K(BDu}p8kKclsIQ0s533JyU5a6{ZjI3B|PDC_#*8*
z;f3tOijUiCDGUT(6HE&7wRSEjyGT2%@4#o)4G^D|*449fne*uL=~b*1iIs>%aauAp
zkY|hCBgM67sbJ~-S`Z<pB$s>V%2|9KZX3Z#HQSnO6}pe4donSpbI=tlj+%ns7RYL;
zuROvEzOl60Vhn=0=r8=%xI~gF`|PiDhrf+RX7arvbg7c_l2oR!HX*H(ardBZx#{CF
zr<HJ;5skIplTR&=U3g6Y&7xGL6nUx9%_+`mqXTW?!r6LjD^vH~Cuen!WX6wF%S88F
zcSEhM%LF}COKxaaU|F2H^E5_WL}YdQy|8{Ys&I?pV*r=u><z*DT$_W7w!_r(s>Gw!
zbQu}P)yXnwafE#9N;vXHcF@bS%e+XosrP<l)5e9Oxm$mKPE{=7FTf52@|%_9k@_)m
zM6Hm<HVJXf71&Hx9EOu9SD9PJiEbN1z>v<Yi=wHCef{$P2&9W9?s|1veov@6{CTOy
z5p0b|0J01xc!A%n2Bq(Ptb@e=Ttm0TQ_BCU2JJ#X5I_^0$lR|}TLs!GE3KkLaa@iZ
zmhfB@9F?z>NZSIw2};2TA+ZH3FvJ0tl`l0AN0}C-0lkiu`s3BR7b8SuMUg^7Rpl5p
z!=M~8F1%a?<(N?V+KR9T9vWZmG=Cle3KRNCB&weNows%EM`$Irk(g)katM)f2_r&#
z6ZPmMIx?FRVpOqL(ihCe!taCy_(-CCetH+84y27bIDGAV2GL)!WYnocVT|yTN@`-u
zU~@xGj*dHMs04O=md@`WVJ4AqGWkT3#lk}O+a7K9aIVaw$wj7MVp4)bp+Vx9p=obF
z@lkD5v%%)1q~$c&WsE^`3<q<5Qg}iCadGBvuOO>XRa1c<?|gb1>IG$bj89l27N)}-
z<NX9oZu}+Km9(}EMN1?Ua2x<f@jh-g=zL7@v3f^1&h9tqP~=^Kj}SBvbvc36ZAQm9
ziK3KmW4g{b6IJg`+0(ZXUY3=ktu#il*`A{Zi!9tm6-|K4lfMcJTBlXLmO_hAb<`?d
zU`K!#TXrQ+BGx7arVBrpYbPFyr+4JRDVy_?2ddr>$s3;_O8sF}>xpy`{F<~_Aw;wW
zO3=}%-J6F&6UUa+9;5QI)EVzomXy2|Bgw|cc6S7m4|hSss7Qwxr<TeX63&SH?cqj3
zyMeQ#m;>fNOJTVnG2%wE9<upH36W@!bD$CoithODqvR`=3SKEW6TqD8l~yRq&YMvG
z#chLXGmiufB3A@Siy*Mtx|zEcw=d&RK~CHUNKMKGF=7gn2i0OWllT`pCASf>`-Tsi
z3A*fQ=b>4Ma#`<F%Pa#?5byGZSHKHsuU8>-H+G8k+N`Ty*efG>NsvV=al?v`oBOj2
zJZ+D{5&jtN&cb*yE-pTocG*C{mz8$=yqg`5ensn5QBiJRDXUZraoOx~s^?#{-FW;I
z`lK>e7dR6Jde_^#ZTq_n42qG-oo5(I0!_z?OoK<|T!YFtBHYK7k)2C%P(4z2Lz}pC
zwmDVuh2<4hTuEb(qj<I9`c+oN{7GJ{UL1K8n2@E!vvRWm5xeH0&CVREX=1K}vKRc5
zu1wUGnUB=C$^&zkvkV0M4iYi0$7^r}zfl@?D9CbTuJ{VGO5?EV{R4?LE6auK^GDUZ
zw%67(WSK)56)DMth1Ts~$RuNsjT_)~l&4wt<<icd=-O~8seQ;c(xVNFGL$)0oUw$p
z(tS%)qAJu_CskwdvI+&&lY0xGOS0)`EFgonFtG6SrNk|+gJ2EHS{>R)u4|Tbr2ptj
zv0NGtAhl^Ons+_qdi6^u1z@KY-&6hsisQgRFd((e2S=fvOhV2l^J>>0TW;)>Y%0Xv
z+^FCm%Jl-jK8rB;yUs?B@-`sAh^(y=BJ^EMZrV2Tc0APff2RDb;S5oaUx?6vP*h~6
z98dm>s}-HpSP|(0nI0~!{!0P}#L^v_iZ&x*{s*Qi!~zoY9QfV{9lJpo8vktq)Pi+g
z-<MFtiolu3!$I_){5RKokH-wr))6Dyf!9Tm@wc@7Bm#MCnu0Y*rIu8QHX@RNDpY@%
z);X6Hb8Zk1B)VIBQ4t$4YV|*!y(mf}s)FOKsEOBAuNM!nqVRhhWT63@Drb0L=slra
zY*HeY1RW>MxM240(Elb<Q|$TFfy<t|_<0ODbP%tgtx)<pK5uJNwAndCg*OjmlORiS
z1>T5NP7AdMwl*<xxN-Rd8PlA(&-C|c(<vXH>tKF0yoa1YXJl9DZcW2yZP2yk_<7-n
zm2$|HqT((liX<Ds?+n3=)V+x3_Kk|D?mGzk1fs_^1k^%T)}!WIn~sfy#?^Ef(U9-q
zm#Eqz!fD!}j23ga`Bd7pg$lVIR+euCVuM^!RMJ^HV5y{dTPgaY7~`V1(k9zcE9O@{
zja&K$+4VA1(jsJ*ucmuU6#4}Q{Ej6J=M2xPq>W!Seu*P6!;4)=ZTtlh4^4jzW+ktL
z==PWT*!9FXsvE3AViIleOHn+0AgsIWrD-<;Y|v2UI*M#jnT7l--%=mLWQ2fvQKB27
z1Q5-cm35kGl77%!uQ^!7d}E8s%yMn@(Va)W$cul>AZ6dfA_%s&D0@}=rhNj=Ta5}+
zsjoi~xs$2UQJY~rpJ{1$JA5aP1oxILNk6t*Kk(v(8ksc%yD|J`o<~_w9dz%qpR4f#
znw=I)UIh{D3>El8A+z08WAbCRb4e7_-ApKIL}BscEK-LyOs9RLe*8gZmlAHt;4Y{(
zPMi@4o~Q29f&I;aHt*QxM9O+x9VNQm#)kMcwtjO%K*a0w7*Zi;7k*Rrbko1Y*wda6
z^C`C%YF9Uu6S~)Y)8o{rQ?`AcrQih25C2`!PV%p1!F}&0S)h}eXy^fYAy^;N6ve&G
zpG!s7Yze89*#ZUO#CR!UMJ?~4Eyx=z2<3LY8cQxj)ni94#Roh17ehh9bQzhnU^6a^
z*m*tEF&yN3hsKjO;e0}-XtV-70X%V;Ff*F#7pY!f(pz%LTmhToF?-K_yh6-1*Hl}{
zkV1ms=vm!O!I?|e;6EFO0buOr_1wXk%X?^*GuEvO&eAYvqsznN6!vbMBlKd?HL17Z
znNSERcIMpf6i9ZhAg{O<CIZhjb%#qO`=V7?zSTUe&BP3cN#+T6fa&VmpN`{j|1X~h
zHh3;|Q(0yawuES~st8(&tj2vF;IA8GqUrVScjNU|Sj_eAV}T4*(P(~)ip@+27yER-
zsf9l`AaGenzrw-_o@6oozxRTSFDB(({=T`GuV2ZMCh7SJzN6mA=f*aqopswobrJD*
z)XL@xM>p~u_6s|q+o1&mc|?wWP%fC8)@C><c8>X)GTzlLC9$Hu*}E6ApC)bDmvExt
zyz6Da!%i@qfN(ZCy)@=FP?)%nuxb(BcHpIx#mhJ4$P=d}+{TKU%atVWF)Qk$67=~Z
z&Y^JmNVVH8r0p}j25zi!1SUnkCU>OXaCG|Z1{Ixg?ZWSSYE~q>Z#g~1%^wJb=mcCf
z$nlJb46PyfJPD1~Y`8qF9LA=o{djt>Q?Jq5s`vQXi5GCR%>jm|S0hd1zkw0x_09xs
z0@E0X^c&`&qYIg{dllT`fBdFdo==#pRK4AFTX@^+n9}1G=Q#u0#cI>Yv1RyEBQFni
zdUyc9%S=Yv;spZ?|MdkdFj0%LuM1B(dNy7{{AO`acM~qlrrLEb282NuBDRl6<#*br
z;Cqth%9t)%Es>R^n$TPgu~aTlL-ll<96g8gJrT4Yjl{X>=~*u=6H-Hjjj|<7@^QFX
zh(x}O23;O*woiKSKVC~5UKS3qt#Fr^x-DX%{)*fmi$wuutTotAU%~;?T)~N{-%tPb
zq;GtwGOl|9iPbh4?7EwP1UBpK<#xs6SjKSeD|C!Zbw(}^j!2-J#F;tT0zZuPjvYoY
z1Fyu@;&Nvu-|3YlJ4$1!`}j*MQ=^j4OpfoSKUM6o4jie@4h<z*lqXZEW8qqjNuVyW
zUUdXV)ZljVePAv7toLWNlRKa(m<;o&pbxkR^#ui}w>!L6BYhCrbJal`<M=QApm^hu
zP%R-m@(cp`?EgSaxDaxHzW+oe+31g})YkB|A8y;D&!)&<v59%8H}=zlCU8D1h0Mec
zd(G<^_KJ|6w5Mw?BM3gwCB#~Zv?-{^5>75P4AT+X^^(4pY3aF<r^m;&(S7t(S4J$T
zpTDp2-k(@_J#(17_j3I}KZk@{ZekalOgfa#_A{CwlP%Zn369RvA2)F|k$a0x>w1q>
zXKYYsdJ0r^UAo7{aMeebdJ|)bIuWBWVcjcAt{qR&<)WX1wb009!$>^DA$@Aos>;^V
z&HW9w{JA7Y9+{sd;=MIDQRs^Xj>)QO7!rRg&(8(H!L)}psPm4xJB?v$)g@?g41Otc
z*DF5%(q;Bp-qHajlQd;dzwaO={|lc$0@e6E;z~{6nLC4WE)resoXTU>teNiMyjf;b
z&GM|1J#)<Nc>BhUFNf&anO0rzHtirPMc%tT1)j3Y>LI>NB!lGm3Pa8IGY=Opi}xi3
z@hDQenh){f<~V$Bs>;tKvMHkzMcBnO{JxF*_xCT);wDgY=)sCHPp|J`R!gYy0^`17
zXlA<GhQ_hk+Ofdx7XdQak5rDu*cVteG&abYNJ>t!KE7S{5NNdnLe}|b0>OCnD>V3=
zomsmgyyY4~?e;=lT+Jn&k)wWhZc`c-P%A5r(JG^-1eqk`5;gXbOE0*TDJ*rD)fZY#
z%cg5Xo8KSF=QxLh<UP9B=X-nu?M2U2Z&hGDx-_5{P7Q@sl|HYOEuOLND=OHhJAQPp
zc+)Lw@1<^EO$4QW7t1Z}Cg=MaJ$H-0OwxWqw9;_bL>TyOE!S!XT}5}-{+rbdaM0ib
zZtWv!BLTIazHTLkL;8lN2)jM4-yT)4bQ<3SW%j6bCJ`5ESaA}vBML1Uq6x*w;O>UA
z?1gSdDWiI{<dAb2xR-xD5Z9}PY@UcpQ1=!htLcFSgOlUMsDpwqj(#v-dFa`*x0wQU
z*^{ddH=bXZ4MadPZ<&i{umX<y9QeRZS`nVRE?OO8N}R3e?<Bfmh<x6U%5K9<0ybtD
zT!|zg80k-%Ie_^uREYh10ujB_L`PIDdH4kWj#8b<r&okg&}MI=77F$b@B6h!We0jZ
zdfJeVuJ7+r)3bXwjs>3X_*JEOd1bRU#!PD?tUk4uR!`2mv{Q#t$r6lcw4mrEgB~kV
zX1?Ju1Lo+`u=|pA*%Ex4SIgPI*6R(G8|8ipThy16@C4V|r6$F<LLMX$$FF7}nz6c4
zR~2c{pcT`X(bO1BtXVVl4dV+<jMY~cG?J!*QyJSx{alj*S&pp*fkt@5zP=FNJ$;TC
zuA_v#`3$;57VxS|I+dZ3;764AHZ)WwI%bWb2)TAZ4g1~&uY!%#bn-o2Xt`+unGeAw
zC%8!uYT8>T%KN9LZ%f8n{dZ|8;-pxC3R&LOi4~TiCReyTIZRA4_B8`k4*YNd27K|<
zvLuOyC*v}v_ODOYFb?Jvwdo-e<3yf({{E?tRy>@XnD;-mileMBIR2x1hC>sEie)(a
zUvP4IK0)Xg5O_=PEg(Uq@~9X~G7XHkABO+-z+iox(mmc*c4XN*e@H{dcLM+M5=BQ9
zDPL;n7WE|Ll&M<MMy<Jl%T`zlq1D9a=(BWMDA-NO<HY89^Cn#XHStB1zmn5m{ldY4
zm7Et=rE|`qdt8D_B6*0Jz7b5Cdy<-e%zL>}FQxz6*H1sySiQ!}kX4_h-Y>YsraK+B
zago|Qf9#o1dR7Znq`<5<FR{E-qy+U~tdr$4hs!}s!e>ev>M!|fwCp!Hh~NU1<Lug3
zQMoZqlS&ybW~am+8$j;-bpmB~65T4Zb-aMt6VnQXE8t)yA=&J@O$Aj(DBx$kg>1m$
zcY8jJr0Sr9?gOSNFg48ix`seE(`qc1w`I}t)tc%Z-FnIN&)wM@;(?EcgM2(9-a!Yy
z)Mkk81t$3j9=MS_f=<igO1z>Z4+0a$aTj`AiR_AEpU?UBPY%3=#8d+Dryx`@A!O?5
z`Onpzz-dJ-NLE}cK38^sAV{t#-wH3OR4FCy&iV%O(Jp=4g+#<Zx%dEn9KCGF9>?kV
z_X?j#DJgWOiM>qZ&iQ_h6Ynx%LYmBg4l+PO=qxrY#%CKc<;JZ*meF!y5&d&fYigDR
zWc1X5d2%yOoE*Euq(+WC2-BU>>LA0`UPgm@%+lmmlY45F1tva{V+vjl*)}t5HA88|
z1n9UMKPHt<)qyW(nRu{!^7S4APe+4d`Hgu!&@}X3r;*pJ$uRdx^n9qyHePi@b4rZm
zXvo?4+`Xl%+YRnm+X9MWG|_ZCgd2$`csGBNRqwcomOgY(^5I|4$Tp4rUu!LcVjsC$
zR74zVs%F*$OceMwZyL6qJ4sC)7t*-HwhWC*5~s004+Hop*!tmO73s;==o&JaV89$F
z)bc#kM}6S6mWGPo($#et5W$iSen3fiQ-{;y2^|h^K-0<te#nJnzKm8GLG;}&EWM^G
zTc<-TvPj`+>BP4KNO;e|e-Q^6p_|Q<U<HRUmy<Cy;@FKP%pYLeU7M@*y<)phM3xZH
zEfC6tbtdu;r=}{@wC+kGwSB4vu9n-RHl&=_oz2dn)}9>8-H86RDX?J@C#OdLpsZ?F
zZ4Q+P9^%MXA&(kL!NHeQ!QSPX<EK3CXvSf|ziaebUw!(_Dc@-j<KswP+r6@kLumNm
zZeD&gG|*cQhUCD2XH~C4Ph+~c81*Ov{vxmw0{$F9);Oy3+T2c&`_z&t9Dt77v|M_J
zd!@rDT9qutm{Y>c-G&)n{$chAa}qY4?T$32gc9%-p}`q$!1dtL0fWSK^&U3Fy~M6>
zcc}tes~Ib?tvX;$sCw6@{r=YZFsI8FC$g-beOu@&8k*Spwu1QR_)*YJ&(*A2rU0!E
z?@|$s0*R|lH@RT$*DG5$+9{DSJl;j<kAA~_=Pi5n8y24-bZU@&zGPuIqjnr7WUi7r
zd%eYewbN-}jbm}{)=b8`WBw-2k{q2GLG{;<V}Gp6f&xirHJZf4CWp@>yeIZs;h~7X
zq^G&AG3<IfxAmwDoEI_}Fp-b^pbE17$pWuqWT#Tk(aRgpi#Dl0in8kdfH0FU!Sk~t
zVxU?$)8REd{3*teK7{<ko7;U8vgb!QZ3pHO35x%EJWjMX-1B^FcXhdr|NF+e5b2Y-
z+t1H=u;Vn!705OJY@!Ya^VkCdf%cRS_}@HDLqmY^g(p_Gr+-!*{Kf=RASpZ-7lCc?
z>{rjHf^mhRJd`sJcDi68K>Yzh?f-fH%h~MhRet<B`<njFt`bB+Hv|BRBq7&5v*luE
zQ38qKMSOZeW&3&QcLDPKJz3l^h}%>{rOEU09_{1_LyV|RbNtFmX8!^;`N{;Yi$N0{
zqmai_>Z{aaUNBqj=9=pMx%k{|)^3XBbj@xuohrk><$ZA-F2PBKabbL?sD2k|z!V57
zh%Lx1XfYTh*a)~Zcp&&GgehbT<T(^ElsYsev^~5td_4j?f-gcFVl5IgQYA9#_o_sV
z@`)OUMvoSSc8zX`fr?R#$&Xoy1;C2Idc>B;PQc;B`NXaG?jPh^%plJIxc>$NfCA_N
zya1Q)S|!|fuIzvF;Fd5h-!F(B{*@RF8;L8o@q-SGB~@k@6crT}-quwFUPndRN=7<D
z77A5=z~kD;7bb5o&z%|4IP1jsD2vTP9;%n4n<PO%0iOphn`P_;yzE$uaYjIgG0A{F
zO>r>mfc4|g0W2Z1X6-H{Q8jVYQ4HquZ+b@xMuxm?;f?=7wHcFqQf1mO>Qcw=a}QuC
zNt<;CyPk2&7u6+%EKThc(=N=b$jWpo$_xL-mi*x?n5#G+Z<mfC8Xmh@aGo|1&>XJ)
z7xCt`d&aIDY0ER&h5Hg&r?)rlzBn&8!)eZVq|I<JV?L6i4!6Z7Z#hx9Zu!oK1V&w5
zA13~{3f72Nsi?-qz9<s&M==c>Ion+|G>q<FcE+U*6mvS7J^tZrCE+tRGD_g}vmW6K
z3(fXty4wBhsSz*9#new$rq#58OT;GeV)AiUHsIR@-(YxIvlW8JMaP1c2I@cvZqieO
zj?Rqdy}UbJcuO?oR>0>K)VXJ<+V{NMDOxox5F^X>$c^k=-gRHSiRP6A{7hH{Cj!}b
z)C=e$*YS6;wmvS%svwMN+C|s9`D*LM;iOgyDi9CBB&aovCYW-<Sau}faztx&#Pe{(
zdU7N}x<-z_##XpSpT5Rtx<;+L#&x*Hyt*bNx<<+Cy}>bX461|p)h{_m&to^J7*6fm
ziZ0(2J-nvEi#(Va_xHko8We`9EcQ3qy!&sOmJmK{djcFvV|3@roFo>nO8<%=+oMJ9
z!4wCM3x#rdd%kI(xlA9kq3n&+wXt#Qg_g3)CmLD1TuDk=8|g9?L*wT}VR|dY#DgQ{
zlUS8-0^cDOI;tJAtFXE+O`wZeWx}e1&Bs4WGe(||xb=WQID+vES!?rx`#H5DNwSOL
zoI5vOKfxyLiG6pc-hpXyUW62?vUs)hl+w*zZ#|1d4y{^UMUu0@M45w<QX{M~_NSnu
zuBOtnlm=F-9g!?hukIMes2)k8B#Q4m?(2Vjj-=GaWMn1=`;bF9`0)7fqY!vFnmL>-
z%&e9{*jerA*gVAa{E*DilU7*sAo=W636qz<qx(Sm&CMNG%llVQup9ac$c)KcqQ=EJ
vkR7ljz`)3&MI@Xh!6l{0k!=r+jQ3~P`ks5-z+|q(DIu`{@a|;aneG1vMcG75

diff --git a/src/font/font/fontello.woff2 b/src/font/font/fontello.woff2
deleted file mode 100755
index 078991eb8f784ad2b420cc2367a5c5813055e053..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 10392
zcmV;JC}-DqPew8T0RR9104SIM4*&oF08S7904P2H0RR9100000000000000000000
z0000SR0dW6h;#@b36^jX2ny;{+9wNQ00A}vBm;p!1Rw>4O$USx41oq4c0@I7n@0SD
z?m!@_{$1iIN+fBCh^o=Ovj6{tq(g=Vx+egvR^35D6eL3sWH75yX$CRKZ(vVM?`bqB
zIs=mSs(m2)r3o9Bmi{21kA-v3jfV>jd4X4+=qFeDc+x^SN%zN<<RZ_9&b^(Wmwf3J
zT^)Jd@{%suxE_RR+FT~&7D3_%$<_e{^2Uyn;$pi2@r02TUX*QnK^O#{N<20mKbCvp
zUtavZBQ*9&t`DD^^}Zw_XhLcdq;-l7V*+Ewm?Jidu8McM?pM>}{=LyzGl-ZiXR(7<
z<5HPB(WdG`1b=VNUh@vrs=$d<OsG=DfFywDsffWZ_Wsvt^}i6&2_yl=IAF-e8QT+*
zCT+}@QqkPIrrPSNa;m#_wHVU^{+E@nQh*XQ4=Az8&61G2IlnI9(d9dS;NicSQ|*qq
zbaIBCp#Bx0qA+JIo7)=1gok)>9iVOgA9-hSgo?poV#NRhEJ{pbCv#{|OcssBin+0p
zv2rI1x7Ka%J#_6l+<$ZLyJ#0t*!Q;n*WJhN%-i`QhN$e9#kW3-529Q12C*O{BO@fr
zhzU}Pj3CFJ**>i(EkZLd@5I=MokUqn{LUc?--<d#O;-&Y&j7&fp`7ZSlabW2pa>9<
zbOkci18&)$)5g-q0y}QmB)bTKBDpzuZjV>tJ2cy>z=F-QwfdLtoCF(eC#hLIk2Dtn
z2%9j5zT(PM?L2!=z&cuAK#utoI#sTfO1X5^-{1d(_xAwt{~&}UhBVpI3seA|31upV
z(lJ#bfln%d@Cc+rav<rHt5|d%m$}ZBdl#LH(q&`!E=!lSL4=!b!`s2EGz=_?1Ch2h
z|C>YtqVEH?o>26-=N-nTM!lDDrR;%3>92*GSZzbZ9-Z1@<dXV}0RgNQEJSz9*KUFH
zCRIO*E?j|$9@oXmO{tKAC2wSy5cx`1QnY?+J~TmJ>8s$1;{xQLa46g{TgU3KHeN5w
zVsYgJubux-VpqL@yP8u&EWu1@d^lBfTWXWJU`l=#Q+Ri{e*pNII8)_q+p#$BzF{BV
z{drI4C5?!b&hWe+2q2WT&&uQFTy+p5_M~O|W^y2CZi9CB|H<P81w|!g6;(AlH$#h7
zZQ6C{)TLXGUVZwRJiL7T1`HZ9Y{aNB;{qlGfwT~o+r{WEh9DA>01_5I1QddRK@e~V
z0ttdZh9FQN2rLi;Dg=QQg1`nrV22=ZKoB?~2wV^Z8VCX%1c4g_fdPV`1q4AW2!b{c
z1nnRQIzSL~f*|MuLC_6?pa%p-uPh=MeX^K=(JwdKF-*B>!SKk<^B7*ac^|_kHy>m8
z<>oNPfZY6uF(^0xU<}FAbc|tn+6!Yuo=&qd`a>clLSsmZq-Y%JkPZbx!y(3me1f-I
z1Qnr<w4q-3UrJTc`Gh$>cOQKlfVcmk`}mgH|C8tL%Cd+v<d2?2(|^N_sK+`oB82Y<
zx7rMVFgi;Fqd2o9zy$nuOd387rMGU|aeMmNi;Br#z&FQUD1!ly&z;9bK!K=F6P9=V
zj;5rwSR=tV7iP#`(+-vqA0jFj)>%5h!HOZ&8KSv_5}th8j=7}*v>??q0aYG*s^X}L
zGa`W)Eh$t~Fez%~P^z`e_DYKSdMdd{2_knxdziPf?q{Uo#K&WkPJ`EwNXj!`3o|0U
z(}65iQ0MLdgf5_Q9qJSW)0#-C85y-rP|a;h2YTpkar}?E${L_jGKT7CY~p<x81yNy
zbvr_7FndxyWHpdPQ>l(r<K_CCl$bEdfsm2&8C@w^vk(>4)YQK=EnJ)F2Lww^pv^{;
ze`rmVdoM;M?1;+hppecz|17At?7~_AIedewtnK0gJ9p?E>d=gi9zdHOS@=g?os;s*
z<&4O`@@^C~pN+TYzR;!#Yx#jze{ko~bmHHhz1c==8K0b?K=1g1_ohr@>DV&?%^*=%
z+V7~7O*|;5=y49CoJD$1=dWlpnTx&&UPA^1L>bgicVL@6JD><TrfFFwZ_on3wIPyl
zIiSan{d3F}<n-LLCk`!J)6}$t7#-r6l)vrOJS_OS{A*!@nt$(_TftJG0N=HAQ7{Kj
zBH&93^d$;`q{2YbAe3|%N(PK1Gcm>-u5arFnxHX?SOimvKrAVcNEA{@g-p^Qmvks3
z14_wktbix71hb4tJECsitl4d0j4^?-YBb#JGm~4+0ra4CgQk78Q_Bua7zu4;!TuZL
zf%BZWzcVs($&jW0-narD4$)Yrw~$}WyC+(U!LkPMe!=RibN5X5%gVbumK)#+`XMF=
zw}#fw(iYXVCzcJIMHH3X>>UbmhjmmsMMy-p^>zN*YuBFON5<X_McmMMbeDH5_aJzr
z+N#z0X>xa(mjXP=$FXS+Rj9}tNW~%0Rs5>R+498yB(KHDxc0C(2+p{57(`AGAE$Pa
zlsY1!fzhmZ9>O?LDohijoa|(jh{h!+`jC<Wq(Qs1Z<Gc*5}nl!wiuBJ4Gk4VWP4K8
zG~|Rxk%pHswFg+EX1;FI4G#{EFnE+=YIyyGKH2BWj_YZNlX(ZMCB`XnQl|D^XXd`(
zgRk&J#gP266R6VL*7UO4YqzRR(o&^X=ZX-~6{kOp(ZNp<{cg_j1*b7_1}$L>OLV9H
z;SN=)z1Zc(olke%%LU@n!4G^1?yW}m6?m{5J(!?uDIdoRkg{T(7m?D1LQwp;X3<Mb
z_v@;=-YZ6OeLFGyQ}mL@`p#_1y`Sj55njh63{TupBKYdkZA>(!`8mdm?n16&Is@n+
zUy|@9pOyR8M|%bEWOJ*s=kXqQGf)1&YZVo`oRloOpYDm`5jpqIv%LY}TKhcY#c&|f
zD%cC+It6!-Ucq0m(4Y_wGAhIi7Mm2(L1u+~LBgU?4zeoL3l`ZF+Cg^Z?ePqw@ip8S
zsr{hpvJq*R92kViz#QR-u=qHvJ}$OM{f2#<fWs%?^a;3p0`74V9-oBQC*kwS#ePiD
z23-{K_%D-OJ=1XGDZa#G0fZC>s#a%&-~$FY54Jy??^cIa1pO5NVF*RBRBFm|b&;IY
zirxwV>-S(aL>&;x$mUr>gMWaLs&=$Tmg$Wx-LDyHr<xw_bhoCWt-u}Y+ER5(hvWa<
zvTf7cEHxa8Ix1Ner)0DHRC^caGUtA^bGVA)vL8eX3?`=PR()1S-Wm4WbFn9Nho3#P
zj#JeZI8?V<Rll!3`vgmxV=2b&BF|LOMZPWTh8v@>k59}}wwMZXWpp|bfTGZ|O5GsK
z1I6#hV)|)!(ZRZu5ImLyOMw_YF}wjgNK(gI04Lju)c_T&-w)*AD9YGf^>A(*GFXaD
z8q(Jz&5oeHKf#XDPM)H8o>+FFhMo$yJE!1wu8v}9Bpqcji>>gT@YVKqW!ZH>C>=ym
zGwW~8CmKzdo@5aK*Y+W~Xdhuf{<SfVpboo@X3S_XyL0g!TsYnr?aEVK$z6+ywmz-n
zF2`mNBFs2P%$Wa}j!kbf86%i-9><N=FoOtUjPVuFHXUnvkQ^h65Nh)%fFo(CF2P(E
zODUpQijf^ax;R3}gVX$=!CPz?xm5qA+RzP>o&s$f%5*QyVmvckw5%5f^X|Slpp>2)
z$N-BDC70w}lhR#50q?XVIgTWV!7@2Y3drswC{sM^5kh4m1&?i5iaAyZ7Grk-%c|Ix
zljClZiz!<rL5?R%*DZrFl;HY#+LLOrMl!6#XD7;)A^U!Yx@uu~qLnSWQxhXJa8$wL
zksu%7XM~xYRz3qgIMWl+4wfTT!&N)lzVZe=y*kP#xXGUPeuJ%(hfvWWOOl`>00m;Y
zY+U_<U-c_U#-CMTp@roT7Tdk;Bn|Hg{Zje5DjgNcNDm7jq=r$`eyKM^dT&&XXTnfp
z+hST1NwBHi#k*qhP^X^GHZyr&0jnW>43bt6V419VL;y6R;U+Obsm(b|x4=JxL9$g(
z3?H(z<(Wo6a{SrVpUinoHSuP!cWoEs)2R>a2zn0buW#$T3F#XSp*WzOO5Lh-bIKyQ
zO*O!ii$!rt6Uqa}-4D>pta-gJj#TaYK-57qP(w2n<I@j=FwBO*C?#5k^xy3`YZtS_
zedT1iD`UhdQm52j6t8%!A7!>$9XT4lQ7-R3WmP(0NF>|}Am}Qr0n3-M$cSzh1t$H~
zj_wjVS@y^B^%pC@<P@WhrPjZepYWm7u%6DpB;cJqZeYfy__D_H?O@G`iqz_w_Xx?_
zom6gi_jgL1S|zXU=}S}X8y{*UMq#Y11>QaDewUb$yz;d<Y{o!oImbm=iwu>(&&~Wj
zDgP`}N<Paxn_uiIp|0LIcb-nL(dI@~B2p(~j1?D#MYZ7*zLd&V7Hu~d7_$SbX5x>(
z=LorXUeTsP7M1OCivV~?j0cHCxnz|H^^G^8#+wHmm_#u#EQ51H3QA7|cuMRHTb3x>
zpFny-$N?Z%7HxyE-c%wk5$H%A>ZU!%N2D$d=wMw@PK-`wuG*KA0L4N7;meBypu6S)
z#9*6Yh|cK>%<HY*5Fv!pZGy(_(_u!Dp@A&ahNL-xiu|<zL}=NjEY#Km-Ly1BbMLN4
z6pFXPBGuV`Zk-+zil-W*UW@i8j*J?R1^pBx#T?))tCcXo#h&mG%|K;)s2(IBy-`u!
zC5(%TFt>z`vd%9l{$OP_A88rf)=ju^P{`D+l^Tr|tzmT~b9mx3jp81J7#1@*N{WH%
zJXA2r>FBEMCPGo(S4Z^%25)8UbV2JbCjdtgb{EJ~A+y=EQlks(%3$4e4^f}AG~zfe
z1vv?C2!=13*48*_S$7K=#qjcy#tas>070?@9rbnfegkV~Q*l>Lt*1K87YMJ>^c1Bw
zRtpu_njU@b8|vO3?)xfd2CJNyB_nesLP5$04%3`SY)z~C$sABh$!WT;3qVexzg*_}
z5Q<sFFmO_x>Vau0U$Cu;C8dpy4b=I|v0Urg!_{5UYGHjfh}KRFR>@I&)8xWI>Ofy%
zwWG1_>R0^ov_dQyavdXE^)r;}!DTvFG`~8WqcXx|3R$tjxs+7|<p&M(t|$f@bcOXY
zZ;I$uL1wenc)00y%lZ>KbTm5h8}eC6JNKx^K1zA-gJ+_@S8ff*>HT3XCG@Or43Z1>
zkmv^%AsVLo)INtsa5Q}2dC_7WV{xn#f+oq)>;zdzefXXobPe~*neM$~mSJX`z>k!M
zO+n4QO$*@4WIKu*g#fV_WD@ZArn5N8@jxDA`QmYaB^lu~M6Yl2OBK$l!h`8<%2wf+
zo4SXHH7Dgrj^#mS)Ps=h#*+t(&DknkRo*}X`L5G#gfOP_WC}TXKec&pfAva7aHG|H
zwB>G7_=JuyVGc1>r$+E$_E7lS-DU?}9T8Caz(NSP6iW96D{B(8Av8N2zMoNd3Fh`-
zH!PTgBKb=o1k2rs%u-&3W9vh6P+nDzWY<S{L>lZLiu6bgIXt3K6U6RJVuL`(Q)nH1
zbGUyn(>;?pSQdRg2-4}4jQ6FwIIdk1UHPCU7K=8}w_5pPyA<Wa1B(_(%MlPN5DA~t
z35zMUO~PJC8pBKjl1_(`D#=}AxM{Fh*so+Gz@z-^*_MYBZMlise6}xouAoNHdR1O7
zU;ZEd9L;vhOpp5i;W!!;%n_aWV$_TMyy$hmf4gC2Z12y0-}8g05BR#HU+vjF3%@(7
z{IBrUKi<$IQS+u5_5Zmadh$Z~_k8WwU4H&+&7VKF9}dFYepr~1!1wPM8>`=XYNmkp
z&h^FlQ?Z_|E=M!={xE$v?e(-f6Tw)|3#ab7dOT)^8h>x~-`o0|8Ttun4(s0XBB1_y
z-Sw9NJzK(F{BL|6`)wz^E-a?=zdNDlX`pQ)lcc5x0lq}3hxyJ;y@Naa`|X1ZSm=QM
zhR8`aTF=uxaBH#;9`fJWJJ{r_q3Cajoiax^@Z7M8UPLuL7{??i^c0PlKOsW#A+g?7
z;yp8?z*K6fuW@l4dbz4f=HgI_7$=d$xj63Ei8w*s^DTlpdzLdO32Ac%7bt_;4Doqg
z5gRmfes*+>@E*=`CJb)jZUN>ByrOVsmd_ey()UuvMszG%eyhGUEPJ}sS(CIbqpBo5
zMp9Y%d1K)mbFI~S6=z*}!P;?^WL<<*BL@V$BS)&FEh=kjZGE~0w?Vi>KW<6#Cc+Yt
zzFN2v$ZL^S9XTQ`5a@(zv{8-XrfJZr8r0X&3bs_#!fQ82Owv$fkCn<}pe|3ixC6@7
zr&C*@10Y<pU9S)pP&KmjQXWOtGHKn*=4whs3%M1vWz@orLlg2wxn@36D&4@TdBWBX
zD7Ph+)(RdVQ)>1%C<IbiCl?EO6#k-D!)vOBD{L|y4^JZ^(4@(|lG0L%IUd<w2!os<
z5{qn@*vsLP%p5dsH<_Vkk|HTRF|pk&6yETIqEl|b=)jK+t8L%TWQzs%RcqIDvOBS@
zhuvLLycwaPwTE@ExEM?J{y4WrBA3euM>VBy-zt@*-u*IJzjrx)afXHr<xUPRRUOF9
zZ{l|3=Jq%3vro|s4bl9j9W7PAel5x+v{hBM5wg?YzBZ&{Z{MobemZ9K9W5?ZG%8i3
zB@78kKj?aUuXZ$xg>)G~4BQI#tBl|Qa4UqVXj?hl3}di{kY#iDn!%tf3jh9qQMg<~
zv4Kao;EgfBW&p$IF!z2mK@Ow!8o@d?@?BuNv5Q)F!F<6|Q6-nD@>D#18m98aM``6>
zKZw9LT0R8|BQW=V?8dp$up5#C4A;#b^rHnl0=2XXmLm-$uX)4aa*V+!@lDSQovNR(
zc3n5CyQHLt)e{;<Ti$apio4f#vY1Q$347LsVQfq(X(h{McS|%v&-CeH3jUc~T&!&@
zG|h;e;vN7)8KJd3EN{Oct>+(4=iE%^=|RbL344-T!;A7V5DfVq$`NI76d9b^4*h<m
zj1^H78Sy;{Np;+sOy^X{^G#0F7DUdHsO3_P!8M8dkrh1-kjMcgONwNkdrFiN1KtEo
z95R70EbOh=*SiB5D@4(0I8^C+wM*>zTg7DFBZ@s8HTu|^<;yRvT#l<AOK0AR@cEZM
z_4JfUCto<){LB9A?38gQNza>3_N)BK9jKk`!L`PSPl@|G@rqqddRz|dRA_{UuO~~k
z&LcLcn{d;NL^$VEX0ld2@0usuxr{WXAl|q3@zyqaviwyV*$dNo5zuAoLE6?|5Pv5f
zob{=fmy@)Yv@gA!!E4?TW5nefS=kv?RvG@u3pPoz?9yiWV2CEo=3mr9KSngqAgx3p
z((vGF+~4W;F3@M_5}UMh-yqdqy7(!UjK>GXo?Vf*?kmTPt<fnVr_jdn`1R3@cH*YQ
z0Pd_?u{E<a8oYpWi(w^jvDAS!UgpBBzZRkYz==+bFIp^7t?S1#QXys3lVJ01zLP}H
z43S<sXoQ?>&Q0%0%8M)I>4><V7_(cpQtwyZFypsN6%D-B?dC=+k&HR=otl5rM+|Bq
zTFY5}oZp?Y%_Z$dEgVq0xohd=vo+r`4aX>@9ulf~+Xf|0CO*<W3F%&h16Q<+s)d>D
zj5{lHlCz`|&%AuhTXbC}OJV_?Y}reVVbGA#nfMGZ(&;rPExUjxHz^XiVCvf9i7F|#
z5IcF-VxILdMOuJHaf5%U%C;txTO>JhX3UB#s&^257A2E}cejTKE8AZ~zO#wz$$yMj
z2U>Ui-3+KDp^^~w0zjQwSlC=xxb~By%$1s&phl;praCrx1=<D(1IL0Ggi!G|+JGAY
zNJ%x=Or9ozpg*LG6}8YF5cWAqA0eJaqZZK_7V!w5f9bOrAq-u7z!ANtqeeXU{H>y_
z>gUgxcgT9?srA`2&wEV3_egZi7rv8Mfi6-j7vVOU`<t%@VjGf~_tSwX#*>k6Po`%S
z6gV$GeuBHzY(9C?Op8-Nq6X7y4ul}yzZe)8zGDik-eFjN!gYYdB~}=Xdnfq8nV^(w
zFeJ2ht)D)9L)ZA|psuc<KT&@Q3LGfHMf#jMfhk$?GLow+i5nBmCA+%)lp2IgeERh2
zz_aJPYn=$Mq~z(dl472)AhRF-JeTP|pfnYOK1XQ!A&O?;Ez6DEp~AuJ2+9MPOOX|h
zOJpOM_fS_gVoS0eV<+C?RfEfkk9PIv)>erC-6;{uT3SY-=<3^^r+ji|mnwidt98I?
zsm1xr7?w8ryp5$sy^yv#CyD@LT%C?>Ve62T_LTE;vJ6FX`h}0XEW31rCX1;?&>0Nc
zDvU<3U$vGW0Lw8D$N>n0Ozdg%axbTP?Xi^K*ar3^t!ZNV*i><YbR2OOlocOWz@QT=
zc_T-tM#hYat5PmWO<ZlJ_kM}rn6xrI`61J%Ekh`gX*tEABYq-*W?w3~pA?aAZ=`Sh
znma3hCO0ezt(h}1eO#)j`M_*Q{_*(7FGyRcVxfp63D8LO?)8isq~1@V5Q+RX{v%1f
z!K(jvIw0^ak3cPOVq`R@#9}Gs6b1jFdA;TG3rRs*+=(FJAaZ$8QFGArfJSDkSB0t`
zf5Qa7k;JDqRdt7!9Up-9%F`1s&@m_&!oUSZv=@3VVQ>h6QTvX{O1)l2<vTQ|W~HaP
z9+rk8mC84sAL$Bn@4I?M`6-p*;XUkbb2qCS`%x)7Ve;hccAi(=_hKeW%SD&g5&Wfk
z`J31~nh&FkO9Q}J6#h-~g9@CF0&emU6{v=v*2J{gm-{)~jD%nfp?~4<d2?>USFOai
z3(pdHw>&A6>}~nXJQBrMF1}_Rk-49C>log3$m!Zy-;v97<su|Rs3t@XMJ5EJ(Sw6*
zOmM*0aIJW(2fHavEBsIW^Tpew>(Sp=_Qxh(#}`lRiOelzW=CI`wO$ikc8!!K-+hyA
z)y|spS#d7yuRB&n%TjlW`%*>ovKr++)BOJYb3Ju*KNa1_Ig?N4PcZ?xY061iVLu<A
zSX&Sz6;Az75Losq<2nqc<M}g@%C=3z<>t`k<0_VMws8s2EiW7`kr8-=cbQ)+{5j@%
zVSJSpKoL0A(z*08zeYn&0S*x8PT|-@r!(6T4EZ5M(doS{{BexkzH6~)P$_LbF0a63
zt%I{&n7{4sJzTQu;n`LIRd}=ol*g(+t~;MGn~C-H%_L%dy;P6&_ANCif5#MbNgC%y
zQIt08S!w1vf;C-IPqRTJ2A<=W#L)W9yrZFMNQp)Zh3~(Rg><vK$2X2Og>5mmYwsgD
z;f;WMd;+=zH%ZBmrVj15rQga;EXz)OnRXca?){h_bNqan{Uf}%Cp+0xTHcn}K0mRz
zTxh~s6HVo%CZUxun_F4UZ^_CtqK_%g+rhj;o+04A+JP_PR$6d%6h0hG(NL&0fOB8X
z;9<Szy$Zd_X7=0e(I1u7{!M0}V$MDGgJ1#`SgXJp#(tC+y(7G1?an;+8h0O4x?4zl
z1B@QyyAH3WHnJM#zUzYdZq*dcKo^WIr^G!p)179w&uK5n|02i+vN#$VvQEnM4@a5b
zwZ2zH!>*xne&Kr=>}{KrY$&I(Jo=~g5n56?wn)dSOSbHU(E$<6eTU@ti7b37bX#w3
zHw!)@9D6iGaD$z%-Gs+?8c!R?`5Pc?I(QlSEwfH*G~)hlnz;vOXd(5PnoeY$0{cn&
zSnH|N-~rVdu#rzEF;IV+;`AxdHc|ifb!H&8W0tn!<Yy9fzJ#6VBJ%i8jp1Cn?P!1w
z4X}5+WPdgy7-Gl>I>*3M0f6#_68w_piZ^U8KJKcsbSfcNT5Tht{41uhV+3v67PAn7
zA)k?T5$KM8|DVKO0(XQ|#a@7vYx)$Sh~)Ej_B(+8OA`mS?hu!nf@UG`MQbPojbpXE
z-D71330l%+H+yE~Oj*r1<ZDYKA;XBfHpsQwJx`h>-exC#X2t1R4p^^uixwgzt|8$o
zaa4T?=`ax8p=*_4T!3ApsqZn{!lCx77(ILVuCTJ0Q($_fZ#{*gYQzCw<Bp9b9IGSg
zO|$;gb4xye(fqIr3H&W5;mu=0y?#^`1~chP!ed4t#0h49!IrdpZ!z1gSCIU+Zl3dH
zkcyEWCu(V(MPd()T3$a)kH1I{I6%h;VWN#lgRVt60&4me@LHR34QU&Uwo3o|DmYW)
zcr9=K`tHRwg+F15c(4p@YP12`j#6L-bPUiKFxfjOeewOn`?pV@)U!C^OphbAyb>sK
zNh*{v5vH&$ZZO9Lp`^x}c3WfiT43%@5@fTiP;jz<*CEKIBEcD%i296Q#t=>Pd{Ar@
zL97I~R}jYg_%z&iBSC0;3GNvdoeQU2;_^vl@=`yMWt=qT003!^pf1F)!edppmvx>5
zzCCd!oHL`lBM<(Sv}?Z=O&b2|1~I+90E}>&|DJdRt*ouq9X7@$cKgty^ReDh>`BQ&
zW0lquG>pX(dFTrq5}iX&LwQNma!BBs`1tYAi|=Gk;jbNAH?6a+TG76E-rO0j)2EEr
z*XYWMM-?cDe$V&8$SJjNJn@SPlbV$zZ8;-xgqs15@A}wNd#XZJSJ3_qaSl7;INcRp
z%NrzLzJ3@Sf030+2jQGF1sH12we<uRSE<rQ5NS3_XDRPWL8bReIGinuE$Nus6n!q{
za>fWWOSP``q)Mywo%9jcFYaznPu8n2@LXoYDBf<0#Gw?b2^66TX%{Ca9QgM{=W3W|
z2X1R|qJI8h)_h!_!)va(<RH&?-h=`89fV-`*T2T`4sY;0PeT+;=maYY;-6AHKd|kU
zpX77-R1E*Q&lQQ{Tk?wB3JeX&|MAMhYt5$M17HEskXXj&4_3{`^=-#gPpC$qzy9im
z=pO37D1U$m5HCk6MALU%C?xkltxPphX(b@%H_P@S8?A8*xf=*GtOEhtSeL<8G%O@f
zDJ>NCIjWFYRb`O-Vw#X6o)>YGKwOWVesa=x*8vcK|FFw0LUNP8owmn0r=75epD84O
z1&9TD1P;}5B9Pjz3e*x&vo@xq7i-;A?UN2Mbp&YoGuQ}dSjZ*}pqc#cPJf?>MW{p!
zQWVc@q&Wp=mecVKoYyKz^XPF=<ca1?I&$FV?b_ekLI>twO*67!iQy*eHK@w6sa8Wu
z1V&H?f%^dmoc0b1wO@_H-rJRzXSO01{6QRc>JZ&xAZrZQpqN|v1f0a@7#!xKCy~MY
zh>&7UM9ofUQc){hp>Au&bp7{R*+g!>?uvs`%B&Biga^?3yExx0t9%@K9AxAngu{pB
zk5O^j>z132D>4}AJrG;H;F2&0+fIDy$f@L;jwHQ(2REY3k+y(2=|4#ZI3M+WyVtb=
zAl>;-7ac@2J(LiXmI4c72+=9?$VnUQCsNnG`nT2UG%E}Ha17IdlkDC#=Zz!c#&wrD
zFLs@y+pPst<RD>7%x%WyGlF^xbbgU1i~e$BbpL?pW3IBdelF3P%@sDbxk!R@C>auS
zndzQy&XmUdCZ2x1!Mb}|a|aH%%oV)5zq{Y=wV@4*oxbz(%7DT4+suCngGry5A7p5-
zAw|3(#rv_VV8x4D15Zo038KK7=#k#%XlZi5Up|GdRX5Vw(5^*!1b>IEYu}PYyUtNT
z7wD=hDD8{$eIvLll3XN7i)lr=mpsj=)zMCIQ;(*SIS`pxB%`^^w@BDHkE$(LYGR16
z_XP1#9O3ak23rXBS|NnoOsIFI%%KjiDiO=m&8mD;y%DV4;}*Qd2qsw0&gCNV#g13u
zOV}FjYUho62GT8BL4J{Bhd3Vz4*htR)^a4gMCN`JA6XLDc?@^!_Gl?^g?#?lNXP~B
z>ku%!Pv#=8ML7qVIivNQWA>Az^>~==4_WipuwtghP%-Q~azHcWj@2U(G}*$t5<MF1
zbVk4}yoGy<rg32^NbIqFXEydy%>Kw=W3|Z`kqdI_^<AExHZ+cS|F%Ho!=|h*0o5SG
zU!#?-h}$}B+Xfoaur&k|+_Hnzj=X&!?0c>)$$mr6es%le{^sKR<apPtSM{vOx!|lF
z$7<Pr&=7H@C6lDh^MMEdJ@hmMzHvpB`Yp&pBn2JB6Q^lB3YiV<cvPFzy4Hz~2vCXI
z_fP;yOWI_Io&Zvy%*@4AB$rg<gUj+-rLaQAq=NTSru`vdP)e&wTeG{?H9B%ZzL9k{
zW=X;s*E26W1ka+LsVW*Jx;Woy-d}+zj(DarKpt&+u2%O+<0WZg#Hm{5Il9rfA=Xf|
za9#1mu+K02aPHMeOncW3X<d#TRLF``G!EligEmO3wmOu)?{UF{;6JR!h@uhjKsY9q
z0t0i96cOTSn!8_%tlBU_Yo^l3EXnIa=O$raaJ$<jF5*Q!Yn-Sic~lnml`>K;R#%z;
zM-oLgNmt+?6jy@deLb<@LYYf>{tR244G9b+?sJGp`=naUG;6b?*J6{h>bHXASf(EH
zb2q)p_netF@v!}-BGt}g=(O8eIKq~O!I!1UYoRS{$5|@Q><dIGwgf7$4P@nB3v6w1
z)esmUQ)@ZLU{I^>67eeE7P*C0Dy5aycHR6oHm$Cw?_}gSfJgS*&7v-gG@gvZz!8q%
z3^Z^(((y<CK>y4$($~33*G%_w;?0(@I8%Mi6`~({3==zph8;~M_84~hVRTf`nrx)G
zw>7Q#CeWL&1g~5_dvbqweRXIytEx=nVd%Tewz_aMw!Lwv29kdOg6?zTpYqNB6|yk_
zfUk}pp92O(9iKnOoWcqKEEGDcN3WCMj|up=8xW=lN!YCIr@3IhQLD(MIBX}&CO*p_
znLe3wp*{vOtD^L6p3v`Z^e>j4MMCVGblLEuq#<PSQ^1;ZCsbv1RWE81>d@8N@|ujg
zo2x?kL$L@oW4;>TvEl>F8n^HFj1kilLU^l$%Ip%xJ3-ukLnK8nMCSbwt7(N7wrRSK
zI%%i?2R)b&QHzTxmGl#>mH;yT1;pxP9dT;Okb<|=?o~9FkN)~m>UPBZKDHnNK?gXB
z#mr<z@nCd=)AWxT3EqN%4m7F>FW31Vgv!j?Nrgzgp2re}mp2CATS<q(^q6bZkZ?@d
zsj5l}it0?m5=NLJLc-=SrJ>q%tOFg|j!HR<Z7B`)53JItp|Kc~dB=Xqx*QsZkK}|5
z_I{+w*i<D88r5!7stvib>1}fIW~u7&o5Ci9I-nKi(N1QnB9dKqvgRbsBd8cNze2OR
zqTQL6U(%*>wQ=xaWTf<j;()fYxq{s??X8M3XT$1`;2T0jNJZ6j!?bM2^?ZkM;d*{Q
z7=**ocoI$HB+c@otY-Cmv0SY;J00>LI10(+3c)|ejk#ukuTfm7=ISTsa&;wlYA!G#
zpO>)~?JRSwAOkQ`jk-#y5CLdKZ}OWlgcV|jDZRg%dMB$*LM2+LQCew~&%y~V5dnCJ
z($k0F@DEEWc*-p)rK^ovQH5m)&eurEsVp5saPEj9zirKw4zsdO$|qM3nyJ!HtiYI2
zC@c~lU~Q?EFr%I`1A>0c3sj#{x8K?Wm51w`+aW9j5|&w1Dx?~PoC6x7ZC+TG$7+Oq
z_D}%&5zR^*n1EC}LQC0-)vd5LhQ+s)%C%Chv^sTa;8QQ7SqI;QzICRRo9nFU($RAE
zm^w9;6CwiR76rK84<Ek!GKu7>ozLLWIgg2xARQeC!Yu5ZvM8~f#&^+OK6;tF2}}cu
z42$zK+}TrdNpi<C@{%tGvQqAP**FKp+*5yS*->w3L*c15CHB!5$^UKSS58vg0RRBm
C)akbX

diff --git a/src/main.js b/src/main.js
index 6469ba5c..fe0fed94 100644
--- a/src/main.js
+++ b/src/main.js
@@ -32,8 +32,8 @@ import VTooltip from 'v-tooltip'
 
 import afterStoreSetup from './boot/after_store.js'
 
-import './font/css/fontello.css'
-import './font/css/animation.css'
+// import './font/css/fontello.css'
+// import './font/css/animation.css'
 
 const currentLocale = (window.navigator.language || 'en').split('-')[0]
 
diff --git a/src/font/config.json b/static/fontello.json
similarity index 100%
rename from src/font/config.json
rename to static/fontello.json
diff --git a/yarn.lock b/yarn.lock
index 4e0d9a22..31209805 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -227,6 +227,13 @@ agent-base@2:
     extend "~3.0.0"
     semver "~5.0.1"
 
+agent-base@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
+  integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
+  dependencies:
+    es6-promisify "^5.0.0"
+
 ajv-errors@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
@@ -1165,6 +1172,14 @@ binary-extensions@^1.0.0:
   version "1.12.0"
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14"
 
+"binary@>= 0.3.0 < 1":
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
+  integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=
+  dependencies:
+    buffers "~0.1.1"
+    chainsaw "~0.1.0"
+
 blob@0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
@@ -1347,6 +1362,11 @@ buffer@^4.3.0:
     ieee754 "^1.1.4"
     isarray "^1.0.0"
 
+buffers@~0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
+  integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s=
+
 builtin-modules@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@@ -1483,6 +1503,13 @@ chai@^3.5.0:
     deep-eql "^0.1.3"
     type-detect "^1.0.0"
 
+chainsaw@~0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
+  integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=
+  dependencies:
+    traverse ">=0.3.0 <0.4"
+
 chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -2359,6 +2386,13 @@ encodeurl@~1.0.1, encodeurl@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
 
+encoding@^0.1.11:
+  version "0.1.12"
+  resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
+  integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
+  dependencies:
+    iconv-lite "~0.4.13"
+
 end-of-stream@^1.0.0, end-of-stream@^1.1.0:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
@@ -2449,6 +2483,18 @@ es-to-primitive@^1.2.0:
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
+es6-promise@^4.0.3:
+  version "4.2.8"
+  resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+  integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
+es6-promisify@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
+  integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
+  dependencies:
+    es6-promise "^4.0.3"
+
 escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -3010,6 +3056,18 @@ follow-redirects@^1.0.0:
   dependencies:
     debug "=3.1.0"
 
+"fontello-webpack-plugin@https://github.com/sypl/fontello-webpack-plugin.git#35dac8cfd851bc1b3be19fd97e361516a1be6633":
+  version "0.4.8"
+  resolved "https://github.com/sypl/fontello-webpack-plugin.git#35dac8cfd851bc1b3be19fd97e361516a1be6633"
+  dependencies:
+    form-data "^2.1.2"
+    html-webpack-plugin "^3.2.0"
+    https-proxy-agent "^2.1.1"
+    lodash "^4.17.4"
+    node-fetch "^1.6.3"
+    unzip "^0.1.11"
+    webpack-sources "^0.2.0"
+
 for-in@^1.0.1, for-in@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -3024,6 +3082,15 @@ forever-agent@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
 
+form-data@^2.1.2:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
+  integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.6"
+    mime-types "^2.1.12"
+
 form-data@~2.3.2:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@@ -3085,6 +3152,16 @@ fsevents@^1.2.7:
     nan "^2.12.1"
     node-pre-gyp "^0.12.0"
 
+"fstream@>= 0.1.30 < 1":
+  version "0.1.31"
+  resolved "https://registry.yarnpkg.com/fstream/-/fstream-0.1.31.tgz#7337f058fbbbbefa8c9f561a28cab0849202c988"
+  integrity sha1-czfwWPu7vvqMn1YaKMqwhJICyYg=
+  dependencies:
+    graceful-fs "~3.0.2"
+    inherits "~2.0.0"
+    mkdirp "0.5"
+    rimraf "2"
+
 ftp@~0.3.10:
   version "0.3.10"
   resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
@@ -3244,6 +3321,13 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
   version "4.1.15"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
 
+graceful-fs@~3.0.2:
+  version "3.0.12"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.12.tgz#0034947ce9ed695ec8ab0b854bc919e82b1ffaef"
+  integrity sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==
+  dependencies:
+    natives "^1.1.3"
+
 "graceful-readlink@>= 1.0.0":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
@@ -3407,7 +3491,7 @@ html-minifier@^3.2.3:
     relateurl "0.2.x"
     uglify-js "3.4.x"
 
-html-webpack-plugin@^3.0.0:
+html-webpack-plugin@^3.0.0, html-webpack-plugin@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b"
   dependencies:
@@ -3493,13 +3577,21 @@ https-proxy-agent@1:
     debug "2"
     extend "3"
 
+https-proxy-agent@^2.1.1:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
+  integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
+  dependencies:
+    agent-base "^4.3.0"
+    debug "^3.1.0"
+
 iconv-lite@0.4.23, iconv-lite@^0.4.4:
   version "0.4.23"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-iconv-lite@^0.4.24:
+iconv-lite@^0.4.24, iconv-lite@~0.4.13:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   dependencies:
@@ -3599,6 +3691,11 @@ inherits@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
 
+inherits@~2.0.0:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
 ini@~1.3.0:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
@@ -3859,7 +3956,7 @@ is-regex@^1.0.4:
   dependencies:
     has "^1.0.1"
 
-is-stream@^1.1.0:
+is-stream@^1.0.1, is-stream@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
 
@@ -4622,6 +4719,14 @@ map-visit@^1.0.0:
   dependencies:
     object-visit "^1.0.0"
 
+"match-stream@>= 0.0.2 < 1":
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/match-stream/-/match-stream-0.0.2.tgz#99eb050093b34dffade421b9ac0b410a9cfa17cf"
+  integrity sha1-mesFAJOzTf+t5CG5rAtBCpz6F88=
+  dependencies:
+    buffers "~0.1.1"
+    readable-stream "~1.0.0"
+
 math-expression-evaluator@^1.2.14:
   version "1.2.17"
   resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
@@ -4826,7 +4931,7 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5, mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   dependencies:
@@ -4920,6 +5025,11 @@ native-promise-only@^0.8.1:
   version "0.8.1"
   resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11"
 
+natives@^1.1.3:
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.6.tgz#a603b4a498ab77173612b9ea1acdec4d980f00bb"
+  integrity sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==
+
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -4973,6 +5083,14 @@ no-case@^2.2.0:
   dependencies:
     lower-case "^1.1.1"
 
+node-fetch@^1.6.3:
+  version "1.7.3"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
+  integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
+  dependencies:
+    encoding "^0.1.11"
+    is-stream "^1.0.1"
+
 node-libs-browser@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.0.tgz#c72f60d9d46de08a940dedbb25f3ffa2f9bbaa77"
@@ -5249,6 +5367,11 @@ osenv@^0.1.4:
     os-homedir "^1.0.0"
     os-tmpdir "^1.0.0"
 
+"over@>= 0.0.5 < 1":
+  version "0.0.5"
+  resolved "https://registry.yarnpkg.com/over/-/over-0.0.5.tgz#f29852e70fd7e25f360e013a8ec44c82aedb5708"
+  integrity sha1-8phS5w/X4l82DgE6jsRMgq7bVwg=
+
 p-defer@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
@@ -5925,6 +6048,16 @@ public-encrypt@^4.0.0:
     randombytes "^2.0.1"
     safe-buffer "^5.1.2"
 
+"pullstream@>= 0.4.1 < 1":
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/pullstream/-/pullstream-0.4.1.tgz#d6fb3bf5aed697e831150eb1002c25a3f8ae1314"
+  integrity sha1-1vs79a7Wl+gxFQ6xACwlo/iuExQ=
+  dependencies:
+    over ">= 0.0.5 < 1"
+    readable-stream "~1.0.31"
+    setimmediate ">= 1.0.2 < 2"
+    slice-stream ">= 1.0.0 < 2"
+
 pump@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
@@ -6089,7 +6222,7 @@ read-pkg@^2.0.0:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
-readable-stream@1.0:
+readable-stream@1.0, readable-stream@~1.0.0, readable-stream@~1.0.31:
   version "1.0.34"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
   dependencies:
@@ -6339,6 +6472,13 @@ rfdc@^1.1.2:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
 
+rimraf@2:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+  integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+  dependencies:
+    glob "^7.1.3"
+
 rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2:
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
@@ -6507,7 +6647,7 @@ set-value@^2.0.0:
     is-plain-object "^2.0.3"
     split-string "^3.0.1"
 
-setimmediate@^1.0.4:
+"setimmediate@>= 1.0.1 < 2", "setimmediate@>= 1.0.2 < 2", setimmediate@^1.0.4:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
 
@@ -6579,6 +6719,13 @@ slice-ansi@^2.1.0:
     astral-regex "^1.0.0"
     is-fullwidth-code-point "^2.0.0"
 
+"slice-stream@>= 1.0.0 < 2":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/slice-stream/-/slice-stream-1.0.0.tgz#5b33bd66f013b1a7f86460b03d463dec39ad3ea0"
+  integrity sha1-WzO9ZvATsaf4ZGCwPUY97DmtPqA=
+  dependencies:
+    readable-stream "~1.0.31"
+
 smart-buffer@^1.0.13:
   version "1.1.15"
   resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16"
@@ -6673,6 +6820,11 @@ sort-keys@^1.0.0:
   dependencies:
     is-plain-obj "^1.0.0"
 
+source-list-map@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.2.tgz#9889019d1024cce55cdc069498337ef6186a11a1"
+  integrity sha1-mIkBnRAkzOVc3AaUmDN+9hhqEaE=
+
 source-list-map@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -6704,7 +6856,7 @@ source-map-url@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
 
-source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7:
+source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.3:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
 
@@ -7102,6 +7254,11 @@ tough-cookie@~2.4.3:
     psl "^1.1.24"
     punycode "^1.4.1"
 
+"traverse@>=0.3.0 <0.4":
+  version "0.3.9"
+  resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
+  integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
+
 trim-newlines@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@@ -7212,6 +7369,18 @@ unset-value@^1.0.0:
     has-value "^0.3.1"
     isobject "^3.0.0"
 
+unzip@^0.1.11:
+  version "0.1.11"
+  resolved "https://registry.yarnpkg.com/unzip/-/unzip-0.1.11.tgz#89749c63b058d7d90d619f86b98aa1535d3b97f0"
+  integrity sha1-iXScY7BY19kNYZ+GuYqhU107l/A=
+  dependencies:
+    binary ">= 0.3.0 < 1"
+    fstream ">= 0.1.30 < 1"
+    match-stream ">= 0.0.2 < 1"
+    pullstream ">= 0.4.1 < 1"
+    readable-stream "~1.0.31"
+    setimmediate ">= 1.0.1 < 2"
+
 upath@^1.1.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068"
@@ -7459,6 +7628,14 @@ webpack-merge@^0.14.1:
     lodash.isplainobject "^3.2.0"
     lodash.merge "^3.3.2"
 
+webpack-sources@^0.2.0:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb"
+  integrity sha1-F8Yr+vE8cH+dAsR54Nzd6DgGl/s=
+  dependencies:
+    source-list-map "^1.1.1"
+    source-map "~0.5.3"
+
 webpack-sources@^1.1.0, webpack-sources@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85"

From 57f46e68e4a0d137e8cb65ba603dfe4b8114ebbf Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 3 Dec 2019 10:34:17 -0500
Subject: [PATCH 046/483] remove needless code

---
 src/main.js | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/main.js b/src/main.js
index fe0fed94..a9db1cff 100644
--- a/src/main.js
+++ b/src/main.js
@@ -32,9 +32,6 @@ import VTooltip from 'v-tooltip'
 
 import afterStoreSetup from './boot/after_store.js'
 
-// import './font/css/fontello.css'
-// import './font/css/animation.css'
-
 const currentLocale = (window.navigator.language || 'en').split('-')[0]
 
 Vue.use(Vuex)

From 9d44015ab477634d6b1e42cb096a58453b8d0914 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 3 Dec 2019 11:16:38 -0500
Subject: [PATCH 047/483] add animate-spin class

---
 src/App.scss | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/src/App.scss b/src/App.scss
index 310962b8..925913f2 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -855,3 +855,18 @@ nav {
 .btn.btn-default {
   min-height: 28px;
 }
+
+.animate-spin {
+  animation: spin 2s infinite linear;
+  display: inline-block;
+}
+
+@keyframes spin {
+  0% {
+    transform: rotate(0deg);
+  }
+
+  100% {
+    transform: rotate(359deg);
+  }
+}

From 36589f32d1b2bfd8cb4a1f446909aa2c7287410f Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 3 Dec 2019 13:26:00 -0500
Subject: [PATCH 048/483] use another fork of fontello-webpack-plugin

---
 package.json |   2 +-
 yarn.lock    | 123 ++++++++++++++++++++++++---------------------------
 2 files changed, 60 insertions(+), 65 deletions(-)

diff --git a/package.json b/package.json
index 648ffbdb..3c9598eb 100644
--- a/package.json
+++ b/package.json
@@ -72,7 +72,7 @@
     "eventsource-polyfill": "^0.9.6",
     "express": "^4.13.3",
     "file-loader": "^3.0.1",
-    "fontello-webpack-plugin": "https://github.com/sypl/fontello-webpack-plugin.git#35dac8cfd851bc1b3be19fd97e361516a1be6633",
+    "fontello-webpack-plugin": "https://github.com/w3geo/fontello-webpack-plugin.git#6149eac8f2672ab6da089e8802fbfcac98487186",
     "function-bind": "^1.0.2",
     "html-webpack-plugin": "^3.0.0",
     "http-proxy-middleware": "^0.17.2",
diff --git a/yarn.lock b/yarn.lock
index 31209805..44473c94 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1160,6 +1160,11 @@ better-assert@~1.0.0:
   dependencies:
     callsite "1.0.0"
 
+big-integer@^1.6.17:
+  version "1.6.48"
+  resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e"
+  integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==
+
 big.js@^3.1.3:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
@@ -1172,7 +1177,7 @@ binary-extensions@^1.0.0:
   version "1.12.0"
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14"
 
-"binary@>= 0.3.0 < 1":
+binary@~0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
   integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=
@@ -1192,6 +1197,11 @@ bluebird@^3.5.3:
   version "3.5.4"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714"
 
+bluebird@~3.4.1:
+  version "3.4.7"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
+  integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=
+
 bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
   version "4.11.8"
   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
@@ -1350,6 +1360,11 @@ buffer-from@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
 
+buffer-indexof-polyfill@~1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz#a9fb806ce8145d5428510ce72f278bb363a638bf"
+  integrity sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8=
+
 buffer-xor@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@@ -2334,6 +2349,13 @@ domutils@^1.5.1:
     dom-serializer "0"
     domelementtype "1"
 
+duplexer2@~0.1.4:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
+  integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=
+  dependencies:
+    readable-stream "^2.0.2"
+
 duplexify@^3.4.2, duplexify@^3.6.0:
   version "3.7.1"
   resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
@@ -3056,16 +3078,16 @@ follow-redirects@^1.0.0:
   dependencies:
     debug "=3.1.0"
 
-"fontello-webpack-plugin@https://github.com/sypl/fontello-webpack-plugin.git#35dac8cfd851bc1b3be19fd97e361516a1be6633":
+"fontello-webpack-plugin@https://github.com/w3geo/fontello-webpack-plugin.git#6149eac8f2672ab6da089e8802fbfcac98487186":
   version "0.4.8"
-  resolved "https://github.com/sypl/fontello-webpack-plugin.git#35dac8cfd851bc1b3be19fd97e361516a1be6633"
+  resolved "https://github.com/w3geo/fontello-webpack-plugin.git#6149eac8f2672ab6da089e8802fbfcac98487186"
   dependencies:
     form-data "^2.1.2"
     html-webpack-plugin "^3.2.0"
     https-proxy-agent "^2.1.1"
     lodash "^4.17.4"
     node-fetch "^1.6.3"
-    unzip "^0.1.11"
+    unzipper "^0.10.5"
     webpack-sources "^0.2.0"
 
 for-in@^1.0.1, for-in@^1.0.2:
@@ -3152,14 +3174,14 @@ fsevents@^1.2.7:
     nan "^2.12.1"
     node-pre-gyp "^0.12.0"
 
-"fstream@>= 0.1.30 < 1":
-  version "0.1.31"
-  resolved "https://registry.yarnpkg.com/fstream/-/fstream-0.1.31.tgz#7337f058fbbbbefa8c9f561a28cab0849202c988"
-  integrity sha1-czfwWPu7vvqMn1YaKMqwhJICyYg=
+fstream@^1.0.12:
+  version "1.0.12"
+  resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
+  integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
   dependencies:
-    graceful-fs "~3.0.2"
+    graceful-fs "^4.1.2"
     inherits "~2.0.0"
-    mkdirp "0.5"
+    mkdirp ">=0.5 0"
     rimraf "2"
 
 ftp@~0.3.10:
@@ -3321,12 +3343,10 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
   version "4.1.15"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
 
-graceful-fs@~3.0.2:
-  version "3.0.12"
-  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.12.tgz#0034947ce9ed695ec8ab0b854bc919e82b1ffaef"
-  integrity sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==
-  dependencies:
-    natives "^1.1.3"
+graceful-fs@^4.2.2:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
+  integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
 
 "graceful-readlink@>= 1.0.0":
   version "1.0.1"
@@ -4300,6 +4320,11 @@ lie@3.1.1:
   dependencies:
     immediate "~3.0.5"
 
+listenercount@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937"
+  integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=
+
 load-json-file@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -4719,14 +4744,6 @@ map-visit@^1.0.0:
   dependencies:
     object-visit "^1.0.0"
 
-"match-stream@>= 0.0.2 < 1":
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/match-stream/-/match-stream-0.0.2.tgz#99eb050093b34dffade421b9ac0b410a9cfa17cf"
-  integrity sha1-mesFAJOzTf+t5CG5rAtBCpz6F88=
-  dependencies:
-    buffers "~0.1.1"
-    readable-stream "~1.0.0"
-
 math-expression-evaluator@^1.2.14:
   version "1.2.17"
   resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
@@ -4931,7 +4948,7 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mkdirp@0.5, mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   dependencies:
@@ -5025,11 +5042,6 @@ native-promise-only@^0.8.1:
   version "0.8.1"
   resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11"
 
-natives@^1.1.3:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.6.tgz#a603b4a498ab77173612b9ea1acdec4d980f00bb"
-  integrity sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==
-
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -5367,11 +5379,6 @@ osenv@^0.1.4:
     os-homedir "^1.0.0"
     os-tmpdir "^1.0.0"
 
-"over@>= 0.0.5 < 1":
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/over/-/over-0.0.5.tgz#f29852e70fd7e25f360e013a8ec44c82aedb5708"
-  integrity sha1-8phS5w/X4l82DgE6jsRMgq7bVwg=
-
 p-defer@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
@@ -6048,16 +6055,6 @@ public-encrypt@^4.0.0:
     randombytes "^2.0.1"
     safe-buffer "^5.1.2"
 
-"pullstream@>= 0.4.1 < 1":
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/pullstream/-/pullstream-0.4.1.tgz#d6fb3bf5aed697e831150eb1002c25a3f8ae1314"
-  integrity sha1-1vs79a7Wl+gxFQ6xACwlo/iuExQ=
-  dependencies:
-    over ">= 0.0.5 < 1"
-    readable-stream "~1.0.31"
-    setimmediate ">= 1.0.2 < 2"
-    slice-stream ">= 1.0.0 < 2"
-
 pump@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
@@ -6222,7 +6219,7 @@ read-pkg@^2.0.0:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
-readable-stream@1.0, readable-stream@~1.0.0, readable-stream@~1.0.31:
+readable-stream@1.0:
   version "1.0.34"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
   dependencies:
@@ -6647,9 +6644,10 @@ set-value@^2.0.0:
     is-plain-object "^2.0.3"
     split-string "^3.0.1"
 
-"setimmediate@>= 1.0.1 < 2", "setimmediate@>= 1.0.2 < 2", setimmediate@^1.0.4:
+setimmediate@^1.0.4, setimmediate@~1.0.4:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
+  integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
 
 setprototypeof@1.1.0:
   version "1.1.0"
@@ -6719,13 +6717,6 @@ slice-ansi@^2.1.0:
     astral-regex "^1.0.0"
     is-fullwidth-code-point "^2.0.0"
 
-"slice-stream@>= 1.0.0 < 2":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/slice-stream/-/slice-stream-1.0.0.tgz#5b33bd66f013b1a7f86460b03d463dec39ad3ea0"
-  integrity sha1-WzO9ZvATsaf4ZGCwPUY97DmtPqA=
-  dependencies:
-    readable-stream "~1.0.31"
-
 smart-buffer@^1.0.13:
   version "1.1.15"
   resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16"
@@ -7369,17 +7360,21 @@ unset-value@^1.0.0:
     has-value "^0.3.1"
     isobject "^3.0.0"
 
-unzip@^0.1.11:
-  version "0.1.11"
-  resolved "https://registry.yarnpkg.com/unzip/-/unzip-0.1.11.tgz#89749c63b058d7d90d619f86b98aa1535d3b97f0"
-  integrity sha1-iXScY7BY19kNYZ+GuYqhU107l/A=
+unzipper@^0.10.5:
+  version "0.10.5"
+  resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.5.tgz#4d189ae6f8af634b26efe1a1817c399e0dd4a1a0"
+  integrity sha512-i5ufkXNjWZYxU/0nKKf6LkvW8kn9YzRvfwuPWjXP+JTFce/8bqeR0gEfbiN2IDdJa6ZU6/2IzFRLK0z1v0uptw==
   dependencies:
-    binary ">= 0.3.0 < 1"
-    fstream ">= 0.1.30 < 1"
-    match-stream ">= 0.0.2 < 1"
-    pullstream ">= 0.4.1 < 1"
-    readable-stream "~1.0.31"
-    setimmediate ">= 1.0.1 < 2"
+    big-integer "^1.6.17"
+    binary "~0.3.0"
+    bluebird "~3.4.1"
+    buffer-indexof-polyfill "~1.0.0"
+    duplexer2 "~0.1.4"
+    fstream "^1.0.12"
+    graceful-fs "^4.2.2"
+    listenercount "~1.0.1"
+    readable-stream "~2.3.6"
+    setimmediate "~1.0.4"
 
 upath@^1.1.1:
   version "1.1.2"

From d37caeeded0ebe90a2e922205eed94b5cc4fcca4 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 3 Dec 2019 14:40:51 -0500
Subject: [PATCH 049/483] add html-webpack-plugin to karma config

---
 test/unit/karma.conf.js | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js
index 8af05c24..45d74f14 100644
--- a/test/unit/karma.conf.js
+++ b/test/unit/karma.conf.js
@@ -5,6 +5,7 @@
 
 // var path = require('path')
 var merge = require('webpack-merge')
+var HtmlWebpackPlugin = require('html-webpack-plugin')
 var baseConfig = require('../../build/webpack.base.conf')
 var utils = require('../../build/utils')
 var webpack = require('webpack')
@@ -24,6 +25,11 @@ var webpackConfig = merge(baseConfig, {
   plugins: [
     new webpack.DefinePlugin({
       'process.env': require('../../config/test.env')
+    }),
+    new HtmlWebpackPlugin({
+      filename: 'index.html',
+      template: 'index.html',
+      inject: true
     })
   ]
 })

From 13fc2612ae388dec682829ae2b6211bb3cb8ccb3 Mon Sep 17 00:00:00 2001
From: Wyatt Benno <wyattbenno@gmail.com>
Date: Thu, 5 Dec 2019 11:48:37 +0900
Subject: [PATCH 050/483] Change 403 messaging

---
 src/components/timeline/timeline.js           |  7 ++++++-
 src/components/timeline/timeline.vue          | 19 ++++++++++++++++---
 src/i18n/en.json                              |  2 ++
 src/modules/statuses.js                       |  7 +++++++
 src/services/api/api.service.js               | 10 ++++++++--
 .../timeline_fetcher.service.js               |  6 ++++++
 6 files changed, 45 insertions(+), 6 deletions(-)

diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 27a9a55e..6086336c 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -36,7 +36,12 @@ const Timeline = {
     }
   },
   computed: {
-    timelineError () { return this.$store.state.statuses.error },
+    timelineError () {
+      return this.$store.state.statuses.error
+    },
+    error403 () {
+      return this.$store.state.statuses.error403
+    },
     newStatusCount () {
       return this.timeline.newStatusCount
     },
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 93f6f570..1c45d0f6 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -11,15 +11,22 @@
       >
         {{ $t('timeline.error_fetching') }}
       </div>
+      <div
+        v-else-if="error403"
+        class="loadmore-error alert error"
+        @click.prevent
+      >
+        {{ $t('timeline.error_403') }}
+      </div>
       <button
-        v-if="timeline.newStatusCount > 0 && !timelineError"
+        v-if="timeline.newStatusCount > 0 && !timelineError && !error403"
         class="loadmore-button"
         @click.prevent="showNewStatuses"
       >
         {{ $t('timeline.show_new') }}{{ newStatusCountStr }}
       </button>
       <div
-        v-if="!timeline.newStatusCount > 0 && !timelineError"
+        v-if="!timeline.newStatusCount > 0 && !timelineError && !error403"
         class="loadmore-text faint"
         @click.prevent
       >
@@ -67,12 +74,18 @@
         {{ $t('timeline.no_more_statuses') }}
       </div>
       <a
-        v-else-if="!timeline.loading"
+        v-else-if="!timeline.loading && !error403"
         href="#"
         @click.prevent="fetchOlderStatuses()"
       >
         <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div>
       </a>
+      <a
+        v-else-if="error403"
+        href="#"
+      >
+        <div class="new-status-notification text-center panel-footer">{{ $t('timeline.error_403_message') }}</div>
+      </a>
       <div
         v-else
         class="new-status-notification text-center panel-footer"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 85146ef5..c203e156 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -535,6 +535,8 @@
     "collapse": "Collapse",
     "conversation": "Conversation",
     "error_fetching": "Error fetching updates",
+    "error_403": "This timeline is not public.",
+    "error_403_message": "Please sign in.",
     "load_older": "Load older statuses",
     "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
     "repeated": "repeated",
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index f11ffdcd..d0e871c8 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -38,6 +38,7 @@ export const defaultState = () => ({
   notifications: emptyNotifications(),
   favorites: new Set(),
   error: false,
+  error403: false,
   timelines: {
     mentions: emptyTl(),
     public: emptyTl(),
@@ -479,6 +480,9 @@ export const mutations = {
   setError (state, { value }) {
     state.error = value
   },
+  set403Error (state, { value }) {
+    state.error403 = value
+  },
   setNotificationsLoading (state, { value }) {
     state.notifications.loading = value
   },
@@ -528,6 +532,9 @@ const statuses = {
     setError ({ rootState, commit }, { value }) {
       commit('setError', { value })
     },
+    set403Error ({ rootState, commit }, { value }) {
+      commit('set403Error', { value })
+    },
     setNotificationsLoading ({ rootState, commit }, { value }) {
       commit('setNotificationsLoading', { value })
     },
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 8f5eb416..a2aa802f 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -532,13 +532,19 @@ const fetchTimeline = ({
 
   return fetch(url, { headers: authHeaders(credentials) })
     .then((data) => {
-      if (data.ok) {
+      if (data.ok || data.status === 403) {
         return data
       }
       throw new Error('Error fetching timeline', data)
     })
     .then((data) => data.json())
-    .then((data) => data.map(isNotifications ? parseNotification : parseStatus))
+    .then((data) => {
+      if (!data.error) {
+        return data.map(isNotifications ? parseNotification : parseStatus)
+      } else {
+        return data
+      }
+    })
 }
 
 const fetchPinnedStatuses = ({ id, credentials }) => {
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index 9eb30c2d..9352d73a 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -6,6 +6,7 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => {
   const ccTimeline = camelCase(timeline)
 
   store.dispatch('setError', { value: false })
+  store.dispatch('set403Error', { value: false })
 
   store.dispatch('addNewStatuses', {
     timeline: ccTimeline,
@@ -45,6 +46,11 @@ const fetchAndUpdate = ({
 
   return apiService.fetchTimeline(args)
     .then((statuses) => {
+      // Change messaging if not public
+      if (statuses.error) {
+        store.dispatch('set403Error', { value: true })
+        return
+      }
       if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {
         store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId })
       }

From 9aac873d6b50e6576578c0aac131971f6af4e4c4 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Thu, 5 Dec 2019 22:46:05 +0900
Subject: [PATCH 051/483] Change output directory of fontello

---
 build/webpack.base.conf.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
index 9313ec20..5cc0135e 100644
--- a/build/webpack.base.conf.js
+++ b/build/webpack.base.conf.js
@@ -98,8 +98,8 @@ module.exports = {
       config: require('../static/fontello.json'),
       name: 'fontello',
       output: {
-        css: '[name].' + now + '.css',  // [hash] is not supported. Use the current timestamp instead for versioning.
-        font: 'font/[name].' + now + '.[ext]'
+        css: 'static/[name].' + now + '.css',  // [hash] is not supported. Use the current timestamp instead for versioning.
+        font: 'static/font/[name].' + now + '.[ext]'
       }
     })
   ]

From 01855f315c7db813d7bbf2f3683017819d2fb70c Mon Sep 17 00:00:00 2001
From: Wyatt Benno <wyattbenno@gmail.com>
Date: Fri, 6 Dec 2019 09:38:55 +0900
Subject: [PATCH 052/483] Wording updates

---
 src/components/timeline/timeline.vue                      | 2 +-
 src/i18n/en.json                                          | 3 +--
 src/services/timeline_fetcher/timeline_fetcher.service.js | 2 +-
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 1c45d0f6..e4d453b8 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -84,7 +84,7 @@
         v-else-if="error403"
         href="#"
       >
-        <div class="new-status-notification text-center panel-footer">{{ $t('timeline.error_403_message') }}</div>
+        <div class="new-status-notification text-center panel-footer">{{ error403 }}</div>
       </a>
       <div
         v-else
diff --git a/src/i18n/en.json b/src/i18n/en.json
index c203e156..1dd99062 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -535,8 +535,7 @@
     "collapse": "Collapse",
     "conversation": "Conversation",
     "error_fetching": "Error fetching updates",
-    "error_403": "This timeline is not public.",
-    "error_403_message": "Please sign in.",
+    "error_403": "Access denied",
     "load_older": "Load older statuses",
     "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
     "repeated": "repeated",
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index 9352d73a..b7952050 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -48,7 +48,7 @@ const fetchAndUpdate = ({
     .then((statuses) => {
       // Change messaging if not public
       if (statuses.error) {
-        store.dispatch('set403Error', { value: true })
+        store.dispatch('set403Error', { value: statuses.error })
         return
       }
       if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {

From 914c78398469988f549930bd35d044082d155277 Mon Sep 17 00:00:00 2001
From: Absturztaube <me@absturztau.be>
Date: Sat, 7 Dec 2019 10:29:53 +0100
Subject: [PATCH 053/483] fix sticker picker height in emojo picker

---
 src/components/emoji_picker/emoji_picker.scss | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 6608f393..29010c5b 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -101,6 +101,10 @@
     }
   }
 
+  .stickers-content {
+    min-height: 269px;
+  }
+
   .emoji {
     &-search {
       padding: 5px;

From 3b11860d3445271c9495b08f8c68330e48c5f33e Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Sun, 8 Dec 2019 02:05:50 +0300
Subject: [PATCH 054/483] Remove whitespace hack on empty post content

It's no longer necessary since the backend handles posts with empty
content fine and also fixes an odd whitespace when attachment links are
disabled.
---
 src/components/post_status_form/post_status_form.js | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index af6299e4..74067fef 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -169,9 +169,7 @@ const PostStatusForm = {
       if (this.submitDisabled) { return }
 
       if (this.newStatus.status === '') {
-        if (this.newStatus.files.length > 0) {
-          this.newStatus.status = '\u200b' // hack
-        } else {
+        if (this.newStatus.files.length === 0) {
           this.error = 'Cannot post an empty status with no files'
           return
         }

From a06705d9390b738ccace9fb6a0c213306f753f42 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 8 Dec 2019 13:52:26 +0300
Subject: [PATCH 055/483] Added OAuth 'push' and 'admin' scopes.

---
 src/services/new_api/oauth.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/services/new_api/oauth.js b/src/services/new_api/oauth.js
index d0d18c03..3c8e64bd 100644
--- a/src/services/new_api/oauth.js
+++ b/src/services/new_api/oauth.js
@@ -12,7 +12,7 @@ export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) =>
 
   form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`)
   form.append('redirect_uris', REDIRECT_URI)
-  form.append('scopes', 'read write follow')
+  form.append('scopes', 'read write follow push admin')
 
   return window.fetch(url, {
     method: 'POST',
@@ -28,7 +28,7 @@ const login = ({ instance, clientId }) => {
     response_type: 'code',
     client_id: clientId,
     redirect_uri: REDIRECT_URI,
-    scope: 'read write follow'
+    scope: 'read write follow push admin'
   }
 
   const dataString = reduce(data, (acc, v, k) => {

From ff95d865d223fed5bab2a4d72ff3a22f014d3c56 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 8 Dec 2019 16:05:41 +0200
Subject: [PATCH 056/483] Updated streaming and improved error-handling, some
 more refactoring to api

---
 .../public_and_external_timeline.js           |  2 +-
 .../public_timeline/public_timeline.js        |  2 +-
 src/components/tag_timeline/tag_timeline.js   |  2 +-
 src/components/user_profile/user_profile.js   |  6 +-
 src/modules/api.js                            | 99 +++++++++++++------
 src/modules/users.js                          |  8 +-
 .../backend_interactor_service.js             |  5 +-
 7 files changed, 84 insertions(+), 40 deletions(-)

diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.js b/src/components/public_and_external_timeline/public_and_external_timeline.js
index f614c13b..cbd4491b 100644
--- a/src/components/public_and_external_timeline/public_and_external_timeline.js
+++ b/src/components/public_and_external_timeline/public_and_external_timeline.js
@@ -10,7 +10,7 @@ const PublicAndExternalTimeline = {
     this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' })
   },
   destroyed () {
-    this.$store.dispatch('stopFetching', 'publicAndExternal')
+    this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal')
   }
 }
 
diff --git a/src/components/public_timeline/public_timeline.js b/src/components/public_timeline/public_timeline.js
index 8976a99c..66c40d3a 100644
--- a/src/components/public_timeline/public_timeline.js
+++ b/src/components/public_timeline/public_timeline.js
@@ -10,7 +10,7 @@ const PublicTimeline = {
     this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
   },
   destroyed () {
-    this.$store.dispatch('stopFetching', 'public')
+    this.$store.dispatch('stopFetchingTimeline', 'public')
   }
 
 }
diff --git a/src/components/tag_timeline/tag_timeline.js b/src/components/tag_timeline/tag_timeline.js
index 458eb1c5..400c6a4b 100644
--- a/src/components/tag_timeline/tag_timeline.js
+++ b/src/components/tag_timeline/tag_timeline.js
@@ -19,7 +19,7 @@ const TagTimeline = {
     }
   },
   destroyed () {
-    this.$store.dispatch('stopFetching', 'tag')
+    this.$store.dispatch('stopFetchingTimeline', 'tag')
   }
 }
 
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 00055707..9558a0bd 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -112,9 +112,9 @@ const UserProfile = {
       }
     },
     stopFetching () {
-      this.$store.dispatch('stopFetching', 'user')
-      this.$store.dispatch('stopFetching', 'favorites')
-      this.$store.dispatch('stopFetching', 'media')
+      this.$store.dispatch('stopFetchingTimeline', 'user')
+      this.$store.dispatch('stopFetchingTimeline', 'favorites')
+      this.$store.dispatch('stopFetchingTimeline', 'media')
     },
     switchUser (userNameOrId) {
       this.stopFetching()
diff --git a/src/modules/api.js b/src/modules/api.js
index 0e7e5e19..185c9db6 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -6,7 +6,7 @@ const api = {
     backendInteractor: backendInteractorService(),
     fetchers: {},
     socket: null,
-    mastoSocket: null,
+    mastoUserSocket: null,
     followRequests: []
   },
   mutations: {
@@ -16,7 +16,8 @@ const api = {
     addFetcher (state, { fetcherName, fetcher }) {
       state.fetchers[fetcherName] = fetcher
     },
-    removeFetcher (state, { fetcherName }) {
+    removeFetcher (state, { fetcherName, fetcher }) {
+      window.clearInterval(fetcher)
       delete state.fetchers[fetcherName]
     },
     setWsToken (state, token) {
@@ -30,13 +31,14 @@ const api = {
     }
   },
   actions: {
-    startMastoSocket (store) {
+    // MastoAPI 'User' sockets
+    startMastoUserSocket (store) {
       const { state, dispatch } = store
-      state.mastoSocket = state.backendInteractor
+      state.mastoUserSocket = state.backendInteractor
         .startUserSocket({
           store,
           onMessage: (message) => {
-            if (!message) return
+            if (!message) return // pings
             if (message.event === 'notification') {
               dispatch('addNewNotifications', {
                 notifications: [message.notification],
@@ -52,41 +54,86 @@ const api = {
             }
           }
         })
-      state.mastoSocket.addEventListener('error', error => {
-        console.error('Error with MastoAPI websocket:', error)
-        dispatch('startFetchingTimeline', { timeline: 'friends' })
-        dispatch('startFetchingNotifications')
+      state.mastoUserSocket.addEventListener('error', error => {
+        console.error('Error in MastoAPI websocket:', error)
+      })
+      state.mastoUserSocket.addEventListener('close', closeEvent => {
+        const ignoreCodes = new Set([
+          1000, // Normal (intended) closure
+          1001 // Going away
+        ])
+        const { code } = closeEvent
+        console.debug('Socket closure event:', closeEvent)
+        if (ignoreCodes.has(code)) {
+          console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
+        } else {
+          console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
+          dispatch('startFetchingTimeline', { timeline: 'friends' })
+          dispatch('startFetchingNotifications')
+          dispatch('restartMastoUserSocket')
+        }
       })
     },
-    startFetchingTimeline (store, { timeline = 'friends', tag = false, userId = false }) {
-      // Don't start fetching if we already are.
+    restartMastoUserSocket ({ dispatch }) {
+      // This basically starts MastoAPI user socket and stops conventional
+      // fetchers when connection reestablished
+      dispatch('startMastoUserSocket').then(() => {
+        dispatch('stopFetchingTimeline', { timeline: 'friends' })
+        dispatch('stopFetchingNotifications')
+      })
+    },
+
+    // Timelines
+    startFetchingTimeline (store, {
+      timeline = 'friends',
+      tag = false,
+      userId = false
+    }) {
       if (store.state.fetchers[timeline]) return
 
-      const fetcher = store.state.backendInteractor.startFetchingTimeline({ timeline, store, userId, tag })
+      const fetcher = store.state.backendInteractor.startFetchingTimeline({
+        timeline, store, userId, tag
+      })
       store.commit('addFetcher', { fetcherName: timeline, fetcher })
     },
-    startFetchingNotifications (store) {
-      // Don't start fetching if we already are.
-      if (store.state.fetchers['notifications']) return
+    stopFetchingTimeline (store, timeline) {
+      const fetcher = store.state.fetchers[timeline]
+      if (!fetcher) return
+      store.commit('removeFetcher', { fetcherName: timeline, fetcher })
+    },
 
+    // Notifications
+    startFetchingNotifications (store) {
+      if (store.state.fetchers.notifications) return
       const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
       store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
     },
+    stopFetchingNotifications (store) {
+      const fetcher = store.state.fetchers.notifications
+      if (!fetcher) return
+      store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
+    },
     fetchAndUpdateNotifications (store) {
       store.state.backendInteractor.fetchAndUpdateNotifications({ store })
     },
-    startFetchingFollowRequest (store) {
-      // Don't start fetching if we already are.
-      if (store.state.fetchers['followRequest']) return
 
-      const fetcher = store.state.backendInteractor.startFetchingFollowRequest({ store })
-      store.commit('addFetcher', { fetcherName: 'followRequest', fetcher })
+    // Follow requests
+    startFetchingFollowRequests (store) {
+      if (store.state.fetchers['followRequests']) return
+      const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
+      store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
     },
-    stopFetching (store, fetcherName) {
-      const fetcher = store.state.fetchers[fetcherName]
-      window.clearInterval(fetcher)
-      store.commit('removeFetcher', { fetcherName })
+    stopFetchingFollowRequests (store) {
+      const fetcher = store.state.fetchers.followRequests
+      if (!fetcher) return
+      store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
     },
+    removeFollowRequest (store, request) {
+      let requests = store.state.followRequests.filter((it) => it !== request)
+      store.commit('setFollowRequests', requests)
+    },
+
+    // Pleroma websocket
     setWsToken (store, token) {
       store.commit('setWsToken', token)
     },
@@ -104,10 +151,6 @@ const api = {
     disconnectFromSocket ({ commit, state }) {
       state.socket && state.socket.disconnect()
       commit('setSocket', null)
-    },
-    removeFollowRequest (store, request) {
-      let requests = store.state.followRequests.filter((it) => it !== request)
-      store.commit('setFollowRequests', requests)
     }
   }
 }
diff --git a/src/modules/users.js b/src/modules/users.js
index eff0c5d5..6bdaf193 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -431,10 +431,10 @@ const users = {
           store.commit('clearCurrentUser')
           store.dispatch('disconnectFromSocket')
           store.commit('clearToken')
-          store.dispatch('stopFetching', 'friends')
+          store.dispatch('stopFetchingTimeline', 'friends')
           store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
-          store.dispatch('stopFetching', 'notifications')
-          store.dispatch('stopFetching', 'followRequest')
+          store.dispatch('stopFetchingNotifications')
+          store.dispatch('stopFetchingFollowRequests')
           store.commit('clearNotifications')
           store.commit('resetStatuses')
         })
@@ -469,7 +469,7 @@ const users = {
                 store.dispatch('initializeSocket')
               }
 
-              store.dispatch('startMastoSocket').catch((error) => {
+              store.dispatch('startMastoUserSocket').catch((error) => {
                 console.error('Failed initializing MastoAPI Streaming socket', error)
                 // Start getting fresh posts.
                 store.dispatch('startFetchingTimeline', { timeline: 'friends' })
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 850b7867..33b79a40 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -24,10 +24,11 @@ const backendInteractorService = credentials => ({
     const serv = store.rootState.instance.server.replace('http', 'ws')
     const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })
     const socket = new WebSocket(url)
-    console.log(socket)
+    console.debug('Socket created:', socket)
     if (socket) {
+      socket.addEventListener('open', (wsEvent) => console.debug('MastoAPI User WebSocket connection established'))
       socket.addEventListener('message', (wsEvent) => onMessage(handleMastoWS(wsEvent)))
-      socket.addEventListener('error', (error) => console.error('WebSocket Error:', error))
+      socket.addEventListener('error', (error) => console.error('MastoApi User WebSocket Error:', error))
       return socket
     } else {
       throw new Error('failed to connect to socket')

From 505fb260610e557e27bbc5d27515337ea07e0e3e Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 8 Dec 2019 19:18:38 +0200
Subject: [PATCH 057/483] better wrapper for websocket

---
 src/modules/api.js                            | 43 +++++++++--------
 src/services/api/api.service.js               | 46 ++++++++++++++++++-
 .../backend_interactor_service.js             | 15 ++----
 3 files changed, 69 insertions(+), 35 deletions(-)

diff --git a/src/modules/api.js b/src/modules/api.js
index 185c9db6..593f8498 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -34,36 +34,35 @@ const api = {
     // MastoAPI 'User' sockets
     startMastoUserSocket (store) {
       const { state, dispatch } = store
-      state.mastoUserSocket = state.backendInteractor
-        .startUserSocket({
-          store,
-          onMessage: (message) => {
-            if (!message) return // pings
-            if (message.event === 'notification') {
-              dispatch('addNewNotifications', {
-                notifications: [message.notification],
-                older: false
-              })
-            } else if (message.event === 'update') {
-              dispatch('addNewStatuses', {
-                statuses: [message.status],
-                userId: false,
-                showImmediately: false,
-                timeline: 'friends'
-              })
-            }
+      state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
+      state.mastoUserSocket.addEventListener(
+        'message',
+        ({ detail: message }) => {
+          if (!message) return // pings
+          if (message.event === 'notification') {
+            dispatch('addNewNotifications', {
+              notifications: [message.notification],
+              older: false
+            })
+          } else if (message.event === 'update') {
+            dispatch('addNewStatuses', {
+              statuses: [message.status],
+              userId: false,
+              showImmediately: false,
+              timeline: 'friends'
+            })
           }
-        })
-      state.mastoUserSocket.addEventListener('error', error => {
+        }
+      )
+      state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
         console.error('Error in MastoAPI websocket:', error)
       })
-      state.mastoUserSocket.addEventListener('close', closeEvent => {
+      state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
         const ignoreCodes = new Set([
           1000, // Normal (intended) closure
           1001 // Going away
         ])
         const { code } = closeEvent
-        console.debug('Socket closure event:', closeEvent)
         if (ignoreCodes.has(code)) {
           console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
         } else {
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 7f27564f..c6818df4 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -953,8 +953,52 @@ const MASTODON_STREAMING_EVENTS = new Set([
   'filters_changed'
 ])
 
+// A thin wrapper around WebSocket API that allows adding a pre-processor to it
+// Uses EventTarget and a CustomEvent to proxy events
+export const ProcessedWS = ({
+  url,
+  preprocessor = handleMastoWS,
+  id = 'Unknown'
+}) => {
+  const eventTarget = new EventTarget()
+  const socket = new WebSocket(url)
+  if (!socket) throw new Error(`Failed to create socket ${id}`)
+  const proxy = (original, eventName, processor = a => a) => {
+    original.addEventListener(eventName, (eventData) => {
+      eventTarget.dispatchEvent(new CustomEvent(
+        eventName,
+        { detail: processor(eventData) }
+      ))
+    })
+  }
+  socket.addEventListener('open', (wsEvent) => {
+    console.debug(`[WS][${id}] Socket connected`, wsEvent)
+  })
+  socket.addEventListener('error', (wsEvent) => {
+    console.debug(`[WS][${id}] Socket errored`, wsEvent)
+  })
+  socket.addEventListener('close', (wsEvent) => {
+    console.debug(
+      `[WS][${id}] Socket disconnected with code ${wsEvent.code}`,
+      wsEvent
+    )
+  })
+  socket.addEventListener('message', (wsEvent) => {
+    console.debug(
+      `[WS][${id}] Message received`,
+      wsEvent
+    )
+  })
+
+  proxy(socket, 'open')
+  proxy(socket, 'close')
+  proxy(socket, 'message', preprocessor)
+  proxy(socket, 'error')
+
+  return eventTarget
+}
+
 export const handleMastoWS = (wsEvent) => {
-  console.debug('Event', wsEvent)
   const { data } = wsEvent
   if (!data) return
   const parsedEvent = JSON.parse(data)
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 33b79a40..b7372ed0 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -1,4 +1,4 @@
-import apiService, { getMastodonSocketURI, handleMastoWS } from '../api/api.service.js'
+import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.service.js'
 import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
 import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
 import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
@@ -20,19 +20,10 @@ const backendInteractorService = credentials => ({
     return followRequestFetcher.startFetching({ store, credentials })
   },
 
-  startUserSocket ({ store, onMessage }) {
+  startUserSocket ({ store }) {
     const serv = store.rootState.instance.server.replace('http', 'ws')
     const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })
-    const socket = new WebSocket(url)
-    console.debug('Socket created:', socket)
-    if (socket) {
-      socket.addEventListener('open', (wsEvent) => console.debug('MastoAPI User WebSocket connection established'))
-      socket.addEventListener('message', (wsEvent) => onMessage(handleMastoWS(wsEvent)))
-      socket.addEventListener('error', (error) => console.error('MastoApi User WebSocket Error:', error))
-      return socket
-    } else {
-      throw new Error('failed to connect to socket')
-    }
+    return ProcessedWS({ url, id: 'User' })
   },
 
   ...Object.entries(apiService).reduce((acc, [key, func]) => {

From e86af0c965d459d016d638c0822bc9af6a6c0ee1 Mon Sep 17 00:00:00 2001
From: Wyatt Benno <wyattbenno@gmail.com>
Date: Mon, 9 Dec 2019 09:02:34 +0900
Subject: [PATCH 058/483] Change naming, make more general

---
 src/components/timeline/timeline.js                  |  4 ++--
 src/components/timeline/timeline.vue                 | 12 ++++++------
 src/modules/statuses.js                              | 10 +++++-----
 src/services/api/api.service.js                      |  5 +----
 .../timeline_fetcher/timeline_fetcher.service.js     |  6 +++---
 5 files changed, 17 insertions(+), 20 deletions(-)

diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 6086336c..9a53acd6 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -39,8 +39,8 @@ const Timeline = {
     timelineError () {
       return this.$store.state.statuses.error
     },
-    error403 () {
-      return this.$store.state.statuses.error403
+    errorData () {
+      return this.$store.state.statuses.errorData
     },
     newStatusCount () {
       return this.timeline.newStatusCount
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index e4d453b8..d9f4025d 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -12,21 +12,21 @@
         {{ $t('timeline.error_fetching') }}
       </div>
       <div
-        v-else-if="error403"
+        v-else-if="errorData"
         class="loadmore-error alert error"
         @click.prevent
       >
         {{ $t('timeline.error_403') }}
       </div>
       <button
-        v-if="timeline.newStatusCount > 0 && !timelineError && !error403"
+        v-if="timeline.newStatusCount > 0 && !timelineError && !errorData"
         class="loadmore-button"
         @click.prevent="showNewStatuses"
       >
         {{ $t('timeline.show_new') }}{{ newStatusCountStr }}
       </button>
       <div
-        v-if="!timeline.newStatusCount > 0 && !timelineError && !error403"
+        v-if="!timeline.newStatusCount > 0 && !timelineError && !errorData"
         class="loadmore-text faint"
         @click.prevent
       >
@@ -74,17 +74,17 @@
         {{ $t('timeline.no_more_statuses') }}
       </div>
       <a
-        v-else-if="!timeline.loading && !error403"
+        v-else-if="!timeline.loading && !errorData"
         href="#"
         @click.prevent="fetchOlderStatuses()"
       >
         <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div>
       </a>
       <a
-        v-else-if="error403"
+        v-else-if="errorData"
         href="#"
       >
-        <div class="new-status-notification text-center panel-footer">{{ error403 }}</div>
+        <div class="new-status-notification text-center panel-footer">{{ errorData }}</div>
       </a>
       <div
         v-else
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index d0e871c8..4068aa02 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -38,7 +38,7 @@ export const defaultState = () => ({
   notifications: emptyNotifications(),
   favorites: new Set(),
   error: false,
-  error403: false,
+  errorData: '',
   timelines: {
     mentions: emptyTl(),
     public: emptyTl(),
@@ -480,8 +480,8 @@ export const mutations = {
   setError (state, { value }) {
     state.error = value
   },
-  set403Error (state, { value }) {
-    state.error403 = value
+  setErrorData (state, { value }) {
+    state.errorData = value
   },
   setNotificationsLoading (state, { value }) {
     state.notifications.loading = value
@@ -532,8 +532,8 @@ const statuses = {
     setError ({ rootState, commit }, { value }) {
       commit('setError', { value })
     },
-    set403Error ({ rootState, commit }, { value }) {
-      commit('set403Error', { value })
+    setErrorData ({ rootState, commit }, { value }) {
+      commit('setErrorData', { value })
     },
     setNotificationsLoading ({ rootState, commit }, { value }) {
       commit('setNotificationsLoading', { value })
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index a2aa802f..45b63caf 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -532,10 +532,7 @@ const fetchTimeline = ({
 
   return fetch(url, { headers: authHeaders(credentials) })
     .then((data) => {
-      if (data.ok || data.status === 403) {
-        return data
-      }
-      throw new Error('Error fetching timeline', data)
+      return data
     })
     .then((data) => data.json())
     .then((data) => {
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index b7952050..1aaae563 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -6,7 +6,7 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => {
   const ccTimeline = camelCase(timeline)
 
   store.dispatch('setError', { value: false })
-  store.dispatch('set403Error', { value: false })
+  store.dispatch('setErrorData', { value: false })
 
   store.dispatch('addNewStatuses', {
     timeline: ccTimeline,
@@ -46,9 +46,9 @@ const fetchAndUpdate = ({
 
   return apiService.fetchTimeline(args)
     .then((statuses) => {
-      // Change messaging if not public
       if (statuses.error) {
-        store.dispatch('set403Error', { value: statuses.error })
+        console.log(statuses)
+        store.dispatch('setErrorData', { value: statuses.error })
         return
       }
       if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {

From 1a043d4350c1cab2a56c5197ee8fb31e68593567 Mon Sep 17 00:00:00 2001
From: Wyatt Benno <wyattbenno@gmail.com>
Date: Mon, 9 Dec 2019 09:11:31 +0900
Subject: [PATCH 059/483] remove console

---
 src/services/timeline_fetcher/timeline_fetcher.service.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index 1aaae563..68644261 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -47,7 +47,6 @@ const fetchAndUpdate = ({
   return apiService.fetchTimeline(args)
     .then((statuses) => {
       if (statuses.error) {
-        console.log(statuses)
         store.dispatch('setErrorData', { value: statuses.error })
         return
       }

From 8ee80339555c53d45602f40fdbe6b487a6992515 Mon Sep 17 00:00:00 2001
From: Wyatt Benno <wyattbenno@gmail.com>
Date: Mon, 9 Dec 2019 10:31:57 +0900
Subject: [PATCH 060/483] Set error data

---
 src/components/timeline/timeline.vue                      | 4 ++--
 src/i18n/en.json                                          | 1 -
 src/modules/statuses.js                                   | 2 +-
 src/services/api/api.service.js                           | 7 ++++++-
 src/services/timeline_fetcher/timeline_fetcher.service.js | 4 ++--
 5 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index d9f4025d..bb4ab379 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -16,7 +16,7 @@
         class="loadmore-error alert error"
         @click.prevent
       >
-        {{ $t('timeline.error_403') }}
+        {{ errorData.statusText }}
       </div>
       <button
         v-if="timeline.newStatusCount > 0 && !timelineError && !errorData"
@@ -84,7 +84,7 @@
         v-else-if="errorData"
         href="#"
       >
-        <div class="new-status-notification text-center panel-footer">{{ errorData }}</div>
+        <div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div>
       </a>
       <div
         v-else
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 1dd99062..85146ef5 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -535,7 +535,6 @@
     "collapse": "Collapse",
     "conversation": "Conversation",
     "error_fetching": "Error fetching updates",
-    "error_403": "Access denied",
     "load_older": "Load older statuses",
     "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
     "repeated": "repeated",
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 4068aa02..e3a1f293 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -38,7 +38,7 @@ export const defaultState = () => ({
   notifications: emptyNotifications(),
   favorites: new Set(),
   error: false,
-  errorData: '',
+  errorData: null,
   timelines: {
     mentions: emptyTl(),
     public: emptyTl(),
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 45b63caf..63e72e30 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -529,9 +529,12 @@ const fetchTimeline = ({
 
   const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
   url += `?${queryString}`
-
+  let status = ''
+  let statusText = ''
   return fetch(url, { headers: authHeaders(credentials) })
     .then((data) => {
+      status = data.status
+      statusText = data.statusText
       return data
     })
     .then((data) => data.json())
@@ -539,6 +542,8 @@ const fetchTimeline = ({
       if (!data.error) {
         return data.map(isNotifications ? parseNotification : parseStatus)
       } else {
+        data.status = status
+        data.statusText = statusText
         return data
       }
     })
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index 68644261..c6b28ad5 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -6,7 +6,7 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => {
   const ccTimeline = camelCase(timeline)
 
   store.dispatch('setError', { value: false })
-  store.dispatch('setErrorData', { value: false })
+  store.dispatch('setErrorData', { value: null })
 
   store.dispatch('addNewStatuses', {
     timeline: ccTimeline,
@@ -47,7 +47,7 @@ const fetchAndUpdate = ({
   return apiService.fetchTimeline(args)
     .then((statuses) => {
       if (statuses.error) {
-        store.dispatch('setErrorData', { value: statuses.error })
+        store.dispatch('setErrorData', { value: statuses })
         return
       }
       if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {

From f6d8f245e68c8a1f747af527f59670d88bde5c08 Mon Sep 17 00:00:00 2001
From: Absturztaube <me@absturztaube.ch>
Date: Mon, 9 Dec 2019 22:15:38 +0100
Subject: [PATCH 061/483] Revert "fix sticker picker height in emojo picker"

This reverts commit 914c78398469988f549930bd35d044082d155277.
---
 src/components/emoji_picker/emoji_picker.scss | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 29010c5b..6608f393 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -101,10 +101,6 @@
     }
   }
 
-  .stickers-content {
-    min-height: 269px;
-  }
-
   .emoji {
     &-search {
       padding: 5px;

From 7e3a4fa8ec183f8e04804938d713953aed6bec66 Mon Sep 17 00:00:00 2001
From: Absturztaube <me@absturztaube.ch>
Date: Mon, 9 Dec 2019 22:16:51 +0100
Subject: [PATCH 062/483] fix invisible tab-switcher in emoji picker

---
 .../sticker_picker/sticker_picker.vue         | 28 ++++++++-----------
 1 file changed, 12 insertions(+), 16 deletions(-)

diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue
index 323855b9..7d6fdc24 100644
--- a/src/components/sticker_picker/sticker_picker.vue
+++ b/src/components/sticker_picker/sticker_picker.vue
@@ -37,22 +37,18 @@
 .sticker-picker {
   width: 100%;
   position: relative;
-  .tab-switcher {
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    left: 0;
-    right: 0;
-  }
-  .sticker-picker-content {
-    .sticker {
-      display: inline-block;
-      width: 20%;
-      height: 20%;
-      img {
-        width: 100%;
-        &:hover {
-          filter: drop-shadow(0 0 5px var(--link, $fallback--link));
+  .contents {
+    min-height: 250px;
+    .sticker-picker-content {
+      .sticker {
+        display: inline-block;
+        width: 20%;
+        height: 20%;
+        img {
+          width: 100%;
+          &:hover {
+            filter: drop-shadow(0 0 5px var(--link, $fallback--link));
+          }
         }
       }
     }

From 2bc63720a5f9da740fa8082d587b6cfdd0c652ee Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Tue, 10 Dec 2019 23:54:28 +0900
Subject: [PATCH 063/483] fix parse for move type notifications

---
 src/i18n/en.json                                            | 3 ++-
 src/modules/statuses.js                                     | 5 ++++-
 src/services/entity_normalizer/entity_normalizer.service.js | 5 ++++-
 3 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/i18n/en.json b/src/i18n/en.json
index 85146ef5..f46d5b3d 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -110,7 +110,8 @@
     "notifications": "Notifications",
     "read": "Read!",
     "repeated_you": "repeated your status",
-    "no_more_notifications": "No more notifications"
+    "no_more_notifications": "No more notifications",
+    "moved_to": "moved to"
   },
   "polls": {
     "add_poll": "Add Poll",
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index f11ffdcd..3cf74a43 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -305,7 +305,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
 
 const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => {
   each(notifications, (notification) => {
-    if (notification.type !== 'follow') {
+    if (notification.type !== 'follow' && notification.type !== 'move') {
       notification.action = addStatusToGlobalStorage(state, notification.action).item
       notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
     }
@@ -338,6 +338,9 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
           case 'follow':
             i18nString = 'followed_you'
             break
+          case 'move':
+            i18nString = 'moved_to'
+            break
         }
 
         if (i18nString) {
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index ca79df6f..ee007bee 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -341,10 +341,13 @@ export const parseNotification = (data) => {
   if (masto) {
     output.type = mastoDict[data.type] || data.type
     output.seen = data.pleroma.is_seen
-    output.status = output.type === 'follow'
+    output.status = output.type === 'follow' || output.type === 'move'
       ? null
       : parseStatus(data.status)
     output.action = output.status // TODO: Refactor, this is unneeded
+    output.target = output.type !== 'move'
+      ? null
+      : parseUser(data.target)
     output.from_profile = parseUser(data.account)
   } else {
     const parsedNotice = parseStatus(data.notice)

From 6af870cd9069a8fc45b7684d264656dad0cf4a70 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Wed, 11 Dec 2019 00:00:10 +0900
Subject: [PATCH 064/483] Add view for moves notifications

---
 src/components/notification/notification.vue       | 14 +++++++++++++-
 src/components/notifications/notifications.scss    |  7 ++++++-
 src/components/settings/settings.vue               |  5 +++++
 src/i18n/en.json                                   |  1 +
 src/modules/config.js                              |  3 ++-
 src/modules/statuses.js                            |  3 ++-
 .../notification_utils/notification_utils.js       |  3 ++-
 src/services/push/push.js                          |  3 ++-
 static/fontello.json                               |  6 ++++++
 9 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 1f192c77..cf4d8072 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -74,9 +74,13 @@
               <i class="fa icon-user-plus lit" />
               <small>{{ $t('notifications.followed_you') }}</small>
             </span>
+            <span v-if="notification.type === 'move'">
+              <i class="fa icon-arrow-curved lit" />
+              <small>{{ $t('notifications.moved_to') }}</small>
+            </span>
           </div>
           <div
-            v-if="notification.type === 'follow'"
+            v-if="notification.type === 'follow' || notification.type === 'move'"
             class="timeago"
           >
             <span class="faint">
@@ -115,6 +119,14 @@
             @{{ notification.from_profile.screen_name }}
           </router-link>
         </div>
+        <div
+          v-else-if="notification.type === 'move'"
+          class="move-text"
+        >
+          <router-link :to="userProfileLink">
+            @{{ notification.target.screen_name }}
+          </router-link>
+        </div>
         <template v-else>
           <status
             class="faint"
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 71876b14..148ac7f2 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -76,7 +76,7 @@
     }
   }
 
-  .follow-text {
+  .follow-text, .move-text {
     padding: 0.5em 0;
   }
 
@@ -151,6 +151,11 @@
       color: var(--cOrange, $fallback--cOrange);
     }
 
+    .icon-arrow-curved.lit {
+      color: $fallback--cBlue;
+      color: var(--cBlue, $fallback--cBlue);
+    }
+
     .status-content {
       margin: 0;
       max-height: 300px;
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index c4021137..31329e82 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -314,6 +314,11 @@
                       {{ $t('settings.notification_visibility_mentions') }}
                     </Checkbox>
                   </li>
+                  <li>
+                    <Checkbox v-model="notificationVisibility.moves">
+                      {{ $t('settings.notification_visibility_moves') }}
+                    </Checkbox>
+                  </li>
                 </ul>
               </div>
               <div>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index f46d5b3d..dcd2f3d7 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -312,6 +312,7 @@
     "notification_visibility_likes": "Likes",
     "notification_visibility_mentions": "Mentions",
     "notification_visibility_repeats": "Repeats",
+    "notification_visibility_moves": "Moves",
     "no_rich_text_description": "Strip rich text formatting from all posts",
     "no_blocks": "No blocks",
     "no_mutes": "No mutes",
diff --git a/src/modules/config.js b/src/modules/config.js
index 329b4091..cc47c848 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -28,7 +28,8 @@ export const defaultState = {
     follows: true,
     mentions: true,
     likes: true,
-    repeats: true
+    repeats: true,
+    moves: true
   },
   webPushNotifications: false,
   muteWords: [],
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 3cf74a43..420b3183 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -66,7 +66,8 @@ const visibleNotificationTypes = (rootState) => {
     rootState.config.notificationVisibility.likes && 'like',
     rootState.config.notificationVisibility.mentions && 'mention',
     rootState.config.notificationVisibility.repeats && 'repeat',
-    rootState.config.notificationVisibility.follows && 'follow'
+    rootState.config.notificationVisibility.follows && 'follow',
+    rootState.config.notificationVisibility.moves && 'move'
   ].filter(_ => _)
 }
 
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index 7021adbd..b08514da 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -6,7 +6,8 @@ export const visibleTypes = store => ([
   store.state.config.notificationVisibility.likes && 'like',
   store.state.config.notificationVisibility.mentions && 'mention',
   store.state.config.notificationVisibility.repeats && 'repeat',
-  store.state.config.notificationVisibility.follows && 'follow'
+  store.state.config.notificationVisibility.follows && 'follow',
+  store.state.config.notificationVisibility.moves && 'move'
 ].filter(_ => _))
 
 const sortById = (a, b) => {
diff --git a/src/services/push/push.js b/src/services/push/push.js
index 1b189a29..5836fc26 100644
--- a/src/services/push/push.js
+++ b/src/services/push/push.js
@@ -65,7 +65,8 @@ function sendSubscriptionToBackEnd (subscription, token, notificationVisibility)
           follow: notificationVisibility.follows,
           favourite: notificationVisibility.likes,
           mention: notificationVisibility.mentions,
-          reblog: notificationVisibility.repeats
+          reblog: notificationVisibility.repeats,
+          move: notificationVisibility.moves
         }
       }
     })
diff --git a/static/fontello.json b/static/fontello.json
index c0cf1727..642381a1 100755
--- a/static/fontello.json
+++ b/static/fontello.json
@@ -303,6 +303,12 @@
       "css": "gauge",
       "code": 61668,
       "src": "fontawesome"
+    },
+    {
+      "uid": "f3ebd6751c15a280af5cc5f4a764187d",
+      "css": "arrow-curved",
+      "code": 59421,
+      "src": "iconic"
     }
   ]
 }
\ No newline at end of file

From 31aa177eea8f2e50e1802d1dd51a8470199b6bcb Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Wed, 11 Dec 2019 04:02:25 +0900
Subject: [PATCH 065/483] Fix target account link

---
 src/components/notification/notification.js  | 12 ++++++++++++
 src/components/notification/notification.vue |  2 +-
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 7d46eb5a..93edf2fa 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -55,6 +55,18 @@ const Notification = {
     userProfileLink () {
       return this.generateUserProfileLink(this.user)
     },
+    targetUserInStore () {
+      return this.$store.getters.findUser(this.notification.target.id)
+    },
+    targetUser () {
+      if (this.targetUserInStore) {
+        return this.targetUserInStore
+      }
+      return this.notification.target
+    },
+    targetUserProfileLink () {
+      return this.generateUserProfileLink(this.targetUser)
+    },
     needMute () {
       return this.user.muted
     }
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index cf4d8072..33ae054f 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -123,7 +123,7 @@
           v-else-if="notification.type === 'move'"
           class="move-text"
         >
-          <router-link :to="userProfileLink">
+          <router-link :to="targetUserProfileLink">
             @{{ notification.target.screen_name }}
           </router-link>
         </div>

From 6acd889589e46b18491d96b5fa992154b4e58d88 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 10 Dec 2019 21:30:27 +0200
Subject: [PATCH 066/483] Option to enable streaming

---
 src/components/settings/settings.js  | 14 +++++++++++++-
 src/components/settings/settings.vue |  9 +++++++++
 src/i18n/en.json                     |  2 ++
 src/i18n/ru.json                     |  2 ++
 src/modules/api.js                   | 18 ++++++++++++++++++
 src/modules/config.js                |  1 +
 src/modules/users.js                 | 14 +++++++++++---
 src/services/api/api.service.js      |  6 ++++++
 8 files changed, 62 insertions(+), 4 deletions(-)

diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index c49083f9..2d7723cc 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -84,7 +84,7 @@ const settings = {
         }
       }])
       .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
-    // Special cases (need to transform values)
+    // Special cases (need to transform values or perform actions first)
     muteWordsString: {
       get () { return this.$store.getters.mergedConfig.muteWords.join('\n') },
       set (value) {
@@ -93,6 +93,18 @@ const settings = {
           value: filter(value.split('\n'), (word) => trim(word).length > 0)
         })
       }
+    },
+    useStreamingApi: {
+      get () { return this.$store.getters.mergedConfig.useStreamingApi },
+      set (value) {
+        const promise = value
+          ? this.$store.dispatch('enableMastoSockets')
+          : this.$store.dispatch('disableMastoSockets')
+
+        promise.then(() => {
+          this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
+        })
+      }
     }
   },
   // Updating nested properties
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index c4021137..b40c85dd 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -73,6 +73,15 @@
                     </li>
                   </ul>
                 </li>
+                <li>
+                  <Checkbox v-model="useStreamingApi">
+                    {{ $t('settings.useStreamingApi') }}
+                    <br/>
+                    <small>
+                    {{ $t('settings.useStreamingApiWarning') }}
+                    </small>
+                  </Checkbox>
+                </li>
                 <li>
                   <Checkbox v-model="autoLoad">
                     {{ $t('settings.autoload') }}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 85146ef5..60fc792f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -358,6 +358,8 @@
     "post_status_content_type": "Post status content type",
     "stop_gifs": "Play-on-hover GIFs",
     "streaming": "Enable automatic streaming of new posts when scrolled to the top",
+    "useStreamingApi": "Receive posts and notifications real-time",
+    "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
     "text": "Text",
     "theme": "Theme",
     "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 19e10f1e..4cb2d497 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -219,6 +219,8 @@
     "subject_input_always_show": "Всегда показывать поле ввода темы",
     "stop_gifs": "Проигрывать GIF анимации только при наведении",
     "streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
+    "useStreamingApi": "Получать сообщения и уведомления в реальном времени",
+    "useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)",
     "text": "Текст",
     "theme": "Тема",
     "theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",
diff --git a/src/modules/api.js b/src/modules/api.js
index 593f8498..dc91d00e 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -31,6 +31,18 @@ const api = {
     }
   },
   actions: {
+    // Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
+    enableMastoSockets (store) {
+      const { state, dispatch } = store
+      if (state.mastoUserSocket) return
+      dispatch('startMastoUserSocket')
+    },
+    disableMastoSockets (store) {
+      const { state, dispatch } = store
+      if (!state.mastoUserSocket) return
+      dispatch('stopMastoUserSocket')
+    },
+
     // MastoAPI 'User' sockets
     startMastoUserSocket (store) {
       const { state, dispatch } = store
@@ -81,6 +93,12 @@ const api = {
         dispatch('stopFetchingNotifications')
       })
     },
+    stopMastoUserSocket ({ state, dispatch }) {
+      dispatch('startFetchingTimeline', { timeline: 'friends' })
+      dispatch('startFetchingNotifications')
+      console.log(state.mastoUserSocket)
+      state.mastoUserSocket.close()
+    },
 
     // Timelines
     startFetchingTimeline (store, {
diff --git a/src/modules/config.js b/src/modules/config.js
index 329b4091..74025db1 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -35,6 +35,7 @@ export const defaultState = {
   highlight: {},
   interfaceLanguage: browserLocale,
   hideScopeNotice: false,
+  useStreamingApi: false,
   scopeCopy: undefined, // instance default
   subjectLineBehavior: undefined, // instance default
   alwaysShowSubjectInput: undefined, // instance default
diff --git a/src/modules/users.js b/src/modules/users.js
index 6bdaf193..cbec6063 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -469,14 +469,22 @@ const users = {
                 store.dispatch('initializeSocket')
               }
 
-              store.dispatch('startMastoUserSocket').catch((error) => {
-                console.error('Failed initializing MastoAPI Streaming socket', error)
+              const startPolling = () => {
                 // Start getting fresh posts.
                 store.dispatch('startFetchingTimeline', { timeline: 'friends' })
 
                 // Start fetching notifications
                 store.dispatch('startFetchingNotifications')
-              })
+              }
+
+              if (store.getters.mergedConfig.useStreamingApi) {
+                store.dispatch('enableMastoSockets').catch((error) => {
+                  console.error('Failed initializing MastoAPI Streaming socket', error)
+                  startPolling()
+                })
+              } else {
+                startPolling()
+              }
 
               // Get user mutes
               store.dispatch('fetchMutes')
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index c6818df4..517b953e 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -983,18 +983,24 @@ export const ProcessedWS = ({
       wsEvent
     )
   })
+  // Commented code reason: very spammy, uncomment to enable message debug logging
+  /*
   socket.addEventListener('message', (wsEvent) => {
     console.debug(
       `[WS][${id}] Message received`,
       wsEvent
     )
   })
+  /**/
 
   proxy(socket, 'open')
   proxy(socket, 'close')
   proxy(socket, 'message', preprocessor)
   proxy(socket, 'error')
 
+  // 1000 = Normal Closure
+  eventTarget.close = () => { socket.close(1000, 'Shutting down socket') }
+
   return eventTarget
 }
 

From 1af536d68f8e457904400bf2019c6755f849d790 Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Wed, 11 Dec 2019 13:52:03 +0500
Subject: [PATCH 067/483] upgrade Babel core to v7

---
 package.json |   4 +-
 yarn.lock    | 171 +++++++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 153 insertions(+), 22 deletions(-)

diff --git a/package.json b/package.json
index 3c9598eb..88790ec6 100644
--- a/package.json
+++ b/package.json
@@ -40,13 +40,13 @@
     "whatwg-fetch": "^2.0.3"
   },
   "devDependencies": {
+    "@babel/core": "^7.7.5",
     "@babel/polyfill": "^7.0.0",
     "@vue/test-utils": "^1.0.0-beta.26",
     "autoprefixer": "^6.4.0",
-    "babel-core": "^6.0.0",
     "babel-eslint": "^7.0.0",
     "babel-helper-vue-jsx-merge-props": "^2.0.3",
-    "babel-loader": "^7.0.0",
+    "babel-loader": "^8.0.6",
     "babel-plugin-syntax-jsx": "^6.18.0",
     "babel-plugin-transform-runtime": "^6.0.0",
     "babel-plugin-transform-vue-jsx": "3",
diff --git a/yarn.lock b/yarn.lock
index 44473c94..a3cc6e2a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8,12 +8,81 @@
   dependencies:
     "@babel/highlight" "^7.0.0"
 
+"@babel/code-frame@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"
+  integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==
+  dependencies:
+    "@babel/highlight" "^7.0.0"
+
+"@babel/core@^7.7.5":
+  version "7.7.5"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.5.tgz#ae1323cd035b5160293307f50647e83f8ba62f7e"
+  integrity sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw==
+  dependencies:
+    "@babel/code-frame" "^7.5.5"
+    "@babel/generator" "^7.7.4"
+    "@babel/helpers" "^7.7.4"
+    "@babel/parser" "^7.7.5"
+    "@babel/template" "^7.7.4"
+    "@babel/traverse" "^7.7.4"
+    "@babel/types" "^7.7.4"
+    convert-source-map "^1.7.0"
+    debug "^4.1.0"
+    json5 "^2.1.0"
+    lodash "^4.17.13"
+    resolve "^1.3.2"
+    semver "^5.4.1"
+    source-map "^0.5.0"
+
+"@babel/generator@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.4.tgz#db651e2840ca9aa66f327dcec1dc5f5fa9611369"
+  integrity sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==
+  dependencies:
+    "@babel/types" "^7.7.4"
+    jsesc "^2.5.1"
+    lodash "^4.17.13"
+    source-map "^0.5.0"
+
+"@babel/helper-function-name@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz#ab6e041e7135d436d8f0a3eca15de5b67a341a2e"
+  integrity sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==
+  dependencies:
+    "@babel/helper-get-function-arity" "^7.7.4"
+    "@babel/template" "^7.7.4"
+    "@babel/types" "^7.7.4"
+
+"@babel/helper-get-function-arity@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz#cb46348d2f8808e632f0ab048172130e636005f0"
+  integrity sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==
+  dependencies:
+    "@babel/types" "^7.7.4"
+
 "@babel/helper-module-imports@^7.0.0-beta.49":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d"
   dependencies:
     "@babel/types" "^7.0.0"
 
+"@babel/helper-split-export-declaration@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz#57292af60443c4a3622cf74040ddc28e68336fd8"
+  integrity sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==
+  dependencies:
+    "@babel/types" "^7.7.4"
+
+"@babel/helpers@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.7.4.tgz#62c215b9e6c712dadc15a9a0dcab76c92a940302"
+  integrity sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg==
+  dependencies:
+    "@babel/template" "^7.7.4"
+    "@babel/traverse" "^7.7.4"
+    "@babel/types" "^7.7.4"
+
 "@babel/highlight@^7.0.0":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
@@ -22,6 +91,11 @@
     esutils "^2.0.2"
     js-tokens "^4.0.0"
 
+"@babel/parser@^7.7.4", "@babel/parser@^7.7.5":
+  version "7.7.5"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.5.tgz#cbf45321619ac12d83363fcf9c94bb67fa646d71"
+  integrity sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==
+
 "@babel/polyfill@^7.0.0":
   version "7.2.5"
   resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.2.5.tgz#6c54b964f71ad27edddc567d065e57e87ed7fa7d"
@@ -29,6 +103,30 @@
     core-js "^2.5.7"
     regenerator-runtime "^0.12.0"
 
+"@babel/template@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b"
+  integrity sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==
+  dependencies:
+    "@babel/code-frame" "^7.0.0"
+    "@babel/parser" "^7.7.4"
+    "@babel/types" "^7.7.4"
+
+"@babel/traverse@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.7.4.tgz#9c1e7c60fb679fe4fcfaa42500833333c2058558"
+  integrity sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==
+  dependencies:
+    "@babel/code-frame" "^7.5.5"
+    "@babel/generator" "^7.7.4"
+    "@babel/helper-function-name" "^7.7.4"
+    "@babel/helper-split-export-declaration" "^7.7.4"
+    "@babel/parser" "^7.7.4"
+    "@babel/types" "^7.7.4"
+    debug "^4.1.0"
+    globals "^11.1.0"
+    lodash "^4.17.13"
+
 "@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49":
   version "7.2.2"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e"
@@ -37,6 +135,15 @@
     lodash "^4.17.10"
     to-fast-properties "^2.0.0"
 
+"@babel/types@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.7.4.tgz#516570d539e44ddf308c07569c258ff94fde9193"
+  integrity sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==
+  dependencies:
+    esutils "^2.0.2"
+    lodash "^4.17.13"
+    to-fast-properties "^2.0.0"
+
 "@chenfengyuan/vue-qrcode@^1.0.0":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@chenfengyuan/vue-qrcode/-/vue-qrcode-1.0.0.tgz#07103fe2048ce0160cddde836fb5378cc7951d6f"
@@ -492,7 +599,7 @@ babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
     esutils "^2.0.2"
     js-tokens "^3.0.2"
 
-babel-core@^6.0.0, babel-core@^6.1.4, babel-core@^6.26.0:
+babel-core@^6.1.4, babel-core@^6.26.0:
   version "6.26.3"
   resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
   dependencies:
@@ -660,13 +767,15 @@ babel-helpers@^6.24.1:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-loader@^7.0.0:
-  version "7.1.5"
-  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68"
+babel-loader@^8.0.6:
+  version "8.0.6"
+  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb"
+  integrity sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==
   dependencies:
-    find-cache-dir "^1.0.0"
+    find-cache-dir "^2.0.0"
     loader-utils "^1.0.2"
     mkdirp "^0.5.1"
+    pify "^4.0.1"
 
 babel-messages@^6.23.0:
   version "6.23.0"
@@ -1840,6 +1949,13 @@ convert-source-map@^1.5.1:
   dependencies:
     safe-buffer "~5.1.1"
 
+convert-source-map@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
+  integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
+  dependencies:
+    safe-buffer "~5.1.1"
+
 cookie-signature@1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -3014,14 +3130,6 @@ find-cache-dir@^0.1.1:
     mkdirp "^0.5.1"
     pkg-dir "^1.0.0"
 
-find-cache-dir@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
-  dependencies:
-    commondir "^1.0.1"
-    make-dir "^1.0.0"
-    pkg-dir "^2.0.0"
-
 find-cache-dir@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
@@ -3321,7 +3429,7 @@ glob@^7.1.2:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-globals@^11.7.0:
+globals@^11.1.0, globals@^11.7.0:
   version "11.12.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
 
@@ -4144,6 +4252,11 @@ jsesc@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
 
+jsesc@^2.5.1:
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
+  integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+
 jsesc@~0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
@@ -4186,6 +4299,13 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
+json5@^2.1.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6"
+  integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==
+  dependencies:
+    minimist "^1.2.0"
+
 jsprim@^1.2.2:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -4647,6 +4767,11 @@ lodash@^4.16.4, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.2
   version "4.17.11"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
 
+lodash@^4.17.13:
+  version "4.17.15"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
+  integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+
 log-symbols@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
@@ -4707,12 +4832,6 @@ lru-cache@~2.6.5:
   version "2.6.5"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5"
 
-make-dir@^1.0.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
-  dependencies:
-    pify "^3.0.0"
-
 make-dir@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@@ -6447,6 +6566,13 @@ resolve@^1.10.0, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1:
   dependencies:
     path-parse "^1.0.6"
 
+resolve@^1.3.2:
+  version "1.13.1"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16"
+  integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==
+  dependencies:
+    path-parse "^1.0.6"
+
 restore-cursor@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
@@ -6577,6 +6703,11 @@ selenium-server@2.53.1:
   version "5.6.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
 
+semver@^5.4.1:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
 semver@^5.5.1:
   version "5.7.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"

From 4f9aba1a7c20a5dadec7ba4960445a0d5876c41e Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Wed, 11 Dec 2019 13:56:08 +0500
Subject: [PATCH 068/483] upgrade babel preset

---
 package.json |    5 +-
 yarn.lock    | 1145 ++++++++++++++++++++++++++++----------------------
 2 files changed, 650 insertions(+), 500 deletions(-)

diff --git a/package.json b/package.json
index 88790ec6..9bf498e1 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
   },
   "devDependencies": {
     "@babel/core": "^7.7.5",
-    "@babel/polyfill": "^7.0.0",
+    "@babel/preset-env": "^7.7.6",
     "@vue/test-utils": "^1.0.0-beta.26",
     "autoprefixer": "^6.4.0",
     "babel-eslint": "^7.0.0",
@@ -50,9 +50,6 @@
     "babel-plugin-syntax-jsx": "^6.18.0",
     "babel-plugin-transform-runtime": "^6.0.0",
     "babel-plugin-transform-vue-jsx": "3",
-    "babel-preset-env": "^1.7.0",
-    "babel-preset-es2015": "^6.0.0",
-    "babel-preset-stage-2": "^6.0.0",
     "babel-register": "^6.0.0",
     "chai": "^3.5.0",
     "chalk": "^1.1.3",
diff --git a/yarn.lock b/yarn.lock
index a3cc6e2a..b5017997 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -45,6 +45,55 @@
     lodash "^4.17.13"
     source-map "^0.5.0"
 
+"@babel/helper-annotate-as-pure@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.4.tgz#bb3faf1e74b74bd547e867e48f551fa6b098b6ce"
+  integrity sha512-2BQmQgECKzYKFPpiycoF9tlb5HA4lrVyAmLLVK177EcQAqjVLciUb2/R+n1boQ9y5ENV3uz2ZqiNw7QMBBw1Og==
+  dependencies:
+    "@babel/types" "^7.7.4"
+
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.7.4.tgz#5f73f2b28580e224b5b9bd03146a4015d6217f5f"
+  integrity sha512-Biq/d/WtvfftWZ9Uf39hbPBYDUo986m5Bb4zhkeYDGUllF43D+nUe5M6Vuo6/8JDK/0YX/uBdeoQpyaNhNugZQ==
+  dependencies:
+    "@babel/helper-explode-assignable-expression" "^7.7.4"
+    "@babel/types" "^7.7.4"
+
+"@babel/helper-call-delegate@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.7.4.tgz#621b83e596722b50c0066f9dc37d3232e461b801"
+  integrity sha512-8JH9/B7J7tCYJ2PpWVpw9JhPuEVHztagNVuQAFBVFYluRMlpG7F1CgKEgGeL6KFqcsIa92ZYVj6DSc0XwmN1ZA==
+  dependencies:
+    "@babel/helper-hoist-variables" "^7.7.4"
+    "@babel/traverse" "^7.7.4"
+    "@babel/types" "^7.7.4"
+
+"@babel/helper-create-regexp-features-plugin@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz#6d5762359fd34f4da1500e4cff9955b5299aaf59"
+  integrity sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A==
+  dependencies:
+    "@babel/helper-regex" "^7.4.4"
+    regexpu-core "^4.6.0"
+
+"@babel/helper-define-map@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.7.4.tgz#2841bf92eb8bd9c906851546fe6b9d45e162f176"
+  integrity sha512-v5LorqOa0nVQUvAUTUF3KPastvUt/HzByXNamKQ6RdJRTV7j8rLL+WB5C/MzzWAwOomxDhYFb1wLLxHqox86lg==
+  dependencies:
+    "@babel/helper-function-name" "^7.7.4"
+    "@babel/types" "^7.7.4"
+    lodash "^4.17.13"
+
+"@babel/helper-explode-assignable-expression@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.4.tgz#fa700878e008d85dc51ba43e9fb835cddfe05c84"
+  integrity sha512-2/SicuFrNSXsZNBxe5UGdLr+HZg+raWBLE9vC98bdYOKX/U6PY0mdGlYUJdtTDPSU0Lw0PNbKKDpwYHJLn2jLg==
+  dependencies:
+    "@babel/traverse" "^7.7.4"
+    "@babel/types" "^7.7.4"
+
 "@babel/helper-function-name@^7.7.4":
   version "7.7.4"
   resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz#ab6e041e7135d436d8f0a3eca15de5b67a341a2e"
@@ -61,12 +110,93 @@
   dependencies:
     "@babel/types" "^7.7.4"
 
+"@babel/helper-hoist-variables@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.4.tgz#612384e3d823fdfaaf9fce31550fe5d4db0f3d12"
+  integrity sha512-wQC4xyvc1Jo/FnLirL6CEgPgPCa8M74tOdjWpRhQYapz5JC7u3NYU1zCVoVAGCE3EaIP9T1A3iW0WLJ+reZlpQ==
+  dependencies:
+    "@babel/types" "^7.7.4"
+
+"@babel/helper-member-expression-to-functions@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz#356438e2569df7321a8326644d4b790d2122cb74"
+  integrity sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw==
+  dependencies:
+    "@babel/types" "^7.7.4"
+
 "@babel/helper-module-imports@^7.0.0-beta.49":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d"
   dependencies:
     "@babel/types" "^7.0.0"
 
+"@babel/helper-module-imports@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz#e5a92529f8888bf319a6376abfbd1cebc491ad91"
+  integrity sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==
+  dependencies:
+    "@babel/types" "^7.7.4"
+
+"@babel/helper-module-transforms@^7.7.4", "@babel/helper-module-transforms@^7.7.5":
+  version "7.7.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz#d044da7ffd91ec967db25cd6748f704b6b244835"
+  integrity sha512-A7pSxyJf1gN5qXVcidwLWydjftUN878VkalhXX5iQDuGyiGK3sOrrKKHF4/A4fwHtnsotv/NipwAeLzY4KQPvw==
+  dependencies:
+    "@babel/helper-module-imports" "^7.7.4"
+    "@babel/helper-simple-access" "^7.7.4"
+    "@babel/helper-split-export-declaration" "^7.7.4"
+    "@babel/template" "^7.7.4"
+    "@babel/types" "^7.7.4"
+    lodash "^4.17.13"
+
+"@babel/helper-optimise-call-expression@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz#034af31370d2995242aa4df402c3b7794b2dcdf2"
+  integrity sha512-VB7gWZ2fDkSuqW6b1AKXkJWO5NyNI3bFL/kK79/30moK57blr6NbH8xcl2XcKCwOmJosftWunZqfO84IGq3ZZg==
+  dependencies:
+    "@babel/types" "^7.7.4"
+
+"@babel/helper-plugin-utils@^7.0.0":
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250"
+  integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==
+
+"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351"
+  integrity sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==
+  dependencies:
+    lodash "^4.17.13"
+
+"@babel/helper-remap-async-to-generator@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.4.tgz#c68c2407350d9af0e061ed6726afb4fff16d0234"
+  integrity sha512-Sk4xmtVdM9sA/jCI80f+KS+Md+ZHIpjuqmYPk1M7F/upHou5e4ReYmExAiu6PVe65BhJPZA2CY9x9k4BqE5klw==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.7.4"
+    "@babel/helper-wrap-function" "^7.7.4"
+    "@babel/template" "^7.7.4"
+    "@babel/traverse" "^7.7.4"
+    "@babel/types" "^7.7.4"
+
+"@babel/helper-replace-supers@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.7.4.tgz#3c881a6a6a7571275a72d82e6107126ec9e2cdd2"
+  integrity sha512-pP0tfgg9hsZWo5ZboYGuBn/bbYT/hdLPVSS4NMmiRJdwWhP0IznPwN9AE1JwyGsjSPLC364I0Qh5p+EPkGPNpg==
+  dependencies:
+    "@babel/helper-member-expression-to-functions" "^7.7.4"
+    "@babel/helper-optimise-call-expression" "^7.7.4"
+    "@babel/traverse" "^7.7.4"
+    "@babel/types" "^7.7.4"
+
+"@babel/helper-simple-access@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.7.4.tgz#a169a0adb1b5f418cfc19f22586b2ebf58a9a294"
+  integrity sha512-zK7THeEXfan7UlWsG2A6CI/L9jVnI5+xxKZOdej39Y0YtDYKx9raHk5F2EtK9K8DHRTihYwg20ADt9S36GR78A==
+  dependencies:
+    "@babel/template" "^7.7.4"
+    "@babel/types" "^7.7.4"
+
 "@babel/helper-split-export-declaration@^7.7.4":
   version "7.7.4"
   resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz#57292af60443c4a3622cf74040ddc28e68336fd8"
@@ -74,6 +204,16 @@
   dependencies:
     "@babel/types" "^7.7.4"
 
+"@babel/helper-wrap-function@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.7.4.tgz#37ab7fed5150e22d9d7266e830072c0cdd8baace"
+  integrity sha512-VsfzZt6wmsocOaVU0OokwrIytHND55yvyT4BPB9AIIgwr8+x7617hetdJTsuGwygN5RC6mxA9EJztTjuwm2ofg==
+  dependencies:
+    "@babel/helper-function-name" "^7.7.4"
+    "@babel/template" "^7.7.4"
+    "@babel/traverse" "^7.7.4"
+    "@babel/types" "^7.7.4"
+
 "@babel/helpers@^7.7.4":
   version "7.7.4"
   resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.7.4.tgz#62c215b9e6c712dadc15a9a0dcab76c92a940302"
@@ -96,12 +236,397 @@
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.5.tgz#cbf45321619ac12d83363fcf9c94bb67fa646d71"
   integrity sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==
 
-"@babel/polyfill@^7.0.0":
-  version "7.2.5"
-  resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.2.5.tgz#6c54b964f71ad27edddc567d065e57e87ed7fa7d"
+"@babel/plugin-proposal-async-generator-functions@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.4.tgz#0351c5ac0a9e927845fffd5b82af476947b7ce6d"
+  integrity sha512-1ypyZvGRXriY/QP668+s8sFr2mqinhkRDMPSQLNghCQE+GAkFtp+wkHVvg2+Hdki8gwP+NFzJBJ/N1BfzCCDEw==
   dependencies:
-    core-js "^2.5.7"
-    regenerator-runtime "^0.12.0"
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-remap-async-to-generator" "^7.7.4"
+    "@babel/plugin-syntax-async-generators" "^7.7.4"
+
+"@babel/plugin-proposal-dynamic-import@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.4.tgz#dde64a7f127691758cbfed6cf70de0fa5879d52d"
+  integrity sha512-StH+nGAdO6qDB1l8sZ5UBV8AC3F2VW2I8Vfld73TMKyptMU9DY5YsJAS8U81+vEtxcH3Y/La0wG0btDrhpnhjQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/plugin-syntax-dynamic-import" "^7.7.4"
+
+"@babel/plugin-proposal-json-strings@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.7.4.tgz#7700a6bfda771d8dc81973249eac416c6b4c697d"
+  integrity sha512-wQvt3akcBTfLU/wYoqm/ws7YOAQKu8EVJEvHip/mzkNtjaclQoCCIqKXFP5/eyfnfbQCDV3OLRIK3mIVyXuZlw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/plugin-syntax-json-strings" "^7.7.4"
+
+"@babel/plugin-proposal-object-rest-spread@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.4.tgz#cc57849894a5c774214178c8ab64f6334ec8af71"
+  integrity sha512-rnpnZR3/iWKmiQyJ3LKJpSwLDcX/nSXhdLk4Aq/tXOApIvyu7qoabrige0ylsAJffaUC51WiBu209Q0U+86OWQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/plugin-syntax-object-rest-spread" "^7.7.4"
+
+"@babel/plugin-proposal-optional-catch-binding@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.7.4.tgz#ec21e8aeb09ec6711bc0a39ca49520abee1de379"
+  integrity sha512-DyM7U2bnsQerCQ+sejcTNZh8KQEUuC3ufzdnVnSiUv/qoGJp2Z3hanKL18KDhsBT5Wj6a7CMT5mdyCNJsEaA9w==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/plugin-syntax-optional-catch-binding" "^7.7.4"
+
+"@babel/plugin-proposal-unicode-property-regex@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.4.tgz#7c239ccaf09470dbe1d453d50057460e84517ebb"
+  integrity sha512-cHgqHgYvffluZk85dJ02vloErm3Y6xtH+2noOBOJ2kXOJH3aVCDnj5eR/lVNlTnYu4hndAPJD3rTFjW3qee0PA==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-async-generators@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.7.4.tgz#331aaf310a10c80c44a66b238b6e49132bd3c889"
+  integrity sha512-Li4+EjSpBgxcsmeEF8IFcfV/+yJGxHXDirDkEoyFjumuwbmfCVHUt0HuowD/iGM7OhIRyXJH9YXxqiH6N815+g==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-dynamic-import@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.7.4.tgz#29ca3b4415abfe4a5ec381e903862ad1a54c3aec"
+  integrity sha512-jHQW0vbRGvwQNgyVxwDh4yuXu4bH1f5/EICJLAhl1SblLs2CDhrsmCk+v5XLdE9wxtAFRyxx+P//Iw+a5L/tTg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-json-strings@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.7.4.tgz#86e63f7d2e22f9e27129ac4e83ea989a382e86cc"
+  integrity sha512-QpGupahTQW1mHRXddMG5srgpHWqRLwJnJZKXTigB9RPFCCGbDGCgBeM/iC82ICXp414WeYx/tD54w7M2qRqTMg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-object-rest-spread@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz#47cf220d19d6d0d7b154304701f468fc1cc6ff46"
+  integrity sha512-mObR+r+KZq0XhRVS2BrBKBpr5jqrqzlPvS9C9vuOf5ilSwzloAl7RPWLrgKdWS6IreaVrjHxTjtyqFiOisaCwg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-optional-catch-binding@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.7.4.tgz#a3e38f59f4b6233867b4a92dcb0ee05b2c334aa6"
+  integrity sha512-4ZSuzWgFxqHRE31Glu+fEr/MirNZOMYmD/0BhBWyLyOOQz/gTAl7QmWm2hX1QxEIXsr2vkdlwxIzTyiYRC4xcQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-top-level-await@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.7.4.tgz#bd7d8fa7b9fee793a36e4027fd6dd1aa32f946da"
+  integrity sha512-wdsOw0MvkL1UIgiQ/IFr3ETcfv1xb8RMM0H9wbiDyLaJFyiDg5oZvDLCXosIXmFeIlweML5iOBXAkqddkYNizg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-arrow-functions@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.7.4.tgz#76309bd578addd8aee3b379d809c802305a98a12"
+  integrity sha512-zUXy3e8jBNPiffmqkHRNDdZM2r8DWhCB7HhcoyZjiK1TxYEluLHAvQuYnTT+ARqRpabWqy/NHkO6e3MsYB5YfA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-async-to-generator@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.4.tgz#694cbeae6d613a34ef0292713fa42fb45c4470ba"
+  integrity sha512-zpUTZphp5nHokuy8yLlyafxCJ0rSlFoSHypTUWgpdwoDXWQcseaect7cJ8Ppk6nunOM6+5rPMkod4OYKPR5MUg==
+  dependencies:
+    "@babel/helper-module-imports" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-remap-async-to-generator" "^7.7.4"
+
+"@babel/plugin-transform-block-scoped-functions@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.7.4.tgz#d0d9d5c269c78eaea76227ace214b8d01e4d837b"
+  integrity sha512-kqtQzwtKcpPclHYjLK//3lH8OFsCDuDJBaFhVwf8kqdnF6MN4l618UDlcA7TfRs3FayrHj+svYnSX8MC9zmUyQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-block-scoping@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.7.4.tgz#200aad0dcd6bb80372f94d9e628ea062c58bf224"
+  integrity sha512-2VBe9u0G+fDt9B5OV5DQH4KBf5DoiNkwFKOz0TCvBWvdAN2rOykCTkrL+jTLxfCAm76l9Qo5OqL7HBOx2dWggg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+    lodash "^4.17.13"
+
+"@babel/plugin-transform-classes@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.4.tgz#c92c14be0a1399e15df72667067a8f510c9400ec"
+  integrity sha512-sK1mjWat7K+buWRuImEzjNf68qrKcrddtpQo3swi9j7dUcG6y6R6+Di039QN2bD1dykeswlagupEmpOatFHHUg==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.7.4"
+    "@babel/helper-define-map" "^7.7.4"
+    "@babel/helper-function-name" "^7.7.4"
+    "@babel/helper-optimise-call-expression" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-replace-supers" "^7.7.4"
+    "@babel/helper-split-export-declaration" "^7.7.4"
+    globals "^11.1.0"
+
+"@babel/plugin-transform-computed-properties@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.7.4.tgz#e856c1628d3238ffe12d668eb42559f79a81910d"
+  integrity sha512-bSNsOsZnlpLLyQew35rl4Fma3yKWqK3ImWMSC/Nc+6nGjC9s5NFWAer1YQ899/6s9HxO2zQC1WoFNfkOqRkqRQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-destructuring@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.7.4.tgz#2b713729e5054a1135097b6a67da1b6fe8789267"
+  integrity sha512-4jFMXI1Cu2aXbcXXl8Lr6YubCn6Oc7k9lLsu8v61TZh+1jny2BWmdtvY9zSUlLdGUvcy9DMAWyZEOqjsbeg/wA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-dotall-regex@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.4.tgz#f7ccda61118c5b7a2599a72d5e3210884a021e96"
+  integrity sha512-mk0cH1zyMa/XHeb6LOTXTbG7uIJ8Rrjlzu91pUx/KS3JpcgaTDwMS8kM+ar8SLOvlL2Lofi4CGBAjCo3a2x+lw==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-duplicate-keys@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.7.4.tgz#3d21731a42e3f598a73835299dd0169c3b90ac91"
+  integrity sha512-g1y4/G6xGWMD85Tlft5XedGaZBCIVN+/P0bs6eabmcPP9egFleMAo65OOjlhcz1njpwagyY3t0nsQC9oTFegJA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-exponentiation-operator@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.7.4.tgz#dd30c0191e3a1ba19bcc7e389bdfddc0729d5db9"
+  integrity sha512-MCqiLfCKm6KEA1dglf6Uqq1ElDIZwFuzz1WH5mTf8k2uQSxEJMbOIEh7IZv7uichr7PMfi5YVSrr1vz+ipp7AQ==
+  dependencies:
+    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-for-of@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.7.4.tgz#248800e3a5e507b1f103d8b4ca998e77c63932bc"
+  integrity sha512-zZ1fD1B8keYtEcKF+M1TROfeHTKnijcVQm0yO/Yu1f7qoDoxEIc/+GX6Go430Bg84eM/xwPFp0+h4EbZg7epAA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-function-name@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.4.tgz#75a6d3303d50db638ff8b5385d12451c865025b1"
+  integrity sha512-E/x09TvjHNhsULs2IusN+aJNRV5zKwxu1cpirZyRPw+FyyIKEHPXTsadj48bVpc1R5Qq1B5ZkzumuFLytnbT6g==
+  dependencies:
+    "@babel/helper-function-name" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-literals@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.7.4.tgz#27fe87d2b5017a2a5a34d1c41a6b9f6a6262643e"
+  integrity sha512-X2MSV7LfJFm4aZfxd0yLVFrEXAgPqYoDG53Br/tCKiKYfX0MjVjQeWPIhPHHsCqzwQANq+FLN786fF5rgLS+gw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-member-expression-literals@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.7.4.tgz#aee127f2f3339fc34ce5e3055d7ffbf7aa26f19a"
+  integrity sha512-9VMwMO7i69LHTesL0RdGy93JU6a+qOPuvB4F4d0kR0zyVjJRVJRaoaGjhtki6SzQUu8yen/vxPKN6CWnCUw6bA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-modules-amd@^7.7.5":
+  version "7.7.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.5.tgz#39e0fb717224b59475b306402bb8eedab01e729c"
+  integrity sha512-CT57FG4A2ZUNU1v+HdvDSDrjNWBrtCmSH6YbbgN3Lrf0Di/q/lWRxZrE72p3+HCCz9UjfZOEBdphgC0nzOS6DQ==
+  dependencies:
+    "@babel/helper-module-transforms" "^7.7.5"
+    "@babel/helper-plugin-utils" "^7.0.0"
+    babel-plugin-dynamic-import-node "^2.3.0"
+
+"@babel/plugin-transform-modules-commonjs@^7.7.5":
+  version "7.7.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz#1d27f5eb0bcf7543e774950e5b2fa782e637b345"
+  integrity sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q==
+  dependencies:
+    "@babel/helper-module-transforms" "^7.7.5"
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-simple-access" "^7.7.4"
+    babel-plugin-dynamic-import-node "^2.3.0"
+
+"@babel/plugin-transform-modules-systemjs@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.4.tgz#cd98152339d3e763dfe838b7d4273edaf520bb30"
+  integrity sha512-y2c96hmcsUi6LrMqvmNDPBBiGCiQu0aYqpHatVVu6kD4mFEXKjyNxd/drc18XXAf9dv7UXjrZwBVmTTGaGP8iw==
+  dependencies:
+    "@babel/helper-hoist-variables" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+    babel-plugin-dynamic-import-node "^2.3.0"
+
+"@babel/plugin-transform-modules-umd@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.4.tgz#1027c355a118de0aae9fee00ad7813c584d9061f"
+  integrity sha512-u2B8TIi0qZI4j8q4C51ktfO7E3cQ0qnaXFI1/OXITordD40tt17g/sXqgNNCcMTcBFKrUPcGDx+TBJuZxLx7tw==
+  dependencies:
+    "@babel/helper-module-transforms" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-named-capturing-groups-regex@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.4.tgz#fb3bcc4ee4198e7385805007373d6b6f42c98220"
+  integrity sha512-jBUkiqLKvUWpv9GLSuHUFYdmHg0ujC1JEYoZUfeOOfNydZXp1sXObgyPatpcwjWgsdBGsagWW0cdJpX/DO2jMw==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.7.4"
+
+"@babel/plugin-transform-new-target@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.7.4.tgz#4a0753d2d60639437be07b592a9e58ee00720167"
+  integrity sha512-CnPRiNtOG1vRodnsyGX37bHQleHE14B9dnnlgSeEs3ek3fHN1A1SScglTCg1sfbe7sRQ2BUcpgpTpWSfMKz3gg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-object-super@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.7.4.tgz#48488937a2d586c0148451bf51af9d7dda567262"
+  integrity sha512-ho+dAEhC2aRnff2JCA0SAK7V2R62zJd/7dmtoe7MHcso4C2mS+vZjn1Pb1pCVZvJs1mgsvv5+7sT+m3Bysb6eg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-replace-supers" "^7.7.4"
+
+"@babel/plugin-transform-parameters@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.7.4.tgz#da4555c97f39b51ac089d31c7380f03bca4075ce"
+  integrity sha512-VJwhVePWPa0DqE9vcfptaJSzNDKrWU/4FbYCjZERtmqEs05g3UMXnYMZoXja7JAJ7Y7sPZipwm/pGApZt7wHlw==
+  dependencies:
+    "@babel/helper-call-delegate" "^7.7.4"
+    "@babel/helper-get-function-arity" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-property-literals@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.7.4.tgz#2388d6505ef89b266103f450f9167e6bd73f98c2"
+  integrity sha512-MatJhlC4iHsIskWYyawl53KuHrt+kALSADLQQ/HkhTjX954fkxIEh4q5slL4oRAnsm/eDoZ4q0CIZpcqBuxhJQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-regenerator@^7.7.5":
+  version "7.7.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.5.tgz#3a8757ee1a2780f390e89f246065ecf59c26fce9"
+  integrity sha512-/8I8tPvX2FkuEyWbjRCt4qTAgZK0DVy8QRguhA524UH48RfGJy94On2ri+dCuwOpcerPRl9O4ebQkRcVzIaGBw==
+  dependencies:
+    regenerator-transform "^0.14.0"
+
+"@babel/plugin-transform-reserved-words@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.7.4.tgz#6a7cf123ad175bb5c69aec8f6f0770387ed3f1eb"
+  integrity sha512-OrPiUB5s5XvkCO1lS7D8ZtHcswIC57j62acAnJZKqGGnHP+TIc/ljQSrgdX/QyOTdEK5COAhuc820Hi1q2UgLQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-shorthand-properties@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.7.4.tgz#74a0a9b2f6d67a684c6fbfd5f0458eb7ba99891e"
+  integrity sha512-q+suddWRfIcnyG5YiDP58sT65AJDZSUhXQDZE3r04AuqD6d/XLaQPPXSBzP2zGerkgBivqtQm9XKGLuHqBID6Q==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-spread@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.7.4.tgz#aa673b356fe6b7e70d69b6e33a17fef641008578"
+  integrity sha512-8OSs0FLe5/80cndziPlg4R0K6HcWSM0zyNhHhLsmw/Nc5MaA49cAsnoJ/t/YZf8qkG7fD+UjTRaApVDB526d7Q==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-sticky-regex@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.7.4.tgz#ffb68c05090c30732076b1285dc1401b404a123c"
+  integrity sha512-Ls2NASyL6qtVe1H1hXts9yuEeONV2TJZmplLONkMPUG158CtmnrzW5Q5teibM5UVOFjG0D3IC5mzXR6pPpUY7A==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-regex" "^7.0.0"
+
+"@babel/plugin-transform-template-literals@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.7.4.tgz#1eb6411736dd3fe87dbd20cc6668e5121c17d604"
+  integrity sha512-sA+KxLwF3QwGj5abMHkHgshp9+rRz+oY9uoRil4CyLtgEuE/88dpkeWgNk5qKVsJE9iSfly3nvHapdRiIS2wnQ==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-typeof-symbol@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.7.4.tgz#3174626214f2d6de322882e498a38e8371b2140e"
+  integrity sha512-KQPUQ/7mqe2m0B8VecdyaW5XcQYaePyl9R7IsKd+irzj6jvbhoGnRE+M0aNkyAzI07VfUQ9266L5xMARitV3wg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-unicode-regex@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.4.tgz#a3c0f65b117c4c81c5b6484f2a5e7b95346b83ae"
+  integrity sha512-N77UUIV+WCvE+5yHw+oks3m18/umd7y392Zv7mYTpFqHtkpcc+QUz+gLJNTWVlWROIWeLqY0f3OjZxV5TcXnRw==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/preset-env@^7.7.6":
+  version "7.7.6"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.6.tgz#39ac600427bbb94eec6b27953f1dfa1d64d457b2"
+  integrity sha512-k5hO17iF/Q7tR9Jv8PdNBZWYW6RofxhnxKjBMc0nG4JTaWvOTiPoO/RLFwAKcA4FpmuBFm6jkoqaRJLGi0zdaQ==
+  dependencies:
+    "@babel/helper-module-imports" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/plugin-proposal-async-generator-functions" "^7.7.4"
+    "@babel/plugin-proposal-dynamic-import" "^7.7.4"
+    "@babel/plugin-proposal-json-strings" "^7.7.4"
+    "@babel/plugin-proposal-object-rest-spread" "^7.7.4"
+    "@babel/plugin-proposal-optional-catch-binding" "^7.7.4"
+    "@babel/plugin-proposal-unicode-property-regex" "^7.7.4"
+    "@babel/plugin-syntax-async-generators" "^7.7.4"
+    "@babel/plugin-syntax-dynamic-import" "^7.7.4"
+    "@babel/plugin-syntax-json-strings" "^7.7.4"
+    "@babel/plugin-syntax-object-rest-spread" "^7.7.4"
+    "@babel/plugin-syntax-optional-catch-binding" "^7.7.4"
+    "@babel/plugin-syntax-top-level-await" "^7.7.4"
+    "@babel/plugin-transform-arrow-functions" "^7.7.4"
+    "@babel/plugin-transform-async-to-generator" "^7.7.4"
+    "@babel/plugin-transform-block-scoped-functions" "^7.7.4"
+    "@babel/plugin-transform-block-scoping" "^7.7.4"
+    "@babel/plugin-transform-classes" "^7.7.4"
+    "@babel/plugin-transform-computed-properties" "^7.7.4"
+    "@babel/plugin-transform-destructuring" "^7.7.4"
+    "@babel/plugin-transform-dotall-regex" "^7.7.4"
+    "@babel/plugin-transform-duplicate-keys" "^7.7.4"
+    "@babel/plugin-transform-exponentiation-operator" "^7.7.4"
+    "@babel/plugin-transform-for-of" "^7.7.4"
+    "@babel/plugin-transform-function-name" "^7.7.4"
+    "@babel/plugin-transform-literals" "^7.7.4"
+    "@babel/plugin-transform-member-expression-literals" "^7.7.4"
+    "@babel/plugin-transform-modules-amd" "^7.7.5"
+    "@babel/plugin-transform-modules-commonjs" "^7.7.5"
+    "@babel/plugin-transform-modules-systemjs" "^7.7.4"
+    "@babel/plugin-transform-modules-umd" "^7.7.4"
+    "@babel/plugin-transform-named-capturing-groups-regex" "^7.7.4"
+    "@babel/plugin-transform-new-target" "^7.7.4"
+    "@babel/plugin-transform-object-super" "^7.7.4"
+    "@babel/plugin-transform-parameters" "^7.7.4"
+    "@babel/plugin-transform-property-literals" "^7.7.4"
+    "@babel/plugin-transform-regenerator" "^7.7.5"
+    "@babel/plugin-transform-reserved-words" "^7.7.4"
+    "@babel/plugin-transform-shorthand-properties" "^7.7.4"
+    "@babel/plugin-transform-spread" "^7.7.4"
+    "@babel/plugin-transform-sticky-regex" "^7.7.4"
+    "@babel/plugin-transform-template-literals" "^7.7.4"
+    "@babel/plugin-transform-typeof-symbol" "^7.7.4"
+    "@babel/plugin-transform-unicode-regex" "^7.7.4"
+    "@babel/types" "^7.7.4"
+    browserslist "^4.6.0"
+    core-js-compat "^3.4.7"
+    invariant "^2.2.2"
+    js-levenshtein "^1.1.3"
+    semver "^5.5.0"
 
 "@babel/template@^7.7.4":
   version "7.7.4"
@@ -645,117 +1170,6 @@ babel-generator@^6.26.0:
     source-map "^0.5.7"
     trim-right "^1.0.1"
 
-babel-helper-bindify-decorators@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
-  dependencies:
-    babel-helper-explode-assignable-expression "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-call-delegate@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
-  dependencies:
-    babel-helper-hoist-variables "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-define-map@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f"
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    lodash "^4.17.4"
-
-babel-helper-explode-assignable-expression@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-explode-class@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb"
-  dependencies:
-    babel-helper-bindify-decorators "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-function-name@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
-  dependencies:
-    babel-helper-get-function-arity "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-get-function-arity@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-hoist-variables@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-optimise-call-expression@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-regex@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72"
-  dependencies:
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    lodash "^4.17.4"
-
-babel-helper-remap-async-to-generator@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b"
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-replace-supers@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a"
-  dependencies:
-    babel-helper-optimise-call-expression "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
 babel-helper-vue-jsx-merge-props@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6"
@@ -787,11 +1201,12 @@ babel-plugin-add-module-exports@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz#9ae9a1f4a8dc67f0cdec4f4aeda1e43a5ff65e25"
 
-babel-plugin-check-es2015-constants@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a"
+babel-plugin-dynamic-import-node@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"
+  integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==
   dependencies:
-    babel-runtime "^6.22.0"
+    object.assign "^4.1.0"
 
 babel-plugin-lodash@^3.2.11:
   version "3.3.4"
@@ -803,368 +1218,22 @@ babel-plugin-lodash@^3.2.11:
     lodash "^4.17.10"
     require-package-name "^2.0.1"
 
-babel-plugin-syntax-async-functions@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
-
-babel-plugin-syntax-async-generators@^6.5.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a"
-
-babel-plugin-syntax-class-properties@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de"
-
-babel-plugin-syntax-decorators@^6.13.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b"
-
-babel-plugin-syntax-dynamic-import@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
-
-babel-plugin-syntax-exponentiation-operator@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
-
 babel-plugin-syntax-jsx@^6.18.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
 
-babel-plugin-syntax-object-rest-spread@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
-
-babel-plugin-syntax-trailing-function-commas@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
-
-babel-plugin-transform-async-generator-functions@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db"
-  dependencies:
-    babel-helper-remap-async-to-generator "^6.24.1"
-    babel-plugin-syntax-async-generators "^6.5.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-to-generator@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
-  dependencies:
-    babel-helper-remap-async-to-generator "^6.24.1"
-    babel-plugin-syntax-async-functions "^6.8.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-class-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac"
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-plugin-syntax-class-properties "^6.8.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-decorators@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d"
-  dependencies:
-    babel-helper-explode-class "^6.24.1"
-    babel-plugin-syntax-decorators "^6.13.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-arrow-functions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es2015-block-scoping@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
-  dependencies:
-    babel-runtime "^6.26.0"
-    babel-template "^6.26.0"
-    babel-traverse "^6.26.0"
-    babel-types "^6.26.0"
-    lodash "^4.17.4"
-
-babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
-  dependencies:
-    babel-helper-define-map "^6.24.1"
-    babel-helper-function-name "^6.24.1"
-    babel-helper-optimise-call-expression "^6.24.1"
-    babel-helper-replace-supers "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-literals@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
-  dependencies:
-    babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
-  version "6.26.2"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
-  dependencies:
-    babel-plugin-transform-strict-mode "^6.24.1"
-    babel-runtime "^6.26.0"
-    babel-template "^6.26.0"
-    babel-types "^6.26.0"
-
-babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
-  dependencies:
-    babel-helper-hoist-variables "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015-modules-umd@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
-  dependencies:
-    babel-plugin-transform-es2015-modules-amd "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
-  dependencies:
-    babel-helper-replace-supers "^6.24.1"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
-  dependencies:
-    babel-helper-call-delegate "^6.24.1"
-    babel-helper-get-function-arity "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-spread@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
-  dependencies:
-    babel-helper-regex "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-template-literals@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
-  dependencies:
-    babel-helper-regex "^6.24.1"
-    babel-runtime "^6.22.0"
-    regexpu-core "^2.0.0"
-
-babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-exponentiation-operator@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
-  dependencies:
-    babel-helper-builder-binary-assignment-operator-visitor "^6.24.1"
-    babel-plugin-syntax-exponentiation-operator "^6.8.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-object-rest-spread@^6.22.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
-  dependencies:
-    babel-plugin-syntax-object-rest-spread "^6.8.0"
-    babel-runtime "^6.26.0"
-
-babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
-  dependencies:
-    regenerator-transform "^0.10.0"
-
 babel-plugin-transform-runtime@^6.0.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-strict-mode@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
 babel-plugin-transform-vue-jsx@3:
   version "3.7.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.7.0.tgz#d40492e6692a36b594f7e9a1928f43e969740960"
   dependencies:
     esutils "^2.0.2"
 
-babel-preset-env@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a"
-  dependencies:
-    babel-plugin-check-es2015-constants "^6.22.0"
-    babel-plugin-syntax-trailing-function-commas "^6.22.0"
-    babel-plugin-transform-async-to-generator "^6.22.0"
-    babel-plugin-transform-es2015-arrow-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoping "^6.23.0"
-    babel-plugin-transform-es2015-classes "^6.23.0"
-    babel-plugin-transform-es2015-computed-properties "^6.22.0"
-    babel-plugin-transform-es2015-destructuring "^6.23.0"
-    babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
-    babel-plugin-transform-es2015-for-of "^6.23.0"
-    babel-plugin-transform-es2015-function-name "^6.22.0"
-    babel-plugin-transform-es2015-literals "^6.22.0"
-    babel-plugin-transform-es2015-modules-amd "^6.22.0"
-    babel-plugin-transform-es2015-modules-commonjs "^6.23.0"
-    babel-plugin-transform-es2015-modules-systemjs "^6.23.0"
-    babel-plugin-transform-es2015-modules-umd "^6.23.0"
-    babel-plugin-transform-es2015-object-super "^6.22.0"
-    babel-plugin-transform-es2015-parameters "^6.23.0"
-    babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
-    babel-plugin-transform-es2015-spread "^6.22.0"
-    babel-plugin-transform-es2015-sticky-regex "^6.22.0"
-    babel-plugin-transform-es2015-template-literals "^6.22.0"
-    babel-plugin-transform-es2015-typeof-symbol "^6.23.0"
-    babel-plugin-transform-es2015-unicode-regex "^6.22.0"
-    babel-plugin-transform-exponentiation-operator "^6.22.0"
-    babel-plugin-transform-regenerator "^6.22.0"
-    browserslist "^3.2.6"
-    invariant "^2.2.2"
-    semver "^5.3.0"
-
-babel-preset-es2015@^6.0.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
-  dependencies:
-    babel-plugin-check-es2015-constants "^6.22.0"
-    babel-plugin-transform-es2015-arrow-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoping "^6.24.1"
-    babel-plugin-transform-es2015-classes "^6.24.1"
-    babel-plugin-transform-es2015-computed-properties "^6.24.1"
-    babel-plugin-transform-es2015-destructuring "^6.22.0"
-    babel-plugin-transform-es2015-duplicate-keys "^6.24.1"
-    babel-plugin-transform-es2015-for-of "^6.22.0"
-    babel-plugin-transform-es2015-function-name "^6.24.1"
-    babel-plugin-transform-es2015-literals "^6.22.0"
-    babel-plugin-transform-es2015-modules-amd "^6.24.1"
-    babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-systemjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-umd "^6.24.1"
-    babel-plugin-transform-es2015-object-super "^6.24.1"
-    babel-plugin-transform-es2015-parameters "^6.24.1"
-    babel-plugin-transform-es2015-shorthand-properties "^6.24.1"
-    babel-plugin-transform-es2015-spread "^6.22.0"
-    babel-plugin-transform-es2015-sticky-regex "^6.24.1"
-    babel-plugin-transform-es2015-template-literals "^6.22.0"
-    babel-plugin-transform-es2015-typeof-symbol "^6.22.0"
-    babel-plugin-transform-es2015-unicode-regex "^6.24.1"
-    babel-plugin-transform-regenerator "^6.24.1"
-
-babel-preset-stage-2@^6.0.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1"
-  dependencies:
-    babel-plugin-syntax-dynamic-import "^6.18.0"
-    babel-plugin-transform-class-properties "^6.24.1"
-    babel-plugin-transform-decorators "^6.24.1"
-    babel-preset-stage-3 "^6.24.1"
-
-babel-preset-stage-3@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395"
-  dependencies:
-    babel-plugin-syntax-trailing-function-commas "^6.22.0"
-    babel-plugin-transform-async-generator-functions "^6.24.1"
-    babel-plugin-transform-async-to-generator "^6.24.1"
-    babel-plugin-transform-exponentiation-operator "^6.24.1"
-    babel-plugin-transform-object-rest-spread "^6.22.0"
-
 babel-register@^6.0.0, babel-register@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
@@ -1177,7 +1246,7 @@ babel-register@^6.0.0, babel-register@^6.26.0:
     mkdirp "^0.5.1"
     source-map-support "^0.4.15"
 
-babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
+babel-runtime@^6.22.0, babel-runtime@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
   dependencies:
@@ -1194,7 +1263,7 @@ babel-template@^6.24.1, babel-template@^6.26.0:
     babylon "^6.18.0"
     lodash "^4.17.4"
 
-babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
+babel-traverse@^6.23.1, babel-traverse@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
   dependencies:
@@ -1208,7 +1277,7 @@ babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
     invariant "^2.2.2"
     lodash "^4.17.4"
 
-babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.26.0:
+babel-types@^6.23.0, babel-types@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
   dependencies:
@@ -1443,12 +1512,14 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
     caniuse-db "^1.0.30000639"
     electron-to-chromium "^1.2.7"
 
-browserslist@^3.2.6:
-  version "3.2.8"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6"
+browserslist@^4.6.0, browserslist@^4.8.2:
+  version "4.8.2"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.2.tgz#b45720ad5fbc8713b7253c20766f701c9a694289"
+  integrity sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==
   dependencies:
-    caniuse-lite "^1.0.30000844"
-    electron-to-chromium "^1.3.47"
+    caniuse-lite "^1.0.30001015"
+    electron-to-chromium "^1.3.322"
+    node-releases "^1.1.42"
 
 buffer-alloc-unsafe@^1.1.0:
   version "1.1.0"
@@ -1604,9 +1675,10 @@ caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
   version "1.0.30000928"
   resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000928.tgz#2e83d2b14276442da239511615eb7c62fed0cfa7"
 
-caniuse-lite@^1.0.30000844:
-  version "1.0.30000928"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000928.tgz#805e828dc72b06498e3683a32e61c7507fd67b88"
+caniuse-lite@^1.0.30001015:
+  version "1.0.30001015"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001015.tgz#15a7ddf66aba786a71d99626bc8f2b91c6f0f5f0"
+  integrity sha512-/xL2AbW/XWHNu1gnIrO8UitBGoFthcsDgU9VLK1/dpsoxbaD5LscHozKze05R6WLsBvLhqv78dAPozMFQBYLbQ==
 
 caseless@~0.12.0:
   version "0.12.0"
@@ -1979,7 +2051,15 @@ copy-descriptor@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
 
-core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7:
+core-js-compat@^3.4.7:
+  version "3.4.8"
+  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.4.8.tgz#f72e6a4ed76437ea710928f44615f926a81607d5"
+  integrity sha512-l3WTmnXHV2Sfu5VuD7EHE2w7y+K68+kULKt5RJg8ZJk3YhHF1qLD4O8v8AmNq+8vbOwnPFFDvds25/AoEvMqlQ==
+  dependencies:
+    browserslist "^4.8.2"
+    semver "^6.3.0"
+
+core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.2.tgz#267988d7268323b349e20b4588211655f0e83944"
 
@@ -2496,10 +2576,15 @@ ejs@2.5.7:
   version "2.5.7"
   resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
 
-electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.47:
+electron-to-chromium@^1.2.7:
   version "1.3.100"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.100.tgz#899fb088def210aee6b838a47655bbb299190e13"
 
+electron-to-chromium@^1.3.322:
+  version "1.3.322"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz#a6f7e1c79025c2b05838e8e344f6e89eb83213a8"
+  integrity sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==
+
 elliptic@^6.0.0:
   version "6.4.1"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"
@@ -4215,6 +4300,11 @@ js-base64@^2.1.9:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.0.tgz#42255ba183ab67ce59a0dee640afdc00ab5ae93e"
 
+js-levenshtein@^1.1.3:
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
+  integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
+
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
@@ -5265,6 +5355,13 @@ node-pre-gyp@^0.12.0:
     semver "^5.3.0"
     tar "^4"
 
+node-releases@^1.1.42:
+  version "1.1.42"
+  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.42.tgz#a999f6a62f8746981f6da90627a8d2fc090bbad7"
+  integrity sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA==
+  dependencies:
+    semver "^6.3.0"
+
 nomnomnomnom@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/nomnomnomnom/-/nomnomnomnom-2.0.1.tgz#b2239f031c8d04da67e32836e1e3199e12f7a8e2"
@@ -5381,7 +5478,7 @@ object-hash@^1.1.4:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
 
-object-keys@^1.0.12:
+object-keys@^1.0.11, object-keys@^1.0.12:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
 
@@ -5395,6 +5492,16 @@ object-visit@^1.0.0:
   dependencies:
     isobject "^3.0.0"
 
+object.assign@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
+  integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
+  dependencies:
+    define-properties "^1.1.2"
+    function-bind "^1.1.1"
+    has-symbols "^1.0.0"
+    object-keys "^1.0.11"
+
 object.getownpropertydescriptors@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
@@ -6399,7 +6506,14 @@ reduce-function-call@^1.0.1:
   dependencies:
     balanced-match "^0.4.2"
 
-regenerate@^1.2.1:
+regenerate-unicode-properties@^8.1.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
+  integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==
+  dependencies:
+    regenerate "^1.4.0"
+
+regenerate@^1.2.1, regenerate@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
 
@@ -6407,16 +6521,11 @@ regenerator-runtime@^0.11.0:
   version "0.11.1"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
 
-regenerator-runtime@^0.12.0:
-  version "0.12.1"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
-
-regenerator-transform@^0.10.0:
-  version "0.10.1"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
+regenerator-transform@^0.14.0:
+  version "0.14.1"
+  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"
+  integrity sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==
   dependencies:
-    babel-runtime "^6.18.0"
-    babel-types "^6.19.0"
     private "^0.1.6"
 
 regex-cache@^0.4.2:
@@ -6444,24 +6553,40 @@ regexpu-core@^1.0.0:
     regjsgen "^0.2.0"
     regjsparser "^0.1.4"
 
-regexpu-core@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
+regexpu-core@^4.6.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6"
+  integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==
   dependencies:
-    regenerate "^1.2.1"
-    regjsgen "^0.2.0"
-    regjsparser "^0.1.4"
+    regenerate "^1.4.0"
+    regenerate-unicode-properties "^8.1.0"
+    regjsgen "^0.5.0"
+    regjsparser "^0.6.0"
+    unicode-match-property-ecmascript "^1.0.4"
+    unicode-match-property-value-ecmascript "^1.1.0"
 
 regjsgen@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
 
+regjsgen@^0.5.0:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c"
+  integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==
+
 regjsparser@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
   dependencies:
     jsesc "~0.5.0"
 
+regjsparser@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c"
+  integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==
+  dependencies:
+    jsesc "~0.5.0"
+
 relateurl@0.2.x:
   version "0.2.7"
   resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
@@ -6712,6 +6837,11 @@ semver@^5.5.1:
   version "5.7.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
 
+semver@^6.3.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
 semver@~5.0.1:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
@@ -7451,6 +7581,29 @@ underscore@~1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
 
+unicode-canonical-property-names-ecmascript@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
+  integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==
+
+unicode-match-property-ecmascript@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c"
+  integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==
+  dependencies:
+    unicode-canonical-property-names-ecmascript "^1.0.4"
+    unicode-property-aliases-ecmascript "^1.0.4"
+
+unicode-match-property-value-ecmascript@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277"
+  integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==
+
+unicode-property-aliases-ecmascript@^1.0.4:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57"
+  integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==
+
 union-value@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"

From a5cce10a7d1369af64c62024f0e9c388e3c17bac Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Wed, 11 Dec 2019 13:56:53 +0500
Subject: [PATCH 069/483] update babelrc presets config

---
 .babelrc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.babelrc b/.babelrc
index bc2b0e31..0fd2cc39 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,5 @@
 {
-  "presets": ["es2015", "stage-2", "env"],
+  "presets": ["@babel/preset-env"],
   "plugins": ["transform-runtime", "lodash", "transform-vue-jsx"],
   "comments": false
 }

From d79dfb94950fff9f99a14ae38891a11d4c5aa373 Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Wed, 11 Dec 2019 14:00:36 +0500
Subject: [PATCH 070/483] upgrade babel-plugin-transform-vue-jsx

---
 package.json |  5 ++--
 yarn.lock    | 67 ++++++++++++++++++++++++++++++++++++----------------
 2 files changed, 48 insertions(+), 24 deletions(-)

diff --git a/package.json b/package.json
index 9bf498e1..299eb5df 100644
--- a/package.json
+++ b/package.json
@@ -42,14 +42,13 @@
   "devDependencies": {
     "@babel/core": "^7.7.5",
     "@babel/preset-env": "^7.7.6",
+    "@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
+    "@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
     "@vue/test-utils": "^1.0.0-beta.26",
     "autoprefixer": "^6.4.0",
     "babel-eslint": "^7.0.0",
-    "babel-helper-vue-jsx-merge-props": "^2.0.3",
     "babel-loader": "^8.0.6",
-    "babel-plugin-syntax-jsx": "^6.18.0",
     "babel-plugin-transform-runtime": "^6.0.0",
-    "babel-plugin-transform-vue-jsx": "3",
     "babel-register": "^6.0.0",
     "chai": "^3.5.0",
     "chalk": "^1.1.3",
diff --git a/yarn.lock b/yarn.lock
index b5017997..ed35517a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -124,19 +124,19 @@
   dependencies:
     "@babel/types" "^7.7.4"
 
-"@babel/helper-module-imports@^7.0.0-beta.49":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d"
-  dependencies:
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-module-imports@^7.7.4":
+"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.7.4":
   version "7.7.4"
   resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz#e5a92529f8888bf319a6376abfbd1cebc491ad91"
   integrity sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==
   dependencies:
     "@babel/types" "^7.7.4"
 
+"@babel/helper-module-imports@^7.0.0-beta.49":
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d"
+  dependencies:
+    "@babel/types" "^7.0.0"
+
 "@babel/helper-module-transforms@^7.7.4", "@babel/helper-module-transforms@^7.7.5":
   version "7.7.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz#d044da7ffd91ec967db25cd6748f704b6b244835"
@@ -306,6 +306,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
+"@babel/plugin-syntax-jsx@^7.2.0":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.7.4.tgz#dab2b56a36fb6c3c222a1fbc71f7bf97f327a9ec"
+  integrity sha512-wuy6fiMe9y7HeZBWXYCGt2RGxZOj0BImZ9EyXJVnVGBKO/Br592rbR3rtIQn0eQhAk9vqaKP5n8tVqEFBQMfLg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
 "@babel/plugin-syntax-object-rest-spread@^7.7.4":
   version "7.7.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz#47cf220d19d6d0d7b154304701f468fc1cc6ff46"
@@ -675,6 +682,23 @@
   dependencies:
     qrcode "^1.3.0"
 
+"@vue/babel-helper-vue-jsx-merge-props@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040"
+  integrity sha512-6tyf5Cqm4m6v7buITuwS+jHzPlIPxbFzEhXR5JGZpbrvOcp1hiQKckd305/3C7C36wFekNTQSxAtgeM0j0yoUw==
+
+"@vue/babel-plugin-transform-vue-jsx@^1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.1.2.tgz#c0a3e6efc022e75e4247b448a8fc6b86f03e91c0"
+  integrity sha512-YfdaoSMvD1nj7+DsrwfTvTnhDXI7bsuh+Y5qWwvQXlD24uLgnsoww3qbiZvWf/EoviZMrvqkqN4CBw0W3BWUTQ==
+  dependencies:
+    "@babel/helper-module-imports" "^7.0.0"
+    "@babel/plugin-syntax-jsx" "^7.2.0"
+    "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0"
+    html-tags "^2.0.0"
+    lodash.kebabcase "^4.1.1"
+    svg-tags "^1.0.0"
+
 "@vue/test-utils@^1.0.0-beta.26":
   version "1.0.0-beta.28"
   resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.28.tgz#767c43413df8cde86128735e58923803e444b9a5"
@@ -1170,10 +1194,6 @@ babel-generator@^6.26.0:
     source-map "^0.5.7"
     trim-right "^1.0.1"
 
-babel-helper-vue-jsx-merge-props@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6"
-
 babel-helpers@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
@@ -1218,22 +1238,12 @@ babel-plugin-lodash@^3.2.11:
     lodash "^4.17.10"
     require-package-name "^2.0.1"
 
-babel-plugin-syntax-jsx@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
-
 babel-plugin-transform-runtime@^6.0.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-vue-jsx@3:
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.7.0.tgz#d40492e6692a36b594f7e9a1928f43e969740960"
-  dependencies:
-    esutils "^2.0.2"
-
 babel-register@^6.0.0, babel-register@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
@@ -3704,6 +3714,11 @@ html-minifier@^3.2.3:
     relateurl "0.2.x"
     uglify-js "3.4.x"
 
+html-tags@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b"
+  integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=
+
 html-webpack-plugin@^3.0.0, html-webpack-plugin@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b"
@@ -4781,6 +4796,11 @@ lodash.istypedarray@^3.0.0:
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62"
 
+lodash.kebabcase@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
+  integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY=
+
 lodash.keys@^3.0.0:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
@@ -7350,6 +7370,11 @@ supports-color@^6.0.0, supports-color@^6.1.0:
   dependencies:
     has-flag "^3.0.0"
 
+svg-tags@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
+  integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
+
 svgo@^0.7.0:
   version "0.7.2"
   resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"

From c141c7a753673705be89c056c1ed99126f786c7d Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Wed, 11 Dec 2019 14:01:53 +0500
Subject: [PATCH 071/483] babel-plugin-add-module-exports is not necessary for
 babel v7

---
 package.json | 1 -
 yarn.lock    | 4 ----
 2 files changed, 5 deletions(-)

diff --git a/package.json b/package.json
index 299eb5df..b920a3ab 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,6 @@
   },
   "dependencies": {
     "@chenfengyuan/vue-qrcode": "^1.0.0",
-    "babel-plugin-add-module-exports": "^0.2.1",
     "babel-plugin-lodash": "^3.2.11",
     "body-scroll-lock": "^2.6.4",
     "chromatism": "^3.0.0",
diff --git a/yarn.lock b/yarn.lock
index ed35517a..acc52ac8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1217,10 +1217,6 @@ babel-messages@^6.23.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-add-module-exports@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz#9ae9a1f4a8dc67f0cdec4f4aeda1e43a5ff65e25"
-
 babel-plugin-dynamic-import-node@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"

From 773fa454bef87db9bfb3c08e4ba25ea38096c415 Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Wed, 11 Dec 2019 14:02:51 +0500
Subject: [PATCH 072/483] upgrade babel-plugin-lodash

---
 package.json | 2 +-
 yarn.lock    | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index b920a3ab..d941e501 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,6 @@
   },
   "dependencies": {
     "@chenfengyuan/vue-qrcode": "^1.0.0",
-    "babel-plugin-lodash": "^3.2.11",
     "body-scroll-lock": "^2.6.4",
     "chromatism": "^3.0.0",
     "cropperjs": "^1.4.3",
@@ -47,6 +46,7 @@
     "autoprefixer": "^6.4.0",
     "babel-eslint": "^7.0.0",
     "babel-loader": "^8.0.6",
+    "babel-plugin-lodash": "^3.3.4",
     "babel-plugin-transform-runtime": "^6.0.0",
     "babel-register": "^6.0.0",
     "chai": "^3.5.0",
diff --git a/yarn.lock b/yarn.lock
index acc52ac8..2dec375a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1224,9 +1224,10 @@ babel-plugin-dynamic-import-node@^2.3.0:
   dependencies:
     object.assign "^4.1.0"
 
-babel-plugin-lodash@^3.2.11:
+babel-plugin-lodash@^3.3.4:
   version "3.3.4"
   resolved "https://registry.yarnpkg.com/babel-plugin-lodash/-/babel-plugin-lodash-3.3.4.tgz#4f6844358a1340baed182adbeffa8df9967bc196"
+  integrity sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg==
   dependencies:
     "@babel/helper-module-imports" "^7.0.0-beta.49"
     "@babel/types" "^7.0.0-beta.49"

From 424e78891afa7f76ed58191187c3e65a7727eaee Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Wed, 11 Dec 2019 14:19:56 +0500
Subject: [PATCH 073/483] upgrade babel-plugin-transform-runtime

---
 package.json |  3 ++-
 yarn.lock    | 28 ++++++++++++++++++++++------
 2 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/package.json b/package.json
index d941e501..c1da6ded 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
     "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
   },
   "dependencies": {
+    "@babel/runtime": "^7.7.6",
     "@chenfengyuan/vue-qrcode": "^1.0.0",
     "body-scroll-lock": "^2.6.4",
     "chromatism": "^3.0.0",
@@ -39,6 +40,7 @@
   },
   "devDependencies": {
     "@babel/core": "^7.7.5",
+    "@babel/plugin-transform-runtime": "^7.7.6",
     "@babel/preset-env": "^7.7.6",
     "@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
     "@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
@@ -47,7 +49,6 @@
     "babel-eslint": "^7.0.0",
     "babel-loader": "^8.0.6",
     "babel-plugin-lodash": "^3.3.4",
-    "babel-plugin-transform-runtime": "^6.0.0",
     "babel-register": "^6.0.0",
     "chai": "^3.5.0",
     "chalk": "^1.1.3",
diff --git a/yarn.lock b/yarn.lock
index 2dec375a..99ddd096 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -533,6 +533,16 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
+"@babel/plugin-transform-runtime@^7.7.6":
+  version "7.7.6"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.6.tgz#4f2b548c88922fb98ec1c242afd4733ee3e12f61"
+  integrity sha512-tajQY+YmXR7JjTwRvwL4HePqoL3DYxpYXIHKVvrOIvJmeHe2y1w4tz5qz9ObUDC9m76rCzIMPyn4eERuwA4a4A==
+  dependencies:
+    "@babel/helper-module-imports" "^7.7.4"
+    "@babel/helper-plugin-utils" "^7.0.0"
+    resolve "^1.8.1"
+    semver "^5.5.1"
+
 "@babel/plugin-transform-shorthand-properties@^7.7.4":
   version "7.7.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.7.4.tgz#74a0a9b2f6d67a684c6fbfd5f0458eb7ba99891e"
@@ -635,6 +645,13 @@
     js-levenshtein "^1.1.3"
     semver "^5.5.0"
 
+"@babel/runtime@^7.7.6":
+  version "7.7.6"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f"
+  integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==
+  dependencies:
+    regenerator-runtime "^0.13.2"
+
 "@babel/template@^7.7.4":
   version "7.7.4"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b"
@@ -1235,12 +1252,6 @@ babel-plugin-lodash@^3.3.4:
     lodash "^4.17.10"
     require-package-name "^2.0.1"
 
-babel-plugin-transform-runtime@^6.0.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee"
-  dependencies:
-    babel-runtime "^6.22.0"
-
 babel-register@^6.0.0, babel-register@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
@@ -6538,6 +6549,11 @@ regenerator-runtime@^0.11.0:
   version "0.11.1"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
 
+regenerator-runtime@^0.13.2:
+  version "0.13.3"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
+  integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==
+
 regenerator-transform@^0.14.0:
   version "0.14.1"
   resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"

From e6d5b9c0bba7410044d0ad2fe7e3670a5026d0d4 Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Wed, 11 Dec 2019 14:21:06 +0500
Subject: [PATCH 074/483] update babelrc

---
 .babelrc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.babelrc b/.babelrc
index 0fd2cc39..3019976f 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,5 @@
 {
   "presets": ["@babel/preset-env"],
-  "plugins": ["transform-runtime", "lodash", "transform-vue-jsx"],
+  "plugins": ["@babel/plugin-transform-runtime", "lodash", "transform-vue-jsx"],
   "comments": false
 }

From cb92865dac3c8d961be01d506726b1692ce50d03 Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Wed, 11 Dec 2019 14:22:11 +0500
Subject: [PATCH 075/483] upgrade babel-register

---
 package.json                |  2 +-
 test/e2e/nightwatch.conf.js |  2 +-
 yarn.lock                   | 35 +++++++++++++++++++++++++++++++++--
 3 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/package.json b/package.json
index c1da6ded..38936b23 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
     "@babel/core": "^7.7.5",
     "@babel/plugin-transform-runtime": "^7.7.6",
     "@babel/preset-env": "^7.7.6",
+    "@babel/register": "^7.7.4",
     "@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
     "@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
     "@vue/test-utils": "^1.0.0-beta.26",
@@ -49,7 +50,6 @@
     "babel-eslint": "^7.0.0",
     "babel-loader": "^8.0.6",
     "babel-plugin-lodash": "^3.3.4",
-    "babel-register": "^6.0.0",
     "chai": "^3.5.0",
     "chalk": "^1.1.3",
     "chromedriver": "^2.21.2",
diff --git a/test/e2e/nightwatch.conf.js b/test/e2e/nightwatch.conf.js
index 2fc3af0b..07d974df 100644
--- a/test/e2e/nightwatch.conf.js
+++ b/test/e2e/nightwatch.conf.js
@@ -1,4 +1,4 @@
-require('babel-register')
+require('@babel/register')
 var config = require('../../config')
 
 // http://nightwatchjs.org/guide#settings-file
diff --git a/yarn.lock b/yarn.lock
index 99ddd096..4b20a6a1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -645,6 +645,17 @@
     js-levenshtein "^1.1.3"
     semver "^5.5.0"
 
+"@babel/register@^7.7.4":
+  version "7.7.4"
+  resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.7.4.tgz#45a4956471a9df3b012b747f5781cc084ee8f128"
+  integrity sha512-/fmONZqL6ZMl9KJUYajetCrID6m0xmL4odX7v+Xvoxcv0DdbP/oO0TWIeLUCHqczQ6L6njDMqmqHFy2cp3FFsA==
+  dependencies:
+    find-cache-dir "^2.0.0"
+    lodash "^4.17.13"
+    make-dir "^2.1.0"
+    pirates "^4.0.0"
+    source-map-support "^0.5.16"
+
 "@babel/runtime@^7.7.6":
   version "7.7.6"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f"
@@ -1252,7 +1263,7 @@ babel-plugin-lodash@^3.3.4:
     lodash "^4.17.10"
     require-package-name "^2.0.1"
 
-babel-register@^6.0.0, babel-register@^6.26.0:
+babel-register@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
   dependencies:
@@ -4950,7 +4961,7 @@ lru-cache@~2.6.5:
   version "2.6.5"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5"
 
-make-dir@^2.0.0:
+make-dir@^2.0.0, make-dir@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
   dependencies:
@@ -5368,6 +5379,11 @@ node-libs-browser@^2.0.0:
     util "^0.11.0"
     vm-browserify "0.0.4"
 
+node-modules-regexp@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
+  integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
+
 node-pre-gyp@^0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
@@ -5885,6 +5901,13 @@ pinkie@^2.0.0:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
 
+pirates@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
+  integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==
+  dependencies:
+    node-modules-regexp "^1.0.0"
+
 pkg-dir@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
@@ -7130,6 +7153,14 @@ source-map-support@^0.4.15:
   dependencies:
     source-map "^0.5.6"
 
+source-map-support@^0.5.16:
+  version "0.5.16"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
+  integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
 source-map-support@~0.5.10:
   version "0.5.12"
   resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"

From e8333fff8dc048f5d5d8406c71cb80e95ea1982c Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Wed, 11 Dec 2019 18:20:23 +0900
Subject: [PATCH 076/483] change the expression of `move`

---
 src/components/notification/notification.vue | 2 +-
 src/i18n/en.json                             | 4 ++--
 src/modules/statuses.js                      | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 33ae054f..16124e50 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -76,7 +76,7 @@
             </span>
             <span v-if="notification.type === 'move'">
               <i class="fa icon-arrow-curved lit" />
-              <small>{{ $t('notifications.moved_to') }}</small>
+              <small>{{ $t('notifications.migrated_to') }}</small>
             </span>
           </div>
           <div
diff --git a/src/i18n/en.json b/src/i18n/en.json
index dcd2f3d7..4fd8ee0b 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -111,7 +111,7 @@
     "read": "Read!",
     "repeated_you": "repeated your status",
     "no_more_notifications": "No more notifications",
-    "moved_to": "moved to"
+    "migrated_to": "migrated to"
   },
   "polls": {
     "add_poll": "Add Poll",
@@ -312,7 +312,7 @@
     "notification_visibility_likes": "Likes",
     "notification_visibility_mentions": "Mentions",
     "notification_visibility_repeats": "Repeats",
-    "notification_visibility_moves": "Moves",
+    "notification_visibility_moves": "User Migrates",
     "no_rich_text_description": "Strip rich text formatting from all posts",
     "no_blocks": "No blocks",
     "no_mutes": "No mutes",
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 420b3183..f3fc0f21 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -340,7 +340,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
             i18nString = 'followed_you'
             break
           case 'move':
-            i18nString = 'moved_to'
+            i18nString = 'migrated_to'
             break
         }
 

From b4acbf5311b5f8db21bc2e32a9e5246425edcec4 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Wed, 11 Dec 2019 18:25:52 +0900
Subject: [PATCH 077/483] Add user migrates filter to interactions

---
 src/components/interactions/interactions.js  | 3 ++-
 src/components/interactions/interactions.vue | 4 ++++
 src/i18n/en.json                             | 1 +
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js
index 1f8a9de9..cc31ff20 100644
--- a/src/components/interactions/interactions.js
+++ b/src/components/interactions/interactions.js
@@ -3,7 +3,8 @@ import Notifications from '../notifications/notifications.vue'
 const tabModeDict = {
   mentions: ['mention'],
   'likes+repeats': ['repeat', 'like'],
-  follows: ['follow']
+  follows: ['follow'],
+  moves: ['move']
 }
 
 const Interactions = {
diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue
index 08cee343..a2e252ab 100644
--- a/src/components/interactions/interactions.vue
+++ b/src/components/interactions/interactions.vue
@@ -21,6 +21,10 @@
         key="follows"
         :label="$t('interactions.follows')"
       />
+      <span
+        key="moves"
+        :label="$t('interactions.moves')"
+      />
     </tab-switcher>
     <Notifications
       ref="notifications"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 4fd8ee0b..effc139f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -141,6 +141,7 @@
   "interactions": {
     "favs_repeats": "Repeats and Favorites",
     "follows": "New follows",
+    "moves": "User migrates",
     "load_older": "Load older interactions"
   },
   "post_status": {

From c3e7806acbd32483eab497a347f947158ab8febf Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Wed, 11 Dec 2019 18:48:18 +0900
Subject: [PATCH 078/483] remove unused fallback

---
 src/components/notification/notification.js | 16 ++--------------
 src/modules/users.js                        |  2 ++
 2 files changed, 4 insertions(+), 14 deletions(-)

diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 93edf2fa..e7bd769e 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -43,26 +43,14 @@ const Notification = {
       const user = this.notification.from_profile
       return highlightStyle(highlight[user.screen_name])
     },
-    userInStore () {
-      return this.$store.getters.findUser(this.notification.from_profile.id)
-    },
     user () {
-      if (this.userInStore) {
-        return this.userInStore
-      }
-      return this.notification.from_profile
+      return this.$store.getters.findUser(this.notification.from_profile.id)
     },
     userProfileLink () {
       return this.generateUserProfileLink(this.user)
     },
-    targetUserInStore () {
-      return this.$store.getters.findUser(this.notification.target.id)
-    },
     targetUser () {
-      if (this.targetUserInStore) {
-        return this.targetUserInStore
-      }
-      return this.notification.target
+      return this.$store.getters.findUser(this.notification.target.id)
     },
     targetUserProfileLink () {
       return this.generateUserProfileLink(this.targetUser)
diff --git a/src/modules/users.js b/src/modules/users.js
index 14b2d8b5..f209b23b 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -368,8 +368,10 @@ const users = {
     },
     addNewNotifications (store, { notifications }) {
       const users = map(notifications, 'from_profile')
+      const targetUsers = map(notifications, 'target')
       const notificationIds = notifications.map(_ => _.id)
       store.commit('addNewUsers', users)
+      store.commit('addNewUsers', targetUsers)
 
       const notificationsObject = store.rootState.statuses.notifications.idStore
       const relevantNotifications = Object.entries(notificationsObject)

From d6dc2bad1f2175f9a9eb0dfd4c04fafab410632b Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Wed, 11 Dec 2019 15:59:29 +0300
Subject: [PATCH 079/483] fixed typo

---
 src/services/api/api.service.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 8f5eb416..68be0d50 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -22,7 +22,7 @@ const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
 
 const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
 const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
-const MFA_DISABLE_OTP_URL = '/api/pleroma/account/mfa/totp'
+const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp'
 
 const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
 const MASTODON_REGISTRATION_URL = '/api/v1/accounts'

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: [PATCH 080/483] 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(-)

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,

From fee3226705beb4b6eddb8e64f8c53b2651ca89fa Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Mon, 9 Dec 2019 15:21:33 -0500
Subject: [PATCH 081/483] add documentation for new instance option

---
 docs/CONFIGURATION.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 35363537..3a21828b 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -77,6 +77,9 @@ Use custom image for NSFW'd images
 ### `showFeaturesPanel`
 Show panel showcasing instance features/settings to logged-out visitors
 
+### `hideSitename`
+Hide instance name in header
+
 ## Indirect configuration
 Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it.
 

From 63a5f50e7c4acfc7676a1093990d0377dcb1a39f Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 11 Dec 2019 18:20:43 +0200
Subject: [PATCH 082/483] fix deletes causing errors

---
 src/services/api/api.service.js | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 517b953e..5f706dc0 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1010,6 +1010,10 @@ export const handleMastoWS = (wsEvent) => {
   const parsedEvent = JSON.parse(data)
   const { event, payload } = parsedEvent
   if (MASTODON_STREAMING_EVENTS.has(event)) {
+    // MastoBE and PleromaBE both send payload for delete as a PLAIN string
+    if (event === 'delete') {
+      return { event, id: payload }
+    }
     const data = payload ? JSON.parse(payload) : null
     if (event === 'update') {
       return { event, status: parseStatus(data) }

From d7bc1aff1d4c52c038457fe7650ffb793dfc4618 Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Thu, 12 Dec 2019 06:35:48 +0500
Subject: [PATCH 083/483] fix babelrc plugin config

---
 .babelrc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.babelrc b/.babelrc
index 3019976f..3c732dd1 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,5 @@
 {
   "presets": ["@babel/preset-env"],
-  "plugins": ["@babel/plugin-transform-runtime", "lodash", "transform-vue-jsx"],
+  "plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-transform-vue-jsx"],
   "comments": false
 }

From 386719b0d03475fb5cab667ce28a5aff354fbc4d Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Thu, 12 Dec 2019 08:33:40 +0500
Subject: [PATCH 084/483] fix css runtime loading issue

---
 src/App.scss                         | 13 +++++++++++++
 src/components/timeline/timeline.vue | 13 -------------
 2 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 925913f2..754ca62e 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -870,3 +870,16 @@ nav {
     transform: rotate(359deg);
   }
 }
+
+.new-status-notification {
+  position:relative;
+  margin-top: -1px;
+  font-size: 1.1em;
+  border-width: 1px 0 0 0;
+  border-style: solid;
+  border-color: var(--border, $fallback--border);
+  padding: 10px;
+  z-index: 1;
+  background-color: $fallback--fg;
+  background-color: var(--panel, $fallback--fg);
+}
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 93f6f570..a6fba452 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -93,17 +93,4 @@
     opacity: 1;
   }
 }
-
-.new-status-notification {
-  position:relative;
-  margin-top: -1px;
-  font-size: 1.1em;
-  border-width: 1px 0 0 0;
-  border-style: solid;
-  border-color: var(--border, $fallback--border);
-  padding: 10px;
-  z-index: 1;
-  background-color: $fallback--fg;
-  background-color: var(--panel, $fallback--fg);
-}
 </style>

From f70fe28f644c037326a1d2c1fdffbf6d365e0a02 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Thu, 12 Dec 2019 08:42:21 +0300
Subject: [PATCH 085/483] mfa: fix login and recovery form

---
 src/components/mfa_form/recovery_form.js | 11 ++++++++---
 src/components/mfa_form/totp_form.js     | 10 ++++++++--
 src/services/new_api/mfa.js              | 12 ++++++------
 3 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/src/components/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js
index 7a3cc22d..b25c65dd 100644
--- a/src/components/mfa_form/recovery_form.js
+++ b/src/components/mfa_form/recovery_form.js
@@ -8,18 +8,23 @@ export default {
   }),
   computed: {
     ...mapGetters({
-      authApp: 'authFlow/app',
       authSettings: 'authFlow/settings'
     }),
-    ...mapState({ instance: 'instance' })
+    ...mapState({
+      instance: 'instance',
+      oauth: 'oauth'
+    })
   },
   methods: {
     ...mapMutations('authFlow', ['requireTOTP', 'abortMFA']),
     ...mapActions({ login: 'authFlow/login' }),
     clearError () { this.error = false },
     submit () {
+      const { clientId, clientSecret } = this.oauth
+
       const data = {
-        app: this.authApp,
+        clientId,
+        clientSecret,
         instance: this.instance.server,
         mfaToken: this.authSettings.mfa_token,
         code: this.code
diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js
index 778bf8dc..1ec7576b 100644
--- a/src/components/mfa_form/totp_form.js
+++ b/src/components/mfa_form/totp_form.js
@@ -10,15 +10,21 @@ export default {
       authApp: 'authFlow/app',
       authSettings: 'authFlow/settings'
     }),
-    ...mapState({ instance: 'instance' })
+    ...mapState({
+      instance: 'instance',
+      oauth: 'oauth'
+    })
   },
   methods: {
     ...mapMutations('authFlow', ['requireRecovery', 'abortMFA']),
     ...mapActions({ login: 'authFlow/login' }),
     clearError () { this.error = false },
     submit () {
+      const { clientId, clientSecret } = this.oauth
+
       const data = {
-        app: this.authApp,
+        clientId,
+        clientSecret,
         instance: this.instance.server,
         mfaToken: this.authSettings.mfa_token,
         code: this.code
diff --git a/src/services/new_api/mfa.js b/src/services/new_api/mfa.js
index cbba06d5..c944667c 100644
--- a/src/services/new_api/mfa.js
+++ b/src/services/new_api/mfa.js
@@ -1,9 +1,9 @@
-const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
+const verifyOTPCode = ({ clientId, clientSecret, instance, mfaToken, code }) => {
   const url = `${instance}/oauth/mfa/challenge`
   const form = new window.FormData()
 
-  form.append('client_id', app.client_id)
-  form.append('client_secret', app.client_secret)
+  form.append('client_id', clientId)
+  form.append('client_secret', clientSecret)
   form.append('mfa_token', mfaToken)
   form.append('code', code)
   form.append('challenge_type', 'totp')
@@ -14,12 +14,12 @@ const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
   }).then((data) => data.json())
 }
 
-const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => {
+const verifyRecoveryCode = ({ clientId, clientSecret, instance, mfaToken, code }) => {
   const url = `${instance}/oauth/mfa/challenge`
   const form = new window.FormData()
 
-  form.append('client_id', app.client_id)
-  form.append('client_secret', app.client_secret)
+  form.append('client_id', clientId)
+  form.append('client_secret', clientSecret)
   form.append('mfa_token', mfaToken)
   form.append('code', code)
   form.append('challenge_type', 'recovery')

From b973ee5915ca9a2c79d9523286f42aee4b1a7db7 Mon Sep 17 00:00:00 2001
From: seven <sjang8762@outlook.com>
Date: Thu, 12 Dec 2019 12:13:31 +0500
Subject: [PATCH 086/483] must use h in higher babel-plugin-transform-vue-jsx

---
 src/hocs/with_load_more/with_load_more.js       | 4 ++--
 src/hocs/with_subscription/with_subscription.js | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/hocs/with_load_more/with_load_more.js b/src/hocs/with_load_more/with_load_more.js
index 1e1b2a74..6142f513 100644
--- a/src/hocs/with_load_more/with_load_more.js
+++ b/src/hocs/with_load_more/with_load_more.js
@@ -65,7 +65,7 @@ const withLoadMore = ({
         }
       }
     },
-    render (createElement) {
+    render (h) {
       const props = {
         props: {
           ...this.$props,
@@ -74,7 +74,7 @@ const withLoadMore = ({
         on: this.$listeners,
         scopedSlots: this.$scopedSlots
       }
-      const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
+      const children = Object.entries(this.$slots).map(([key, value]) => h('template', { slot: key }, value))
       return (
         <div class="with-load-more">
           <WrappedComponent {...props}>
diff --git a/src/hocs/with_subscription/with_subscription.js b/src/hocs/with_subscription/with_subscription.js
index 91fc4cca..1775adcb 100644
--- a/src/hocs/with_subscription/with_subscription.js
+++ b/src/hocs/with_subscription/with_subscription.js
@@ -49,7 +49,7 @@ const withSubscription = ({
         }
       }
     },
-    render (createElement) {
+    render (h) {
       if (!this.error && !this.loading) {
         const props = {
           props: {
@@ -59,7 +59,7 @@ const withSubscription = ({
           on: this.$listeners,
           scopedSlots: this.$scopedSlots
         }
-        const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
+        const children = Object.entries(this.$slots).map(([key, value]) => h('template', { slot: key }, value))
         return (
           <div class="with-subscription">
             <WrappedComponent {...props}>

From b3992358487d5afa7499759a90d6447a2b0bfe20 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 12 Dec 2019 09:38:24 +0000
Subject: [PATCH 087/483] Revert "Merge branch 'oauth-extra-scopes' into
 'develop'"

This reverts merge request !1024
---
 src/services/new_api/oauth.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/services/new_api/oauth.js b/src/services/new_api/oauth.js
index 3c8e64bd..d0d18c03 100644
--- a/src/services/new_api/oauth.js
+++ b/src/services/new_api/oauth.js
@@ -12,7 +12,7 @@ export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) =>
 
   form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`)
   form.append('redirect_uris', REDIRECT_URI)
-  form.append('scopes', 'read write follow push admin')
+  form.append('scopes', 'read write follow')
 
   return window.fetch(url, {
     method: 'POST',
@@ -28,7 +28,7 @@ const login = ({ instance, clientId }) => {
     response_type: 'code',
     client_id: clientId,
     redirect_uri: REDIRECT_URI,
-    scope: 'read write follow push admin'
+    scope: 'read write follow'
   }
 
   const dataString = reduce(data, (acc, v, k) => {

From ed3144eb1168ece5fcd3eed2867c653f785039d8 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 12 Dec 2019 18:19:46 +0700
Subject: [PATCH 088/483] Support "native" captcha

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

diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 5bb06a4f..222b67a8 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -172,7 +172,7 @@
                 for="captcha-label"
               >{{ $t('captcha') }}</label>
 
-              <template v-if="captcha.type == 'kocaptcha'">
+              <template v-if="['kocaptcha', 'native'].includes(captcha.type)">
                 <img
                   :src="captcha.url"
                   @click="setCaptcha"

From 341416b0e0102b20d9b8ee317218ee55d0402452 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantbusiness@gmail.com>
Date: Thu, 12 Dec 2019 14:43:48 +0000
Subject: [PATCH 089/483] Revert "Merge branch 'revert-96cab6d8' into
 'develop'"

This reverts merge request !1032
---
 src/services/new_api/oauth.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/services/new_api/oauth.js b/src/services/new_api/oauth.js
index d0d18c03..3c8e64bd 100644
--- a/src/services/new_api/oauth.js
+++ b/src/services/new_api/oauth.js
@@ -12,7 +12,7 @@ export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) =>
 
   form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`)
   form.append('redirect_uris', REDIRECT_URI)
-  form.append('scopes', 'read write follow')
+  form.append('scopes', 'read write follow push admin')
 
   return window.fetch(url, {
     method: 'POST',
@@ -28,7 +28,7 @@ const login = ({ instance, clientId }) => {
     response_type: 'code',
     client_id: clientId,
     redirect_uri: REDIRECT_URI,
-    scope: 'read write follow'
+    scope: 'read write follow push admin'
   }
 
   const dataString = reduce(data, (acc, v, k) => {

From 7fa294f11c9ea3912b134a16225b3fc0c2ad29cc Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sat, 30 Nov 2019 12:30:25 -0500
Subject: [PATCH 090/483] add icons to nav panel

---
 src/components/nav_panel/nav_panel.vue     | 14 +++++-----
 src/components/side_drawer/side_drawer.vue | 28 ++++++++++----------
 static/fontello.json                       | 30 ++++++++++++++++++++++
 3 files changed, 51 insertions(+), 21 deletions(-)

diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 28589bb1..d660f189 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -4,22 +4,22 @@
       <ul>
         <li v-if="currentUser">
           <router-link :to="{ name: 'friends' }">
-            {{ $t("nav.timeline") }}
+            <i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }}
           </router-link>
         </li>
         <li v-if="currentUser">
           <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
-            {{ $t("nav.interactions") }}
+            <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
           </router-link>
         </li>
         <li v-if="currentUser">
           <router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
-            {{ $t("nav.dms") }}
+            <i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }}
           </router-link>
         </li>
         <li v-if="currentUser && currentUser.locked">
           <router-link :to="{ name: 'friend-requests' }">
-            {{ $t("nav.friend_requests") }}
+            <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
             <span
               v-if="followRequestCount > 0"
               class="badge follow-request-count"
@@ -30,17 +30,17 @@
         </li>
         <li>
           <router-link :to="{ name: 'public-timeline' }">
-            {{ $t("nav.public_tl") }}
+            <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
           </router-link>
         </li>
         <li>
           <router-link :to="{ name: 'public-external-timeline' }">
-            {{ $t("nav.twkn") }}
+            <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
           </router-link>
         </li>
         <li>
           <router-link :to="{ name: 'about' }">
-            {{ $t("nav.about") }}
+            <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
           </router-link>
         </li>
       </ul>
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index eb429f29..1c65c173 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -36,7 +36,7 @@
           @click="toggleDrawer"
         >
           <router-link :to="{ name: 'login' }">
-            {{ $t("login.login") }}
+            <i class="button-icon icon-login" /> {{ $t("login.login") }}
           </router-link>
         </li>
         <li
@@ -44,7 +44,7 @@
           @click="toggleDrawer"
         >
           <router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
-            {{ $t("nav.dms") }}
+            <i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }}
           </router-link>
         </li>
         <li
@@ -52,7 +52,7 @@
           @click="toggleDrawer"
         >
           <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
-            {{ $t("nav.interactions") }}
+            <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
           </router-link>
         </li>
       </ul>
@@ -62,7 +62,7 @@
           @click="toggleDrawer"
         >
           <router-link :to="{ name: 'friends' }">
-            {{ $t("nav.timeline") }}
+            <i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }}
           </router-link>
         </li>
         <li
@@ -70,7 +70,7 @@
           @click="toggleDrawer"
         >
           <router-link to="/friend-requests">
-            {{ $t("nav.friend_requests") }}
+            <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
             <span
               v-if="followRequestCount > 0"
               class="badge follow-request-count"
@@ -81,12 +81,12 @@
         </li>
         <li @click="toggleDrawer">
           <router-link to="/main/public">
-            {{ $t("nav.public_tl") }}
+            <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
           </router-link>
         </li>
         <li @click="toggleDrawer">
           <router-link to="/main/all">
-            {{ $t("nav.twkn") }}
+            <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
           </router-link>
         </li>
         <li
@@ -94,14 +94,14 @@
           @click="toggleDrawer"
         >
           <router-link :to="{ name: 'chat' }">
-            {{ $t("nav.chat") }}
+            <i class="button-icon icon-chat" /> {{ $t("nav.chat") }}
           </router-link>
         </li>
       </ul>
       <ul>
         <li @click="toggleDrawer">
           <router-link :to="{ name: 'search' }">
-            {{ $t("nav.search") }}
+            <i class="button-icon icon-search" /> {{ $t("nav.search") }}
           </router-link>
         </li>
         <li
@@ -109,17 +109,17 @@
           @click="toggleDrawer"
         >
           <router-link :to="{ name: 'who-to-follow' }">
-            {{ $t("nav.who_to_follow") }}
+            <i class="button-icon icon-user-plus" /> {{ $t("nav.who_to_follow") }}
           </router-link>
         </li>
         <li @click="toggleDrawer">
           <router-link :to="{ name: 'settings' }">
-            {{ $t("settings.settings") }}
+            <i class="button-icon icon-cog" /> {{ $t("settings.settings") }}
           </router-link>
         </li>
         <li @click="toggleDrawer">
           <router-link :to="{ name: 'about'}">
-            {{ $t("nav.about") }}
+            <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
           </router-link>
         </li>
         <li
@@ -130,7 +130,7 @@
             href="/pleroma/admin/#/login-pleroma"
             target="_blank"
           >
-            {{ $t("nav.administration") }}
+            <i class="button-icon icon-gauge" /> {{ $t("nav.administration") }}
           </a>
         </li>
         <li
@@ -141,7 +141,7 @@
             href="#"
             @click="doLogout"
           >
-            {{ $t("login.logout") }}
+            <i class="button-icon icon-logout" /> {{ $t("login.logout") }}
           </a>
         </li>
       </ul>
diff --git a/static/fontello.json b/static/fontello.json
index c0cf1727..c1ed3393 100755
--- a/static/fontello.json
+++ b/static/fontello.json
@@ -303,6 +303,36 @@
       "css": "gauge",
       "code": 61668,
       "src": "fontawesome"
+    },
+    {
+      "uid": "31972e4e9d080eaa796290349ae6c1fd",
+      "css": "users",
+      "code": 59421,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
+      "css": "info-circled",
+      "code": 59423,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "w3nzesrlbezu6f30q7ytyq919p6gdlb6",
+      "css": "home-2",
+      "code": 59425,
+      "src": "typicons"
+    },
+    {
+      "uid": "dcedf50ab1ede3283d7a6c70e2fe32f3",
+      "css": "chat",
+      "code": 59422,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "3a00327e61b997b58518bd43ed83c3df",
+      "css": "login",
+      "code": 59424,
+      "src": "fontawesome"
     }
   ]
 }
\ No newline at end of file

From a412b53801bb9e9398c4c32aad7c430bf922c723 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Mon, 2 Dec 2019 11:45:55 -0500
Subject: [PATCH 091/483] increase icon width a little bit in the nav panel

---
 src/components/nav_panel/nav_panel.vue     | 4 ++++
 src/components/side_drawer/side_drawer.vue | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index d660f189..4b79b500 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -113,4 +113,8 @@
     }
   }
 }
+
+.nav-panel .button-icon:before {
+  width: 1.1em;
+}
 </style>
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 1c65c173..31724fa2 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -215,6 +215,10 @@
   box-shadow: var(--panelShadow);
   background-color: $fallback--bg;
   background-color: var(--bg, $fallback--bg);
+
+  .button-icon:before {
+    width: 1.1em;
+  }
 }
 
 .side-drawer-logo-wrapper {

From 585702b1cedf25547ff5faf0170263088e5484a6 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 12 Dec 2019 18:53:36 +0200
Subject: [PATCH 092/483] fix desktop notifications not working with streaming

---
 src/modules/users.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/modules/users.js b/src/modules/users.js
index cbec6063..5dc2f36f 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -481,6 +481,8 @@ const users = {
                 store.dispatch('enableMastoSockets').catch((error) => {
                   console.error('Failed initializing MastoAPI Streaming socket', error)
                   startPolling()
+                }).then(() => {
+                  setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000)
                 })
               } else {
                 startPolling()

From addacf36d1ee080158b7acba44b70d31e1edb3da Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Thu, 12 Dec 2019 21:19:13 +0300
Subject: [PATCH 093/483] mfa: removed unused code

---
 src/components/login_form/login_form.js | 2 +-
 src/components/mfa_form/totp_form.js    | 1 -
 src/modules/auth_flow.js                | 8 +-------
 3 files changed, 2 insertions(+), 9 deletions(-)

diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js
index 0b574a04..0d8f1da6 100644
--- a/src/components/login_form/login_form.js
+++ b/src/components/login_form/login_form.js
@@ -58,7 +58,7 @@ const LoginForm = {
         ).then((result) => {
           if (result.error) {
             if (result.error === 'mfa_required') {
-              this.requireMFA({ app: app, settings: result })
+              this.requireMFA({ settings: result })
             } else if (result.identifier === 'password_reset_required') {
               this.$router.push({ name: 'password-reset', params: { passwordResetRequested: true } })
             } else {
diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js
index 1ec7576b..b774f2d0 100644
--- a/src/components/mfa_form/totp_form.js
+++ b/src/components/mfa_form/totp_form.js
@@ -7,7 +7,6 @@ export default {
   }),
   computed: {
     ...mapGetters({
-      authApp: 'authFlow/app',
       authSettings: 'authFlow/settings'
     }),
     ...mapState({
diff --git a/src/modules/auth_flow.js b/src/modules/auth_flow.js
index d0a90feb..956d40e8 100644
--- a/src/modules/auth_flow.js
+++ b/src/modules/auth_flow.js
@@ -7,7 +7,6 @@ const RECOVERY_STRATEGY = 'recovery'
 
 // initial state
 const state = {
-  app: null,
   settings: {},
   strategy: PASSWORD_STRATEGY,
   initStrategy: PASSWORD_STRATEGY // default strategy from config
@@ -16,14 +15,10 @@ const state = {
 const resetState = (state) => {
   state.strategy = state.initStrategy
   state.settings = {}
-  state.app = null
 }
 
 // getters
 const getters = {
-  app: (state, getters) => {
-    return state.app
-  },
   settings: (state, getters) => {
     return state.settings
   },
@@ -55,9 +50,8 @@ const mutations = {
   requireToken (state) {
     state.strategy = TOKEN_STRATEGY
   },
-  requireMFA (state, { app, settings }) {
+  requireMFA (state, { settings }) {
     state.settings = settings
-    state.app = app
     state.strategy = TOTP_STRATEGY // default strategy of MFA
   },
   requireRecovery (state) {

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: [PATCH 094/483] Lint

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

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 = {

From 211d25cd5a4df92f9de249e053e00a7c788aacba Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 12 Dec 2019 13:39:18 -0600
Subject: [PATCH 095/483] More lint

---
 src/components/side_drawer/side_drawer.vue | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 1454e0d8..064472b9 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -79,12 +79,14 @@
             </span>
           </router-link>
         </li>
-        <li @click="toggleDrawer" v-if="currentUser || !privateMode">
+        <li @click="toggleDrawer"
+          v-if="currentUser || !privateMode">
           <router-link to="/main/public">
             <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
           </router-link>
         </li>
-        <li @click="toggleDrawer" v-if="(currentUser || !privateMode) && federating">
+        <li @click="toggleDrawer"
+          v-if="(currentUser || !privateMode) && federating">
           <router-link to="/main/all">
             <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
           </router-link>
@@ -99,7 +101,8 @@
         </li>
       </ul>
       <ul>
-        <li @click="toggleDrawer" v-if="currentUser || !privateMode">
+        <li @click="toggleDrawer"
+          v-if="currentUser || !privateMode">
           <router-link :to="{ name: 'search' }">
             <i class="button-icon icon-search" /> {{ $t("nav.search") }}
           </router-link>

From afe5b3a82d5b6e9b02fa6db9354060aa72edb712 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 12 Dec 2019 13:41:52 -0600
Subject: [PATCH 096/483] More lint

---
 src/components/side_drawer/side_drawer.vue | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 064472b9..bf61d38c 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -79,14 +79,14 @@
             </span>
           </router-link>
         </li>
-        <li @click="toggleDrawer"
-          v-if="currentUser || !privateMode">
+        <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 @click="toggleDrawer"
-          v-if="(currentUser || !privateMode) && federating">
+        <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 +101,8 @@
         </li>
       </ul>
       <ul>
-        <li @click="toggleDrawer"
-          v-if="currentUser || !privateMode">
+        <li v-if="currentUser || !privateMode"
+          @click="toggleDrawer">
           <router-link :to="{ name: 'search' }">
             <i class="button-icon icon-search" /> {{ $t("nav.search") }}
           </router-link>

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: [PATCH 097/483] Finally trust eslint

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

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>

From 2514dc183f604686d1648c0a69f6eba00d943ce7 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 12 Dec 2019 13:51:50 -0600
Subject: [PATCH 098/483] Logic should be to hide TWKN if not federating OR if
 instance is not public

Private instances should not show any timelines
---
 src/components/nav_panel/nav_panel.vue     | 2 +-
 src/components/side_drawer/side_drawer.vue | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 97c36711..034259d9 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -33,7 +33,7 @@
             <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
           </router-link>
         </li>
-        <li v-if="(currentUser || !privateMode) && federating">
+        <li v-if="federating && !privateMode">
           <router-link :to="{ name: 'public-external-timeline' }">
             <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
           </router-link>
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 49c25bba..3fba9058 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -88,7 +88,7 @@
           </router-link>
         </li>
         <li
-          v-if="(currentUser || !privateMode) && federating"
+          v-if="federating && !privateMode"
           @click="toggleDrawer"
         >
           <router-link to="/main/all">

From 0743fbb28b94dd4308f8e2ca01d9def91b6ffddf Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 12 Dec 2019 13:56:07 -0600
Subject: [PATCH 099/483] The value we are looking for is
 federationPolicy.enabled, not federationPolicy.federating

Also the || true fallback does not work and always becomes true
---
 src/components/nav_panel/nav_panel.js     | 2 +-
 src/components/side_drawer/side_drawer.js | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index f27727fc..7da9f8c6 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -11,7 +11,7 @@ const NavPanel = {
     chat: state => state.chat.channel,
     followRequestCount: state => state.api.followRequests.length,
     privateMode: state => state.instance.private,
-    federating: state => state.instance.federationPolicy.federating || true
+    federating: state => state.instance.federationPolicy.enabled
   })
 }
 
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 22cb1a55..e08f7cbe 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -46,7 +46,7 @@ const SideDrawer = {
       return this.$store.state.instance.private
     },
     federating () {
-      return this.$store.state.instance.federationPolicy.federating || true
+      return this.$store.state.instance.federationPolicy.enabled
     }
   },
   methods: {

From 8d14036a23adbd13b098abeeef9622471f4a64d3 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 12 Dec 2019 15:17:23 -0600
Subject: [PATCH 100/483] Add fallback in case BE does not report federating
 status in nodeinfo

---
 src/components/nav_panel/nav_panel.js     | 2 +-
 src/components/side_drawer/side_drawer.js | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index 7da9f8c6..515e47e6 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -11,7 +11,7 @@ const NavPanel = {
     chat: state => state.chat.channel,
     followRequestCount: state => state.api.followRequests.length,
     privateMode: state => state.instance.private,
-    federating: state => state.instance.federationPolicy.enabled
+    federating: state => state.instance.federationPolicy.enabled || state.instance.federationPolicy.enabled !== false
   })
 }
 
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index e08f7cbe..670d28b4 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -46,7 +46,7 @@ const SideDrawer = {
       return this.$store.state.instance.private
     },
     federating () {
-      return this.$store.state.instance.federationPolicy.enabled
+      return this.$store.state.instance.federationPolicy.enabled || this.$store.state.instance.federationPolicy.enabled !== false
     }
   },
   methods: {

From d899d06973c7c46e77f9e47f480d6967e83b4adf Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 12 Dec 2019 15:29:50 -0600
Subject: [PATCH 101/483] Use a centralized fallback for missing values and use
 instance.federating instead of instance.federation.enabled

---
 src/boot/after_store.js                   | 6 ++++++
 src/components/nav_panel/nav_panel.js     | 2 +-
 src/components/side_drawer/side_drawer.js | 2 +-
 3 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index f169d7ba..228a0497 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -228,6 +228,12 @@ const getNodeInfo = async ({ store }) => {
 
       const federation = metadata.federation
       store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
+      store.dispatch('setInstanceOption', {
+        name: 'federating',
+        value: typeof federation.enabled === 'undefined'
+          ? true
+          : federation.enabled
+      })
 
       const accounts = metadata.staffAccounts
       await resolveStaffAccounts({ store, accounts })
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index 515e47e6..d9268585 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -11,7 +11,7 @@ const NavPanel = {
     chat: state => state.chat.channel,
     followRequestCount: state => state.api.followRequests.length,
     privateMode: state => state.instance.private,
-    federating: state => state.instance.federationPolicy.enabled || state.instance.federationPolicy.enabled !== false
+    federating: state => state.instance.federating
   })
 }
 
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 670d28b4..2534eb8f 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -46,7 +46,7 @@ const SideDrawer = {
       return this.$store.state.instance.private
     },
     federating () {
-      return this.$store.state.instance.federationPolicy.enabled || this.$store.state.instance.federationPolicy.enabled !== false
+      return this.$store.state.instance.federating
     }
   },
   methods: {

From 1cba6c56f07a0ed3e87ac4b8d3311ecbbbe5b634 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo <hakabahitoyo@yahoo.co.jp>
Date: Fri, 13 Dec 2019 17:05:13 +0000
Subject: [PATCH 102/483] i18n/update-ja_easy

---
 src/i18n/ja_easy.json | 43 +++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 41 insertions(+), 2 deletions(-)

diff --git a/src/i18n/ja_easy.json b/src/i18n/ja_easy.json
index 592a7257..be447f1c 100644
--- a/src/i18n/ja_easy.json
+++ b/src/i18n/ja_easy.json
@@ -1,4 +1,23 @@
 {
+  "about": {
+    "staff": "スタッフ",
+    "federation": "フェデレーション",
+    "mrf_policies": "ゆうこうなMRFポリシー",
+    "mrf_policies_desc": "MRFポリシーは、このインスタンスのフェデレーションのふるまいを、いじります。これらのMRFポリシーがゆうこうになっています:",
+    "mrf_policy_simple": "インスタンスのポリシー",
+    "mrf_policy_simple_accept": "うけいれ",
+    "mrf_policy_simple_accept_desc": "このインスンスは、これらのインスタンスからのメッセージのみをうけいれます:",
+    "mrf_policy_simple_reject": "おことわり",
+    "mrf_policy_simple_reject_desc": "このインスタンスは、これらのインスタンスからのメッセージをうけいれません:",
+    "mrf_policy_simple_quarantine": "けんえき",
+    "mrf_policy_simple_quarantine_desc": "このインスタンスは、これらのインスタンスに、パブリックなとうこうのみを、おくります:",
+    "mrf_policy_simple_ftl_removal": "「つながっているすべてのネットワーク」タイムラインからのぞく",
+    "mrf_policy_simple_ftl_removal_desc": "このインスタンスは、つながっているすべてのネットワーク」タイムラインから、これらのインスタンスを、とりのぞきます:",
+    "mrf_policy_simple_media_removal": "メディアをのぞく",
+    "mrf_policy_simple_media_removal_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、とりのぞきます:",
+    "mrf_policy_simple_media_nsfw": "メディアをすべてセンシティブにする",
+    "mrf_policy_simple_media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:"
+  },
   "chat": {
     "title": "チャット"
   },
@@ -68,6 +87,7 @@
   },
   "nav": {
     "about": "これはなに?",
+    "administration": "アドミニストレーション",
     "back": "もどる",
     "chat": "ローカルチャット",
     "friend_requests": "フォローリクエスト",
@@ -113,7 +133,9 @@
     "search_emoji": "えもじをさがす",
     "add_emoji": "えもじをうちこむ",
     "custom": "カスタムえもじ",
-    "unicode": "ユニコードえもじ"
+    "unicode": "ユニコードえもじ",
+    "load_all_hint": "はじめの {saneAmount} このえもじだけがロードされています。すべてのえもじをロードすると、パフォーマンスがわるくなるかもしれません。",
+    "load_all": "すべてのえもじをロード ({emojiAmount} こあります)"
   },
   "stickers": {
     "add_sticker": "ステッカーをふやす"
@@ -173,6 +195,11 @@
       "password_confirmation_match": "パスワードがちがいます"
     }
   },
+  "remote_user_resolver": {
+    "remote_user_resolver": "リモートユーザーリゾルバー",
+    "searching_for": "さがしています:",
+    "error": "みつかりませんでした。"
+  },
   "selectable_list": {
     "select_all": "すべてえらぶ"
   },
@@ -220,6 +247,9 @@
     "cGreen": "リピート",
     "cOrange": "おきにいり",
     "cRed": "キャンセル",
+    "change_email": "メールアドレスをかえる",
+    "change_email_error": "メールアドレスをかえようとしましたが、なにかがおかしいです。",
+    "changed_email": "メールアドレスをかえることができました!",
     "change_password": "パスワードをかえる",
     "change_password_error": "パスワードをかえることが、できなかったかもしれません。",
     "changed_password": "パスワードが、かわりました!",
@@ -279,6 +309,7 @@
     "use_contain_fit": "がぞうのサムネイルを、きりぬかない",
     "name": "なまえ",
     "name_bio": "なまえとプロフィール",
+    "new_email": "あたらしいメールアドレス",
     "new_password": "あたらしいパスワード",
     "notification_visibility": "ひょうじするつうち",
     "notification_visibility_follows": "フォロー",
@@ -344,6 +375,8 @@
       "false": "いいえ",
       "true": "はい"
     },
+    "fun": "おたのしみ",
+    "greentext": "ミームやじるし",
     "notifications": "つうち",
     "notification_setting": "つうちをうけとる:",
     "notification_setting_follows": "あなたがフォローしているひとから",
@@ -391,6 +424,7 @@
         "_tab_label": "くわしく",
         "alert": "アラートのバックグラウンド",
         "alert_error": "エラー",
+        "alert_warning": "けいこく",
         "badge": "バッジのバックグラウンド",
         "badge_notification": "つうち",
         "panel_header": "パネルヘッダー",
@@ -542,6 +576,7 @@
     "followers": "フォロワー",
     "following": "フォローしています!",
     "follows_you": "フォローされました!",
+    "hidden": "かくされています",
     "its_you": "これはあなたです!",
     "media": "メディア",
     "mention": "メンション",
@@ -559,6 +594,8 @@
     "unmute": "ミュートをやめる",
     "unmute_progress": "ミュートをとりけしています...",
     "mute_progress": "ミュートしています...",
+    "hide_repeats": "リピートをかくす",
+    "show_repeats": "リピートをみる",
     "admin_menu": {
       "moderation": "モデレーション",
       "grant_admin": "アドミンにする",
@@ -634,6 +671,8 @@
     "return_home": "ホームページにもどる",
     "not_found": "そのメールアドレスまたはユーザーめいを、みつけることができませんでした。",
     "too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。",
-    "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。"
+    "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。",
+    "password_reset_required": "ログインするには、パスワードをリセットしてください。",
+    "password_reset_required_but_mailer_is_disabled": "あなたはパスワードのリセットがひつようです。しかし、まずいことに、このインスタンスでは、パスワードのリセットができなくなっています。このインスタンスのアドミニストレーターに、おといあわせください。"
   }
 }

From 32d7a49b9d02bfa6c6e997f4d6e923752b062a90 Mon Sep 17 00:00:00 2001
From: Absturztaube <me@absturztaube.ch>
Date: Sun, 15 Dec 2019 17:58:37 +0100
Subject: [PATCH 103/483] use flex for stickers

---
 src/components/sticker_picker/sticker_picker.vue | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue
index 7d6fdc24..3863908a 100644
--- a/src/components/sticker_picker/sticker_picker.vue
+++ b/src/components/sticker_picker/sticker_picker.vue
@@ -36,16 +36,20 @@
 
 .sticker-picker {
   width: 100%;
-  position: relative;
   .contents {
     min-height: 250px;
     .sticker-picker-content {
+      display: flex;
+      flex-wrap: wrap;
+      padding: 0 4px;
       .sticker {
-        display: inline-block;
-        width: 20%;
-        height: 20%;
+        display: flex;
+        flex: 1 1 auto;
+        margin: 4px;
+        width: 56px;
+        height: 56px;
         img {
-          width: 100%;
+          height: 100%;
           &:hover {
             filter: drop-shadow(0 0 5px var(--link, $fallback--link));
           }

From 54f692622ad4fe6c427ae6d67816be93a7644ac8 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sun, 15 Dec 2019 14:29:45 -0500
Subject: [PATCH 104/483] wire up staff accounts with correct store data

---
 src/boot/after_store.js                   | 11 ++++-------
 src/components/staff_panel/staff_panel.js |  3 ++-
 2 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 228a0497..0bb1b2b4 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -185,12 +185,9 @@ const getAppSecret = async ({ store }) => {
     })
 }
 
-const resolveStaffAccounts = async ({ store, accounts }) => {
-  const backendInteractor = store.state.api.backendInteractor
-  let nicknames = accounts.map(uri => uri.split('/').pop())
-    .map(id => backendInteractor.fetchUser({ id }))
-  nicknames = await Promise.all(nicknames)
-
+const resolveStaffAccounts = ({ store, accounts }) => {
+  const nicknames = accounts.map(uri => uri.split('/').pop())
+  nicknames.map(nickname => store.dispatch('fetchUser', nickname))
   store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
 }
 
@@ -236,7 +233,7 @@ const getNodeInfo = async ({ store }) => {
       })
 
       const accounts = metadata.staffAccounts
-      await resolveStaffAccounts({ store, accounts })
+      resolveStaffAccounts({ store, accounts })
     } else {
       throw (res)
     }
diff --git a/src/components/staff_panel/staff_panel.js b/src/components/staff_panel/staff_panel.js
index 93e950ad..4f98fff6 100644
--- a/src/components/staff_panel/staff_panel.js
+++ b/src/components/staff_panel/staff_panel.js
@@ -1,3 +1,4 @@
+import map from 'lodash/map'
 import BasicUserCard from '../basic_user_card/basic_user_card.vue'
 
 const StaffPanel = {
@@ -6,7 +7,7 @@ const StaffPanel = {
   },
   computed: {
     staffAccounts () {
-      return this.$store.state.instance.staffAccounts
+      return map(this.$store.state.instance.staffAccounts, nickname => this.$store.getters.findUser(nickname)).filter(_ => _)
     }
   }
 }

From 506822bed01deb2d2fb98f511902d6801819cbd8 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Mon, 18 Nov 2019 20:29:12 -0500
Subject: [PATCH 105/483] replace setActivationStatus api with new one

---
 .../moderation_tools/moderation_tools.js      |  2 +-
 src/services/api/api.service.js               | 19 +++++++------------
 .../backend_interactor_service.js             |  6 +++---
 3 files changed, 11 insertions(+), 16 deletions(-)

diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
index 8aadc8c5..10a20709 100644
--- a/src/components/moderation_tools/moderation_tools.js
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -73,7 +73,7 @@ const ModerationTools = {
     toggleActivationStatus () {
       const store = this.$store
       const status = !!this.user.deactivated
-      store.state.api.backendInteractor.setActivationStatus(this.user, status).then(response => {
+      store.state.api.backendInteractor.toggleActivationStatus(this.user).then(response => {
         if (!response.ok) { return }
         store.commit('updateActivationStatus', { user: this.user, status: status })
       })
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 7eb0547e..dbc8320e 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -12,7 +12,7 @@ const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
 const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
 const TAG_USER_URL = '/api/pleroma/admin/users/tag'
 const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
-const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenName}/activation_status`
+const TOGGLE_ACTIVATION_URL = screenName => `/api/pleroma/admin/users/${screenName}/toggle_activation`
 const ADMIN_USERS_URL = '/api/pleroma/admin/users'
 const SUGGESTIONS_URL = '/api/v1/suggestions'
 const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
@@ -450,19 +450,14 @@ const deleteRight = ({ right, credentials, ...user }) => {
   })
 }
 
-const setActivationStatus = ({ status, credentials, ...user }) => {
-  const screenName = user.screen_name
-  const body = {
-    status: status
-  }
-
+// eslint-disable-next-line camelcase
+const toggleActivationStatus = ({ credentials, screen_name }) => {
   const headers = authHeaders(credentials)
   headers['Content-Type'] = 'application/json'
 
-  return fetch(ACTIVATION_STATUS_URL(screenName), {
-    method: 'PUT',
-    headers: headers,
-    body: JSON.stringify(body)
+  return fetch(TOGGLE_ACTIVATION_URL(screen_name), {
+    method: 'PATCH',
+    headers: headers
   })
 }
 
@@ -979,7 +974,7 @@ const apiService = {
   deleteUser,
   addRight,
   deleteRight,
-  setActivationStatus,
+  toggleActivationStatus,
   register,
   getCaptcha,
   updateAvatar,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index c16bd1f1..e0a15d3b 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -89,8 +89,8 @@ const backendInteractorService = credentials => {
   }
 
   // eslint-disable-next-line camelcase
-  const setActivationStatus = ({ screen_name }, status) => {
-    return apiService.setActivationStatus({ screen_name, status, credentials })
+  const toggleActivationStatus = ({ screen_name }) => {
+    return apiService.toggleActivationStatus({ screen_name, credentials })
   }
 
   // eslint-disable-next-line camelcase
@@ -191,7 +191,7 @@ const backendInteractorService = credentials => {
     addRight,
     deleteRight,
     deleteUser,
-    setActivationStatus,
+    toggleActivationStatus,
     register,
     getCaptcha,
     updateAvatar,

From 45e7f93c49042c365badf0043ebd0480ecbb9c49 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Mon, 18 Nov 2019 20:33:07 -0500
Subject: [PATCH 106/483] refactor toggleActivationStatus

---
 src/services/api/api.service.js | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index dbc8320e..b739ec1f 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -452,13 +452,7 @@ const deleteRight = ({ right, credentials, ...user }) => {
 
 // eslint-disable-next-line camelcase
 const toggleActivationStatus = ({ credentials, screen_name }) => {
-  const headers = authHeaders(credentials)
-  headers['Content-Type'] = 'application/json'
-
-  return fetch(TOGGLE_ACTIVATION_URL(screen_name), {
-    method: 'PATCH',
-    headers: headers
-  })
+  return promisedRequest({ url: TOGGLE_ACTIVATION_URL(screen_name), method: 'PATCH', credentials })
 }
 
 const deleteUser = ({ credentials, ...user }) => {

From 36376ce57c93c81317b6a8b0b50699d6b8488f57 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Mon, 18 Nov 2019 20:42:10 -0500
Subject: [PATCH 107/483] use vuex action

---
 src/components/moderation_tools/moderation_tools.js |  7 +------
 src/modules/users.js                                | 10 ++++++++--
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
index 10a20709..02b92fef 100644
--- a/src/components/moderation_tools/moderation_tools.js
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -71,12 +71,7 @@ const ModerationTools = {
       }
     },
     toggleActivationStatus () {
-      const store = this.$store
-      const status = !!this.user.deactivated
-      store.state.api.backendInteractor.toggleActivationStatus(this.user).then(response => {
-        if (!response.ok) { return }
-        store.commit('updateActivationStatus', { user: this.user, status: status })
-      })
+      this.$store.dispatch('toggleActivationStatus', this.user)
     },
     deleteUserDialog (show) {
       this.showDeleteUserDialog = show
diff --git a/src/modules/users.js b/src/modules/users.js
index 14b2d8b5..ec2ef608 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -95,9 +95,9 @@ export const mutations = {
     newRights[right] = value
     set(user, 'rights', newRights)
   },
-  updateActivationStatus (state, { user: { id }, status }) {
+  updateActivationStatus (state, { user: { id }, deactivated }) {
     const user = state.usersObject[id]
-    set(user, 'deactivated', !status)
+    set(user, 'deactivated', deactivated)
   },
   setCurrentUser (state, user) {
     state.lastLoginName = user.screen_name
@@ -331,6 +331,12 @@ const users = {
       return rootState.api.backendInteractor.unsubscribeUser(id)
         .then((relationship) => commit('updateUserRelationship', [relationship]))
     },
+    toggleActivationStatus ({ rootState, commit }, user) {
+      rootState.api.backendInteractor.toggleActivationStatus(user)
+        .then(response => {
+          commit('updateActivationStatus', { user, deactivated: response.deactivated })
+        })
+    },
     registerPushNotifications (store) {
       const token = store.state.currentUser.credentials
       const vapidPublicKey = store.rootState.instance.vapidPublicKey

From 4e4c4af422c400d016e4605f8bcf699f7154a8b4 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 19 Nov 2019 14:41:39 -0500
Subject: [PATCH 108/483] toggle_activation api is also deprecated

---
 src/modules/users.js                          |  7 ++---
 src/services/api/api.service.js               | 31 +++++++++++++++----
 .../backend_interactor_service.js             | 12 +++++--
 3 files changed, 37 insertions(+), 13 deletions(-)

diff --git a/src/modules/users.js b/src/modules/users.js
index ec2ef608..82d3c4e8 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -332,10 +332,9 @@ const users = {
         .then((relationship) => commit('updateUserRelationship', [relationship]))
     },
     toggleActivationStatus ({ rootState, commit }, user) {
-      rootState.api.backendInteractor.toggleActivationStatus(user)
-        .then(response => {
-          commit('updateActivationStatus', { user, deactivated: response.deactivated })
-        })
+      const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser
+      api(user)
+        .then(({ deactivated }) => commit('updateActivationStatus', { user, deactivated }))
     },
     registerPushNotifications (store) {
       const token = store.state.currentUser.credentials
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index b739ec1f..a69fa53c 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1,4 +1,4 @@
-import { each, map, concat, last } from 'lodash'
+import { each, map, concat, last, get } from 'lodash'
 import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
 import 'whatwg-fetch'
 import { RegistrationError, StatusCodeError } from '../errors/errors'
@@ -12,7 +12,8 @@ const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
 const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
 const TAG_USER_URL = '/api/pleroma/admin/users/tag'
 const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
-const TOGGLE_ACTIVATION_URL = screenName => `/api/pleroma/admin/users/${screenName}/toggle_activation`
+const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
+const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
 const ADMIN_USERS_URL = '/api/pleroma/admin/users'
 const SUGGESTIONS_URL = '/api/v1/suggestions'
 const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
@@ -450,9 +451,26 @@ const deleteRight = ({ right, credentials, ...user }) => {
   })
 }
 
-// eslint-disable-next-line camelcase
-const toggleActivationStatus = ({ credentials, screen_name }) => {
-  return promisedRequest({ url: TOGGLE_ACTIVATION_URL(screen_name), method: 'PATCH', credentials })
+const activateUser = ({ credentials, screen_name: nickname }) => {
+  return promisedRequest({
+    url: ACTIVATE_USER_URL,
+    method: 'PATCH',
+    credentials,
+    payload: {
+      nicknames: [nickname]
+    }
+  }).then(response => get(response, 'users.0'))
+}
+
+const deactivateUser = ({ credentials, screen_name: nickname }) => {
+  return promisedRequest({
+    url: DEACTIVATE_USER_URL,
+    method: 'PATCH',
+    credentials,
+    payload: {
+      nicknames: [nickname]
+    }
+  }).then(response => get(response, 'users.0'))
 }
 
 const deleteUser = ({ credentials, ...user }) => {
@@ -968,7 +986,8 @@ const apiService = {
   deleteUser,
   addRight,
   deleteRight,
-  toggleActivationStatus,
+  activateUser,
+  deactivateUser,
   register,
   getCaptcha,
   updateAvatar,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index e0a15d3b..3d4ec6ac 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -89,8 +89,13 @@ const backendInteractorService = credentials => {
   }
 
   // eslint-disable-next-line camelcase
-  const toggleActivationStatus = ({ screen_name }) => {
-    return apiService.toggleActivationStatus({ screen_name, credentials })
+  const activateUser = ({ screen_name }) => {
+    return apiService.activateUser({ screen_name, credentials })
+  }
+
+  // eslint-disable-next-line camelcase
+  const deactivateUser = ({ screen_name }) => {
+    return apiService.deactivateUser({ screen_name, credentials })
   }
 
   // eslint-disable-next-line camelcase
@@ -191,7 +196,8 @@ const backendInteractorService = credentials => {
     addRight,
     deleteRight,
     deleteUser,
-    toggleActivationStatus,
+    activateUser,
+    deactivateUser,
     register,
     getCaptcha,
     updateAvatar,

From 43197c424366099301e59d3d1c7be58b987cb833 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 26 Dec 2019 14:12:35 +0200
Subject: [PATCH 109/483] Some error handling

---
 src/components/settings/settings.js |  4 ++
 src/modules/api.js                  | 87 ++++++++++++++++-------------
 2 files changed, 51 insertions(+), 40 deletions(-)

diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index 2d7723cc..31a9e9be 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -103,6 +103,10 @@ const settings = {
 
         promise.then(() => {
           this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
+        }).catch((e) => {
+          console.error('Failed starting MastoAPI Streaming socket', e)
+          this.$store.dispatch('disableMastoSockets')
+          this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
         })
       }
     }
diff --git a/src/modules/api.js b/src/modules/api.js
index dc91d00e..b6dd7fcf 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -35,60 +35,67 @@ const api = {
     enableMastoSockets (store) {
       const { state, dispatch } = store
       if (state.mastoUserSocket) return
-      dispatch('startMastoUserSocket')
+      return dispatch('startMastoUserSocket')
     },
     disableMastoSockets (store) {
       const { state, dispatch } = store
       if (!state.mastoUserSocket) return
-      dispatch('stopMastoUserSocket')
+      return dispatch('stopMastoUserSocket')
     },
 
     // MastoAPI 'User' sockets
     startMastoUserSocket (store) {
-      const { state, dispatch } = store
-      state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
-      state.mastoUserSocket.addEventListener(
-        'message',
-        ({ detail: message }) => {
-          if (!message) return // pings
-          if (message.event === 'notification') {
-            dispatch('addNewNotifications', {
-              notifications: [message.notification],
-              older: false
-            })
-          } else if (message.event === 'update') {
-            dispatch('addNewStatuses', {
-              statuses: [message.status],
-              userId: false,
-              showImmediately: false,
-              timeline: 'friends'
-            })
-          }
-        }
-      )
-      state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
-        console.error('Error in MastoAPI websocket:', error)
-      })
-      state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
-        const ignoreCodes = new Set([
-          1000, // Normal (intended) closure
-          1001 // Going away
-        ])
-        const { code } = closeEvent
-        if (ignoreCodes.has(code)) {
-          console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
-        } else {
-          console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
-          dispatch('startFetchingTimeline', { timeline: 'friends' })
-          dispatch('startFetchingNotifications')
-          dispatch('restartMastoUserSocket')
+      return new Promise((resolve, reject) => {
+        try {
+          const { state, dispatch } = store
+          state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
+          state.mastoUserSocket.addEventListener(
+            'message',
+            ({ detail: message }) => {
+              if (!message) return // pings
+              if (message.event === 'notification') {
+                dispatch('addNewNotifications', {
+                  notifications: [message.notification],
+                  older: false
+                })
+              } else if (message.event === 'update') {
+                dispatch('addNewStatuses', {
+                  statuses: [message.status],
+                  userId: false,
+                  showImmediately: false,
+                  timeline: 'friends'
+                })
+              }
+            }
+          )
+          state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
+            console.error('Error in MastoAPI websocket:', error)
+          })
+          state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
+            const ignoreCodes = new Set([
+              1000, // Normal (intended) closure
+              1001 // Going away
+            ])
+            const { code } = closeEvent
+            if (ignoreCodes.has(code)) {
+              console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
+            } else {
+              console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
+              dispatch('startFetchingTimeline', { timeline: 'friends' })
+              dispatch('startFetchingNotifications')
+              dispatch('restartMastoUserSocket')
+            }
+          })
+          resolve()
+        } catch (e) {
+          reject(e)
         }
       })
     },
     restartMastoUserSocket ({ dispatch }) {
       // This basically starts MastoAPI user socket and stops conventional
       // fetchers when connection reestablished
-      dispatch('startMastoUserSocket').then(() => {
+      return dispatch('startMastoUserSocket').then(() => {
         dispatch('stopFetchingTimeline', { timeline: 'friends' })
         dispatch('stopFetchingNotifications')
       })

From bd07e5de1c03a5619db5af49f14dc20602134881 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 26 Dec 2019 14:35:46 +0200
Subject: [PATCH 110/483] unify showimmideately

---
 src/modules/api.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/modules/api.js b/src/modules/api.js
index b6dd7fcf..9c296275 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -47,7 +47,8 @@ const api = {
     startMastoUserSocket (store) {
       return new Promise((resolve, reject) => {
         try {
-          const { state, dispatch } = store
+          const { state, dispatch, rootState } = store
+          const timelineData = rootState.statuses.timelines.friends
           state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
           state.mastoUserSocket.addEventListener(
             'message',
@@ -62,7 +63,7 @@ const api = {
                 dispatch('addNewStatuses', {
                   statuses: [message.status],
                   userId: false,
-                  showImmediately: false,
+                  showImmediately: timelineData.visibleStatuses.length === 0,
                   timeline: 'friends'
                 })
               }

From e5a34870f0f7154712783fb6d9c20edf4c06ad35 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sat, 28 Dec 2019 15:55:42 +0200
Subject: [PATCH 111/483] Accent works

---
 src/App.scss                                  |  2 +-
 src/components/color_input/color_input.vue    | 42 +++++++++++++++++--
 .../sticker_picker/sticker_picker.vue         |  2 +-
 .../style_switcher/style_switcher.js          |  5 ++-
 .../style_switcher/style_switcher.vue         | 11 ++++-
 src/i18n/en.json                              |  1 +
 src/services/style_setter/style_setter.js     | 13 +++++-
 static/themes/breezy-dark.json                |  2 +-
 static/themes/breezy-light.json               |  2 +-
 9 files changed, 68 insertions(+), 12 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 754ca62e..3b03a761 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -198,7 +198,7 @@ input, textarea, .select {
     &:checked + label::before {
       box-shadow: 0px 0px 2px black inset, 0px 0px 0px 4px $fallback--fg inset;
       box-shadow: var(--inputShadow), 0px 0px 0px 4px var(--fg, $fallback--fg) inset;
-      background-color: var(--link, $fallback--link);
+      background-color: var(--accent, $fallback--link);
     }
     &:disabled {
       &,
diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue
index 9db62e81..fa26d079 100644
--- a/src/components/color_input/color_input.vue
+++ b/src/components/color_input/color_input.vue
@@ -18,7 +18,7 @@
       @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
     >
     <label
-      v-if="typeof fallback !== 'undefined'"
+      v-if="typeof fallback !== 'undefined' && showOptionalTickbox"
       class="opt-l"
       :for="name + '-o'"
     />
@@ -43,9 +43,43 @@
 
 <script>
 export default {
-  props: [
-    'name', 'label', 'value', 'fallback', 'disabled'
-  ],
+  props: {
+    // Name of color, used for identifying
+    name: {
+      required: true,
+      type: String
+    },
+    // Readable label
+    label: {
+      required: true,
+      type: String
+    },
+    // Color value, should be required but vue cannot tell the difference
+    // between "property missing" and "property set to undefined"
+    value: {
+      required: false,
+      type: String,
+      default: undefined
+    },
+    // Color fallback to use when value is not defeind
+    fallback: {
+      required: false,
+      type: String,
+      default: undefined
+    },
+    // Disable the control
+    disabled: {
+      required: false,
+      type: Boolean,
+      default: false
+    },
+    // Show "optional" tickbox, for when value might become mandatory
+    showOptionalTickbox: {
+      required: false,
+      type: Boolean,
+      default: true
+    }
+  },
   computed: {
     present () {
       return typeof this.value !== 'undefined'
diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue
index 3863908a..dc449ccb 100644
--- a/src/components/sticker_picker/sticker_picker.vue
+++ b/src/components/sticker_picker/sticker_picker.vue
@@ -51,7 +51,7 @@
         img {
           height: 100%;
           &:hover {
-            filter: drop-shadow(0 0 5px var(--link, $fallback--link));
+            filter: drop-shadow(0 0 5px var(--accent, $fallback--link));
           }
         }
       }
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index ebde4475..1e512407 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -46,7 +46,8 @@ export default {
       keepFonts: false,
 
       textColorLocal: '',
-      linkColorLocal: '',
+      accentColorLocal: undefined,
+      linkColorLocal: undefined,
 
       bgColorLocal: '',
       bgOpacityLocal: undefined,
@@ -132,6 +133,8 @@ export default {
         fgText: this.fgTextColorLocal,
         fgLink: this.fgLinkColorLocal,
 
+        accent: this.accentColorLocal,
+
         panel: this.panelColorLocal,
         panelText: this.panelTextColorLocal,
         panelLink: this.panelLinkColorLocal,
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index ad032041..8bbdc7b7 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -114,9 +114,18 @@
               :label="$t('settings.text')"
             />
             <ContrastRatio :contrast="previewContrast.bgText" />
+            <ColorInput
+              v-model="accentColorLocal"
+              name="accentColor"
+              :fallback="previewTheme.colors.link"
+              :showOptionalTickbox="typeof linkColorLocal !== 'undefined'"
+              :label="$t('settings.accent')"
+            />
             <ColorInput
               v-model="linkColorLocal"
               name="linkColor"
+              :fallback="previewTheme.colors.accent"
+              :showOptionalTickbox="typeof accentColorLocal !== 'undefined'"
               :label="$t('settings.links')"
             />
             <ContrastRatio :contrast="previewContrast.bgLink" />
@@ -336,7 +345,7 @@
             <ColorInput
               v-model="faintColorLocal"
               name="faintColor"
-              :fallback="previewTheme.colors.faint || 1"
+              :fallback="previewTheme.colors.faint"
               :label="$t('settings.text')"
             />
             <ColorInput
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 60fc792f..323813f6 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -272,6 +272,7 @@
     "follow_import": "Follow import",
     "follow_import_error": "Error importing followers",
     "follows_imported": "Follows imported! Processing them will take a while.",
+    "accent": "Accent",
     "foreground": "Foreground",
     "general": "General",
     "hide_attachments_in_convo": "Hide attachments in conversations",
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index eaa495c4..1e4bc59e 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -160,7 +160,13 @@ const generateColors = (input) => {
     }
     return acc
   }, {}))
-  const col = Object.entries(input.colors || input).reduce((acc, [k, v]) => {
+
+  const inputColors = input.colors || input
+
+  const compat = input.v3compat || {}
+  const compatColors = compat.colors || {}
+
+  const col = Object.entries({ ...inputColors, ...compatColors }).reduce((acc, [k, v]) => {
     if (typeof v === 'object') {
       acc[k] = v
     } else {
@@ -174,7 +180,10 @@ const generateColors = (input) => {
 
   colors.text = col.text
   colors.lightText = brightness(20 * mod, colors.text).rgb
-  colors.link = col.link
+
+  colors.accent = col.accent || col.link
+  colors.link = col.link || col.accent
+
   colors.faint = col.faint || Object.assign({}, col.text)
 
   colors.bg = col.bg
diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index 6119bf88..7ce41384 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -50,7 +50,7 @@
           "y": "0",
           "blur": 0,
           "spread": "1",
-          "color": "--link",
+          "color": "--accent",
           "alpha": "0.3",
           "inset": true
         },
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index becf704f..dc43f90e 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -50,7 +50,7 @@
           "y": "0",
           "blur": 0,
           "spread": "1",
-          "color": "--link",
+          "color": "--accent",
           "alpha": "0.3",
           "inset": true
         },

From 1a78461443e4f85461283c1921694a5fb28ea39f Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sat, 28 Dec 2019 17:02:34 +0200
Subject: [PATCH 112/483] fixed import-export

---
 .../export_import/export_import.vue           |  2 +-
 .../style_switcher/style_switcher.js          | 34 +++++++++++++++----
 .../style_switcher/style_switcher.vue         |  4 +--
 src/services/style_setter/style_setter.js     | 27 +++++++++++++--
 4 files changed, 56 insertions(+), 11 deletions(-)

diff --git a/src/components/export_import/export_import.vue b/src/components/export_import/export_import.vue
index 20c6f569..ae00487f 100644
--- a/src/components/export_import/export_import.vue
+++ b/src/components/export_import/export_import.vue
@@ -42,7 +42,7 @@ export default {
   },
   methods: {
     exportData () {
-      const stringified = JSON.stringify(this.exportObject) // Pretty-print and indent with 2 spaces
+      const stringified = JSON.stringify(this.exportObject, null, 2) // Pretty-print and indent with 2 spaces
 
       // Create an invisible link with a data url and simulate a click
       const e = document.createElement('a')
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 1e512407..b450dc8e 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -1,6 +1,7 @@
 import { rgb2hex, hex2rgb, getContrastRatio, alphaBlend } from '../../services/color_convert/color_convert.js'
 import { set, delete as del } from 'vue'
-import { generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } from '../../services/style_setter/style_setter.js'
+import { merge } from 'lodash'
+import { generateCompat, generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } from '../../services/style_setter/style_setter.js'
 import ColorInput from '../color_input/color_input.vue'
 import RangeInput from '../range_input/range_input.vue'
 import OpacityInput from '../opacity_input/opacity_input.vue'
@@ -123,6 +124,15 @@ export default {
     selectedVersion () {
       return Array.isArray(this.selected) ? 1 : 2
     },
+    currentCompat () {
+      return generateCompat({
+        shadows: this.shadowsLocal,
+        fonts: this.fontsLocal,
+        opacity: this.currentOpacity,
+        colors: this.currentColors,
+        radii: this.currentRadii
+      })
+    },
     currentColors () {
       return {
         bg: this.bgColorLocal,
@@ -332,7 +342,7 @@ export default {
 
       return {
         // To separate from other random JSON files and possible future theme formats
-        _pleroma_theme_version: 2, theme
+        _pleroma_theme_version: 2, theme: merge(theme, this.currentCompat)
       }
     }
   },
@@ -364,7 +374,7 @@ export default {
     onImport (parsed) {
       if (parsed._pleroma_theme_version === 1) {
         this.normalizeLocalState(parsed, 1)
-      } else if (parsed._pleroma_theme_version === 2) {
+      } else if (parsed._pleroma_theme_version >= 2) {
         this.normalizeLocalState(parsed.theme, 2)
       }
     },
@@ -414,6 +424,7 @@ export default {
 
     /**
      * This applies stored theme data onto form. Supports three versions of data:
+     * v3 (version = 3) - same as 2 but with some incompatible changes
      * v2 (version = 2) - newer version of themes.
      * v1 (version = 1) - older version of themes (import from file)
      * v1l (version = l1) - older version of theme (load from local storage)
@@ -421,12 +432,21 @@ export default {
      * @param {Object} input - input data
      * @param {Number} version - version of data. 0 means try to guess based on data. "l1" means v1, locastorage type
      */
-    normalizeLocalState (input, version = 0) {
-      const colors = input.colors || input
+    normalizeLocalState (originalInput, version = 0) {
+      let input
+      if (typeof originalInput.v3compat !== undefined) {
+        version = 3
+        input = merge(originalInput, originalInput.v3compat)
+      } else {
+        input = originalInput
+      }
+
+      const compat = input.v3compat
       const radii = input.radii || input
       const opacity = input.opacity
       const shadows = input.shadows || {}
       const fonts = input.fonts || {}
+      const colors = input.colors || input
 
       if (version === 0) {
         if (input.version) version = input.version
@@ -530,6 +550,7 @@ export default {
     currentColors () {
       try {
         this.previewColors = generateColors({
+          v3compat: this.currentCompat,
           opacity: this.currentOpacity,
           colors: this.currentColors
         })
@@ -542,8 +563,9 @@ export default {
     currentOpacity () {
       try {
         this.previewColors = generateColors({
+          v3compat: this.currentCompat,
           opacity: this.currentOpacity,
-          colors: this.currentColors
+          colors: this.currentColors,
         })
       } catch (e) {
         console.warn(e)
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 8bbdc7b7..2ecd275a 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -2,7 +2,7 @@
   <div class="style-switcher">
     <div class="presets-container">
       <div class="save-load">
-        <export-import
+        <ExportImport
           :export-object="exportedTheme"
           :export-label="$t(&quot;settings.export_theme&quot;)"
           :import-label="$t(&quot;settings.import_theme&quot;)"
@@ -38,7 +38,7 @@
               </label>
             </div>
           </template>
-        </export-import>
+        </ExportImport>
       </div>
       <div class="save-load-options">
         <span class="keep-option">
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 1e4bc59e..19a06587 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -148,6 +148,26 @@ const getCssColor = (input, a) => {
   return rgb2rgba({ ...rgb, a })
 }
 
+// Generates a "patch" for theme to make it compatible with v2
+export const generateCompat = (input) => {
+  const { colors } = input
+  const v3compat = {
+    colors: {}
+  }
+  const v2colorsPatch = {}
+
+  // # Link became optional in v3
+  if (typeof colors.link === 'undefined') {
+    v2colorsPatch.link = colors.accent
+    v3compat.colors.link = null
+  }
+
+  return {
+    v3compat,
+    colors: v2colorsPatch
+  }
+}
+
 const generateColors = (input) => {
   const colors = {}
   const opacity = Object.assign({
@@ -164,7 +184,10 @@ const generateColors = (input) => {
   const inputColors = input.colors || input
 
   const compat = input.v3compat || {}
-  const compatColors = compat.colors || {}
+  const compatColors = Object.entries(compat.colors || {}).reduce((acc, [key, value]) => {
+    const newVal = value === null ? undefined : value
+    return { ...acc, [key]: newVal }
+  }, {})
 
   const col = Object.entries({ ...inputColors, ...compatColors }).reduce((acc, [k, v]) => {
     if (typeof v === 'object') {
@@ -210,7 +233,7 @@ const generateColors = (input) => {
   colors.topBarText = col.topBarText || getTextColor(colors.topBar, colors.fgText)
   colors.topBarLink = col.topBarLink || getTextColor(colors.topBar, colors.fgLink)
 
-  colors.faintLink = col.faintLink || Object.assign({}, col.link)
+  colors.faintLink = col.faintLink || Object.assign({}, col.link || col.accent)
   colors.linkBg = alphaBlend(colors.link, 0.4, colors.bg)
 
   colors.icon = mixrgb(colors.bg, colors.text)

From 6e11924c27288e590de8f5b675fb53704581bcc8 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sat, 28 Dec 2019 18:49:35 +0200
Subject: [PATCH 113/483] underlay customization, updated contrast calculations
 to account for alpha blending

---
 src/App.scss                                  |  1 +
 .../style_switcher/style_switcher.js          | 47 +++++++++++--------
 .../style_switcher/style_switcher.vue         | 14 ++++++
 src/services/color_convert/color_convert.js   | 23 +++++++++
 src/services/style_setter/style_setter.js     | 37 ++++++++++-----
 5 files changed, 91 insertions(+), 31 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 3b03a761..b6d4943a 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -32,6 +32,7 @@ h4 {
   min-height: 100vh;
   max-width: 980px;
   background-color: rgba(0,0,0,0.15);
+  background-color: var(--underlay, rgba(0,0,0,0.15));
   align-content: flex-start;
 }
 
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index b450dc8e..602a635e 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -1,4 +1,4 @@
-import { rgb2hex, hex2rgb, getContrastRatio, alphaBlend } from '../../services/color_convert/color_convert.js'
+import { rgb2hex, hex2rgb, getContrastRatio, getContrastRatioLayers, alphaBlend } from '../../services/color_convert/color_convert.js'
 import { set, delete as del } from 'vue'
 import { merge } from 'lodash'
 import { generateCompat, generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } from '../../services/style_setter/style_setter.js'
@@ -53,6 +53,9 @@ export default {
       bgColorLocal: '',
       bgOpacityLocal: undefined,
 
+      underlayColorLocal: '',
+      underlayOpacityLocal: undefined,
+
       fgColorLocal: '',
       fgTextColorLocal: undefined,
       fgLinkColorLocal: undefined,
@@ -145,6 +148,8 @@ export default {
 
         accent: this.accentColorLocal,
 
+        underlay: this.underlayColorLocal,
+
         panel: this.panelColorLocal,
         panelText: this.panelTextColorLocal,
         panelLink: this.panelLinkColorLocal,
@@ -182,7 +187,8 @@ export default {
         panel: this.panelOpacityLocal,
         topBar: this.topBarOpacityLocal,
         border: this.borderOpacityLocal,
-        faint: this.faintOpacityLocal
+        faint: this.faintOpacityLocal,
+        underlay: this.underlayOpacityLocal
       }
     },
     currentRadii () {
@@ -240,6 +246,7 @@ export default {
 
       const bgs = {
         bg: hex2rgb(colors.bg),
+        underlay: hex2rgb(colors.underlay),
         btn: hex2rgb(colors.btn),
         panel: hex2rgb(colors.panel),
         topBar: hex2rgb(colors.topBar),
@@ -249,29 +256,31 @@ export default {
         badgeNotification: hex2rgb(colors.badgeNotification)
       }
 
-      /* This is a bit confusing because "bottom layer" used is text color
-       * This is done to get worst case scenario when background below transparent
-       * layer matches text color, making it harder to read the lower alpha is.
-       */
-      const ratios = {
-        bgText: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.text), fgs.text),
-        bgLink: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.link), fgs.link),
-        bgRed: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.red), fgs.red),
-        bgGreen: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.green), fgs.green),
-        bgBlue: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.blue), fgs.blue),
-        bgOrange: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.orange), fgs.orange),
+      const bg = [bgs.bg, opacity.bg]
+      const underlay = [bgs.underlay, opacity.underlay]
 
+      const panel = [underlay, bg]
+
+      const ratios = {
+        bgText: getContrastRatioLayers(fgs.text, panel, fgs.text),
+        bgLink: getContrastRatioLayers(fgs.link, panel, fgs.link),
+        bgRed: getContrastRatioLayers(fgs.red, panel, fgs.red),
+        bgGreen: getContrastRatioLayers(fgs.green, panel, fgs.green),
+        bgBlue: getContrastRatioLayers(fgs.blue, panel, fgs.blue),
+        bgOrange: getContrastRatioLayers(fgs.orange, panel, fgs.orange),
+
+        // TODO what's this?
         tintText: getContrastRatio(alphaBlend(bgs.bg, 0.5, fgs.panelText), fgs.text),
 
-        panelText: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelText), fgs.panelText),
-        panelLink: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelLink), fgs.panelLink),
+        panelText: getContrastRatioLayers(fgs.text, [...panel, [bgs.panel, opacity.panel]], fgs.panelText),
+        panelLink: getContrastRatioLayers(fgs.link, [...panel, [bgs.panel, opacity.panel]], fgs.panelLink),
 
-        btnText: getContrastRatio(alphaBlend(bgs.btn, opacity.btn, fgs.btnText), fgs.btnText),
+        btnText: getContrastRatioLayers(fgs.text, [...panel, [bgs.btn, opacity.btn]], fgs.btnText),
 
-        inputText: getContrastRatio(alphaBlend(bgs.input, opacity.input, fgs.inputText), fgs.inputText),
+        inputText: getContrastRatioLayers(fgs.text, [...panel, [bgs.input, opacity.input]], fgs.inputText),
 
-        topBarText: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarText), fgs.topBarText),
-        topBarLink: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarLink), fgs.topBarLink)
+        topBarText: getContrastRatioLayers(fgs.text, [...panel, [bgs.topBar, opacity.topBar]], fgs.topBarText),
+        topBarLink: getContrastRatioLayers(fgs.link, [...panel, [bgs.topBar, opacity.topBar]], fgs.topBarLink)
       }
 
       return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 2ecd275a..38ca2017 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -366,6 +366,20 @@
               :fallback="previewTheme.opacity.faint || 0.5"
             />
           </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.underlay') }}</h4>
+            <ColorInput
+              v-model="underlayColorLocal"
+              name="underlay"
+              :label="$t('settings.style.advanced_colors.underlay')"
+              fallback='#000000'
+            />
+            <OpacityInput
+              v-model="underlayOpacityLocal"
+              name="underlayOpacity"
+              :fallback="previewTheme.opacity.underlay || 0.15"
+            />
+          </div>
         </div>
 
         <div
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index d1b17c61..757a36bd 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -1,5 +1,8 @@
 import { map } from 'lodash'
 
+// useful for visualizing color when debugging
+export const consoleColor = (color) => console.log('%c##########', 'background: ' + color + '; color: ' + color)
+
 const rgb2hex = (r, g, b) => {
   if (r === null || typeof r === 'undefined') {
     return undefined
@@ -78,6 +81,16 @@ const getContrastRatio = (a, b) => {
 
   return (l1 + 0.05) / (l2 + 0.05)
 }
+/**
+ * Same as `getContrastRatio` but for multiple layers in-between
+ *
+ * @param {Object} text - text color (topmost layer)
+ * @param {[Object, Number]} layers[] - layers between text and bedrock
+ * @param {Object} bedrock - layer at the very bottom
+ */
+export const getContrastRatioLayers = (text, layers, bedrock) => {
+  return getContrastRatio(alphaBlendLayers(bedrock, layers), text)
+}
 
 /**
  * This performs alpha blending between solid background and semi-transparent foreground
@@ -97,6 +110,16 @@ const alphaBlend = (fg, fga, bg) => {
   }, {})
 }
 
+/**
+ * Same as `alphaBlend` but for multiple layers in-between
+ *
+ * @param {Object} bedrock - layer at the very bottom
+ * @param {[Object, Number]} layers[] - layers between text and bedrock
+ */
+export const alphaBlendLayers = (bedrock, layers) => layers.reduce((acc, [color, opacity]) => {
+  return alphaBlend(color, opacity, acc)
+}, bedrock)
+
 const invert = (rgb) => {
   return 'rgb'.split('').reduce((acc, c) => {
     acc[c] = 255 - rgb[c]
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 19a06587..8740fc55 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,6 +1,6 @@
 import { times } from 'lodash'
 import { brightness, invertLightness, convert, contrastRatio } from 'chromatism'
-import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend } from '../color_convert/color_convert.js'
+import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend, alphaBlendLayers } from '../color_convert/color_convert.js'
 
 // While this is not used anymore right now, I left it in if we want to do custom
 // styles that aren't just colors, so user can pick from a few different distinct
@@ -64,8 +64,10 @@ const getTextColor = function (bg, text, preserve) {
     const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
     const result = Object.assign(base, invertLightness(text).rgb)
     if (!preserve && getContrastRatio(bg, result) < 4.5) {
+      // B&W
       return contrastRatio(bg, text).rgb
     }
+    // Inverted color
     return result
   }
   return text
@@ -173,7 +175,8 @@ const generateColors = (input) => {
   const opacity = Object.assign({
     alert: 0.5,
     input: 0.5,
-    faint: 0.5
+    faint: 0.5,
+    underlay: 0.15
   }, Object.entries(input.opacity || {}).reduce((acc, [k, v]) => {
     if (typeof v !== 'undefined') {
       acc[k] = v
@@ -210,28 +213,37 @@ const generateColors = (input) => {
   colors.faint = col.faint || Object.assign({}, col.text)
 
   colors.bg = col.bg
-  colors.lightBg = col.lightBg || brightness(5, colors.bg).rgb
+  colors.lightBg = col.lightBg || brightness(5 * mod, colors.bg).rgb
+
+  const underlay = [col.underlay, opacity.underlay]
+  const fg = [col.fg, opacity.fg]
+  const bg = [col.bg, opacity.bg]
 
   colors.fg = col.fg
-  colors.fgText = col.fgText || getTextColor(colors.fg, colors.text)
-  colors.fgLink = col.fgLink || getTextColor(colors.fg, colors.link, true)
+  colors.fgText = col.fgText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, fg]), colors.text)
+  colors.fgLink = col.fgLink || getTextColor(alphaBlendLayers(colors.link, [underlay, bg, fg]), colors.link, true)
+  colors.underlay = col.underlay || hex2rgb('#000000')
 
   colors.border = col.border || brightness(2 * mod, colors.fg).rgb
 
   colors.btn = col.btn || Object.assign({}, col.fg)
-  colors.btnText = col.btnText || getTextColor(colors.btn, colors.fgText)
+  const btn = [colors.btn, opacity.btn || 1]
+  colors.btnText = col.btnText || getTextColor(alphaBlendLayers(colors.fgText, [underlay, bg, fg, btn]), colors.fgText)
 
   colors.input = col.input || Object.assign({}, col.fg)
-  colors.inputText = col.inputText || getTextColor(colors.input, colors.lightText)
+  const inputCol = [colors.input, opacity.input]
+  colors.inputText = col.inputText || getTextColor(alphaBlendLayers(colors.lightText, [underlay, bg, fg, inputCol]), colors.lightText)
 
   colors.panel = col.panel || Object.assign({}, col.fg)
-  colors.panelText = col.panelText || getTextColor(colors.panel, colors.fgText)
-  colors.panelLink = col.panelLink || getTextColor(colors.panel, colors.fgLink)
-  colors.panelFaint = col.panelFaint || getTextColor(colors.panel, colors.faint)
+  const panel = [colors.panel, opacity.panel]
+  colors.panelText = col.panelText || getTextColor(alphaBlendLayers(colors.fgText, [underlay, bg, panel]), colors.fgText)
+  colors.panelLink = col.panelLink || getTextColor(alphaBlendLayers(colors.fgLink, [underlay, bg, panel]), colors.fgLink)
+  colors.panelFaint = col.panelFaint || getTextColor(alphaBlendLayers(colors.faint, [underlay, bg, panel]), colors.faint)
 
   colors.topBar = col.topBar || Object.assign({}, col.fg)
-  colors.topBarText = col.topBarText || getTextColor(colors.topBar, colors.fgText)
-  colors.topBarLink = col.topBarLink || getTextColor(colors.topBar, colors.fgLink)
+  const topBar = [colors.topBar, opacity.topBar]
+  colors.topBarText = col.topBarText || getTextColor(alphaBlendLayers(colors.fgText, [topBar]), colors.fgText)
+  colors.topBarLink = col.topBarLink || getTextColor(alphaBlendLayers(colors.fgLink, [topBar]), colors.fgLink)
 
   colors.faintLink = col.faintLink || Object.assign({}, col.link || col.accent)
   colors.linkBg = alphaBlend(colors.link, 0.4, colors.bg)
@@ -255,6 +267,7 @@ const generateColors = (input) => {
   colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb
 
   Object.entries(opacity).forEach(([ k, v ]) => {
+    console.log(k)
     if (typeof v === 'undefined') return
     if (k === 'alert') {
       colors.alertError.a = v

From 332d31dc02b83d6ca06837fdfb4f663050d9effd Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 30 Dec 2019 00:45:48 +0200
Subject: [PATCH 114/483] support for "transparent" color keyword

---
 .../style_switcher/style_switcher.js          | 56 +++++++++++--------
 src/services/color_convert/color_convert.js   |  3 +-
 src/services/style_setter/style_setter.js     | 50 +++++++++++------
 static/themes/breezy-dark.json                |  8 ++-
 static/themes/breezy-light.json               |  5 ++
 5 files changed, 78 insertions(+), 44 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 602a635e..9fe1bf59 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -25,6 +25,14 @@ const v1OnlyNames = [
   'cOrange'
 ].map(_ => _ + 'ColorLocal')
 
+const colorConvert = (color) => {
+  if (color === 'transparent') {
+    return color
+  } else {
+    return hex2rgb(color)
+  }
+}
+
 export default {
   data () {
     return {
@@ -228,36 +236,36 @@ export default {
 
       // fgsfds :DDDD
       const fgs = {
-        text: hex2rgb(colors.text),
-        panelText: hex2rgb(colors.panelText),
-        panelLink: hex2rgb(colors.panelLink),
-        btnText: hex2rgb(colors.btnText),
-        topBarText: hex2rgb(colors.topBarText),
-        inputText: hex2rgb(colors.inputText),
+        text: colorConvert(colors.text),
+        panelText: colorConvert(colors.panelText),
+        panelLink: colorConvert(colors.panelLink),
+        btnText: colorConvert(colors.btnText),
+        topBarText: colorConvert(colors.topBarText),
+        inputText: colorConvert(colors.inputText),
 
-        link: hex2rgb(colors.link),
-        topBarLink: hex2rgb(colors.topBarLink),
+        link: colorConvert(colors.link),
+        topBarLink: colorConvert(colors.topBarLink),
 
-        red: hex2rgb(colors.cRed),
-        green: hex2rgb(colors.cGreen),
-        blue: hex2rgb(colors.cBlue),
-        orange: hex2rgb(colors.cOrange)
+        red: colorConvert(colors.cRed),
+        green: colorConvert(colors.cGreen),
+        blue: colorConvert(colors.cBlue),
+        orange: colorConvert(colors.cOrange)
       }
 
       const bgs = {
-        bg: hex2rgb(colors.bg),
-        underlay: hex2rgb(colors.underlay),
-        btn: hex2rgb(colors.btn),
-        panel: hex2rgb(colors.panel),
-        topBar: hex2rgb(colors.topBar),
-        input: hex2rgb(colors.input),
-        alertError: hex2rgb(colors.alertError),
-        alertWarning: hex2rgb(colors.alertWarning),
-        badgeNotification: hex2rgb(colors.badgeNotification)
+        bg: colorConvert(colors.bg),
+        underlay: colorConvert(colors.underlay),
+        btn: colorConvert(colors.btn),
+        panel: colorConvert(colors.panel),
+        topBar: colorConvert(colors.topBar),
+        input: colorConvert(colors.input),
+        alertError: colorConvert(colors.alertError),
+        alertWarning: colorConvert(colors.alertWarning),
+        badgeNotification: colorConvert(colors.badgeNotification)
       }
 
       const bg = [bgs.bg, opacity.bg]
-      const underlay = [bgs.underlay, opacity.underlay]
+      const underlay = [bgs.underlay || colorConvert('#000000'), opacity.underlay]
 
       const panel = [underlay, bg]
 
@@ -443,7 +451,7 @@ export default {
      */
     normalizeLocalState (originalInput, version = 0) {
       let input
-      if (typeof originalInput.v3compat !== undefined) {
+      if (typeof originalInput.v3compat !== 'undefined') {
         version = 3
         input = merge(originalInput, originalInput.v3compat)
       } else {
@@ -574,7 +582,7 @@ export default {
         this.previewColors = generateColors({
           v3compat: this.currentCompat,
           opacity: this.currentOpacity,
-          colors: this.currentColors,
+          colors: this.currentColors
         })
       } catch (e) {
         console.warn(e)
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 757a36bd..32b4d50e 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -7,7 +7,8 @@ const rgb2hex = (r, g, b) => {
   if (r === null || typeof r === 'undefined') {
     return undefined
   }
-  if (r[0] === '#') {
+  // TODO: clean up this mess
+  if (r[0] === '#' || r === 'transparent') {
     return r
   }
   if (typeof r === 'object') {
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 8740fc55..df22f94f 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -170,23 +170,32 @@ export const generateCompat = (input) => {
   }
 }
 
-const generateColors = (input) => {
+const generateColors = (themeData) => {
   const colors = {}
-  const opacity = Object.assign({
+  const rawOpacity = Object.assign({
     alert: 0.5,
     input: 0.5,
     faint: 0.5,
     underlay: 0.15
-  }, Object.entries(input.opacity || {}).reduce((acc, [k, v]) => {
+  }, Object.entries(themeData.opacity || {}).reduce((acc, [k, v]) => {
     if (typeof v !== 'undefined') {
       acc[k] = v
     }
     return acc
   }, {}))
 
-  const inputColors = input.colors || input
+  const inputColors = themeData.colors || themeData
 
-  const compat = input.v3compat || {}
+  const transparentsOpacity = Object.entries(inputColors).reduce((acc, [k, v]) => {
+    if (v === 'transparent') {
+      acc[k] = 0
+    }
+    return acc
+  }, {})
+
+  const opacity = { ...rawOpacity, ...transparentsOpacity }
+
+  const compat = themeData.v3compat || {}
   const compatColors = Object.entries(compat.colors || {}).reduce((acc, [key, value]) => {
     const newVal = value === null ? undefined : value
     return { ...acc, [key]: newVal }
@@ -196,15 +205,22 @@ const generateColors = (input) => {
     if (typeof v === 'object') {
       acc[k] = v
     } else {
-      acc[k] = hex2rgb(v)
+      let value = v
+      if (v === 'transparent') {
+        value = '#FF00FF'
+      }
+      acc[k] = hex2rgb(value)
     }
     return acc
   }, {})
 
-  const isLightOnDark = convert(col.bg).hsl.l < convert(col.text).hsl.l
+  colors.bg = col.bg
+  colors.underlay = col.underlay || hex2rgb('#000000')
+  colors.text = col.text
+
+  const isLightOnDark = convert(colors.bg).hsl.l < convert(colors.text).hsl.l
   const mod = isLightOnDark ? 1 : -1
 
-  colors.text = col.text
   colors.lightText = brightness(20 * mod, colors.text).rgb
 
   colors.accent = col.accent || col.link
@@ -212,17 +228,15 @@ const generateColors = (input) => {
 
   colors.faint = col.faint || Object.assign({}, col.text)
 
-  colors.bg = col.bg
   colors.lightBg = col.lightBg || brightness(5 * mod, colors.bg).rgb
 
-  const underlay = [col.underlay, opacity.underlay]
+  const underlay = [colors.underlay, opacity.underlay]
   const fg = [col.fg, opacity.fg]
   const bg = [col.bg, opacity.bg]
 
   colors.fg = col.fg
   colors.fgText = col.fgText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, fg]), colors.text)
   colors.fgLink = col.fgLink || getTextColor(alphaBlendLayers(colors.link, [underlay, bg, fg]), colors.link, true)
-  colors.underlay = col.underlay || hex2rgb('#000000')
 
   colors.border = col.border || brightness(2 * mod, colors.fg).rgb
 
@@ -231,8 +245,8 @@ const generateColors = (input) => {
   colors.btnText = col.btnText || getTextColor(alphaBlendLayers(colors.fgText, [underlay, bg, fg, btn]), colors.fgText)
 
   colors.input = col.input || Object.assign({}, col.fg)
-  const inputCol = [colors.input, opacity.input]
-  colors.inputText = col.inputText || getTextColor(alphaBlendLayers(colors.lightText, [underlay, bg, fg, inputCol]), colors.lightText)
+  const input = [colors.input, opacity.input]
+  colors.inputText = col.inputText || getTextColor(alphaBlendLayers(colors.lightText, [underlay, bg, fg, input]), colors.lightText)
 
   colors.panel = col.panel || Object.assign({}, col.fg)
   const panel = [colors.panel, opacity.panel]
@@ -256,12 +270,14 @@ const generateColors = (input) => {
   colors.cOrange = col.cOrange || hex2rgb('#E3FF00')
 
   colors.alertError = col.alertError || Object.assign({}, colors.cRed)
-  colors.alertErrorText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.bg), colors.text)
-  colors.alertErrorPanelText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.panel), colors.panelText)
+  const alertError = [colors.alertError, opacity.alert]
+  colors.alertErrorText = getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertError]), colors.text)
+  colors.alertErrorPanelText = getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertError]), colors.panelText)
 
   colors.alertWarning = col.alertWarning || Object.assign({}, colors.cOrange)
-  colors.alertWarningText = getTextColor(alphaBlend(colors.alertWarning, opacity.alert, colors.bg), colors.text)
-  colors.alertWarningPanelText = getTextColor(alphaBlend(colors.alertWarning, opacity.alert, colors.panel), colors.panelText)
+  const alertWarning = [colors.alertWarning, opacity.alert]
+  colors.alertWarningText = getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertWarning]), colors.text)
+  colors.alertWarningPanelText = getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertWarning]), colors.panelText)
 
   colors.badgeNotification = col.badgeNotification || Object.assign({}, colors.cRed)
   colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb
diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index 7ce41384..0ed55184 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -107,8 +107,12 @@
     },
     "fonts": {},
     "opacity": {
-      "input": "1",
-      "panel": "0"
+      "input": "1"
+    },
+    "v3compat": {
+      "colors": {
+        "panel": "transparent"
+      }
     },
     "colors": {
       "bg": "#31363b",
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index dc43f90e..5db185dd 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -109,6 +109,11 @@
     "opacity": {
       "input": "1"
     },
+    "v3compat": {
+      "colors": {
+        "panel": "transparent"
+      }
+    },
     "colors": {
       "bg": "#eff0f1",
       "text": "#232627",

From 816c077c4fdd72eb04c5d8fc8bfd567d9a292a3c Mon Sep 17 00:00:00 2001
From: Wyatt Benno <wyattbenno@gmail.com>
Date: Mon, 30 Dec 2019 19:27:44 +0900
Subject: [PATCH 115/483] mfa fix

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

diff --git a/src/components/user_settings/mfa.js b/src/components/user_settings/mfa.js
index 3090138a..abf37062 100644
--- a/src/components/user_settings/mfa.js
+++ b/src/components/user_settings/mfa.js
@@ -139,7 +139,7 @@ const Mfa = {
 
     // fetch settings from server
     async fetchSettings () {
-      let result = await this.backendInteractor.fetchSettingsMFA()
+      let result = await this.backendInteractor.settingsMFA()
       if (result.error) return
       this.settings = result.settings
       this.settings.available = true

From 4bb1c98e0f28bcf1d0dff2d90d01013cd5487522 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 2 Jan 2020 20:36:10 +0200
Subject: [PATCH 116/483] Replaced `v3compat` with `source` to reduce code
 complexity. Made more slots customizable. `theme` now contains a snapshot of
 theme generated for better compatiblity and future-proofing

---
 .../style_switcher/style_switcher.js          |  68 +++---
 .../style_switcher/style_switcher.vue         |  14 +-
 src/services/style_setter/style_setter.js     |  42 ++--
 static/themes/breezy-dark.json                | 224 +++++++++++++++++-
 static/themes/breezy-light.json               | 220 ++++++++++++++++-
 5 files changed, 488 insertions(+), 80 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 9fe1bf59..f751260a 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -1,7 +1,15 @@
 import { rgb2hex, hex2rgb, getContrastRatio, getContrastRatioLayers, alphaBlend } from '../../services/color_convert/color_convert.js'
 import { set, delete as del } from 'vue'
 import { merge } from 'lodash'
-import { generateCompat, generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } from '../../services/style_setter/style_setter.js'
+import {
+  generateColors,
+  generateShadows,
+  generateRadii,
+  generateFonts,
+  composePreset,
+  getThemes,
+  CURRENT_VERSION
+} from '../../services/style_setter/style_setter.js'
 import ColorInput from '../color_input/color_input.vue'
 import RangeInput from '../range_input/range_input.vue'
 import OpacityInput from '../opacity_input/opacity_input.vue'
@@ -135,15 +143,6 @@ export default {
     selectedVersion () {
       return Array.isArray(this.selected) ? 1 : 2
     },
-    currentCompat () {
-      return generateCompat({
-        shadows: this.shadowsLocal,
-        fonts: this.fontsLocal,
-        opacity: this.currentOpacity,
-        colors: this.currentColors,
-        radii: this.currentRadii
-      })
-    },
     currentColors () {
       return {
         bg: this.bgColorLocal,
@@ -339,27 +338,32 @@ export default {
         !this.keepColor
       )
 
-      const theme = {}
+      const source = {
+        themeEngineVersion: CURRENT_VERSION
+      }
 
       if (this.keepFonts || saveEverything) {
-        theme.fonts = this.fontsLocal
+        source.fonts = this.fontsLocal
       }
       if (this.keepShadows || saveEverything) {
-        theme.shadows = this.shadowsLocal
+        source.shadows = this.shadowsLocal
       }
       if (this.keepOpacity || saveEverything) {
-        theme.opacity = this.currentOpacity
+        source.opacity = this.currentOpacity
       }
       if (this.keepColor || saveEverything) {
-        theme.colors = this.currentColors
+        source.colors = this.currentColors
       }
       if (this.keepRoundness || saveEverything) {
-        theme.radii = this.currentRadii
+        source.radii = this.currentRadii
       }
 
+      const theme = this.previewTheme
+
+      console.log(source)
       return {
-        // To separate from other random JSON files and possible future theme formats
-        _pleroma_theme_version: 2, theme: merge(theme, this.currentCompat)
+        // To separate from other random JSON files and possible future source formats
+        _pleroma_theme_version: 2, theme, source
       }
     }
   },
@@ -392,7 +396,7 @@ export default {
       if (parsed._pleroma_theme_version === 1) {
         this.normalizeLocalState(parsed, 1)
       } else if (parsed._pleroma_theme_version >= 2) {
-        this.normalizeLocalState(parsed.theme, 2)
+        this.normalizeLocalState(parsed.theme, 2, parsed.source)
       }
     },
     importValidator (parsed) {
@@ -402,7 +406,7 @@ export default {
     clearAll () {
       const state = this.$store.getters.mergedConfig.customTheme
       const version = state.colors ? 2 : 'l1'
-      this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme, version)
+      this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme, version, this.$store.getters.mergedConfig.customThemeSource)
     },
 
     // Clears all the extra stuff when loading V1 theme
@@ -441,24 +445,30 @@ export default {
 
     /**
      * This applies stored theme data onto form. Supports three versions of data:
-     * v3 (version = 3) - same as 2 but with some incompatible changes
+     * v3 (version >= 3) - newest version of themes which supports snapshots for better compatiblity
      * v2 (version = 2) - newer version of themes.
      * v1 (version = 1) - older version of themes (import from file)
      * v1l (version = l1) - older version of theme (load from local storage)
      * v1 and v1l differ because of way themes were stored/exported.
-     * @param {Object} input - input data
+     * @param {Object} theme - theme data (snapshot)
      * @param {Number} version - version of data. 0 means try to guess based on data. "l1" means v1, locastorage type
+     * @param {Object} source - theme source - this will be used if compatible
+     * @param {Boolean} source - by default source won't be used if version doesn't match since it might render differently
+     *                           this allows importing source anyway
      */
-    normalizeLocalState (originalInput, version = 0) {
+    normalizeLocalState (theme, version = 0, source, forceSource = false) {
       let input
-      if (typeof originalInput.v3compat !== 'undefined') {
-        version = 3
-        input = merge(originalInput, originalInput.v3compat)
+      if (typeof source !== 'undefined') {
+        if (forceSource || source.themeEngineVersion === CURRENT_VERSION) {
+          input = source
+          version = source.themeEngineVersion
+        } else {
+          input = theme
+        }
       } else {
-        input = originalInput
+        input = theme
       }
 
-      const compat = input.v3compat
       const radii = input.radii || input
       const opacity = input.opacity
       const shadows = input.shadows || {}
@@ -615,7 +625,7 @@ export default {
           this.cOrangeColorLocal = this.selected[8]
         }
       } else if (this.selectedVersion >= 2) {
-        this.normalizeLocalState(this.selected.theme, 2)
+        this.normalizeLocalState(this.selected.theme, 2, this.selected.source)
       }
     }
   }
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 38ca2017..2eadbe25 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -106,7 +106,7 @@
             <OpacityInput
               v-model="bgOpacityLocal"
               name="bgOpacity"
-              :fallback="previewTheme.opacity.bg || 1"
+              :fallback="previewTheme.opacity.bg"
             />
             <ColorInput
               v-model="textColorLocal"
@@ -238,7 +238,7 @@
             <OpacityInput
               v-model="panelOpacityLocal"
               name="panelOpacity"
-              :fallback="previewTheme.opacity.panel || 1"
+              :fallback="previewTheme.opacity.panel"
             />
             <ColorInput
               v-model="panelTextColorLocal"
@@ -295,7 +295,7 @@
             <OpacityInput
               v-model="inputOpacityLocal"
               name="inputOpacity"
-              :fallback="previewTheme.opacity.input || 1"
+              :fallback="previewTheme.opacity.input"
             />
             <ColorInput
               v-model="inputTextColorLocal"
@@ -316,7 +316,7 @@
             <OpacityInput
               v-model="btnOpacityLocal"
               name="btnOpacity"
-              :fallback="previewTheme.opacity.btn || 1"
+              :fallback="previewTheme.opacity.btn"
             />
             <ColorInput
               v-model="btnTextColorLocal"
@@ -337,7 +337,7 @@
             <OpacityInput
               v-model="borderOpacityLocal"
               name="borderOpacity"
-              :fallback="previewTheme.opacity.border || 1"
+              :fallback="previewTheme.opacity.border"
             />
           </div>
           <div class="color-item">
@@ -363,7 +363,7 @@
             <OpacityInput
               v-model="faintOpacityLocal"
               name="faintOpacity"
-              :fallback="previewTheme.opacity.faint || 0.5"
+              :fallback="previewTheme.opacity.faint"
             />
           </div>
           <div class="color-item">
@@ -377,7 +377,7 @@
             <OpacityInput
               v-model="underlayOpacityLocal"
               name="underlayOpacity"
-              :fallback="previewTheme.opacity.underlay || 0.15"
+              :fallback="previewTheme.opacity.underlay"
             />
           </div>
         </div>
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index df22f94f..e8a64517 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -2,6 +2,8 @@ import { times } from 'lodash'
 import { brightness, invertLightness, convert, contrastRatio } from 'chromatism'
 import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend, alphaBlendLayers } from '../color_convert/color_convert.js'
 
+export const CURRENT_VERSION = 3
+
 // While this is not used anymore right now, I left it in if we want to do custom
 // styles that aren't just colors, so user can pick from a few different distinct
 // styles as well as set their own colors in the future.
@@ -150,29 +152,13 @@ const getCssColor = (input, a) => {
   return rgb2rgba({ ...rgb, a })
 }
 
-// Generates a "patch" for theme to make it compatible with v2
-export const generateCompat = (input) => {
-  const { colors } = input
-  const v3compat = {
-    colors: {}
-  }
-  const v2colorsPatch = {}
-
-  // # Link became optional in v3
-  if (typeof colors.link === 'undefined') {
-    v2colorsPatch.link = colors.accent
-    v3compat.colors.link = null
-  }
-
-  return {
-    v3compat,
-    colors: v2colorsPatch
-  }
-}
-
 const generateColors = (themeData) => {
   const colors = {}
   const rawOpacity = Object.assign({
+    panel: 1,
+    btn: 1,
+    border: 1,
+    bg: 1,
     alert: 0.5,
     input: 0.5,
     faint: 0.5,
@@ -207,6 +193,7 @@ const generateColors = (themeData) => {
     } else {
       let value = v
       if (v === 'transparent') {
+        // TODO: hack to keep rest of the code from complaining
         value = '#FF00FF'
       }
       acc[k] = hex2rgb(value)
@@ -221,7 +208,7 @@ const generateColors = (themeData) => {
   const isLightOnDark = convert(colors.bg).hsl.l < convert(colors.text).hsl.l
   const mod = isLightOnDark ? 1 : -1
 
-  colors.lightText = brightness(20 * mod, colors.text).rgb
+  colors.lightText = col.lightText || brightness(20 * mod, colors.text).rgb
 
   colors.accent = col.accent || col.link
   colors.link = col.link || col.accent
@@ -231,7 +218,8 @@ const generateColors = (themeData) => {
   colors.lightBg = col.lightBg || brightness(5 * mod, colors.bg).rgb
 
   const underlay = [colors.underlay, opacity.underlay]
-  const fg = [col.fg, opacity.fg]
+  // Technically, foreground can't be transparent (descendants can) but let's keep it just in case
+  const fg = [col.fg, opacity.fg || 1]
   const bg = [col.bg, opacity.bg]
 
   colors.fg = col.fg
@@ -271,16 +259,16 @@ const generateColors = (themeData) => {
 
   colors.alertError = col.alertError || Object.assign({}, colors.cRed)
   const alertError = [colors.alertError, opacity.alert]
-  colors.alertErrorText = getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertError]), colors.text)
-  colors.alertErrorPanelText = getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertError]), colors.panelText)
+  colors.alertErrorText = col.alertErrorText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertError]), colors.text)
+  colors.alertErrorPanelText = col.alertErrorPanelText || getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertError]), colors.panelText)
 
   colors.alertWarning = col.alertWarning || Object.assign({}, colors.cOrange)
   const alertWarning = [colors.alertWarning, opacity.alert]
-  colors.alertWarningText = getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertWarning]), colors.text)
-  colors.alertWarningPanelText = getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertWarning]), colors.panelText)
+  colors.alertWarningText = col.alertWarningText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertWarning]), colors.text)
+  colors.alertWarningPanelText = col.alertWarningPanelText || getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertWarning]), colors.panelText)
 
   colors.badgeNotification = col.badgeNotification || Object.assign({}, colors.cRed)
-  colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb
+  colors.badgeNotificationText = colors.badgeNotificationText || contrastRatio(colors.badgeNotification).rgb
 
   Object.entries(opacity).forEach(([ k, v ]) => {
     console.log(k)
diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index 0ed55184..d447005f 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -2,6 +2,218 @@
   "_pleroma_theme_version": 2,
   "name": "Breezy Dark (beta)",
   "theme": {
+    "shadows": {
+      "panel": [
+        {
+          "x": "1",
+          "y": "2",
+          "blur": "6",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.6
+        }
+      ],
+      "topBar": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.6
+        }
+      ],
+      "popup": [
+        {
+          "x": 2,
+          "y": 2,
+          "blur": 3,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.5
+        }
+      ],
+      "avatar": [
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 8,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.7
+        }
+      ],
+      "avatarStatus": [],
+      "panelHeader": [
+        {
+          "x": 0,
+          "y": "40",
+          "blur": "40",
+          "spread": "-40",
+          "inset": true,
+          "color": "#ffffff",
+          "alpha": "0.1"
+        }
+      ],
+      "button": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": "0",
+          "spread": "1",
+          "color": "#ffffff",
+          "alpha": "0.15",
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": "1",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "buttonHover": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "--accent",
+          "alpha": "0.3",
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": "1",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "buttonPressed": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "0",
+          "spread": "50",
+          "color": "--faint",
+          "alpha": 1,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "#ffffff",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "input": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "#FFFFFF",
+          "alpha": "0.2",
+          "inset": true
+        }
+      ]
+    },
+    "colors": {
+      "bg": "#31363b",
+      "underlay": "#000000",
+      "text": "#eff0f1",
+      "lightText": "#ffffff",
+      "accent": "#3daee9",
+      "link": "#3daee9",
+      "faint": "#eff0f1",
+      "lightBg": "#3d4349",
+      "fg": "#31363b",
+      "fgText": "#eff0f1",
+      "fgLink": "#3daee9",
+      "border": "#4c545b",
+      "btn": "#31363b",
+      "btnText": "#eff0f1",
+      "input": "#232629",
+      "inputText": "#ffffff",
+      "panel": "#ff00ff",
+      "panelText": "#eff0f1",
+      "panelLink": "#3daee9",
+      "panelFaint": "#eff0f1",
+      "topBar": "#31363b",
+      "topBarText": "#eff0f1",
+      "topBarLink": "#eff0f1",
+      "faintLink": "#3daee9",
+      "linkBg": "#366681",
+      "icon": "#909396",
+      "cBlue": "#3daee9",
+      "cRed": "#da4453",
+      "cGreen": "#27ae60",
+      "cOrange": "#f67400",
+      "alertError": "#da4453",
+      "alertErrorText": "#eff0f1",
+      "alertErrorPanelText": "#eff0f1",
+      "alertWarning": "#f67400",
+      "alertWarningText": "#eff0f1",
+      "alertWarningPanelText": "#eff0f1",
+      "badgeNotification": "#da4453",
+      "badgeNotificationText": "#ffffff"
+    },
+    "opacity": {
+      "panel": 0,
+      "btn": 1,
+      "border": 1,
+      "bg": 1,
+      "alert": 0.5,
+      "input": 0.5,
+      "faint": 0.5,
+      "underlay": 0.15
+    },
+    "radii": {
+      "btn": "2",
+      "input": "2",
+      "checkbox": "1",
+      "panel": "2",
+      "avatar": "2",
+      "avatarAlt": "2",
+      "tooltip": "2",
+      "attachment": "2"
+    },
+    "fonts": {
+      "interface": {
+        "family": "sans-serif"
+      },
+      "input": {
+        "family": "inherit"
+      },
+      "post": {
+        "family": "inherit"
+      },
+      "postCode": {
+        "family": "monospace"
+      }
+    }
+  },
+  "source": {
+    "themeEngineVersion": 3,
+    "fonts": {},
     "shadows": {
       "panel": [
         {
@@ -105,21 +317,13 @@
         }
       ]
     },
-    "fonts": {},
-    "opacity": {
-      "input": "1"
-    },
-    "v3compat": {
-      "colors": {
-        "panel": "transparent"
-      }
-    },
+    "opacity": {},
     "colors": {
       "bg": "#31363b",
       "text": "#eff0f1",
       "link": "#3daee9",
       "fg": "#31363b",
-      "panel": "#31363b",
+      "panel": "transparent",
       "input": "#232629",
       "topBarLink": "#eff0f1",
       "btn": "#31363b",
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index 5db185dd..243b8593 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -2,6 +2,218 @@
   "_pleroma_theme_version": 2,
   "name": "Breezy Light (beta)",
   "theme": {
+    "shadows": {
+      "panel": [
+        {
+          "x": "1",
+          "y": "2",
+          "blur": "6",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.6
+        }
+      ],
+      "topBar": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.6
+        }
+      ],
+      "popup": [
+        {
+          "x": 2,
+          "y": 2,
+          "blur": 3,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.5
+        }
+      ],
+      "avatar": [
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 8,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.7
+        }
+      ],
+      "avatarStatus": [],
+      "panelHeader": [
+        {
+          "x": 0,
+          "y": "40",
+          "blur": "40",
+          "spread": "-40",
+          "inset": true,
+          "color": "#ffffff",
+          "alpha": "0.1"
+        }
+      ],
+      "button": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": "0",
+          "spread": "1",
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": "1",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "buttonHover": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "--accent",
+          "alpha": "0.3",
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": "1",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "buttonPressed": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "0",
+          "spread": "50",
+          "color": "--faint",
+          "alpha": 1,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "#ffffff",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": "1",
+          "y": "1",
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": false
+        }
+      ],
+      "input": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "#000000",
+          "alpha": "0.2",
+          "inset": true
+        }
+      ]
+    },
+    "colors": {
+      "bg": "#eff0f1",
+      "underlay": "#000000",
+      "text": "#232627",
+      "lightText": "#000000",
+      "accent": "#2980b9",
+      "link": "#2980b9",
+      "faint": "#232627",
+      "lightBg": "#e2e4e6",
+      "fg": "#bcc2c7",
+      "fgText": "#232627",
+      "fgLink": "#2980b9",
+      "border": "#b7bdc3",
+      "btn": "#eff0f1",
+      "btnText": "#232627",
+      "input": "#fcfcfc",
+      "inputText": "#000000",
+      "panel": "#475057",
+      "panelText": "#fcfcfc",
+      "panelLink": "#ffffff",
+      "panelFaint": "#d8dbdc",
+      "topBar": "#475057",
+      "topBarText": "#d8dbdc",
+      "topBarLink": "#eff0f1",
+      "faintLink": "#2980b9",
+      "linkBg": "#a0c4db",
+      "icon": "#898b8c",
+      "cBlue": "#2980b9",
+      "cRed": "#da4453",
+      "cGreen": "#27ae60",
+      "cOrange": "#f67400",
+      "alertError": "#da4453",
+      "alertErrorText": "#232627",
+      "alertErrorPanelText": "#fcfcfc",
+      "alertWarning": "#f67400",
+      "alertWarningText": "#232627",
+      "alertWarningPanelText": "#fcfcfc",
+      "badgeNotification": "#da4453",
+      "badgeNotificationText": "#ffffff"
+    },
+    "opacity": {
+      "panel": 1,
+      "btn": 1,
+      "border": 1,
+      "bg": 1,
+      "alert": 0.5,
+      "input": "1",
+      "faint": 0.5,
+      "underlay": 0.15
+    },
+    "radii": {
+      "btn": "2",
+      "input": "2",
+      "checkbox": "1",
+      "panel": "2",
+      "avatar": "2",
+      "avatarAlt": "2",
+      "tooltip": "2",
+      "attachment": "2"
+    },
+    "fonts": {
+      "interface": {
+        "family": "sans-serif"
+      },
+      "input": {
+        "family": "inherit"
+      },
+      "post": {
+        "family": "inherit"
+      },
+      "postCode": {
+        "family": "monospace"
+      }
+    }
+  },
+  "source": {
+    "themeEngineVersion": 3,
+    "fonts": {},
     "shadows": {
       "panel": [
         {
@@ -105,20 +317,14 @@
         }
       ]
     },
-    "fonts": {},
     "opacity": {
       "input": "1"
     },
-    "v3compat": {
-      "colors": {
-        "panel": "transparent"
-      }
-    },
     "colors": {
       "bg": "#eff0f1",
       "text": "#232627",
-      "link": "#2980b9",
       "fg": "#bcc2c7",
+      "accent": "#2980b9",
       "panel": "#475057",
       "panelText": "#fcfcfc",
       "input": "#fcfcfc",

From adbab6ad2a413357b24edbd5b9beec3baae695fa Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 2 Jan 2020 21:36:01 +0200
Subject: [PATCH 117/483] added optional checkbox for opacity, similar to color
 input

---
 .../opacity_input/opacity_input.vue           | 37 +++++++++++++++++--
 .../style_switcher/style_switcher.vue         |  9 ++++-
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue
index c677f18c..f7b10d30 100644
--- a/src/components/opacity_input/opacity_input.vue
+++ b/src/components/opacity_input/opacity_input.vue
@@ -18,7 +18,7 @@
       @input="$emit('input', !present ? fallback : undefined)"
     >
     <label
-      v-if="typeof fallback !== 'undefined'"
+      v-if="typeof fallback !== 'undefined' && showOptionalTickbox"
       class="opt-l"
       :for="name + '-o'"
     />
@@ -38,9 +38,38 @@
 
 <script>
 export default {
-  props: [
-    'name', 'value', 'fallback', 'disabled'
-  ],
+  props: {
+    // Name of opacity, used for identifying
+    name: {
+      required: true,
+      type: String
+    },
+    // Opacity value, should be required but vue cannot tell the difference
+    // between "property missing" and "property set to undefined"
+    value: {
+      required: false,
+      type: Number,
+      default: undefined
+    },
+    // Opacity fallback to use when value is not defeind
+    fallback: {
+      required: false,
+      type: Number,
+      default: undefined
+    },
+    // Disable the control
+    disabled: {
+      required: false,
+      type: Boolean,
+      default: false
+    },
+    // Show "optional" tickbox, for when value might become mandatory
+    showOptionalTickbox: {
+      required: false,
+      type: Boolean,
+      default: true
+    }
+  },
   computed: {
     present () {
       return typeof this.value !== 'undefined'
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 2eadbe25..f5b12e7e 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -118,15 +118,15 @@
               v-model="accentColorLocal"
               name="accentColor"
               :fallback="previewTheme.colors.link"
-              :showOptionalTickbox="typeof linkColorLocal !== 'undefined'"
               :label="$t('settings.accent')"
+              :showOptionalTickbox="typeof linkColorLocal !== 'undefined'"
             />
             <ColorInput
               v-model="linkColorLocal"
               name="linkColor"
               :fallback="previewTheme.colors.accent"
-              :showOptionalTickbox="typeof accentColorLocal !== 'undefined'"
               :label="$t('settings.links')"
+              :showOptionalTickbox="typeof accentColorLocal !== 'undefined'"
             />
             <ContrastRatio :contrast="previewContrast.bgLink" />
           </div>
@@ -239,6 +239,7 @@
               v-model="panelOpacityLocal"
               name="panelOpacity"
               :fallback="previewTheme.opacity.panel"
+              :showOptionalTickbox="panelColorLocal !== 'transparent'"
             />
             <ColorInput
               v-model="panelTextColorLocal"
@@ -296,6 +297,7 @@
               v-model="inputOpacityLocal"
               name="inputOpacity"
               :fallback="previewTheme.opacity.input"
+              :showOptionalTickbox="inputColorLocal !== 'transparent'"
             />
             <ColorInput
               v-model="inputTextColorLocal"
@@ -317,6 +319,7 @@
               v-model="btnOpacityLocal"
               name="btnOpacity"
               :fallback="previewTheme.opacity.btn"
+              :showOptionalTickbox="btnColorLocal !== 'transparent'"
             />
             <ColorInput
               v-model="btnTextColorLocal"
@@ -338,6 +341,7 @@
               v-model="borderOpacityLocal"
               name="borderOpacity"
               :fallback="previewTheme.opacity.border"
+              :showOptionalTickbox="borderColorLocal !== 'transparent'"
             />
           </div>
           <div class="color-item">
@@ -378,6 +382,7 @@
               v-model="underlayOpacityLocal"
               name="underlayOpacity"
               :fallback="previewTheme.opacity.underlay"
+              :showOptionalTickbox="underlayOpacityLocal !== 'transparent'"
             />
           </div>
         </div>

From c7e9f21da0eb035b73a165e16f77a9cd18036305 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 2 Jan 2020 22:44:54 +0200
Subject: [PATCH 118/483] color input and opacity input now use Checkbox
 component. Cleanup.

---
 src/components/color_input/color_input.vue    | 20 ++++----
 .../opacity_input/opacity_input.vue           | 51 ++++---------------
 src/components/range_input/range_input.vue    |  2 +-
 .../style_switcher/style_switcher.scss        | 12 ++---
 .../style_switcher/style_switcher.vue         | 10 ++--
 5 files changed, 30 insertions(+), 65 deletions(-)

diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue
index fa26d079..aa289288 100644
--- a/src/components/color_input/color_input.vue
+++ b/src/components/color_input/color_input.vue
@@ -9,18 +9,12 @@
     >
       {{ label }}
     </label>
-    <input
-      v-if="typeof fallback !== 'undefined'"
-      :id="name + '-o'"
-      class="opt exlcude-disabled"
-      type="checkbox"
-      :checked="present"
-      @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
-    >
-    <label
+    <Checkbox
       v-if="typeof fallback !== 'undefined' && showOptionalTickbox"
-      class="opt-l"
-      :for="name + '-o'"
+      :checked="present"
+      :disabled="disabled"
+      @change="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
+      class="opt"
     />
     <input
       :id="name"
@@ -42,6 +36,7 @@
 </template>
 
 <script>
+import Checkbox from '../checkbox/checkbox.vue'
 export default {
   props: {
     // Name of color, used for identifying
@@ -80,6 +75,9 @@ export default {
       default: true
     }
   },
+  components: {
+    Checkbox
+  },
   computed: {
     present () {
       return typeof this.value !== 'undefined'
diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue
index f7b10d30..cfda9926 100644
--- a/src/components/opacity_input/opacity_input.vue
+++ b/src/components/opacity_input/opacity_input.vue
@@ -9,18 +9,12 @@
     >
       {{ $t('settings.style.common.opacity') }}
     </label>
-    <input
+    <Checkbox
       v-if="typeof fallback !== 'undefined'"
-      :id="name + '-o'"
-      class="opt exclude-disabled"
-      type="checkbox"
       :checked="present"
-      @input="$emit('input', !present ? fallback : undefined)"
-    >
-    <label
-      v-if="typeof fallback !== 'undefined' && showOptionalTickbox"
-      class="opt-l"
-      :for="name + '-o'"
+      :disabled="disabled"
+      @change="$emit('input', !present ? fallback : undefined)"
+      class="opt"
     />
     <input
       :id="name"
@@ -37,38 +31,13 @@
 </template>
 
 <script>
+import Checkbox from '../checkbox/checkbox.vue'
 export default {
-  props: {
-    // Name of opacity, used for identifying
-    name: {
-      required: true,
-      type: String
-    },
-    // Opacity value, should be required but vue cannot tell the difference
-    // between "property missing" and "property set to undefined"
-    value: {
-      required: false,
-      type: Number,
-      default: undefined
-    },
-    // Opacity fallback to use when value is not defeind
-    fallback: {
-      required: false,
-      type: Number,
-      default: undefined
-    },
-    // Disable the control
-    disabled: {
-      required: false,
-      type: Boolean,
-      default: false
-    },
-    // Show "optional" tickbox, for when value might become mandatory
-    showOptionalTickbox: {
-      required: false,
-      type: Boolean,
-      default: true
-    }
+  props: [
+    'name', 'value', 'fallback', 'disabled'
+  ],
+  components: {
+    Checkbox
   },
   computed: {
     present () {
diff --git a/src/components/range_input/range_input.vue b/src/components/range_input/range_input.vue
index aaa2ed26..5857a5c1 100644
--- a/src/components/range_input/range_input.vue
+++ b/src/components/range_input/range_input.vue
@@ -12,7 +12,7 @@
     <input
       v-if="typeof fallback !== 'undefined'"
       :id="name + '-o'"
-      class="opt exclude-disabled"
+      class="opt"
       type="checkbox"
       :checked="present"
       @input="$emit('input', !present ? fallback : undefined)"
diff --git a/src/components/style_switcher/style_switcher.scss b/src/components/style_switcher/style_switcher.scss
index 135c113a..7291a884 100644
--- a/src/components/style_switcher/style_switcher.scss
+++ b/src/components/style_switcher/style_switcher.scss
@@ -15,12 +15,14 @@
 
     &.disabled {
       input, select {
-        &:not(.exclude-disabled) {
-          opacity: .5
-        }
+        opacity: .5
       }
     }
 
+    .opt {
+      margin: .5em;
+    }
+
     input, select {
       min-width: 3em;
       margin: 0;
@@ -44,10 +46,6 @@
         min-width: 3em;
       }
 
-      &[type=checkbox] + label {
-        margin: 6px 0;
-      }
-
       &:not([type=number]):not([type=text]) {
         align-self: flex-start;
       }
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index f5b12e7e..f993e070 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -239,7 +239,7 @@
               v-model="panelOpacityLocal"
               name="panelOpacity"
               :fallback="previewTheme.opacity.panel"
-              :showOptionalTickbox="panelColorLocal !== 'transparent'"
+              :disabled="panelColorLocal === 'transparent'"
             />
             <ColorInput
               v-model="panelTextColorLocal"
@@ -297,7 +297,7 @@
               v-model="inputOpacityLocal"
               name="inputOpacity"
               :fallback="previewTheme.opacity.input"
-              :showOptionalTickbox="inputColorLocal !== 'transparent'"
+              :disabled="inputColorLocal === 'transparent'"
             />
             <ColorInput
               v-model="inputTextColorLocal"
@@ -319,7 +319,7 @@
               v-model="btnOpacityLocal"
               name="btnOpacity"
               :fallback="previewTheme.opacity.btn"
-              :showOptionalTickbox="btnColorLocal !== 'transparent'"
+              :disabled="btnColorLocal === 'transparent'"
             />
             <ColorInput
               v-model="btnTextColorLocal"
@@ -341,7 +341,7 @@
               v-model="borderOpacityLocal"
               name="borderOpacity"
               :fallback="previewTheme.opacity.border"
-              :showOptionalTickbox="borderColorLocal !== 'transparent'"
+              :disabled="borderColorLocal === 'transparent'"
             />
           </div>
           <div class="color-item">
@@ -382,7 +382,7 @@
               v-model="underlayOpacityLocal"
               name="underlayOpacity"
               :fallback="previewTheme.opacity.underlay"
-              :showOptionalTickbox="underlayOpacityLocal !== 'transparent'"
+              :disabled="underlayOpacityLocal === 'transparent'"
             />
           </div>
         </div>

From a2f676d31741f51d037fa4031855f63e1c43f2b3 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 2 Jan 2020 23:48:52 +0200
Subject: [PATCH 119/483] Improved ColorInput to showcase transparent color,
 improved global input styles a bit

---
 src/App.scss                                  | 17 +++++--
 src/components/color_input/color_input.vue    | 50 ++++++++++++-------
 .../style_switcher/style_switcher.scss        | 16 ++----
 3 files changed, 48 insertions(+), 35 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index b6d4943a..7c9c91af 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -122,12 +122,15 @@ button {
   }
 }
 
-label.select {
-  padding: 0;
+input, textarea, .select, .input {
 
-}
+  &.unstyled {
+    border-radius: 0;
+    background: none;
+    box-shadow: none;
+    height: unset;
+  }
 
-input, textarea, .select {
   border: none;
   border-radius: $fallback--inputRadius;
   border-radius: var(--inputRadius, $fallback--inputRadius);
@@ -141,13 +144,17 @@ input, textarea, .select {
   font-family: var(--inputFont, sans-serif);
   font-size: 14px;
   margin: 0;
-  padding: 8px .5em;
   box-sizing: border-box;
   display: inline-block;
   position: relative;
   height: 28px;
   line-height: 16px;
   hyphens: none;
+  padding: 8px .5em;
+
+  &.select {
+    padding: 0;
+  }
 
   &:disabled, &[disabled=disabled] {
     cursor: not-allowed;
diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue
index aa289288..7fe04433 100644
--- a/src/components/color_input/color_input.vue
+++ b/src/components/color_input/color_input.vue
@@ -1,6 +1,6 @@
 <template>
   <div
-    class="color-control style-control"
+    class="color-input style-control"
     :class="{ disabled: !present || disabled }"
   >
     <label
@@ -16,27 +16,35 @@
       @change="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
       class="opt"
     />
-    <input
-      :id="name"
-      class="color-input"
-      type="color"
-      :value="value || fallback"
-      :disabled="!present || disabled"
-      @input="$emit('input', $event.target.value)"
-    >
-    <input
-      :id="name + '-t'"
-      class="text-input"
-      type="text"
-      :value="value || fallback"
-      :disabled="!present || disabled"
-      @input="$emit('input', $event.target.value)"
-    >
+    <div class="input color-input-field">
+      <input
+        :id="name + '-t'"
+        class="textColor unstyled"
+        type="text"
+        :value="value || fallback"
+        :disabled="!present || disabled"
+        @input="$emit('input', $event.target.value)"
+      >
+      <input
+        v-if="validColor"
+        :id="name"
+        class="nativeColor unstyled"
+        type="color"
+        :value="value || fallback"
+        :disabled="!present || disabled"
+        @input="$emit('input', $event.target.value)"
+      >
+      <div
+        v-if="transparentColor"
+        class="transparentIndicator"
+      />
+    </div>
   </div>
 </template>
-
+<style lang="scss" src="./color_input.scss"></style>
 <script>
 import Checkbox from '../checkbox/checkbox.vue'
+import { hex2rgb } from '../../services/color_convert/color_convert.js'
 export default {
   props: {
     // Name of color, used for identifying
@@ -81,6 +89,12 @@ export default {
   computed: {
     present () {
       return typeof this.value !== 'undefined'
+    },
+    validColor () {
+      return hex2rgb(this.value || this.fallback)
+    },
+    transparentColor () {
+      return this.value === 'transparent'
     }
   }
 }
diff --git a/src/components/style_switcher/style_switcher.scss b/src/components/style_switcher/style_switcher.scss
index 7291a884..987245a2 100644
--- a/src/components/style_switcher/style_switcher.scss
+++ b/src/components/style_switcher/style_switcher.scss
@@ -23,20 +23,15 @@
       margin: .5em;
     }
 
+    .color-input {
+      flex: 0 0 0;
+    }
+
     input, select {
       min-width: 3em;
       margin: 0;
       flex: 0;
 
-      &[type=color] {
-        padding: 1px;
-        cursor: pointer;
-        height: 29px;
-        min-width: 2em;
-        border: none;
-        align-self: stretch;
-      }
-
       &[type=number] {
         min-width: 5em;
       }
@@ -44,9 +39,6 @@
       &[type=range] {
         flex: 1;
         min-width: 3em;
-      }
-
-      &:not([type=number]):not([type=text]) {
         align-self: flex-start;
       }
     }

From cce64077b54ae57fd5a8f5b5c520ee3274c5a61e Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 6 Jan 2020 22:55:14 +0200
Subject: [PATCH 120/483] Refactored style_setter to be more declarative
 instead of walls of copypasted code

---
 src/services/style_setter/style_setter.js | 380 +++++++++++++++++-----
 1 file changed, 305 insertions(+), 75 deletions(-)

diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index e8a64517..40a552a1 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -3,6 +3,262 @@ import { brightness, invertLightness, convert, contrastRatio } from 'chromatism'
 import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend, alphaBlendLayers } from '../color_convert/color_convert.js'
 
 export const CURRENT_VERSION = 3
+/* This is a definition of all layer combinations
+ * each key is a topmost layer, each value represents layer underneath
+ * this is essentially a simplified tree
+ */
+export const LAYERS = {
+  undelay: null, // root
+  topBar: null, // no transparency support
+  badge: null, //  no transparency support
+  fg: null,
+  bg: 'underlay',
+  panel: 'bg',
+  btn: 'bg',
+  btnPanel: 'panel',
+  btnTopBar: 'topBar',
+  input: 'bg',
+  inputPanel: 'panel',
+  inputTopBar: 'topBar',
+  alert: 'bg',
+  alertPanel: 'panel'
+}
+
+export const SLOT_INHERITANCE = {
+  bg: null,
+  fg: null,
+  text: null,
+  underlay: '#000000',
+  link: '--accent',
+  accent: '--link',
+  faint: '--text',
+  faintLink: '--link',
+
+  cBlue: '#0000ff',
+  cRed: '#FF0000',
+  cGreen: '#00FF00',
+  cOrange: '#E3FF00',
+
+  lightBg: {
+    depends: ['bg'],
+    color: (mod, bg) => brightness(5 * mod, bg).rgb
+  },
+  lightText: {
+    depends: ['text'],
+    color: (mod, text) => brightness(20 * mod, text).rgb
+  },
+
+  border: {
+    depends: 'fg',
+    color: (mod, fg) => brightness(2 * mod, fg).rgb
+  },
+
+  linkBg: {
+    depends: ['accent', 'bg'],
+    color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg).rgb
+  },
+
+  icon: {
+    depends: ['bg', 'text'],
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
+  // Foreground
+  fgText: {
+    depends: ['text', 'fg', 'underlay', 'bg'],
+    layer: 'fg',
+    textColor: true
+  },
+  fgLink: {
+    depends: ['link', 'fg', 'underlay', 'bg'],
+    layer: 'fg',
+    textColor: 'preserve'
+  },
+
+  // Panel header
+  panel: '--fg',
+  panelText: {
+    depends: ['fgText', 'panel'],
+    layer: 'panel',
+    textColor: true
+  },
+  panelFaint: {
+    depends: ['fgText', 'panel'],
+    layer: 'panel',
+    textColor: true
+  },
+  panelLink: {
+    depends: ['fgLink', 'panel'],
+    layer: 'panel',
+    textColor: 'preserve'
+  },
+
+  // Top bar
+  topBar: '--fg',
+  topBarText: {
+    depends: ['fgText', 'topBar'],
+    layer: 'topBar',
+    textColor: true
+  },
+  topBarLink: {
+    depends: ['fgLink', 'topBar'],
+    layer: 'topBar',
+    textColor: 'preserve'
+  },
+
+  // Buttons
+  btn: '--fg',
+  btnText: {
+    depends: ['fgText', 'btn'],
+    layer: 'btn'
+  },
+  btnPanelText: {
+    depends: ['panelText', 'btn', 'panel'],
+    layer: 'btnPanel',
+    variant: 'btn',
+    textColor: true
+  },
+  btnTopBarText: {
+    depends: ['topBarText', 'btn', 'topBar'],
+    layer: 'btnTopBar',
+    variant: 'btn',
+    textColor: true
+  },
+
+  // Input fields
+  input: '--fg',
+  inputText: {
+    depends: ['text', 'input'],
+    layer: 'input',
+    textColor: true
+  },
+  inputPanelText: {
+    depends: ['panelText', 'input', 'panel'],
+    layer: 'inputPanel',
+    variant: 'input',
+    textColor: true
+  },
+  inputTopbarText: {
+    depends: ['topBarText', 'input', 'topBar'],
+    layer: 'inputTopBar',
+    variant: 'input',
+    textColor: true
+  },
+
+  alertError: '--cRed',
+  alertErrorText: {
+    depends: ['text', 'alertError'],
+    layer: 'alert',
+    variant: 'alertError',
+    textColor: true
+  },
+  alertErrorPanelText: {
+    depends: ['panelText', 'alertError', 'panel'],
+    layer: 'alertPanel',
+    variant: 'alertError',
+    textColor: true
+  },
+
+  alertWarning: '--cOrange',
+  alertWarningText: {
+    depends: ['text', 'alertWarning'],
+    layer: 'alert',
+    variant: 'alertWarning',
+    textColor: true
+  },
+  alertWarningPanelText: {
+    depends: ['panelText', 'alertWarning', 'panel'],
+    layer: 'alertPanel',
+    variant: 'alertWarning',
+    textColor: true
+  },
+
+  badgeNotification: '--cRed',
+  badgeNotificationText: {
+    depends: ['text', 'badgeNotification'],
+    layer: 'badge',
+    variant: 'badgeNotification',
+    textColor: true
+  }
+}
+
+const getDependencies = (key, inheritance) => {
+  const data = inheritance[key]
+  if (typeof data === 'string' && data.startsWith('--')) {
+    return [data.substring(2)]
+  } else {
+    if (data === null) return []
+    const { depends } = data
+    if (Array.isArray(depends)) {
+      return depends
+    } else if (typeof depends === 'object') {
+      return [depends]
+    } else {
+      return []
+    }
+  }
+}
+
+export const topoSort = (
+  inheritance = SLOT_INHERITANCE,
+  getDeps = getDependencies
+) => {
+  // This is an implementation of https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+
+  const allKeys = Object.keys(inheritance)
+  const whites = new Set(allKeys)
+  const grays = new Set()
+  const blacks = new Set()
+  const unprocessed = [...allKeys]
+  const output = []
+
+  const step = (node) => {
+    if (whites.has(node)) {
+      // Make node "gray"
+      whites.delete(node)
+      grays.add(node)
+      // Do step for each node connected to it (one way)
+      getDeps(node, inheritance).forEach(step)
+      // Make node "black"
+      grays.delete(node)
+      blacks.add(node)
+      // Put it into the output list
+      output.push(node)
+    } else if (grays.has(node)) {
+      console.debug('Cyclic depenency in topoSort, ignoring')
+      output.push(node)
+    } else if (blacks.has(node)) {
+      // do nothing
+    } else {
+      throw new Error('Unintended condition in topoSort!')
+    }
+  }
+  while (unprocessed.length > 0) {
+    step(unprocessed.pop())
+  }
+  return output
+}
+
+export const SLOT_ORDERED = topoSort(SLOT_INHERITANCE)
+
+export const getLayersArray = (layer, data = LAYERS) => {
+  let array = [layer]
+  let parent = data[layer]
+  while (parent) {
+    array.unshift(parent)
+    parent = data[parent]
+  }
+  return array
+}
+
+export const getLayers = (layer, variant = layer, colors, opacity) => {
+  return getLayersArray(layer).map((currentLayer) => ([
+    currentLayer === layer
+      ? colors[variant]
+      : colors[currentLayer],
+    opacity[currentLayer]
+  ]))
+}
 
 // While this is not used anymore right now, I left it in if we want to do custom
 // styles that aren't just colors, so user can pick from a few different distinct
@@ -153,12 +409,13 @@ const getCssColor = (input, a) => {
 }
 
 const generateColors = (themeData) => {
-  const colors = {}
   const rawOpacity = Object.assign({
     panel: 1,
     btn: 1,
     border: 1,
     bg: 1,
+    badge: 1,
+    text: 1,
     alert: 0.5,
     input: 0.5,
     faint: 0.5,
@@ -171,7 +428,6 @@ const generateColors = (themeData) => {
   }, {}))
 
   const inputColors = themeData.colors || themeData
-
   const transparentsOpacity = Object.entries(inputColors).reduce((acc, [k, v]) => {
     if (v === 'transparent') {
       acc[k] = 0
@@ -181,13 +437,8 @@ const generateColors = (themeData) => {
 
   const opacity = { ...rawOpacity, ...transparentsOpacity }
 
-  const compat = themeData.v3compat || {}
-  const compatColors = Object.entries(compat.colors || {}).reduce((acc, [key, value]) => {
-    const newVal = value === null ? undefined : value
-    return { ...acc, [key]: newVal }
-  }, {})
-
-  const col = Object.entries({ ...inputColors, ...compatColors }).reduce((acc, [k, v]) => {
+  // Cycle one: just whatever we have
+  const sourceColors = Object.entries(inputColors).reduce((acc, [k, v]) => {
     if (typeof v === 'object') {
       acc[k] = v
     } else {
@@ -201,77 +452,53 @@ const generateColors = (themeData) => {
     return acc
   }, {})
 
-  colors.bg = col.bg
-  colors.underlay = col.underlay || hex2rgb('#000000')
-  colors.text = col.text
-
-  const isLightOnDark = convert(colors.bg).hsl.l < convert(colors.text).hsl.l
+  const isLightOnDark = convert(sourceColors.bg).hsl.l < convert(sourceColors.text).hsl.l
   const mod = isLightOnDark ? 1 : -1
 
-  colors.lightText = col.lightText || brightness(20 * mod, colors.text).rgb
+  const colors = SLOT_ORDERED.reduce((acc, key) => {
+    const value = SLOT_INHERITANCE[key]
+    if (sourceColors[key]) {
+      return { ...acc, [key]: { ...sourceColors[key] } }
+    } else if (typeof value === 'string' && value.startsWith('#')) {
+      return { ...acc, [key]: convert(value).rgb }
+    } else {
+      const isObject = typeof value === 'object'
+      const defaultColorFunc = (mod, dep) => ({ ...dep })
+      const deps = getDependencies(key, SLOT_INHERITANCE)
+      const colorFunc = (isObject && value.color) || defaultColorFunc
 
-  colors.accent = col.accent || col.link
-  colors.link = col.link || col.accent
-
-  colors.faint = col.faint || Object.assign({}, col.text)
-
-  colors.lightBg = col.lightBg || brightness(5 * mod, colors.bg).rgb
-
-  const underlay = [colors.underlay, opacity.underlay]
-  // Technically, foreground can't be transparent (descendants can) but let's keep it just in case
-  const fg = [col.fg, opacity.fg || 1]
-  const bg = [col.bg, opacity.bg]
-
-  colors.fg = col.fg
-  colors.fgText = col.fgText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, fg]), colors.text)
-  colors.fgLink = col.fgLink || getTextColor(alphaBlendLayers(colors.link, [underlay, bg, fg]), colors.link, true)
-
-  colors.border = col.border || brightness(2 * mod, colors.fg).rgb
-
-  colors.btn = col.btn || Object.assign({}, col.fg)
-  const btn = [colors.btn, opacity.btn || 1]
-  colors.btnText = col.btnText || getTextColor(alphaBlendLayers(colors.fgText, [underlay, bg, fg, btn]), colors.fgText)
-
-  colors.input = col.input || Object.assign({}, col.fg)
-  const input = [colors.input, opacity.input]
-  colors.inputText = col.inputText || getTextColor(alphaBlendLayers(colors.lightText, [underlay, bg, fg, input]), colors.lightText)
-
-  colors.panel = col.panel || Object.assign({}, col.fg)
-  const panel = [colors.panel, opacity.panel]
-  colors.panelText = col.panelText || getTextColor(alphaBlendLayers(colors.fgText, [underlay, bg, panel]), colors.fgText)
-  colors.panelLink = col.panelLink || getTextColor(alphaBlendLayers(colors.fgLink, [underlay, bg, panel]), colors.fgLink)
-  colors.panelFaint = col.panelFaint || getTextColor(alphaBlendLayers(colors.faint, [underlay, bg, panel]), colors.faint)
-
-  colors.topBar = col.topBar || Object.assign({}, col.fg)
-  const topBar = [colors.topBar, opacity.topBar]
-  colors.topBarText = col.topBarText || getTextColor(alphaBlendLayers(colors.fgText, [topBar]), colors.fgText)
-  colors.topBarLink = col.topBarLink || getTextColor(alphaBlendLayers(colors.fgLink, [topBar]), colors.fgLink)
-
-  colors.faintLink = col.faintLink || Object.assign({}, col.link || col.accent)
-  colors.linkBg = alphaBlend(colors.link, 0.4, colors.bg)
-
-  colors.icon = mixrgb(colors.bg, colors.text)
-
-  colors.cBlue = col.cBlue || hex2rgb('#0000FF')
-  colors.cRed = col.cRed || hex2rgb('#FF0000')
-  colors.cGreen = col.cGreen || hex2rgb('#00FF00')
-  colors.cOrange = col.cOrange || hex2rgb('#E3FF00')
-
-  colors.alertError = col.alertError || Object.assign({}, colors.cRed)
-  const alertError = [colors.alertError, opacity.alert]
-  colors.alertErrorText = col.alertErrorText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertError]), colors.text)
-  colors.alertErrorPanelText = col.alertErrorPanelText || getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertError]), colors.panelText)
-
-  colors.alertWarning = col.alertWarning || Object.assign({}, colors.cOrange)
-  const alertWarning = [colors.alertWarning, opacity.alert]
-  colors.alertWarningText = col.alertWarningText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, alertWarning]), colors.text)
-  colors.alertWarningPanelText = col.alertWarningPanelText || getTextColor(alphaBlendLayers(colors.panelText, [underlay, bg, panel, panel, alertWarning]), colors.panelText)
-
-  colors.badgeNotification = col.badgeNotification || Object.assign({}, colors.cRed)
-  colors.badgeNotificationText = colors.badgeNotificationText || contrastRatio(colors.badgeNotification).rgb
+      if (value.textColor) {
+        return {
+          ...acc,
+          [key]: getTextColor(
+            alphaBlendLayers(
+              { ...acc[deps[0]] },
+              getLayers(
+                value.layer,
+                value.variant || value.layer,
+                acc,
+                opacity
+              )
+            ),
+            { ...acc[deps[0]] },
+            value.textColor === 'preserve'
+          )
+        }
+      } else {
+        console.log('BENIS', key, deps, deps.map((dep) => ({ ...acc[dep] })))
+        return {
+          ...acc,
+          [key]: colorFunc(
+            mod,
+            ...deps.map((dep) => ({ ...acc[dep] }))
+          )
+        }
+      }
+    }
+  }, {})
 
+  // Inheriting opacities
   Object.entries(opacity).forEach(([ k, v ]) => {
-    console.log(k)
     if (typeof v === 'undefined') return
     if (k === 'alert') {
       colors.alertError.a = v
@@ -285,6 +512,9 @@ const generateColors = (themeData) => {
     if (k === 'bg') {
       colors['lightBg'].a = v
     }
+    if (k === 'badge') {
+      colors['badgeNotification'].a = v
+    }
     if (colors[k]) {
       colors[k].a = v
     } else {

From ae2ae00d415e928922984734deafafe6383ed914 Mon Sep 17 00:00:00 2001
From: Sierra <1271-cdmnky@users.noreply.git.pleroma.social>
Date: Tue, 7 Jan 2020 21:13:51 +0000
Subject: [PATCH 121/483] Add AMOLED dark theme

---
 static/styles.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/static/styles.json b/static/styles.json
index 842092c4..23508970 100644
--- a/static/styles.json
+++ b/static/styles.json
@@ -1,6 +1,7 @@
 {
   "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
   "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
+  "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
   "classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
   "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
   "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],

From 38f2b969e467d1cf47597dc4cc2b958e5df99828 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sat, 11 Jan 2020 23:07:41 +0200
Subject: [PATCH 122/483] simplified definition for text color by accounting
 for layers automatically, fixed badge notification text color by adding 'bw'
 option for textColor

---
 src/services/style_setter/style_setter.js | 123 ++++++++++++----------
 1 file changed, 69 insertions(+), 54 deletions(-)

diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 40a552a1..992b3194 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -65,12 +65,12 @@ export const SLOT_INHERITANCE = {
 
   // Foreground
   fgText: {
-    depends: ['text', 'fg', 'underlay', 'bg'],
+    depends: ['text'],
     layer: 'fg',
     textColor: true
   },
   fgLink: {
-    depends: ['link', 'fg', 'underlay', 'bg'],
+    depends: ['link'],
     layer: 'fg',
     textColor: 'preserve'
   },
@@ -78,17 +78,17 @@ export const SLOT_INHERITANCE = {
   // Panel header
   panel: '--fg',
   panelText: {
-    depends: ['fgText', 'panel'],
+    depends: ['fgText'],
     layer: 'panel',
     textColor: true
   },
   panelFaint: {
-    depends: ['fgText', 'panel'],
+    depends: ['fgText'],
     layer: 'panel',
     textColor: true
   },
   panelLink: {
-    depends: ['fgLink', 'panel'],
+    depends: ['fgLink'],
     layer: 'panel',
     textColor: 'preserve'
   },
@@ -96,12 +96,12 @@ export const SLOT_INHERITANCE = {
   // Top bar
   topBar: '--fg',
   topBarText: {
-    depends: ['fgText', 'topBar'],
+    depends: ['fgText'],
     layer: 'topBar',
     textColor: true
   },
   topBarLink: {
-    depends: ['fgLink', 'topBar'],
+    depends: ['fgLink'],
     layer: 'topBar',
     textColor: 'preserve'
   },
@@ -109,17 +109,17 @@ export const SLOT_INHERITANCE = {
   // Buttons
   btn: '--fg',
   btnText: {
-    depends: ['fgText', 'btn'],
+    depends: ['fgText'],
     layer: 'btn'
   },
   btnPanelText: {
-    depends: ['panelText', 'btn', 'panel'],
+    depends: ['panelText'],
     layer: 'btnPanel',
     variant: 'btn',
     textColor: true
   },
   btnTopBarText: {
-    depends: ['topBarText', 'btn', 'topBar'],
+    depends: ['topBarText'],
     layer: 'btnTopBar',
     variant: 'btn',
     textColor: true
@@ -128,18 +128,18 @@ export const SLOT_INHERITANCE = {
   // Input fields
   input: '--fg',
   inputText: {
-    depends: ['text', 'input'],
+    depends: ['text'],
     layer: 'input',
     textColor: true
   },
   inputPanelText: {
-    depends: ['panelText', 'input', 'panel'],
+    depends: ['panelText'],
     layer: 'inputPanel',
     variant: 'input',
     textColor: true
   },
   inputTopbarText: {
-    depends: ['topBarText', 'input', 'topBar'],
+    depends: ['topBarText'],
     layer: 'inputTopBar',
     variant: 'input',
     textColor: true
@@ -153,7 +153,7 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
   alertErrorPanelText: {
-    depends: ['panelText', 'alertError', 'panel'],
+    depends: ['panelText', 'alertError'],
     layer: 'alertPanel',
     variant: 'alertError',
     textColor: true
@@ -167,7 +167,7 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
   alertWarningPanelText: {
-    depends: ['panelText', 'alertWarning', 'panel'],
+    depends: ['panelText', 'alertWarning'],
     layer: 'alertPanel',
     variant: 'alertWarning',
     textColor: true
@@ -178,23 +178,47 @@ export const SLOT_INHERITANCE = {
     depends: ['text', 'badgeNotification'],
     layer: 'badge',
     variant: 'badgeNotification',
-    textColor: true
+    textColor: 'bw'
   }
 }
 
+export const getLayersArray = (layer, data = LAYERS) => {
+  let array = [layer]
+  let parent = data[layer]
+  while (parent) {
+    array.unshift(parent)
+    parent = data[parent]
+  }
+  return array
+}
+
+export const getLayers = (layer, variant = layer, colors, opacity) => {
+  return getLayersArray(layer).map((currentLayer) => ([
+    currentLayer === layer
+      ? colors[variant]
+      : colors[currentLayer],
+    opacity[currentLayer]
+  ]))
+}
+
 const getDependencies = (key, inheritance) => {
   const data = inheritance[key]
   if (typeof data === 'string' && data.startsWith('--')) {
     return [data.substring(2)]
   } else {
     if (data === null) return []
-    const { depends } = data
+    const { depends, layer, variant } = data
+    const layerDeps = layer
+      ? getLayersArray(layer).map(currentLayer => {
+        return currentLayer === layer
+          ? variant || layer
+          : currentLayer
+      })
+      : []
     if (Array.isArray(depends)) {
-      return depends
-    } else if (typeof depends === 'object') {
-      return [depends]
+      return [...depends, ...layerDeps]
     } else {
-      return []
+      return [...layerDeps]
     }
   }
 }
@@ -241,25 +265,6 @@ export const topoSort = (
 
 export const SLOT_ORDERED = topoSort(SLOT_INHERITANCE)
 
-export const getLayersArray = (layer, data = LAYERS) => {
-  let array = [layer]
-  let parent = data[layer]
-  while (parent) {
-    array.unshift(parent)
-    parent = data[parent]
-  }
-  return array
-}
-
-export const getLayers = (layer, variant = layer, colors, opacity) => {
-  return getLayersArray(layer).map((currentLayer) => ([
-    currentLayer === layer
-      ? colors[variant]
-      : colors[currentLayer],
-    opacity[currentLayer]
-  ]))
-}
-
 // While this is not used anymore right now, I left it in if we want to do custom
 // styles that aren't just colors, so user can pick from a few different distinct
 // styles as well as set their own colors in the future.
@@ -318,6 +323,8 @@ const getTextColor = function (bg, text, preserve) {
   const bgIsLight = convert(bg).hsl.l > 50
   const textIsLight = convert(text).hsl.l > 50
 
+  console.log(bgIsLight, textIsLight)
+
   if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) {
     const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
     const result = Object.assign(base, invertLightness(text).rgb)
@@ -468,21 +475,29 @@ const generateColors = (themeData) => {
       const colorFunc = (isObject && value.color) || defaultColorFunc
 
       if (value.textColor) {
-        return {
-          ...acc,
-          [key]: getTextColor(
-            alphaBlendLayers(
-              { ...acc[deps[0]] },
-              getLayers(
-                value.layer,
-                value.variant || value.layer,
-                acc,
-                opacity
-              )
-            ),
-            { ...acc[deps[0]] },
-            value.textColor === 'preserve'
+        const bg = alphaBlendLayers(
+          { ...acc[deps[0]] },
+          getLayers(
+            value.layer,
+            value.variant || value.layer,
+            acc,
+            opacity
           )
+        )
+        if (value.textColor === 'bw') {
+          return {
+            ...acc,
+            [key]: contrastRatio(bg)
+          }
+        } else {
+          return {
+            ...acc,
+            [key]: getTextColor(
+              bg,
+              { ...acc[deps[0]] },
+              value.textColor === 'preserve'
+            )
+          }
         }
       } else {
         console.log('BENIS', key, deps, deps.map((dep) => ({ ...acc[dep] })))

From 622c9d388e0df1f53c544c34b7def2bb6fe498cd Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 03:44:06 +0200
Subject: [PATCH 123/483] Refactoring, forgotten files

---
 src/components/color_input/color_input.scss   |  65 +++
 src/services/color_convert/color_convert.js   | 101 +++-
 src/services/style_setter/style_setter.js     | 438 ++----------------
 src/services/theme_data/theme_data.service.js | 315 +++++++++++++
 .../style_setter/style_setter.spec.js         |  79 ++++
 5 files changed, 577 insertions(+), 421 deletions(-)
 create mode 100644 src/components/color_input/color_input.scss
 create mode 100644 src/services/theme_data/theme_data.service.js
 create mode 100644 test/unit/specs/services/style_setter/style_setter.spec.js

diff --git a/src/components/color_input/color_input.scss b/src/components/color_input/color_input.scss
new file mode 100644
index 00000000..92bf87c5
--- /dev/null
+++ b/src/components/color_input/color_input.scss
@@ -0,0 +1,65 @@
+@import '../../_variables.scss';
+
+.color-input {
+  display: inline-flex;
+
+  &-field.input {
+    display: inline-flex;
+    flex: 0 0 0;
+    max-width: 9em;
+    align-items: stretch;
+    padding: .2em 8px;
+
+    input {
+      background: none;
+      color: $fallback--lightText;
+      color: var(--inputText, $fallback--lightText);
+      border: none;
+      padding: 0;
+      margin: 0;
+
+      &.textColor {
+        flex: 1 0 3em;
+        min-width: 3em;
+        padding: 0;
+      }
+
+      &.nativeColor {
+        flex: 0 0 2em;
+        min-width: 2em;
+        align-self: center;
+        height: 100%;
+      }
+    }
+    .transparentIndicator {
+      flex: 0 0 2em;
+      min-width: 2em;
+      align-self: center;
+      height: 100%;
+      // forgot to install counter-strike source, ooops
+      background-color: #FF00FF;
+      position: relative;
+      &::before, &::after {
+        display: block;
+        content: '';
+        background-color: #000000;
+        position: absolute;
+        height: 50%;
+        width: 50%;
+      }
+      &::after {
+        top: 0;
+        left: 0;
+      }
+      &::before {
+        bottom: 0;
+        right: 0;
+      }
+    }
+  }
+
+  .label {
+    flex: 1 1 auto;
+  }
+
+}
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 32b4d50e..464f6495 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -1,9 +1,16 @@
-import { map } from 'lodash'
+import { invertLightness, convert, contrastRatio } from 'chromatism'
 
 // useful for visualizing color when debugging
 export const consoleColor = (color) => console.log('%c##########', 'background: ' + color + '; color: ' + color)
 
-const rgb2hex = (r, g, b) => {
+/**
+ * Convert r, g, b values into hex notation. All components are [0-255]
+ *
+ * @param {Number|String|Object} r - Either red component, {r,g,b} object, or hex string
+ * @param {Number} [g] - Green component
+ * @param {Number} [b] - Blue component
+ */
+export const rgb2hex = (r, g, b) => {
   if (r === null || typeof r === 'undefined') {
     return undefined
   }
@@ -14,7 +21,7 @@ const rgb2hex = (r, g, b) => {
   if (typeof r === 'object') {
     ({ r, g, b } = r)
   }
-  [r, g, b] = map([r, g, b], (val) => {
+  [r, g, b] = [r, g, b].map(val => {
     val = Math.ceil(val)
     val = val < 0 ? 0 : val
     val = val > 255 ? 255 : val
@@ -82,6 +89,7 @@ const getContrastRatio = (a, b) => {
 
   return (l1 + 0.05) / (l2 + 0.05)
 }
+
 /**
  * Same as `getContrastRatio` but for multiple layers in-between
  *
@@ -101,7 +109,7 @@ export const getContrastRatioLayers = (text, layers, bedrock) => {
  * @param {Object} bg - bottom layer color
  * @returns {Object} sRGB of resulting color
  */
-const alphaBlend = (fg, fga, bg) => {
+export const alphaBlend = (fg, fga, bg) => {
   if (fga === 1 || typeof fga === 'undefined') return fg
   return 'rgb'.split('').reduce((acc, c) => {
     // Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
@@ -121,14 +129,20 @@ export const alphaBlendLayers = (bedrock, layers) => layers.reduce((acc, [color,
   return alphaBlend(color, opacity, acc)
 }, bedrock)
 
-const invert = (rgb) => {
+export const invert = (rgb) => {
   return 'rgb'.split('').reduce((acc, c) => {
     acc[c] = 255 - rgb[c]
     return acc
   }, {})
 }
 
-const hex2rgb = (hex) => {
+/**
+ * Converts #rrggbb hex notation into an {r, g, b} object
+ *
+ * @param {String} hex - #rrggbb string
+ * @returns {Object} rgb representation of the color, values are 0-255
+ */
+export const hex2rgb = (hex) => {
   const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
   return result ? {
     r: parseInt(result[1], 16),
@@ -137,18 +151,75 @@ const hex2rgb = (hex) => {
   } : null
 }
 
-const mixrgb = (a, b) => {
+/**
+ * Old somewhat weird function for mixing two colors together
+ *
+ * @param {Object} a - one color (rgb)
+ * @param {Object} b - other color (rgb)
+ * @returns {Object} result
+ */
+export const mixrgb = (a, b) => {
   return Object.keys(a).reduce((acc, k) => {
     acc[k] = (a[k] + b[k]) / 2
     return acc
   }, {})
 }
-
-export {
-  rgb2hex,
-  hex2rgb,
-  mixrgb,
-  invert,
-  getContrastRatio,
-  alphaBlend
+/**
+ * Converts rgb object into a CSS rgba() color
+ *
+ * @param {Object} color - rgb
+ * @returns {String} CSS rgba() color
+ */
+export const rgba2css = function (rgba) {
+  return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
+}
+
+/**
+ * Get text color for given background color and intended text color
+ * This checks if text and background don't have enough color and inverts
+ * text color's lightness if needed. If text color is still not enough it
+ * will fall back to black or white
+ *
+ * @param {Object} bg - background color
+ * @param {Object} text - intended text color
+ * @param {Boolean} preserve - try to preserve intended text color's hue/saturation (i.e. no BW)
+ */
+export const getTextColor = function (bg, text, preserve) {
+  const bgIsLight = convert(bg).hsl.l > 50
+  const textIsLight = convert(text).hsl.l > 50
+
+  if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) {
+    const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
+    const result = Object.assign(base, invertLightness(text).rgb)
+    if (!preserve && getContrastRatio(bg, result) < 4.5) {
+      // B&W
+      return contrastRatio(bg, text).rgb
+    }
+    // Inverted color
+    return result
+  }
+  return text
+}
+
+/**
+ * Converts color to CSS Color value
+ *
+ * @param {Object|String} input - color
+ * @param {Number} [a] - alpha value
+ * @returns {String} a CSS Color value
+ */
+export const getCssColor = (input, a) => {
+  let rgb = {}
+  if (typeof input === 'object') {
+    rgb = input
+  } else if (typeof input === 'string') {
+    if (input.startsWith('#')) {
+      rgb = hex2rgb(input)
+    } else if (input.startsWith('--')) {
+      return `var(${input})`
+    } else {
+      return input
+    }
+  }
+  return rgba2css({ ...rgb, a })
 }
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 992b3194..46b08628 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,275 +1,13 @@
 import { times } from 'lodash'
-import { brightness, invertLightness, convert, contrastRatio } from 'chromatism'
-import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend, alphaBlendLayers } from '../color_convert/color_convert.js'
-
-export const CURRENT_VERSION = 3
-/* This is a definition of all layer combinations
- * each key is a topmost layer, each value represents layer underneath
- * this is essentially a simplified tree
- */
-export const LAYERS = {
-  undelay: null, // root
-  topBar: null, // no transparency support
-  badge: null, //  no transparency support
-  fg: null,
-  bg: 'underlay',
-  panel: 'bg',
-  btn: 'bg',
-  btnPanel: 'panel',
-  btnTopBar: 'topBar',
-  input: 'bg',
-  inputPanel: 'panel',
-  inputTopBar: 'topBar',
-  alert: 'bg',
-  alertPanel: 'panel'
-}
-
-export const SLOT_INHERITANCE = {
-  bg: null,
-  fg: null,
-  text: null,
-  underlay: '#000000',
-  link: '--accent',
-  accent: '--link',
-  faint: '--text',
-  faintLink: '--link',
-
-  cBlue: '#0000ff',
-  cRed: '#FF0000',
-  cGreen: '#00FF00',
-  cOrange: '#E3FF00',
-
-  lightBg: {
-    depends: ['bg'],
-    color: (mod, bg) => brightness(5 * mod, bg).rgb
-  },
-  lightText: {
-    depends: ['text'],
-    color: (mod, text) => brightness(20 * mod, text).rgb
-  },
-
-  border: {
-    depends: 'fg',
-    color: (mod, fg) => brightness(2 * mod, fg).rgb
-  },
-
-  linkBg: {
-    depends: ['accent', 'bg'],
-    color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg).rgb
-  },
-
-  icon: {
-    depends: ['bg', 'text'],
-    color: (mod, bg, text) => mixrgb(bg, text)
-  },
-
-  // Foreground
-  fgText: {
-    depends: ['text'],
-    layer: 'fg',
-    textColor: true
-  },
-  fgLink: {
-    depends: ['link'],
-    layer: 'fg',
-    textColor: 'preserve'
-  },
-
-  // Panel header
-  panel: '--fg',
-  panelText: {
-    depends: ['fgText'],
-    layer: 'panel',
-    textColor: true
-  },
-  panelFaint: {
-    depends: ['fgText'],
-    layer: 'panel',
-    textColor: true
-  },
-  panelLink: {
-    depends: ['fgLink'],
-    layer: 'panel',
-    textColor: 'preserve'
-  },
-
-  // Top bar
-  topBar: '--fg',
-  topBarText: {
-    depends: ['fgText'],
-    layer: 'topBar',
-    textColor: true
-  },
-  topBarLink: {
-    depends: ['fgLink'],
-    layer: 'topBar',
-    textColor: 'preserve'
-  },
-
-  // Buttons
-  btn: '--fg',
-  btnText: {
-    depends: ['fgText'],
-    layer: 'btn'
-  },
-  btnPanelText: {
-    depends: ['panelText'],
-    layer: 'btnPanel',
-    variant: 'btn',
-    textColor: true
-  },
-  btnTopBarText: {
-    depends: ['topBarText'],
-    layer: 'btnTopBar',
-    variant: 'btn',
-    textColor: true
-  },
-
-  // Input fields
-  input: '--fg',
-  inputText: {
-    depends: ['text'],
-    layer: 'input',
-    textColor: true
-  },
-  inputPanelText: {
-    depends: ['panelText'],
-    layer: 'inputPanel',
-    variant: 'input',
-    textColor: true
-  },
-  inputTopbarText: {
-    depends: ['topBarText'],
-    layer: 'inputTopBar',
-    variant: 'input',
-    textColor: true
-  },
-
-  alertError: '--cRed',
-  alertErrorText: {
-    depends: ['text', 'alertError'],
-    layer: 'alert',
-    variant: 'alertError',
-    textColor: true
-  },
-  alertErrorPanelText: {
-    depends: ['panelText', 'alertError'],
-    layer: 'alertPanel',
-    variant: 'alertError',
-    textColor: true
-  },
-
-  alertWarning: '--cOrange',
-  alertWarningText: {
-    depends: ['text', 'alertWarning'],
-    layer: 'alert',
-    variant: 'alertWarning',
-    textColor: true
-  },
-  alertWarningPanelText: {
-    depends: ['panelText', 'alertWarning'],
-    layer: 'alertPanel',
-    variant: 'alertWarning',
-    textColor: true
-  },
-
-  badgeNotification: '--cRed',
-  badgeNotificationText: {
-    depends: ['text', 'badgeNotification'],
-    layer: 'badge',
-    variant: 'badgeNotification',
-    textColor: 'bw'
-  }
-}
-
-export const getLayersArray = (layer, data = LAYERS) => {
-  let array = [layer]
-  let parent = data[layer]
-  while (parent) {
-    array.unshift(parent)
-    parent = data[parent]
-  }
-  return array
-}
-
-export const getLayers = (layer, variant = layer, colors, opacity) => {
-  return getLayersArray(layer).map((currentLayer) => ([
-    currentLayer === layer
-      ? colors[variant]
-      : colors[currentLayer],
-    opacity[currentLayer]
-  ]))
-}
-
-const getDependencies = (key, inheritance) => {
-  const data = inheritance[key]
-  if (typeof data === 'string' && data.startsWith('--')) {
-    return [data.substring(2)]
-  } else {
-    if (data === null) return []
-    const { depends, layer, variant } = data
-    const layerDeps = layer
-      ? getLayersArray(layer).map(currentLayer => {
-        return currentLayer === layer
-          ? variant || layer
-          : currentLayer
-      })
-      : []
-    if (Array.isArray(depends)) {
-      return [...depends, ...layerDeps]
-    } else {
-      return [...layerDeps]
-    }
-  }
-}
-
-export const topoSort = (
-  inheritance = SLOT_INHERITANCE,
-  getDeps = getDependencies
-) => {
-  // This is an implementation of https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
-
-  const allKeys = Object.keys(inheritance)
-  const whites = new Set(allKeys)
-  const grays = new Set()
-  const blacks = new Set()
-  const unprocessed = [...allKeys]
-  const output = []
-
-  const step = (node) => {
-    if (whites.has(node)) {
-      // Make node "gray"
-      whites.delete(node)
-      grays.add(node)
-      // Do step for each node connected to it (one way)
-      getDeps(node, inheritance).forEach(step)
-      // Make node "black"
-      grays.delete(node)
-      blacks.add(node)
-      // Put it into the output list
-      output.push(node)
-    } else if (grays.has(node)) {
-      console.debug('Cyclic depenency in topoSort, ignoring')
-      output.push(node)
-    } else if (blacks.has(node)) {
-      // do nothing
-    } else {
-      throw new Error('Unintended condition in topoSort!')
-    }
-  }
-  while (unprocessed.length > 0) {
-    step(unprocessed.pop())
-  }
-  return output
-}
-
-export const SLOT_ORDERED = topoSort(SLOT_INHERITANCE)
+import { convert } from 'chromatism'
+import { rgb2hex, hex2rgb, rgba2css, getCssColor } from '../color_convert/color_convert.js'
+import { getColors } from '../theme_data/theme_data.service.js'
 
 // While this is not used anymore right now, I left it in if we want to do custom
 // styles that aren't just colors, so user can pick from a few different distinct
 // styles as well as set their own colors in the future.
 
-const setStyle = (href, commit) => {
+export const setStyle = (href, commit) => {
   /***
       What's going on here?
       I want to make it easy for admins to style this application. To have
@@ -315,30 +53,7 @@ const setStyle = (href, commit) => {
   cssEl.addEventListener('load', setDynamic)
 }
 
-const rgb2rgba = function (rgba) {
-  return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
-}
-
-const getTextColor = function (bg, text, preserve) {
-  const bgIsLight = convert(bg).hsl.l > 50
-  const textIsLight = convert(text).hsl.l > 50
-
-  console.log(bgIsLight, textIsLight)
-
-  if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) {
-    const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
-    const result = Object.assign(base, invertLightness(text).rgb)
-    if (!preserve && getContrastRatio(bg, result) < 4.5) {
-      // B&W
-      return contrastRatio(bg, text).rgb
-    }
-    // Inverted color
-    return result
-  }
-  return text
-}
-
-const applyTheme = (input, commit) => {
+export const applyTheme = (input, commit) => {
   const { rules, theme } = generatePreset(input)
   const head = document.head
   const body = document.body
@@ -399,22 +114,6 @@ const getCssShadowFilter = (input) => {
     .join(' ')
 }
 
-const getCssColor = (input, a) => {
-  let rgb = {}
-  if (typeof input === 'object') {
-    rgb = input
-  } else if (typeof input === 'string') {
-    if (input.startsWith('#')) {
-      rgb = hex2rgb(input)
-    } else if (input.startsWith('--')) {
-      return `var(${input})`
-    } else {
-      return input
-    }
-  }
-  return rgb2rgba({ ...rgb, a })
-}
-
 const generateColors = (themeData) => {
   const rawOpacity = Object.assign({
     panel: 1,
@@ -435,14 +134,16 @@ const generateColors = (themeData) => {
   }, {}))
 
   const inputColors = themeData.colors || themeData
-  const transparentsOpacity = Object.entries(inputColors).reduce((acc, [k, v]) => {
-    if (v === 'transparent') {
-      acc[k] = 0
-    }
-    return acc
-  }, {})
 
-  const opacity = { ...rawOpacity, ...transparentsOpacity }
+  const opacity = {
+    ...rawOpacity,
+    ...Object.entries(inputColors).reduce((acc, [k, v]) => {
+      if (v === 'transparent') {
+        acc[k] = 0
+      }
+      return acc
+    }, {})
+  }
 
   // Cycle one: just whatever we have
   const sourceColors = Object.entries(inputColors).reduce((acc, [k, v]) => {
@@ -462,55 +163,7 @@ const generateColors = (themeData) => {
   const isLightOnDark = convert(sourceColors.bg).hsl.l < convert(sourceColors.text).hsl.l
   const mod = isLightOnDark ? 1 : -1
 
-  const colors = SLOT_ORDERED.reduce((acc, key) => {
-    const value = SLOT_INHERITANCE[key]
-    if (sourceColors[key]) {
-      return { ...acc, [key]: { ...sourceColors[key] } }
-    } else if (typeof value === 'string' && value.startsWith('#')) {
-      return { ...acc, [key]: convert(value).rgb }
-    } else {
-      const isObject = typeof value === 'object'
-      const defaultColorFunc = (mod, dep) => ({ ...dep })
-      const deps = getDependencies(key, SLOT_INHERITANCE)
-      const colorFunc = (isObject && value.color) || defaultColorFunc
-
-      if (value.textColor) {
-        const bg = alphaBlendLayers(
-          { ...acc[deps[0]] },
-          getLayers(
-            value.layer,
-            value.variant || value.layer,
-            acc,
-            opacity
-          )
-        )
-        if (value.textColor === 'bw') {
-          return {
-            ...acc,
-            [key]: contrastRatio(bg)
-          }
-        } else {
-          return {
-            ...acc,
-            [key]: getTextColor(
-              bg,
-              { ...acc[deps[0]] },
-              value.textColor === 'preserve'
-            )
-          }
-        }
-      } else {
-        console.log('BENIS', key, deps, deps.map((dep) => ({ ...acc[dep] })))
-        return {
-          ...acc,
-          [key]: colorFunc(
-            mod,
-            ...deps.map((dep) => ({ ...acc[dep] }))
-          )
-        }
-      }
-    }
-  }, {})
+  const colors = getColors(sourceColors, opacity, mod)
 
   // Inheriting opacities
   Object.entries(opacity).forEach(([ k, v ]) => {
@@ -541,7 +194,7 @@ const generateColors = (themeData) => {
     .reduce((acc, [k, v]) => {
       if (!v) return acc
       acc.solid[k] = rgb2hex(v)
-      acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgb2rgba(v)
+      acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgba2css(v)
       return acc
     }, { complete: {}, solid: {} })
   return {
@@ -740,14 +393,12 @@ const composePreset = (colors, radii, shadows, fonts) => {
   }
 }
 
-const generatePreset = (input) => {
-  const shadows = generateShadows(input)
-  const colors = generateColors(input)
-  const radii = generateRadii(input)
-  const fonts = generateFonts(input)
-
-  return composePreset(colors, radii, shadows, fonts)
-}
+const generatePreset = (input) => composePreset(
+  generateColors(input),
+  generateRadii(input),
+  generateShadows(input),
+  generateFonts(input)
+)
 
 const getThemes = () => {
   return window.fetch('/static/styles.json')
@@ -779,33 +430,24 @@ const getThemes = () => {
     })
 }
 
-const setPreset = (val, commit) => {
+export const setPreset = (val, commit) => {
   return getThemes().then((themes) => {
     const theme = themes[val] ? themes[val] : themes['pleroma-dark']
     const isV1 = Array.isArray(theme)
     const data = isV1 ? {} : theme.theme
 
     if (isV1) {
-      const bgRgb = hex2rgb(theme[1])
-      const fgRgb = hex2rgb(theme[2])
-      const textRgb = hex2rgb(theme[3])
-      const linkRgb = hex2rgb(theme[4])
+      const bg = hex2rgb(theme[1])
+      const fg = hex2rgb(theme[2])
+      const text = hex2rgb(theme[3])
+      const link = hex2rgb(theme[4])
 
-      const cRedRgb = hex2rgb(theme[5] || '#FF0000')
-      const cGreenRgb = hex2rgb(theme[6] || '#00FF00')
-      const cBlueRgb = hex2rgb(theme[7] || '#0000FF')
-      const cOrangeRgb = hex2rgb(theme[8] || '#E3FF00')
+      const cRed = hex2rgb(theme[5] || '#FF0000')
+      const cGreen = hex2rgb(theme[6] || '#00FF00')
+      const cBlue = hex2rgb(theme[7] || '#0000FF')
+      const cOrange = hex2rgb(theme[8] || '#E3FF00')
 
-      data.colors = {
-        bg: bgRgb,
-        fg: fgRgb,
-        text: textRgb,
-        link: linkRgb,
-        cRed: cRedRgb,
-        cBlue: cBlueRgb,
-        cGreen: cGreenRgb,
-        cOrange: cOrangeRgb
-      }
+      data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
     }
 
     // This is a hack, this function is only called during initial load.
@@ -819,19 +461,3 @@ const setPreset = (val, commit) => {
     }
   })
 }
-
-export {
-  setStyle,
-  setPreset,
-  applyTheme,
-  getTextColor,
-  generateColors,
-  generateRadii,
-  generateShadows,
-  generateFonts,
-  generatePreset,
-  getThemes,
-  composePreset,
-  getCssShadow,
-  getCssShadowFilter
-}
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
new file mode 100644
index 00000000..c9c80727
--- /dev/null
+++ b/src/services/theme_data/theme_data.service.js
@@ -0,0 +1,315 @@
+import { convert, brightness, contrastRatio } from 'chromatism'
+import { alphaBlend, alphaBlendLayers, getTextColor, mixrgb } from '../color_convert/color_convert.js'
+
+export const CURRENT_VERSION = 3
+/* This is a definition of all layer combinations
+ * each key is a topmost layer, each value represents layer underneath
+ * this is essentially a simplified tree
+ */
+export const LAYERS = {
+  undelay: null, // root
+  topBar: null, // no transparency support
+  badge: null, //  no transparency support
+  fg: null,
+  bg: 'underlay',
+  panel: 'bg',
+  btn: 'bg',
+  btnPanel: 'panel',
+  btnTopBar: 'topBar',
+  input: 'bg',
+  inputPanel: 'panel',
+  inputTopBar: 'topBar',
+  alert: 'bg',
+  alertPanel: 'panel'
+}
+
+export const SLOT_INHERITANCE = {
+  bg: null,
+  fg: null,
+  text: null,
+  underlay: '#000000',
+  link: '--accent',
+  accent: '--link',
+  faint: '--text',
+  faintLink: '--link',
+
+  cBlue: '#0000ff',
+  cRed: '#FF0000',
+  cGreen: '#00FF00',
+  cOrange: '#E3FF00',
+
+  lightBg: {
+    depends: ['bg'],
+    color: (mod, bg) => brightness(5 * mod, bg).rgb
+  },
+  lightText: {
+    depends: ['text'],
+    color: (mod, text) => brightness(20 * mod, text).rgb
+  },
+
+  border: {
+    depends: 'fg',
+    color: (mod, fg) => brightness(2 * mod, fg).rgb
+  },
+
+  linkBg: {
+    depends: ['accent', 'bg'],
+    color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg).rgb
+  },
+
+  icon: {
+    depends: ['bg', 'text'],
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
+  // Foreground
+  fgText: {
+    depends: ['text'],
+    layer: 'fg',
+    textColor: true
+  },
+  fgLink: {
+    depends: ['link'],
+    layer: 'fg',
+    textColor: 'preserve'
+  },
+
+  // Panel header
+  panel: '--fg',
+  panelText: {
+    depends: ['fgText'],
+    layer: 'panel',
+    textColor: true
+  },
+  panelFaint: {
+    depends: ['fgText'],
+    layer: 'panel',
+    textColor: true
+  },
+  panelLink: {
+    depends: ['fgLink'],
+    layer: 'panel',
+    textColor: 'preserve'
+  },
+
+  // Top bar
+  topBar: '--fg',
+  topBarText: {
+    depends: ['fgText'],
+    layer: 'topBar',
+    textColor: true
+  },
+  topBarLink: {
+    depends: ['fgLink'],
+    layer: 'topBar',
+    textColor: 'preserve'
+  },
+
+  // Buttons
+  btn: '--fg',
+  btnText: {
+    depends: ['fgText'],
+    layer: 'btn'
+  },
+  btnPanelText: {
+    depends: ['panelText'],
+    layer: 'btnPanel',
+    variant: 'btn',
+    textColor: true
+  },
+  btnTopBarText: {
+    depends: ['topBarText'],
+    layer: 'btnTopBar',
+    variant: 'btn',
+    textColor: true
+  },
+
+  // Input fields
+  input: '--fg',
+  inputText: {
+    depends: ['text'],
+    layer: 'input',
+    textColor: true
+  },
+  inputPanelText: {
+    depends: ['panelText'],
+    layer: 'inputPanel',
+    variant: 'input',
+    textColor: true
+  },
+  inputTopbarText: {
+    depends: ['topBarText'],
+    layer: 'inputTopBar',
+    variant: 'input',
+    textColor: true
+  },
+
+  alertError: '--cRed',
+  alertErrorText: {
+    depends: ['text', 'alertError'],
+    layer: 'alert',
+    variant: 'alertError',
+    textColor: true
+  },
+  alertErrorPanelText: {
+    depends: ['panelText', 'alertError'],
+    layer: 'alertPanel',
+    variant: 'alertError',
+    textColor: true
+  },
+
+  alertWarning: '--cOrange',
+  alertWarningText: {
+    depends: ['text', 'alertWarning'],
+    layer: 'alert',
+    variant: 'alertWarning',
+    textColor: true
+  },
+  alertWarningPanelText: {
+    depends: ['panelText', 'alertWarning'],
+    layer: 'alertPanel',
+    variant: 'alertWarning',
+    textColor: true
+  },
+
+  badgeNotification: '--cRed',
+  badgeNotificationText: {
+    depends: ['text', 'badgeNotification'],
+    layer: 'badge',
+    variant: 'badgeNotification',
+    textColor: 'bw'
+  }
+}
+
+export const getLayersArray = (layer, data = LAYERS) => {
+  let array = [layer]
+  let parent = data[layer]
+  while (parent) {
+    array.unshift(parent)
+    parent = data[parent]
+  }
+  return array
+}
+
+export const getLayers = (layer, variant = layer, colors, opacity) => {
+  return getLayersArray(layer).map((currentLayer) => ([
+    currentLayer === layer
+      ? colors[variant]
+      : colors[currentLayer],
+    opacity[currentLayer]
+  ]))
+}
+
+const getDependencies = (key, inheritance) => {
+  const data = inheritance[key]
+  if (typeof data === 'string' && data.startsWith('--')) {
+    return [data.substring(2)]
+  } else {
+    if (data === null) return []
+    const { depends, layer, variant } = data
+    const layerDeps = layer
+      ? getLayersArray(layer).map(currentLayer => {
+        return currentLayer === layer
+          ? variant || layer
+          : currentLayer
+      })
+      : []
+    if (Array.isArray(depends)) {
+      return [...depends, ...layerDeps]
+    } else {
+      return [...layerDeps]
+    }
+  }
+}
+
+export const topoSort = (
+  inheritance = SLOT_INHERITANCE,
+  getDeps = getDependencies
+) => {
+  // This is an implementation of https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+
+  const allKeys = Object.keys(inheritance)
+  const whites = new Set(allKeys)
+  const grays = new Set()
+  const blacks = new Set()
+  const unprocessed = [...allKeys]
+  const output = []
+
+  const step = (node) => {
+    if (whites.has(node)) {
+      // Make node "gray"
+      whites.delete(node)
+      grays.add(node)
+      // Do step for each node connected to it (one way)
+      getDeps(node, inheritance).forEach(step)
+      // Make node "black"
+      grays.delete(node)
+      blacks.add(node)
+      // Put it into the output list
+      output.push(node)
+    } else if (grays.has(node)) {
+      console.debug('Cyclic depenency in topoSort, ignoring')
+      output.push(node)
+    } else if (blacks.has(node)) {
+      // do nothing
+    } else {
+      throw new Error('Unintended condition in topoSort!')
+    }
+  }
+  while (unprocessed.length > 0) {
+    step(unprocessed.pop())
+  }
+  return output
+}
+
+export const SLOT_ORDERED = topoSort(SLOT_INHERITANCE)
+
+export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce((acc, key) => {
+  const value = SLOT_INHERITANCE[key]
+  if (sourceColors[key]) {
+    return { ...acc, [key]: { ...sourceColors[key] } }
+  } else if (typeof value === 'string' && value.startsWith('#')) {
+    return { ...acc, [key]: convert(value).rgb }
+  } else {
+    const isObject = typeof value === 'object'
+    const defaultColorFunc = (mod, dep) => ({ ...dep })
+    const deps = getDependencies(key, SLOT_INHERITANCE)
+    const colorFunc = (isObject && value.color) || defaultColorFunc
+
+    if (value.textColor) {
+      const bg = alphaBlendLayers(
+        { ...acc[deps[0]] },
+        getLayers(
+          value.layer,
+          value.variant || value.layer,
+          acc,
+          sourceOpacity
+        )
+      )
+      if (value.textColor === 'bw') {
+        return {
+          ...acc,
+          [key]: contrastRatio(bg)
+        }
+      } else {
+        return {
+          ...acc,
+          [key]: getTextColor(
+            bg,
+            { ...acc[deps[0]] },
+            value.textColor === 'preserve'
+          )
+        }
+      }
+    } else {
+      console.log('BENIS', key, deps, deps.map((dep) => ({ ...acc[dep] })))
+      return {
+        ...acc,
+        [key]: colorFunc(
+          mod,
+          ...deps.map((dep) => ({ ...acc[dep] }))
+        )
+      }
+    }
+  }
+}, {})
diff --git a/test/unit/specs/services/style_setter/style_setter.spec.js b/test/unit/specs/services/style_setter/style_setter.spec.js
new file mode 100644
index 00000000..7f789124
--- /dev/null
+++ b/test/unit/specs/services/style_setter/style_setter.spec.js
@@ -0,0 +1,79 @@
+import { getLayersArray, topoSort } from 'src/services/style_setter/style_setter'
+
+describe('getLayersArray', () => {
+  const fixture = {
+    layer1: null,
+    layer2: 'layer1',
+    layer3a: 'layer2',
+    layer3b: 'layer2'
+  }
+
+  it('should expand layers properly (3b)', () => {
+    const out = getLayersArray('layer3b', fixture)
+    expect(out).to.eql(['layer1', 'layer2', 'layer3b'])
+  })
+
+  it('should expand layers properly (3a)', () => {
+    const out = getLayersArray('layer3a', fixture)
+    expect(out).to.eql(['layer1', 'layer2', 'layer3a'])
+  })
+
+  it('should expand layers properly (2)', () => {
+    const out = getLayersArray('layer2', fixture)
+    expect(out).to.eql(['layer1', 'layer2'])
+  })
+
+  it('should expand layers properly (1)', () => {
+    const out = getLayersArray('layer1', fixture)
+    expect(out).to.eql(['layer1'])
+  })
+})
+
+describe('topoSort', () => {
+  const fixture1 = {
+    layerA: [],
+    layer1A: ['layerA'],
+    layer2A: ['layer1A'],
+    layerB: [],
+    layer1B: ['layerB'],
+    layer2B: ['layer1B'],
+    layer3AB: ['layer2B', 'layer2A']
+  }
+
+  // Same thing but messed up order
+  const fixture2 = {
+    layer1A: ['layerA'],
+    layer1B: ['layerB'],
+    layer2A: ['layer1A'],
+    layerB: [],
+    layer3AB: ['layer2B', 'layer2A'],
+    layer2B: ['layer1B'],
+    layerA: []
+  }
+
+  it('should make a topologically sorted array', () => {
+    const out = topoSort(fixture1, (node, inheritance) => inheritance[node])
+    // This basically checks all ordering that matters
+    expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A'))
+    expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A'))
+    expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B'))
+    expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B'))
+    expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB'))
+    expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB'))
+  })
+
+  it('order in object shouldn\'t matter', () => {
+    const out = topoSort(fixture2, (node, inheritance) => inheritance[node])
+    // This basically checks all ordering that matters
+    expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A'))
+    expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A'))
+    expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B'))
+    expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B'))
+    expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB'))
+    expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB'))
+  })
+  it('ignores cyclic dependencies', () => {
+    const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => inheritance[node])
+    expect(out.indexOf('a')).to.be.below(out.indexOf('c'))
+  })
+})

From f31ed7e5a8a9248886837311d7407b01164724c1 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 03:53:08 +0200
Subject: [PATCH 124/483] remove snapshot data from breezy

---
 static/themes/breezy-dark.json  | 210 --------------------------------
 static/themes/breezy-light.json | 210 --------------------------------
 2 files changed, 420 deletions(-)

diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index d447005f..580ce82c 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -1,216 +1,6 @@
 {
   "_pleroma_theme_version": 2,
   "name": "Breezy Dark (beta)",
-  "theme": {
-    "shadows": {
-      "panel": [
-        {
-          "x": "1",
-          "y": "2",
-          "blur": "6",
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.6
-        }
-      ],
-      "topBar": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": 4,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.6
-        }
-      ],
-      "popup": [
-        {
-          "x": 2,
-          "y": 2,
-          "blur": 3,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.5
-        }
-      ],
-      "avatar": [
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 8,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.7
-        }
-      ],
-      "avatarStatus": [],
-      "panelHeader": [
-        {
-          "x": 0,
-          "y": "40",
-          "blur": "40",
-          "spread": "-40",
-          "inset": true,
-          "color": "#ffffff",
-          "alpha": "0.1"
-        }
-      ],
-      "button": [
-        {
-          "x": 0,
-          "y": "0",
-          "blur": "0",
-          "spread": "1",
-          "color": "#ffffff",
-          "alpha": "0.15",
-          "inset": true
-        },
-        {
-          "x": "1",
-          "y": "1",
-          "blur": "1",
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.3",
-          "inset": false
-        }
-      ],
-      "buttonHover": [
-        {
-          "x": 0,
-          "y": "0",
-          "blur": 0,
-          "spread": "1",
-          "color": "--accent",
-          "alpha": "0.3",
-          "inset": true
-        },
-        {
-          "x": "1",
-          "y": "1",
-          "blur": "1",
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.3",
-          "inset": false
-        }
-      ],
-      "buttonPressed": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": "0",
-          "spread": "50",
-          "color": "--faint",
-          "alpha": 1,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": "0",
-          "blur": 0,
-          "spread": "1",
-          "color": "#ffffff",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": "1",
-          "y": "1",
-          "blur": 0,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.3",
-          "inset": false
-        }
-      ],
-      "input": [
-        {
-          "x": 0,
-          "y": "0",
-          "blur": 0,
-          "spread": "1",
-          "color": "#FFFFFF",
-          "alpha": "0.2",
-          "inset": true
-        }
-      ]
-    },
-    "colors": {
-      "bg": "#31363b",
-      "underlay": "#000000",
-      "text": "#eff0f1",
-      "lightText": "#ffffff",
-      "accent": "#3daee9",
-      "link": "#3daee9",
-      "faint": "#eff0f1",
-      "lightBg": "#3d4349",
-      "fg": "#31363b",
-      "fgText": "#eff0f1",
-      "fgLink": "#3daee9",
-      "border": "#4c545b",
-      "btn": "#31363b",
-      "btnText": "#eff0f1",
-      "input": "#232629",
-      "inputText": "#ffffff",
-      "panel": "#ff00ff",
-      "panelText": "#eff0f1",
-      "panelLink": "#3daee9",
-      "panelFaint": "#eff0f1",
-      "topBar": "#31363b",
-      "topBarText": "#eff0f1",
-      "topBarLink": "#eff0f1",
-      "faintLink": "#3daee9",
-      "linkBg": "#366681",
-      "icon": "#909396",
-      "cBlue": "#3daee9",
-      "cRed": "#da4453",
-      "cGreen": "#27ae60",
-      "cOrange": "#f67400",
-      "alertError": "#da4453",
-      "alertErrorText": "#eff0f1",
-      "alertErrorPanelText": "#eff0f1",
-      "alertWarning": "#f67400",
-      "alertWarningText": "#eff0f1",
-      "alertWarningPanelText": "#eff0f1",
-      "badgeNotification": "#da4453",
-      "badgeNotificationText": "#ffffff"
-    },
-    "opacity": {
-      "panel": 0,
-      "btn": 1,
-      "border": 1,
-      "bg": 1,
-      "alert": 0.5,
-      "input": 0.5,
-      "faint": 0.5,
-      "underlay": 0.15
-    },
-    "radii": {
-      "btn": "2",
-      "input": "2",
-      "checkbox": "1",
-      "panel": "2",
-      "avatar": "2",
-      "avatarAlt": "2",
-      "tooltip": "2",
-      "attachment": "2"
-    },
-    "fonts": {
-      "interface": {
-        "family": "sans-serif"
-      },
-      "input": {
-        "family": "inherit"
-      },
-      "post": {
-        "family": "inherit"
-      },
-      "postCode": {
-        "family": "monospace"
-      }
-    }
-  },
   "source": {
     "themeEngineVersion": 3,
     "fonts": {},
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index 243b8593..0c85a3c9 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -1,216 +1,6 @@
 {
   "_pleroma_theme_version": 2,
   "name": "Breezy Light (beta)",
-  "theme": {
-    "shadows": {
-      "panel": [
-        {
-          "x": "1",
-          "y": "2",
-          "blur": "6",
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.6
-        }
-      ],
-      "topBar": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": 4,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.6
-        }
-      ],
-      "popup": [
-        {
-          "x": 2,
-          "y": 2,
-          "blur": 3,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.5
-        }
-      ],
-      "avatar": [
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 8,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.7
-        }
-      ],
-      "avatarStatus": [],
-      "panelHeader": [
-        {
-          "x": 0,
-          "y": "40",
-          "blur": "40",
-          "spread": "-40",
-          "inset": true,
-          "color": "#ffffff",
-          "alpha": "0.1"
-        }
-      ],
-      "button": [
-        {
-          "x": 0,
-          "y": "0",
-          "blur": "0",
-          "spread": "1",
-          "color": "#000000",
-          "alpha": "0.3",
-          "inset": true
-        },
-        {
-          "x": "1",
-          "y": "1",
-          "blur": "1",
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.3",
-          "inset": false
-        }
-      ],
-      "buttonHover": [
-        {
-          "x": 0,
-          "y": "0",
-          "blur": 0,
-          "spread": "1",
-          "color": "--accent",
-          "alpha": "0.3",
-          "inset": true
-        },
-        {
-          "x": "1",
-          "y": "1",
-          "blur": "1",
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.3",
-          "inset": false
-        }
-      ],
-      "buttonPressed": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": "0",
-          "spread": "50",
-          "color": "--faint",
-          "alpha": 1,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": "0",
-          "blur": 0,
-          "spread": "1",
-          "color": "#ffffff",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": "1",
-          "y": "1",
-          "blur": 0,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.3",
-          "inset": false
-        }
-      ],
-      "input": [
-        {
-          "x": 0,
-          "y": "0",
-          "blur": 0,
-          "spread": "1",
-          "color": "#000000",
-          "alpha": "0.2",
-          "inset": true
-        }
-      ]
-    },
-    "colors": {
-      "bg": "#eff0f1",
-      "underlay": "#000000",
-      "text": "#232627",
-      "lightText": "#000000",
-      "accent": "#2980b9",
-      "link": "#2980b9",
-      "faint": "#232627",
-      "lightBg": "#e2e4e6",
-      "fg": "#bcc2c7",
-      "fgText": "#232627",
-      "fgLink": "#2980b9",
-      "border": "#b7bdc3",
-      "btn": "#eff0f1",
-      "btnText": "#232627",
-      "input": "#fcfcfc",
-      "inputText": "#000000",
-      "panel": "#475057",
-      "panelText": "#fcfcfc",
-      "panelLink": "#ffffff",
-      "panelFaint": "#d8dbdc",
-      "topBar": "#475057",
-      "topBarText": "#d8dbdc",
-      "topBarLink": "#eff0f1",
-      "faintLink": "#2980b9",
-      "linkBg": "#a0c4db",
-      "icon": "#898b8c",
-      "cBlue": "#2980b9",
-      "cRed": "#da4453",
-      "cGreen": "#27ae60",
-      "cOrange": "#f67400",
-      "alertError": "#da4453",
-      "alertErrorText": "#232627",
-      "alertErrorPanelText": "#fcfcfc",
-      "alertWarning": "#f67400",
-      "alertWarningText": "#232627",
-      "alertWarningPanelText": "#fcfcfc",
-      "badgeNotification": "#da4453",
-      "badgeNotificationText": "#ffffff"
-    },
-    "opacity": {
-      "panel": 1,
-      "btn": 1,
-      "border": 1,
-      "bg": 1,
-      "alert": 0.5,
-      "input": "1",
-      "faint": 0.5,
-      "underlay": 0.15
-    },
-    "radii": {
-      "btn": "2",
-      "input": "2",
-      "checkbox": "1",
-      "panel": "2",
-      "avatar": "2",
-      "avatarAlt": "2",
-      "tooltip": "2",
-      "attachment": "2"
-    },
-    "fonts": {
-      "interface": {
-        "family": "sans-serif"
-      },
-      "input": {
-        "family": "inherit"
-      },
-      "post": {
-        "family": "inherit"
-      },
-      "postCode": {
-        "family": "monospace"
-      }
-    }
-  },
   "source": {
     "themeEngineVersion": 3,
     "fonts": {},

From a9a1fc37f546840f3d0fbaed493b131cb9c60669 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 04:00:41 +0200
Subject: [PATCH 125/483] fixes, cleanup

---
 src/components/style_switcher/style_switcher.js | 13 +++++++++----
 src/services/style_setter/style_setter.js       | 15 ++++++++-------
 2 files changed, 17 insertions(+), 11 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index f751260a..4749d3f7 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -1,15 +1,20 @@
-import { rgb2hex, hex2rgb, getContrastRatio, getContrastRatioLayers, alphaBlend } from '../../services/color_convert/color_convert.js'
 import { set, delete as del } from 'vue'
-import { merge } from 'lodash'
+import {
+  rgb2hex,
+  hex2rgb,
+  getContrastRatio,
+  getContrastRatioLayers,
+  alphaBlend
+} from '../../services/color_convert/color_convert.js'
 import {
   generateColors,
   generateShadows,
   generateRadii,
   generateFonts,
   composePreset,
-  getThemes,
-  CURRENT_VERSION
+  getThemes
 } from '../../services/style_setter/style_setter.js'
+import { CURRENT_VERSION } from '../../services/theme_data/theme_data.service.js'
 import ColorInput from '../color_input/color_input.vue'
 import RangeInput from '../range_input/range_input.vue'
 import OpacityInput from '../opacity_input/opacity_input.vue'
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 46b08628..516fb5f6 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -114,7 +114,7 @@ const getCssShadowFilter = (input) => {
     .join(' ')
 }
 
-const generateColors = (themeData) => {
+export const generateColors = (themeData) => {
   const rawOpacity = Object.assign({
     panel: 1,
     btn: 1,
@@ -182,6 +182,7 @@ const generateColors = (themeData) => {
     }
     if (k === 'badge') {
       colors['badgeNotification'].a = v
+      return
     }
     if (colors[k]) {
       colors[k].a = v
@@ -211,7 +212,7 @@ const generateColors = (themeData) => {
   }
 }
 
-const generateRadii = (input) => {
+export const generateRadii = (input) => {
   let inputRadii = input.radii || {}
   // v1 -> v2
   if (typeof input.btnRadius !== 'undefined') {
@@ -244,7 +245,7 @@ const generateRadii = (input) => {
   }
 }
 
-const generateFonts = (input) => {
+export const generateFonts = (input) => {
   const fonts = Object.entries(input.fonts || {}).filter(([k, v]) => v).reduce((acc, [k, v]) => {
     acc[k] = Object.entries(v).filter(([k, v]) => v).reduce((acc, [k, v]) => {
       acc[k] = v
@@ -279,7 +280,7 @@ const generateFonts = (input) => {
   }
 }
 
-const generateShadows = (input) => {
+export const generateShadows = (input) => {
   const border = (top, shadow) => ({
     x: 0,
     y: top ? 1 : -1,
@@ -376,7 +377,7 @@ const generateShadows = (input) => {
   }
 }
 
-const composePreset = (colors, radii, shadows, fonts) => {
+export const composePreset = (colors, radii, shadows, fonts) => {
   return {
     rules: {
       ...shadows.rules,
@@ -393,14 +394,14 @@ const composePreset = (colors, radii, shadows, fonts) => {
   }
 }
 
-const generatePreset = (input) => composePreset(
+export const generatePreset = (input) => composePreset(
   generateColors(input),
   generateRadii(input),
   generateShadows(input),
   generateFonts(input)
 )
 
-const getThemes = () => {
+export const getThemes = () => {
   return window.fetch('/static/styles.json')
     .then((data) => data.json())
     .then((themes) => {

From 1aea1f217e150f36020ab1902ee959c15775d299 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 04:01:31 +0200
Subject: [PATCH 126/483] remove debug

---
 src/services/theme_data/theme_data.service.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index c9c80727..a4df97c2 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -302,7 +302,6 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
         }
       }
     } else {
-      console.log('BENIS', key, deps, deps.map((dep) => ({ ...acc[dep] })))
       return {
         ...acc,
         [key]: colorFunc(

From d342f32a94478bb6dc7fd356798d67179c811083 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 04:05:26 +0200
Subject: [PATCH 127/483] fix

---
 src/services/color_convert/color_convert.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 464f6495..576bf902 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -82,7 +82,7 @@ const relativeLuminance = (srgb) => {
  * @param {Object} b - sRGB color
  * @returns {Number} color ratio
  */
-const getContrastRatio = (a, b) => {
+export const getContrastRatio = (a, b) => {
   const la = relativeLuminance(a)
   const lb = relativeLuminance(b)
   const [l1, l2] = la > lb ? [la, lb] : [lb, la]

From 4418baf62aa7a8bef05568e7590b9b4303c29dc7 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 04:16:04 +0200
Subject: [PATCH 128/483] fix

---
 src/components/style_switcher/style_switcher.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index f993e070..82b85bd5 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -27,8 +27,8 @@
                     :key="style.name"
                     :value="style"
                     :style="{
-                      backgroundColor: style[1] || style.theme.colors.bg,
-                      color: style[3] || style.theme.colors.text
+                      backgroundColor: style[1] || (style.theme || style.source).colors.bg,
+                      color: style[3] || (style.theme || style.source).colors.text
                     }"
                   >
                     {{ style[0] || style.name }}

From d52d1812273ff21bc0436dd261e8934c6010d52f Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 15:04:05 +0200
Subject: [PATCH 129/483] more fixes

---
 src/components/shadow_control/shadow_control.js | 3 +--
 src/services/theme_data/theme_data.service.js   | 2 +-
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js
index 44e4a22f..7e82b9c0 100644
--- a/src/components/shadow_control/shadow_control.js
+++ b/src/components/shadow_control/shadow_control.js
@@ -1,7 +1,6 @@
 import ColorInput from '../color_input/color_input.vue'
 import OpacityInput from '../opacity_input/opacity_input.vue'
-import { getCssShadow } from '../../services/style_setter/style_setter.js'
-import { hex2rgb } from '../../services/color_convert/color_convert.js'
+import { hex2rgb, getCssShadow } from '../../services/color_convert/color_convert.js'
 
 export default {
   // 'Value' and 'Fallback' can be undefined, but if they are
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index a4df97c2..1117ab05 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -48,7 +48,7 @@ export const SLOT_INHERITANCE = {
   },
 
   border: {
-    depends: 'fg',
+    depends: ['fg'],
     color: (mod, fg) => brightness(2 * mod, fg).rgb
   },
 

From 88f83fc9fa3652efdbe6aa622d3e0089883e8057 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 17:46:07 +0200
Subject: [PATCH 130/483] overhaul how style-switcher makes state, removed tons
 of copy-paste

---
 .../style_switcher/style_switcher.js          | 208 +++++-------------
 .../style_switcher/style_switcher.vue         |  12 +
 src/services/style_setter/style_setter.js     |  15 +-
 src/services/theme_data/theme_data.service.js |  15 +-
 4 files changed, 82 insertions(+), 168 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 4749d3f7..9c6f3266 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -14,7 +14,12 @@ import {
   composePreset,
   getThemes
 } from '../../services/style_setter/style_setter.js'
-import { CURRENT_VERSION } from '../../services/theme_data/theme_data.service.js'
+import {
+  CURRENT_VERSION,
+  SLOT_INHERITANCE,
+  DEFAULT_OPACITY,
+  getLayers
+} from '../../services/theme_data/theme_data.service.js'
 import ColorInput from '../color_input/color_input.vue'
 import RangeInput from '../range_input/range_input.vue'
 import OpacityInput from '../opacity_input/opacity_input.vue'
@@ -67,55 +72,13 @@ export default {
       keepRoundness: false,
       keepFonts: false,
 
-      textColorLocal: '',
-      accentColorLocal: undefined,
-      linkColorLocal: undefined,
+      ...Object.keys(SLOT_INHERITANCE)
+        .map(key => [key, ''])
+        .reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}),
 
-      bgColorLocal: '',
-      bgOpacityLocal: undefined,
-
-      underlayColorLocal: '',
-      underlayOpacityLocal: undefined,
-
-      fgColorLocal: '',
-      fgTextColorLocal: undefined,
-      fgLinkColorLocal: undefined,
-
-      btnColorLocal: undefined,
-      btnTextColorLocal: undefined,
-      btnOpacityLocal: undefined,
-
-      inputColorLocal: undefined,
-      inputTextColorLocal: undefined,
-      inputOpacityLocal: undefined,
-
-      panelColorLocal: undefined,
-      panelTextColorLocal: undefined,
-      panelLinkColorLocal: undefined,
-      panelFaintColorLocal: undefined,
-      panelOpacityLocal: undefined,
-
-      topBarColorLocal: undefined,
-      topBarTextColorLocal: undefined,
-      topBarLinkColorLocal: undefined,
-
-      alertErrorColorLocal: undefined,
-      alertWarningColorLocal: undefined,
-
-      badgeOpacityLocal: undefined,
-      badgeNotificationColorLocal: undefined,
-
-      borderColorLocal: undefined,
-      borderOpacityLocal: undefined,
-
-      faintColorLocal: undefined,
-      faintOpacityLocal: undefined,
-      faintLinkColorLocal: undefined,
-
-      cRedColorLocal: '',
-      cBlueColorLocal: '',
-      cGreenColorLocal: '',
-      cOrangeColorLocal: '',
+      ...Object.keys(DEFAULT_OPACITY)
+        .map(key => [key, undefined])
+        .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}),
 
       shadowSelected: undefined,
       shadowsLocal: {},
@@ -149,59 +112,14 @@ export default {
       return Array.isArray(this.selected) ? 1 : 2
     },
     currentColors () {
-      return {
-        bg: this.bgColorLocal,
-        text: this.textColorLocal,
-        link: this.linkColorLocal,
-
-        fg: this.fgColorLocal,
-        fgText: this.fgTextColorLocal,
-        fgLink: this.fgLinkColorLocal,
-
-        accent: this.accentColorLocal,
-
-        underlay: this.underlayColorLocal,
-
-        panel: this.panelColorLocal,
-        panelText: this.panelTextColorLocal,
-        panelLink: this.panelLinkColorLocal,
-        panelFaint: this.panelFaintColorLocal,
-
-        input: this.inputColorLocal,
-        inputText: this.inputTextColorLocal,
-
-        topBar: this.topBarColorLocal,
-        topBarText: this.topBarTextColorLocal,
-        topBarLink: this.topBarLinkColorLocal,
-
-        btn: this.btnColorLocal,
-        btnText: this.btnTextColorLocal,
-
-        alertError: this.alertErrorColorLocal,
-        alertWarning: this.alertWarningColorLocal,
-        badgeNotification: this.badgeNotificationColorLocal,
-
-        faint: this.faintColorLocal,
-        faintLink: this.faintLinkColorLocal,
-        border: this.borderColorLocal,
-
-        cRed: this.cRedColorLocal,
-        cBlue: this.cBlueColorLocal,
-        cGreen: this.cGreenColorLocal,
-        cOrange: this.cOrangeColorLocal
-      }
+      return Object.keys(SLOT_INHERITANCE)
+        .map(key => [key, this[key + 'ColorLocal']])
+        .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
     },
     currentOpacity () {
-      return {
-        bg: this.bgOpacityLocal,
-        btn: this.btnOpacityLocal,
-        input: this.inputOpacityLocal,
-        panel: this.panelOpacityLocal,
-        topBar: this.topBarOpacityLocal,
-        border: this.borderOpacityLocal,
-        faint: this.faintOpacityLocal,
-        underlay: this.underlayOpacityLocal
-      }
+      return Object.keys(DEFAULT_OPACITY)
+        .map(key => [key, this[key + 'OpacityLocal']])
+        .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {})
     },
     currentRadii () {
       return {
@@ -237,63 +155,45 @@ export default {
         laa: ratio >= 3,
         laaa: ratio >= 4.5
       })
+      const colorsConverted = Object.entries(colors).reduce((acc, [key, value]) => ({ ...acc, [key]: colorConvert(value) }), {})
 
-      // fgsfds :DDDD
-      const fgs = {
-        text: colorConvert(colors.text),
-        panelText: colorConvert(colors.panelText),
-        panelLink: colorConvert(colors.panelLink),
-        btnText: colorConvert(colors.btnText),
-        topBarText: colorConvert(colors.topBarText),
-        inputText: colorConvert(colors.inputText),
+      const ratios = Object.entries(SLOT_INHERITANCE).reduce((acc, [key, value]) => {
+        const slotIsBaseText = key === 'text' || key === 'link'
+        const slotIsText = slotIsBaseText || (
+          typeof value === 'object' && value !== null && value.textColor
+        )
+        if (!slotIsText) return acc
+        const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
+        const background = variant || layer
+        const textColors = [
+          key,
+          ...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : [])
+        ]
 
-        link: colorConvert(colors.link),
-        topBarLink: colorConvert(colors.topBarLink),
+        const layers = getLayers(
+          layer,
+          variant || layer,
+          colorsConverted,
+          opacity
+        )
 
-        red: colorConvert(colors.cRed),
-        green: colorConvert(colors.cGreen),
-        blue: colorConvert(colors.cBlue),
-        orange: colorConvert(colors.cOrange)
-      }
-
-      const bgs = {
-        bg: colorConvert(colors.bg),
-        underlay: colorConvert(colors.underlay),
-        btn: colorConvert(colors.btn),
-        panel: colorConvert(colors.panel),
-        topBar: colorConvert(colors.topBar),
-        input: colorConvert(colors.input),
-        alertError: colorConvert(colors.alertError),
-        alertWarning: colorConvert(colors.alertWarning),
-        badgeNotification: colorConvert(colors.badgeNotification)
-      }
-
-      const bg = [bgs.bg, opacity.bg]
-      const underlay = [bgs.underlay || colorConvert('#000000'), opacity.underlay]
-
-      const panel = [underlay, bg]
-
-      const ratios = {
-        bgText: getContrastRatioLayers(fgs.text, panel, fgs.text),
-        bgLink: getContrastRatioLayers(fgs.link, panel, fgs.link),
-        bgRed: getContrastRatioLayers(fgs.red, panel, fgs.red),
-        bgGreen: getContrastRatioLayers(fgs.green, panel, fgs.green),
-        bgBlue: getContrastRatioLayers(fgs.blue, panel, fgs.blue),
-        bgOrange: getContrastRatioLayers(fgs.orange, panel, fgs.orange),
-
-        // TODO what's this?
-        tintText: getContrastRatio(alphaBlend(bgs.bg, 0.5, fgs.panelText), fgs.text),
-
-        panelText: getContrastRatioLayers(fgs.text, [...panel, [bgs.panel, opacity.panel]], fgs.panelText),
-        panelLink: getContrastRatioLayers(fgs.link, [...panel, [bgs.panel, opacity.panel]], fgs.panelLink),
-
-        btnText: getContrastRatioLayers(fgs.text, [...panel, [bgs.btn, opacity.btn]], fgs.btnText),
-
-        inputText: getContrastRatioLayers(fgs.text, [...panel, [bgs.input, opacity.input]], fgs.inputText),
-
-        topBarText: getContrastRatioLayers(fgs.text, [...panel, [bgs.topBar, opacity.topBar]], fgs.topBarText),
-        topBarLink: getContrastRatioLayers(fgs.link, [...panel, [bgs.topBar, opacity.topBar]], fgs.topBarLink)
-      }
+        return {
+          ...acc,
+          ...textColors.reduce((acc, textColorKey) => {
+            const newKey = slotIsBaseText
+              ? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1)
+              : textColorKey
+            return {
+              ...acc,
+              [newKey]: getContrastRatioLayers(
+                colorsConverted[textColorKey],
+                layers,
+                colorsConverted[textColorKey]
+              )
+            }
+          }, {})
+        }
+      }, {})
 
       return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
     },
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 82b85bd5..b059eb8a 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -209,6 +209,12 @@
               :label="$t('settings.style.advanced_colors.alert_error')"
               :fallback="previewTheme.colors.alertError"
             />
+            <ColorInput
+              v-model="alertErrorTextColorLocal"
+              name="alertErrorText"
+              :label="$t('settings.text')"
+              :fallback="previewTheme.colors.alertErrorText"
+            />
             <ContrastRatio :contrast="previewContrast.alertError" />
             <ColorInput
               v-model="alertWarningColorLocal"
@@ -217,6 +223,12 @@
               :fallback="previewTheme.colors.alertWarning"
             />
             <ContrastRatio :contrast="previewContrast.alertWarning" />
+            <ColorInput
+              v-model="alertWarningTextColorLocal"
+              name="alertWarningText"
+              :label="$t('settings.text')"
+              :fallback="previewTheme.colors.alertWarningText"
+            />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 516fb5f6..e11516c0 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,7 +1,7 @@
 import { times } from 'lodash'
 import { convert } from 'chromatism'
 import { rgb2hex, hex2rgb, rgba2css, getCssColor } from '../color_convert/color_convert.js'
-import { getColors } from '../theme_data/theme_data.service.js'
+import { getColors, DEFAULT_OPACITY } from '../theme_data/theme_data.service.js'
 
 // While this is not used anymore right now, I left it in if we want to do custom
 // styles that aren't just colors, so user can pick from a few different distinct
@@ -115,18 +115,7 @@ const getCssShadowFilter = (input) => {
 }
 
 export const generateColors = (themeData) => {
-  const rawOpacity = Object.assign({
-    panel: 1,
-    btn: 1,
-    border: 1,
-    bg: 1,
-    badge: 1,
-    text: 1,
-    alert: 0.5,
-    input: 0.5,
-    faint: 0.5,
-    underlay: 0.15
-  }, Object.entries(themeData.opacity || {}).reduce((acc, [k, v]) => {
+  const rawOpacity = Object.assign({ ...DEFAULT_OPACITY }, Object.entries(themeData.opacity || {}).reduce((acc, [k, v]) => {
     if (typeof v !== 'undefined') {
       acc[k] = v
     }
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 1117ab05..297d0223 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -23,6 +23,19 @@ export const LAYERS = {
   alertPanel: 'panel'
 }
 
+export const DEFAULT_OPACITY = {
+  panel: 1,
+  btn: 1,
+  border: 1,
+  bg: 1,
+  badge: 1,
+  text: 1,
+  alert: 0.5,
+  input: 0.5,
+  faint: 0.5,
+  underlay: 0.15
+}
+
 export const SLOT_INHERITANCE = {
   bg: null,
   fg: null,
@@ -289,7 +302,7 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
       if (value.textColor === 'bw') {
         return {
           ...acc,
-          [key]: contrastRatio(bg)
+          [key]: contrastRatio(bg).rgb
         }
       } else {
         return {

From 39dd08e69423722a172bcc3cec3791ae172a8175 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 17:58:26 +0200
Subject: [PATCH 131/483] replace hsl's l with relative luminance for better
 lightness detection

---
 src/components/style_switcher/style_switcher.js | 5 +----
 src/i18n/en.json                                | 3 ++-
 src/services/color_convert/color_convert.js     | 6 +++---
 src/services/theme_data/theme_data.service.js   | 8 ++++----
 4 files changed, 10 insertions(+), 12 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 9c6f3266..49b34405 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -2,9 +2,7 @@ import { set, delete as del } from 'vue'
 import {
   rgb2hex,
   hex2rgb,
-  getContrastRatio,
-  getContrastRatioLayers,
-  alphaBlend
+  getContrastRatioLayers
 } from '../../services/color_convert/color_convert.js'
 import {
   generateColors,
@@ -265,7 +263,6 @@ export default {
 
       const theme = this.previewTheme
 
-      console.log(source)
       return {
         // To separate from other random JSON files and possible future source formats
         _pleroma_theme_version: 2, theme, source
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 323813f6..d97b4909 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -430,7 +430,8 @@
         "borders": "Borders",
         "buttons": "Buttons",
         "inputs": "Input fields",
-        "faint_text": "Faded text"
+        "faint_text": "Faded text",
+        "underlay": "Underlay"
       },
       "radii": {
         "_tab_label": "Roundness"
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 576bf902..c727a9fe 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -1,4 +1,4 @@
-import { invertLightness, convert, contrastRatio } from 'chromatism'
+import { invertLightness, contrastRatio } from 'chromatism'
 
 // useful for visualizing color when debugging
 export const consoleColor = (color) => console.log('%c##########', 'background: ' + color + '; color: ' + color)
@@ -185,8 +185,8 @@ export const rgba2css = function (rgba) {
  * @param {Boolean} preserve - try to preserve intended text color's hue/saturation (i.e. no BW)
  */
 export const getTextColor = function (bg, text, preserve) {
-  const bgIsLight = convert(bg).hsl.l > 50
-  const textIsLight = convert(text).hsl.l > 50
+  const bgIsLight = relativeLuminance(bg) > 0.5
+  const textIsLight = relativeLuminance(text) > 0.5
 
   if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) {
     const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 297d0223..e2e9331c 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -159,13 +159,13 @@ export const SLOT_INHERITANCE = {
 
   alertError: '--cRed',
   alertErrorText: {
-    depends: ['text', 'alertError'],
+    depends: ['text'],
     layer: 'alert',
     variant: 'alertError',
     textColor: true
   },
   alertErrorPanelText: {
-    depends: ['panelText', 'alertError'],
+    depends: ['panelText'],
     layer: 'alertPanel',
     variant: 'alertError',
     textColor: true
@@ -173,13 +173,13 @@ export const SLOT_INHERITANCE = {
 
   alertWarning: '--cOrange',
   alertWarningText: {
-    depends: ['text', 'alertWarning'],
+    depends: ['text'],
     layer: 'alert',
     variant: 'alertWarning',
     textColor: true
   },
   alertWarningPanelText: {
-    depends: ['panelText', 'alertWarning'],
+    depends: ['panelText'],
     layer: 'alertPanel',
     variant: 'alertWarning',
     textColor: true

From 3492d7f81ec3f99c4f15f9ab75f658b3d1db7799 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 17:59:41 +0200
Subject: [PATCH 132/483] eslint

---
 src/components/color_input/color_input.vue       | 8 ++++----
 src/components/opacity_input/opacity_input.vue   | 8 ++++----
 src/components/settings/settings.vue             | 4 ++--
 src/components/style_switcher/style_switcher.vue | 6 +++---
 4 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue
index 7fe04433..e54409fe 100644
--- a/src/components/color_input/color_input.vue
+++ b/src/components/color_input/color_input.vue
@@ -13,8 +13,8 @@
       v-if="typeof fallback !== 'undefined' && showOptionalTickbox"
       :checked="present"
       :disabled="disabled"
-      @change="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
       class="opt"
+      @change="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
     />
     <div class="input color-input-field">
       <input
@@ -46,6 +46,9 @@
 import Checkbox from '../checkbox/checkbox.vue'
 import { hex2rgb } from '../../services/color_convert/color_convert.js'
 export default {
+  components: {
+    Checkbox
+  },
   props: {
     // Name of color, used for identifying
     name: {
@@ -83,9 +86,6 @@ export default {
       default: true
     }
   },
-  components: {
-    Checkbox
-  },
   computed: {
     present () {
       return typeof this.value !== 'undefined'
diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue
index cfda9926..3cc3942b 100644
--- a/src/components/opacity_input/opacity_input.vue
+++ b/src/components/opacity_input/opacity_input.vue
@@ -13,8 +13,8 @@
       v-if="typeof fallback !== 'undefined'"
       :checked="present"
       :disabled="disabled"
-      @change="$emit('input', !present ? fallback : undefined)"
       class="opt"
+      @change="$emit('input', !present ? fallback : undefined)"
     />
     <input
       :id="name"
@@ -33,12 +33,12 @@
 <script>
 import Checkbox from '../checkbox/checkbox.vue'
 export default {
-  props: [
-    'name', 'value', 'fallback', 'disabled'
-  ],
   components: {
     Checkbox
   },
+  props: [
+    'name', 'value', 'fallback', 'disabled'
+  ],
   computed: {
     present () {
       return typeof this.value !== 'undefined'
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index b40c85dd..e118dbcc 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -76,9 +76,9 @@
                 <li>
                   <Checkbox v-model="useStreamingApi">
                     {{ $t('settings.useStreamingApi') }}
-                    <br/>
+                    <br>
                     <small>
-                    {{ $t('settings.useStreamingApiWarning') }}
+                      {{ $t('settings.useStreamingApiWarning') }}
                     </small>
                   </Checkbox>
                 </li>
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index b059eb8a..ff6a8264 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -119,14 +119,14 @@
               name="accentColor"
               :fallback="previewTheme.colors.link"
               :label="$t('settings.accent')"
-              :showOptionalTickbox="typeof linkColorLocal !== 'undefined'"
+              :show-optional-tickbox="typeof linkColorLocal !== 'undefined'"
             />
             <ColorInput
               v-model="linkColorLocal"
               name="linkColor"
               :fallback="previewTheme.colors.accent"
               :label="$t('settings.links')"
-              :showOptionalTickbox="typeof accentColorLocal !== 'undefined'"
+              :show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
             />
             <ContrastRatio :contrast="previewContrast.bgLink" />
           </div>
@@ -388,7 +388,7 @@
               v-model="underlayColorLocal"
               name="underlay"
               :label="$t('settings.style.advanced_colors.underlay')"
-              fallback='#000000'
+              fallback="#000000"
             />
             <OpacityInput
               v-model="underlayOpacityLocal"

From 21d9c87b344598c457ae01b872b85c033a5e043f Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 23:05:32 +0200
Subject: [PATCH 133/483] fix tests

---
 .../style_setter.spec.js => theme_data/theme_data.spec.js}      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
 rename test/unit/specs/services/{style_setter/style_setter.spec.js => theme_data/theme_data.spec.js} (96%)

diff --git a/test/unit/specs/services/style_setter/style_setter.spec.js b/test/unit/specs/services/theme_data/theme_data.spec.js
similarity index 96%
rename from test/unit/specs/services/style_setter/style_setter.spec.js
rename to test/unit/specs/services/theme_data/theme_data.spec.js
index 7f789124..507905eb 100644
--- a/test/unit/specs/services/style_setter/style_setter.spec.js
+++ b/test/unit/specs/services/theme_data/theme_data.spec.js
@@ -1,4 +1,4 @@
-import { getLayersArray, topoSort } from 'src/services/style_setter/style_setter'
+import { getLayersArray, topoSort } from 'src/services/theme_data/theme_data.service.js'
 
 describe('getLayersArray', () => {
   const fixture = {

From 40ba3e225209539639b65c2f636fa1b5af9861ff Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 12 Jan 2020 23:41:11 +0200
Subject: [PATCH 134/483] added more slots to UI

---
 src/components/poll/poll.vue                  |  4 +-
 .../style_switcher/style_switcher.vue         | 37 +++++++++++++++++--
 src/i18n/en.json                              |  4 +-
 src/services/theme_data/theme_data.service.js | 15 ++++++--
 4 files changed, 51 insertions(+), 9 deletions(-)

diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue
index db8e33b3..56e91cca 100644
--- a/src/components/poll/poll.vue
+++ b/src/components/poll/poll.vue
@@ -104,8 +104,10 @@
   .result-fill {
     height: 100%;
     position: absolute;
+    color: $fallback--text;
+    color: var(--pollText, $fallback--text);
     background-color: $fallback--lightBg;
-    background-color: var(--linkBg, $fallback--lightBg);
+    background-color: var(--poll, $fallback--lightBg);
     border-radius: $fallback--panelRadius;
     border-radius: var(--panelRadius, $fallback--panelRadius);
     top: 0;
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index ff6a8264..e0894b6d 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -215,20 +215,20 @@
               :label="$t('settings.text')"
               :fallback="previewTheme.colors.alertErrorText"
             />
-            <ContrastRatio :contrast="previewContrast.alertError" />
+            <ContrastRatio :contrast="previewContrast.alertErrorText" large="1"/>
             <ColorInput
               v-model="alertWarningColorLocal"
               name="alertWarning"
               :label="$t('settings.style.advanced_colors.alert_warning')"
               :fallback="previewTheme.colors.alertWarning"
             />
-            <ContrastRatio :contrast="previewContrast.alertWarning" />
             <ColorInput
               v-model="alertWarningTextColorLocal"
               name="alertWarningText"
               :label="$t('settings.text')"
               :fallback="previewTheme.colors.alertWarningText"
             />
+            <ContrastRatio :contrast="previewContrast.alertWarningText" large="1"/>
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
@@ -238,6 +238,13 @@
               :label="$t('settings.style.advanced_colors.badge_notification')"
               :fallback="previewTheme.colors.badgeNotification"
             />
+            <ColorInput
+              v-model="badgeNotificationTextColorLocal"
+              name="badgeNotificationText"
+              :label="$t('settings.text')"
+              :fallback="previewTheme.colors.badgeNotificationText"
+            />
+            <ContrastRatio :contrast="previewContrast.badgeNotificationText" large="1" />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4>
@@ -388,7 +395,7 @@
               v-model="underlayColorLocal"
               name="underlay"
               :label="$t('settings.style.advanced_colors.underlay')"
-              fallback="#000000"
+              :fallback="previewTheme.colors.underlay"
             />
             <OpacityInput
               v-model="underlayOpacityLocal"
@@ -397,6 +404,30 @@
               :disabled="underlayOpacityLocal === 'transparent'"
             />
           </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.poll') }}</h4>
+            <ColorInput
+              v-model="pollColorLocal"
+              name="poll"
+              :label="$t('settings.background')"
+              :fallback="previewTheme.colors.poll"
+            />
+            <ColorInput
+              v-model="pollTextColorLocal"
+              name="poll"
+              :label="$t('settings.text')"
+              :fallback="previewTheme.colors.pollText"
+            />
+          </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.icons') }}</h4>
+            <ColorInput
+              v-model="iconColorLocal"
+              name="poll"
+              :label="$t('settings.style.advanced_colors.icons')"
+              :fallback="previewTheme.colors.icon"
+            />
+          </div>
         </div>
 
         <div
diff --git a/src/i18n/en.json b/src/i18n/en.json
index d97b4909..bd3d7413 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -431,7 +431,9 @@
         "buttons": "Buttons",
         "inputs": "Input fields",
         "faint_text": "Faded text",
-        "underlay": "Underlay"
+        "underlay": "Underlay",
+        "poll": "Poll graph",
+        "icons": "Icons"
       },
       "radii": {
         "_tab_label": "Roundness"
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index e2e9331c..21bab1a2 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -20,7 +20,8 @@ export const LAYERS = {
   inputPanel: 'panel',
   inputTopBar: 'topBar',
   alert: 'bg',
-  alertPanel: 'panel'
+  alertPanel: 'panel',
+  poll: 'bg'
 }
 
 export const DEFAULT_OPACITY = {
@@ -33,7 +34,8 @@ export const DEFAULT_OPACITY = {
   alert: 0.5,
   input: 0.5,
   faint: 0.5,
-  underlay: 0.15
+  underlay: 0.15,
+  poll: 1
 }
 
 export const SLOT_INHERITANCE = {
@@ -65,9 +67,14 @@ export const SLOT_INHERITANCE = {
     color: (mod, fg) => brightness(2 * mod, fg).rgb
   },
 
-  linkBg: {
+  poll: {
     depends: ['accent', 'bg'],
-    color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg).rgb
+    color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg)
+  },
+  pollText: {
+    depends: ['text'],
+    layer: 'poll',
+    textColor: true
   },
 
   icon: {

From 8f63bbb64fa2860e73d8562de9d6c62629a8668f Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 00:33:04 +0200
Subject: [PATCH 135/483] poll slot renamed, lightBg customization implemented

---
 src/App.scss                                  |  4 +++
 src/components/autosuggest/autosuggest.vue    |  4 +--
 src/components/dialog_modal/dialog_modal.vue  | 12 ++++-----
 src/components/emoji_input/emoji_input.vue    |  4 +++
 src/components/nav_panel/nav_panel.vue        | 10 +++++++
 .../selectable_list/selectable_list.vue       |  4 +++
 src/components/side_drawer/side_drawer.vue    |  5 ++++
 src/components/status/status.vue              |  7 +++--
 .../style_switcher/style_switcher.vue         | 27 +++++++++++++++++--
 src/services/style_setter/style_setter.js     |  4 ++-
 src/services/theme_data/theme_data.service.js | 26 ++++++++++++++++++
 11 files changed, 94 insertions(+), 13 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 7c9c91af..7da3688a 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -495,6 +495,10 @@ main-router {
     color: $fallback--faint;
     color: var(--panelFaint, $fallback--faint);
   }
+  .faint-link {
+    color: $fallback--faint;
+    color: var(--faintLink, $fallback--faint);
+  }
 
   .alert {
     white-space: nowrap;
diff --git a/src/components/autosuggest/autosuggest.vue b/src/components/autosuggest/autosuggest.vue
index 1f86e996..f283ab82 100644
--- a/src/components/autosuggest/autosuggest.vue
+++ b/src/components/autosuggest/autosuggest.vue
@@ -40,8 +40,8 @@
     top: 100%;
     right: 0;
     max-height: 400px;
-    background-color: $fallback--lightBg;
-    background-color: var(--lightBg, $fallback--lightBg);
+    background-color: $fallback--bg;
+    background-color: var(--bg, $fallback--bg);
     border-style: solid;
     border-width: 1px;
     border-color: $fallback--border;
diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue
index 55d7a7d2..3241ce3e 100644
--- a/src/components/dialog_modal/dialog_modal.vue
+++ b/src/components/dialog_modal/dialog_modal.vue
@@ -75,18 +75,18 @@
   .dialog-modal-content {
     margin: 0;
     padding: 1rem 1rem;
-    background-color: $fallback--lightBg;
-    background-color: var(--lightBg, $fallback--lightBg);
+    background-color: $fallback--bg;
+    background-color: var(--bg, $fallback--bg);
     white-space: normal;
   }
 
   .dialog-modal-footer {
     margin: 0;
     padding: .5em .5em;
-    background-color: $fallback--lightBg;
-    background-color: var(--lightBg, $fallback--lightBg);
-    border-top: 1px solid $fallback--bg;
-    border-top: 1px solid var(--bg, $fallback--bg);
+    background-color: $fallback--bg;
+    background-color: var(--bg, $fallback--bg);
+    border-top: 1px solid $fallback--border;
+    border-top: 1px solid var(--border, $fallback--border);
     display: flex;
     justify-content: flex-end;
 
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index a7215670..dcf51ff9 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -158,6 +158,10 @@
       &.highlighted {
         background-color: $fallback--fg;
         background-color: var(--lightBg, $fallback--fg);
+        color: var(--lightBgText, $fallback--text);
+        --faint: var(--lightBgFaintText, $fallback--faint);
+        --faintLink: var(--lightBgFaintLink, $fallback--faint);
+        --icon: var(--lightBgIcon, $fallback--icon);
       }
     }
   }
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 034259d9..0886bf8c 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -101,12 +101,22 @@
   &:hover {
     background-color: $fallback--lightBg;
     background-color: var(--lightBg, $fallback--lightBg);
+    color: $fallback--link;
+    color: var(--lightBgText, $fallback--link);
+    --faint: var(--lightBgFaintText, $fallback--faint);
+    --faintLink: var(--lightBgFaintLink, $fallback--faint);
+    --icon: var(--lightBgIcon, $fallback--icon);
   }
 
   &.router-link-active {
     font-weight: bolder;
     background-color: $fallback--lightBg;
     background-color: var(--lightBg, $fallback--lightBg);
+    color: $fallback--text;
+    color: var(--lightBgText, $fallback--text);
+    --faint: var(--lightBgFaintText, $fallback--faint);
+    --faintLink: var(--lightBgFaintLink, $fallback--faint);
+    --icon: var(--lightBgIcon, $fallback--icon);
 
     &:hover {
       text-decoration: underline;
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
index d9ec7ece..416c9b6a 100644
--- a/src/components/selectable_list/selectable_list.vue
+++ b/src/components/selectable_list/selectable_list.vue
@@ -69,6 +69,10 @@
   &-item-selected-inner {
     background-color: $fallback--lightBg;
     background-color: var(--lightBg, $fallback--lightBg);
+    color: var(--lightBgText, $fallback--text);
+    --faint: var(--lightBgFaintText, $fallback--faint);
+    --faintLink: var(--lightBgFaintLink, $fallback--faint);
+    --icon: var(--lightBgIcon, $fallback--icon);
   }
 
   &-header {
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 3fba9058..6d75221f 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -290,6 +290,11 @@
     &:hover {
       background-color: $fallback--lightBg;
       background-color: var(--lightBg, $fallback--lightBg);
+      color: $fallback--text;
+      color: var(--lightBgText, $fallback--text);
+      --faint: var(--lightBgFaintText, $fallback--faint);
+      --faintLink: var(--lightBgFaintLink, $fallback--faint);
+      --icon: var(--lightBgIcon, $fallback--icon);
     }
   }
 }
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index d291e762..72e9b25a 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -446,6 +446,11 @@ $status-margin: 0.75em;
   &_focused {
     background-color: $fallback--lightBg;
     background-color: var(--lightBg, $fallback--lightBg);
+    color: $fallback--text;
+    color: var(--lightBgText, $fallback--text);
+    --faint: var(--lightBgFaintText, $fallback--faint);
+    --faintLink: var(--lightBgFaintLink, $fallback--faint);
+    --icon: var(--lightBgIcon, $fallback--icon);
   }
 
   .timeline & {
@@ -573,8 +578,6 @@ $status-margin: 0.75em;
       overflow: hidden;
       text-overflow: ellipsis;
       margin: 0 0.4em 0 0.2em;
-      color: $fallback--faint;
-      color: var(--faint, $fallback--faint);
     }
 
     .replies-separator {
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index e0894b6d..1381f1fb 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -414,7 +414,7 @@
             />
             <ColorInput
               v-model="pollTextColorLocal"
-              name="poll"
+              name="pollText"
               :label="$t('settings.text')"
               :fallback="previewTheme.colors.pollText"
             />
@@ -423,11 +423,34 @@
             <h4>{{ $t('settings.style.advanced_colors.icons') }}</h4>
             <ColorInput
               v-model="iconColorLocal"
-              name="poll"
+              name="icon"
               :label="$t('settings.style.advanced_colors.icons')"
               :fallback="previewTheme.colors.icon"
             />
           </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.lightBg') }}</h4>
+            <ColorInput
+              v-model="lightBgColorLocal"
+              name="lightBg"
+              :label="$t('settings.style.advanced_colors.lightBg')"
+              :fallback="previewTheme.colors.lightBg"
+            />
+            <ColorInput
+              v-model="lightBgTextColorLocal"
+              name="lightBgText"
+              :label="$t('settings.text')"
+              :fallback="previewTheme.colors.lightBgText"
+            />
+            <ContrastRatio :contrast="previewContrast.lightBgText" />
+            <ColorInput
+              v-model="lightBgLinkColorLocal"
+              name="lightBgLink"
+              :label="$t('settings.links')"
+              :fallback="previewTheme.colors.lightBgLink"
+            />
+            <ContrastRatio :contrast="previewContrast.lightBgLink" />
+          </div>
         </div>
 
         <div
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index e11516c0..204c162b 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -163,8 +163,10 @@ export const generateColors = (themeData) => {
       return
     }
     if (k === 'faint') {
-      colors[k + 'Link'].a = v
+      colors['faintLink'].a = v
       colors['panelFaint'].a = v
+      colors['lightBgFaintText'].a = v
+      colors['lightBgFaintLink'].a = v
     }
     if (k === 'bg') {
       colors['lightBg'].a = v
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 21bab1a2..808f67d5 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -12,6 +12,7 @@ export const LAYERS = {
   badge: null, //  no transparency support
   fg: null,
   bg: 'underlay',
+  lightBg: 'bg',
   panel: 'bg',
   btn: 'bg',
   btnPanel: 'panel',
@@ -57,6 +58,31 @@ export const SLOT_INHERITANCE = {
     depends: ['bg'],
     color: (mod, bg) => brightness(5 * mod, bg).rgb
   },
+  lightBgFaintText: {
+    depends: ['faint'],
+    layer: 'lightBg',
+    textColor: true
+  },
+  lightBgFaintLink: {
+    depends: ['faintLink'],
+    layer: 'lightBg',
+    textColor: 'preserve'
+  },
+  lightBgText: {
+    depends: ['text'],
+    layer: 'lightBg',
+    textColor: true
+  },
+  lightBgLink: {
+    depends: ['link'],
+    layer: 'lightBg',
+    textColor: 'preserve'
+  },
+  lightBgIcon: {
+    depends: ['lightBg', 'lightBgText'],
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
   lightText: {
     depends: ['text'],
     color: (mod, text) => brightness(20 * mod, text).rgb

From fa2fcc882737c8c057fd12d4cc1a85d67f3b672e Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 01:54:56 +0200
Subject: [PATCH 136/483] better fallbacks in UI

---
 src/components/style_switcher/style_switcher.vue | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 1381f1fb..c1ec7c9a 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -251,7 +251,7 @@
             <ColorInput
               v-model="panelColorLocal"
               name="panelColor"
-              :fallback="fgColorLocal"
+              :fallback="previewTheme.colors.panel"
               :label="$t('settings.background')"
             />
             <OpacityInput
@@ -286,7 +286,7 @@
             <ColorInput
               v-model="topBarColorLocal"
               name="topBarColor"
-              :fallback="fgColorLocal"
+              :fallback="previewTheme.colors.topBar"
               :label="$t('settings.background')"
             />
             <ColorInput
@@ -309,7 +309,7 @@
             <ColorInput
               v-model="inputColorLocal"
               name="inputColor"
-              :fallback="fgColorLocal"
+              :fallback="previewTheme.colors.input"
               :label="$t('settings.background')"
             />
             <OpacityInput
@@ -331,7 +331,7 @@
             <ColorInput
               v-model="btnColorLocal"
               name="btnColor"
-              :fallback="fgColorLocal"
+              :fallback="previewTheme.colors.btn"
               :label="$t('settings.background')"
             />
             <OpacityInput

From 5881c13adc312f864a280f9216993839ee86ef1e Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 01:55:10 +0200
Subject: [PATCH 137/483] computed color display support in color_input

---
 src/components/color_input/color_input.scss | 3 +++
 src/components/color_input/color_input.vue  | 8 ++++++++
 2 files changed, 11 insertions(+)

diff --git a/src/components/color_input/color_input.scss b/src/components/color_input/color_input.scss
index 92bf87c5..8e9923cf 100644
--- a/src/components/color_input/color_input.scss
+++ b/src/components/color_input/color_input.scss
@@ -31,11 +31,14 @@
         height: 100%;
       }
     }
+    .computedIndicator,
     .transparentIndicator {
       flex: 0 0 2em;
       min-width: 2em;
       align-self: center;
       height: 100%;
+    }
+    .transparentIndicator {
       // forgot to install counter-strike source, ooops
       background-color: #FF00FF;
       position: relative;
diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue
index e54409fe..8fb16113 100644
--- a/src/components/color_input/color_input.vue
+++ b/src/components/color_input/color_input.vue
@@ -38,6 +38,11 @@
         v-if="transparentColor"
         class="transparentIndicator"
       />
+      <div
+        v-if="computedColor"
+        class="computedIndicator"
+        :style="{backgroundColor: fallback}"
+      />
     </div>
   </div>
 </template>
@@ -95,6 +100,9 @@ export default {
     },
     transparentColor () {
       return this.value === 'transparent'
+    },
+    computedColor () {
+      return this.value && this.value.startsWith('--')
     }
   }
 }

From 8d2f2866f6d32b4ada155d76e07910b92c218146 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 01:56:29 +0200
Subject: [PATCH 138/483] computed colors support

---
 src/App.scss                                  |  4 ++
 .../style_switcher/style_switcher.js          |  6 +-
 .../style_switcher/style_switcher.vue         | 16 ++++-
 src/i18n/en.json                              |  4 +-
 src/services/style_setter/style_setter.js     |  6 +-
 src/services/theme_data/theme_data.service.js | 69 ++++++++++++++++---
 6 files changed, 92 insertions(+), 13 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 7da3688a..ef139e88 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -99,6 +99,10 @@ button {
   &:active {
     box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
     box-shadow: var(--buttonPressedShadow);
+    color: $fallback--text;
+    color: var(--btnPressedText, $fallback--text);
+    background-color: $fallback--fg;
+    background-color: var(--btnPressed, $fallback--fg)
   }
 
   &:disabled {
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 49b34405..b84d2489 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -42,7 +42,7 @@ const v1OnlyNames = [
 ].map(_ => _ + 'ColorLocal')
 
 const colorConvert = (color) => {
-  if (color === 'transparent') {
+  if (color.startsWith('--') || color === 'transparent') {
     return color
   } else {
     return hex2rgb(color)
@@ -409,7 +409,9 @@ export default {
         }
 
         keys.forEach(key => {
-          this[key + 'ColorLocal'] = rgb2hex(colors[key])
+          const color = colors[key]
+          const hex = rgb2hex(colors[key])
+          this[key + 'ColorLocal'] = hex === '#aN' ? color : hex
         })
       }
 
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index c1ec7c9a..f2fdfea2 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -347,6 +347,20 @@
               :label="$t('settings.text')"
             />
             <ContrastRatio :contrast="previewContrast.btnText" />
+            <h4>{{ $t('settings.style.advanced_colors.pressed') }}</h4>
+            <ColorInput
+              v-model="btnPressedColorLocal"
+              name="btnPressedColor"
+              :fallback="previewTheme.colors.btnPressed"
+              :label="$t('settings.background')"
+            />
+            <ColorInput
+              v-model="btnPressedTextColorLocal"
+              name="btnPressedTextColor"
+              :fallback="previewTheme.colors.btnPressedText"
+              :label="$t('settings.text')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnText" />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.borders') }}</h4>
@@ -433,7 +447,7 @@
             <ColorInput
               v-model="lightBgColorLocal"
               name="lightBg"
-              :label="$t('settings.style.advanced_colors.lightBg')"
+              :label="$t('settings.background')"
               :fallback="previewTheme.colors.lightBg"
             />
             <ColorInput
diff --git a/src/i18n/en.json b/src/i18n/en.json
index bd3d7413..544bc2ba 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -433,7 +433,9 @@
         "faint_text": "Faded text",
         "underlay": "Underlay",
         "poll": "Poll graph",
-        "icons": "Icons"
+        "icons": "Icons",
+        "lightBg": "Highlighted elements",
+        "pressed": "Pressed"
       },
       "radii": {
         "_tab_label": "Roundness"
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 204c162b..61612cf4 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -144,7 +144,11 @@ export const generateColors = (themeData) => {
         // TODO: hack to keep rest of the code from complaining
         value = '#FF00FF'
       }
-      acc[k] = hex2rgb(value)
+      if (!value || value.startsWith('--')) {
+        acc[k] = value
+      } else {
+        acc[k] = hex2rgb(value)
+      }
     }
     return acc
   }, {})
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 808f67d5..221c3b48 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -40,12 +40,27 @@ export const DEFAULT_OPACITY = {
 }
 
 export const SLOT_INHERITANCE = {
-  bg: null,
-  fg: null,
-  text: null,
+  bg: {
+    depends: [],
+    priority: 1
+  },
+  fg: {
+    depends: [],
+    priority: 1
+  },
+  text: {
+    depends: [],
+    priority: 1
+  },
   underlay: '#000000',
-  link: '--accent',
-  accent: '--link',
+  link: {
+    depends: ['accent'],
+    priority: 1
+  },
+  accent: {
+    depends: ['link'],
+    priority: 1
+  },
   faint: '--text',
   faintLink: '--link',
 
@@ -170,6 +185,26 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
 
+  btnPressed: '--btn',
+  btnPressedText: {
+    depends: ['btnText'],
+    layer: 'btn',
+    variant: 'btnPressed',
+    textColor: true
+  },
+  btnPressedPanelText: {
+    depends: ['btnPanelText'],
+    layer: 'btnPanel',
+    variant: 'btnPressed',
+    textColor: true
+  },
+  btnPressedTopBarText: {
+    depends: ['btnTopBarText'],
+    layer: 'btnTopBar',
+    variant: 'btnPressed',
+    textColor: true
+  },
+
   // Input fields
   input: '--fg',
   inputText: {
@@ -308,12 +343,30 @@ export const topoSort = (
   return output
 }
 
-export const SLOT_ORDERED = topoSort(SLOT_INHERITANCE)
+export const SLOT_ORDERED = topoSort(
+  Object.entries(SLOT_INHERITANCE)
+    .sort(([aK, aV], [bK, bV]) => ((aV && aV.priority) || 0) - ((bV && bV.priority) || 0))
+    .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
+)
+
+console.log(SLOT_ORDERED)
 
 export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce((acc, key) => {
   const value = SLOT_INHERITANCE[key]
-  if (sourceColors[key]) {
-    return { ...acc, [key]: { ...sourceColors[key] } }
+  const sourceColor = sourceColors[key]
+  if (sourceColor) {
+    let targetColor = sourceColor
+    if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) {
+      const [variable, modifier] = sourceColor.split(/,/g).map(str => str.trim())
+      const variableSlot = variable.substring(2)
+      targetColor = acc[variableSlot] || sourceColors[variableSlot]
+      if (modifier) {
+        console.log(targetColor, acc, variableSlot)
+        targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
+      }
+      console.log(targetColor, acc, variableSlot)
+    }
+    return { ...acc, [key]: { ...targetColor } }
   } else if (typeof value === 'string' && value.startsWith('#')) {
     return { ...acc, [key]: convert(value).rgb }
   } else {

From 9943e1cc4306675f4ce5cad7e3be8e3353356f60 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 02:02:31 +0200
Subject: [PATCH 139/483] removed references to v3compat

---
 src/components/style_switcher/style_switcher.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index b84d2489..34826f9f 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -481,7 +481,6 @@ export default {
     currentColors () {
       try {
         this.previewColors = generateColors({
-          v3compat: this.currentCompat,
           opacity: this.currentOpacity,
           colors: this.currentColors
         })
@@ -494,7 +493,6 @@ export default {
     currentOpacity () {
       try {
         this.previewColors = generateColors({
-          v3compat: this.currentCompat,
           opacity: this.currentOpacity,
           colors: this.currentColors
         })

From 9af00424529e2cb9ea9418034fa1d689f248192d Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 02:02:40 +0200
Subject: [PATCH 140/483] updated breezy theme

---
 static/themes/breezy-dark.json  | 13 +++----------
 static/themes/breezy-light.json | 13 +++----------
 2 files changed, 6 insertions(+), 20 deletions(-)

diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index 580ce82c..ce0f10ab 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -67,15 +67,6 @@
         }
       ],
       "buttonPressed": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": "0",
-          "spread": "50",
-          "color": "--faint",
-          "alpha": 1,
-          "inset": true
-        },
         {
           "x": 0,
           "y": "0",
@@ -121,7 +112,9 @@
       "cRed": "#da4453",
       "cBlue": "#3daee9",
       "cGreen": "#27ae60",
-      "cOrange": "#f67400"
+      "cOrange": "#f67400",
+      "btnPressed": "--accent",
+      "lightBg": "--accent,-20"
     },
     "radii": {
       "btn": "2",
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index 0c85a3c9..c4e54c6d 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -67,15 +67,6 @@
         }
       ],
       "buttonPressed": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": "0",
-          "spread": "50",
-          "color": "--faint",
-          "alpha": 1,
-          "inset": true
-        },
         {
           "x": 0,
           "y": "0",
@@ -124,7 +115,9 @@
       "cRed": "#da4453",
       "cBlue": "#2980b9",
       "cGreen": "#27ae60",
-      "cOrange": "#f67400"
+      "cOrange": "#f67400",
+      "btnPressed": "--accent",
+      "lightBg": "--accent,-20"
     },
     "radii": {
       "btn": "2",

From 29a0b4a593219a54c01faa982be4752bcddfc7d0 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 02:08:39 +0200
Subject: [PATCH 141/483] fix shadow and opacity

---
 src/components/shadow_control/shadow_control.js | 3 ++-
 src/components/style_switcher/style_switcher.js | 2 +-
 src/services/style_setter/style_setter.js       | 2 +-
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js
index 7e82b9c0..44e4a22f 100644
--- a/src/components/shadow_control/shadow_control.js
+++ b/src/components/shadow_control/shadow_control.js
@@ -1,6 +1,7 @@
 import ColorInput from '../color_input/color_input.vue'
 import OpacityInput from '../opacity_input/opacity_input.vue'
-import { hex2rgb, getCssShadow } from '../../services/color_convert/color_convert.js'
+import { getCssShadow } from '../../services/style_setter/style_setter.js'
+import { hex2rgb } from '../../services/color_convert/color_convert.js'
 
 export default {
   // 'Value' and 'Fallback' can be undefined, but if they are
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 34826f9f..98c2cbc5 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -117,7 +117,7 @@ export default {
     currentOpacity () {
       return Object.keys(DEFAULT_OPACITY)
         .map(key => [key, this[key + 'OpacityLocal']])
-        .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {})
+        .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
     },
     currentRadii () {
       return {
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 61612cf4..c1a25101 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -76,7 +76,7 @@ export const applyTheme = (input, commit) => {
   commit('setOption', { name: 'colors', value: theme.colors })
 }
 
-const getCssShadow = (input, usesDropShadow) => {
+export const getCssShadow = (input, usesDropShadow) => {
   if (input.length === 0) {
     return 'none'
   }

From 8a7f3fc16a511e6c5917c0e25cba1f6163659264 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 20:40:16 +0200
Subject: [PATCH 142/483] selectedPost and selectedMenu support

---
 src/components/emoji_input/emoji_input.vue    | 10 ++--
 src/components/nav_panel/nav_panel.vue        | 20 +++----
 .../selectable_list/selectable_list.vue       | 10 ++--
 src/components/side_drawer/side_drawer.vue    | 10 ++--
 src/components/status/status.vue              | 10 ++--
 .../style_switcher/style_switcher.vue         | 46 ++++++++++++++++
 src/services/theme_data/theme_data.service.js | 52 +++++++++++++++++++
 7 files changed, 128 insertions(+), 30 deletions(-)

diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index dcf51ff9..46ed6f25 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -157,11 +157,11 @@
 
       &.highlighted {
         background-color: $fallback--fg;
-        background-color: var(--lightBg, $fallback--fg);
-        color: var(--lightBgText, $fallback--text);
-        --faint: var(--lightBgFaintText, $fallback--faint);
-        --faintLink: var(--lightBgFaintLink, $fallback--faint);
-        --icon: var(--lightBgIcon, $fallback--icon);
+        background-color: var(--selectedMenu, $fallback--fg);
+        color: var(--selectedMenuText, $fallback--text);
+        --faint: var(--selectedMenuFaintText, $fallback--faint);
+        --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+        --icon: var(--selectedMenuIcon, $fallback--icon);
       }
     }
   }
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 0886bf8c..afc611ea 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -100,23 +100,23 @@
 
   &:hover {
     background-color: $fallback--lightBg;
-    background-color: var(--lightBg, $fallback--lightBg);
+    background-color: var(--selectedMenu, $fallback--lightBg);
     color: $fallback--link;
-    color: var(--lightBgText, $fallback--link);
-    --faint: var(--lightBgFaintText, $fallback--faint);
-    --faintLink: var(--lightBgFaintLink, $fallback--faint);
-    --icon: var(--lightBgIcon, $fallback--icon);
+    color: var(--selectedMenuText, $fallback--link);
+    --faint: var(--selectedMenuFaintText, $fallback--faint);
+    --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+    --icon: var(--selectedMenuIcon, $fallback--icon);
   }
 
   &.router-link-active {
     font-weight: bolder;
     background-color: $fallback--lightBg;
-    background-color: var(--lightBg, $fallback--lightBg);
+    background-color: var(--selectedMenu, $fallback--lightBg);
     color: $fallback--text;
-    color: var(--lightBgText, $fallback--text);
-    --faint: var(--lightBgFaintText, $fallback--faint);
-    --faintLink: var(--lightBgFaintLink, $fallback--faint);
-    --icon: var(--lightBgIcon, $fallback--icon);
+    color: var(--selectedMenuText, $fallback--text);
+    --faint: var(--selectedMenuFaintText, $fallback--faint);
+    --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+    --icon: var(--selectedMenuIcon, $fallback--icon);
 
     &:hover {
       text-decoration: underline;
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
index 416c9b6a..2d1e44a3 100644
--- a/src/components/selectable_list/selectable_list.vue
+++ b/src/components/selectable_list/selectable_list.vue
@@ -68,11 +68,11 @@
 
   &-item-selected-inner {
     background-color: $fallback--lightBg;
-    background-color: var(--lightBg, $fallback--lightBg);
-    color: var(--lightBgText, $fallback--text);
-    --faint: var(--lightBgFaintText, $fallback--faint);
-    --faintLink: var(--lightBgFaintLink, $fallback--faint);
-    --icon: var(--lightBgIcon, $fallback--icon);
+    background-color: var(--selectedMenu, $fallback--lightBg);
+    color: var(--selectedMenuText, $fallback--text);
+    --faint: var(--selectedMenuFaintText, $fallback--faint);
+    --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+    --icon: var(--selectedMenuIcon, $fallback--icon);
   }
 
   &-header {
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 6d75221f..3188fd59 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -289,12 +289,12 @@
 
     &:hover {
       background-color: $fallback--lightBg;
-      background-color: var(--lightBg, $fallback--lightBg);
+      background-color: var(--selectedMenu, $fallback--lightBg);
       color: $fallback--text;
-      color: var(--lightBgText, $fallback--text);
-      --faint: var(--lightBgFaintText, $fallback--faint);
-      --faintLink: var(--lightBgFaintLink, $fallback--faint);
-      --icon: var(--lightBgIcon, $fallback--icon);
+      color: var(--selectedMenuText, $fallback--text);
+      --faint: var(--selectedMenuFaintText, $fallback--faint);
+      --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+      --icon: var(--selectedMenuIcon, $fallback--icon);
     }
   }
 }
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 72e9b25a..38d091ed 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -445,12 +445,12 @@ $status-margin: 0.75em;
 
   &_focused {
     background-color: $fallback--lightBg;
-    background-color: var(--lightBg, $fallback--lightBg);
+    background-color: var(--selectedPost, $fallback--lightBg);
     color: $fallback--text;
-    color: var(--lightBgText, $fallback--text);
-    --faint: var(--lightBgFaintText, $fallback--faint);
-    --faintLink: var(--lightBgFaintLink, $fallback--faint);
-    --icon: var(--lightBgIcon, $fallback--icon);
+    color: var(--selectedPostText, $fallback--text);
+    --faint: var(--selectedPostFaintText, $fallback--faint);
+    --faintLink: var(--selectedPostFaintLink, $fallback--faint);
+    --icon: var(--selectedPostIcon, $fallback--icon);
   }
 
   .timeline & {
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index f2fdfea2..613abd1f 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -465,6 +465,52 @@
             />
             <ContrastRatio :contrast="previewContrast.lightBgLink" />
           </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.selectedPost') }}</h4>
+            <ColorInput
+              v-model="selectedPostColorLocal"
+              name="selectedPost"
+              :label="$t('settings.background')"
+              :fallback="previewTheme.colors.selectedPost"
+            />
+            <ColorInput
+              v-model="selectedPostTextColorLocal"
+              name="selectedPostText"
+              :label="$t('settings.text')"
+              :fallback="previewTheme.colors.selectedPostText"
+            />
+            <ContrastRatio :contrast="previewContrast.selectedPostText" />
+            <ColorInput
+              v-model="selectedPostLinkColorLocal"
+              name="selectedPostLink"
+              :label="$t('settings.links')"
+              :fallback="previewTheme.colors.selectedPostLink"
+            />
+            <ContrastRatio :contrast="previewContrast.selectedPostLink" />
+          </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.selectedMenu') }}</h4>
+            <ColorInput
+              v-model="selectedMenuColorLocal"
+              name="selectedMenu"
+              :label="$t('settings.background')"
+              :fallback="previewTheme.colors.selectedMenu"
+            />
+            <ColorInput
+              v-model="selectedMenuTextColorLocal"
+              name="selectedMenuText"
+              :label="$t('settings.text')"
+              :fallback="previewTheme.colors.selectedMenuText"
+            />
+            <ContrastRatio :contrast="previewContrast.selectedMenuText" />
+            <ColorInput
+              v-model="selectedMenuLinkColorLocal"
+              name="selectedMenuLink"
+              :label="$t('settings.links')"
+              :fallback="previewTheme.colors.selectedMenuLink"
+            />
+            <ContrastRatio :contrast="previewContrast.selectedMenuLink" />
+          </div>
         </div>
 
         <div
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 221c3b48..dd5f8fd0 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -98,6 +98,58 @@ export const SLOT_INHERITANCE = {
     color: (mod, bg, text) => mixrgb(bg, text)
   },
 
+  selectedPost: '--lightBg',
+  selectedPostFaintText: {
+    depends: ['lightBgFaintText'],
+    layer: 'lightBg',
+    textColor: true
+  },
+  selectedPostFaintLink: {
+    depends: ['lightBgFaintLink'],
+    layer: 'lightBg',
+    textColor: 'preserve'
+  },
+  selectedPostText: {
+    depends: ['lightBgText'],
+    layer: 'lightBg',
+    textColor: true
+  },
+  selectedPostLink: {
+    depends: ['lightBgLink'],
+    layer: 'lightBg',
+    textColor: 'preserve'
+  },
+  selectedPostIcon: {
+    depends: ['selectedPost', 'selectedPostText'],
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
+  selectedMenu: '--lightBg',
+  selectedMenuFaintText: {
+    depends: ['lightBgFaintText'],
+    layer: 'lightBg',
+    textColor: true
+  },
+  selectedMenuFaintLink: {
+    depends: ['lightBgFaintLink'],
+    layer: 'lightBg',
+    textColor: 'preserve'
+  },
+  selectedMenuText: {
+    depends: ['lightBgText'],
+    layer: 'lightBg',
+    textColor: true
+  },
+  selectedMenuLink: {
+    depends: ['lightBgLink'],
+    layer: 'lightBg',
+    textColor: 'preserve'
+  },
+  selectedMenuIcon: {
+    depends: ['selectedMenu', 'selectedMenuText'],
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
   lightText: {
     depends: ['text'],
     color: (mod, text) => brightness(20 * mod, text).rgb

From 4b8e0f0afa2c7f1d0f4f0c2f39f289b75b3cae08 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 21:30:55 +0200
Subject: [PATCH 143/483] buttonPressed & buttonDisabled slots

---
 src/App.scss                                  | 44 +++++++++++++++++-
 .../style_switcher/style_switcher.vue         | 16 ++++++-
 src/services/theme_data/theme_data.service.js | 46 +++++++++++++++++--
 3 files changed, 100 insertions(+), 6 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index ef139e88..7f404bc6 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -107,7 +107,10 @@ button {
 
   &:disabled {
     cursor: not-allowed;
-    opacity: 0.5;
+    color: $fallback--text;
+    color: var(--btnDisabledText, $fallback--text);
+    background-color: $fallback--fg;
+    background-color: var(--btnDisabled, $fallback--fg)
   }
 
   &.pressed {
@@ -365,6 +368,26 @@ i[class*=icon-] {
   height: 50px;
   box-sizing: border-box;
 
+  button {
+    &, i[class*=icon-] {
+      color: $fallback--text;
+      color: var(--btnTopBarText, $fallback--text);
+    }
+
+    &:active {
+      background-color: $fallback--fg;
+      background-color: var(--btnTopBarPressed, $fallback--fg);
+      color: $fallback--text;
+      color: var(--btnTopBarPressedText, $fallback--text);
+    }
+
+    &:disabled {
+      color: $fallback--text;
+      color: var(--btnTopBarDisabledText, $fallback--text);
+    }
+  }
+
+
   .logo {
     display: flex;
     position: absolute;
@@ -525,6 +548,25 @@ main-router {
     align-self: stretch;
   }
 
+  button {
+    &, i[class*=icon-] {
+      color: $fallback--text;
+      color: var(--btnPanelText, $fallback--text);
+    }
+
+    &:active {
+      background-color: $fallback--fg;
+      background-color: var(--btnPanelPressed, $fallback--fg);
+      color: $fallback--text;
+      color: var(--btnPanelPressedText, $fallback--text);
+    }
+
+    &:disabled {
+      color: $fallback--text;
+      color: var(--btnPanelDisabledText, $fallback--text);
+    }
+  }
+
   a {
     color: $fallback--link;
     color: var(--panelLink, $fallback--link)
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 613abd1f..3921c953 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -360,7 +360,21 @@
               :fallback="previewTheme.colors.btnPressedText"
               :label="$t('settings.text')"
             />
-            <ContrastRatio :contrast="previewContrast.btnText" />
+            <ContrastRatio :contrast="previewContrast.btnPressedText" />
+            <h4>{{ $t('settings.style.advanced_colors.disabled') }}</h4>
+            <ColorInput
+              v-model="btnDisabledColorLocal"
+              name="btnDisabledColor"
+              :fallback="previewTheme.colors.btnDisabled"
+              :label="$t('settings.background')"
+            />
+            <ColorInput
+              v-model="btnDisabledTextColorLocal"
+              name="btnDisabledTextColor"
+              :fallback="previewTheme.colors.btnDisabledText"
+              :label="$t('settings.text')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnDisabledText" />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.borders') }}</h4>
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index dd5f8fd0..c5dd8047 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -36,7 +36,8 @@ export const DEFAULT_OPACITY = {
   input: 0.5,
   faint: 0.5,
   underlay: 0.15,
-  poll: 1
+  poll: 1,
+  topBar: 1
 }
 
 export const SLOT_INHERITANCE = {
@@ -222,7 +223,8 @@ export const SLOT_INHERITANCE = {
   btn: '--fg',
   btnText: {
     depends: ['fgText'],
-    layer: 'btn'
+    layer: 'btn',
+    textColor: true
   },
   btnPanelText: {
     depends: ['panelText'],
@@ -257,6 +259,32 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
 
+  btnDisabled: {
+    depends: ['btn', 'bg'],
+    color: (mod, btn, bg) => alphaBlend(btn, 0.5, bg)
+  },
+  btnDisabledText: {
+    depends: ['btnText'],
+    layer: 'btn',
+    variant: 'btnDisabled',
+    textColor: true,
+    color: (mod, text) => brightness(mod * -60, text).rgb
+  },
+  btnDisabledPanelText: {
+    depends: ['btnPanelText'],
+    layer: 'btnPanel',
+    variant: 'btnDisabled',
+    textColor: true,
+    color: (mod, text) => brightness(mod * -60, text).rgb
+  },
+  btnDisabledTopBarText: {
+    depends: ['btnTopBarText'],
+    layer: 'btnTopBar',
+    variant: 'btnDisabled',
+    textColor: true,
+    color: (mod, text) => brightness(mod * -60, text).rgb
+  },
+
   // Input fields
   input: '--fg',
   inputText: {
@@ -329,7 +357,10 @@ export const getLayers = (layer, variant = layer, colors, opacity) => {
     currentLayer === layer
       ? colors[variant]
       : colors[currentLayer],
-    opacity[currentLayer]
+    // TODO: Remove this hack when opacities/layers system is improved
+    currentLayer.startsWith('btn')
+      ? opacity.btn
+      : opacity[currentLayer]
   ]))
 }
 
@@ -443,11 +474,18 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
           [key]: contrastRatio(bg).rgb
         }
       } else {
+        let color = { ...acc[deps[0]] }
+        if (value.color) {
+          const isLightOnDark = convert(bg).hsl.l < convert(color).hsl.l
+          const mod = isLightOnDark ? 1 : -1
+          color = value.color(mod, ...deps.map((dep) => ({ ...acc[dep] })))
+        }
+
         return {
           ...acc,
           [key]: getTextColor(
             bg,
-            { ...acc[deps[0]] },
+            { ...color },
             value.textColor === 'preserve'
           )
         }

From 9f7af191e8a77a0c5620c8698cc7b26dde02868b Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 22:19:19 +0200
Subject: [PATCH 144/483] tabs & toggled (ex pressed) buttons

---
 src/App.scss                                  | 22 ++++++++---
 .../follow_button/follow_button.vue           |  2 +-
 .../moderation_tools/moderation_tools.vue     |  2 +-
 .../style_switcher/style_switcher.vue         | 23 +++++++++++
 src/components/tab_switcher/tab_switcher.scss |  7 ++++
 src/components/user_card/user_card.vue        | 10 +----
 src/services/theme_data/theme_data.service.js | 39 +++++++++++++++++++
 7 files changed, 90 insertions(+), 15 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 7f404bc6..70983fe2 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -113,11 +113,11 @@ button {
     background-color: var(--btnDisabled, $fallback--fg)
   }
 
-  &.pressed {
-    color: $fallback--faint;
-    color: var(--faint, $fallback--faint);
-    background-color: $fallback--bg;
-    background-color: var(--bg, $fallback--bg)
+  &.toggled {
+    color: $fallback--text;
+    color: var(--btnToggledText, $fallback--text);
+    background-color: $fallback--fg;
+    background-color: var(--btnToggled, $fallback--fg)
   }
 
   &.danger {
@@ -385,6 +385,13 @@ i[class*=icon-] {
       color: $fallback--text;
       color: var(--btnTopBarDisabledText, $fallback--text);
     }
+
+    &.toggled {
+      color: $fallback--text;
+      color: var(--btnTopBarToggledText, $fallback--text);
+      background-color: $fallback--fg;
+      background-color: var(--btnTopBarToggled, $fallback--fg)
+    }
   }
 
 
@@ -565,6 +572,11 @@ main-router {
       color: $fallback--text;
       color: var(--btnPanelDisabledText, $fallback--text);
     }
+
+    &.toggled {
+      color: $fallback--text;
+      color: var(--btnPanelToggledText, $fallback--text);
+    }
   }
 
   a {
diff --git a/src/components/follow_button/follow_button.vue b/src/components/follow_button/follow_button.vue
index f0cbb94b..bfdc137b 100644
--- a/src/components/follow_button/follow_button.vue
+++ b/src/components/follow_button/follow_button.vue
@@ -1,7 +1,7 @@
 <template>
   <button
     class="btn btn-default follow-button"
-    :class="{ pressed: isPressed }"
+    :class="{ toggled: isPressed }"
     :disabled="inProgress"
     :title="title"
     @click="onClick"
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
index 006d6373..e78e05f1 100644
--- a/src/components/moderation_tools/moderation_tools.vue
+++ b/src/components/moderation_tools/moderation_tools.vue
@@ -123,7 +123,7 @@
       </div>
       <button
         class="btn btn-default btn-block"
-        :class="{ pressed: showDropDown }"
+        :class="{ toggled: showDropDown }"
       >
         {{ $t('user_card.admin_menu.moderation') }}
       </button>
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 3921c953..2fca5570 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -376,6 +376,29 @@
             />
             <ContrastRatio :contrast="previewContrast.btnDisabledText" />
           </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.tabs') }}</h4>
+            <ColorInput
+              v-model="tabColorLocal"
+              name="tabColor"
+              :fallback="previewTheme.colors.tab"
+              :label="$t('settings.background')"
+            />
+            <ColorInput
+              v-model="tabTextColorLocal"
+              name="tabTextColor"
+              :fallback="previewTheme.colors.tabText"
+              :label="$t('settings.text')"
+            />
+            <ContrastRatio :contrast="previewContrast.tabText" />
+            <ColorInput
+              v-model="tabActiveTextColorLocal"
+              name="tabActiveTextColor"
+              :fallback="previewTheme.colors.tabActiveText"
+              :label="$t('settings.text')"
+            />
+            <ContrastRatio :contrast="previewContrast.tabActiveText" />
+          </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.borders') }}</h4>
             <ColorInput
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index 3e5eacd5..df585faa 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -52,6 +52,11 @@
         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;
 
@@ -63,6 +68,8 @@
         &.active {
           background: transparent;
           z-index: 5;
+          color: $fallback--text;
+          color: var(--tabActiveText, $fallback--text);
         }
 
         img {
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 93d55fff..e286ceea 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -151,7 +151,7 @@
               </ProgressButton>
               <ProgressButton
                 v-else
-                class="btn btn-default pressed"
+                class="btn btn-default toggled"
                 :click="unsubscribeUser"
                 :title="$t('user_card.unsubscribe')"
               >
@@ -162,7 +162,7 @@
           <div>
             <button
               v-if="user.muted"
-              class="btn btn-default btn-block pressed"
+              class="btn btn-default btn-block toggled"
               @click="unmuteUser"
             >
               {{ $t('user_card.muted') }}
@@ -538,12 +538,6 @@
 
     button {
       margin: 0;
-
-      &.pressed {
-        // TODO: This should be themed.
-        border-bottom-color: rgba(255, 255, 255, 0.2);
-        border-top-color: rgba(0, 0, 0, 0.2);
-      }
     }
   }
 }
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index c5dd8047..a345d996 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -219,6 +219,19 @@ export const SLOT_INHERITANCE = {
     textColor: 'preserve'
   },
 
+  // Tabs
+  tab: '--btn',
+  tabText: {
+    depends: ['btnText'],
+    layer: 'btn',
+    textColor: true
+  },
+  tabActiveText: {
+    depends: ['text'],
+    layer: 'bg',
+    textColor: true
+  },
+
   // Buttons
   btn: '--fg',
   btnText: {
@@ -239,6 +252,7 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
 
+  // Buttons: pressed
   btnPressed: '--btn',
   btnPressedText: {
     depends: ['btnText'],
@@ -259,6 +273,31 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
 
+  // Buttons: toggled
+  btnToggled: {
+    depends: ['btn'],
+    color: (mod, btn) => brightness(mod * 20, btn).rgb
+  },
+  btnToggledText: {
+    depends: ['btnText'],
+    layer: 'btn',
+    variant: 'btnToggled',
+    textColor: true
+  },
+  btnToggledPanelText: {
+    depends: ['btnPanelText'],
+    layer: 'btnPanel',
+    variant: 'btnToggled',
+    textColor: true
+  },
+  btnToggledTopBarText: {
+    depends: ['btnTopBarText'],
+    layer: 'btnTopBar',
+    variant: 'btnToggled',
+    textColor: true
+  },
+
+  // Buttons: disabled
   btnDisabled: {
     depends: ['btn', 'bg'],
     color: (mod, btn, bg) => alphaBlend(btn, 0.5, bg)

From c3cd66335fd5971d28d310e8742be95dab2678a1 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Jan 2020 22:26:31 +0200
Subject: [PATCH 145/483] i18n & fixes

---
 src/App.scss     | 18 +++++++++---------
 src/i18n/en.json |  6 +++++-
 2 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 70983fe2..dd994a38 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -376,21 +376,21 @@ i[class*=icon-] {
 
     &:active {
       background-color: $fallback--fg;
-      background-color: var(--btnTopBarPressed, $fallback--fg);
+      background-color: var(--btnPressedTopBar, $fallback--fg);
       color: $fallback--text;
-      color: var(--btnTopBarPressedText, $fallback--text);
+      color: var(--btnPressedTopBarText, $fallback--text);
     }
 
     &:disabled {
       color: $fallback--text;
-      color: var(--btnTopBarDisabledText, $fallback--text);
+      color: var(--btnDisabledTopBarText, $fallback--text);
     }
 
     &.toggled {
       color: $fallback--text;
-      color: var(--btnTopBarToggledText, $fallback--text);
+      color: var(--btnToggledTopBarText, $fallback--text);
       background-color: $fallback--fg;
-      background-color: var(--btnTopBarToggled, $fallback--fg)
+      background-color: var(--btnToggledTopBar, $fallback--fg)
     }
   }
 
@@ -563,19 +563,19 @@ main-router {
 
     &:active {
       background-color: $fallback--fg;
-      background-color: var(--btnPanelPressed, $fallback--fg);
+      background-color: var(--btnPressedPanel, $fallback--fg);
       color: $fallback--text;
-      color: var(--btnPanelPressedText, $fallback--text);
+      color: var(--btnPressedPanelText, $fallback--text);
     }
 
     &:disabled {
       color: $fallback--text;
-      color: var(--btnPanelDisabledText, $fallback--text);
+      color: var(--btnDisabledPanelText, $fallback--text);
     }
 
     &.toggled {
       color: $fallback--text;
-      color: var(--btnPanelToggledText, $fallback--text);
+      color: var(--btnToggledPanelText, $fallback--text);
     }
   }
 
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 544bc2ba..f7a26262 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -435,7 +435,11 @@
         "poll": "Poll graph",
         "icons": "Icons",
         "lightBg": "Highlighted elements",
-        "pressed": "Pressed"
+        "pressed": "Pressed",
+        "selectedPost": "Selected post",
+        "selectedMenu": "Selected menu item",
+        "disabled": "Disabled",
+        "tabs": "Tabs"
       },
       "radii": {
         "_tab_label": "Roundness"

From 33abbed5a1e1d1cf99d21d481b2a22481d7533b2 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Mon, 13 Jan 2020 23:34:39 +0200
Subject: [PATCH 146/483] usable-but-buggy: picker, adding/removing reaction on
 click, search, styles

---
 src/components/react_button/react_button.js   | 25 ++++----
 src/components/react_button/react_button.vue  | 37 +++++++++--
 src/components/status/status.js               | 21 +++++--
 src/components/status/status.vue              | 24 ++++++--
 src/modules/statuses.js                       | 61 ++++++++++++++++++-
 src/services/api/api.service.js               | 22 +++++++
 .../backend_interactor_service.js             |  4 ++
 7 files changed, 166 insertions(+), 28 deletions(-)

diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index d1d15d93..76a49305 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -6,6 +6,7 @@ const ReactButton = {
     return {
       animated: false,
       showTooltip: false,
+      filterWord: '',
       popperOptions: {
         modifiers: {
           preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
@@ -14,27 +15,25 @@ const ReactButton = {
     }
   },
   methods: {
-    openReactionSelect () {
-      console.log('test')
-      this.showTooltip = true
+    toggleReactionSelect () {
+      this.showTooltip = !this.showTooltip
     },
     closeReactionSelect () {
       this.showTooltip = false
     },
-    favorite () {
-      if (!this.status.favorited) {
-        this.$store.dispatch('favorite', { id: this.status.id })
-      } else {
-        this.$store.dispatch('unfavorite', { id: this.status.id })
-      }
-      this.animated = true
-      setTimeout(() => {
-        this.animated = false
-      }, 500)
+    addReaction (event, emoji) {
+      this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
+      this.closeReactionSelect()
     }
   },
   computed: {
+    commonEmojis () {
+      return ['💖', '😠', '👀', '😂', '🔥']
+    },
     emojis () {
+      if (this.filterWord !== '') {
+        return this.$store.state.instance.emoji.filter(emoji => emoji.displayText.includes(this.filterWord))
+      }
       return this.$store.state.instance.emoji || []
     },
     classes () {
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index 93638770..f7015316 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -5,21 +5,37 @@
     trigger="manual"
     placement="top"
     class="react-button-popover"
-    @close-group="closeReactionSelect"
+    @hide="closeReactionSelect"
   >
     <div slot="popover">
+      <div class="reaction-picker-filter">
+        <input v-model="filterWord" placeholder="Search...">
+      </div>
       <div class="reaction-picker">
+        <span
+          v-for="emoji in commonEmojis"
+          :key="emoji"
+          class="emoji-reaction-button"
+          @click="addReaction($event, emoji)"
+        >
+          {{ emoji }}
+        </span>
+        <div class="reaction-picker-divider" />
         <span
           v-for="(emoji, key) in emojis"
           :key="key"
           class="emoji-reaction-button"
+          @click="addReaction($event, emoji.replacement)"
         >
           {{ emoji.replacement }}
         </span>
         <div class="reaction-bottom-fader" />
       </div>
     </div>
-    <div @click.prevent="openReactionSelect" v-if="loggedIn">
+    <div
+      v-if="loggedIn"
+      @click.prevent="toggleReactionSelect"
+    >
       <i
         :class="classes"
         class="button-icon favorite-button fav-active"
@@ -35,15 +51,28 @@
 <style lang="scss">
 @import '../../_variables.scss';
 
+.reaction-picker-filter {
+  padding: 0.5em;
+}
+
+.reaction-picker-divider {
+  height: 1px;
+  width: 100%;
+  margin: 0.4em;
+  background-color: var(--border, $fallback--border);
+}
+
 .reaction-picker {
   width: 10em;
-  height: 8em;
+  height: 9em;
   font-size: 1.5em;
   overflow-y: scroll;
   display: flex;
   flex-wrap: wrap;
   padding: 0.5em;
-  text-align:center;
+  text-align: center;
+  align-content: flex-start;
+  user-select: none;
 
   mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
     linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 8c6fc0cf..cc0c9e06 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -280,10 +280,7 @@ const Status = {
       return this.mergedConfig.hidePostStats
     },
     emojiReactions () {
-      return {
-        '🤔': [{ 'id': 'xyz..' }, { 'id': 'zyx...' }],
-        '🐻': [{ 'id': 'abc...' }]
-      }
+      return this.status.emojiReactions
     },
     ...mapGetters(['mergedConfig'])
   },
@@ -385,6 +382,22 @@ const Status = {
     setMedia () {
       const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
       return () => this.$store.dispatch('setMedia', attachments)
+    },
+    reactedWith (emoji) {
+      return this.status.reactedWithEmoji.includes(emoji)
+    },
+    reactWith (emoji) {
+      this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
+    },
+    unreact (emoji) {
+      this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
+    },
+    emojiOnClick (emoji, event) {
+      if (this.reactedWith(emoji)) {
+        this.unreact(emoji)
+      } else {
+        this.reactWith(emoji)
+      }
     }
   },
   watch: {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index d455ccf6..503de98d 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -354,13 +354,15 @@
             </div>
           </transition>
 
-          <div class="emoji-reactions">
+          <div v-if="isFocused" class="emoji-reactions">
             <button
               v-for="(users, emoji) in emojiReactions"
               :key="emoji"
               class="emoji-reaction btn btn-default"
+              :class="{ 'picked-reaction': reactedWith(emoji) }"
+              @click="emojiOnClick(emoji, $event)"
             >
-              <span>{{ users.length }}</span>
+              <span v-if="users">{{ users.length }}</span>
               <span>{{ emoji }}</span>
             </button>
           </div>
@@ -788,19 +790,33 @@ $status-margin: 0.75em;
 
 .emoji-reactions {
   display: flex;
-  margin-top: 0.75em;
+  margin-top: 0.25em;
+  flex-wrap: wrap;
 }
 
 .emoji-reaction {
   padding: 0 0.5em;
   margin-right: 0.5em;
+  margin-top: 0.5em;
   display: flex;
   align-items: center;
   justify-content: center;
-
+  box-sizing: border-box;
   :first-child {
     margin-right: 0.25em;
   }
+  :last-child {
+    width: 1.5em;
+  }
+  &:focus {
+    outline: none;
+  }
+}
+
+.picked-reaction {
+  border: 1px solid var(--link, $fallback--link);
+  margin-left: -1px; // offset the border, can't use inset shadows either
+  margin-right: calc(0.5em - 1px);
 }
 
 .button-icon.icon-reply {
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index c285b452..fcb6d1f3 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -1,4 +1,20 @@
-import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy, findKey } from 'lodash'
+import {
+  remove,
+  slice,
+  each,
+  findIndex,
+  find,
+  maxBy,
+  minBy,
+  merge,
+  first,
+  last,
+  isArray,
+  omitBy,
+  flow,
+  filter,
+  keys
+} from 'lodash'
 import { set } from 'vue'
 import apiService from '../services/api/api.service.js'
 // import parse from '../services/status_parser/status_parser.js'
@@ -512,8 +528,29 @@ export const mutations = {
   },
   addEmojiReactions (state, { id, emojiReactions, currentUser }) {
     const status = state.allStatusesObject[id]
-    status.emojiReactions = emojiReactions
-    status.reactedWithEmoji = findKey(emojiReactions, { id: currentUser.id })
+    set(status, 'emojiReactions', emojiReactions)
+    const reactedWithEmoji = flow(keys, filter(reaction => find(reaction, { id: currentUser.id })))(emojiReactions)
+    set(status, 'reactedWithEmoji', reactedWithEmoji)
+  },
+  addOwnReaction (state, { id, emoji, currentUser }) {
+    const status = state.allStatusesObject[id]
+    status.emojiReactions = status.emojiReactions || {}
+    const listOfUsers = (status.emojiReactions && status.emojiReactions[emoji]) || []
+    const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id })
+    if (!hasSelfAlready) {
+      set(status.emojiReactions, emoji, listOfUsers.concat([{ id: currentUser.id }]))
+      set(status, 'reactedWithEmoji', emoji)
+    }
+  },
+  removeOwnReaction (state, { id, emoji, currentUser }) {
+    const status = state.allStatusesObject[id]
+    const listOfUsers = status.emojiReactions[emoji] || []
+    const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id })
+    if (hasSelfAlready) {
+      const newUsers = filter(listOfUsers, user => user.id !== currentUser.id)
+      set(status.emojiReactions, emoji, newUsers)
+      set(status, 'reactedWith', emoji)
+    }
   },
   updateStatusWithPoll (state, { id, poll }) {
     const status = state.allStatusesObject[id]
@@ -616,6 +653,24 @@ const statuses = {
         commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
       })
     },
+    reactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) {
+      const currentUser = rootState.users.currentUser
+      commit('addOwnReaction', { id, emoji, currentUser })
+      rootState.api.backendInteractor.reactWithEmoji(id, emoji).then(
+        status => {
+          dispatch('fetchEmojiReactions', id)
+        }
+      )
+    },
+    unreactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) {
+      const currentUser = rootState.users.currentUser
+      commit('removeOwnReaction', { id, emoji, currentUser })
+      rootState.api.backendInteractor.unreactWithEmoji(id, emoji).then(
+        status => {
+          dispatch('fetchEmojiReactions', id)
+        }
+      )
+    },
     fetchEmojiReactions ({ rootState, commit }, id) {
       rootState.api.backendInteractor.fetchEmojiReactions(id).then(
         emojiReactions => {
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 7ef4b74a..2e96264a 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -72,6 +72,8 @@ const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
 const MASTODON_SEARCH_2 = `/api/v2/search`
 const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
 const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by`
+const PLEROMA_EMOJI_REACT_URL = id => `/api/v1/pleroma/statuses/${id}/react_with_emoji`
+const PLEROMA_EMOJI_UNREACT_URL = id => `/api/v1/pleroma/statuses/${id}/unreact_with_emoji`
 
 const oldfetch = window.fetch
 
@@ -869,6 +871,24 @@ const fetchEmojiReactions = ({ id }) => {
   return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) })
 }
 
+const reactWithEmoji = ({ id, emoji, credentials }) => {
+  return promisedRequest({
+    url: PLEROMA_EMOJI_REACT_URL(id),
+    method: 'POST',
+    credentials,
+    payload: { emoji }
+  }).then(status => parseStatus(status))
+}
+
+const unreactWithEmoji = ({ id, emoji, credentials }) => {
+  return promisedRequest({
+    url: PLEROMA_EMOJI_UNREACT_URL(id),
+    method: 'POST',
+    credentials,
+    payload: { emoji }
+  }).then(parseStatus)
+}
+
 const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
   return promisedRequest({
     url: MASTODON_REPORT_USER_URL,
@@ -1003,6 +1023,8 @@ const apiService = {
   fetchFavoritedByUsers,
   fetchRebloggedByUsers,
   fetchEmojiReactions,
+  reactWithEmoji,
+  unreactWithEmoji,
   reportUser,
   updateNotificationSettings,
   search2,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 52234fcc..44233a24 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -144,6 +144,8 @@ const backendInteractorService = credentials => {
   const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id })
   const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id })
   const fetchEmojiReactions = (id) => apiService.fetchEmojiReactions({ id })
+  const reactWithEmoji = (id, emoji) => apiService.reactWithEmoji({ id, emoji, credentials })
+  const unreactWithEmoji = (id, emoji) => apiService.unreactWithEmoji({ id, emoji, credentials })
   const reportUser = (params) => apiService.reportUser({ credentials, ...params })
 
   const favorite = (id) => apiService.favorite({ id, credentials })
@@ -212,6 +214,8 @@ const backendInteractorService = credentials => {
     fetchFavoritedByUsers,
     fetchRebloggedByUsers,
     fetchEmojiReactions,
+    reactWithEmoji,
+    unreactWithEmoji,
     reportUser,
     favorite,
     unfavorite,

From b10b92a876eb185a88e751d028e69063c9117298 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Tue, 14 Jan 2020 10:06:14 +0200
Subject: [PATCH 147/483] clean up code, fix prediction bug

---
 .../emoji_reactions/emoji_reactions.js        | 30 +++++++++++
 .../emoji_reactions/emoji_reactions.vue       | 51 +++++++++++++++++++
 src/components/react_button/react_button.js   |  5 +-
 src/components/react_button/react_button.vue  | 46 +++++++++--------
 src/components/status/status.js               | 23 ++-------
 src/components/status/status.vue              | 47 ++---------------
 src/modules/statuses.js                       |  9 ++--
 7 files changed, 122 insertions(+), 89 deletions(-)
 create mode 100644 src/components/emoji_reactions/emoji_reactions.js
 create mode 100644 src/components/emoji_reactions/emoji_reactions.vue

diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
new file mode 100644
index 00000000..e81e6e25
--- /dev/null
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -0,0 +1,30 @@
+
+const EmojiReactions = {
+  name: 'EmojiReactions',
+  props: ['status'],
+  computed: {
+    emojiReactions () {
+      return this.status.emojiReactions
+    }
+  },
+  methods: {
+    reactedWith (emoji) {
+      return this.status.reactedWithEmoji.includes(emoji)
+    },
+    reactWith (emoji) {
+      this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
+    },
+    unreact (emoji) {
+      this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
+    },
+    emojiOnClick (emoji, event) {
+      if (this.reactedWith(emoji)) {
+        this.unreact(emoji)
+      } else {
+        this.reactWith(emoji)
+      }
+    }
+  }
+}
+
+export default EmojiReactions
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
new file mode 100644
index 00000000..d83f60b6
--- /dev/null
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -0,0 +1,51 @@
+<template>
+  <div class="emoji-reactions">
+    <button
+      v-for="(users, emoji) in emojiReactions"
+      :key="emoji"
+      class="emoji-reaction btn btn-default"
+      :class="{ 'picked-reaction': reactedWith(emoji) }"
+      @click="emojiOnClick(emoji, $event)"
+    >
+      <span v-if="users">{{ users.length }}</span>
+      <span>{{ emoji }}</span>
+    </button>
+  </div>
+</template>
+
+<script src="./emoji_reactions.js" ></script>
+<style lang="scss">
+@import '../../_variables.scss';
+
+.emoji-reactions {
+  display: flex;
+  margin-top: 0.25em;
+  flex-wrap: wrap;
+}
+
+.emoji-reaction {
+  padding: 0 0.5em;
+  margin-right: 0.5em;
+  margin-top: 0.5em;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-sizing: border-box;
+  :first-child {
+    margin-right: 0.25em;
+  }
+  :last-child {
+    width: 1.5em;
+  }
+  &:focus {
+    outline: none;
+  }
+}
+
+.picked-reaction {
+  border: 1px solid var(--link, $fallback--link);
+  margin-left: -1px; // offset the border, can't use inset shadows either
+  margin-right: calc(0.5em - 1px);
+}
+
+</style>
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index 76a49305..d1a179bc 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -15,8 +15,9 @@ const ReactButton = {
     }
   },
   methods: {
-    toggleReactionSelect () {
-      this.showTooltip = !this.showTooltip
+    openReactionSelect () {
+      this.showTooltip = true
+      this.filterWord = ''
     },
     closeReactionSelect () {
       this.showTooltip = false
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index f7015316..ae975dee 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -9,13 +9,16 @@
   >
     <div slot="popover">
       <div class="reaction-picker-filter">
-        <input v-model="filterWord" placeholder="Search...">
+        <input
+          v-model="filterWord"
+          :placeholder="$t('emoji.search_emoji')"
+        >
       </div>
       <div class="reaction-picker">
         <span
           v-for="emoji in commonEmojis"
           :key="emoji"
-          class="emoji-reaction-button"
+          class="emoji-button"
           @click="addReaction($event, emoji)"
         >
           {{ emoji }}
@@ -24,7 +27,7 @@
         <span
           v-for="(emoji, key) in emojis"
           :key="key"
-          class="emoji-reaction-button"
+          class="emoji-button"
           @click="addReaction($event, emoji.replacement)"
         >
           {{ emoji.replacement }}
@@ -34,11 +37,11 @@
     </div>
     <div
       v-if="loggedIn"
-      @click.prevent="toggleReactionSelect"
+      @click.prevent="openReactionSelect"
     >
       <i
         :class="classes"
-        class="button-icon favorite-button fav-active"
+        class="button-icon add-reaction-button"
         :title="$t('tool_tip.add_reaction')"
       />
       <span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span>
@@ -58,7 +61,7 @@
 .reaction-picker-divider {
   height: 1px;
   width: 100%;
-  margin: 0.4em;
+  margin: 0.5em;
   background-color: var(--border, $fallback--border);
 }
 
@@ -82,26 +85,27 @@
   // Autoprefixed seem to ignore this one, and also syntax is different
   -webkit-mask-composite: xor;
   mask-composite: exclude;
-}
 
-.emoji-reaction-button {
-  flex-basis: 20%;
-  line-height: 1.5em;
-  align-content: center;
-}
+  .emoji-button {
+    cursor: pointer;
 
-.fav-active {
-  cursor: pointer;
-  animation-duration: 0.6s;
+    flex-basis: 20%;
+    line-height: 1.5em;
+    align-content: center;
 
-  &:hover {
-    color: $fallback--cOrange;
-    color: var(--cOrange, $fallback--cOrange);
+    &:hover {
+      transform: scale(1.25);
+    }
   }
 }
 
-.favorite-button.icon-star {
-  color: $fallback--cOrange;
-  color: var(--cOrange, $fallback--cOrange);
+.add-reaction-button {
+  cursor: pointer;
+
+  &:hover {
+    color: $fallback--text;
+    color: var(--text, $fallback--text);
+  }
 }
+
 </style>
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 18617938..81b57667 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -12,6 +12,7 @@ import LinkPreview from '../link-preview/link-preview.vue'
 import AvatarList from '../avatar_list/avatar_list.vue'
 import Timeago from '../timeago/timeago.vue'
 import StatusPopover from '../status_popover/status_popover.vue'
+import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 import fileType from 'src/services/file_type/file_type.service'
 import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
@@ -311,9 +312,6 @@ const Status = {
     hidePostStats () {
       return this.mergedConfig.hidePostStats
     },
-    emojiReactions () {
-      return this.status.emojiReactions
-    },
     ...mapGetters(['mergedConfig']),
     ...mapState({
       betterShadow: state => state.interface.browserSupport.cssFilter,
@@ -334,7 +332,8 @@ const Status = {
     LinkPreview,
     AvatarList,
     Timeago,
-    StatusPopover
+    StatusPopover,
+    EmojiReactions
   },
   methods: {
     visibilityIcon (visibility) {
@@ -418,22 +417,6 @@ const Status = {
     setMedia () {
       const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
       return () => this.$store.dispatch('setMedia', attachments)
-    },
-    reactedWith (emoji) {
-      return this.status.reactedWithEmoji.includes(emoji)
-    },
-    reactWith (emoji) {
-      this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
-    },
-    unreact (emoji) {
-      this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
-    },
-    emojiOnClick (emoji, event) {
-      if (this.reactedWith(emoji)) {
-        this.unreact(emoji)
-      } else {
-        this.reactWith(emoji)
-      }
     }
   },
   watch: {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 4ea1b74b..87e8b5da 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -354,18 +354,10 @@
             </div>
           </transition>
 
-          <div v-if="isFocused" class="emoji-reactions">
-            <button
-              v-for="(users, emoji) in emojiReactions"
-              :key="emoji"
-              class="emoji-reaction btn btn-default"
-              :class="{ 'picked-reaction': reactedWith(emoji) }"
-              @click="emojiOnClick(emoji, $event)"
-            >
-              <span v-if="users">{{ users.length }}</span>
-              <span>{{ emoji }}</span>
-            </button>
-          </div>
+          <EmojiReactions
+            v-if="isFocused"
+            :status="status"
+          />
 
           <div
             v-if="!noHeading && !isPreview"
@@ -789,37 +781,6 @@ $status-margin: 0.75em;
   }
 }
 
-.emoji-reactions {
-  display: flex;
-  margin-top: 0.25em;
-  flex-wrap: wrap;
-}
-
-.emoji-reaction {
-  padding: 0 0.5em;
-  margin-right: 0.5em;
-  margin-top: 0.5em;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  box-sizing: border-box;
-  :first-child {
-    margin-right: 0.25em;
-  }
-  :last-child {
-    width: 1.5em;
-  }
-  &:focus {
-    outline: none;
-  }
-}
-
-.picked-reaction {
-  border: 1px solid var(--link, $fallback--link);
-  margin-left: -1px; // offset the border, can't use inset shadows either
-  margin-right: calc(0.5em - 1px);
-}
-
 .button-icon.icon-reply {
   &:not(.button-icon-disabled):hover,
   &.button-icon-active {
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index ae6f6853..dbae9d38 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -537,7 +537,10 @@ export const mutations = {
   addEmojiReactions (state, { id, emojiReactions, currentUser }) {
     const status = state.allStatusesObject[id]
     set(status, 'emojiReactions', emojiReactions)
-    const reactedWithEmoji = flow(keys, filter(reaction => find(reaction, { id: currentUser.id })))(emojiReactions)
+    const reactedWithEmoji = flow(
+      keys,
+      filter(reaction => find(reaction, { id: currentUser.id }))
+    )(emojiReactions)
     set(status, 'reactedWithEmoji', reactedWithEmoji)
   },
   addOwnReaction (state, { id, emoji, currentUser }) {
@@ -547,7 +550,7 @@ export const mutations = {
     const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id })
     if (!hasSelfAlready) {
       set(status.emojiReactions, emoji, listOfUsers.concat([{ id: currentUser.id }]))
-      set(status, 'reactedWithEmoji', emoji)
+      set(status, 'reactedWithEmoji', [...status.reactedWithEmoji, emoji])
     }
   },
   removeOwnReaction (state, { id, emoji, currentUser }) {
@@ -557,7 +560,7 @@ export const mutations = {
     if (hasSelfAlready) {
       const newUsers = filter(listOfUsers, user => user.id !== currentUser.id)
       set(status.emojiReactions, emoji, newUsers)
-      set(status, 'reactedWith', emoji)
+      set(status, 'reactedWithEmoji', status.reactedWithEmoji.filter(e => e !== emoji))
     }
   },
   updateStatusWithPoll (state, { id, poll }) {

From 910b82e231fd3a6fe2e53990d87972066fe1a23c Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Tue, 14 Jan 2020 11:13:59 +0200
Subject: [PATCH 148/483] Use last seen notif instead of first unseen notif for
 sinceId

---
 .../notifications_fetcher/notifications_fetcher.service.js | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index 47008026..64499a1b 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -2,7 +2,6 @@ import apiService from '../api/api.service.js'
 
 const update = ({ store, notifications, older }) => {
   store.dispatch('setNotificationsError', { value: false })
-
   store.dispatch('addNewNotifications', { notifications, older })
 }
 
@@ -30,9 +29,9 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
 
     // load unread notifications repeatedly to provide consistency between browser tabs
     const notifications = timelineData.data
-    const unread = notifications.filter(n => !n.seen).map(n => n.id)
-    if (unread.length) {
-      args['since'] = Math.min(...unread)
+    const readNotifsIds = notifications.filter(n => n.seen).map(n => n.id)
+    if (readNotifsIds.length) {
+      args['since'] = Math.max(...readNotifsIds)
       fetchNotifications({ store, args, older })
     }
 

From 9b8cdb652518e55cd126d70d3902a0953979c145 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Tue, 14 Jan 2020 13:39:01 +0200
Subject: [PATCH 149/483] update CHANGELOG

---
 CHANGELOG.md | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d6ef1a5..2e17dc64 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
+### Fixed
+- Single notifications left unread when hitting read on another device/tab
+
+## [1.1.8] - 2020-01-10
+### Added
+- Icons in nav panel
+- Private mode support
+- Support for 'Move' type notifications
+- Pleroma AMOLED dark theme
+### Changed
+- 403 messaging
+### Fixed
+- Deactivation of remote accounts from frontend
+
+## [1.1.7 and earlier] - 2019-12-14
 ### Added
 - Ability to hide/show repeats from user
 - User profile button clutter organized into a menu
@@ -11,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Started changelog anew
 - Ability to change user's email
 - About page
+- Added remote user redirect
 ### Changed
 - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes
 ### Fixed

From 86380f042976557d5260a3f5c2de0a9b0bcdbac6 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Tue, 14 Jan 2020 13:28:57 +0000
Subject: [PATCH 150/483] Optimize Notifications Rendering

---
 CHANGELOG.md                                  |  2 ++
 src/components/notifications/notifications.js | 27 ++++++++++++++++---
 .../notifications/notifications.vue           |  2 +-
 .../notification_utils/notification_utils.js  |  4 +--
 .../notification_utils.spec.js                |  4 +--
 5 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e17dc64..9f5a9305 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
+### Changed
+- Notifications column now cleans itself up to optimize performance when tab is left open for a long time
 ### Fixed
 - Single notifications left unread when hitting read on another device/tab
 
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index a89c0cdc..26ffbab6 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -2,10 +2,12 @@ import Notification from '../notification/notification.vue'
 import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
 import {
   notificationsFromStore,
-  visibleNotificationsFromStore,
+  filteredNotificationsFromStore,
   unseenNotificationsFromStore
 } from '../../services/notification_utils/notification_utils.js'
 
+const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
+
 const Notifications = {
   props: {
     // Disables display of panel header
@@ -18,7 +20,11 @@ const Notifications = {
   },
   data () {
     return {
-      bottomedOut: false
+      bottomedOut: false,
+      // How many seen notifications to display in the list. The more there are,
+      // the heavier the page becomes. This count is increased when loading
+      // older notifications, and cut back to default whenever hitting "Read!".
+      seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
     }
   },
   computed: {
@@ -34,14 +40,17 @@ const Notifications = {
     unseenNotifications () {
       return unseenNotificationsFromStore(this.$store)
     },
-    visibleNotifications () {
-      return visibleNotificationsFromStore(this.$store, this.filterMode)
+    filteredNotifications () {
+      return filteredNotificationsFromStore(this.$store, this.filterMode)
     },
     unseenCount () {
       return this.unseenNotifications.length
     },
     loading () {
       return this.$store.state.statuses.notifications.loading
+    },
+    notificationsToDisplay () {
+      return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
     }
   },
   components: {
@@ -64,12 +73,21 @@ const Notifications = {
   methods: {
     markAsSeen () {
       this.$store.dispatch('markNotificationsAsSeen')
+      this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT
     },
     fetchOlderNotifications () {
       if (this.loading) {
         return
       }
 
+      const seenCount = this.filteredNotifications.length - this.unseenCount
+      if (this.seenToDisplayCount < seenCount) {
+        this.seenToDisplayCount = Math.min(this.seenToDisplayCount + 20, seenCount)
+        return
+      } else if (this.seenToDisplayCount > seenCount) {
+        this.seenToDisplayCount = seenCount
+      }
+
       const store = this.$store
       const credentials = store.state.users.currentUser.credentials
       store.commit('setNotificationsLoading', { value: true })
@@ -82,6 +100,7 @@ const Notifications = {
         if (notifs.length === 0) {
           this.bottomedOut = true
         }
+        this.seenToDisplayCount += notifs.length
       })
     }
   }
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index c42c35e6..d477a41b 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -32,7 +32,7 @@
       </div>
       <div class="panel-body">
         <div
-          v-for="notification in visibleNotifications"
+          v-for="notification in notificationsToDisplay"
           :key="notification.id"
           class="notification"
           :class="{&quot;unseen&quot;: !minimalMode && !notification.seen}"
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index b08514da..860620fc 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -26,7 +26,7 @@ const sortById = (a, b) => {
   }
 }
 
-export const visibleNotificationsFromStore = (store, types) => {
+export const filteredNotificationsFromStore = (store, types) => {
   // map is just to clone the array since sort mutates it and it causes some issues
   let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById)
   sortedNotifications = sortBy(sortedNotifications, 'seen')
@@ -36,4 +36,4 @@ export const visibleNotificationsFromStore = (store, types) => {
 }
 
 export const unseenNotificationsFromStore = store =>
-  filter(visibleNotificationsFromStore(store), ({ seen }) => !seen)
+  filter(filteredNotificationsFromStore(store), ({ seen }) => !seen)
diff --git a/test/unit/specs/services/notification_utils/notification_utils.spec.js b/test/unit/specs/services/notification_utils/notification_utils.spec.js
index 1baa5fc9..00628d83 100644
--- a/test/unit/specs/services/notification_utils/notification_utils.spec.js
+++ b/test/unit/specs/services/notification_utils/notification_utils.spec.js
@@ -1,7 +1,7 @@
 import * as NotificationUtils from 'src/services/notification_utils/notification_utils.js'
 
 describe('NotificationUtils', () => {
-  describe('visibleNotificationsFromStore', () => {
+  describe('filteredNotificationsFromStore', () => {
     it('should return sorted notifications with configured types', () => {
       const store = {
         state: {
@@ -47,7 +47,7 @@ describe('NotificationUtils', () => {
           type: 'like'
         }
       ]
-      expect(NotificationUtils.visibleNotificationsFromStore(store)).to.eql(expected)
+      expect(NotificationUtils.filteredNotificationsFromStore(store)).to.eql(expected)
     })
   })
 

From 662afe973a72efc17e61fd4c071ed985bfe4bf6b Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Tue, 14 Jan 2020 13:45:00 +0000
Subject: [PATCH 151/483] Fix #750 , fix error messages and captcha resetting

---
 CHANGELOG.md                                 |  2 ++
 src/components/registration/registration.js  |  3 ++-
 src/components/registration/registration.vue |  2 +-
 src/modules/users.js                         |  4 +++-
 src/services/errors/errors.js                | 14 ++++++++++----
 5 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f5a9305..6dcbc7da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,9 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
 ### Changed
+- Captcha now resets on failed registrations
 - Notifications column now cleans itself up to optimize performance when tab is left open for a long time
 ### Fixed
 - Single notifications left unread when hitting read on another device/tab
+- Registration fixed
 
 ## [1.1.8] - 2020-01-10
 ### Added
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index 57f3caf0..ace8cc7c 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -63,7 +63,8 @@ const registration = {
           await this.signUp(this.user)
           this.$router.push({ name: 'friends' })
         } catch (error) {
-          console.warn('Registration failed: ' + error)
+          console.warn('Registration failed: ', error)
+          this.setCaptcha()
         }
       }
     },
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 222b67a8..fdbda007 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -170,7 +170,7 @@
               <label
                 class="form--label"
                 for="captcha-label"
-              >{{ $t('captcha') }}</label>
+              >{{ $t('registration.captcha') }}</label>
 
               <template v-if="['kocaptcha', 'native'].includes(captcha.type)">
                 <img
diff --git a/src/modules/users.js b/src/modules/users.js
index 84fa0255..b9ed0efa 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -401,7 +401,9 @@ const users = {
       let rootState = store.rootState
 
       try {
-        let data = await rootState.api.backendInteractor.register({ ...userInfo })
+        let data = await rootState.api.backendInteractor.register(
+          { params: { ...userInfo } }
+        )
         store.commit('signUpSuccess')
         store.commit('setToken', data.access_token)
         store.dispatch('loginUser', data.access_token)
diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js
index 590552da..d4cf9132 100644
--- a/src/services/errors/errors.js
+++ b/src/services/errors/errors.js
@@ -32,12 +32,18 @@ export class RegistrationError extends Error {
       }
 
       if (typeof error === 'object') {
+        const errorContents = JSON.parse(error.error)
+        // keys will have the property that has the error, for example 'ap_id',
+        // 'email' or 'captcha', the value will be an array of its error
+        // like "ap_id": ["has been taken"] or "captcha": ["Invalid CAPTCHA"]
+
         // replace ap_id with username
-        if (error.ap_id) {
-          error.username = error.ap_id
-          delete error.ap_id
+        if (errorContents.ap_id) {
+          errorContents.username = errorContents.ap_id
+          delete errorContents.ap_id
         }
-        this.message = humanizeErrors(error)
+
+        this.message = humanizeErrors(errorContents)
       } else {
         this.message = error
       }

From 7397636914a9d3e7fd30373034c25175273ab808 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Tue, 14 Jan 2020 16:01:30 +0200
Subject: [PATCH 152/483] change changelog to reflect actual release history of
 frontend

---
 CHANGELOG.md | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6dcbc7da..42554607 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,22 +4,18 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
-### Changed
-- Captcha now resets on failed registrations
-- Notifications column now cleans itself up to optimize performance when tab is left open for a long time
-### Fixed
-- Single notifications left unread when hitting read on another device/tab
-- Registration fixed
-
-## [1.1.8] - 2020-01-10
 ### Added
 - Icons in nav panel
 - Private mode support
 - Support for 'Move' type notifications
 - Pleroma AMOLED dark theme
 ### Changed
+- Captcha now resets on failed registrations
+- Notifications column now cleans itself up to optimize performance when tab is left open for a long time
 - 403 messaging
 ### Fixed
+- Single notifications left unread when hitting read on another device/tab
+- Registration fixed
 - Deactivation of remote accounts from frontend
 
 ## [1.1.7 and earlier] - 2019-12-14

From 7a013ac39392ef251c0789f27dd4660dcd30bd6d Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Wed, 15 Jan 2020 20:22:54 +0000
Subject: [PATCH 153/483] Implement domain mutes v2

---
 .../domain_mute_card/domain_mute_card.js      |  15 ++
 .../domain_mute_card/domain_mute_card.vue     |  38 +++++
 src/components/user_settings/user_settings.js |  19 ++-
 .../user_settings/user_settings.vue           | 161 +++++++++++++-----
 src/i18n/en.json                              |   9 +
 src/modules/users.js                          |  44 +++++
 src/services/api/api.service.js               |  28 ++-
 7 files changed, 265 insertions(+), 49 deletions(-)
 create mode 100644 src/components/domain_mute_card/domain_mute_card.js
 create mode 100644 src/components/domain_mute_card/domain_mute_card.vue

diff --git a/src/components/domain_mute_card/domain_mute_card.js b/src/components/domain_mute_card/domain_mute_card.js
new file mode 100644
index 00000000..c8e838ba
--- /dev/null
+++ b/src/components/domain_mute_card/domain_mute_card.js
@@ -0,0 +1,15 @@
+import ProgressButton from '../progress_button/progress_button.vue'
+
+const DomainMuteCard = {
+  props: ['domain'],
+  components: {
+    ProgressButton
+  },
+  methods: {
+    unmuteDomain () {
+      return this.$store.dispatch('unmuteDomain', this.domain)
+    }
+  }
+}
+
+export default DomainMuteCard
diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue
new file mode 100644
index 00000000..567d81c5
--- /dev/null
+++ b/src/components/domain_mute_card/domain_mute_card.vue
@@ -0,0 +1,38 @@
+<template>
+  <div class="domain-mute-card">
+    <div class="domain-mute-card-domain">
+      {{ domain }}
+    </div>
+    <ProgressButton
+      :click="unmuteDomain"
+      class="btn btn-default"
+    >
+      {{ $t('domain_mute_card.unmute') }}
+      <template slot="progress">
+        {{ $t('domain_mute_card.unmute_progress') }}
+      </template>
+    </ProgressButton>
+  </div>
+</template>
+
+<script src="./domain_mute_card.js"></script>
+
+<style lang="scss">
+.domain-mute-card {
+  flex: 1 0;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0.6em 1em 0.6em 0;
+
+  &-domain {
+    margin-right: 1em;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  button {
+    width: 10em;
+  }
+}
+</style>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index d5d671e4..1709b48f 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -9,6 +9,7 @@ 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'
@@ -32,6 +33,12 @@ const MuteList = withSubscription({
   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 UserSettings = {
   data () {
     return {
@@ -67,7 +74,8 @@ const UserSettings = {
       changedPassword: false,
       changePasswordError: false,
       activeTab: 'profile',
-      notificationSettings: this.$store.state.users.currentUser.notification_settings
+      notificationSettings: this.$store.state.users.currentUser.notification_settings,
+      newDomainToMute: ''
     }
   },
   created () {
@@ -80,10 +88,12 @@ const UserSettings = {
     ImageCropper,
     BlockList,
     MuteList,
+    DomainMuteList,
     EmojiInput,
     Autosuggest,
     BlockCard,
     MuteCard,
+    DomainMuteCard,
     ProgressButton,
     Importer,
     Exporter,
@@ -365,6 +375,13 @@ const UserSettings = {
     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 = '' })
+    },
     identity (value) {
       return value
     }
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 3f1982a6..2222c293 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -509,59 +509,114 @@
         </div>
 
         <div :label="$t('settings.mutes_tab')">
-          <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)"
+          <tab-switcher>
+            <div label="Users">
+              <div class="profile-edit-usersearch-wrapper">
+                <Autosuggest
+                  :filter="filterUnMutedUsers"
+                  :query="queryUserIds"
+                  :placeholder="$t('settings.search_user_to_mute')"
                 >
-                  {{ $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)"
+                  <MuteCard
+                    slot-scope="row"
+                    :user-id="row.item"
+                  />
+                </Autosuggest>
+              </div>
+              <MuteList
+                :refresh="true"
+                :get-key="identity"
+              >
+                <template
+                  slot="header"
+                  slot-scope="{selected}"
                 >
-                  {{ $t('user_card.unmute') }}
+                  <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('user_card.unmute_progress') }}
+                    {{ $t('domain_mute_card.mute_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>
+              <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>
@@ -639,6 +694,18 @@
     }
   }
 
+  &-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/i18n/en.json b/src/i18n/en.json
index 75d66b9f..31f4ac24 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -21,6 +21,12 @@
   "chat": {
     "title": "Chat"
   },
+  "domain_mute_card": {
+    "mute": "Mute",
+    "mute_progress": "Muting...",
+    "unmute": "Unmute",
+    "unmute_progress": "Unmuting..."
+  },
   "exporter": {
     "export": "Export",
     "processing": "Processing, you'll soon be asked to download your file"
@@ -264,6 +270,7 @@
     "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
     "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
     "discoverable": "Allow discovery of this account in search results and other services",
+    "domain_mutes": "Domains",
     "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
     "pad_emoji": "Pad emoji with spaces when adding from picker",
     "export_theme": "Save preset",
@@ -361,6 +368,7 @@
     "post_status_content_type": "Post status content type",
     "stop_gifs": "Play-on-hover GIFs",
     "streaming": "Enable automatic streaming of new posts when scrolled to the top",
+    "user_mutes": "Users",
     "useStreamingApi": "Receive posts and notifications real-time",
     "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
     "text": "Text",
@@ -369,6 +377,7 @@
     "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
     "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
     "tooltipRadius": "Tooltips/alerts",
+    "type_domains_to_mute": "Type in domains to mute",
     "upload_a_photo": "Upload a photo",
     "user_settings": "User Settings",
     "values": {
diff --git a/src/modules/users.js b/src/modules/users.js
index b9ed0efa..ce3e595d 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -72,6 +72,16 @@ const showReblogs = (store, userId) => {
     .then((relationship) => store.commit('updateUserRelationship', [relationship]))
 }
 
+const muteDomain = (store, domain) => {
+  return store.rootState.api.backendInteractor.muteDomain({ domain })
+    .then(() => store.commit('addDomainMute', domain))
+}
+
+const unmuteDomain = (store, domain) => {
+  return store.rootState.api.backendInteractor.unmuteDomain({ domain })
+    .then(() => store.commit('removeDomainMute', domain))
+}
+
 export const mutations = {
   setMuted (state, { user: { id }, muted }) {
     const user = state.usersObject[id]
@@ -177,6 +187,20 @@ export const mutations = {
       state.currentUser.muteIds.push(muteId)
     }
   },
+  saveDomainMutes (state, domainMutes) {
+    state.currentUser.domainMutes = domainMutes
+  },
+  addDomainMute (state, domain) {
+    if (state.currentUser.domainMutes.indexOf(domain) === -1) {
+      state.currentUser.domainMutes.push(domain)
+    }
+  },
+  removeDomainMute (state, domain) {
+    const index = state.currentUser.domainMutes.indexOf(domain)
+    if (index !== -1) {
+      state.currentUser.domainMutes.splice(index, 1)
+    }
+  },
   setPinnedToUser (state, status) {
     const user = state.usersObject[status.user.id]
     const index = user.pinnedStatusIds.indexOf(status.id)
@@ -297,6 +321,25 @@ const users = {
     unmuteUsers (store, ids = []) {
       return Promise.all(ids.map(id => unmuteUser(store, id)))
     },
+    fetchDomainMutes (store) {
+      return store.rootState.api.backendInteractor.fetchDomainMutes()
+        .then((domainMutes) => {
+          store.commit('saveDomainMutes', domainMutes)
+          return domainMutes
+        })
+    },
+    muteDomain (store, domain) {
+      return muteDomain(store, domain)
+    },
+    unmuteDomain (store, domain) {
+      return unmuteDomain(store, domain)
+    },
+    muteDomains (store, domains = []) {
+      return Promise.all(domains.map(domain => muteDomain(store, domain)))
+    },
+    unmuteDomains (store, domain = []) {
+      return Promise.all(domain.map(domain => unmuteDomain(store, domain)))
+    },
     fetchFriends ({ rootState, commit }, id) {
       const user = rootState.users.usersObject[id]
       const maxId = last(user.friendIds)
@@ -460,6 +503,7 @@ const users = {
               user.credentials = accessToken
               user.blockIds = []
               user.muteIds = []
+              user.domainMutes = []
               commit('setCurrentUser', user)
               commit('addNewUsers', [user])
 
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index ef0267aa..dcbedd8b 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -72,6 +72,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
 const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
 const MASTODON_SEARCH_2 = `/api/v2/search`
 const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
+const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
 const MASTODON_STREAMING = '/api/v1/streaming'
 
 const oldfetch = window.fetch
@@ -948,6 +949,28 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
     })
 }
 
+const fetchDomainMutes = ({ credentials }) => {
+  return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
+}
+
+const muteDomain = ({ domain, credentials }) => {
+  return promisedRequest({
+    url: MASTODON_DOMAIN_BLOCKS_URL,
+    method: 'POST',
+    payload: { domain },
+    credentials
+  })
+}
+
+const unmuteDomain = ({ domain, credentials }) => {
+  return promisedRequest({
+    url: MASTODON_DOMAIN_BLOCKS_URL,
+    method: 'DELETE',
+    payload: { domain },
+    credentials
+  })
+}
+
 export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
   return Object.entries({
     ...(credentials
@@ -1110,7 +1133,10 @@ const apiService = {
   reportUser,
   updateNotificationSettings,
   search2,
-  searchUsers
+  searchUsers,
+  fetchDomainMutes,
+  muteDomain,
+  unmuteDomain
 }
 
 export default apiService

From f052ac4a1e59685332bf3798ce3978d6304816d8 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Wed, 15 Jan 2020 22:38:31 +0200
Subject: [PATCH 154/483] Add domain mutes to changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 42554607..b09eb3a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Private mode support
 - Support for 'Move' type notifications
 - Pleroma AMOLED dark theme
+- User level domain mutes, under User Settings -> Mutes
 ### Changed
 - Captcha now resets on failed registrations
 - Notifications column now cleans itself up to optimize performance when tab is left open for a long time

From f16ec75c7011fa7e6d5deb7763553b2c70d9a86e Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 16 Jan 2020 20:53:05 +0200
Subject: [PATCH 155/483] opacity handling

---
 .../style_switcher/style_switcher.js          |  10 +-
 src/services/color_convert/color_convert.js   |   2 +-
 src/services/style_setter/style_setter.js     |  70 +------
 src/services/theme_data/theme_data.service.js | 193 +++++++++++++-----
 4 files changed, 149 insertions(+), 126 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 98c2cbc5..16be209a 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -15,7 +15,7 @@ import {
 import {
   CURRENT_VERSION,
   SLOT_INHERITANCE,
-  DEFAULT_OPACITY,
+  OPACITIES,
   getLayers
 } from '../../services/theme_data/theme_data.service.js'
 import ColorInput from '../color_input/color_input.vue'
@@ -74,8 +74,8 @@ export default {
         .map(key => [key, ''])
         .reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}),
 
-      ...Object.keys(DEFAULT_OPACITY)
-        .map(key => [key, undefined])
+      ...Object.keys(OPACITIES)
+        .map(key => console.log(key) || [key, ''])
         .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}),
 
       shadowSelected: undefined,
@@ -115,8 +115,8 @@ export default {
         .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
     },
     currentOpacity () {
-      return Object.keys(DEFAULT_OPACITY)
-        .map(key => [key, this[key + 'OpacityLocal']])
+      return Object.keys(OPACITIES)
+        .map(key => console.log(key) || [key, this[key + 'OpacityLocal']])
         .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
     },
     currentRadii () {
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index c727a9fe..6b228a58 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -159,7 +159,7 @@ export const hex2rgb = (hex) => {
  * @returns {Object} result
  */
 export const mixrgb = (a, b) => {
-  return Object.keys(a).reduce((acc, k) => {
+  return 'rgb'.split('').reduce((acc, k) => {
     acc[k] = (a[k] + b[k]) / 2
     return acc
   }, {})
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index c1a25101..9237a8dc 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,7 +1,7 @@
 import { times } from 'lodash'
 import { convert } from 'chromatism'
 import { rgb2hex, hex2rgb, rgba2css, getCssColor } from '../color_convert/color_convert.js'
-import { getColors, DEFAULT_OPACITY } from '../theme_data/theme_data.service.js'
+import { getColors } from '../theme_data/theme_data.service.js'
 
 // While this is not used anymore right now, I left it in if we want to do custom
 // styles that aren't just colors, so user can pick from a few different distinct
@@ -115,76 +115,12 @@ const getCssShadowFilter = (input) => {
 }
 
 export const generateColors = (themeData) => {
-  const rawOpacity = Object.assign({ ...DEFAULT_OPACITY }, Object.entries(themeData.opacity || {}).reduce((acc, [k, v]) => {
-    if (typeof v !== 'undefined') {
-      acc[k] = v
-    }
-    return acc
-  }, {}))
-
-  const inputColors = themeData.colors || themeData
-
-  const opacity = {
-    ...rawOpacity,
-    ...Object.entries(inputColors).reduce((acc, [k, v]) => {
-      if (v === 'transparent') {
-        acc[k] = 0
-      }
-      return acc
-    }, {})
-  }
-
-  // Cycle one: just whatever we have
-  const sourceColors = Object.entries(inputColors).reduce((acc, [k, v]) => {
-    if (typeof v === 'object') {
-      acc[k] = v
-    } else {
-      let value = v
-      if (v === 'transparent') {
-        // TODO: hack to keep rest of the code from complaining
-        value = '#FF00FF'
-      }
-      if (!value || value.startsWith('--')) {
-        acc[k] = value
-      } else {
-        acc[k] = hex2rgb(value)
-      }
-    }
-    return acc
-  }, {})
+  const sourceColors = themeData.colors || themeData
 
   const isLightOnDark = convert(sourceColors.bg).hsl.l < convert(sourceColors.text).hsl.l
   const mod = isLightOnDark ? 1 : -1
 
-  const colors = getColors(sourceColors, opacity, mod)
-
-  // Inheriting opacities
-  Object.entries(opacity).forEach(([ k, v ]) => {
-    if (typeof v === 'undefined') return
-    if (k === 'alert') {
-      colors.alertError.a = v
-      colors.alertWarning.a = v
-      return
-    }
-    if (k === 'faint') {
-      colors['faintLink'].a = v
-      colors['panelFaint'].a = v
-      colors['lightBgFaintText'].a = v
-      colors['lightBgFaintLink'].a = v
-    }
-    if (k === 'bg') {
-      colors['lightBg'].a = v
-    }
-    if (k === 'badge') {
-      colors['badgeNotification'].a = v
-      return
-    }
-    if (colors[k]) {
-      colors[k].a = v
-    } else {
-      console.error('Wrong key ' + k)
-    }
-  })
+  const { colors, opacity } = getColors(sourceColors, themeData.opacity || {}, mod)
 
   const htmlColors = Object.entries(colors)
     .reduce((acc, [k, v]) => {
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index a345d996..e76d70ed 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -26,24 +26,17 @@ export const LAYERS = {
 }
 
 export const DEFAULT_OPACITY = {
-  panel: 1,
-  btn: 1,
-  border: 1,
-  bg: 1,
-  badge: 1,
-  text: 1,
   alert: 0.5,
   input: 0.5,
   faint: 0.5,
-  underlay: 0.15,
-  poll: 1,
-  topBar: 1
+  underlay: 0.15
 }
 
 export const SLOT_INHERITANCE = {
   bg: {
     depends: [],
-    priority: 1
+    priority: 1,
+    opacity: 'bg'
   },
   fg: {
     depends: [],
@@ -53,7 +46,10 @@ export const SLOT_INHERITANCE = {
     depends: [],
     priority: 1
   },
-  underlay: '#000000',
+  underlay: {
+    default: '#000000',
+    opacity: 'underlay'
+  },
   link: {
     depends: ['accent'],
     priority: 1
@@ -62,8 +58,14 @@ export const SLOT_INHERITANCE = {
     depends: ['link'],
     priority: 1
   },
-  faint: '--text',
-  faintLink: '--link',
+  faint: {
+    depends: ['text'],
+    opacity: 'faint'
+  },
+  faintLink: {
+    depends: ['link'],
+    opacity: 'faint'
+  },
 
   cBlue: '#0000ff',
   cRed: '#FF0000',
@@ -158,11 +160,13 @@ export const SLOT_INHERITANCE = {
 
   border: {
     depends: ['fg'],
+    opacity: 'border',
     color: (mod, fg) => brightness(2 * mod, fg).rgb
   },
 
   poll: {
     depends: ['accent', 'bg'],
+    copacity: 'poll',
     color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg)
   },
   pollText: {
@@ -173,6 +177,7 @@ export const SLOT_INHERITANCE = {
 
   icon: {
     depends: ['bg', 'text'],
+    inheritsOpacity: false,
     color: (mod, bg, text) => mixrgb(bg, text)
   },
 
@@ -189,7 +194,10 @@ export const SLOT_INHERITANCE = {
   },
 
   // Panel header
-  panel: '--fg',
+  panel: {
+    depends: ['fg'],
+    opacity: 'panel'
+  },
   panelText: {
     depends: ['fgText'],
     layer: 'panel',
@@ -198,6 +206,7 @@ export const SLOT_INHERITANCE = {
   panelFaint: {
     depends: ['fgText'],
     layer: 'panel',
+    opacity: 'faint',
     textColor: true
   },
   panelLink: {
@@ -233,7 +242,10 @@ export const SLOT_INHERITANCE = {
   },
 
   // Buttons
-  btn: '--fg',
+  btn: {
+    depends: ['fg'],
+    opacity: 'btn'
+  },
   btnText: {
     depends: ['fgText'],
     layer: 'btn',
@@ -325,7 +337,10 @@ export const SLOT_INHERITANCE = {
   },
 
   // Input fields
-  input: '--fg',
+  input: {
+    depends: ['fg'],
+    opacity: 'input'
+  },
   inputText: {
     depends: ['text'],
     layer: 'input',
@@ -344,7 +359,10 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
 
-  alertError: '--cRed',
+  alertError: {
+    depends: ['cRed'],
+    opacity: 'alert'
+  },
   alertErrorText: {
     depends: ['text'],
     layer: 'alert',
@@ -358,7 +376,10 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
 
-  alertWarning: '--cOrange',
+  alertWarning: {
+    depends: ['cOrange'],
+    opacity: 'alert'
+  },
   alertWarningText: {
     depends: ['text'],
     layer: 'alert',
@@ -465,78 +486,144 @@ export const topoSort = (
   return output
 }
 
+export const getOpacitySlot = (
+  v,
+  inheritance = SLOT_INHERITANCE,
+  getDeps = getDependencies
+) => {
+  if (v.opacity === null) return
+  if (v.opacity) return v.opacity
+  const findInheritedOpacity = (val) => {
+    const depSlot = val.depends[0]
+    if (depSlot === undefined) return
+    const dependency = getDeps(depSlot, inheritance)[0]
+    if (dependency === undefined) return
+    if (dependency.opacity || dependency === null) {
+      return dependency.opacity
+    } else if (dependency.depends) {
+      return findInheritedOpacity(dependency)
+    } else {
+      return null
+    }
+  }
+  if (v.depends) {
+    return findInheritedOpacity(v)
+  }
+}
+
 export const SLOT_ORDERED = topoSort(
   Object.entries(SLOT_INHERITANCE)
     .sort(([aK, aV], [bK, bV]) => ((aV && aV.priority) || 0) - ((bV && bV.priority) || 0))
     .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
 )
 
-console.log(SLOT_ORDERED)
+export const SLOTS_OPACITIES_DICT = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
+  const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies)
+  if (opacity) {
+    return { ...acc, [k]: opacity }
+  } else {
+    return acc
+  }
+}, {})
 
-export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce((acc, key) => {
+export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
+  const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies)
+  if (opacity) {
+    return {
+      ...acc,
+      [opacity]: {
+        defaultValue: DEFAULT_OPACITY[opacity] || 1,
+        affectedSlots: [...((acc[opacity] && acc[opacity].affectedSlots) || []), k]
+      }
+    }
+  } else {
+    return acc
+  }
+}, {})
+
+export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => {
   const value = SLOT_INHERITANCE[key]
+  const isObject = typeof value === 'object'
+  const isString = typeof value === 'string'
   const sourceColor = sourceColors[key]
+  let outputColor = null
   if (sourceColor) {
+    // Color is defined in source color
     let targetColor = sourceColor
-    if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) {
+    if (targetColor === 'transparent') {
+      targetColor = {
+        // TODO: try to use alpha-blended background here
+        ...convert('#FF00FF').rgb,
+        a: 0
+      }
+    } else if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) {
+      // Color references other color
       const [variable, modifier] = sourceColor.split(/,/g).map(str => str.trim())
       const variableSlot = variable.substring(2)
-      targetColor = acc[variableSlot] || sourceColors[variableSlot]
+      targetColor = colors[variableSlot] || sourceColors[variableSlot]
       if (modifier) {
-        console.log(targetColor, acc, variableSlot)
         targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
       }
-      console.log(targetColor, acc, variableSlot)
+    } else if (typeof sourceColor === 'string' && sourceColor.startsWith('#')) {
+      targetColor = convert(targetColor).rgb
     }
-    return { ...acc, [key]: { ...targetColor } }
-  } else if (typeof value === 'string' && value.startsWith('#')) {
-    return { ...acc, [key]: convert(value).rgb }
+    outputColor = { ...targetColor }
+  } else if (isString && value.startsWith('#')) {
+    // slot: '#000000' shorthand
+    outputColor = convert(value).rgb
+  } else if (isObject && value.default) {
+    // same as above except in object form
+    outputColor = convert(value.default).rgb
   } else {
-    const isObject = typeof value === 'object'
+    // calculate color
     const defaultColorFunc = (mod, dep) => ({ ...dep })
     const deps = getDependencies(key, SLOT_INHERITANCE)
     const colorFunc = (isObject && value.color) || defaultColorFunc
 
     if (value.textColor) {
+      // textColor case
       const bg = alphaBlendLayers(
-        { ...acc[deps[0]] },
+        { ...colors[deps[0]] },
         getLayers(
           value.layer,
           value.variant || value.layer,
-          acc,
-          sourceOpacity
+          colors,
+          opacity
         )
       )
       if (value.textColor === 'bw') {
-        return {
-          ...acc,
-          [key]: contrastRatio(bg).rgb
-        }
+        outputColor = contrastRatio(bg).rgb
       } else {
-        let color = { ...acc[deps[0]] }
+        let color = { ...colors[deps[0]] }
         if (value.color) {
           const isLightOnDark = convert(bg).hsl.l < convert(color).hsl.l
           const mod = isLightOnDark ? 1 : -1
-          color = value.color(mod, ...deps.map((dep) => ({ ...acc[dep] })))
+          color = value.color(mod, ...deps.map((dep) => ({ ...colors[dep] })))
         }
 
-        return {
-          ...acc,
-          [key]: getTextColor(
-            bg,
-            { ...color },
-            value.textColor === 'preserve'
-          )
-        }
-      }
-    } else {
-      return {
-        ...acc,
-        [key]: colorFunc(
-          mod,
-          ...deps.map((dep) => ({ ...acc[dep] }))
+        outputColor = getTextColor(
+          bg,
+          { ...color },
+          value.textColor === 'preserve'
         )
       }
+    } else {
+      // background color case
+      outputColor = colorFunc(
+        mod,
+        ...deps.map((dep) => ({ ...colors[dep] }))
+      )
     }
   }
-}, {})
+  if (!outputColor) {
+    throw new Error('Couldn\'t generate color for ' + key)
+  }
+  const opacitySlot = SLOTS_OPACITIES_DICT[key]
+  if (opacitySlot && outputColor.a === undefined) {
+    outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1
+  }
+  return {
+    colors: { ...colors, [key]: outputColor },
+    opacity: { ...opacity, [opacitySlot]: outputColor.a }
+  }
+}, { colors: {}, opacity: {} })

From e070ec4b66e10c6f18acd0dbdb9dceb7eb0514b7 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 16 Jan 2020 21:34:33 +0200
Subject: [PATCH 156/483] more opacity handling

---
 .../style_switcher/style_switcher.js          |  5 ++++-
 src/services/theme_data/theme_data.service.js | 21 ++++++++++++-------
 2 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 16be209a..b78d8236 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -16,7 +16,8 @@ import {
   CURRENT_VERSION,
   SLOT_INHERITANCE,
   OPACITIES,
-  getLayers
+  getLayers,
+  getOpacitySlot
 } from '../../services/theme_data/theme_data.service.js'
 import ColorInput from '../color_input/color_input.vue'
 import RangeInput from '../range_input/range_input.vue'
@@ -162,6 +163,7 @@ export default {
         )
         if (!slotIsText) return acc
         const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
+        const opacitySlot = getOpacitySlot(SLOT_INHERITANCE[variant || layer])
         const background = variant || layer
         const textColors = [
           key,
@@ -171,6 +173,7 @@ export default {
         const layers = getLayers(
           layer,
           variant || layer,
+          opacitySlot,
           colorsConverted,
           opacity
         )
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index e76d70ed..5dbef554 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -412,14 +412,13 @@ export const getLayersArray = (layer, data = LAYERS) => {
   return array
 }
 
-export const getLayers = (layer, variant = layer, colors, opacity) => {
+export const getLayers = (layer, variant = layer, opacitySlot, colors, opacity) => {
   return getLayersArray(layer).map((currentLayer) => ([
     currentLayer === layer
       ? colors[variant]
       : colors[currentLayer],
-    // TODO: Remove this hack when opacities/layers system is improved
-    currentLayer.startsWith('btn')
-      ? opacity.btn
+    currentLayer === layer
+      ? opacity[opacitySlot] || 1
       : opacity[currentLayer]
   ]))
 }
@@ -587,6 +586,7 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
         getLayers(
           value.layer,
           value.variant || value.layer,
+          getOpacitySlot(SLOT_INHERITANCE[value.variant || value.layer]),
           colors,
           opacity
         )
@@ -622,8 +622,15 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
   if (opacitySlot && outputColor.a === undefined) {
     outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1
   }
-  return {
-    colors: { ...colors, [key]: outputColor },
-    opacity: { ...opacity, [opacitySlot]: outputColor.a }
+  if (opacitySlot) {
+    return {
+      colors: { ...colors, [key]: outputColor },
+      opacity: { ...opacity, [opacitySlot]: outputColor.a }
+    }
+  } else {
+    return {
+      colors: { ...colors, [key]: outputColor },
+      opacity
+    }
   }
 }, { colors: {}, opacity: {} })

From 8536f3cc320d550340a834c8357e1c8fd4318649 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 16 Jan 2020 21:59:06 +0200
Subject: [PATCH 157/483] small fix

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

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index b78d8236..03cbb2a1 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -163,8 +163,8 @@ export default {
         )
         if (!slotIsText) return acc
         const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
-        const opacitySlot = getOpacitySlot(SLOT_INHERITANCE[variant || layer])
         const background = variant || layer
+        const opacitySlot = getOpacitySlot(SLOT_INHERITANCE[background])
         const textColors = [
           key,
           ...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : [])

From 552d13a060fe680d1f0800e311f69be8ba25057b Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 16 Jan 2020 23:09:46 +0200
Subject: [PATCH 158/483] better fallback for transparent colors

---
 src/services/theme_data/theme_data.service.js | 65 +++++++++++++++++--
 1 file changed, 58 insertions(+), 7 deletions(-)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 5dbef554..b885c56c 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -35,8 +35,8 @@ export const DEFAULT_OPACITY = {
 export const SLOT_INHERITANCE = {
   bg: {
     depends: [],
-    priority: 1,
-    opacity: 'bg'
+    opacity: 'bg',
+    priority: 1
   },
   fg: {
     depends: [],
@@ -272,6 +272,9 @@ export const SLOT_INHERITANCE = {
     variant: 'btnPressed',
     textColor: true
   },
+  btnPressedPanel: {
+    depends: ['btnPressed']
+  },
   btnPressedPanelText: {
     depends: ['btnPanelText'],
     layer: 'btnPanel',
@@ -490,8 +493,13 @@ export const getOpacitySlot = (
   inheritance = SLOT_INHERITANCE,
   getDeps = getDependencies
 ) => {
-  if (v.opacity === null) return
-  if (v.opacity) return v.opacity
+  const value = typeof v === 'string'
+    ? {
+      depends: v.startsWith('--') ? [v.substring(2)] : []
+    }
+    : v
+  if (value.opacity === null) return
+  if (value.opacity) return v.opacity
   const findInheritedOpacity = (val) => {
     const depSlot = val.depends[0]
     if (depSlot === undefined) return
@@ -505,8 +513,40 @@ export const getOpacitySlot = (
       return null
     }
   }
-  if (v.depends) {
-    return findInheritedOpacity(v)
+  if (value.depends) {
+    return findInheritedOpacity(value)
+  }
+}
+
+export const getLayerSlot = (
+  k,
+  v,
+  inheritance = SLOT_INHERITANCE,
+  getDeps = getDependencies
+) => {
+  const value = typeof v === 'string'
+    ? {
+      depends: v.startsWith('--') ? [v.substring(2)] : []
+    }
+    : v
+  if (LAYERS[k]) return k
+  if (value.layer === null) return
+  if (value.layer) return v.layer
+  const findInheritedLayer = (val) => {
+    const depSlot = val.depends[0]
+    if (depSlot === undefined) return
+    const dependency = getDeps(depSlot, inheritance)[0]
+    if (dependency === undefined) return
+    if (dependency.layer || dependency === null) {
+      return dependency.layer
+    } else if (dependency.depends) {
+      return findInheritedLayer(dependency)
+    } else {
+      return null
+    }
+  }
+  if (value.depends) {
+    return findInheritedLayer(value)
   }
 }
 
@@ -550,9 +590,20 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
     // Color is defined in source color
     let targetColor = sourceColor
     if (targetColor === 'transparent') {
+      // We take only layers below current one
+      const layers = getLayers(
+        getLayerSlot(key, value),
+        key,
+        value.opacity || key,
+        colors,
+        opacity
+      ).slice(0, -1)
       targetColor = {
         // TODO: try to use alpha-blended background here
-        ...convert('#FF00FF').rgb,
+        ...alphaBlendLayers(
+          convert('#FF00FF').rgb,
+          layers
+        ),
         a: 0
       }
     } else if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) {

From c351e5124c41be994a85c1562aed72d6f48bc273 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 16 Jan 2020 23:28:42 +0200
Subject: [PATCH 159/483] fix selectedPost/selectedMenu

---
 src/services/theme_data/theme_data.service.js | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index b885c56c..a0448eb3 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -105,21 +105,25 @@ export const SLOT_INHERITANCE = {
   selectedPostFaintText: {
     depends: ['lightBgFaintText'],
     layer: 'lightBg',
+    variant: 'selectedPost',
     textColor: true
   },
   selectedPostFaintLink: {
     depends: ['lightBgFaintLink'],
     layer: 'lightBg',
+    variant: 'selectedPost',
     textColor: 'preserve'
   },
   selectedPostText: {
     depends: ['lightBgText'],
     layer: 'lightBg',
+    variant: 'selectedPost',
     textColor: true
   },
   selectedPostLink: {
     depends: ['lightBgLink'],
     layer: 'lightBg',
+    variant: 'selectedPost',
     textColor: 'preserve'
   },
   selectedPostIcon: {
@@ -131,21 +135,25 @@ export const SLOT_INHERITANCE = {
   selectedMenuFaintText: {
     depends: ['lightBgFaintText'],
     layer: 'lightBg',
+    variant: 'selectedMenu',
     textColor: true
   },
   selectedMenuFaintLink: {
     depends: ['lightBgFaintLink'],
     layer: 'lightBg',
+    variant: 'selectedMenu',
     textColor: 'preserve'
   },
   selectedMenuText: {
     depends: ['lightBgText'],
     layer: 'lightBg',
+    variant: 'selectedMenu',
     textColor: true
   },
   selectedMenuLink: {
     depends: ['lightBgLink'],
     layer: 'lightBg',
+    variant: 'selectedMenu',
     textColor: 'preserve'
   },
   selectedMenuIcon: {

From 1f5ada08c156687c6a8de22f9f8acb6c5c15824b Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 16 Jan 2020 23:29:19 +0200
Subject: [PATCH 160/483] themes update

---
 static/themes/breezy-dark.json   | 3 ++-
 static/themes/breezy-light.json  | 3 ++-
 static/themes/redmond-xx-se.json | 5 ++++-
 static/themes/redmond-xx.json    | 5 ++++-
 static/themes/redmond-xxi.json   | 5 ++++-
 5 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index ce0f10ab..97e81f3d 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -114,7 +114,8 @@
       "cGreen": "#27ae60",
       "cOrange": "#f67400",
       "btnPressed": "--accent",
-      "lightBg": "--accent,-20"
+      "lightBg": "--accent",
+      "selectedPost": "--bg,10"
     },
     "radii": {
       "btn": "2",
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index c4e54c6d..fd307b80 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -117,7 +117,8 @@
       "cGreen": "#27ae60",
       "cOrange": "#f67400",
       "btnPressed": "--accent",
-      "lightBg": "--accent,-20"
+      "lightBg": "--accent",
+      "selectedPost": "--bg,10"
     },
     "radii": {
       "btn": "2",
diff --git a/static/themes/redmond-xx-se.json b/static/themes/redmond-xx-se.json
index 70ee89d1..13ed3b0e 100644
--- a/static/themes/redmond-xx-se.json
+++ b/static/themes/redmond-xx-se.json
@@ -268,6 +268,7 @@
       "bg": "#c0c0c0",
       "text": "#000000",
       "link": "#0000ff",
+      "accent": "#000080",
       "fg": "#c0c0c0",
       "panel": "#000080",
       "panelFaint": "#c0c0c0",
@@ -281,7 +282,9 @@
       "cRed": "#FF0000",
       "cBlue": "#008080",
       "cGreen": "#008000",
-      "cOrange": "#808000"
+      "cOrange": "#808000",
+      "lightBg": "--accent",
+      "selectedPost": "--bg,-10"
     },
     "radii": {
       "btn": "0",
diff --git a/static/themes/redmond-xx.json b/static/themes/redmond-xx.json
index 4fd6a369..7c687ae0 100644
--- a/static/themes/redmond-xx.json
+++ b/static/themes/redmond-xx.json
@@ -259,6 +259,7 @@
       "bg": "#c0c0c0",
       "text": "#000000",
       "link": "#0000ff",
+      "accent": "#000080",
       "fg": "#c0c0c0",
       "panel": "#000080",
       "panelFaint": "#c0c0c0",
@@ -272,7 +273,9 @@
       "cRed": "#FF0000",
       "cBlue": "#008080",
       "cGreen": "#008000",
-      "cOrange": "#808000"
+      "cOrange": "#808000",
+      "lightBg": "--accent",
+      "selectedPost": "--bg,-10"
     },
     "radii": {
       "btn": "0",
diff --git a/static/themes/redmond-xxi.json b/static/themes/redmond-xxi.json
index d10bf138..5371ee64 100644
--- a/static/themes/redmond-xxi.json
+++ b/static/themes/redmond-xxi.json
@@ -241,6 +241,7 @@
       "bg": "#d6d6ce",
       "text": "#000000",
       "link": "#0000ff",
+      "accent": "#0a246a",
       "fg": "#d6d6ce",
       "panel": "#042967",
       "panelFaint": "#FFFFFF",
@@ -254,7 +255,9 @@
       "cRed": "#c42726",
       "cBlue": "#6699cc",
       "cGreen": "#669966",
-      "cOrange": "#cc6633"
+      "cOrange": "#cc6633",
+      "lightBg": "--accent",
+      "selectedPost": "--bg,-10"
     },
     "radii": {
       "btn": "0",

From 24a7a9bfd8dbdaae8b5a2e5dde5cb754a122905b Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 16 Jan 2020 23:30:13 +0200
Subject: [PATCH 161/483] lint

---
 src/components/style_switcher/style_switcher.vue | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 2fca5570..e3b899f0 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -215,7 +215,10 @@
               :label="$t('settings.text')"
               :fallback="previewTheme.colors.alertErrorText"
             />
-            <ContrastRatio :contrast="previewContrast.alertErrorText" large="1"/>
+            <ContrastRatio
+              :contrast="previewContrast.alertErrorText"
+              large="1"
+            />
             <ColorInput
               v-model="alertWarningColorLocal"
               name="alertWarning"
@@ -228,7 +231,10 @@
               :label="$t('settings.text')"
               :fallback="previewTheme.colors.alertWarningText"
             />
-            <ContrastRatio :contrast="previewContrast.alertWarningText" large="1"/>
+            <ContrastRatio
+              :contrast="previewContrast.alertWarningText"
+              large="1"
+            />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
@@ -244,7 +250,10 @@
               :label="$t('settings.text')"
               :fallback="previewTheme.colors.badgeNotificationText"
             />
-            <ContrastRatio :contrast="previewContrast.badgeNotificationText" large="1" />
+            <ContrastRatio
+              :contrast="previewContrast.badgeNotificationText"
+              large="1"
+            />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4>

From f77d675434ad7238e36e712ed69d01bc3233b156 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 17 Jan 2020 00:27:46 +0200
Subject: [PATCH 162/483] optimized theme loading so that it wouldn't wait
 until ALL themes are loaded to select one by default

---
 .../style_switcher/style_switcher.js          | 23 ++++++-
 src/services/style_setter/style_setter.js     | 67 +++++++++----------
 2 files changed, 53 insertions(+), 37 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 03cbb2a1..52ece3a1 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -96,9 +96,26 @@ export default {
   created () {
     const self = this
 
-    getThemes().then((themesComplete) => {
-      self.availableStyles = themesComplete
-    })
+    getThemes()
+      .then((promises) => {
+        return Promise.all(
+          Object.entries(promises)
+            .map(([k, v]) => v.then(res => [k, res]))
+        )
+      })
+      .then(themes => themes.reduce((acc, [k, v]) => {
+        if (v) {
+          return {
+            ...acc,
+            [k]: v
+          }
+        } else {
+          return acc
+        }
+      }, {}))
+      .then((themesComplete) => {
+        self.availableStyles = themesComplete
+      })
   },
   mounted () {
     this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme)
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 9237a8dc..872dd393 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -336,25 +336,23 @@ export const getThemes = () => {
   return window.fetch('/static/styles.json')
     .then((data) => data.json())
     .then((themes) => {
-      return Promise.all(Object.entries(themes).map(([k, v]) => {
+      return Object.entries(themes).map(([k, v]) => {
+        let promise = null
         if (typeof v === 'object') {
-          return Promise.resolve([k, v])
+          promise = Promise.resolve(v)
         } else if (typeof v === 'string') {
-          return window.fetch(v)
+          promise = window.fetch(v)
             .then((data) => data.json())
-            .then((theme) => {
-              return [k, theme]
-            })
             .catch((e) => {
               console.error(e)
-              return []
+              return null
             })
         }
-      }))
+        return [k, promise]
+      })
     })
     .then((promises) => {
       return promises
-        .filter(([k, v]) => v)
         .reduce((acc, [k, v]) => {
           acc[k] = v
           return acc
@@ -363,33 +361,34 @@ export const getThemes = () => {
 }
 
 export const setPreset = (val, commit) => {
-  return getThemes().then((themes) => {
-    const theme = themes[val] ? themes[val] : themes['pleroma-dark']
-    const isV1 = Array.isArray(theme)
-    const data = isV1 ? {} : theme.theme
+  return getThemes()
+    .then((themes) => console.log(themes) || themes[val] ? themes[val] : themes['pleroma-dark'])
+    .then((theme) => {
+      const isV1 = Array.isArray(theme)
+      const data = isV1 ? {} : theme.theme
 
-    if (isV1) {
-      const bg = hex2rgb(theme[1])
-      const fg = hex2rgb(theme[2])
-      const text = hex2rgb(theme[3])
-      const link = hex2rgb(theme[4])
+      if (isV1) {
+        const bg = hex2rgb(theme[1])
+        const fg = hex2rgb(theme[2])
+        const text = hex2rgb(theme[3])
+        const link = hex2rgb(theme[4])
 
-      const cRed = hex2rgb(theme[5] || '#FF0000')
-      const cGreen = hex2rgb(theme[6] || '#00FF00')
-      const cBlue = hex2rgb(theme[7] || '#0000FF')
-      const cOrange = hex2rgb(theme[8] || '#E3FF00')
+        const cRed = hex2rgb(theme[5] || '#FF0000')
+        const cGreen = hex2rgb(theme[6] || '#00FF00')
+        const cBlue = hex2rgb(theme[7] || '#0000FF')
+        const cOrange = hex2rgb(theme[8] || '#E3FF00')
 
-      data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
-    }
+        data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
+      }
 
-    // This is a hack, this function is only called during initial load.
-    // We want to cancel loading the theme from config.json if we're already
-    // loading a theme from the persisted state.
-    // Needed some way of dealing with the async way of things.
-    // load config -> set preset -> wait for styles.json to load ->
-    // load persisted state -> set colors -> styles.json loaded -> set colors
-    if (!window.themeLoaded) {
-      applyTheme(data, commit)
-    }
-  })
+      // This is a hack, this function is only called during initial load.
+      // We want to cancel loading the theme from config.json if we're already
+      // loading a theme from the persisted state.
+      // Needed some way of dealing with the async way of things.
+      // load config -> set preset -> wait for styles.json to load ->
+      // load persisted state -> set colors -> styles.json loaded -> set colors
+      if (!window.themeLoaded) {
+        applyTheme(data, commit)
+      }
+    })
 }

From 62343f6099ca06449a6755487930ea80706e1335 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 19 Jan 2020 20:59:54 +0200
Subject: [PATCH 163/483] documentation

---
 src/services/theme_data/theme_data.service.js | 113 ++++++++++++++++++
 1 file changed, 113 insertions(+)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index a0448eb3..9f010fdf 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -1,7 +1,42 @@
 import { convert, brightness, contrastRatio } from 'chromatism'
 import { alphaBlend, alphaBlendLayers, getTextColor, mixrgb } from '../color_convert/color_convert.js'
 
+/*
+ * # What's all this?
+ * Here be theme engine for pleromafe. All of this supposed to ease look
+ * and feel customization, making widget styles and make developer's life
+ * easier when it comes to supporting themes. Like many other theme systems
+ * it operates on color definitions, or "slots" - for example you define
+ * "button" color slot and then in UI component Button's CSS you refer to
+ * it as a CSS3 Variable.
+ *
+ * Some applications allow you to customize colors for certain things.
+ * Some UI toolkits allow you to define colors for each type of widget.
+ * Most of them are pretty barebones and have no assistance for common
+ * problems and cases, and in general themes themselves are very hard to
+ * maintain in all aspects. This theme engine tries to solve all of the
+ * common problems with themes.
+ *
+ * You don't have redefine several similar colors if you just want to
+ * change one color - all color slots are derived from other ones, so you
+ * can have at least one or two "basic" colors defined and have all other
+ * components inherit and modify basic ones.
+ *
+ * You don't have to test contrast ratio for colors or pick text color for
+ * each element even if you have light-on-dark elements in dark-on-light
+ * theme.
+ *
+ * You don't have to maintain order of code for inheriting slots from othet
+ * slots - dependency graph resolving does it for you.
+ */
+
+/* This indicates that this version of code outputs similar theme data and
+ * should be incremented if output changes - for instance if getTextColor
+ * function changes and older themes no longer render text colors as
+ * author intended previously.
+ */
 export const CURRENT_VERSION = 3
+
 /* This is a definition of all layer combinations
  * each key is a topmost layer, each value represents layer underneath
  * this is essentially a simplified tree
@@ -25,6 +60,9 @@ export const LAYERS = {
   poll: 'bg'
 }
 
+/* By default opacity slots have 1 as default opacity
+ * this allows redefining it to something else
+ */
 export const DEFAULT_OPACITY = {
   alert: 0.5,
   input: 0.5,
@@ -32,6 +70,44 @@ export const DEFAULT_OPACITY = {
   underlay: 0.15
 }
 
+/**  SUBJECT TO CHANGE IN THE FUTURE, this is all beta
+ * Color and opacity slots definitions. Each key represents a slot.
+ *
+ * Short-hands:
+ * String beginning with `--` - value after dashes treated as sole
+ *     dependency - i.e. `--value` equivalent to { depends: ['value']}
+ * String beginning with `#` - value would be treated as solid color
+ *     defined in hexadecimal representation (i.e. #FFFFFF) and will be
+ *     used as default. `#FFFFFF` is equivalent to { default: '#FFFFFF'}
+ *
+ * Full definition:
+ * @property {String[]} depends - color slot names this color depends ones.
+ *   cyclic dependencies are supported to some extent but not recommended.
+ * @property {String} [opacity] - opacity slot used by this color slot.
+ *   opacity is inherited from parents. To break inheritance graph use null
+ * @property {Number} [priority] - EXPERIMENTAL. used to pre-sort slots so
+ *   that slots with higher priority come earlier
+ * @property {Function(mod, ...colors)} [color] - function that will be
+ *   used to determine the color. By default it just copies first color in
+ *   dependency list.
+ * @argument {Number} mod - `1` (light-on-dark) or `-1` (dark-on-light)
+ *   depending on background color (for textColor)/given color.
+ * @argument {...Object} deps - each argument after mod represents each
+ *   color from `depends` array. All colors take user customizations into
+ *   account and represented by { r, g, b } objects.
+ * @returns {Object} resulting color, should be in { r, g, b } form
+ *
+ * @property {Boolean|String} [textColor] - true to mark color slot as text
+ *   color. This enables automatic text color generation for the slot. Use
+ *   'preserve' string if you don't want text color to fall back to
+ *   black/white. Use 'bw' to only ever use black or white. This also makes
+ *   following properties required:
+ * @property {String} [layer] - which layer the text sit on top on - used
+ *   to account for transparency in text color calculation
+ *   layer is inherited from parents. To break inheritance graph use null
+ * @property {String} [variant] - which color slot is background (same as
+ *   above, used to account for transparency)
+ */
 export const SLOT_INHERITANCE = {
   bg: {
     depends: [],
@@ -456,6 +532,16 @@ const getDependencies = (key, inheritance) => {
   }
 }
 
+/**
+ * Sorts inheritance object topologically - dependant slots come after
+ * dependencies
+ *
+ * @property {Object} inheritance - object defining the nodes
+ * @property {Function} getDeps - function that returns dependencies for
+ *   given value and inheritance object.
+ * @returns {String[]} keys of inheritance object, sorted in topological
+ *   order
+ */
 export const topoSort = (
   inheritance = SLOT_INHERITANCE,
   getDeps = getDependencies
@@ -496,6 +582,11 @@ export const topoSort = (
   return output
 }
 
+/**
+ * retrieves opacity slot for given slot. This goes up the depenency graph
+ * to find which parent has opacity slot defined for it.
+ * TODO refactor this
+ */
 export const getOpacitySlot = (
   v,
   inheritance = SLOT_INHERITANCE,
@@ -526,6 +617,13 @@ export const getOpacitySlot = (
   }
 }
 
+/**
+ * retrieves layer slot for given slot. This goes up the depenency graph
+ * to find which parent has opacity slot defined for it.
+ * this is basically copypaste of getOpacitySlot except it checks if key is
+ * in LAYERS
+ * TODO refactor this
+ */
 export const getLayerSlot = (
   k,
   v,
@@ -558,12 +656,19 @@ export const getLayerSlot = (
   }
 }
 
+/**
+ * topologically sorted SLOT_INHERITANCE + additional priority sort
+ */
 export const SLOT_ORDERED = topoSort(
   Object.entries(SLOT_INHERITANCE)
     .sort(([aK, aV], [bK, bV]) => ((aV && aV.priority) || 0) - ((bV && bV.priority) || 0))
     .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
 )
 
+/**
+ * Dictionary where keys are color slots and values are opacity associated
+ * with them
+ */
 export const SLOTS_OPACITIES_DICT = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
   const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies)
   if (opacity) {
@@ -573,6 +678,10 @@ export const SLOTS_OPACITIES_DICT = Object.entries(SLOT_INHERITANCE).reduce((acc
   }
 }, {})
 
+/**
+ * All opacity slots used in color slots, their default values and affected
+ * color slots.
+ */
 export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
   const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies)
   if (opacity) {
@@ -588,6 +697,10 @@ export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) =
   }
 }, {})
 
+/**
+ * THE function you want to use. Takes provided colors and opacities, mod
+ * value and uses inheritance data to figure out color needed for the slot.
+ */
 export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => {
   const value = SLOT_INHERITANCE[key]
   const isObject = typeof value === 'object'

From 7d7ccf729855283ad0f5b9d6943b8d86ed1f180d Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 19 Jan 2020 22:44:35 +0200
Subject: [PATCH 164/483] fix some contrast ratios not displaying

---
 .../contrast_ratio/contrast_ratio.vue          | 12 +++++++++---
 .../style_switcher/style_switcher.vue          | 18 +++++++++---------
 2 files changed, 18 insertions(+), 12 deletions(-)

diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue
index 15a450a2..5e79ef41 100644
--- a/src/components/contrast_ratio/contrast_ratio.vue
+++ b/src/components/contrast_ratio/contrast_ratio.vue
@@ -37,9 +37,15 @@
 
 <script>
 export default {
-  props: [
-    'large', 'contrast'
-  ],
+  props: {
+    large: {
+      required: false
+    },
+    contrast: {
+      required: true,
+      type: Object
+    }
+  },
   computed: {
     hint () {
       const levelVal = this.contrast.aaa ? 'aaa' : (this.contrast.aa ? 'aa' : 'bad')
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index e3b899f0..c8c02b8d 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -157,13 +157,13 @@
               name="cRedColor"
               :label="$t('settings.cRed')"
             />
-            <ContrastRatio :contrast="previewContrast.bgRed" />
+            <ContrastRatio :contrast="previewContrast.bgCRed" />
             <ColorInput
               v-model="cBlueColorLocal"
               name="cBlueColor"
               :label="$t('settings.cBlue')"
             />
-            <ContrastRatio :contrast="previewContrast.bgBlue" />
+            <ContrastRatio :contrast="previewContrast.bgCBlue" />
           </div>
           <div class="color-item">
             <ColorInput
@@ -171,13 +171,13 @@
               name="cGreenColor"
               :label="$t('settings.cGreen')"
             />
-            <ContrastRatio :contrast="previewContrast.bgGreen" />
+            <ContrastRatio :contrast="previewContrast.bgCGreen" />
             <ColorInput
               v-model="cOrangeColorLocal"
               name="cOrangeColor"
               :label="$t('settings.cOrange')"
             />
-            <ContrastRatio :contrast="previewContrast.bgOrange" />
+            <ContrastRatio :contrast="previewContrast.bgCOrange" />
           </div>
           <p>{{ $t('settings.theme_help_v2_2') }}</p>
         </div>
@@ -217,7 +217,7 @@
             />
             <ContrastRatio
               :contrast="previewContrast.alertErrorText"
-              large="1"
+              large="true"
             />
             <ColorInput
               v-model="alertWarningColorLocal"
@@ -233,7 +233,7 @@
             />
             <ContrastRatio
               :contrast="previewContrast.alertWarningText"
-              large="1"
+              large="true"
             />
           </div>
           <div class="color-item">
@@ -252,7 +252,7 @@
             />
             <ContrastRatio
               :contrast="previewContrast.badgeNotificationText"
-              large="1"
+              large="true"
             />
           </div>
           <div class="color-item">
@@ -277,7 +277,7 @@
             />
             <ContrastRatio
               :contrast="previewContrast.panelText"
-              large="1"
+              large="true"
             />
             <ColorInput
               v-model="panelLinkColorLocal"
@@ -287,7 +287,7 @@
             />
             <ContrastRatio
               :contrast="previewContrast.panelLink"
-              large="1"
+              large="true"
             />
           </div>
           <div class="color-item">

From e4033c85e2066d8a575a1cd8e5a59bb685a3adf0 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 20 Jan 2020 00:34:49 +0200
Subject: [PATCH 165/483] removed console logs

---
 .../shadow_control/shadow_control.js          |  15 ++
 .../shadow_control/shadow_control.vue         |   2 +
 .../style_switcher/style_switcher.js          |  49 ++---
 .../style_switcher/style_switcher.vue         |   2 +-
 src/services/style_setter/style_setter.js     | 178 ++++++++++--------
 src/services/theme_data/theme_data.service.js |  28 ++-
 6 files changed, 166 insertions(+), 108 deletions(-)

diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js
index 44e4a22f..653d5a9b 100644
--- a/src/components/shadow_control/shadow_control.js
+++ b/src/components/shadow_control/shadow_control.js
@@ -61,6 +61,21 @@ export default {
         }
       }
     },
+    currentFallback () {
+      if (this.ready && this.fallback.length > 0) {
+        return this.fallback[this.selectedId]
+      } else {
+        return {
+          x: 0,
+          y: 0,
+          blur: 0,
+          spread: 0,
+          inset: false,
+          color: '#000000',
+          alpha: 1
+        }
+      }
+    },
     moveUpValid () {
       return this.ready && this.selectedId > 0
     },
diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue
index de8a42d1..efbb980e 100644
--- a/src/components/shadow_control/shadow_control.vue
+++ b/src/components/shadow_control/shadow_control.vue
@@ -191,6 +191,8 @@
         v-model="selected.color"
         :disabled="!present"
         :label="$t('settings.style.common.color')"
+        :fallback="currentFallback.color"
+        :showOptionalTickbox="false"
         name="shadow"
       />
       <OpacityInput
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 52ece3a1..2984873f 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -5,6 +5,7 @@ import {
   getContrastRatioLayers
 } from '../../services/color_convert/color_convert.js'
 import {
+  DEFAULT_SHADOWS,
   generateColors,
   generateShadows,
   generateRadii,
@@ -76,7 +77,7 @@ export default {
         .reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}),
 
       ...Object.keys(OPACITIES)
-        .map(key => console.log(key) || [key, ''])
+        .map(key => [key, ''])
         .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}),
 
       shadowSelected: undefined,
@@ -134,7 +135,7 @@ export default {
     },
     currentOpacity () {
       return Object.keys(OPACITIES)
-        .map(key => console.log(key) || [key, this[key + 'OpacityLocal']])
+        .map(key => [key, this[key + 'OpacityLocal']])
         .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
     },
     currentRadii () {
@@ -224,7 +225,7 @@ export default {
       ].join(';')
     },
     shadowsAvailable () {
-      return Object.keys(this.previewTheme.shadows).sort()
+      return Object.keys(DEFAULT_SHADOWS).sort()
     },
     currentShadowOverriden: {
       get () {
@@ -239,7 +240,7 @@ export default {
       }
     },
     currentShadowFallback () {
-      return this.previewTheme.shadows[this.shadowSelected]
+      return (this.previewTheme.shadows || {})[this.shadowSelected]
     },
     currentShadow: {
       get () {
@@ -314,6 +315,17 @@ export default {
         }
       })
     },
+    updatePreviewColorsAndShadows () {
+      this.previewColors = generateColors({
+        opacity: this.currentOpacity,
+        colors: this.currentColors
+      })
+      this.previewShadows = generateShadows(
+        { shadows: this.shadowsLocal },
+        this.previewColors.theme.colors,
+        this.previewColors.mod
+      )
+    },
     onImport (parsed) {
       if (parsed._pleroma_theme_version === 1) {
         this.normalizeLocalState(parsed, 1)
@@ -435,6 +447,14 @@ export default {
         })
       }
 
+      if (opacity && !this.keepOpacity) {
+        this.clearOpacity()
+        Object.entries(opacity).forEach(([k, v]) => {
+          if (typeof v === 'undefined' || v === null || Number.isNaN(v)) return
+          this[k + 'OpacityLocal'] = v
+        })
+      }
+
       if (!this.keepRoundness) {
         this.clearRoundness()
         Object.entries(radii).forEach(([k, v]) => {
@@ -454,14 +474,6 @@ export default {
         this.clearFonts()
         this.fontsLocal = fonts
       }
-
-      if (opacity && !this.keepOpacity) {
-        this.clearOpacity()
-        Object.entries(opacity).forEach(([k, v]) => {
-          if (typeof v === 'undefined' || v === null || Number.isNaN(v)) return
-          this[k + 'OpacityLocal'] = v
-        })
-      }
     }
   },
   watch: {
@@ -476,8 +488,9 @@ export default {
     },
     shadowsLocal: {
       handler () {
+        if (Object.getOwnPropertyNames(this.previewColors).length === 1) return
         try {
-          this.previewShadows = generateShadows({ shadows: this.shadowsLocal })
+          this.updatePreviewColorsAndShadows()
           this.shadowsInvalid = false
         } catch (e) {
           this.shadowsInvalid = true
@@ -500,10 +513,7 @@ export default {
     },
     currentColors () {
       try {
-        this.previewColors = generateColors({
-          opacity: this.currentOpacity,
-          colors: this.currentColors
-        })
+        this.updatePreviewColorsAndShadows()
         this.colorsInvalid = false
       } catch (e) {
         this.colorsInvalid = true
@@ -512,10 +522,7 @@ export default {
     },
     currentOpacity () {
       try {
-        this.previewColors = generateColors({
-          opacity: this.currentOpacity,
-          colors: this.currentColors
-        })
+        this.updatePreviewColorsAndShadows()
       } catch (e) {
         console.warn(e)
       }
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index c8c02b8d..287d31b7 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -691,7 +691,7 @@
               {{ $t('settings.style.switcher.clear_all') }}
             </button>
           </div>
-          <shadow-control
+          <ShadowControl
             v-model="currentShadow"
             :ready="!!currentShadowFallback"
             :fallback="currentShadowFallback"
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 872dd393..74af190c 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,7 +1,7 @@
 import { times } from 'lodash'
 import { convert } from 'chromatism'
 import { rgb2hex, hex2rgb, rgba2css, getCssColor } from '../color_convert/color_convert.js'
-import { getColors } from '../theme_data/theme_data.service.js'
+import { getColors, computeDynamicColor } from '../theme_data/theme_data.service.js'
 
 // While this is not used anymore right now, I left it in if we want to do custom
 // styles that aren't just colors, so user can pick from a few different distinct
@@ -139,7 +139,8 @@ export const generateColors = (themeData) => {
     theme: {
       colors: htmlColors.solid,
       opacity
-    }
+    },
+    mod
   }
 }
 
@@ -211,83 +212,99 @@ export const generateFonts = (input) => {
   }
 }
 
-export const generateShadows = (input) => {
-  const border = (top, shadow) => ({
-    x: 0,
-    y: top ? 1 : -1,
-    blur: 0,
+const border = (top, shadow) => ({
+  x: 0,
+  y: top ? 1 : -1,
+  blur: 0,
+  spread: 0,
+  color: shadow ? '#000000' : '#FFFFFF',
+  alpha: 0.2,
+  inset: true
+})
+const buttonInsetFakeBorders = [border(true, false), border(false, true)]
+const inputInsetFakeBorders = [border(true, true), border(false, false)]
+const hoverGlow = {
+  x: 0,
+  y: 0,
+  blur: 4,
+  spread: 0,
+  color: '--faint',
+  alpha: 1
+}
+
+export const DEFAULT_SHADOWS = {
+  panel: [{
+    x: 1,
+    y: 1,
+    blur: 4,
     spread: 0,
-    color: shadow ? '#000000' : '#FFFFFF',
-    alpha: 0.2,
-    inset: true
-  })
-  const buttonInsetFakeBorders = [border(true, false), border(false, true)]
-  const inputInsetFakeBorders = [border(true, true), border(false, false)]
-  const hoverGlow = {
+    color: '#000000',
+    alpha: 0.6
+  }],
+  topBar: [{
     x: 0,
     y: 0,
     blur: 4,
     spread: 0,
-    color: '--faint',
+    color: '#000000',
+    alpha: 0.6
+  }],
+  popup: [{
+    x: 2,
+    y: 2,
+    blur: 3,
+    spread: 0,
+    color: '#000000',
+    alpha: 0.5
+  }],
+  avatar: [{
+    x: 0,
+    y: 1,
+    blur: 8,
+    spread: 0,
+    color: '#000000',
+    alpha: 0.7
+  }],
+  avatarStatus: [],
+  panelHeader: [],
+  button: [{
+    x: 0,
+    y: 0,
+    blur: 2,
+    spread: 0,
+    color: '#000000',
     alpha: 1
-  }
-
-  const shadows = {
-    panel: [{
-      x: 1,
-      y: 1,
-      blur: 4,
-      spread: 0,
-      color: '#000000',
-      alpha: 0.6
-    }],
-    topBar: [{
-      x: 0,
-      y: 0,
-      blur: 4,
-      spread: 0,
-      color: '#000000',
-      alpha: 0.6
-    }],
-    popup: [{
-      x: 2,
-      y: 2,
-      blur: 3,
-      spread: 0,
-      color: '#000000',
-      alpha: 0.5
-    }],
-    avatar: [{
-      x: 0,
-      y: 1,
-      blur: 8,
-      spread: 0,
-      color: '#000000',
-      alpha: 0.7
-    }],
-    avatarStatus: [],
-    panelHeader: [],
-    button: [{
-      x: 0,
-      y: 0,
-      blur: 2,
-      spread: 0,
-      color: '#000000',
-      alpha: 1
-    }, ...buttonInsetFakeBorders],
-    buttonHover: [hoverGlow, ...buttonInsetFakeBorders],
-    buttonPressed: [hoverGlow, ...inputInsetFakeBorders],
-    input: [...inputInsetFakeBorders, {
-      x: 0,
-      y: 0,
-      blur: 2,
-      inset: true,
-      spread: 0,
-      color: '#000000',
-      alpha: 1
-    }],
+  }, ...buttonInsetFakeBorders],
+  buttonHover: [hoverGlow, ...buttonInsetFakeBorders],
+  buttonPressed: [hoverGlow, ...inputInsetFakeBorders],
+  input: [...inputInsetFakeBorders, {
+    x: 0,
+    y: 0,
+    blur: 2,
+    inset: true,
+    spread: 0,
+    color: '#000000',
+    alpha: 1
+  }]
+}
+export const generateShadows = (input, colors, mod) => {
+  const shadows = Object.entries({
+    ...DEFAULT_SHADOWS,
     ...(input.shadows || {})
-  }
+  }).reduce((shadowsAcc, [slotName, shadowdefs]) => {
+    const newShadow = shadowdefs.reduce((shadowAcc, def) => [
+      ...shadowAcc,
+      {
+        ...def,
+        color: rgb2hex(computeDynamicColor(
+          def.color,
+          (variableSlot) => convert(colors[variableSlot]).rgb,
+          mod
+        ))
+      }
+    ], [])
+    return { ...shadowsAcc, [slotName]: newShadow }
+  }, {})
 
   return {
     rules: {
@@ -325,12 +342,15 @@ export const composePreset = (colors, radii, shadows, fonts) => {
   }
 }
 
-export const generatePreset = (input) => composePreset(
-  generateColors(input),
-  generateRadii(input),
-  generateShadows(input),
-  generateFonts(input)
-)
+export const generatePreset = (input) => {
+  const colors = generateColors(input)
+  return composePreset(
+    colors,
+    generateRadii(input),
+    generateShadows(input, colors.theme.colors, colors.mod),
+    generateFonts(input)
+  )
+}
 
 export const getThemes = () => {
   return window.fetch('/static/styles.json')
@@ -362,7 +382,7 @@ export const getThemes = () => {
 
 export const setPreset = (val, commit) => {
   return getThemes()
-    .then((themes) => console.log(themes) || themes[val] ? themes[val] : themes['pleroma-dark'])
+    .then((themes) => themes[val] ? themes[val] : themes['pleroma-dark'])
     .then((theme) => {
       const isV1 = Array.isArray(theme)
       const data = isV1 ? {} : theme.theme
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 9f010fdf..e4456b29 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -697,6 +697,22 @@ export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) =
   }
 }, {})
 
+/**
+ * Handle dynamic color
+ */
+export const computeDynamicColor = (sourceColor, getColor, mod) => {
+  if (typeof sourceColor !== 'string' || !sourceColor.startsWith('--')) return sourceColor
+  let targetColor = null
+  // Color references other color
+  const [variable, modifier] = sourceColor.split(/,/g).map(str => str.trim())
+  const variableSlot = variable.substring(2)
+  targetColor = getColor(variableSlot)
+  if (modifier) {
+    targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
+  }
+  return targetColor
+}
+
 /**
  * THE function you want to use. Takes provided colors and opacities, mod
  * value and uses inheritance data to figure out color needed for the slot.
@@ -728,13 +744,11 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
         a: 0
       }
     } else if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) {
-      // Color references other color
-      const [variable, modifier] = sourceColor.split(/,/g).map(str => str.trim())
-      const variableSlot = variable.substring(2)
-      targetColor = colors[variableSlot] || sourceColors[variableSlot]
-      if (modifier) {
-        targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
-      }
+      targetColor = computeDynamicColor(
+        sourceColor,
+        variableSlot => colors[variableSlot] || sourceColors[variableSlot],
+        mod
+      )
     } else if (typeof sourceColor === 'string' && sourceColor.startsWith('#')) {
       targetColor = convert(targetColor).rgb
     }

From 6a3714fcc64fe25e6fc38a700083427c2764caec Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 20 Jan 2020 00:37:45 +0200
Subject: [PATCH 166/483] Checked contrast rating errors

---
 src/components/contrast_ratio/contrast_ratio.vue | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue
index 5e79ef41..ba92bc17 100644
--- a/src/components/contrast_ratio/contrast_ratio.vue
+++ b/src/components/contrast_ratio/contrast_ratio.vue
@@ -41,8 +41,10 @@ export default {
     large: {
       required: false
     },
+    // TODO: Make theme switcher compute theme initially so that contrast
+    // component won't be called without contrast data
     contrast: {
-      required: true,
+      required: false,
       type: Object
     }
   },

From 6e1c538e4182263a75fb65b0f3c5d1ad9de94541 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 20 Jan 2020 01:31:54 +0200
Subject: [PATCH 167/483] multiple fixes to make style switcher not die. Made
 shadows work, incuding compatibility

---
 .../style_switcher/style_switcher.js          | 117 ++++++++++--------
 src/services/style_setter/style_setter.js     |  23 +++-
 src/services/theme_data/theme_data.service.js |   9 +-
 static/themes/breezy-dark.json                |   2 +-
 static/themes/breezy-light.json               |   2 +-
 5 files changed, 94 insertions(+), 59 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 2984873f..2a1e439b 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -11,7 +11,8 @@ import {
   generateRadii,
   generateFonts,
   composePreset,
-  getThemes
+  getThemes,
+  shadows2to3
 } from '../../services/style_setter/style_setter.js'
 import {
   CURRENT_VERSION,
@@ -159,62 +160,66 @@ export default {
     },
     // This needs optimization maybe
     previewContrast () {
-      if (!this.previewTheme.colors.bg) return {}
-      const colors = this.previewTheme.colors
-      const opacity = this.previewTheme.opacity
-      if (!colors.bg) return {}
-      const hints = (ratio) => ({
-        text: ratio.toPrecision(3) + ':1',
-        // AA level, AAA level
-        aa: ratio >= 4.5,
-        aaa: ratio >= 7,
-        // same but for 18pt+ texts
-        laa: ratio >= 3,
-        laaa: ratio >= 4.5
-      })
-      const colorsConverted = Object.entries(colors).reduce((acc, [key, value]) => ({ ...acc, [key]: colorConvert(value) }), {})
+      try {
+        if (!this.previewTheme.colors.bg) return {}
+        const colors = this.previewTheme.colors
+        const opacity = this.previewTheme.opacity
+        if (!colors.bg) return {}
+        const hints = (ratio) => ({
+          text: ratio.toPrecision(3) + ':1',
+          // AA level, AAA level
+          aa: ratio >= 4.5,
+          aaa: ratio >= 7,
+          // same but for 18pt+ texts
+          laa: ratio >= 3,
+          laaa: ratio >= 4.5
+        })
+        const colorsConverted = Object.entries(colors).reduce((acc, [key, value]) => ({ ...acc, [key]: colorConvert(value) }), {})
 
-      const ratios = Object.entries(SLOT_INHERITANCE).reduce((acc, [key, value]) => {
-        const slotIsBaseText = key === 'text' || key === 'link'
-        const slotIsText = slotIsBaseText || (
-          typeof value === 'object' && value !== null && value.textColor
-        )
-        if (!slotIsText) return acc
-        const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
-        const background = variant || layer
-        const opacitySlot = getOpacitySlot(SLOT_INHERITANCE[background])
-        const textColors = [
-          key,
-          ...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : [])
-        ]
+        const ratios = Object.entries(SLOT_INHERITANCE).reduce((acc, [key, value]) => {
+          const slotIsBaseText = key === 'text' || key === 'link'
+          const slotIsText = slotIsBaseText || (
+            typeof value === 'object' && value !== null && value.textColor
+          )
+          if (!slotIsText) return acc
+          const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
+          const background = variant || layer
+          const opacitySlot = getOpacitySlot(SLOT_INHERITANCE[background])
+          const textColors = [
+            key,
+            ...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : [])
+          ]
 
-        const layers = getLayers(
-          layer,
-          variant || layer,
-          opacitySlot,
-          colorsConverted,
-          opacity
-        )
+          const layers = getLayers(
+            layer,
+            variant || layer,
+            opacitySlot,
+            colorsConverted,
+            opacity
+          )
 
-        return {
-          ...acc,
-          ...textColors.reduce((acc, textColorKey) => {
-            const newKey = slotIsBaseText
-              ? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1)
-              : textColorKey
-            return {
-              ...acc,
-              [newKey]: getContrastRatioLayers(
-                colorsConverted[textColorKey],
-                layers,
-                colorsConverted[textColorKey]
-              )
-            }
-          }, {})
-        }
-      }, {})
+          return {
+            ...acc,
+            ...textColors.reduce((acc, textColorKey) => {
+              const newKey = slotIsBaseText
+                    ? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1)
+                    : textColorKey
+              return {
+                ...acc,
+                [newKey]: getContrastRatioLayers(
+                  colorsConverted[textColorKey],
+                  layers,
+                  colorsConverted[textColorKey]
+                )
+              }
+            }, {})
+          }
+        }, {})
 
-      return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
+        return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
+      } catch (e) {
+        console.warn('Failure computing contrasts', e)
+      }
     },
     previewRules () {
       if (!this.preview.rules) return ''
@@ -466,7 +471,11 @@ export default {
 
       if (!this.keepShadows) {
         this.clearShadows()
-        this.shadowsLocal = shadows
+        if (version === 2) {
+          this.shadowsLocal = shadows2to3(shadows)
+        } else {
+          this.shadowsLocal = shadows
+        }
         this.shadowSelected = this.shadowsAvailable[0]
       }
 
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 74af190c..ee264c49 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -291,8 +291,8 @@ export const generateShadows = (input, colors, mod) => {
   const shadows = Object.entries({
     ...DEFAULT_SHADOWS,
     ...(input.shadows || {})
-  }).reduce((shadowsAcc, [slotName, shadowdefs]) => {
-    const newShadow = shadowdefs.reduce((shadowAcc, def) => [
+  }).reduce((shadowsAcc, [slotName, shadowDefs]) => {
+    const newShadow = shadowDefs.reduce((shadowAcc, def) => [
       ...shadowAcc,
       {
         ...def,
@@ -380,6 +380,25 @@ export const getThemes = () => {
     })
 }
 
+/**
+ * This handles compatibility issues when importing v2 theme's shadows to current format
+ *
+ * Back in v2 shadows allowed you to use dynamic colors however those used pure CSS3 variables
+ */
+export const shadows2to3 = (shadows) => {
+  return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => {
+    const isDynamic = ({ color }) => console.log(color) || color.startsWith('--')
+    const newShadow = shadowDefs.reduce((shadowAcc, def) => [
+      ...shadowAcc,
+      {
+        ...def,
+        alpha: isDynamic(def) ? 1 : def.alpha
+      }
+    ], [])
+    return { ...shadowsAcc, [slotName]: newShadow }
+  }, {})
+}
+
 export const setPreset = (val, commit) => {
   return getThemes()
     .then((themes) => themes[val] ? themes[val] : themes['pleroma-dark'])
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index e4456b29..36837b44 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -663,7 +663,14 @@ export const SLOT_ORDERED = topoSort(
   Object.entries(SLOT_INHERITANCE)
     .sort(([aK, aV], [bK, bV]) => ((aV && aV.priority) || 0) - ((bV && bV.priority) || 0))
     .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
-)
+).sort((a, b) => {
+  const depsA = getDependencies(a, SLOT_INHERITANCE).length
+  const depsB = getDependencies(b, SLOT_INHERITANCE).length
+
+  if (depsA === depsB || (depsB !== 0 && depsA !== 0)) return 0
+  if (depsA === 0 && depsB !== 0) return -1
+  if (depsB === 0 && depsA !== 0) return 1
+})
 
 /**
  * Dictionary where keys are color slots and values are opacity associated
diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index 97e81f3d..3aafda52 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -53,7 +53,7 @@
           "blur": 0,
           "spread": "1",
           "color": "--accent",
-          "alpha": "0.3",
+          "alpha": "1",
           "inset": true
         },
         {
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index fd307b80..df6e1a66 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -53,7 +53,7 @@
           "blur": 0,
           "spread": "1",
           "color": "--accent",
-          "alpha": "0.3",
+          "alpha": "1",
           "inset": true
         },
         {

From 2b36a62c5600738737e0fda4e0fc92e6e1f59c33 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 20 Jan 2020 01:44:11 +0200
Subject: [PATCH 168/483] fix tests, integrate depenentless sorting into
 toposort for easier testing and better guarantees

---
 src/services/theme_data/theme_data.service.js | 22 +++++++++----------
 .../services/theme_data/theme_data.spec.js    | 10 ++++++++-
 2 files changed, 20 insertions(+), 12 deletions(-)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 36837b44..63bfb5af 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -540,7 +540,7 @@ const getDependencies = (key, inheritance) => {
  * @property {Function} getDeps - function that returns dependencies for
  *   given value and inheritance object.
  * @returns {String[]} keys of inheritance object, sorted in topological
- *   order
+ *   order. Additionally, dependency-less nodes will always be first in line
  */
 export const topoSort = (
   inheritance = SLOT_INHERITANCE,
@@ -579,7 +579,14 @@ export const topoSort = (
   while (unprocessed.length > 0) {
     step(unprocessed.pop())
   }
-  return output
+  return output.sort((a, b) => {
+    const depsA = getDeps(a, inheritance).length
+    const depsB = getDeps(b, inheritance).length
+
+    if (depsA === depsB || (depsB !== 0 && depsA !== 0)) return 0
+    if (depsA === 0 && depsB !== 0) return -1
+    if (depsB === 0 && depsA !== 0) return 1
+  })
 }
 
 /**
@@ -657,20 +664,13 @@ export const getLayerSlot = (
 }
 
 /**
- * topologically sorted SLOT_INHERITANCE + additional priority sort
+ * topologically sorted SLOT_INHERITANCE
  */
 export const SLOT_ORDERED = topoSort(
   Object.entries(SLOT_INHERITANCE)
     .sort(([aK, aV], [bK, bV]) => ((aV && aV.priority) || 0) - ((bV && bV.priority) || 0))
     .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
-).sort((a, b) => {
-  const depsA = getDependencies(a, SLOT_INHERITANCE).length
-  const depsB = getDependencies(b, SLOT_INHERITANCE).length
-
-  if (depsA === depsB || (depsB !== 0 && depsA !== 0)) return 0
-  if (depsA === 0 && depsB !== 0) return -1
-  if (depsB === 0 && depsA !== 0) return 1
-})
+)
 
 /**
  * Dictionary where keys are color slots and values are opacity associated
diff --git a/test/unit/specs/services/theme_data/theme_data.spec.js b/test/unit/specs/services/theme_data/theme_data.spec.js
index 507905eb..d8a04ba7 100644
--- a/test/unit/specs/services/theme_data/theme_data.spec.js
+++ b/test/unit/specs/services/theme_data/theme_data.spec.js
@@ -72,8 +72,16 @@ describe('topoSort', () => {
     expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB'))
     expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB'))
   })
+
+  it('dependentless nodes should be first', () => {
+    const out = topoSort(fixture2, (node, inheritance) => inheritance[node])
+    // This basically checks all ordering that matters
+    expect(out.indexOf('layerA')).to.eql(0)
+    expect(out.indexOf('layerB')).to.eql(1)
+  })
+
   it('ignores cyclic dependencies', () => {
-    const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => inheritance[node])
+    const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => [inheritance[node]])
     expect(out.indexOf('a')).to.be.below(out.indexOf('c'))
   })
 })

From a7b6c79136012353397203f937d2baa667d08895 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 20 Jan 2020 01:45:48 +0200
Subject: [PATCH 169/483] eslint

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

diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue
index efbb980e..c1db5b8f 100644
--- a/src/components/shadow_control/shadow_control.vue
+++ b/src/components/shadow_control/shadow_control.vue
@@ -192,7 +192,7 @@
         :disabled="!present"
         :label="$t('settings.style.common.color')"
         :fallback="currentFallback.color"
-        :showOptionalTickbox="false"
+        :show-optional-tickbox="false"
         name="shadow"
       />
       <OpacityInput
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 2a1e439b..799646b1 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -202,8 +202,8 @@ export default {
             ...acc,
             ...textColors.reduce((acc, textColorKey) => {
               const newKey = slotIsBaseText
-                    ? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1)
-                    : textColorKey
+                ? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1)
+                : textColorKey
               return {
                 ...acc,
                 [newKey]: getContrastRatioLayers(

From 93dfb4d3524df14f730a3f0ad46ebb86ceb89984 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 20 Jan 2020 02:00:13 +0200
Subject: [PATCH 170/483] fix shadow picker preview, change hint

---
 src/components/shadow_control/shadow_control.js  | 2 +-
 src/components/shadow_control/shadow_control.vue | 9 ++++++---
 src/i18n/en.json                                 | 2 +-
 3 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js
index 653d5a9b..bdfb4429 100644
--- a/src/components/shadow_control/shadow_control.js
+++ b/src/components/shadow_control/shadow_control.js
@@ -95,7 +95,7 @@ export default {
     },
     style () {
       return this.ready ? {
-        boxShadow: getCssShadow(this.cValue)
+        boxShadow: getCssShadow(this.fallback)
       } : {}
     }
   }
diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue
index c1db5b8f..815a9e59 100644
--- a/src/components/shadow_control/shadow_control.vue
+++ b/src/components/shadow_control/shadow_control.vue
@@ -199,9 +199,12 @@
         v-model="selected.alpha"
         :disabled="!present"
       />
-      <p>
-        {{ $t('settings.style.shadows.hint') }}
-      </p>
+      <i18n
+        path="settings.style.shadows.hintV3"
+        tag="p"
+      >
+        <code>--variable,mod</code>
+      </i18n>
     </div>
   </div>
 </template>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index f7a26262..f624a1c5 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -452,7 +452,7 @@
         "blur": "Blur",
         "spread": "Spread",
         "inset": "Inset",
-        "hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.",
+        "hintV3": "For shadows you can also use the {0} notation to use other color slot.",
         "filter_hint": {
           "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
           "drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",

From 8080981fcdaab4efd07c2c3f4ff3e2131f8aa802 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Tue, 21 Jan 2020 16:51:49 +0100
Subject: [PATCH 171/483] Fix follower request fetching

---
 src/components/nav_panel/nav_panel.js                           | 2 +-
 src/components/side_drawer/side_drawer.js                       | 2 +-
 src/modules/api.js                                              | 1 +
 .../backend_interactor_service/backend_interactor_service.js    | 2 +-
 4 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index d9268585..8f7edb7f 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -3,7 +3,7 @@ import { mapState } from 'vuex'
 const NavPanel = {
   created () {
     if (this.currentUser && this.currentUser.locked) {
-      this.$store.dispatch('startFetchingFollowRequest')
+      this.$store.dispatch('startFetchingFollowRequests')
     }
   },
   computed: mapState({
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 2534eb8f..2181ecc7 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -12,7 +12,7 @@ const SideDrawer = {
     this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, this.toggleDrawer)
 
     if (this.currentUser && this.currentUser.locked) {
-      this.$store.dispatch('startFetchingFollowRequest')
+      this.$store.dispatch('startFetchingFollowRequests')
     }
   },
   components: { UserCard },
diff --git a/src/modules/api.js b/src/modules/api.js
index 9c296275..748570e5 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -146,6 +146,7 @@ const api = {
     startFetchingFollowRequests (store) {
       if (store.state.fetchers['followRequests']) return
       const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
+
       store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
     },
     stopFetchingFollowRequests (store) {
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index b7372ed0..e1c32860 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -16,7 +16,7 @@ const backendInteractorService = credentials => ({
     return notificationsFetcher.fetchAndUpdate({ store, credentials })
   },
 
-  startFetchingFollowRequest ({ store }) {
+  startFetchingFollowRequests ({ store }) {
     return followRequestFetcher.startFetching({ store, credentials })
   },
 

From 9336140486f50159b935001b7ebadf3d9bda89ec Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 22 Jan 2020 00:37:19 +0200
Subject: [PATCH 172/483] massively improved initial theme loading code, added
 checks and warnings when loading theme files (import/localStorage/defaults)

---
 src/boot/after_store.js                       |  32 ++--
 .../style_switcher/style_switcher.js          | 153 ++++++++++++++++--
 .../style_switcher/style_switcher.scss        |   4 +
 .../style_switcher/style_switcher.vue         |  79 ++++++---
 src/i18n/en.json                              |  12 +-
 src/modules/config.js                         |   7 +-
 src/modules/instance.js                       |  17 +-
 src/services/style_setter/style_setter.js     |  27 +---
 8 files changed, 259 insertions(+), 72 deletions(-)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 228a0497..6c4f0e1b 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -5,6 +5,8 @@ import App from '../App.vue'
 import { windowWidth } from '../services/window_utils/window_utils'
 import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
 import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
+import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
+import { applyTheme } from '../services/style_setter/style_setter.js'
 
 const getStatusnetConfig = async ({ store }) => {
   try {
@@ -261,7 +263,7 @@ const checkOAuthToken = async ({ store }) => {
       try {
         await store.dispatch('loginUser', store.getters.getUserToken())
       } catch (e) {
-        console.log(e)
+        console.error(e)
       }
     }
     resolve()
@@ -269,23 +271,29 @@ const checkOAuthToken = async ({ store }) => {
 }
 
 const afterStoreSetup = async ({ store, i18n }) => {
-  if (store.state.config.customTheme) {
-    // This is a hack to deal with async loading of config.json and themes
-    // See: style_setter.js, setPreset()
-    window.themeLoaded = true
-    store.dispatch('setOption', {
-      name: 'customTheme',
-      value: store.state.config.customTheme
-    })
-  }
-
   const width = windowWidth()
   store.dispatch('setMobileLayout', width <= 800)
+  await setConfig({ store })
+
+  const { customTheme, customThemeSource } = store.state.config
+  const { theme } = store.state.instance
+  const customThemePresent = customThemeSource || customTheme
+
+  if (customThemePresent) {
+    if (customThemeSource && customThemeSource.version === CURRENT_VERSION) {
+      applyTheme(customThemeSource)
+    } else {
+      applyTheme(customTheme)
+    }
+  } else if (theme) {
+    // do nothing, it will load asynchronously
+  } else {
+    console.error('Failed to load any theme!')
+  }
 
   // Now we can try getting the server settings and logging in
   await Promise.all([
     checkOAuthToken({ store }),
-    setConfig({ store }),
     getTOS({ store }),
     getInstancePanel({ store }),
     getStickers({ store }),
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 799646b1..0ef02f39 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -57,6 +57,8 @@ export default {
     return {
       availableStyles: [],
       selected: this.$store.getters.mergedConfig.theme,
+      themeWarning: undefined,
+      tempImportFile: undefined,
 
       previewShadows: {},
       previewColors: {},
@@ -120,12 +122,62 @@ export default {
       })
   },
   mounted () {
-    this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme)
+    this.loadThemeFromLocalStorage()
     if (typeof this.shadowSelected === 'undefined') {
       this.shadowSelected = this.shadowsAvailable[0]
     }
   },
   computed: {
+    themeWarningHelp () {
+      if (!this.themeWarning) return
+      const t = this.$t
+      const pre = 'settings.style.switcher.help.'
+      const {
+        origin,
+        themeEngineVersion,
+        type,
+        noActionsPossible
+      } = this.themeWarning
+      if (origin === 'file') {
+        // Loaded v2 theme from file
+        if (themeEngineVersion === 2 && type === 'wrong_version') {
+          return t(pre + 'v2_imported')
+        }
+        if (themeEngineVersion > CURRENT_VERSION) {
+          return t(pre + 'future_version_imported') + ' ' +
+            (
+              noActionsPossible
+                ? t(pre + 'snapshot_missing')
+                : t(pre + 'snapshot_present')
+            )
+        }
+        if (themeEngineVersion < CURRENT_VERSION) {
+          return t(pre + 'future_version_imported') + ' ' +
+            (
+              noActionsPossible
+                ? t(pre + 'snapshot_missing')
+                : t(pre + 'snapshot_present')
+            )
+        }
+      } else if (origin === 'localStorage') {
+        // FE upgraded from v2
+        if (themeEngineVersion === 2) {
+          return 'upgraded_from_v2'
+        }
+        // Admin downgraded FE
+        if (themeEngineVersion > CURRENT_VERSION) {
+          return noActionsPossible
+            ? 'downgraded_theme'
+            : 'downgraded_theme_missing_snapshot'
+        }
+        // Admin upgraded FE
+        if (themeEngineVersion < CURRENT_VERSION) {
+          return noActionsPossible
+            ? 'upgraded_theme'
+            : 'upgraded_theme_missing_snapshot'
+        }
+      }
+    },
     selectedVersion () {
       return Array.isArray(this.selected) ? 1 : 2
     },
@@ -308,10 +360,96 @@ export default {
     Checkbox
   },
   methods: {
+    loadTheme (
+      {
+        theme,
+        source,
+        _pleroma_theme_version: fileVersion
+      },
+      origin,
+      forceUseSource = false
+    ) {
+      if (!source && !theme) {
+        throw new Error('Can\'t load theme: empty')
+      }
+      const version = (origin === 'localstorage' && !theme.colors)
+        ? 'l1'
+        : fileVersion
+      const themeEngineVersion = (source || {}).themeEngineVersion || 2
+      const versionsMatch = themeEngineVersion === CURRENT_VERSION
+      // Force loading of source if user requested it or if snapshot
+      // is unavailable
+      const forcedSourceLoad = (source && forceUseSource) || !theme
+      if (!versionsMatch &&
+          !forcedSourceLoad &&
+          version !== 'l1' &&
+          origin !== 'defaults'
+      ) {
+        if (!theme) {
+          this.themeWarning = {
+            origin,
+            noActionsPossible: true,
+            themeEngineVersion,
+            type: 'no_snapshot_old_version'
+          }
+        } else if (!versionsMatch) {
+          this.themeWarning = {
+            origin,
+            noActionsPossible: !source,
+            themeEngineVersion,
+            type: 'wrong_version'
+          }
+        }
+      }
+      this.normalizeLocalState(theme, version, source, forcedSourceLoad)
+    },
+    forceLoadLocalStorage () {
+      this.loadThemeFromLocalStorage(true)
+    },
+    dismissWarning () {
+      this.themeWarning = undefined
+      this.tempImportFile = undefined
+    },
+    forceLoad () {
+      const { origin } = this.themeWarning
+      switch (origin) {
+        case 'localstorage':
+          this.loadThemeFromLocalStorage(true)
+          break
+        case 'file':
+          this.onImport(this.tempImportFile, true)
+          break
+      }
+    },
+    loadThemeFromLocalStorage (confirmLoadSource = false) {
+      const {
+        customTheme: theme,
+        customThemeSource: source
+      } = this.$store.getters.mergedConfig
+      if (!theme && !source) {
+        // Anon user or never touched themes
+        this.loadTheme(
+          this.$store.state.instance.themeData,
+          'defaults',
+          confirmLoadSource
+        )
+      } else {
+        this.loadTheme(
+          { theme, source },
+          'localStorage',
+          confirmLoadSource
+        )
+      }
+    },
     setCustomTheme () {
       this.$store.dispatch('setOption', {
         name: 'customTheme',
+        value: this.previewTheme
+      })
+      this.$store.dispatch('setOption', {
+        name: 'customThemeSource',
         value: {
+          themeEngineVersion: CURRENT_VERSION,
           shadows: this.shadowsLocal,
           fonts: this.fontsLocal,
           opacity: this.currentOpacity,
@@ -331,21 +469,16 @@ export default {
         this.previewColors.mod
       )
     },
-    onImport (parsed) {
-      if (parsed._pleroma_theme_version === 1) {
-        this.normalizeLocalState(parsed, 1)
-      } else if (parsed._pleroma_theme_version >= 2) {
-        this.normalizeLocalState(parsed.theme, 2, parsed.source)
-      }
+    onImport (parsed, forceSource = false) {
+      this.tempImportFile = parsed
+      this.loadTheme(parsed, 'file', forceSource)
     },
     importValidator (parsed) {
       const version = parsed._pleroma_theme_version
       return version >= 1 || version <= 2
     },
     clearAll () {
-      const state = this.$store.getters.mergedConfig.customTheme
-      const version = state.colors ? 2 : 'l1'
-      this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme, version, this.$store.getters.mergedConfig.customThemeSource)
+      this.loadThemeFromLocalStorage()
     },
 
     // Clears all the extra stuff when loading V1 theme
diff --git a/src/components/style_switcher/style_switcher.scss b/src/components/style_switcher/style_switcher.scss
index 987245a2..71d0f05e 100644
--- a/src/components/style_switcher/style_switcher.scss
+++ b/src/components/style_switcher/style_switcher.scss
@@ -1,5 +1,9 @@
 @import '../../_variables.scss';
 .style-switcher {
+  .theme-warning {
+    display: flex;
+    align-items: baseline;
+  }
   .preset-switcher {
     margin-right: 1em;
   }
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 287d31b7..61f8800a 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -1,31 +1,60 @@
 <template>
-  <div class="style-switcher">
-    <div class="presets-container">
-      <div class="save-load">
-        <ExportImport
-          :export-object="exportedTheme"
-          :export-label="$t(&quot;settings.export_theme&quot;)"
-          :import-label="$t(&quot;settings.import_theme&quot;)"
-          :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
-          :on-import="onImport"
-          :validator="importValidator"
-        >
-          <template slot="before">
-            <div class="presets">
-              {{ $t('settings.presets') }}
-              <label
-                for="preset-switcher"
-                class="select"
+<div class="style-switcher">
+  <div class="presets-container">
+    <div class="save-load">
+      <div class="theme-warning" v-if="themeWarning">
+        <div class="alert warning">
+        {{ themeWarningHelp }}
+        </div>
+        <div class="buttons">
+          <template v-if="themeWarning.noActionsPossible">
+            <button
+              class="btn"
+              @click="dismissWarning"
+            >
+              {{ $t('general.dismiss') }}
+            </button>
+          </template>
+          <template v-else>
+            <button
+              class="btn"
+              @click="forceLoad"
+            >
+              {{ $t('settings.style.switcher.load_theme') }}
+            </button>
+            <button
+              class="btn"
+              @click="dismissWarning"
+            >
+              {{ $t('settings.style.switcher.use_snapshot') }}
+            </button>
+          </template>
+        </div>
+      </div>
+      <ExportImport
+        :export-object="exportedTheme"
+        :export-label="$t(&quot;settings.export_theme&quot;)"
+        :import-label="$t(&quot;settings.import_theme&quot;)"
+        :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
+        :on-import="onImport"
+        :validator="importValidator"
+      >
+        <template slot="before">
+          <div class="presets">
+            {{ $t('settings.presets') }}
+            <label
+              for="preset-switcher"
+              class="select"
               >
-                <select
-                  id="preset-switcher"
-                  v-model="selected"
-                  class="preset-switcher"
+              <select
+                id="preset-switcher"
+                v-model="selected"
+                class="preset-switcher"
                 >
-                  <option
-                    v-for="style in availableStyles"
-                    :key="style.name"
-                    :value="style"
+                <option
+                  v-for="style in availableStyles"
+                  :key="style.name"
+                  :value="style"
                     :style="{
                       backgroundColor: style[1] || (style.theme || style.source).colors.bg,
                       color: style[3] || (style.theme || style.source).colors.text
diff --git a/src/i18n/en.json b/src/i18n/en.json
index f624a1c5..6994d62f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -46,6 +46,7 @@
     "optional": "optional",
     "show_more": "Show more",
     "show_less": "Show less",
+    "dismiss": "Dismiss",
     "cancel": "Cancel",
     "disable": "Disable",
     "enable": "Enable",
@@ -394,7 +395,16 @@
         "save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
         "reset": "Reset",
         "clear_all": "Clear all",
-        "clear_opacity": "Clear opacity"
+        "clear_opacity": "Clear opacity",
+        "load_theme": "Load theme",
+        "use_snapshot": "Keep as is",
+        "help": {
+          "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsitencies.",
+          "snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.",
+          "snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.",
+          "future_version_imported": "File you imported was made in newer version of FE.",
+          "older_version_imported": "File you imported was made in older version of FE."
+        }
       },
       "common": {
         "color": "Color",
diff --git a/src/modules/config.js b/src/modules/config.js
index 74025db1..ee474b16 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -5,6 +5,9 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0]
 
 export const defaultState = {
   colors: {},
+  theme: undefined,
+  customTheme: undefined,
+  customThemeSource: undefined,
   hideISP: false,
   // bad name: actually hides posts of muted USERS
   hideMutedPosts: undefined, // instance default
@@ -93,10 +96,10 @@ const config = {
       commit('setOption', { name, value })
       switch (name) {
         case 'theme':
-          setPreset(value, commit)
+          setPreset(value)
           break
         case 'customTheme':
-          applyTheme(value, commit)
+          applyTheme(value)
       }
     }
   }
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 625323b9..8781646d 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -1,5 +1,5 @@
 import { set } from 'vue'
-import { setPreset } from '../services/style_setter/style_setter.js'
+import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
 import { instanceDefaultProperties } from './config.js'
 
 const defaultState = {
@@ -10,6 +10,7 @@ const defaultState = {
   textlimit: 5000,
   server: 'http://localhost:4040/',
   theme: 'pleroma-dark',
+  themeData: undefined,
   background: '/static/aurora_borealis.jpg',
   logo: '/static/logo.png',
   logoMask: true,
@@ -96,6 +97,9 @@ const instance = {
             dispatch('initializeSocket')
           }
           break
+        case 'theme':
+          dispatch('setTheme', value)
+          break
       }
     },
     async getStaticEmoji ({ commit }) {
@@ -147,9 +151,16 @@ const instance = {
       }
     },
 
-    setTheme ({ commit }, themeName) {
+    setTheme ({ commit, rootState }, themeName) {
       commit('setInstanceOption', { name: 'theme', value: themeName })
-      return setPreset(themeName, commit)
+      getPreset(themeName)
+        .then(themeData => {
+          commit('setInstanceOption', { name: 'themeData', value: themeData })
+          // No need to apply theme if there's user theme already
+          const { customTheme } = rootState.config
+          if (customTheme) return
+          applyTheme(themeData.theme)
+        })
     },
     fetchEmoji ({ dispatch, state }) {
       if (!state.customEmojiFetched) {
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index ee264c49..9610d799 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -7,7 +7,7 @@ import { getColors, computeDynamicColor } from '../theme_data/theme_data.service
 // styles that aren't just colors, so user can pick from a few different distinct
 // styles as well as set their own colors in the future.
 
-export const setStyle = (href, commit) => {
+export const setStyle = (href) => {
   /***
       What's going on here?
       I want to make it easy for admins to style this application. To have
@@ -53,8 +53,8 @@ export const setStyle = (href, commit) => {
   cssEl.addEventListener('load', setDynamic)
 }
 
-export const applyTheme = (input, commit) => {
-  const { rules, theme } = generatePreset(input)
+export const applyTheme = (input) => {
+  const { rules } = generatePreset(input)
   const head = document.head
   const body = document.body
   body.classList.add('hidden')
@@ -69,11 +69,6 @@ export const applyTheme = (input, commit) => {
   styleSheet.insertRule(`body { ${rules.shadows} }`, 'index-max')
   styleSheet.insertRule(`body { ${rules.fonts} }`, 'index-max')
   body.classList.remove('hidden')
-
-  // commit('setOption', { name: 'colors', value: htmlColors })
-  // commit('setOption', { name: 'radii', value: radii })
-  commit('setOption', { name: 'customTheme', value: input })
-  commit('setOption', { name: 'colors', value: theme.colors })
 }
 
 export const getCssShadow = (input, usesDropShadow) => {
@@ -387,7 +382,7 @@ export const getThemes = () => {
  */
 export const shadows2to3 = (shadows) => {
   return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => {
-    const isDynamic = ({ color }) => console.log(color) || color.startsWith('--')
+    const isDynamic = ({ color }) => color.startsWith('--')
     const newShadow = shadowDefs.reduce((shadowAcc, def) => [
       ...shadowAcc,
       {
@@ -399,7 +394,7 @@ export const shadows2to3 = (shadows) => {
   }, {})
 }
 
-export const setPreset = (val, commit) => {
+export const getPreset = (val) => {
   return getThemes()
     .then((themes) => themes[val] ? themes[val] : themes['pleroma-dark'])
     .then((theme) => {
@@ -420,14 +415,8 @@ export const setPreset = (val, commit) => {
         data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
       }
 
-      // This is a hack, this function is only called during initial load.
-      // We want to cancel loading the theme from config.json if we're already
-      // loading a theme from the persisted state.
-      // Needed some way of dealing with the async way of things.
-      // load config -> set preset -> wait for styles.json to load ->
-      // load persisted state -> set colors -> styles.json loaded -> set colors
-      if (!window.themeLoaded) {
-        applyTheme(data, commit)
-      }
+      return { theme: data, source: theme.source }
     })
 }
+
+export const setPreset = (val) => getPreset(val).then(data => applyTheme(data.theme))

From cf1149d8c5248914ced0de4ea1e176f2e816d635 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 22 Jan 2020 00:41:21 +0200
Subject: [PATCH 173/483] updated changelog, slightly

---
 CHANGELOG.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d6ef1a5..554870ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Ability to change user's email
 - About page
 ### Changed
+- theme engine update to 3
+- theme doesn't get saved to local storage when opening FE anonymously
 - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes
 ### Fixed
+- anon viewers won't get theme data saved to local storage, so admin changing default theme will have an effect for users coming back to instance.
 - improved hotkey behavior on autocomplete popup

From 1191207aa5a32627e54f7febcd279d50d03c66b3 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 22 Jan 2020 00:53:51 +0200
Subject: [PATCH 174/483] more help strings

---
 .../style_switcher/style_switcher.js           | 18 ++++++++++++------
 src/i18n/en.json                               |  8 ++++++--
 2 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 0ef02f39..8f0de067 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -166,15 +166,21 @@ export default {
         }
         // Admin downgraded FE
         if (themeEngineVersion > CURRENT_VERSION) {
-          return noActionsPossible
-            ? 'downgraded_theme'
-            : 'downgraded_theme_missing_snapshot'
+          return t(pre + 'fe_downgraded') + ' ' +
+            (
+              noActionsPossible
+                ? t(pre + 'migration_snapshot_ok')
+                : t(pre + 'migration_snapshot_gone')
+            )
         }
         // Admin upgraded FE
         if (themeEngineVersion < CURRENT_VERSION) {
-          return noActionsPossible
-            ? 'upgraded_theme'
-            : 'upgraded_theme_missing_snapshot'
+          return t(pre + 'fe_upgraded') + ' ' +
+            (
+              noActionsPossible
+                ? t(pre + 'migration_snapshot_ok')
+                : t(pre + 'migration_snapshot_gone')
+            )
         }
       }
     },
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 6994d62f..d53fa697 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -400,10 +400,14 @@
         "use_snapshot": "Keep as is",
         "help": {
           "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsitencies.",
+          "future_version_imported": "File you imported was made in newer version of FE.",
+          "older_version_imported": "File you imported was made in older version of FE.",
           "snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.",
           "snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.",
-          "future_version_imported": "File you imported was made in newer version of FE.",
-          "older_version_imported": "File you imported was made in older version of FE."
+          "fe_upgraded": "PleromaFE's theme engine upgraded after version update.",
+          "fe_downgraded": "PleromaFE's version rolled back.",
+          "migration_snapshot_ok": "Just to be safe, theme snapshot loaded. You can try loading theme data.",
+          "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember."
         }
       },
       "common": {

From 803edcb53a664382f9db9a00ccebe8efee1873ef Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 22 Jan 2020 01:28:05 +0200
Subject: [PATCH 175/483] dismiss warning when loading theme

---
 src/components/style_switcher/style_switcher.js | 3 ++-
 src/i18n/en.json                                | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 8f0de067..44e7595d 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -162,7 +162,7 @@ export default {
       } else if (origin === 'localStorage') {
         // FE upgraded from v2
         if (themeEngineVersion === 2) {
-          return 'upgraded_from_v2'
+          return t(pre + 'upgraded_from_v2')
         }
         // Admin downgraded FE
         if (themeEngineVersion > CURRENT_VERSION) {
@@ -426,6 +426,7 @@ export default {
           this.onImport(this.tempImportFile, true)
           break
       }
+      this.dismissWarning()
     },
     loadThemeFromLocalStorage (confirmLoadSource = false) {
       const {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index d53fa697..815c11b1 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -399,6 +399,7 @@
         "load_theme": "Load theme",
         "use_snapshot": "Keep as is",
         "help": {
+          "upgraded_from_v2": "PleromaFE has been upgraded, theme could look a little bit different than you remember.",
           "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsitencies.",
           "future_version_imported": "File you imported was made in newer version of FE.",
           "older_version_imported": "File you imported was made in older version of FE.",

From 644ce497a0f118b2e5050a2e45b67450915c53f0 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 22 Jan 2020 01:28:46 +0200
Subject: [PATCH 176/483] lightBg -> highlight because it causes issues with v2

---
 .../style_switcher/style_switcher.vue         | 24 ++++----
 src/i18n/en.json                              |  2 +-
 src/services/theme_data/theme_data.service.js | 60 +++++++++----------
 static/themes/breezy-dark.json                |  2 +-
 static/themes/breezy-light.json               |  2 +-
 static/themes/redmond-xx-se.json              |  2 +-
 static/themes/redmond-xx.json                 |  2 +-
 static/themes/redmond-xxi.json                |  2 +-
 8 files changed, 48 insertions(+), 48 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 61f8800a..79c2fcd3 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -518,27 +518,27 @@
             />
           </div>
           <div class="color-item">
-            <h4>{{ $t('settings.style.advanced_colors.lightBg') }}</h4>
+            <h4>{{ $t('settings.style.advanced_colors.highlight') }}</h4>
             <ColorInput
-              v-model="lightBgColorLocal"
-              name="lightBg"
+              v-model="highlightColorLocal"
+              name="highlight"
               :label="$t('settings.background')"
-              :fallback="previewTheme.colors.lightBg"
+              :fallback="previewTheme.colors.highlight"
             />
             <ColorInput
-              v-model="lightBgTextColorLocal"
-              name="lightBgText"
+              v-model="highlightTextColorLocal"
+              name="highlightText"
               :label="$t('settings.text')"
-              :fallback="previewTheme.colors.lightBgText"
+              :fallback="previewTheme.colors.highlightText"
             />
-            <ContrastRatio :contrast="previewContrast.lightBgText" />
+            <ContrastRatio :contrast="previewContrast.highlightText" />
             <ColorInput
-              v-model="lightBgLinkColorLocal"
-              name="lightBgLink"
+              v-model="highlightLinkColorLocal"
+              name="highlightLink"
               :label="$t('settings.links')"
-              :fallback="previewTheme.colors.lightBgLink"
+              :fallback="previewTheme.colors.highlightLink"
             />
-            <ContrastRatio :contrast="previewContrast.lightBgLink" />
+            <ContrastRatio :contrast="previewContrast.highlightLink" />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.selectedPost') }}</h4>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 815c11b1..2622157a 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -449,7 +449,7 @@
         "underlay": "Underlay",
         "poll": "Poll graph",
         "icons": "Icons",
-        "lightBg": "Highlighted elements",
+        "highlight": "Highlighted elements",
         "pressed": "Pressed",
         "selectedPost": "Selected post",
         "selectedMenu": "Selected menu item",
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 63bfb5af..8c114004 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -47,7 +47,7 @@ export const LAYERS = {
   badge: null, //  no transparency support
   fg: null,
   bg: 'underlay',
-  lightBg: 'bg',
+  highlight: 'bg',
   panel: 'bg',
   btn: 'bg',
   btnPanel: 'panel',
@@ -148,57 +148,57 @@ export const SLOT_INHERITANCE = {
   cGreen: '#00FF00',
   cOrange: '#E3FF00',
 
-  lightBg: {
+  highlight: {
     depends: ['bg'],
     color: (mod, bg) => brightness(5 * mod, bg).rgb
   },
-  lightBgFaintText: {
+  highlightFaintText: {
     depends: ['faint'],
-    layer: 'lightBg',
+    layer: 'highlight',
     textColor: true
   },
-  lightBgFaintLink: {
+  highlightFaintLink: {
     depends: ['faintLink'],
-    layer: 'lightBg',
+    layer: 'highlight',
     textColor: 'preserve'
   },
-  lightBgText: {
+  highlightText: {
     depends: ['text'],
-    layer: 'lightBg',
+    layer: 'highlight',
     textColor: true
   },
-  lightBgLink: {
+  highlightLink: {
     depends: ['link'],
-    layer: 'lightBg',
+    layer: 'highlight',
     textColor: 'preserve'
   },
-  lightBgIcon: {
-    depends: ['lightBg', 'lightBgText'],
+  highlightIcon: {
+    depends: ['highlight', 'highlightText'],
     color: (mod, bg, text) => mixrgb(bg, text)
   },
 
-  selectedPost: '--lightBg',
+  selectedPost: '--highlight',
   selectedPostFaintText: {
-    depends: ['lightBgFaintText'],
-    layer: 'lightBg',
+    depends: ['highlightFaintText'],
+    layer: 'highlight',
     variant: 'selectedPost',
     textColor: true
   },
   selectedPostFaintLink: {
-    depends: ['lightBgFaintLink'],
-    layer: 'lightBg',
+    depends: ['highlightFaintLink'],
+    layer: 'highlight',
     variant: 'selectedPost',
     textColor: 'preserve'
   },
   selectedPostText: {
-    depends: ['lightBgText'],
-    layer: 'lightBg',
+    depends: ['highlightText'],
+    layer: 'highlight',
     variant: 'selectedPost',
     textColor: true
   },
   selectedPostLink: {
-    depends: ['lightBgLink'],
-    layer: 'lightBg',
+    depends: ['highlightLink'],
+    layer: 'highlight',
     variant: 'selectedPost',
     textColor: 'preserve'
   },
@@ -207,28 +207,28 @@ export const SLOT_INHERITANCE = {
     color: (mod, bg, text) => mixrgb(bg, text)
   },
 
-  selectedMenu: '--lightBg',
+  selectedMenu: '--highlight',
   selectedMenuFaintText: {
-    depends: ['lightBgFaintText'],
-    layer: 'lightBg',
+    depends: ['highlightFaintText'],
+    layer: 'highlight',
     variant: 'selectedMenu',
     textColor: true
   },
   selectedMenuFaintLink: {
-    depends: ['lightBgFaintLink'],
-    layer: 'lightBg',
+    depends: ['highlightFaintLink'],
+    layer: 'highlight',
     variant: 'selectedMenu',
     textColor: 'preserve'
   },
   selectedMenuText: {
-    depends: ['lightBgText'],
-    layer: 'lightBg',
+    depends: ['highlightText'],
+    layer: 'highlight',
     variant: 'selectedMenu',
     textColor: true
   },
   selectedMenuLink: {
-    depends: ['lightBgLink'],
-    layer: 'lightBg',
+    depends: ['highlightLink'],
+    layer: 'highlight',
     variant: 'selectedMenu',
     textColor: 'preserve'
   },
diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index 3aafda52..c3bdcf65 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -114,7 +114,7 @@
       "cGreen": "#27ae60",
       "cOrange": "#f67400",
       "btnPressed": "--accent",
-      "lightBg": "--accent",
+      "highlight": "--accent",
       "selectedPost": "--bg,10"
     },
     "radii": {
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index df6e1a66..478407c9 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -117,7 +117,7 @@
       "cGreen": "#27ae60",
       "cOrange": "#f67400",
       "btnPressed": "--accent",
-      "lightBg": "--accent",
+      "highlight": "--accent",
       "selectedPost": "--bg,10"
     },
     "radii": {
diff --git a/static/themes/redmond-xx-se.json b/static/themes/redmond-xx-se.json
index 13ed3b0e..1a867fcc 100644
--- a/static/themes/redmond-xx-se.json
+++ b/static/themes/redmond-xx-se.json
@@ -283,7 +283,7 @@
       "cBlue": "#008080",
       "cGreen": "#008000",
       "cOrange": "#808000",
-      "lightBg": "--accent",
+      "highlight": "--accent",
       "selectedPost": "--bg,-10"
     },
     "radii": {
diff --git a/static/themes/redmond-xx.json b/static/themes/redmond-xx.json
index 7c687ae0..78c92f10 100644
--- a/static/themes/redmond-xx.json
+++ b/static/themes/redmond-xx.json
@@ -274,7 +274,7 @@
       "cBlue": "#008080",
       "cGreen": "#008000",
       "cOrange": "#808000",
-      "lightBg": "--accent",
+      "highlight": "--accent",
       "selectedPost": "--bg,-10"
     },
     "radii": {
diff --git a/static/themes/redmond-xxi.json b/static/themes/redmond-xxi.json
index 5371ee64..28f68351 100644
--- a/static/themes/redmond-xxi.json
+++ b/static/themes/redmond-xxi.json
@@ -256,7 +256,7 @@
       "cBlue": "#6699cc",
       "cGreen": "#669966",
       "cOrange": "#cc6633",
-      "lightBg": "--accent",
+      "highlight": "--accent",
       "selectedPost": "--bg,-10"
     },
     "radii": {

From c1e3632f4245800df45c1585ff39c97f5377200b Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 22 Jan 2020 02:15:47 +0200
Subject: [PATCH 177/483] fix shadows not being valid from the get-go

---
 src/components/style_switcher/style_switcher.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 44e7595d..195c6b35 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -664,8 +664,10 @@ export default {
       try {
         this.updatePreviewColorsAndShadows()
         this.colorsInvalid = false
+        this.shadowsInvalid = false
       } catch (e) {
         this.colorsInvalid = true
+        this.shadowsInvalid = true
         console.warn(e)
       }
     },

From d98e31af458ed42e678072879265267e8148c627 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 22 Jan 2020 02:44:39 +0200
Subject: [PATCH 178/483] snapshot/source mismatch case

---
 .../style_switcher/style_switcher.js          | 53 ++++++++++++++++---
 .../style_switcher/style_switcher.vue         | 18 ++++++-
 src/i18n/en.json                              |  7 ++-
 3 files changed, 68 insertions(+), 10 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 195c6b35..3b538ac7 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -160,6 +160,9 @@ export default {
             )
         }
       } else if (origin === 'localStorage') {
+        if (type === 'snapshot_source_mismatch') {
+          return t(pre + 'snapshot_source_mismatch')
+        }
         // FE upgraded from v2
         if (themeEngineVersion === 2) {
           return t(pre + 'upgraded_from_v2')
@@ -345,7 +348,10 @@ export default {
         source.radii = this.currentRadii
       }
 
-      const theme = this.previewTheme
+      const theme = {
+        themeEngineVersion: CURRENT_VERSION,
+        ...this.previewTheme
+      }
 
       return {
         // To separate from other random JSON files and possible future source formats
@@ -381,17 +387,34 @@ export default {
       const version = (origin === 'localstorage' && !theme.colors)
         ? 'l1'
         : fileVersion
+      const snapshotEngineVersion = (theme || {}).themeEngineVersion
       const themeEngineVersion = (source || {}).themeEngineVersion || 2
       const versionsMatch = themeEngineVersion === CURRENT_VERSION
+      console.log(
+        theme !== undefined,
+        source !== undefined,
+        themeEngineVersion !== snapshotEngineVersion
+      )
+      const sourceSnapshotMismatch = (
+        theme !== undefined &&
+          source !== undefined &&
+          themeEngineVersion !== snapshotEngineVersion
+      )
       // Force loading of source if user requested it or if snapshot
       // is unavailable
       const forcedSourceLoad = (source && forceUseSource) || !theme
-      if (!versionsMatch &&
+      if (!(versionsMatch && !sourceSnapshotMismatch) &&
           !forcedSourceLoad &&
           version !== 'l1' &&
           origin !== 'defaults'
       ) {
-        if (!theme) {
+        if (sourceSnapshotMismatch) {
+          this.themeWarning = {
+            origin,
+            themeEngineVersion,
+            type: 'snapshot_source_mismatch'
+          }
+        } else if (!theme) {
           this.themeWarning = {
             origin,
             noActionsPossible: true,
@@ -428,7 +451,19 @@ export default {
       }
       this.dismissWarning()
     },
-    loadThemeFromLocalStorage (confirmLoadSource = false) {
+    forceSnapshot() {
+      const { origin } = this.themeWarning
+      switch (origin) {
+        case 'localstorage':
+          this.loadThemeFromLocalStorage(false, true)
+          break
+        case 'file':
+          console.err('Forcing snapshout from file is not supported yet')
+          break
+      }
+      this.dismissWarning()
+    },
+    loadThemeFromLocalStorage (confirmLoadSource = false, forceSnapshot = false) {
       const {
         customTheme: theme,
         customThemeSource: source
@@ -442,7 +477,10 @@ export default {
         )
       } else {
         this.loadTheme(
-          { theme, source },
+          {
+            theme,
+            source: forceSnapshot ? theme : source
+          },
           'localStorage',
           confirmLoadSource
         )
@@ -451,7 +489,10 @@ export default {
     setCustomTheme () {
       this.$store.dispatch('setOption', {
         name: 'customTheme',
-        value: this.previewTheme
+        value: {
+          themeEngineVersion: CURRENT_VERSION,
+          ...this.previewTheme
+        }
       })
       this.$store.dispatch('setOption', {
         name: 'customThemeSource',
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 79c2fcd3..793d68f1 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -7,7 +7,21 @@
         {{ themeWarningHelp }}
         </div>
         <div class="buttons">
-          <template v-if="themeWarning.noActionsPossible">
+          <template v-if="themeWarning.type === 'snapshot_source_mismatch'">
+            <button
+              class="btn"
+              @click="forceLoad"
+            >
+              {{ $t('settings.style.switcher.use_source') }}
+            </button>
+            <button
+              class="btn"
+              @click="dismissWarning"
+            >
+              {{ $t('settings.style.switcher.use_snapshot') }}
+            </button>
+          </template>
+          <template v-else-if="themeWarning.noActionsPossible">
             <button
               class="btn"
               @click="dismissWarning"
@@ -26,7 +40,7 @@
               class="btn"
               @click="dismissWarning"
             >
-              {{ $t('settings.style.switcher.use_snapshot') }}
+              {{ $t('settings.style.switcher.keep_as_is') }}
             </button>
           </template>
         </div>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 2622157a..81dde663 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -397,7 +397,9 @@
         "clear_all": "Clear all",
         "clear_opacity": "Clear opacity",
         "load_theme": "Load theme",
-        "use_snapshot": "Keep as is",
+        "keep_as_is": "Keep as is",
+        "use_snapshot": "Old version",
+        "use_source": "New version",
         "help": {
           "upgraded_from_v2": "PleromaFE has been upgraded, theme could look a little bit different than you remember.",
           "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsitencies.",
@@ -408,7 +410,8 @@
           "fe_upgraded": "PleromaFE's theme engine upgraded after version update.",
           "fe_downgraded": "PleromaFE's version rolled back.",
           "migration_snapshot_ok": "Just to be safe, theme snapshot loaded. You can try loading theme data.",
-          "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember."
+          "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember.",
+          "snapshot_source_mismatch": "Versions conflict: most likely FE was rolled back and updated again, if you changed theme using older version of FE you most likely want to use old version, otherwise use new version."
         }
       },
       "common": {

From c7f42b7799d4a0484f7d40ac0a7377021caf9cb6 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 22 Jan 2020 02:53:40 +0200
Subject: [PATCH 179/483] made it actually work, the forceSnapshot

---
 src/components/style_switcher/style_switcher.js  | 11 +++--------
 src/components/style_switcher/style_switcher.vue |  2 +-
 2 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 3b538ac7..27df0d10 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -390,11 +390,6 @@ export default {
       const snapshotEngineVersion = (theme || {}).themeEngineVersion
       const themeEngineVersion = (source || {}).themeEngineVersion || 2
       const versionsMatch = themeEngineVersion === CURRENT_VERSION
-      console.log(
-        theme !== undefined,
-        source !== undefined,
-        themeEngineVersion !== snapshotEngineVersion
-      )
       const sourceSnapshotMismatch = (
         theme !== undefined &&
           source !== undefined &&
@@ -442,7 +437,7 @@ export default {
     forceLoad () {
       const { origin } = this.themeWarning
       switch (origin) {
-        case 'localstorage':
+        case 'localStorage':
           this.loadThemeFromLocalStorage(true)
           break
         case 'file':
@@ -451,10 +446,10 @@ export default {
       }
       this.dismissWarning()
     },
-    forceSnapshot() {
+    forceSnapshot () {
       const { origin } = this.themeWarning
       switch (origin) {
-        case 'localstorage':
+        case 'localStorage':
           this.loadThemeFromLocalStorage(false, true)
           break
         case 'file':
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 793d68f1..ff7f4710 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -16,7 +16,7 @@
             </button>
             <button
               class="btn"
-              @click="dismissWarning"
+              @click="forceSnapshot"
             >
               {{ $t('settings.style.switcher.use_snapshot') }}
             </button>

From 8de7eabd8fdfc9f97fb262552f0cca9fd6da95eb Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 22 Jan 2020 23:26:24 +0200
Subject: [PATCH 180/483] v2 compatibility fixes

---
 src/boot/after_store.js                       |  2 +-
 .../style_switcher/style_switcher.js          |  7 ++-
 src/services/color_convert/color_convert.js   |  5 +-
 src/services/style_setter/style_setter.js     | 49 ++++++++++++++++++-
 src/services/theme_data/theme_data.service.js | 12 +++--
 5 files changed, 62 insertions(+), 13 deletions(-)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 6c4f0e1b..f61e9252 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -280,7 +280,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
   const customThemePresent = customThemeSource || customTheme
 
   if (customThemePresent) {
-    if (customThemeSource && customThemeSource.version === CURRENT_VERSION) {
+    if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) {
       applyTheme(customThemeSource)
     } else {
       applyTheme(customTheme)
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 27df0d10..76e6cdb7 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -12,7 +12,8 @@ import {
   generateFonts,
   composePreset,
   getThemes,
-  shadows2to3
+  shadows2to3,
+  colors2to3
 } from '../../services/style_setter/style_setter.js'
 import {
   CURRENT_VERSION,
@@ -588,7 +589,9 @@ export default {
       const opacity = input.opacity
       const shadows = input.shadows || {}
       const fonts = input.fonts || {}
-      const colors = input.colors || input
+      const colors = !input.themeEngineVersion
+        ? colors2to3(input.colors)
+        : input.colors || input
 
       if (version === 0) {
         if (input.version) version = input.version
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 6b228a58..93cb1ba6 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -185,10 +185,9 @@ export const rgba2css = function (rgba) {
  * @param {Boolean} preserve - try to preserve intended text color's hue/saturation (i.e. no BW)
  */
 export const getTextColor = function (bg, text, preserve) {
-  const bgIsLight = relativeLuminance(bg) > 0.5
-  const textIsLight = relativeLuminance(text) > 0.5
+  const contrast = getContrastRatio(bg, text)
 
-  if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) {
+  if (contrast < 4.5) {
     const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
     const result = Object.assign(base, invertLightness(text).rgb)
     if (!preserve && getContrastRatio(bg, result) < 4.5) {
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 9610d799..b43eb0dd 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -110,7 +110,9 @@ const getCssShadowFilter = (input) => {
 }
 
 export const generateColors = (themeData) => {
-  const sourceColors = themeData.colors || themeData
+  const sourceColors = !themeData.themeEngineVersion
+    ? colors2to3(themeData.colors)
+    : themeData.colors || themeData
 
   const isLightOnDark = convert(sourceColors.bg).hsl.l < convert(sourceColors.text).hsl.l
   const mod = isLightOnDark ? 1 : -1
@@ -283,9 +285,12 @@ export const DEFAULT_SHADOWS = {
   }]
 }
 export const generateShadows = (input, colors, mod) => {
+  const inputShadows = !input.themeEngineVersion
+    ? shadows2to3(input.shadows)
+    : input.shadows || {}
   const shadows = Object.entries({
     ...DEFAULT_SHADOWS,
-    ...(input.shadows || {})
+    ...inputShadows
   }).reduce((shadowsAcc, [slotName, shadowDefs]) => {
     const newShadow = shadowDefs.reduce((shadowAcc, def) => [
       ...shadowAcc,
@@ -374,6 +379,46 @@ export const getThemes = () => {
         }, {})
     })
 }
+export const colors2to3 = (colors) => {
+  return Object.entries(colors).reduce((acc, [slotName, color]) => {
+    const btnStates = ['', 'Pressed', 'Disabled', 'Toggled']
+    const btnPositions = ['', 'Panel', 'TopBar']
+    switch (slotName) {
+      case 'lightBg':
+        return { ...acc, highlight: color }
+      case 'btn':
+        return {
+          ...acc,
+          ...btnStates.reduce((stateAcc, state) => ({ ...stateAcc, ['btn' + state]: color }), {})
+        }
+      case 'btnText':
+        console.log(
+          btnPositions
+            .map(position => btnStates.map(state => state + position))
+            .flat()
+            .reduce(
+              (statePositionAcc, statePosition) =>
+                ({ ...statePositionAcc, ['btn' + statePosition + 'Text']: color })
+              , {}
+            )
+        )
+        return {
+          ...acc,
+          ...btnPositions
+            .map(position => btnStates.map(state => state + position))
+            .flat()
+            .reduce(
+              (statePositionAcc, statePosition) =>
+                ({ ...statePositionAcc, ['btn' + statePosition + 'Text']: color })
+              , {}
+            )
+        }
+      default:
+        console.log('aaa', slotName, color, acc)
+        return { ...acc, [slotName]: color }
+    }
+  }, {})
+}
 
 /**
  * This handles compatibility issues when importing v2 theme's shadows to current format
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 8c114004..2cae85de 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -283,12 +283,12 @@ export const SLOT_INHERITANCE = {
     opacity: 'panel'
   },
   panelText: {
-    depends: ['fgText'],
+    depends: ['text'],
     layer: 'panel',
     textColor: true
   },
   panelFaint: {
-    depends: ['fgText'],
+    depends: ['text'],
     layer: 'panel',
     opacity: 'faint',
     textColor: true
@@ -302,7 +302,7 @@ export const SLOT_INHERITANCE = {
   // Top bar
   topBar: '--fg',
   topBarText: {
-    depends: ['fgText'],
+    depends: ['text'],
     layer: 'topBar',
     textColor: true
   },
@@ -313,7 +313,9 @@ export const SLOT_INHERITANCE = {
   },
 
   // Tabs
-  tab: '--btn',
+  tab: {
+    depends: ['btn']
+  },
   tabText: {
     depends: ['btnText'],
     layer: 'btn',
@@ -331,7 +333,7 @@ export const SLOT_INHERITANCE = {
     opacity: 'btn'
   },
   btnText: {
-    depends: ['fgText'],
+    depends: ['text'],
     layer: 'btn',
     textColor: true
   },

From 7354b6f7062def8bd1ac0a0e457cd05256eba0d5 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 23 Jan 2020 00:35:56 +0200
Subject: [PATCH 181/483] fixed get(Layer|Opacity)Slot not working properly
 which broke Mojave theme

---
 .../style_switcher/style_switcher.js          |  2 +-
 src/services/style_setter/style_setter.js     | 11 ----
 src/services/theme_data/theme_data.service.js | 60 +++++++++----------
 3 files changed, 30 insertions(+), 43 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 76e6cdb7..06ef71a7 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -246,7 +246,7 @@ export default {
           if (!slotIsText) return acc
           const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
           const background = variant || layer
-          const opacitySlot = getOpacitySlot(SLOT_INHERITANCE[background])
+          const opacitySlot = getOpacitySlot(background)
           const textColors = [
             key,
             ...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : [])
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index b43eb0dd..7f9f17be 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -392,16 +392,6 @@ export const colors2to3 = (colors) => {
           ...btnStates.reduce((stateAcc, state) => ({ ...stateAcc, ['btn' + state]: color }), {})
         }
       case 'btnText':
-        console.log(
-          btnPositions
-            .map(position => btnStates.map(state => state + position))
-            .flat()
-            .reduce(
-              (statePositionAcc, statePosition) =>
-                ({ ...statePositionAcc, ['btn' + statePosition + 'Text']: color })
-              , {}
-            )
-        )
         return {
           ...acc,
           ...btnPositions
@@ -414,7 +404,6 @@ export const colors2to3 = (colors) => {
             )
         }
       default:
-        console.log('aaa', slotName, color, acc)
         return { ...acc, [slotName]: color }
     }
   }, {})
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 2cae85de..5e49fa17 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -591,38 +591,41 @@ export const topoSort = (
   })
 }
 
+const expandSlotValue = (value) => {
+  if (typeof value === 'object') return value
+  return {
+    depends: value.startsWith('--') ? [value.substring(2)] : [],
+    default: value.startsWith('#') ? value : undefined
+  }
+}
 /**
  * retrieves opacity slot for given slot. This goes up the depenency graph
  * to find which parent has opacity slot defined for it.
  * TODO refactor this
  */
 export const getOpacitySlot = (
-  v,
+  k,
   inheritance = SLOT_INHERITANCE,
   getDeps = getDependencies
 ) => {
-  const value = typeof v === 'string'
-    ? {
-      depends: v.startsWith('--') ? [v.substring(2)] : []
-    }
-    : v
+  const value = expandSlotValue(inheritance[k])
   if (value.opacity === null) return
-  if (value.opacity) return v.opacity
-  const findInheritedOpacity = (val) => {
-    const depSlot = val.depends[0]
+  if (value.opacity) return value.opacity
+  const findInheritedOpacity = (key, visited = [k]) => {
+    const depSlot = getDeps(key, inheritance)[0]
     if (depSlot === undefined) return
-    const dependency = getDeps(depSlot, inheritance)[0]
+    const dependency = inheritance[depSlot]
     if (dependency === undefined) return
     if (dependency.opacity || dependency === null) {
       return dependency.opacity
-    } else if (dependency.depends) {
-      return findInheritedOpacity(dependency)
+    } else if (dependency.depends && visited.includes(depSlot)) {
+      return findInheritedOpacity(depSlot, [...visited, depSlot])
     } else {
       return null
     }
   }
   if (value.depends) {
-    return findInheritedOpacity(value)
+    return findInheritedOpacity(k)
   }
 }
 
@@ -635,33 +638,28 @@ export const getOpacitySlot = (
  */
 export const getLayerSlot = (
   k,
-  v,
   inheritance = SLOT_INHERITANCE,
   getDeps = getDependencies
 ) => {
-  const value = typeof v === 'string'
-    ? {
-      depends: v.startsWith('--') ? [v.substring(2)] : []
-    }
-    : v
+  const value = expandSlotValue(inheritance[k])
   if (LAYERS[k]) return k
   if (value.layer === null) return
-  if (value.layer) return v.layer
-  const findInheritedLayer = (val) => {
-    const depSlot = val.depends[0]
+  if (value.layer) return value.layer
+  const findInheritedLayer = (key, visited = [k]) => {
+    const depSlot = getDeps(key, inheritance)[0]
     if (depSlot === undefined) return
-    const dependency = getDeps(depSlot, inheritance)[0]
+    const dependency = inheritance[depSlot]
     if (dependency === undefined) return
     if (dependency.layer || dependency === null) {
       return dependency.layer
     } else if (dependency.depends) {
-      return findInheritedLayer(dependency)
+      return findInheritedLayer(dependency, [...visited, depSlot])
     } else {
       return null
     }
   }
   if (value.depends) {
-    return findInheritedLayer(value)
+    return findInheritedLayer(k)
   }
 }
 
@@ -679,7 +677,7 @@ export const SLOT_ORDERED = topoSort(
  * with them
  */
 export const SLOTS_OPACITIES_DICT = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
-  const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies)
+  const opacity = getOpacitySlot(k, SLOT_INHERITANCE, getDependencies)
   if (opacity) {
     return { ...acc, [k]: opacity }
   } else {
@@ -692,7 +690,7 @@ export const SLOTS_OPACITIES_DICT = Object.entries(SLOT_INHERITANCE).reduce((acc
  * color slots.
  */
 export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
-  const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies)
+  const opacity = getOpacitySlot(k, SLOT_INHERITANCE, getDependencies)
   if (opacity) {
     return {
       ...acc,
@@ -738,9 +736,9 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
     if (targetColor === 'transparent') {
       // We take only layers below current one
       const layers = getLayers(
-        getLayerSlot(key, value),
+        getLayerSlot(key),
         key,
-        value.opacity || key,
+        getOpacitySlot(key) || key,
         colors,
         opacity
       ).slice(0, -1)
@@ -781,7 +779,7 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
         getLayers(
           value.layer,
           value.variant || value.layer,
-          getOpacitySlot(SLOT_INHERITANCE[value.variant || value.layer]),
+          getOpacitySlot(value.variant || value.layer),
           colors,
           opacity
         )
@@ -813,7 +811,7 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
   if (!outputColor) {
     throw new Error('Couldn\'t generate color for ' + key)
   }
-  const opacitySlot = SLOTS_OPACITIES_DICT[key]
+  const opacitySlot = getOpacitySlot(key)
   if (opacitySlot && outputColor.a === undefined) {
     outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1
   }

From 64fc07f080635c83f27b9a5541be32013f1d9a26 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 23 Jan 2020 00:36:38 +0200
Subject: [PATCH 182/483] removed unused constant, using getOpacitySlot now

---
 src/services/theme_data/theme_data.service.js | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 5e49fa17..bbabd175 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -672,19 +672,6 @@ export const SLOT_ORDERED = topoSort(
     .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
 )
 
-/**
- * Dictionary where keys are color slots and values are opacity associated
- * with them
- */
-export const SLOTS_OPACITIES_DICT = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
-  const opacity = getOpacitySlot(k, SLOT_INHERITANCE, getDependencies)
-  if (opacity) {
-    return { ...acc, [k]: opacity }
-  } else {
-    return acc
-  }
-}, {})
-
 /**
  * All opacity slots used in color slots, their default values and affected
  * color slots.

From 9d8dbd83400bd48006873ff2dae220c0a46c5f2e Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 23 Jan 2020 12:00:50 -0600
Subject: [PATCH 183/483] Fix missing TWKN when logged in, but server is set to
 private mode.

---
 src/components/nav_panel/nav_panel.vue     | 2 +-
 src/components/side_drawer/side_drawer.vue | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 034259d9..0f3296eb 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -33,7 +33,7 @@
             <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
           </router-link>
         </li>
-        <li v-if="federating && !privateMode">
+        <li v-if="federating && (currentUser || !privateMode)">
           <router-link :to="{ name: 'public-external-timeline' }">
             <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
           </router-link>
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 3fba9058..28637afc 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -88,7 +88,7 @@
           </router-link>
         </li>
         <li
-          v-if="federating && !privateMode"
+          v-if="federating && (currentUser || !privateMode)"
           @click="toggleDrawer"
         >
           <router-link to="/main/all">

From a69723badf45a62cb9f6e0adc09b49ff10c9eaac Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 23 Jan 2020 22:04:05 +0200
Subject: [PATCH 184/483] fix snapshot mismatch message for file

---
 src/components/style_switcher/style_switcher.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 06ef71a7..59ec1bf5 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -385,7 +385,7 @@ export default {
       if (!source && !theme) {
         throw new Error('Can\'t load theme: empty')
       }
-      const version = (origin === 'localstorage' && !theme.colors)
+      const version = (origin === 'localStorage' && !theme.colors)
         ? 'l1'
         : fileVersion
       const snapshotEngineVersion = (theme || {}).themeEngineVersion
@@ -404,7 +404,7 @@ export default {
           version !== 'l1' &&
           origin !== 'defaults'
       ) {
-        if (sourceSnapshotMismatch) {
+        if (sourceSnapshotMismatch && origin === 'localStorage') {
           this.themeWarning = {
             origin,
             themeEngineVersion,

From c752f56d0b7711e9a815468a19e440363a6dcf02 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 23 Jan 2020 22:26:49 +0200
Subject: [PATCH 185/483] v l1 compatibility

---
 src/components/style_switcher/style_switcher.js | 2 +-
 src/services/style_setter/style_setter.js       | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 59ec1bf5..14da4f80 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -590,7 +590,7 @@ export default {
       const shadows = input.shadows || {}
       const fonts = input.fonts || {}
       const colors = !input.themeEngineVersion
-        ? colors2to3(input.colors)
+        ? colors2to3(input.colors || input)
         : input.colors || input
 
       if (version === 0) {
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 7f9f17be..533145d4 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -111,7 +111,7 @@ const getCssShadowFilter = (input) => {
 
 export const generateColors = (themeData) => {
   const sourceColors = !themeData.themeEngineVersion
-    ? colors2to3(themeData.colors)
+    ? colors2to3(themeData.colors || themeData)
     : themeData.colors || themeData
 
   const isLightOnDark = convert(sourceColors.bg).hsl.l < convert(sourceColors.text).hsl.l
@@ -285,7 +285,7 @@ export const DEFAULT_SHADOWS = {
   }]
 }
 export const generateShadows = (input, colors, mod) => {
-  const inputShadows = !input.themeEngineVersion
+  const inputShadows = input.shadows && !input.themeEngineVersion
     ? shadows2to3(input.shadows)
     : input.shadows || {}
   const shadows = Object.entries({

From f85a3e3f6dfb7961a94eb5a9570e0d3dcb8f55e9 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 23 Jan 2020 22:42:59 +0200
Subject: [PATCH 186/483] style the dropdown menus better

---
 src/components/popper/popper.scss | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss
index 06daa871..9c2ce25f 100644
--- a/src/components/popper/popper.scss
+++ b/src/components/popper/popper.scss
@@ -137,11 +137,14 @@
       }
     }
 
-    &:hover {
-      // TODO: improve the look on breeze themes
-      background-color: $fallback--fg;
-      background-color: var(--btn, $fallback--fg);
-      box-shadow: none;
+    &:active, &:hover {
+      background-color: $fallback--lightBg;
+      background-color: var(--selectedMenu, $fallback--lightBg);
+      color: $fallback--link;
+      color: var(--selectedMenuText, $fallback--link);
+      --faint: var(--selectedMenuFaintText, $fallback--faint);
+      --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+      --icon: var(--selectedMenuIcon, $fallback--icon);
     }
   }
 }

From c1f2457112c38e680f8fb9050cbc44b2dea3a113 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 23 Jan 2020 22:48:32 +0200
Subject: [PATCH 187/483] revert fgText -> text after some consideration. case
 was fixed already in other way

---
 src/services/theme_data/theme_data.service.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index bbabd175..d88bb9ea 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -288,7 +288,7 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
   panelFaint: {
-    depends: ['text'],
+    depends: ['fgText'],
     layer: 'panel',
     opacity: 'faint',
     textColor: true
@@ -302,7 +302,7 @@ export const SLOT_INHERITANCE = {
   // Top bar
   topBar: '--fg',
   topBarText: {
-    depends: ['text'],
+    depends: ['fgText'],
     layer: 'topBar',
     textColor: true
   },
@@ -333,7 +333,7 @@ export const SLOT_INHERITANCE = {
     opacity: 'btn'
   },
   btnText: {
-    depends: ['text'],
+    depends: ['fgText'],
     layer: 'btn',
     textColor: true
   },

From f7ea12c905d0cf0a9f5e0d1284a7803ef4d7abad Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 23 Jan 2020 23:19:48 +0200
Subject: [PATCH 188/483] separate actual theme data from theme framework

---
 .../style_switcher/style_switcher.js          |   4 +-
 src/services/theme_data/pleromafe.js          | 455 +++++++++++++++++
 src/services/theme_data/theme_data.service.js | 457 +-----------------
 3 files changed, 460 insertions(+), 456 deletions(-)
 create mode 100644 src/services/theme_data/pleromafe.js

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 14da4f80..17ae9f30 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -15,9 +15,11 @@ import {
   shadows2to3,
   colors2to3
 } from '../../services/style_setter/style_setter.js'
+import {
+  SLOT_INHERITANCE
+} from '../../services/theme_data/pleromafe.js'
 import {
   CURRENT_VERSION,
-  SLOT_INHERITANCE,
   OPACITIES,
   getLayers,
   getOpacitySlot
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
new file mode 100644
index 00000000..0deb829f
--- /dev/null
+++ b/src/services/theme_data/pleromafe.js
@@ -0,0 +1,455 @@
+import { brightness } from 'chromatism'
+import { alphaBlend, mixrgb } from '../color_convert/color_convert.js'
+/* This is a definition of all layer combinations
+ * each key is a topmost layer, each value represents layer underneath
+ * this is essentially a simplified tree
+ */
+export const LAYERS = {
+  undelay: null, // root
+  topBar: null, // no transparency support
+  badge: null, //  no transparency support
+  fg: null,
+  bg: 'underlay',
+  highlight: 'bg',
+  panel: 'bg',
+  btn: 'bg',
+  btnPanel: 'panel',
+  btnTopBar: 'topBar',
+  input: 'bg',
+  inputPanel: 'panel',
+  inputTopBar: 'topBar',
+  alert: 'bg',
+  alertPanel: 'panel',
+  poll: 'bg'
+}
+
+/* By default opacity slots have 1 as default opacity
+ * this allows redefining it to something else
+ */
+export const DEFAULT_OPACITY = {
+  alert: 0.5,
+  input: 0.5,
+  faint: 0.5,
+  underlay: 0.15
+}
+
+/**  SUBJECT TO CHANGE IN THE FUTURE, this is all beta
+ * Color and opacity slots definitions. Each key represents a slot.
+ *
+ * Short-hands:
+ * String beginning with `--` - value after dashes treated as sole
+ *     dependency - i.e. `--value` equivalent to { depends: ['value']}
+ * String beginning with `#` - value would be treated as solid color
+ *     defined in hexadecimal representation (i.e. #FFFFFF) and will be
+ *     used as default. `#FFFFFF` is equivalent to { default: '#FFFFFF'}
+ *
+ * Full definition:
+ * @property {String[]} depends - color slot names this color depends ones.
+ *   cyclic dependencies are supported to some extent but not recommended.
+ * @property {String} [opacity] - opacity slot used by this color slot.
+ *   opacity is inherited from parents. To break inheritance graph use null
+ * @property {Number} [priority] - EXPERIMENTAL. used to pre-sort slots so
+ *   that slots with higher priority come earlier
+ * @property {Function(mod, ...colors)} [color] - function that will be
+ *   used to determine the color. By default it just copies first color in
+ *   dependency list.
+ * @argument {Number} mod - `1` (light-on-dark) or `-1` (dark-on-light)
+ *   depending on background color (for textColor)/given color.
+ * @argument {...Object} deps - each argument after mod represents each
+ *   color from `depends` array. All colors take user customizations into
+ *   account and represented by { r, g, b } objects.
+ * @returns {Object} resulting color, should be in { r, g, b } form
+ *
+ * @property {Boolean|String} [textColor] - true to mark color slot as text
+ *   color. This enables automatic text color generation for the slot. Use
+ *   'preserve' string if you don't want text color to fall back to
+ *   black/white. Use 'bw' to only ever use black or white. This also makes
+ *   following properties required:
+ * @property {String} [layer] - which layer the text sit on top on - used
+ *   to account for transparency in text color calculation
+ *   layer is inherited from parents. To break inheritance graph use null
+ * @property {String} [variant] - which color slot is background (same as
+ *   above, used to account for transparency)
+ */
+export const SLOT_INHERITANCE = {
+  bg: {
+    depends: [],
+    opacity: 'bg',
+    priority: 1
+  },
+  fg: {
+    depends: [],
+    priority: 1
+  },
+  text: {
+    depends: [],
+    priority: 1
+  },
+  underlay: {
+    default: '#000000',
+    opacity: 'underlay'
+  },
+  link: {
+    depends: ['accent'],
+    priority: 1
+  },
+  accent: {
+    depends: ['link'],
+    priority: 1
+  },
+  faint: {
+    depends: ['text'],
+    opacity: 'faint'
+  },
+  faintLink: {
+    depends: ['link'],
+    opacity: 'faint'
+  },
+
+  cBlue: '#0000ff',
+  cRed: '#FF0000',
+  cGreen: '#00FF00',
+  cOrange: '#E3FF00',
+
+  highlight: {
+    depends: ['bg'],
+    color: (mod, bg) => brightness(5 * mod, bg).rgb
+  },
+  highlightFaintText: {
+    depends: ['faint'],
+    layer: 'highlight',
+    textColor: true
+  },
+  highlightFaintLink: {
+    depends: ['faintLink'],
+    layer: 'highlight',
+    textColor: 'preserve'
+  },
+  highlightText: {
+    depends: ['text'],
+    layer: 'highlight',
+    textColor: true
+  },
+  highlightLink: {
+    depends: ['link'],
+    layer: 'highlight',
+    textColor: 'preserve'
+  },
+  highlightIcon: {
+    depends: ['highlight', 'highlightText'],
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
+  selectedPost: '--highlight',
+  selectedPostFaintText: {
+    depends: ['highlightFaintText'],
+    layer: 'highlight',
+    variant: 'selectedPost',
+    textColor: true
+  },
+  selectedPostFaintLink: {
+    depends: ['highlightFaintLink'],
+    layer: 'highlight',
+    variant: 'selectedPost',
+    textColor: 'preserve'
+  },
+  selectedPostText: {
+    depends: ['highlightText'],
+    layer: 'highlight',
+    variant: 'selectedPost',
+    textColor: true
+  },
+  selectedPostLink: {
+    depends: ['highlightLink'],
+    layer: 'highlight',
+    variant: 'selectedPost',
+    textColor: 'preserve'
+  },
+  selectedPostIcon: {
+    depends: ['selectedPost', 'selectedPostText'],
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
+  selectedMenu: '--highlight',
+  selectedMenuFaintText: {
+    depends: ['highlightFaintText'],
+    layer: 'highlight',
+    variant: 'selectedMenu',
+    textColor: true
+  },
+  selectedMenuFaintLink: {
+    depends: ['highlightFaintLink'],
+    layer: 'highlight',
+    variant: 'selectedMenu',
+    textColor: 'preserve'
+  },
+  selectedMenuText: {
+    depends: ['highlightText'],
+    layer: 'highlight',
+    variant: 'selectedMenu',
+    textColor: true
+  },
+  selectedMenuLink: {
+    depends: ['highlightLink'],
+    layer: 'highlight',
+    variant: 'selectedMenu',
+    textColor: 'preserve'
+  },
+  selectedMenuIcon: {
+    depends: ['selectedMenu', 'selectedMenuText'],
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
+  lightText: {
+    depends: ['text'],
+    color: (mod, text) => brightness(20 * mod, text).rgb
+  },
+
+  border: {
+    depends: ['fg'],
+    opacity: 'border',
+    color: (mod, fg) => brightness(2 * mod, fg).rgb
+  },
+
+  poll: {
+    depends: ['accent', 'bg'],
+    copacity: 'poll',
+    color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg)
+  },
+  pollText: {
+    depends: ['text'],
+    layer: 'poll',
+    textColor: true
+  },
+
+  icon: {
+    depends: ['bg', 'text'],
+    inheritsOpacity: false,
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
+  // Foreground
+  fgText: {
+    depends: ['text'],
+    layer: 'fg',
+    textColor: true
+  },
+  fgLink: {
+    depends: ['link'],
+    layer: 'fg',
+    textColor: 'preserve'
+  },
+
+  // Panel header
+  panel: {
+    depends: ['fg'],
+    opacity: 'panel'
+  },
+  panelText: {
+    depends: ['text'],
+    layer: 'panel',
+    textColor: true
+  },
+  panelFaint: {
+    depends: ['fgText'],
+    layer: 'panel',
+    opacity: 'faint',
+    textColor: true
+  },
+  panelLink: {
+    depends: ['fgLink'],
+    layer: 'panel',
+    textColor: 'preserve'
+  },
+
+  // Top bar
+  topBar: '--fg',
+  topBarText: {
+    depends: ['fgText'],
+    layer: 'topBar',
+    textColor: true
+  },
+  topBarLink: {
+    depends: ['fgLink'],
+    layer: 'topBar',
+    textColor: 'preserve'
+  },
+
+  // Tabs
+  tab: {
+    depends: ['btn']
+  },
+  tabText: {
+    depends: ['btnText'],
+    layer: 'btn',
+    textColor: true
+  },
+  tabActiveText: {
+    depends: ['text'],
+    layer: 'bg',
+    textColor: true
+  },
+
+  // Buttons
+  btn: {
+    depends: ['fg'],
+    opacity: 'btn'
+  },
+  btnText: {
+    depends: ['fgText'],
+    layer: 'btn',
+    textColor: true
+  },
+  btnPanelText: {
+    depends: ['panelText'],
+    layer: 'btnPanel',
+    variant: 'btn',
+    textColor: true
+  },
+  btnTopBarText: {
+    depends: ['topBarText'],
+    layer: 'btnTopBar',
+    variant: 'btn',
+    textColor: true
+  },
+
+  // Buttons: pressed
+  btnPressed: '--btn',
+  btnPressedText: {
+    depends: ['btnText'],
+    layer: 'btn',
+    variant: 'btnPressed',
+    textColor: true
+  },
+  btnPressedPanel: {
+    depends: ['btnPressed']
+  },
+  btnPressedPanelText: {
+    depends: ['btnPanelText'],
+    layer: 'btnPanel',
+    variant: 'btnPressed',
+    textColor: true
+  },
+  btnPressedTopBarText: {
+    depends: ['btnTopBarText'],
+    layer: 'btnTopBar',
+    variant: 'btnPressed',
+    textColor: true
+  },
+
+  // Buttons: toggled
+  btnToggled: {
+    depends: ['btn'],
+    color: (mod, btn) => brightness(mod * 20, btn).rgb
+  },
+  btnToggledText: {
+    depends: ['btnText'],
+    layer: 'btn',
+    variant: 'btnToggled',
+    textColor: true
+  },
+  btnToggledPanelText: {
+    depends: ['btnPanelText'],
+    layer: 'btnPanel',
+    variant: 'btnToggled',
+    textColor: true
+  },
+  btnToggledTopBarText: {
+    depends: ['btnTopBarText'],
+    layer: 'btnTopBar',
+    variant: 'btnToggled',
+    textColor: true
+  },
+
+  // Buttons: disabled
+  btnDisabled: {
+    depends: ['btn', 'bg'],
+    color: (mod, btn, bg) => alphaBlend(btn, 0.5, bg)
+  },
+  btnDisabledText: {
+    depends: ['btnText'],
+    layer: 'btn',
+    variant: 'btnDisabled',
+    textColor: true,
+    color: (mod, text) => brightness(mod * -60, text).rgb
+  },
+  btnDisabledPanelText: {
+    depends: ['btnPanelText'],
+    layer: 'btnPanel',
+    variant: 'btnDisabled',
+    textColor: true,
+    color: (mod, text) => brightness(mod * -60, text).rgb
+  },
+  btnDisabledTopBarText: {
+    depends: ['btnTopBarText'],
+    layer: 'btnTopBar',
+    variant: 'btnDisabled',
+    textColor: true,
+    color: (mod, text) => brightness(mod * -60, text).rgb
+  },
+
+  // Input fields
+  input: {
+    depends: ['fg'],
+    opacity: 'input'
+  },
+  inputText: {
+    depends: ['text'],
+    layer: 'input',
+    textColor: true
+  },
+  inputPanelText: {
+    depends: ['panelText'],
+    layer: 'inputPanel',
+    variant: 'input',
+    textColor: true
+  },
+  inputTopbarText: {
+    depends: ['topBarText'],
+    layer: 'inputTopBar',
+    variant: 'input',
+    textColor: true
+  },
+
+  alertError: {
+    depends: ['cRed'],
+    opacity: 'alert'
+  },
+  alertErrorText: {
+    depends: ['text'],
+    layer: 'alert',
+    variant: 'alertError',
+    textColor: true
+  },
+  alertErrorPanelText: {
+    depends: ['panelText'],
+    layer: 'alertPanel',
+    variant: 'alertError',
+    textColor: true
+  },
+
+  alertWarning: {
+    depends: ['cOrange'],
+    opacity: 'alert'
+  },
+  alertWarningText: {
+    depends: ['text'],
+    layer: 'alert',
+    variant: 'alertWarning',
+    textColor: true
+  },
+  alertWarningPanelText: {
+    depends: ['panelText'],
+    layer: 'alertPanel',
+    variant: 'alertWarning',
+    textColor: true
+  },
+
+  badgeNotification: '--cRed',
+  badgeNotificationText: {
+    depends: ['text', 'badgeNotification'],
+    layer: 'badge',
+    variant: 'badgeNotification',
+    textColor: 'bw'
+  }
+}
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index d88bb9ea..ea28481b 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -1,5 +1,6 @@
 import { convert, brightness, contrastRatio } from 'chromatism'
-import { alphaBlend, alphaBlendLayers, getTextColor, mixrgb } from '../color_convert/color_convert.js'
+import { alphaBlendLayers, getTextColor } from '../color_convert/color_convert.js'
+import { LAYERS, DEFAULT_OPACITY, SLOT_INHERITANCE } from './pleromafe.js'
 
 /*
  * # What's all this?
@@ -37,460 +38,6 @@ import { alphaBlend, alphaBlendLayers, getTextColor, mixrgb } from '../color_con
  */
 export const CURRENT_VERSION = 3
 
-/* This is a definition of all layer combinations
- * each key is a topmost layer, each value represents layer underneath
- * this is essentially a simplified tree
- */
-export const LAYERS = {
-  undelay: null, // root
-  topBar: null, // no transparency support
-  badge: null, //  no transparency support
-  fg: null,
-  bg: 'underlay',
-  highlight: 'bg',
-  panel: 'bg',
-  btn: 'bg',
-  btnPanel: 'panel',
-  btnTopBar: 'topBar',
-  input: 'bg',
-  inputPanel: 'panel',
-  inputTopBar: 'topBar',
-  alert: 'bg',
-  alertPanel: 'panel',
-  poll: 'bg'
-}
-
-/* By default opacity slots have 1 as default opacity
- * this allows redefining it to something else
- */
-export const DEFAULT_OPACITY = {
-  alert: 0.5,
-  input: 0.5,
-  faint: 0.5,
-  underlay: 0.15
-}
-
-/**  SUBJECT TO CHANGE IN THE FUTURE, this is all beta
- * Color and opacity slots definitions. Each key represents a slot.
- *
- * Short-hands:
- * String beginning with `--` - value after dashes treated as sole
- *     dependency - i.e. `--value` equivalent to { depends: ['value']}
- * String beginning with `#` - value would be treated as solid color
- *     defined in hexadecimal representation (i.e. #FFFFFF) and will be
- *     used as default. `#FFFFFF` is equivalent to { default: '#FFFFFF'}
- *
- * Full definition:
- * @property {String[]} depends - color slot names this color depends ones.
- *   cyclic dependencies are supported to some extent but not recommended.
- * @property {String} [opacity] - opacity slot used by this color slot.
- *   opacity is inherited from parents. To break inheritance graph use null
- * @property {Number} [priority] - EXPERIMENTAL. used to pre-sort slots so
- *   that slots with higher priority come earlier
- * @property {Function(mod, ...colors)} [color] - function that will be
- *   used to determine the color. By default it just copies first color in
- *   dependency list.
- * @argument {Number} mod - `1` (light-on-dark) or `-1` (dark-on-light)
- *   depending on background color (for textColor)/given color.
- * @argument {...Object} deps - each argument after mod represents each
- *   color from `depends` array. All colors take user customizations into
- *   account and represented by { r, g, b } objects.
- * @returns {Object} resulting color, should be in { r, g, b } form
- *
- * @property {Boolean|String} [textColor] - true to mark color slot as text
- *   color. This enables automatic text color generation for the slot. Use
- *   'preserve' string if you don't want text color to fall back to
- *   black/white. Use 'bw' to only ever use black or white. This also makes
- *   following properties required:
- * @property {String} [layer] - which layer the text sit on top on - used
- *   to account for transparency in text color calculation
- *   layer is inherited from parents. To break inheritance graph use null
- * @property {String} [variant] - which color slot is background (same as
- *   above, used to account for transparency)
- */
-export const SLOT_INHERITANCE = {
-  bg: {
-    depends: [],
-    opacity: 'bg',
-    priority: 1
-  },
-  fg: {
-    depends: [],
-    priority: 1
-  },
-  text: {
-    depends: [],
-    priority: 1
-  },
-  underlay: {
-    default: '#000000',
-    opacity: 'underlay'
-  },
-  link: {
-    depends: ['accent'],
-    priority: 1
-  },
-  accent: {
-    depends: ['link'],
-    priority: 1
-  },
-  faint: {
-    depends: ['text'],
-    opacity: 'faint'
-  },
-  faintLink: {
-    depends: ['link'],
-    opacity: 'faint'
-  },
-
-  cBlue: '#0000ff',
-  cRed: '#FF0000',
-  cGreen: '#00FF00',
-  cOrange: '#E3FF00',
-
-  highlight: {
-    depends: ['bg'],
-    color: (mod, bg) => brightness(5 * mod, bg).rgb
-  },
-  highlightFaintText: {
-    depends: ['faint'],
-    layer: 'highlight',
-    textColor: true
-  },
-  highlightFaintLink: {
-    depends: ['faintLink'],
-    layer: 'highlight',
-    textColor: 'preserve'
-  },
-  highlightText: {
-    depends: ['text'],
-    layer: 'highlight',
-    textColor: true
-  },
-  highlightLink: {
-    depends: ['link'],
-    layer: 'highlight',
-    textColor: 'preserve'
-  },
-  highlightIcon: {
-    depends: ['highlight', 'highlightText'],
-    color: (mod, bg, text) => mixrgb(bg, text)
-  },
-
-  selectedPost: '--highlight',
-  selectedPostFaintText: {
-    depends: ['highlightFaintText'],
-    layer: 'highlight',
-    variant: 'selectedPost',
-    textColor: true
-  },
-  selectedPostFaintLink: {
-    depends: ['highlightFaintLink'],
-    layer: 'highlight',
-    variant: 'selectedPost',
-    textColor: 'preserve'
-  },
-  selectedPostText: {
-    depends: ['highlightText'],
-    layer: 'highlight',
-    variant: 'selectedPost',
-    textColor: true
-  },
-  selectedPostLink: {
-    depends: ['highlightLink'],
-    layer: 'highlight',
-    variant: 'selectedPost',
-    textColor: 'preserve'
-  },
-  selectedPostIcon: {
-    depends: ['selectedPost', 'selectedPostText'],
-    color: (mod, bg, text) => mixrgb(bg, text)
-  },
-
-  selectedMenu: '--highlight',
-  selectedMenuFaintText: {
-    depends: ['highlightFaintText'],
-    layer: 'highlight',
-    variant: 'selectedMenu',
-    textColor: true
-  },
-  selectedMenuFaintLink: {
-    depends: ['highlightFaintLink'],
-    layer: 'highlight',
-    variant: 'selectedMenu',
-    textColor: 'preserve'
-  },
-  selectedMenuText: {
-    depends: ['highlightText'],
-    layer: 'highlight',
-    variant: 'selectedMenu',
-    textColor: true
-  },
-  selectedMenuLink: {
-    depends: ['highlightLink'],
-    layer: 'highlight',
-    variant: 'selectedMenu',
-    textColor: 'preserve'
-  },
-  selectedMenuIcon: {
-    depends: ['selectedMenu', 'selectedMenuText'],
-    color: (mod, bg, text) => mixrgb(bg, text)
-  },
-
-  lightText: {
-    depends: ['text'],
-    color: (mod, text) => brightness(20 * mod, text).rgb
-  },
-
-  border: {
-    depends: ['fg'],
-    opacity: 'border',
-    color: (mod, fg) => brightness(2 * mod, fg).rgb
-  },
-
-  poll: {
-    depends: ['accent', 'bg'],
-    copacity: 'poll',
-    color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg)
-  },
-  pollText: {
-    depends: ['text'],
-    layer: 'poll',
-    textColor: true
-  },
-
-  icon: {
-    depends: ['bg', 'text'],
-    inheritsOpacity: false,
-    color: (mod, bg, text) => mixrgb(bg, text)
-  },
-
-  // Foreground
-  fgText: {
-    depends: ['text'],
-    layer: 'fg',
-    textColor: true
-  },
-  fgLink: {
-    depends: ['link'],
-    layer: 'fg',
-    textColor: 'preserve'
-  },
-
-  // Panel header
-  panel: {
-    depends: ['fg'],
-    opacity: 'panel'
-  },
-  panelText: {
-    depends: ['text'],
-    layer: 'panel',
-    textColor: true
-  },
-  panelFaint: {
-    depends: ['fgText'],
-    layer: 'panel',
-    opacity: 'faint',
-    textColor: true
-  },
-  panelLink: {
-    depends: ['fgLink'],
-    layer: 'panel',
-    textColor: 'preserve'
-  },
-
-  // Top bar
-  topBar: '--fg',
-  topBarText: {
-    depends: ['fgText'],
-    layer: 'topBar',
-    textColor: true
-  },
-  topBarLink: {
-    depends: ['fgLink'],
-    layer: 'topBar',
-    textColor: 'preserve'
-  },
-
-  // Tabs
-  tab: {
-    depends: ['btn']
-  },
-  tabText: {
-    depends: ['btnText'],
-    layer: 'btn',
-    textColor: true
-  },
-  tabActiveText: {
-    depends: ['text'],
-    layer: 'bg',
-    textColor: true
-  },
-
-  // Buttons
-  btn: {
-    depends: ['fg'],
-    opacity: 'btn'
-  },
-  btnText: {
-    depends: ['fgText'],
-    layer: 'btn',
-    textColor: true
-  },
-  btnPanelText: {
-    depends: ['panelText'],
-    layer: 'btnPanel',
-    variant: 'btn',
-    textColor: true
-  },
-  btnTopBarText: {
-    depends: ['topBarText'],
-    layer: 'btnTopBar',
-    variant: 'btn',
-    textColor: true
-  },
-
-  // Buttons: pressed
-  btnPressed: '--btn',
-  btnPressedText: {
-    depends: ['btnText'],
-    layer: 'btn',
-    variant: 'btnPressed',
-    textColor: true
-  },
-  btnPressedPanel: {
-    depends: ['btnPressed']
-  },
-  btnPressedPanelText: {
-    depends: ['btnPanelText'],
-    layer: 'btnPanel',
-    variant: 'btnPressed',
-    textColor: true
-  },
-  btnPressedTopBarText: {
-    depends: ['btnTopBarText'],
-    layer: 'btnTopBar',
-    variant: 'btnPressed',
-    textColor: true
-  },
-
-  // Buttons: toggled
-  btnToggled: {
-    depends: ['btn'],
-    color: (mod, btn) => brightness(mod * 20, btn).rgb
-  },
-  btnToggledText: {
-    depends: ['btnText'],
-    layer: 'btn',
-    variant: 'btnToggled',
-    textColor: true
-  },
-  btnToggledPanelText: {
-    depends: ['btnPanelText'],
-    layer: 'btnPanel',
-    variant: 'btnToggled',
-    textColor: true
-  },
-  btnToggledTopBarText: {
-    depends: ['btnTopBarText'],
-    layer: 'btnTopBar',
-    variant: 'btnToggled',
-    textColor: true
-  },
-
-  // Buttons: disabled
-  btnDisabled: {
-    depends: ['btn', 'bg'],
-    color: (mod, btn, bg) => alphaBlend(btn, 0.5, bg)
-  },
-  btnDisabledText: {
-    depends: ['btnText'],
-    layer: 'btn',
-    variant: 'btnDisabled',
-    textColor: true,
-    color: (mod, text) => brightness(mod * -60, text).rgb
-  },
-  btnDisabledPanelText: {
-    depends: ['btnPanelText'],
-    layer: 'btnPanel',
-    variant: 'btnDisabled',
-    textColor: true,
-    color: (mod, text) => brightness(mod * -60, text).rgb
-  },
-  btnDisabledTopBarText: {
-    depends: ['btnTopBarText'],
-    layer: 'btnTopBar',
-    variant: 'btnDisabled',
-    textColor: true,
-    color: (mod, text) => brightness(mod * -60, text).rgb
-  },
-
-  // Input fields
-  input: {
-    depends: ['fg'],
-    opacity: 'input'
-  },
-  inputText: {
-    depends: ['text'],
-    layer: 'input',
-    textColor: true
-  },
-  inputPanelText: {
-    depends: ['panelText'],
-    layer: 'inputPanel',
-    variant: 'input',
-    textColor: true
-  },
-  inputTopbarText: {
-    depends: ['topBarText'],
-    layer: 'inputTopBar',
-    variant: 'input',
-    textColor: true
-  },
-
-  alertError: {
-    depends: ['cRed'],
-    opacity: 'alert'
-  },
-  alertErrorText: {
-    depends: ['text'],
-    layer: 'alert',
-    variant: 'alertError',
-    textColor: true
-  },
-  alertErrorPanelText: {
-    depends: ['panelText'],
-    layer: 'alertPanel',
-    variant: 'alertError',
-    textColor: true
-  },
-
-  alertWarning: {
-    depends: ['cOrange'],
-    opacity: 'alert'
-  },
-  alertWarningText: {
-    depends: ['text'],
-    layer: 'alert',
-    variant: 'alertWarning',
-    textColor: true
-  },
-  alertWarningPanelText: {
-    depends: ['panelText'],
-    layer: 'alertPanel',
-    variant: 'alertWarning',
-    textColor: true
-  },
-
-  badgeNotification: '--cRed',
-  badgeNotificationText: {
-    depends: ['text', 'badgeNotification'],
-    layer: 'badge',
-    variant: 'badgeNotification',
-    textColor: 'bw'
-  }
-}
-
 export const getLayersArray = (layer, data = LAYERS) => {
   let array = [layer]
   let parent = data[layer]

From 2c61eb8e7f4674253d65cce6048ca272075064e2 Mon Sep 17 00:00:00 2001
From: eugenijm <eugenijm@protonmail.com>
Date: Thu, 23 Jan 2020 23:53:48 +0300
Subject: [PATCH 189/483] Added polyfills for EventTarget (needed for Safari)
 and CustomEvent (needed for IE)

---
 package.json                     |  2 ++
 src/lib/event_target_polyfill.js |  9 +++++++++
 src/main.js                      |  3 +++
 yarn.lock                        | 10 ++++++++++
 4 files changed, 24 insertions(+)
 create mode 100644 src/lib/event_target_polyfill.js

diff --git a/package.json b/package.json
index 38936b23..9ec8c1eb 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
     "@babel/plugin-transform-runtime": "^7.7.6",
     "@babel/preset-env": "^7.7.6",
     "@babel/register": "^7.7.4",
+    "@ungap/event-target": "^0.1.0",
     "@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
     "@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
     "@vue/test-utils": "^1.0.0-beta.26",
@@ -56,6 +57,7 @@
     "connect-history-api-fallback": "^1.1.0",
     "cross-spawn": "^4.0.2",
     "css-loader": "^0.28.0",
+    "custom-event-polyfill": "^1.0.7",
     "eslint": "^5.16.0",
     "eslint-config-standard": "^12.0.0",
     "eslint-friendly-formatter": "^2.0.5",
diff --git a/src/lib/event_target_polyfill.js b/src/lib/event_target_polyfill.js
new file mode 100644
index 00000000..2042c770
--- /dev/null
+++ b/src/lib/event_target_polyfill.js
@@ -0,0 +1,9 @@
+import EventTargetPolyfill from '@ungap/event-target'
+
+try {
+  /* eslint-disable no-new  */
+  new EventTarget()
+  /* eslint-enable no-new  */
+} catch (e) {
+  window.EventTarget = EventTargetPolyfill
+}
diff --git a/src/main.js b/src/main.js
index a9db1cff..baf73ac8 100644
--- a/src/main.js
+++ b/src/main.js
@@ -2,6 +2,9 @@ import Vue from 'vue'
 import VueRouter from 'vue-router'
 import Vuex from 'vuex'
 
+import 'custom-event-polyfill'
+import './lib/event_target_polyfill.js'
+
 import interfaceModule from './modules/interface.js'
 import instanceModule from './modules/instance.js'
 import statusesModule from './modules/statuses.js'
diff --git a/yarn.lock b/yarn.lock
index 4b20a6a1..1a5d4cef 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -710,6 +710,11 @@
   dependencies:
     qrcode "^1.3.0"
 
+"@ungap/event-target@^0.1.0":
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/@ungap/event-target/-/event-target-0.1.0.tgz#88d527d40de86c4b0c99a060ca241d755999915b"
+  integrity sha512-W2oyj0Fe1w/XhPZjkI3oUcDUAmu5P4qsdT2/2S8aMhtAWM/CE/jYWtji0pKNPDfxLI75fa5gWSEmnynKMNP/oA==
+
 "@vue/babel-helper-vue-jsx-merge-props@^1.0.0":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040"
@@ -2281,6 +2286,11 @@ currently-unhandled@^0.4.1:
   dependencies:
     array-find-index "^1.0.1"
 
+custom-event-polyfill@^1.0.7:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee"
+  integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
+
 custom-event@~1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"

From 53576df72aff5405ab30764e5cfcf2162bde37bc Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 24 Jan 2020 00:02:16 +0200
Subject: [PATCH 190/483] popover/selected menu improvements

---
 src/components/emoji_input/emoji_input.vue    | 11 +++--
 src/components/popper/popper.scss             |  7 ++-
 src/components/side_drawer/side_drawer.vue    |  7 ++-
 .../style_switcher/style_switcher.vue         | 29 ++++++++++++
 src/services/theme_data/pleromafe.js          | 44 ++++++++++++++++---
 5 files changed, 87 insertions(+), 11 deletions(-)

diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 46ed6f25..9c2501a9 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -109,10 +109,13 @@
         box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5);
         box-shadow: var(--popupShadow);
         min-width: 75%;
-        background: $fallback--bg;
-        background: var(--bg, $fallback--bg);
-        color: $fallback--lightText;
-        color: var(--lightText, $fallback--lightText);
+        background-color: $fallback--bg;
+        background-color: var(--popover, $fallback--bg);
+        color: $fallback--link;
+        color: var(--popoverText, $fallback--link);
+        --faint: var(--popoverFaintText, $fallback--faint);
+        --faintLink: var(--popoverFaintLink, $fallback--faint);
+        --icon: var(--popoverIcon, $fallback--icon);
       }
     }
 
diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss
index 9c2ce25f..e70b277d 100644
--- a/src/components/popper/popper.scss
+++ b/src/components/popper/popper.scss
@@ -9,7 +9,12 @@
     border-radius: $fallback--btnRadius;
     border-radius: var(--btnRadius, $fallback--btnRadius);
     background-color: $fallback--bg;
-    background-color: var(--bg, $fallback--bg);
+    background-color: var(--popover, $fallback--bg);
+    color: $fallback--text;
+    color: var(--popoverText, $fallback--text);
+    --faint: var(--popoverFaintText, $fallback--faint);
+    --faintLink: var(--popoverFaintLink, $fallback--faint);
+    --icon: var(--popoverIcon, $fallback--icon);
   }
 
   .popover-arrow {
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 3188fd59..a5b754d7 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -223,7 +223,12 @@
   box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
   box-shadow: var(--panelShadow);
   background-color: $fallback--bg;
-  background-color: var(--bg, $fallback--bg);
+  background-color: var(--popover, $fallback--bg);
+  color: $fallback--link;
+  color: var(--popoverText, $fallback--link);
+  --faint: var(--popoverFaintText, $fallback--faint);
+  --faintLink: var(--popoverFaintLink, $fallback--faint);
+  --icon: var(--popoverIcon, $fallback--icon);
 
   .button-icon:before {
     width: 1.1em;
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index ff7f4710..1c39a806 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -554,6 +554,35 @@
             />
             <ContrastRatio :contrast="previewContrast.highlightLink" />
           </div>
+          <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.popover') }}</h4>
+            <ColorInput
+              v-model="popoverColorLocal"
+              name="popover"
+              :label="$t('settings.background')"
+              :fallback="previewTheme.colors.popover"
+            />
+            <OpacityInput
+              v-model="popoverOpacityLocal"
+              name="popoverOpacity"
+              :fallback="previewTheme.opacity.popover"
+              :disabled="popoverOpacityLocal === 'transparent'"
+            />
+            <ColorInput
+              v-model="popoverTextColorLocal"
+              name="popoverText"
+              :label="$t('settings.text')"
+              :fallback="previewTheme.colors.popoverText"
+            />
+            <ContrastRatio :contrast="previewContrast.popoverText" />
+            <ColorInput
+              v-model="popoverLinkColorLocal"
+              name="popoverLink"
+              :label="$t('settings.links')"
+              :fallback="previewTheme.colors.popoverLink"
+            />
+            <ContrastRatio :contrast="previewContrast.popoverLink" />
+          </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.selectedPost') }}</h4>
             <ColorInput
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index 0deb829f..484874d7 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -12,6 +12,8 @@ export const LAYERS = {
   bg: 'underlay',
   highlight: 'bg',
   panel: 'bg',
+  popover: 'bg',
+  selectedMenu: 'popover',
   btn: 'bg',
   btnPanel: 'panel',
   btnTopBar: 'topBar',
@@ -140,6 +142,35 @@ export const SLOT_INHERITANCE = {
     color: (mod, bg, text) => mixrgb(bg, text)
   },
 
+  popover: {
+    depends: ['bg'],
+    opacity: 'popover'
+  },
+  popoverFaintText: {
+    depends: ['faint'],
+    layer: 'popover',
+    textColor: true
+  },
+  popoverFaintLink: {
+    depends: ['faintLink'],
+    layer: 'popover',
+    textColor: 'preserve'
+  },
+  popoverText: {
+    depends: ['text'],
+    layer: 'popover',
+    textColor: true
+  },
+  popoverLink: {
+    depends: ['link'],
+    layer: 'popover',
+    textColor: 'preserve'
+  },
+  popoverIcon: {
+    depends: ['popover', 'popoverText'],
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
   selectedPost: '--highlight',
   selectedPostFaintText: {
     depends: ['highlightFaintText'],
@@ -170,28 +201,31 @@ export const SLOT_INHERITANCE = {
     color: (mod, bg, text) => mixrgb(bg, text)
   },
 
-  selectedMenu: '--highlight',
+  selectedMenu: {
+    depends: ['popover'],
+    color: (mod, bg) => brightness(5 * mod, bg).rgb
+  },
   selectedMenuFaintText: {
     depends: ['highlightFaintText'],
-    layer: 'highlight',
+    layer: 'selectedMenu',
     variant: 'selectedMenu',
     textColor: true
   },
   selectedMenuFaintLink: {
     depends: ['highlightFaintLink'],
-    layer: 'highlight',
+    layer: 'selectedMenu',
     variant: 'selectedMenu',
     textColor: 'preserve'
   },
   selectedMenuText: {
     depends: ['highlightText'],
-    layer: 'highlight',
+    layer: 'selectedMenu',
     variant: 'selectedMenu',
     textColor: true
   },
   selectedMenuLink: {
     depends: ['highlightLink'],
-    layer: 'highlight',
+    layer: 'selectedMenu',
     variant: 'selectedMenu',
     textColor: 'preserve'
   },

From b96993e4dd6b2d9197b430af404e8fa652888b51 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 24 Jan 2020 00:36:32 +0200
Subject: [PATCH 191/483] improved selectedMenu again

---
 src/components/emoji_input/emoji_input.vue    | 12 ++--
 src/components/emoji_picker/emoji_picker.scss |  9 +++
 src/components/nav_panel/nav_panel.vue        |  2 +
 src/components/popper/popper.scss             | 12 ++--
 .../selectable_list/selectable_list.vue       |  1 +
 src/components/side_drawer/side_drawer.vue    | 12 ++--
 src/components/status/status.vue              |  1 +
 src/services/color_convert/color_convert.js   |  2 +-
 src/services/theme_data/pleromafe.js          | 63 ++++++++++++++++++-
 src/services/theme_data/theme_data.service.js |  7 ++-
 static/themes/breezy-dark.json                |  4 +-
 static/themes/breezy-light.json               |  4 +-
 12 files changed, 105 insertions(+), 24 deletions(-)

diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 9c2501a9..94eae560 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -115,6 +115,7 @@
         color: var(--popoverText, $fallback--link);
         --faint: var(--popoverFaintText, $fallback--faint);
         --faintLink: var(--popoverFaintLink, $fallback--faint);
+        --lightText: var(--popoverLightText, $fallback--lightText);
         --icon: var(--popoverIcon, $fallback--icon);
       }
     }
@@ -160,11 +161,12 @@
 
       &.highlighted {
         background-color: $fallback--fg;
-        background-color: var(--selectedMenu, $fallback--fg);
-        color: var(--selectedMenuText, $fallback--text);
-        --faint: var(--selectedMenuFaintText, $fallback--faint);
-        --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
-        --icon: var(--selectedMenuIcon, $fallback--icon);
+        background-color: var(--selectedMenuPopover, $fallback--fg);
+        color: var(--selectedMenuPopoverText, $fallback--text);
+        --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
+        --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
+        --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
+        --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
       }
     }
   }
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 6608f393..8bd07e45 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -8,6 +8,15 @@
   left: 0;
   margin: 0 !important;
   z-index: 1;
+  background-color: $fallback--bg;
+  background-color: var(--popover, $fallback--bg);
+  color: $fallback--link;
+  color: var(--popoverText, $fallback--link);
+  --lightText: var(--popoverLightText, $fallback--faint);
+  --faint: var(--popoverFaintText, $fallback--faint);
+  --faintLink: var(--popoverFaintLink, $fallback--faint);
+  --lightText: var(--popoverLightText, $fallback--lightText);
+  --icon: var(--popoverIcon, $fallback--icon);
 
   .keep-open,
   .too-many-emoji {
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index afc611ea..a934a411 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -105,6 +105,7 @@
     color: var(--selectedMenuText, $fallback--link);
     --faint: var(--selectedMenuFaintText, $fallback--faint);
     --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+    --lightText: var(--selectedMenuLightText, $fallback--lightText);
     --icon: var(--selectedMenuIcon, $fallback--icon);
   }
 
@@ -116,6 +117,7 @@
     color: var(--selectedMenuText, $fallback--text);
     --faint: var(--selectedMenuFaintText, $fallback--faint);
     --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+    --lightText: var(--selectedMenuLightText, $fallback--lightText);
     --icon: var(--selectedMenuIcon, $fallback--icon);
 
     &:hover {
diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss
index e70b277d..591be37e 100644
--- a/src/components/popper/popper.scss
+++ b/src/components/popper/popper.scss
@@ -14,6 +14,7 @@
     color: var(--popoverText, $fallback--text);
     --faint: var(--popoverFaintText, $fallback--faint);
     --faintLink: var(--popoverFaintLink, $fallback--faint);
+    --lightText: var(--popoverLightText, $fallback--lightText);
     --icon: var(--popoverIcon, $fallback--icon);
   }
 
@@ -144,12 +145,13 @@
 
     &:active, &:hover {
       background-color: $fallback--lightBg;
-      background-color: var(--selectedMenu, $fallback--lightBg);
+      background-color: var(--selectedMenuPopover, $fallback--lightBg);
       color: $fallback--link;
-      color: var(--selectedMenuText, $fallback--link);
-      --faint: var(--selectedMenuFaintText, $fallback--faint);
-      --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
-      --icon: var(--selectedMenuIcon, $fallback--icon);
+      color: var(--selectedMenuPopoverText, $fallback--link);
+      --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
+      --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
+      --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
+      --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
     }
   }
 }
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
index 2d1e44a3..a9bb12a1 100644
--- a/src/components/selectable_list/selectable_list.vue
+++ b/src/components/selectable_list/selectable_list.vue
@@ -72,6 +72,7 @@
     color: var(--selectedMenuText, $fallback--text);
     --faint: var(--selectedMenuFaintText, $fallback--faint);
     --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+    --lightText: var(--selectedMenuLightText, $fallback--lightText);
     --icon: var(--selectedMenuIcon, $fallback--icon);
   }
 
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index a5b754d7..25e4de01 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -228,6 +228,7 @@
   color: var(--popoverText, $fallback--link);
   --faint: var(--popoverFaintText, $fallback--faint);
   --faintLink: var(--popoverFaintLink, $fallback--faint);
+  --lightText: var(--popoverLightText, $fallback--lightText);
   --icon: var(--popoverIcon, $fallback--icon);
 
   .button-icon:before {
@@ -294,12 +295,13 @@
 
     &:hover {
       background-color: $fallback--lightBg;
-      background-color: var(--selectedMenu, $fallback--lightBg);
+      background-color: var(--selectedMenuPopover, $fallback--lightBg);
       color: $fallback--text;
-      color: var(--selectedMenuText, $fallback--text);
-      --faint: var(--selectedMenuFaintText, $fallback--faint);
-      --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
-      --icon: var(--selectedMenuIcon, $fallback--icon);
+      color: var(--selectedMenuPopoverText, $fallback--text);
+      --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
+      --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
+      --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
+      --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
     }
   }
 }
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 38d091ed..1997e187 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -448,6 +448,7 @@ $status-margin: 0.75em;
     background-color: var(--selectedPost, $fallback--lightBg);
     color: $fallback--text;
     color: var(--selectedPostText, $fallback--text);
+    --lightText: var(--selectedPostLightText, $fallback--light);
     --faint: var(--selectedPostFaintText, $fallback--faint);
     --faintLink: var(--selectedPostFaintLink, $fallback--faint);
     --icon: var(--selectedPostIcon, $fallback--icon);
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 93cb1ba6..0bf8f646 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -69,7 +69,7 @@ const srgbToLinear = (srgb) => {
  * @param {Object} srgb - sRGB color
  * @returns {Number} relative luminance
  */
-const relativeLuminance = (srgb) => {
+export const relativeLuminance = (srgb) => {
   const { r, g, b } = srgbToLinear(srgb)
   return 0.2126 * r + 0.7152 * g + 0.0722 * b
 }
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index 484874d7..a40d08a6 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -117,6 +117,11 @@ export const SLOT_INHERITANCE = {
     depends: ['bg'],
     color: (mod, bg) => brightness(5 * mod, bg).rgb
   },
+  highlightLightText: {
+    depends: ['lightText'],
+    layer: 'highlight',
+    textColor: true
+  },
   highlightFaintText: {
     depends: ['faint'],
     layer: 'highlight',
@@ -146,6 +151,11 @@ export const SLOT_INHERITANCE = {
     depends: ['bg'],
     opacity: 'popover'
   },
+  popoverLightText: {
+    depends: ['lightText'],
+    layer: 'popover',
+    textColor: true
+  },
   popoverFaintText: {
     depends: ['faint'],
     layer: 'popover',
@@ -178,6 +188,12 @@ export const SLOT_INHERITANCE = {
     variant: 'selectedPost',
     textColor: true
   },
+  selectedPostLightText: {
+    depends: ['highlightLightText'],
+    layer: 'highlight',
+    variant: 'selectedPost',
+    textColor: true
+  },
   selectedPostFaintLink: {
     depends: ['highlightFaintLink'],
     layer: 'highlight',
@@ -202,9 +218,15 @@ export const SLOT_INHERITANCE = {
   },
 
   selectedMenu: {
-    depends: ['popover'],
+    depends: ['bg'],
     color: (mod, bg) => brightness(5 * mod, bg).rgb
   },
+  selectedMenuLightText: {
+    depends: ['highlightLightText'],
+    layer: 'selectedMenu',
+    variant: 'selectedMenu',
+    textColor: true
+  },
   selectedMenuFaintText: {
     depends: ['highlightFaintText'],
     layer: 'selectedMenu',
@@ -234,6 +256,45 @@ export const SLOT_INHERITANCE = {
     color: (mod, bg, text) => mixrgb(bg, text)
   },
 
+  selectedMenuPopover: {
+    depends: ['popover'],
+    color: (mod, bg) => brightness(5 * mod, bg).rgb
+  },
+  selectedMenuPopoverLightText: {
+    depends: ['selectedMenuLightText'],
+    layer: 'selectedMenuPopover',
+    variant: 'selectedMenuPopover',
+    textColor: true
+  },
+  selectedMenuPopoverFaintText: {
+    depends: ['selectedMenuFaintText'],
+    layer: 'selectedMenuPopover',
+    variant: 'selectedMenuPopover',
+    textColor: true
+  },
+  selectedMenuPopoverFaintLink: {
+    depends: ['selectedMenuFaintLink'],
+    layer: 'selectedMenuPopover',
+    variant: 'selectedMenuPopover',
+    textColor: 'preserve'
+  },
+  selectedMenuPopoverText: {
+    depends: ['selectedMenuText'],
+    layer: 'selectedMenuPopover',
+    variant: 'selectedMenuPopover',
+    textColor: true
+  },
+  selectedMenuPopoverLink: {
+    depends: ['selectedMenuLink'],
+    layer: 'selectedMenuPopover',
+    variant: 'selectedMenuPopover',
+    textColor: 'preserve'
+  },
+  selectedMenuPopoverIcon: {
+    depends: ['selectedMenuPopover', 'selectedMenuText'],
+    color: (mod, bg, text) => mixrgb(bg, text)
+  },
+
   lightText: {
     depends: ['text'],
     color: (mod, text) => brightness(20 * mod, text).rgb
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index ea28481b..c0861a4a 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -1,5 +1,5 @@
 import { convert, brightness, contrastRatio } from 'chromatism'
-import { alphaBlendLayers, getTextColor } from '../color_convert/color_convert.js'
+import { alphaBlendLayers, getTextColor, relativeLuminance } from '../color_convert/color_convert.js'
 import { LAYERS, DEFAULT_OPACITY, SLOT_INHERITANCE } from './pleromafe.js'
 
 /*
@@ -318,13 +318,14 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
           opacity
         )
       )
+      const isLightOnDark = relativeLuminance(bg) > 127
+      const mod = isLightOnDark ? 1 : -1
+
       if (value.textColor === 'bw') {
         outputColor = contrastRatio(bg).rgb
       } else {
         let color = { ...colors[deps[0]] }
         if (value.color) {
-          const isLightOnDark = convert(bg).hsl.l < convert(color).hsl.l
-          const mod = isLightOnDark ? 1 : -1
           color = value.color(mod, ...deps.map((dep) => ({ ...colors[dep] })))
         }
 
diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index c3bdcf65..236b94ad 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -114,8 +114,8 @@
       "cGreen": "#27ae60",
       "cOrange": "#f67400",
       "btnPressed": "--accent",
-      "highlight": "--accent",
-      "selectedPost": "--bg,10"
+      "selectedMenu": "--accent",
+      "selectedMenuPopover": "--accent"
     },
     "radii": {
       "btn": "2",
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index 478407c9..d3f74cec 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -117,8 +117,8 @@
       "cGreen": "#27ae60",
       "cOrange": "#f67400",
       "btnPressed": "--accent",
-      "highlight": "--accent",
-      "selectedPost": "--bg,10"
+      "selectedMenu": "--accent",
+      "selectedMenuPopover": "--accent"
     },
     "radii": {
       "btn": "2",

From 75fa07626d4543c0fcca80007c717367f4f85d90 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 24 Jan 2020 00:39:21 +0200
Subject: [PATCH 192/483] fix icons in menus

---
 src/components/popper/popper.scss | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss
index 591be37e..341416c1 100644
--- a/src/components/popper/popper.scss
+++ b/src/components/popper/popper.scss
@@ -152,6 +152,9 @@
       --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
       --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
       --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
+      i {
+        color: var(--selectedMenuPopoverIcon, $fallback--icon);
+      }
     }
   }
 }

From d7e7f47b66de160b351e4e836de08ac188fc3079 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 24 Jan 2020 00:56:47 +0200
Subject: [PATCH 193/483] fix transparent color not affecting downstream slots

---
 src/services/theme_data/theme_data.service.js | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index c0861a4a..0a6733a9 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -326,7 +326,7 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
       } else {
         let color = { ...colors[deps[0]] }
         if (value.color) {
-          color = value.color(mod, ...deps.map((dep) => ({ ...colors[dep] })))
+          color = colorFunc(mod, ...deps.map((dep) => ({ ...colors[dep] })))
         }
 
         outputColor = getTextColor(
@@ -348,7 +348,13 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
   }
   const opacitySlot = getOpacitySlot(key)
   if (opacitySlot && outputColor.a === undefined) {
-    outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1
+    const deps = getDependencies(key, SLOT_INHERITANCE)
+    const dependencySlot = deps && deps[0]
+    if (dependencySlot && sourceColors[dependencySlot] === 'transparent') {
+      outputColor.a = 0
+    } else {
+      outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1
+    }
   }
   if (opacitySlot) {
     return {

From a018ea622c4ae34fd204e840b20aba53f84cd051 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Sun, 26 Jan 2020 15:45:12 +0200
Subject: [PATCH 194/483] change emoji reactions to use new format

---
 src/components/conversation/conversation.js   |  2 +-
 .../emoji_reactions/emoji_reactions.js        |  9 ++-
 .../emoji_reactions/emoji_reactions.vue       | 12 ++--
 src/components/status/status.vue              |  1 -
 src/modules/statuses.js                       | 66 +++++++++++--------
 .../entity_normalizer.service.js              |  1 +
 test/unit/specs/modules/statuses.spec.js      | 45 +++++++++++++
 7 files changed, 99 insertions(+), 37 deletions(-)

diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 7ff0ac08..45fb2bf6 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -150,7 +150,7 @@ const conversation = {
       if (!id) return
       this.highlight = id
       this.$store.dispatch('fetchFavsAndRepeats', id)
-      this.$store.dispatch('fetchEmojiReactions', id)
+      this.$store.dispatch('fetchEmojiReactionsBy', id)
     },
     getHighlight () {
       return this.isExpanded ? this.highlight : null
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index e81e6e25..b37cce3d 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -4,12 +4,17 @@ const EmojiReactions = {
   props: ['status'],
   computed: {
     emojiReactions () {
-      return this.status.emojiReactions
+      console.log(this.status.emoji_reactions)
+      return this.status.emoji_reactions
     }
   },
   methods: {
     reactedWith (emoji) {
-      return this.status.reactedWithEmoji.includes(emoji)
+      // return []
+      const user = this.$store.state.users.currentUser
+      const reaction = this.status.emoji_reactions.find(r => r.emoji === emoji)
+      console.log(reaction)
+      return reaction.accounts && reaction.accounts.find(u => u.id === user.id)
     },
     reactWith (emoji) {
       this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index d83f60b6..8a229240 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -1,14 +1,14 @@
 <template>
   <div class="emoji-reactions">
     <button
-      v-for="(users, emoji) in emojiReactions"
-      :key="emoji"
+      v-for="(reaction) in emojiReactions"
+      :key="reaction.emoji"
       class="emoji-reaction btn btn-default"
-      :class="{ 'picked-reaction': reactedWith(emoji) }"
-      @click="emojiOnClick(emoji, $event)"
+      :class="{ 'picked-reaction': reactedWith(reaction.emoji) }"
+      @click="emojiOnClick(reaction.emoji, $event)"
     >
-      <span v-if="users">{{ users.length }}</span>
-      <span>{{ emoji }}</span>
+      <span>{{ reaction.count }}</span>
+      <span>{{ reaction.emoji }}</span>
     </button>
   </div>
 </template>
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 87e8b5da..d5739304 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -355,7 +355,6 @@
           </transition>
 
           <EmojiReactions
-            v-if="isFocused"
             :status="status"
           />
 
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index dbae9d38..ea0c1749 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -10,10 +10,7 @@ import {
   first,
   last,
   isArray,
-  omitBy,
-  flow,
-  filter,
-  keys
+  omitBy
 } from 'lodash'
 import { set } from 'vue'
 import apiService from '../services/api/api.service.js'
@@ -534,33 +531,48 @@ export const mutations = {
     newStatus.fave_num = newStatus.favoritedBy.length
     newStatus.favorited = !!newStatus.favoritedBy.find(({ id }) => currentUser.id === id)
   },
-  addEmojiReactions (state, { id, emojiReactions, currentUser }) {
+  addEmojiReactionsBy (state, { id, emojiReactions, currentUser }) {
     const status = state.allStatusesObject[id]
-    set(status, 'emojiReactions', emojiReactions)
-    const reactedWithEmoji = flow(
-      keys,
-      filter(reaction => find(reaction, { id: currentUser.id }))
-    )(emojiReactions)
-    set(status, 'reactedWithEmoji', reactedWithEmoji)
+    set(status, 'emoji_reactions', emojiReactions)
   },
   addOwnReaction (state, { id, emoji, currentUser }) {
     const status = state.allStatusesObject[id]
-    status.emojiReactions = status.emojiReactions || {}
-    const listOfUsers = (status.emojiReactions && status.emojiReactions[emoji]) || []
-    const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id })
-    if (!hasSelfAlready) {
-      set(status.emojiReactions, emoji, listOfUsers.concat([{ id: currentUser.id }]))
-      set(status, 'reactedWithEmoji', [...status.reactedWithEmoji, emoji])
+    const reactionIndex = findIndex(status.emoji_reactions, { emoji })
+    const reaction = status.emoji_reactions[reactionIndex] || { emoji, count: 0, accounts: [] }
+
+    const newReaction = {
+      ...reaction,
+      count: reaction.count + 1,
+      accounts: [
+        ...reaction.accounts,
+        currentUser
+      ]
+    }
+
+    // Update count of existing reaction if it exists, otherwise append at the end
+    if (reactionIndex >= 0) {
+      set(status.emoji_reactions, reactionIndex, newReaction)
+    } else {
+      set(status, 'emoji_reactions', [...status.emoji_reactions, newReaction])
     }
   },
   removeOwnReaction (state, { id, emoji, currentUser }) {
     const status = state.allStatusesObject[id]
-    const listOfUsers = status.emojiReactions[emoji] || []
-    const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id })
-    if (hasSelfAlready) {
-      const newUsers = filter(listOfUsers, user => user.id !== currentUser.id)
-      set(status.emojiReactions, emoji, newUsers)
-      set(status, 'reactedWithEmoji', status.reactedWithEmoji.filter(e => e !== emoji))
+    const reactionIndex = findIndex(status.emoji_reactions, { emoji })
+    if (reactionIndex < 0) return
+
+    const reaction = status.emoji_reactions[reactionIndex]
+
+    const newReaction = {
+      ...reaction,
+      count: reaction.count - 1,
+      accounts: reaction.accounts.filter(acc => acc.id === currentUser.id)
+    }
+
+    if (newReaction.count > 0) {
+      set(status.emoji_reactions, reactionIndex, newReaction)
+    } else {
+      set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.emoji !== emoji))
     }
   },
   updateStatusWithPoll (state, { id, poll }) {
@@ -672,7 +684,7 @@ const statuses = {
       commit('addOwnReaction', { id, emoji, currentUser })
       rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(
         status => {
-          dispatch('fetchEmojiReactions', id)
+          dispatch('fetchEmojiReactionsBy', id)
         }
       )
     },
@@ -681,14 +693,14 @@ const statuses = {
       commit('removeOwnReaction', { id, emoji, currentUser })
       rootState.api.backendInteractor.unreactWithEmoji({ id, emoji }).then(
         status => {
-          dispatch('fetchEmojiReactions', id)
+          dispatch('fetchEmojiReactionsBy', id)
         }
       )
     },
-    fetchEmojiReactions ({ rootState, commit }, id) {
+    fetchEmojiReactionsBy ({ rootState, commit }, id) {
       rootState.api.backendInteractor.fetchEmojiReactions({ id }).then(
         emojiReactions => {
-          commit('addEmojiReactions', { id, emojiReactions, currentUser: rootState.users.currentUser })
+          commit('addEmojiReactionsBy', { id, emojiReactions, currentUser: rootState.users.currentUser })
         }
       )
     },
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index ee007bee..03eaa5d7 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -233,6 +233,7 @@ export const parseStatus = (data) => {
     output.statusnet_html = addEmojis(data.content, data.emojis)
 
     output.tags = data.tags
+    output.emoji_reactions = [{ emoji: 'A', count: 5 }] // data.pleroma.emoji_reactions
 
     if (data.pleroma) {
       const { pleroma } = data
diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js
index f794997b..e53aa388 100644
--- a/test/unit/specs/modules/statuses.spec.js
+++ b/test/unit/specs/modules/statuses.spec.js
@@ -241,6 +241,51 @@ describe('Statuses module', () => {
     })
   })
 
+  describe('emojiReactions', () => {
+    it('increments count in existing reaction', () => {
+      const state = defaultState()
+      const status = makeMockStatus({ id: '1' })
+      status.emoji_reactions = [ { emoji: '😂', count: 1, accounts: [] } ]
+
+      mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
+      mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
+      expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(2)
+      expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me')
+    })
+
+    it('adds a new reaction', () => {
+      const state = defaultState()
+      const status = makeMockStatus({ id: '1' })
+      status.emoji_reactions = []
+
+      mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
+      mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
+      expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1)
+      expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me')
+    })
+
+    it('decreases count in existing reaction', () => {
+      const state = defaultState()
+      const status = makeMockStatus({ id: '1' })
+      status.emoji_reactions = [ { emoji: '😂', count: 2, accounts: [{ id: 'me' }] } ]
+
+      mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
+      mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} })
+      expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1)
+      expect(state.allStatusesObject['1'].emoji_reactions[0].accounts).to.eql([])
+    })
+
+    it('removes a reaction', () => {
+      const state = defaultState()
+      const status = makeMockStatus({ id: '1' })
+      status.emoji_reactions = [{ emoji: '😂', count: 1, accounts: [{ id: 'me' }] }]
+
+      mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
+      mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} })
+      expect(state.allStatusesObject['1'].emoji_reactions.length).to.eql(0)
+    })
+  })
+
   describe('showNewStatuses', () => {
     it('resets the minId to the min of the visible statuses when adding new to visible statuses', () => {
       const state = defaultState()

From 7cfe1b05e8d16fcbb6eab3b42f19e464d57ea35b Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Sun, 26 Jan 2020 15:57:40 +0200
Subject: [PATCH 195/483] remove mock data

---
 src/services/entity_normalizer/entity_normalizer.service.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 03eaa5d7..f66d09ac 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -233,7 +233,7 @@ export const parseStatus = (data) => {
     output.statusnet_html = addEmojis(data.content, data.emojis)
 
     output.tags = data.tags
-    output.emoji_reactions = [{ emoji: 'A', count: 5 }] // data.pleroma.emoji_reactions
+    output.emoji_reactions = data.pleroma.emoji_reactions
 
     if (data.pleroma) {
       const { pleroma } = data

From 0de627baae53d4d284920c1f6d7daf64769be4a6 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Sun, 26 Jan 2020 16:18:57 +0200
Subject: [PATCH 196/483] remove favs count from react button

---
 src/components/react_button/react_button.vue | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index ae975dee..7f1bc492 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -44,7 +44,6 @@
         class="button-icon add-reaction-button"
         :title="$t('tool_tip.add_reaction')"
       />
-      <span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span>
     </div>
   </v-popover>
 </template>

From 7c074b87418602effac03c4bae34afffcfca283f Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 27 Jan 2020 04:20:13 +0200
Subject: [PATCH 197/483] fix rgba css generation, add some tests to
 automatically verify that themes are generated properly

---
 build/webpack.base.conf.js                    |   1 +
 src/services/color_convert/color_convert.js   |   2 +-
 src/services/theme_data/theme_data.service.js |   2 +-
 static/themes/redmond-xx-se.json              |   2 +-
 static/themes/redmond-xx.json                 |   2 +-
 static/themes/redmond-xxi.json                |   2 +-
 .../services/theme_data/sanity_checks.spec.js |  28 +++
 .../services/theme_data/theme_data.spec.js    | 161 +++++++++---------
 8 files changed, 116 insertions(+), 84 deletions(-)
 create mode 100644 test/unit/specs/services/theme_data/sanity_checks.spec.js

diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
index 5cc0135e..dfef37a6 100644
--- a/build/webpack.base.conf.js
+++ b/build/webpack.base.conf.js
@@ -35,6 +35,7 @@ module.exports = {
     ],
     alias: {
       'vue$': 'vue/dist/vue.runtime.common',
+      'static': path.resolve(__dirname, '../static'),
       'src': path.resolve(__dirname, '../src'),
       'assets': path.resolve(__dirname, '../src/assets'),
       'components': path.resolve(__dirname, '../src/components')
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 0bf8f646..b5461038 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -171,7 +171,7 @@ export const mixrgb = (a, b) => {
  * @returns {String} CSS rgba() color
  */
 export const rgba2css = function (rgba) {
-  return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
+  return `rgba(${Math.floor(rgba.r)}, ${Math.floor(rgba.g)}, ${Math.floor(rgba.b)}, .5)`
 }
 
 /**
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 0a6733a9..49b99148 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -353,7 +353,7 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
     if (dependencySlot && sourceColors[dependencySlot] === 'transparent') {
       outputColor.a = 0
     } else {
-      outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1
+      outputColor.a = Number(sourceOpacity[opacitySlot]) || OPACITIES[opacitySlot].defaultValue || 1
     }
   }
   if (opacitySlot) {
diff --git a/static/themes/redmond-xx-se.json b/static/themes/redmond-xx-se.json
index 1a867fcc..8deab7b7 100644
--- a/static/themes/redmond-xx-se.json
+++ b/static/themes/redmond-xx-se.json
@@ -1,7 +1,7 @@
 {
   "_pleroma_theme_version": 2,
   "name": "Redmond XX SE",
-  "theme": {
+  "source": {
     "shadows": {
       "panel": [
         {
diff --git a/static/themes/redmond-xx.json b/static/themes/redmond-xx.json
index 78c92f10..cdb89fe3 100644
--- a/static/themes/redmond-xx.json
+++ b/static/themes/redmond-xx.json
@@ -1,7 +1,7 @@
 {
   "_pleroma_theme_version": 2,
   "name": "Redmond XX",
-  "theme": {
+  "source": {
     "shadows": {
       "panel": [
         {
diff --git a/static/themes/redmond-xxi.json b/static/themes/redmond-xxi.json
index 28f68351..79a2cb26 100644
--- a/static/themes/redmond-xxi.json
+++ b/static/themes/redmond-xxi.json
@@ -1,7 +1,7 @@
 {
   "_pleroma_theme_version": 2,
   "name": "Redmond XXI",
-  "theme": {
+  "source": {
     "shadows": {
       "panel": [
         {
diff --git a/test/unit/specs/services/theme_data/sanity_checks.spec.js b/test/unit/specs/services/theme_data/sanity_checks.spec.js
new file mode 100644
index 00000000..f0072e7d
--- /dev/null
+++ b/test/unit/specs/services/theme_data/sanity_checks.spec.js
@@ -0,0 +1,28 @@
+import { getColors } from 'src/services/theme_data/theme_data.service.js'
+
+const checkColors = (output) => {
+  expect(output).to.have.property('colors')
+  Object.entries(output.colors).forEach(([key, v]) => {
+    expect(v, key).to.be.an('object')
+    expect(v, key).to.include.all.keys('r', 'g', 'b')
+    'rgba'.split('').forEach(k => {
+      if ((k === 'a' && v.hasOwnProperty('a')) || k !== 'a') {
+        expect(v[k], key + '.' + k).to.be.a('number')
+        expect(v[k], key + '.' + k).to.be.least(0)
+        expect(v[k], key + '.' + k).to.be.most(k === 'a' ? 1 : 255)
+      }
+    })
+  })
+}
+
+describe('Theme Data utility functions', () => {
+  const context = require.context('static/themes/', false, /\.json$/)
+  context.keys().forEach((key) => {
+    it(`Should render all colors for ${key} properly`, () => {
+      const { theme, source } = context(key)
+      const data = source || theme
+      const colors = getColors(data.colors, data.opacity, 1)
+      checkColors(colors)
+    })
+  })
+})
diff --git a/test/unit/specs/services/theme_data/theme_data.spec.js b/test/unit/specs/services/theme_data/theme_data.spec.js
index d8a04ba7..67f4fd5a 100644
--- a/test/unit/specs/services/theme_data/theme_data.spec.js
+++ b/test/unit/specs/services/theme_data/theme_data.spec.js
@@ -1,87 +1,90 @@
 import { getLayersArray, topoSort } from 'src/services/theme_data/theme_data.service.js'
 
-describe('getLayersArray', () => {
-  const fixture = {
-    layer1: null,
-    layer2: 'layer1',
-    layer3a: 'layer2',
-    layer3b: 'layer2'
-  }
+describe('Theme Data utility functions', () => {
+  describe('getLayersArray', () => {
+    const fixture = {
+      layer1: null,
+      layer2: 'layer1',
+      layer3a: 'layer2',
+      layer3b: 'layer2'
+    }
 
-  it('should expand layers properly (3b)', () => {
-    const out = getLayersArray('layer3b', fixture)
-    expect(out).to.eql(['layer1', 'layer2', 'layer3b'])
+    it('should expand layers properly (3b)', () => {
+      const out = getLayersArray('layer3b', fixture)
+      expect(out).to.eql(['layer1', 'layer2', 'layer3b'])
+    })
+
+    it('should expand layers properly (3a)', () => {
+      const out = getLayersArray('layer3a', fixture)
+      expect(out).to.eql(['layer1', 'layer2', 'layer3a'])
+    })
+
+    it('should expand layers properly (2)', () => {
+      const out = getLayersArray('layer2', fixture)
+      expect(out).to.eql(['layer1', 'layer2'])
+    })
+
+    it('should expand layers properly (1)', () => {
+      const out = getLayersArray('layer1', fixture)
+      expect(out).to.eql(['layer1'])
+    })
   })
 
-  it('should expand layers properly (3a)', () => {
-    const out = getLayersArray('layer3a', fixture)
-    expect(out).to.eql(['layer1', 'layer2', 'layer3a'])
+  describe('topoSort', () => {
+    const fixture1 = {
+      layerA: [],
+      layer1A: ['layerA'],
+      layer2A: ['layer1A'],
+      layerB: [],
+      layer1B: ['layerB'],
+      layer2B: ['layer1B'],
+      layer3AB: ['layer2B', 'layer2A']
+    }
+
+    // Same thing but messed up order
+    const fixture2 = {
+      layer1A: ['layerA'],
+      layer1B: ['layerB'],
+      layer2A: ['layer1A'],
+      layerB: [],
+      layer3AB: ['layer2B', 'layer2A'],
+      layer2B: ['layer1B'],
+      layerA: []
+    }
+
+    it('should make a topologically sorted array', () => {
+      const out = topoSort(fixture1, (node, inheritance) => inheritance[node])
+      // This basically checks all ordering that matters
+      expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A'))
+      expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A'))
+      expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B'))
+      expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B'))
+      expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB'))
+      expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB'))
+    })
+
+    it('order in object shouldn\'t matter', () => {
+      const out = topoSort(fixture2, (node, inheritance) => inheritance[node])
+      // This basically checks all ordering that matters
+      expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A'))
+      expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A'))
+      expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B'))
+      expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B'))
+      expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB'))
+      expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB'))
+    })
+
+    it('dependentless nodes should be first', () => {
+      const out = topoSort(fixture2, (node, inheritance) => inheritance[node])
+      // This basically checks all ordering that matters
+      expect(out.indexOf('layerA')).to.eql(0)
+      expect(out.indexOf('layerB')).to.eql(1)
+    })
+
+    it('ignores cyclic dependencies', () => {
+      const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => [inheritance[node]])
+      expect(out.indexOf('a')).to.be.below(out.indexOf('c'))
+    })
   })
 
-  it('should expand layers properly (2)', () => {
-    const out = getLayersArray('layer2', fixture)
-    expect(out).to.eql(['layer1', 'layer2'])
-  })
-
-  it('should expand layers properly (1)', () => {
-    const out = getLayersArray('layer1', fixture)
-    expect(out).to.eql(['layer1'])
-  })
-})
-
-describe('topoSort', () => {
-  const fixture1 = {
-    layerA: [],
-    layer1A: ['layerA'],
-    layer2A: ['layer1A'],
-    layerB: [],
-    layer1B: ['layerB'],
-    layer2B: ['layer1B'],
-    layer3AB: ['layer2B', 'layer2A']
-  }
-
-  // Same thing but messed up order
-  const fixture2 = {
-    layer1A: ['layerA'],
-    layer1B: ['layerB'],
-    layer2A: ['layer1A'],
-    layerB: [],
-    layer3AB: ['layer2B', 'layer2A'],
-    layer2B: ['layer1B'],
-    layerA: []
-  }
-
-  it('should make a topologically sorted array', () => {
-    const out = topoSort(fixture1, (node, inheritance) => inheritance[node])
-    // This basically checks all ordering that matters
-    expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A'))
-    expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A'))
-    expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B'))
-    expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B'))
-    expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB'))
-    expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB'))
-  })
-
-  it('order in object shouldn\'t matter', () => {
-    const out = topoSort(fixture2, (node, inheritance) => inheritance[node])
-    // This basically checks all ordering that matters
-    expect(out.indexOf('layerA')).to.be.below(out.indexOf('layer1A'))
-    expect(out.indexOf('layer1A')).to.be.below(out.indexOf('layer2A'))
-    expect(out.indexOf('layerB')).to.be.below(out.indexOf('layer1B'))
-    expect(out.indexOf('layer1B')).to.be.below(out.indexOf('layer2B'))
-    expect(out.indexOf('layer2A')).to.be.below(out.indexOf('layer3AB'))
-    expect(out.indexOf('layer2B')).to.be.below(out.indexOf('layer3AB'))
-  })
-
-  it('dependentless nodes should be first', () => {
-    const out = topoSort(fixture2, (node, inheritance) => inheritance[node])
-    // This basically checks all ordering that matters
-    expect(out.indexOf('layerA')).to.eql(0)
-    expect(out.indexOf('layerB')).to.eql(1)
-  })
-
-  it('ignores cyclic dependencies', () => {
-    const out = topoSort({ a: 'b', b: 'a', c: 'a' }, (node, inheritance) => [inheritance[node]])
-    expect(out.indexOf('a')).to.be.below(out.indexOf('c'))
-  })
 })

From 5313833d80221bbad667aeda39ca9e7321489e30 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 27 Jan 2020 04:24:00 +0200
Subject: [PATCH 198/483] lint

---
 .../style_switcher/style_switcher.vue         | 139 +++++++++---------
 .../services/theme_data/theme_data.spec.js    |   1 -
 2 files changed, 71 insertions(+), 69 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 1c39a806..6e38bd8e 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -1,74 +1,77 @@
 <template>
-<div class="style-switcher">
-  <div class="presets-container">
-    <div class="save-load">
-      <div class="theme-warning" v-if="themeWarning">
-        <div class="alert warning">
-        {{ themeWarningHelp }}
-        </div>
-        <div class="buttons">
-          <template v-if="themeWarning.type === 'snapshot_source_mismatch'">
-            <button
-              class="btn"
-              @click="forceLoad"
-            >
-              {{ $t('settings.style.switcher.use_source') }}
-            </button>
-            <button
-              class="btn"
-              @click="forceSnapshot"
-            >
-              {{ $t('settings.style.switcher.use_snapshot') }}
-            </button>
-          </template>
-          <template v-else-if="themeWarning.noActionsPossible">
-            <button
-              class="btn"
-              @click="dismissWarning"
-            >
-              {{ $t('general.dismiss') }}
-            </button>
-          </template>
-          <template v-else>
-            <button
-              class="btn"
-              @click="forceLoad"
-            >
-              {{ $t('settings.style.switcher.load_theme') }}
-            </button>
-            <button
-              class="btn"
-              @click="dismissWarning"
-            >
-              {{ $t('settings.style.switcher.keep_as_is') }}
-            </button>
-          </template>
-        </div>
-      </div>
-      <ExportImport
-        :export-object="exportedTheme"
-        :export-label="$t(&quot;settings.export_theme&quot;)"
-        :import-label="$t(&quot;settings.import_theme&quot;)"
-        :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
-        :on-import="onImport"
-        :validator="importValidator"
-      >
-        <template slot="before">
-          <div class="presets">
-            {{ $t('settings.presets') }}
-            <label
-              for="preset-switcher"
-              class="select"
+  <div class="style-switcher">
+    <div class="presets-container">
+      <div class="save-load">
+        <div
+          v-if="themeWarning"
+          class="theme-warning"
+        >
+          <div class="alert warning">
+            {{ themeWarningHelp }}
+          </div>
+          <div class="buttons">
+            <template v-if="themeWarning.type === 'snapshot_source_mismatch'">
+              <button
+                class="btn"
+                @click="forceLoad"
               >
-              <select
-                id="preset-switcher"
-                v-model="selected"
-                class="preset-switcher"
+                {{ $t('settings.style.switcher.use_source') }}
+              </button>
+              <button
+                class="btn"
+                @click="forceSnapshot"
+              >
+                {{ $t('settings.style.switcher.use_snapshot') }}
+              </button>
+            </template>
+            <template v-else-if="themeWarning.noActionsPossible">
+              <button
+                class="btn"
+                @click="dismissWarning"
+              >
+                {{ $t('general.dismiss') }}
+              </button>
+            </template>
+            <template v-else>
+              <button
+                class="btn"
+                @click="forceLoad"
+              >
+                {{ $t('settings.style.switcher.load_theme') }}
+              </button>
+              <button
+                class="btn"
+                @click="dismissWarning"
+              >
+                {{ $t('settings.style.switcher.keep_as_is') }}
+              </button>
+            </template>
+          </div>
+        </div>
+        <ExportImport
+          :export-object="exportedTheme"
+          :export-label="$t(&quot;settings.export_theme&quot;)"
+          :import-label="$t(&quot;settings.import_theme&quot;)"
+          :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
+          :on-import="onImport"
+          :validator="importValidator"
+        >
+          <template slot="before">
+            <div class="presets">
+              {{ $t('settings.presets') }}
+              <label
+                for="preset-switcher"
+                class="select"
+              >
+                <select
+                  id="preset-switcher"
+                  v-model="selected"
+                  class="preset-switcher"
                 >
-                <option
-                  v-for="style in availableStyles"
-                  :key="style.name"
-                  :value="style"
+                  <option
+                    v-for="style in availableStyles"
+                    :key="style.name"
+                    :value="style"
                     :style="{
                       backgroundColor: style[1] || (style.theme || style.source).colors.bg,
                       color: style[3] || (style.theme || style.source).colors.text
diff --git a/test/unit/specs/services/theme_data/theme_data.spec.js b/test/unit/specs/services/theme_data/theme_data.spec.js
index 67f4fd5a..c634cade 100644
--- a/test/unit/specs/services/theme_data/theme_data.spec.js
+++ b/test/unit/specs/services/theme_data/theme_data.spec.js
@@ -86,5 +86,4 @@ describe('Theme Data utility functions', () => {
       expect(out.indexOf('a')).to.be.below(out.indexOf('c'))
     })
   })
-
 })

From e4e3a28838f431872ab5fd6b10bb8db4a03af389 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Mon, 27 Jan 2020 15:49:05 +0200
Subject: [PATCH 199/483] remove logs/commented code

---
 src/components/emoji_reactions/emoji_reactions.js | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index b37cce3d..95d52cb6 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -4,16 +4,13 @@ const EmojiReactions = {
   props: ['status'],
   computed: {
     emojiReactions () {
-      console.log(this.status.emoji_reactions)
       return this.status.emoji_reactions
     }
   },
   methods: {
     reactedWith (emoji) {
-      // return []
       const user = this.$store.state.users.currentUser
       const reaction = this.status.emoji_reactions.find(r => r.emoji === emoji)
-      console.log(reaction)
       return reaction.accounts && reaction.accounts.find(u => u.id === user.id)
     },
     reactWith (emoji) {

From cb205036f931e143726790cbc3292e1b53f435ce Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Mon, 27 Jan 2020 14:18:15 +0000
Subject: [PATCH 200/483] Apply suggestion to src/services/api/api.service.js

---
 src/services/api/api.service.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index aa31f123..11aa0675 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -894,7 +894,7 @@ const reactWithEmoji = ({ id, emoji, credentials }) => {
     method: 'POST',
     credentials,
     payload: { emoji }
-  }).then(status => parseStatus(status))
+  }).then(parseStatus)
 }
 
 const unreactWithEmoji = ({ id, emoji, credentials }) => {

From e6291e4ee179ab85f212b1eef7d9e03565e6a8f8 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Mon, 27 Jan 2020 18:43:26 +0200
Subject: [PATCH 201/483] remove unnecessary anonymous function

---
 src/services/api/api.service.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index aa31f123..11aa0675 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -894,7 +894,7 @@ const reactWithEmoji = ({ id, emoji, credentials }) => {
     method: 'POST',
     credentials,
     payload: { emoji }
-  }).then(status => parseStatus(status))
+  }).then(parseStatus)
 }
 
 const unreactWithEmoji = ({ id, emoji, credentials }) => {

From 18ca2a035bc9107288e35f11be8dac83c7539503 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 28 Jan 2020 00:25:05 +0200
Subject: [PATCH 202/483] fix rgba function, whoops

---
 src/services/color_convert/color_convert.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index b5461038..3b6fdcc7 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -171,7 +171,7 @@ export const mixrgb = (a, b) => {
  * @returns {String} CSS rgba() color
  */
 export const rgba2css = function (rgba) {
-  return `rgba(${Math.floor(rgba.r)}, ${Math.floor(rgba.g)}, ${Math.floor(rgba.b)}, .5)`
+  return `rgba(${Math.floor(rgba.r)}, ${Math.floor(rgba.g)}, ${Math.floor(rgba.b)}, ${rgba.a})`
 }
 
 /**

From 526b43eba66c597c78857da6ded3c7f672f96e49 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 28 Jan 2020 01:29:20 +0200
Subject: [PATCH 203/483] Kenomo (see: #624) theme. Ability to define link
 color for post contents. Fixes

---
 src/components/emoji_input/emoji_input.vue    |  2 +
 .../notifications/notifications.scss          |  3 +
 src/components/popper/popper.scss             |  2 +
 src/components/status/status.vue              |  7 ++
 .../style_switcher/style_switcher.vue         | 17 +++++
 src/components/user_card/user_card.vue        |  5 ++
 src/services/theme_data/pleromafe.js          | 34 +++++++++
 static/themes/kenomo.json                     | 71 +++++++++++++++++++
 8 files changed, 141 insertions(+)
 create mode 100644 static/themes/kenomo.json

diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 94eae560..e9ac09c3 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -116,6 +116,8 @@
         --faint: var(--popoverFaintText, $fallback--faint);
         --faintLink: var(--popoverFaintLink, $fallback--faint);
         --lightText: var(--popoverLightText, $fallback--lightText);
+        --postLink: var(--popoverPostLink, $fallback--link);
+        --postFaintLink: var(--popoverPostFaintLink, $fallback--link);
         --icon: var(--popoverIcon, $fallback--icon);
       }
     }
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 71876b14..ca762432 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -68,6 +68,9 @@
         a {
           color: var(--faintLink);
         }
+        .status-content a {
+          color: var(--postFaintLink);
+        }
       }
       padding: 0;
       .media-body {
diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss
index 341416c1..8e2dcd55 100644
--- a/src/components/popper/popper.scss
+++ b/src/components/popper/popper.scss
@@ -15,6 +15,8 @@
     --faint: var(--popoverFaintText, $fallback--faint);
     --faintLink: var(--popoverFaintLink, $fallback--faint);
     --lightText: var(--popoverLightText, $fallback--lightText);
+    --postLink: var(--popoverPostLink, $fallback--link);
+    --postFaintLink: var(--popoverPostFaintLink, $fallback--link);
     --icon: var(--popoverIcon, $fallback--icon);
   }
 
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 1997e187..b1048832 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -451,6 +451,8 @@ $status-margin: 0.75em;
     --lightText: var(--selectedPostLightText, $fallback--light);
     --faint: var(--selectedPostFaintText, $fallback--faint);
     --faintLink: var(--selectedPostFaintLink, $fallback--faint);
+    --postLink: var(--selectedPostPostLink, $fallback--faint);
+    --postFaintLink: var(--selectedPostFaintPostLink, $fallback--faint);
     --icon: var(--selectedPostIcon, $fallback--icon);
   }
 
@@ -640,6 +642,11 @@ $status-margin: 0.75em;
     line-height: 1.4em;
     white-space: pre-wrap;
 
+    a {
+      color: $fallback--link;
+      color: var(--postLink, $fallback--link);
+    }
+
     img, video {
       max-width: 100%;
       max-height: 400px;
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 6e38bd8e..0b3b0fbf 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -248,6 +248,23 @@
             </button>
           </div>
           <div class="color-item">
+            <h4>{{ $t('settings.style.advanced_colors.post') }}</h4>
+            <ColorInput
+              v-model="postLinkColorLocal"
+              name="postLinkColor"
+              :fallback="previewTheme.colors.accent"
+              :label="$t('settings.links')"
+              :show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
+            />
+            <ContrastRatio :contrast="previewContrast.bgPostLink" />
+            <ColorInput
+              v-model="iconColorLocal"
+              name="iconColor"
+              :fallback="previewTheme.colors.accent"
+              :label="$t('settings.links')"
+              :show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
+            />
+            <ContrastRatio :contrast="previewContrast.bgIcon" />
             <h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
             <ColorInput
               v-model="alertErrorColorLocal"
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index e286ceea..72656435 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -299,6 +299,11 @@
   &-bio {
     text-align: center;
 
+    a {
+      color: $fallback--link;
+      color: var(--postLink, $fallback--link);
+    }
+
     img {
       object-fit: contain;
       vertical-align: middle;
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index a40d08a6..74e6fc1c 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -107,6 +107,10 @@ export const SLOT_INHERITANCE = {
     depends: ['link'],
     opacity: 'faint'
   },
+  postFaintLink: {
+    depends: ['postLink'],
+    opacity: 'faint'
+  },
 
   cBlue: '#0000ff',
   cRed: '#FF0000',
@@ -122,6 +126,11 @@ export const SLOT_INHERITANCE = {
     layer: 'highlight',
     textColor: true
   },
+  highlightPostLink: {
+    depends: ['postLink'],
+    layer: 'highlight',
+    textColor: 'preserve'
+  },
   highlightFaintText: {
     depends: ['faint'],
     layer: 'highlight',
@@ -132,6 +141,11 @@ export const SLOT_INHERITANCE = {
     layer: 'highlight',
     textColor: 'preserve'
   },
+  highlightPostFaintLink: {
+    depends: ['postFaintLink'],
+    layer: 'highlight',
+    textColor: 'preserve'
+  },
   highlightText: {
     depends: ['text'],
     layer: 'highlight',
@@ -156,6 +170,11 @@ export const SLOT_INHERITANCE = {
     layer: 'popover',
     textColor: true
   },
+  popoverPostLink: {
+    depends: ['postLink'],
+    layer: 'popover',
+    textColor: 'preserve'
+  },
   popoverFaintText: {
     depends: ['faint'],
     layer: 'popover',
@@ -166,6 +185,11 @@ export const SLOT_INHERITANCE = {
     layer: 'popover',
     textColor: 'preserve'
   },
+  popoverPostFaintLink: {
+    depends: ['postFaintLink'],
+    layer: 'popover',
+    textColor: 'preserve'
+  },
   popoverText: {
     depends: ['text'],
     layer: 'popover',
@@ -194,6 +218,12 @@ export const SLOT_INHERITANCE = {
     variant: 'selectedPost',
     textColor: true
   },
+  selectedPostPostLink: {
+    depends: ['highlightPostLink'],
+    layer: 'highlight',
+    variant: 'selectedPost',
+    textColor: 'preserve'
+  },
   selectedPostFaintLink: {
     depends: ['highlightFaintLink'],
     layer: 'highlight',
@@ -300,6 +330,10 @@ export const SLOT_INHERITANCE = {
     color: (mod, text) => brightness(20 * mod, text).rgb
   },
 
+  postLink: {
+    depends: ['link']
+  },
+
   border: {
     depends: ['fg'],
     opacity: 'border',
diff --git a/static/themes/kenomo.json b/static/themes/kenomo.json
new file mode 100644
index 00000000..98ddf974
--- /dev/null
+++ b/static/themes/kenomo.json
@@ -0,0 +1,71 @@
+{
+  "_pleroma_theme_version": 2,
+  "name": "Kenomo",
+  "source": {
+    "themeEngineVersion": 3,
+    "fonts": {},
+    "shadows": {
+      "panel": [],
+      "topBar": [],
+      "button": [
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "input": [
+        {
+          "x": 0,
+          "y": "0",
+          "blur": 0,
+          "spread": "1",
+          "color": "#576574",
+          "alpha": "1",
+          "inset": true
+        }
+      ]
+    },
+    "opacity": {
+      "underlay": "1",
+      "border": "0"
+    },
+    "colors": {
+      "bg": "#ffffff",
+      "fg": "#f6f6f6",
+      "text": "#494949",
+      "underlay": "#ffffff",
+      "link": "#818181",
+      "accent": "#818181",
+      "cBlue": "#2e86de",
+      "cRed": "#c96248",
+      "cGreen": "#0fa00f",
+      "cOrange": "#aa7623",
+      "postLink": "#2e86de",
+      "border": "#ffffff",
+      "icon": "#8a8a8a",
+      "panel": "transparent",
+      "topBarText": "#4b4b4b",
+      "tab": "--btn,-30",
+      "btn": "#576574"
+    },
+    "radii": {
+      "panel": "0",
+      "avatar": "6",
+      "avatarAlt": "6"
+    }
+  }
+}

From 53a15eec7d3f5d150b6b6b163057345062eefb39 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 28 Jan 2020 01:31:37 +0200
Subject: [PATCH 204/483] add theme to list

---
 static/styles.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/static/styles.json b/static/styles.json
index 842092c4..2bddc8d8 100644
--- a/static/styles.json
+++ b/static/styles.json
@@ -11,5 +11,6 @@
   "redmond-xxi": "/static/themes/redmond-xxi.json",
   "breezy-dark": "/static/themes/breezy-dark.json",
   "breezy-light": "/static/themes/breezy-light.json",
-  "mammal": "/static/themes/mammal.json"
+  "mammal": "/static/themes/mammal.json",
+  "kenomo": "/static/themes/kenomo.json"
 }

From b63e679a31a573c30868477f17322d6ed040af58 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 28 Jan 2020 02:03:21 +0200
Subject: [PATCH 205/483] removed one color TODO

---
 src/components/popper/popper.scss             |  2 ++
 .../style_switcher/style_switcher.vue         | 29 ++++++++++++++-----
 src/components/user_card/user_card.vue        |  5 ++--
 src/i18n/en.json                              |  3 ++
 src/services/theme_data/pleromafe.js          | 20 ++++++++++++-
 5 files changed, 47 insertions(+), 12 deletions(-)

diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss
index 8e2dcd55..99b7e6fc 100644
--- a/src/components/popper/popper.scss
+++ b/src/components/popper/popper.scss
@@ -137,6 +137,8 @@
     width: 100%;
     height: 100%;
 
+    --btnText: var(--popoverText, $fallback--text);
+
     &-icon {
       padding-left: 0.5rem;
 
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 0b3b0fbf..9de3a96c 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -257,14 +257,6 @@
               :show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
             />
             <ContrastRatio :contrast="previewContrast.bgPostLink" />
-            <ColorInput
-              v-model="iconColorLocal"
-              name="iconColor"
-              :fallback="previewTheme.colors.accent"
-              :label="$t('settings.links')"
-              :show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
-            />
-            <ContrastRatio :contrast="previewContrast.bgIcon" />
             <h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
             <ColorInput
               v-model="alertErrorColorLocal"
@@ -298,6 +290,27 @@
               :contrast="previewContrast.alertWarningText"
               large="true"
             />
+            <ColorInput
+              v-model="alertNeutralColorLocal"
+              name="alertNeutral"
+              :label="$t('settings.style.advanced_colors.alert_neutral')"
+              :fallback="previewTheme.colors.alertNeutral"
+            />
+            <ColorInput
+              v-model="alertNeutralTextColorLocal"
+              name="alertNeutralText"
+              :label="$t('settings.text')"
+              :fallback="previewTheme.colors.alertNeutralText"
+            />
+            <ContrastRatio
+              :contrast="previewContrast.alertNeutralText"
+              large="true"
+            />
+            <OpacityInput
+              v-model="alertOpacityLocal"
+              name="alertOpacity"
+              :fallback="previewTheme.opacity.alert"
+            />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 72656435..3988ff1c 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -465,14 +465,13 @@
       color: var(--text, $fallback--text);
     }
 
-    // TODO use proper colors
     .staff {
       flex: none;
       text-transform: capitalize;
       color: $fallback--text;
-      color: var(--btnText, $fallback--text);
+      color: var(--alertNeutralText, $fallback--text);
       background-color: $fallback--fg;
-      background-color: var(--btn, $fallback--fg);
+      background-color: var(--alertNeutral, $fallback--fg);
     }
   }
 
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 81dde663..06cc12f0 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -441,7 +441,10 @@
         "alert": "Alert background",
         "alert_error": "Error",
         "alert_warning": "Warning",
+        "alert_neutral": "Neutral",
+        "post": "Posts/User bios",
         "badge": "Badge background",
+        "popover": "Tooltips, menus, popovers",
         "badge_notification": "Notification",
         "panel_header": "Panel header",
         "top_bar": "Top bar",
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index 74e6fc1c..354009ee 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -1,4 +1,4 @@
-import { brightness } from 'chromatism'
+import { invertLightness, brightness } from 'chromatism'
 import { alphaBlend, mixrgb } from '../color_convert/color_convert.js'
 /* This is a definition of all layer combinations
  * each key is a topmost layer, each value represents layer underneath
@@ -574,6 +574,24 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
 
+  alertNeutral: {
+    depends: ['text'],
+    opacity: 'alert'
+  },
+  alertNeutralText: {
+    depends: ['text'],
+    layer: 'alert',
+    variant: 'alertNeutral',
+    color: (mod, text) => invertLightness(text).rgb,
+    textColor: true
+  },
+  alertNeutralPanelText: {
+    depends: ['panelText'],
+    layer: 'alertPanel',
+    variant: 'alertNeutral',
+    textColor: true
+  },
+
   badgeNotification: '--cRed',
   badgeNotificationText: {
     depends: ['text', 'badgeNotification'],

From 566f013ac49139cb3411b24920b8b235c8c3c424 Mon Sep 17 00:00:00 2001
From: eugenijm <eugenijm@protonmail.com>
Date: Tue, 28 Jan 2020 06:12:32 +0300
Subject: [PATCH 206/483] Fix password and email update

---
 src/components/user_settings/user_settings.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 1709b48f..38373056 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -307,7 +307,7 @@ const UserSettings = {
         newPassword: this.changePasswordInputs[1],
         newPasswordConfirmation: this.changePasswordInputs[2]
       }
-      this.$store.state.api.backendInteractor.changePassword({ params })
+      this.$store.state.api.backendInteractor.changePassword(params)
         .then((res) => {
           if (res.status === 'success') {
             this.changedPassword = true
@@ -324,7 +324,7 @@ const UserSettings = {
         email: this.newEmail,
         password: this.changeEmailPassword
       }
-      this.$store.state.api.backendInteractor.changeEmail({ params })
+      this.$store.state.api.backendInteractor.changeEmail(params)
         .then((res) => {
           if (res.status === 'success') {
             this.changedEmail = true

From 6afff4f8c205ec70d3694564c706f6a46a61db9e Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Tue, 28 Jan 2020 17:09:25 +0200
Subject: [PATCH 207/483] review changes

---
 src/components/emoji_reactions/emoji_reactions.vue       | 6 +++---
 src/components/react_button/react_button.js              | 9 +--------
 src/components/react_button/react_button.vue             | 3 +--
 .../entity_normalizer/entity_normalizer.service.js       | 2 +-
 4 files changed, 6 insertions(+), 14 deletions(-)

diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 8a229240..741fc11e 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -7,8 +7,8 @@
       :class="{ 'picked-reaction': reactedWith(reaction.emoji) }"
       @click="emojiOnClick(reaction.emoji, $event)"
     >
-      <span>{{ reaction.count }}</span>
       <span>{{ reaction.emoji }}</span>
+      <span>{{ reaction.count }}</span>
     </button>
   </div>
 </template>
@@ -31,10 +31,10 @@
   align-items: center;
   justify-content: center;
   box-sizing: border-box;
-  :first-child {
+  &:first-child {
     margin-right: 0.25em;
   }
-  :last-child {
+  &:last-child {
     width: 1.5em;
   }
   &:focus {
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index d1a179bc..6fb2a780 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -4,7 +4,6 @@ const ReactButton = {
   props: ['status', 'loggedIn'],
   data () {
     return {
-      animated: false,
       showTooltip: false,
       filterWord: '',
       popperOptions: {
@@ -29,7 +28,7 @@ const ReactButton = {
   },
   computed: {
     commonEmojis () {
-      return ['💖', '😠', '👀', '😂', '🔥']
+      return ['❤️', '😠', '👀', '😂', '🔥']
     },
     emojis () {
       if (this.filterWord !== '') {
@@ -37,12 +36,6 @@ const ReactButton = {
       }
       return this.$store.state.instance.emoji || []
     },
-    classes () {
-      return {
-        'icon-smile': true,
-        'animate-spin': this.animated
-      }
-    },
     ...mapGetters(['mergedConfig'])
   }
 }
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index 7f1bc492..c925dd71 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -40,8 +40,7 @@
       @click.prevent="openReactionSelect"
     >
       <i
-        :class="classes"
-        class="button-icon add-reaction-button"
+        class="icon-smile button-icon add-reaction-button"
         :title="$t('tool_tip.add_reaction')"
       />
     </div>
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index f66d09ac..a3d0b782 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -233,7 +233,6 @@ export const parseStatus = (data) => {
     output.statusnet_html = addEmojis(data.content, data.emojis)
 
     output.tags = data.tags
-    output.emoji_reactions = data.pleroma.emoji_reactions
 
     if (data.pleroma) {
       const { pleroma } = data
@@ -243,6 +242,7 @@ export const parseStatus = (data) => {
       output.is_local = pleroma.local
       output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
       output.thread_muted = pleroma.thread_muted
+      output.emoji_reactions = pleroma.emoji_reactions
     } else {
       output.text = data.content
       output.summary = data.spoiler_text

From 29806c962972091cda2c7d55380b3326b5a07ba4 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Tue, 28 Jan 2020 17:53:47 +0200
Subject: [PATCH 208/483] fix emoji reaction classes broken in develop

---
 src/components/emoji_reactions/emoji_reactions.vue | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 741fc11e..00d6d2b7 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -7,7 +7,7 @@
       :class="{ 'picked-reaction': reactedWith(reaction.emoji) }"
       @click="emojiOnClick(reaction.emoji, $event)"
     >
-      <span>{{ reaction.emoji }}</span>
+      <span class="reaction-emoji">{{ reaction.emoji }}</span>
       <span>{{ reaction.count }}</span>
     </button>
   </div>
@@ -31,12 +31,10 @@
   align-items: center;
   justify-content: center;
   box-sizing: border-box;
-  &:first-child {
+  .reaction-emoji {
+    width: 1.25em;
     margin-right: 0.25em;
   }
-  &:last-child {
-    width: 1.5em;
-  }
   &:focus {
     outline: none;
   }

From c54111797ae1058e59931b2d1f12e6ab6a6f96a9 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shpuld@shpposter.club>
Date: Tue, 28 Jan 2020 17:54:40 +0200
Subject: [PATCH 209/483] add emoji reactions to changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b09eb3a0..65973dbb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Support for 'Move' type notifications
 - Pleroma AMOLED dark theme
 - User level domain mutes, under User Settings -> Mutes
+- Emoji reactions for statuses
 ### Changed
 - Captcha now resets on failed registrations
 - Notifications column now cleans itself up to optimize performance when tab is left open for a long time

From 846285326900a497c68dc994ce06dcb1d5d1ca9c Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 28 Jan 2020 22:40:15 +0200
Subject: [PATCH 210/483] update button toggled state, apply it to emoji
 reactions

---
 src/App.scss                                       |  4 +++-
 src/components/emoji_reactions/emoji_reactions.vue |  8 +-------
 src/components/style_switcher/style_switcher.vue   | 13 +++++++++++++
 static/themes/redmond-xx-se.json                   |  2 ++
 static/themes/redmond-xx.json                      |  2 ++
 static/themes/redmond-xxi.json                     |  2 ++
 6 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index dd994a38..1fabaaa9 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -117,7 +117,9 @@ button {
     color: $fallback--text;
     color: var(--btnToggledText, $fallback--text);
     background-color: $fallback--fg;
-    background-color: var(--btnToggled, $fallback--fg)
+    background-color: var(--btnToggled, $fallback--fg);
+    box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
+    box-shadow: var(--buttonPressedShadow);
   }
 
   &.danger {
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 00d6d2b7..f5d2e79a 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -4,7 +4,7 @@
       v-for="(reaction) in emojiReactions"
       :key="reaction.emoji"
       class="emoji-reaction btn btn-default"
-      :class="{ 'picked-reaction': reactedWith(reaction.emoji) }"
+      :class="{ 'toggled': reactedWith(reaction.emoji) }"
       @click="emojiOnClick(reaction.emoji, $event)"
     >
       <span class="reaction-emoji">{{ reaction.emoji }}</span>
@@ -40,10 +40,4 @@
   }
 }
 
-.picked-reaction {
-  border: 1px solid var(--link, $fallback--link);
-  margin-left: -1px; // offset the border, can't use inset shadows either
-  margin-right: calc(0.5em - 1px);
-}
-
 </style>
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 9de3a96c..5a42bba7 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -460,6 +460,19 @@
               :label="$t('settings.text')"
             />
             <ContrastRatio :contrast="previewContrast.btnDisabledText" />
+            <h4>{{ $t('settings.style.advanced_colors.toggled') }}</h4>
+            <ColorInput
+              v-model="btnToggledColorLocal"
+              name="btnToggledColor"
+              :fallback="previewTheme.colors.btnToggled"
+              :label="$t('settings.background')"
+            />
+            <ColorInput
+              v-model="btnToggledTextColorLocal"
+              name="btnToggledTextColor"
+              :fallback="previewTheme.colors.btnToggledText"
+              :label="$t('settings.text')"
+            />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.tabs') }}</h4>
diff --git a/static/themes/redmond-xx-se.json b/static/themes/redmond-xx-se.json
index 8deab7b7..7a4a29da 100644
--- a/static/themes/redmond-xx-se.json
+++ b/static/themes/redmond-xx-se.json
@@ -2,6 +2,7 @@
   "_pleroma_theme_version": 2,
   "name": "Redmond XX SE",
   "source": {
+    "themeEngineVersion": 3,
     "shadows": {
       "panel": [
         {
@@ -276,6 +277,7 @@
       "topBar": "#000080",
       "topBarLink": "#ffffff",
       "btn": "#c0c0c0",
+      "btnToggled": "--btn",
       "faint": "#3f3f3f",
       "faintLink": "#404080",
       "border": "#808080",
diff --git a/static/themes/redmond-xx.json b/static/themes/redmond-xx.json
index cdb89fe3..ff95b1e0 100644
--- a/static/themes/redmond-xx.json
+++ b/static/themes/redmond-xx.json
@@ -2,6 +2,7 @@
   "_pleroma_theme_version": 2,
   "name": "Redmond XX",
   "source": {
+    "themeEngineVersion": 3,
     "shadows": {
       "panel": [
         {
@@ -267,6 +268,7 @@
       "topBar": "#000080",
       "topBarLink": "#ffffff",
       "btn": "#c0c0c0",
+      "btnToggled": "--btn",
       "faint": "#3f3f3f",
       "faintLink": "#404080",
       "border": "#808080",
diff --git a/static/themes/redmond-xxi.json b/static/themes/redmond-xxi.json
index 79a2cb26..f788bdb8 100644
--- a/static/themes/redmond-xxi.json
+++ b/static/themes/redmond-xxi.json
@@ -2,6 +2,7 @@
   "_pleroma_theme_version": 2,
   "name": "Redmond XXI",
   "source": {
+    "themeEngineVersion": 3,
     "shadows": {
       "panel": [
         {
@@ -249,6 +250,7 @@
       "topBar": "#042967",
       "topBarLink": "#ffffff",
       "btn": "#d6d6ce",
+      "btnToggled": "--btn",
       "faint": "#3f3f3f",
       "faintLink": "#404080",
       "border": "#808080",

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: [PATCH 211/483] updated preview to account for underlay

---
 src/App.scss                                  |   4 +-
 src/App.vue                                   |   2 +-
 src/components/style_switcher/preview.vue     | 198 ++++++++++--------
 .../style_switcher/style_switcher.vue         |   4 +-
 4 files changed, 112 insertions(+), 96 deletions(-)

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>
-      </div>
-      <span class="faint">
-        {{ $t('settings.style.preview.header_faint') }}
-      </span>
-      <span class="alert error">
-        {{ $t('settings.style.preview.error') }}
-      </span>
-      <button class="btn">
-        {{ $t('settings.style.preview.button') }}
-      </button>
-    </div>
-    <div class="panel-body theme-preview-content">
-      <div class="post">
-        <div class="avatar">
-          ( ͡° ͜ʖ ͡°)
-        </div>
-        <div class="content">
-          <h4>
-            {{ $t('settings.style.preview.content') }}
-          </h4>
-
-          <i18n path="settings.style.preview.text">
-            <code style="font-family: var(--postCodeFont)">
-              {{ $t('settings.style.preview.mono') }}
-            </code>
-            <a style="color: var(--link)">
-              {{ $t('settings.style.preview.link') }}
-            </a>
-          </i18n>
-
-          <div class="icons">
-            <i
-              style="color: var(--cBlue)"
-              class="button-icon icon-reply"
-            />
-            <i
-              style="color: var(--cGreen)"
-              class="button-icon icon-retweet"
-            />
-            <i
-              style="color: var(--cOrange)"
-              class="button-icon icon-star"
-            />
-            <i
-              style="color: var(--cRed)"
-              class="button-icon icon-cancel"
-            />
-          </div>
-        </div>
-      </div>
-
-      <div class="after-post">
-        <div class="avatar-alt">
-          :^)
-        </div>
-        <div class="content">
-          <i18n
-            path="settings.style.preview.fine_print"
-            tag="span"
-            class="faint"
-          >
-            <a style="color: var(--faintLink)">
-              {{ $t('settings.style.preview.faint_link') }}
-            </a>
-          </i18n>
-        </div>
-      </div>
-      <div class="separator" />
-
-      <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>
       </div>
+      <div class="panel-body theme-preview-content">
+        <div class="post">
+          <div class="avatar">
+            ( ͡° ͜ʖ ͡°)
+          </div>
+          <div class="content">
+            <h4>
+              {{ $t('settings.style.preview.content') }}
+            </h4>
+
+            <i18n path="settings.style.preview.text">
+              <code style="font-family: var(--postCodeFont)">
+                {{ $t('settings.style.preview.mono') }}
+              </code>
+              <a style="color: var(--link)">
+                {{ $t('settings.style.preview.link') }}
+              </a>
+            </i18n>
+
+            <div class="icons">
+              <i
+                style="color: var(--cBlue)"
+                class="button-icon icon-reply"
+              />
+              <i
+                style="color: var(--cGreen)"
+                class="button-icon icon-retweet"
+              />
+              <i
+                style="color: var(--cOrange)"
+                class="button-icon icon-star"
+              />
+              <i
+                style="color: var(--cRed)"
+                class="button-icon icon-cancel"
+              />
+            </div>
+          </div>
+        </div>
+
+        <div class="after-post">
+          <div class="avatar-alt">
+            :^)
+          </div>
+          <div class="content">
+            <i18n
+              path="settings.style.preview.fine_print"
+              tag="span"
+              class="faint"
+            >
+              <a style="color: var(--faintLink)">
+                {{ $t('settings.style.preview.faint_link') }}
+              </a>
+            </i18n>
+          </div>
+        </div>
+        <div class="separator" />
+
+        <span class="alert error">
+          {{ $t('settings.style.preview.error') }}
+        </span>
+        <input
+          :value="$t('settings.style.preview.input')"
+          type="text"
+        >
+
+        <div class="actions">
+          <span class="checkbox">
+            <input
+              id="preview_checkbox"
+              checked="very yes"
+              type="checkbox"
+            >
+            <label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label>
+          </span>
+          <button class="btn">
+            {{ $t('settings.style.preview.button') }}
+          </button>
+        </div>
+      </div>
     </div>
   </div>
 </template>
+
+<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">

From 746416207bd15f7883af18e359b84f0c4444a12a Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Thu, 30 Jan 2020 19:55:01 +0300
Subject: [PATCH 212/483] Escape HTML from display name and subject fields

Closes #724
---
 package.json                                                | 1 +
 src/services/entity_normalizer/entity_normalizer.service.js | 6 ++++--
 yarn.lock                                                   | 3 ++-
 3 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/package.json b/package.json
index 9ec8c1eb..5c7fa31e 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
     "chromatism": "^3.0.0",
     "cropperjs": "^1.4.3",
     "diff": "^3.0.1",
+    "escape-html": "^1.0.3",
     "karma-mocha-reporter": "^2.2.1",
     "localforage": "^1.5.0",
     "object-path": "^0.11.3",
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index a3d0b782..3116d211 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -1,3 +1,5 @@
+import escape from 'escape-html'
+
 const qvitterStatusType = (status) => {
   if (status.is_post_verb) {
     return 'status'
@@ -41,7 +43,7 @@ export const parseUser = (data) => {
     }
 
     output.name = data.display_name
-    output.name_html = addEmojis(data.display_name, data.emojis)
+    output.name_html = addEmojis(escape(data.display_name), data.emojis)
 
     output.description = data.note
     output.description_html = addEmojis(data.note, data.emojis)
@@ -256,7 +258,7 @@ export const parseStatus = (data) => {
       output.retweeted_status = parseStatus(data.reblog)
     }
 
-    output.summary_html = addEmojis(data.spoiler_text, data.emojis)
+    output.summary_html = addEmojis(escape(data.spoiler_text), data.emojis)
     output.external_url = data.url
     output.poll = data.poll
     output.pinned = data.pinned
diff --git a/yarn.lock b/yarn.lock
index 1a5d4cef..b794042f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2757,9 +2757,10 @@ es6-promisify@^5.0.0:
   dependencies:
     es6-promise "^4.0.3"
 
-escape-html@~1.0.3:
+escape-html@^1.0.3, escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+  integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
 
 escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"

From 4a266a4d0547c0f68628001e8948dd171ef4554b Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 31 Jan 2020 00:24:54 +0000
Subject: [PATCH 213/483] Fix one click nsfw unhide on videos

---
 CHANGELOG.md                            |  1 +
 src/components/attachment/attachment.js | 11 ++++++++---
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 65973dbb..abefd958 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Single notifications left unread when hitting read on another device/tab
 - Registration fixed
 - Deactivation of remote accounts from frontend
+- Fixed NSFW unhiding not working with videos when using one-click unhiding/displaying
 
 ## [1.1.7 and earlier] - 2019-12-14
 ### Added
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index 06b496b0..b832e10f 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -2,6 +2,7 @@ import StillImage from '../still-image/still-image.vue'
 import VideoAttachment from '../video_attachment/video_attachment.vue'
 import nsfwImage from '../../assets/nsfw.png'
 import fileTypeService from '../../services/file_type/file_type.service.js'
+import { mapGetters } from 'vuex'
 
 const Attachment = {
   props: [
@@ -49,7 +50,8 @@ const Attachment = {
     },
     fullwidth () {
       return this.type === 'html' || this.type === 'audio'
-    }
+    },
+    ...mapGetters(['mergedConfig'])
   },
   methods: {
     linkClicked ({ target }) {
@@ -58,7 +60,7 @@ const Attachment = {
       }
     },
     openModal (event) {
-      const modalTypes = this.$store.getters.mergedConfig.playVideosInModal
+      const modalTypes = this.mergedConfig.playVideosInModal
         ? ['image', 'video']
         : ['image']
       if (fileTypeService.fileMatchesSomeType(modalTypes, this.attachment) ||
@@ -71,7 +73,10 @@ const Attachment = {
       }
     },
     toggleHidden (event) {
-      if (this.$store.getters.mergedConfig.useOneClickNsfw && !this.showHidden) {
+      if (
+        (this.mergedConfig.useOneClickNsfw && !this.showHidden) &&
+        (this.type !== 'video' || this.mergedConfig.playVideosInModal)
+      ) {
         this.openModal(event)
         return
       }

From 9bbf10b55d97f6dbe3197ebbd1bb29d294ff6b55 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Tue, 4 Feb 2020 04:26:32 +0900
Subject: [PATCH 214/483] Add setting for allow_following_move

---
 src/components/user_settings/user_settings.js            | 2 ++
 src/components/user_settings/user_settings.vue           | 9 ++++++---
 .../entity_normalizer/entity_normalizer.service.js       | 2 ++
 3 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 38373056..eca6f9b1 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -55,6 +55,7 @@ const UserSettings = {
       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,
@@ -162,6 +163,7 @@ const UserSettings = {
             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
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 2222c293..8b2336b4 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -90,9 +90,7 @@
               </Checkbox>
             </p>
             <p>
-              <Checkbox
-                v-model="hideFollowers"
-              >
+              <Checkbox v-model="hideFollowers">
                 {{ $t('settings.hide_followers_description') }}
               </Checkbox>
             </p>
@@ -104,6 +102,11 @@
                 {{ $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'">
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index a3d0b782..3bc46886 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -81,6 +81,8 @@ export const parseUser = (data) => {
         output.subscribed = relationship.subscribing
       }
 
+      output.allow_following_move = data.pleroma.allow_following_move
+
       output.hide_follows = data.pleroma.hide_follows
       output.hide_followers = data.pleroma.hide_followers
       output.hide_follows_count = data.pleroma.hide_follows_count

From 9b7497a65957b3f1d3b9f920266fae9bdae11dd5 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Tue, 4 Feb 2020 04:30:31 +0900
Subject: [PATCH 215/483] Change to hide User migrates tab when allow following
 move

---
 src/components/interactions/interactions.js  | 1 +
 src/components/interactions/interactions.vue | 1 +
 2 files changed, 2 insertions(+)

diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js
index cc31ff20..7fe5e76d 100644
--- a/src/components/interactions/interactions.js
+++ b/src/components/interactions/interactions.js
@@ -10,6 +10,7 @@ const tabModeDict = {
 const Interactions = {
   data () {
     return {
+      allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
       filterMode: tabModeDict['mentions']
     }
   },
diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue
index a2e252ab..57d5d87c 100644
--- a/src/components/interactions/interactions.vue
+++ b/src/components/interactions/interactions.vue
@@ -22,6 +22,7 @@
         :label="$t('interactions.follows')"
       />
       <span
+        v-if="!allowFollowingMove"
         key="moves"
         :label="$t('interactions.moves')"
       />

From a06f3a7fbc40cd51df3c2d04406494cf60b0cf8a Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Tue, 4 Feb 2020 04:37:29 +0900
Subject: [PATCH 216/483] Add `with_move` param for fetching notification

---
 src/services/api/api.service.js                             | 6 +++++-
 .../notifications_fetcher/notifications_fetcher.service.js  | 3 +++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 11aa0675..b794fd58 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -495,7 +495,8 @@ const fetchTimeline = ({
   until = false,
   userId = false,
   tag = false,
-  withMuted = false
+  withMuted = false,
+  withMove = false
 }) => {
   const timelineUrls = {
     public: MASTODON_PUBLIC_TIMELINE,
@@ -535,6 +536,9 @@ const fetchTimeline = ({
   if (timeline === 'public' || timeline === 'publicAndExternal') {
     params.push(['only_media', false])
   }
+  if (timeline === 'notifications') {
+    params.push(['with_move', withMove])
+  }
 
   params.push(['count', 20])
   params.push(['with_muted', withMuted])
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index 64499a1b..864e32f8 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -11,9 +11,12 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
   const rootState = store.rootState || store.state
   const timelineData = rootState.statuses.notifications
   const hideMutedPosts = getters.mergedConfig.hideMutedPosts
+  const allowFollowingMove = rootState.users.currentUser.allow_following_move
 
   args['withMuted'] = !hideMutedPosts
 
+  args['withMove'] = !allowFollowingMove
+
   args['timeline'] = 'notifications'
   if (older) {
     if (timelineData.minId !== Number.POSITIVE_INFINITY) {

From ce68ef0138f43fed6617f197d46cc09ac68f9e31 Mon Sep 17 00:00:00 2001
From: kPherox <admin@mail.kr-kp.com>
Date: Tue, 4 Feb 2020 04:50:44 +0900
Subject: [PATCH 217/483] Add option text

---
 src/i18n/en.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/i18n/en.json b/src/i18n/en.json
index db2ce54d..54ddbf82 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -232,6 +232,7 @@
         "desc": "To enable two-factor authentication, enter the code from your two-factor app:"
       }
     },
+    "allow_following_move": "Allow auto-follow when following account moves",
     "attachmentRadius": "Attachments",
     "attachments": "Attachments",
     "autoload": "Enable automatic loading when scrolled to the bottom",

From 611da13a4b252c10f1613d70d877e2d039ba64b7 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 7 Feb 2020 01:25:26 +0200
Subject: [PATCH 218/483] Better Disabled buttons support. Mammal theme fixes.
 Implemented proper context-aware `mod` argument - now checks lightness of
 "variant" color. needs retesting tho

---
 src/App.scss                                  |  4 +-
 src/components/checkbox/checkbox.vue          |  4 +-
 .../style_switcher/style_switcher.vue         | 57 +++++++++++++++++++
 src/i18n/en.json                              |  1 +
 src/services/style_setter/style_setter.js     | 17 +-----
 src/services/theme_data/pleromafe.js          | 31 +++++-----
 src/services/theme_data/theme_data.service.js |  9 ++-
 7 files changed, 89 insertions(+), 34 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 85fd0b47..9f37ff12 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -179,7 +179,7 @@ input, textarea, .select, .input {
     right: 5px;
     height: 100%;
     color: $fallback--text;
-    color: var(--text, $fallback--text);
+    color: var(--inputText, $fallback--text);
     line-height: 28px;
     z-index: 0;
     pointer-events: none;
@@ -254,7 +254,7 @@ input, textarea, .select, .input {
     display: none;
     &:checked + label::before {
       color: $fallback--text;
-      color: var(--text, $fallback--text);
+      color: var(--inputText, $fallback--text);
     }
     &:disabled {
       &,
diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue
index 1113f81d..03375b2f 100644
--- a/src/components/checkbox/checkbox.vue
+++ b/src/components/checkbox/checkbox.vue
@@ -87,13 +87,13 @@ export default {
 
     &:checked + .checkbox-indicator::before {
       color: $fallback--text;
-      color: var(--text, $fallback--text);
+      color: var(--inputText, $fallback--text);
     }
 
     &:indeterminate + .checkbox-indicator::before {
       content: '–';
       color: $fallback--text;
-      color: var(--text, $fallback--text);
+      color: var(--inputText, $fallback--text);
     }
 
   }
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index a3f9b488..705a60a2 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -430,6 +430,20 @@
               :label="$t('settings.text')"
             />
             <ContrastRatio :contrast="previewContrast.btnText" />
+            <ColorInput
+              v-model="btnPanelTextColorLocal"
+              name="btnPanelTextColor"
+              :fallback="previewTheme.colors.btnPanelText"
+              :label="$t('settings.style.advanced_colors.panel_header')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnPanelText" />
+            <ColorInput
+              v-model="btnTopBarTextColorLocal"
+              name="btnTopBarTextColor"
+              :fallback="previewTheme.colors.btnTopBarText"
+              :label="$t('settings.style.advanced_colors.top_bar')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnTopBarText" />
             <h4>{{ $t('settings.style.advanced_colors.pressed') }}</h4>
             <ColorInput
               v-model="btnPressedColorLocal"
@@ -444,6 +458,20 @@
               :label="$t('settings.text')"
             />
             <ContrastRatio :contrast="previewContrast.btnPressedText" />
+            <ColorInput
+              v-model="btnPressedPanelTextColorLocal"
+              name="btnPressedPanelTextColor"
+              :fallback="previewTheme.colors.btnPressedPanelText"
+              :label="$t('settings.style.advanced_colors.panel_header')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnPressedPanelText" />
+            <ColorInput
+              v-model="btnPressedTopBarTextColorLocal"
+              name="btnPressedTopBarTextColor"
+              :fallback="previewTheme.colors.btnPressedTopBarText"
+              :label="$t('settings.style.advanced_colors.top_bar')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnPressedTopBarText" />
             <h4>{{ $t('settings.style.advanced_colors.disabled') }}</h4>
             <ColorInput
               v-model="btnDisabledColorLocal"
@@ -458,6 +486,20 @@
               :label="$t('settings.text')"
             />
             <ContrastRatio :contrast="previewContrast.btnDisabledText" />
+            <ColorInput
+              v-model="btnDisabledPanelTextColorLocal"
+              name="btnDisabledPanelTextColor"
+              :fallback="previewTheme.colors.btnDisabledPanelText"
+              :label="$t('settings.style.advanced_colors.panel_header')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnDisabledPanelText" />
+            <ColorInput
+              v-model="btnDisabledTopBarTextColorLocal"
+              name="btnDisabledTopBarTextColor"
+              :fallback="previewTheme.colors.btnDisabledTopBarText"
+              :label="$t('settings.style.advanced_colors.top_bar')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnDisabledTopBarText" />
             <h4>{{ $t('settings.style.advanced_colors.toggled') }}</h4>
             <ColorInput
               v-model="btnToggledColorLocal"
@@ -471,6 +513,21 @@
               :fallback="previewTheme.colors.btnToggledText"
               :label="$t('settings.text')"
             />
+            <ContrastRatio :contrast="previewContrast.btnToggledText" />
+            <ColorInput
+              v-model="btnToggledPanelTextColorLocal"
+              name="btnToggledPanelTextColor"
+              :fallback="previewTheme.colors.btnToggledPanelText"
+              :label="$t('settings.style.advanced_colors.panel_header')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnToggledPanelText" />
+            <ColorInput
+              v-model="btnToggledTopBarTextColorLocal"
+              name="btnToggledTopBarTextColor"
+              :fallback="previewTheme.colors.btnToggledTopBarText"
+              :label="$t('settings.style.advanced_colors.top_bar')"
+            />
+            <ContrastRatio :contrast="previewContrast.btnToggledTopBarText" />
           </div>
           <div class="color-item">
             <h4>{{ $t('settings.style.advanced_colors.tabs') }}</h4>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 4efa41c3..771805e9 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -472,6 +472,7 @@
         "selectedPost": "Selected post",
         "selectedMenu": "Selected menu item",
         "disabled": "Disabled",
+        "toggled": "Toggled",
         "tabs": "Tabs"
       },
       "radii": {
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 533145d4..b9a23ad7 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -114,10 +114,7 @@ export const generateColors = (themeData) => {
     ? colors2to3(themeData.colors || themeData)
     : themeData.colors || themeData
 
-  const isLightOnDark = convert(sourceColors.bg).hsl.l < convert(sourceColors.text).hsl.l
-  const mod = isLightOnDark ? 1 : -1
-
-  const { colors, opacity } = getColors(sourceColors, themeData.opacity || {}, mod)
+  const { colors, opacity } = getColors(sourceColors, themeData.opacity || {})
 
   const htmlColors = Object.entries(colors)
     .reduce((acc, [k, v]) => {
@@ -381,25 +378,17 @@ export const getThemes = () => {
 }
 export const colors2to3 = (colors) => {
   return Object.entries(colors).reduce((acc, [slotName, color]) => {
-    const btnStates = ['', 'Pressed', 'Disabled', 'Toggled']
     const btnPositions = ['', 'Panel', 'TopBar']
     switch (slotName) {
       case 'lightBg':
         return { ...acc, highlight: color }
-      case 'btn':
-        return {
-          ...acc,
-          ...btnStates.reduce((stateAcc, state) => ({ ...stateAcc, ['btn' + state]: color }), {})
-        }
       case 'btnText':
         return {
           ...acc,
           ...btnPositions
-            .map(position => btnStates.map(state => state + position))
-            .flat()
             .reduce(
-              (statePositionAcc, statePosition) =>
-                ({ ...statePositionAcc, ['btn' + statePosition + 'Text']: color })
+              (statePositionAcc, position) =>
+                ({ ...statePositionAcc, ['btn' + position + 'Text']: color })
               , {}
             )
         }
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index 354009ee..98fba5ef 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -85,6 +85,8 @@ export const SLOT_INHERITANCE = {
   },
   text: {
     depends: [],
+    layer: 'bg',
+    opacity: null,
     priority: 1
   },
   underlay: {
@@ -422,6 +424,7 @@ export const SLOT_INHERITANCE = {
   // Buttons
   btn: {
     depends: ['fg'],
+    variant: 'btn',
     opacity: 'btn'
   },
   btnText: {
@@ -430,20 +433,23 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
   btnPanelText: {
-    depends: ['panelText'],
+    depends: ['btnText'],
     layer: 'btnPanel',
     variant: 'btn',
     textColor: true
   },
   btnTopBarText: {
-    depends: ['topBarText'],
+    depends: ['btnText'],
     layer: 'btnTopBar',
     variant: 'btn',
     textColor: true
   },
 
   // Buttons: pressed
-  btnPressed: '--btn',
+  btnPressed: {
+    depends: ['btn'],
+    layer: 'btn'
+  },
   btnPressedText: {
     depends: ['btnText'],
     layer: 'btn',
@@ -451,7 +457,8 @@ export const SLOT_INHERITANCE = {
     textColor: true
   },
   btnPressedPanel: {
-    depends: ['btnPressed']
+    depends: ['btnPressed'],
+    layer: 'btn'
   },
   btnPressedPanelText: {
     depends: ['btnPanelText'],
@@ -469,6 +476,7 @@ export const SLOT_INHERITANCE = {
   // Buttons: toggled
   btnToggled: {
     depends: ['btn'],
+    layer: 'btn',
     color: (mod, btn) => brightness(mod * 20, btn).rgb
   },
   btnToggledText: {
@@ -496,25 +504,22 @@ export const SLOT_INHERITANCE = {
     color: (mod, btn, bg) => alphaBlend(btn, 0.5, bg)
   },
   btnDisabledText: {
-    depends: ['btnText'],
+    depends: ['btnText', 'btnDisabled'],
     layer: 'btn',
     variant: 'btnDisabled',
-    textColor: true,
-    color: (mod, text) => brightness(mod * -60, text).rgb
+    color: (mod, text, btn) => alphaBlend(text, 0.5, btn)
   },
   btnDisabledPanelText: {
-    depends: ['btnPanelText'],
+    depends: ['btnPanelText', 'btnDisabled'],
     layer: 'btnPanel',
     variant: 'btnDisabled',
-    textColor: true,
-    color: (mod, text) => brightness(mod * -60, text).rgb
+    color: (mod, text, btn) => alphaBlend(text, 0.5, btn)
   },
   btnDisabledTopBarText: {
-    depends: ['btnTopBarText'],
+    depends: ['btnTopBarText', 'btnDisabled'],
     layer: 'btnTopBar',
     variant: 'btnDisabled',
-    textColor: true,
-    color: (mod, text) => brightness(mod * -60, text).rgb
+    color: (mod, text, btn) => alphaBlend(text, 0.5, btn)
   },
 
   // Input fields
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 49b99148..e9ed3781 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -255,14 +255,17 @@ export const computeDynamicColor = (sourceColor, getColor, mod) => {
 }
 
 /**
- * THE function you want to use. Takes provided colors and opacities, mod
+ * THE function you want to use. Takes provided colors and opacities
  * value and uses inheritance data to figure out color needed for the slot.
  */
-export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => {
+export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => {
   const value = SLOT_INHERITANCE[key]
   const isObject = typeof value === 'object'
   const isString = typeof value === 'string'
   const sourceColor = sourceColors[key]
+  const variant = value.variant || value.layer || 'bg'
+  const isLightOnDark = relativeLuminance(colors[variant] || sourceColors[variant]) < 0.5
+  const mod = isLightOnDark ? 1 : -1
   let outputColor = null
   if (sourceColor) {
     // Color is defined in source color
@@ -318,7 +321,7 @@ export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.redu
           opacity
         )
       )
-      const isLightOnDark = relativeLuminance(bg) > 127
+      const isLightOnDark = relativeLuminance(bg) > 0.5
       const mod = isLightOnDark ? 1 : -1
 
       if (value.textColor === 'bw') {

From 36e19128bf958559437144b26a3e71f30c9b3377 Mon Sep 17 00:00:00 2001
From: xenofem <xenofem@xeno.science>
Date: Sat, 8 Feb 2020 13:15:09 -0500
Subject: [PATCH 219/483] Indicate whether collapsed statuses contain gallery
 media or link preview cards

---
 src/components/status/status.vue | 12 +++++++++++-
 static/fontello.json             |  8 +++++++-
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index d5739304..b9e3fa1d 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -277,7 +277,17 @@
               href="#"
               class="cw-status-hider"
               @click.prevent="toggleShowMore"
-            >{{ $t("general.show_more") }}</a>
+            >
+              {{ $t("general.show_more") }}
+              <span
+                v-if="galleryAttachments.length > 0"
+                class="icon-picture"
+              />
+              <span
+                v-if="status.card"
+                class="icon-link"
+              />
+            </a>
             <a
               v-if="showingMore"
               href="#"
diff --git a/static/fontello.json b/static/fontello.json
index 829241b5..5a7086a2 100755
--- a/static/fontello.json
+++ b/static/fontello.json
@@ -339,6 +339,12 @@
       "css": "arrow-curved",
       "code": 59426,
       "src": "iconic"
+    },
+    {
+      "uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1",
+      "css": "link",
+      "code": 59427,
+      "src": "fontawesome"
     }
   ]
-}
\ No newline at end of file
+}

From e36c39be192ce98fe20e11ea8e2d4fec1b3d50fd Mon Sep 17 00:00:00 2001
From: xenofem <xenofem@xeno.science>
Date: Sat, 8 Feb 2020 16:01:01 -0500
Subject: [PATCH 220/483] Include non-gallery attachments and distinguish
 between images and videos

---
 src/components/status/status.js  | 10 ++++++++++
 src/components/status/status.vue |  6 +++++-
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/components/status/status.js b/src/components/status/status.js
index 81b57667..fc5956ec 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -256,6 +256,16 @@ const Status = {
         file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
       )
     },
+    hasImageAttachments () {
+      return this.status.attachments.some(
+        file => fileType.fileType(file.mimetype) === 'image'
+      )
+    },
+    hasVideoAttachments () {
+      return this.status.attachments.some(
+        file => fileType.fileType(file.mimetype) === 'video'
+      )
+    },
     maxThumbnails () {
       return this.mergedConfig.maxThumbnails
     },
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index b9e3fa1d..0a82dcbe 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -280,9 +280,13 @@
             >
               {{ $t("general.show_more") }}
               <span
-                v-if="galleryAttachments.length > 0"
+                v-if="hasImageAttachments"
                 class="icon-picture"
               />
+              <span
+                v-if="hasVideoAttachments"
+                class="icon-video"
+              />
               <span
                 v-if="status.card"
                 class="icon-link"

From 44dea9f3646a5c27083dfe6cd6b1522e11c7dc69 Mon Sep 17 00:00:00 2001
From: xenofem <xenofem@xeno.science>
Date: Sun, 9 Feb 2020 17:25:24 -0500
Subject: [PATCH 221/483] Allow emoji suggestions based on a match anywhere in
 the emoji name, but improve sorting

---
 src/components/emoji_input/suggestor.js | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
index aec5c39d..9e437ccc 100644
--- a/src/components/emoji_input/suggestor.js
+++ b/src/components/emoji_input/suggestor.js
@@ -29,17 +29,21 @@ export default data => input => {
 export const suggestEmoji = emojis => input => {
   const noPrefix = input.toLowerCase().substr(1)
   return emojis
-    .filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix))
+    .filter(({ displayText }) => displayText.toLowerCase().match(noPrefix))
     .sort((a, b) => {
       let aScore = 0
       let bScore = 0
 
-      // Make custom emojis a priority
-      aScore += a.imageUrl ? 10 : 0
-      bScore += b.imageUrl ? 10 : 0
+      // Prioritize emoji that start with the input string
+      aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
+      bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
 
-      // Sort alphabetically
-      const alphabetically = a.displayText > b.displayText ? 1 : -1
+      // Sort by length
+      aScore -= a.displayText.length
+      bScore -= b.displayText.length
+
+      // Break ties alphabetically
+      const alphabetically = a.displayText > b.displayText ? 0.5 : -0.5
 
       return bScore - aScore + alphabetically
     })

From dafced3a864c80f69316417082aaa08e10adee49 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo <hakabahitoyo@yahoo.co.jp>
Date: Mon, 10 Feb 2020 08:04:58 +0000
Subject: [PATCH 222/483] MRF Keyword Policy Disclosure

---
 CHANGELOG.md                                  |  1 +
 .../mrf_transparency_panel.js                 | 10 ++++-
 .../mrf_transparency_panel.vue                | 43 +++++++++++++++++++
 src/i18n/en.json                              | 11 ++++-
 4 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index abefd958..c011835c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Pleroma AMOLED dark theme
 - User level domain mutes, under User Settings -> Mutes
 - Emoji reactions for statuses
+- MRF keyword policy disclosure
 ### Changed
 - Captcha now resets on failed registrations
 - Notifications column now cleans itself up to optimize performance when tab is left open for a long time
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
index 6a1baec8..a0b600d2 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
@@ -11,7 +11,10 @@ const MRFTransparencyPanel = {
       rejectInstances: state => get(state, 'instance.federationPolicy.mrf_simple.reject', []),
       ftlRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.federated_timeline_removal', []),
       mediaNsfwInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_nsfw', []),
-      mediaRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_removal', [])
+      mediaRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_removal', []),
+      keywordsFtlRemoval: state => get(state, 'instance.federationPolicy.mrf_keyword.federated_timeline_removal', []),
+      keywordsReject: state => get(state, 'instance.federationPolicy.mrf_keyword.reject', []),
+      keywordsReplace: state => get(state, 'instance.federationPolicy.mrf_keyword.replace', [])
     }),
     hasInstanceSpecificPolicies () {
       return this.quarantineInstances.length ||
@@ -20,6 +23,11 @@ const MRFTransparencyPanel = {
         this.ftlRemovalInstances.length ||
         this.mediaNsfwInstances.length ||
         this.mediaRemovalInstances.length
+    },
+    hasKeywordPolicies () {
+      return this.keywordsFtlRemoval.length ||
+        this.keywordsReject.length ||
+        this.keywordsReplace.length
     }
   }
 }
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
index d6495dc6..8038e587 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
@@ -109,6 +109,49 @@
               />
             </ul>
           </div>
+
+          <h2 v-if="hasKeywordPolicies">
+            {{ $t("about.mrf.keyword.keyword_policies") }}
+          </h2>
+
+          <div v-if="keywordsFtlRemoval.length">
+            <h4>{{ $t("about.mrf.keyword.ftl_removal") }}</h4>
+
+            <ul>
+              <li
+                v-for="keyword in keywordsFtlRemoval"
+                :key="keyword"
+                v-text="keyword"
+              />
+            </ul>
+          </div>
+
+          <div v-if="keywordsReject.length">
+            <h4>{{ $t("about.mrf.keyword.reject") }}</h4>
+
+            <ul>
+              <li
+                v-for="keyword in keywordsReject"
+                :key="keyword"
+                v-text="keyword"
+              />
+            </ul>
+          </div>
+
+          <div v-if="keywordsReplace.length">
+            <h4>{{ $t("about.mrf.keyword.replace") }}</h4>
+
+            <ul>
+              <li
+                v-for="keyword in keywordsReplace"
+                :key="keyword"
+              >
+                {{ keyword.pattern }}
+                {{ $t("about.mrf.keyword.is_replaced_by") }}
+                {{ keyword.replacement }}
+              </li>
+            </ul>
+          </div>
         </div>
       </div>
     </div>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index db2ce54d..ef841bbb 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -16,7 +16,16 @@
     "mrf_policy_simple_media_removal": "Media Removal",
     "mrf_policy_simple_media_removal_desc": "This instance removes media from posts on the following instances:",
     "mrf_policy_simple_media_nsfw": "Media Force-set As Sensitive",
-    "mrf_policy_simple_media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
+    "mrf_policy_simple_media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:",
+    "mrf": {
+      "keyword": {
+        "keyword_policies": "Keyword Policies",
+        "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+        "reject": "Reject",
+        "replace": "Replace",
+        "is_replaced_by": "→"
+      }
+    }
   },
   "chat": {
     "title": "Chat"

From 02864bc07b2ab2f08232ba1c4c27079454dc87ef Mon Sep 17 00:00:00 2001
From: xenofem <xenofem@xeno.science>
Date: Mon, 10 Feb 2020 09:32:07 -0500
Subject: [PATCH 223/483] Prioritize custom emoji a lot and boost exact matches
 to the top

---
 src/components/emoji_input/suggestor.js | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
index 9e437ccc..15a71eff 100644
--- a/src/components/emoji_input/suggestor.js
+++ b/src/components/emoji_input/suggestor.js
@@ -34,7 +34,15 @@ export const suggestEmoji = emojis => input => {
       let aScore = 0
       let bScore = 0
 
-      // Prioritize emoji that start with the input string
+      // An exact match always wins
+      aScore += a.displayText.toLowerCase() === noPrefix ? 200 : 0
+      bScore += b.displayText.toLowerCase() === noPrefix ? 200 : 0
+
+      // Prioritize custom emoji a lot
+      aScore += a.imageUrl ? 100 : 0
+      bScore += b.imageUrl ? 100 : 0
+
+      // Prioritize prefix matches somewhat
       aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
       bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
 

From 38c34b8b51136e3d73506cf5347aa57541b36653 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 11 Feb 2020 00:34:30 +0200
Subject: [PATCH 224/483] fixed eslint, made `mod` work properly depending on
 context including in shadows

---
 src/components/style_switcher/preview.vue     |  2 +-
 src/services/style_setter/style_setter.js     | 22 +++++--
 src/services/theme_data/theme_data.service.js | 59 ++++++++++---------
 3 files changed, 49 insertions(+), 34 deletions(-)

diff --git a/src/components/style_switcher/preview.vue b/src/components/style_switcher/preview.vue
index 5b8c3839..8afbb123 100644
--- a/src/components/style_switcher/preview.vue
+++ b/src/components/style_switcher/preview.vue
@@ -1,6 +1,6 @@
 <template>
   <div class="preview-container">
-    <div class="underlay underlay-preview"/>
+    <div class="underlay underlay-preview" />
     <div class="panel dummy">
       <div class="panel-heading">
         <div class="title">
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index b9a23ad7..32102152 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,6 +1,6 @@
 import { times } from 'lodash'
 import { convert } from 'chromatism'
-import { rgb2hex, hex2rgb, rgba2css, getCssColor } from '../color_convert/color_convert.js'
+import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js'
 import { getColors, computeDynamicColor } from '../theme_data/theme_data.service.js'
 
 // While this is not used anymore right now, I left it in if we want to do custom
@@ -133,8 +133,7 @@ export const generateColors = (themeData) => {
     theme: {
       colors: htmlColors.solid,
       opacity
-    },
-    mod
+    }
   }
 }
 
@@ -281,7 +280,18 @@ export const DEFAULT_SHADOWS = {
     alpha: 1
   }]
 }
-export const generateShadows = (input, colors, mod) => {
+export const generateShadows = (input, colors) => {
+  // TODO this is a small hack for `mod` to work with shadows
+  // this is used to get the "context" of shadow, i.e. for `mod` properly depend on background color of element
+  const hackContextDict = {
+    button: 'btn',
+    panel: 'bg',
+    top: 'topBar',
+    popup: 'popover',
+    avatar: 'bg',
+    panelHeader: 'panel',
+    input: 'input'
+  }
   const inputShadows = input.shadows && !input.themeEngineVersion
     ? shadows2to3(input.shadows)
     : input.shadows || {}
@@ -289,6 +299,10 @@ export const generateShadows = (input, colors, mod) => {
     ...DEFAULT_SHADOWS,
     ...inputShadows
   }).reduce((shadowsAcc, [slotName, shadowDefs]) => {
+    const slotFirstWord = slotName.replace(/[A-Z].*$/, '')
+    const colorSlotName = hackContextDict[slotFirstWord]
+    const isLightOnDark = relativeLuminance(convert(colors[colorSlotName]).rgb) < 0.5
+    const mod = isLightOnDark ? 1 : -1
     const newShadow = shadowDefs.reduce((shadowAcc, def) => [
       ...shadowAcc,
       {
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index e9ed3781..7479a55e 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -259,13 +259,34 @@ export const computeDynamicColor = (sourceColor, getColor, mod) => {
  * value and uses inheritance data to figure out color needed for the slot.
  */
 export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => {
-  const value = SLOT_INHERITANCE[key]
-  const isObject = typeof value === 'object'
-  const isString = typeof value === 'string'
   const sourceColor = sourceColors[key]
-  const variant = value.variant || value.layer || 'bg'
-  const isLightOnDark = relativeLuminance(colors[variant] || sourceColors[variant]) < 0.5
+  const value = expandSlotValue(SLOT_INHERITANCE[key])
+  const deps = getDependencies(key, SLOT_INHERITANCE)
+  const isTextColor = !!value.textColor
+  const variant = value.variant || value.layer
+
+  let backgroundColor = null
+
+  if (isTextColor) {
+    backgroundColor = alphaBlendLayers(
+      { ...(colors[deps[0]] || convert(sourceColors[key] || '#FF00FF').rgb) },
+      getLayers(
+        getLayerSlot(key) || 'bg',
+        variant || 'bg',
+        getOpacitySlot(variant),
+        colors,
+        opacity
+      )
+    )
+  } else if (variant && variant !== key) {
+    backgroundColor = colors[variant] || convert(sourceColors[variant]).rgb
+  } else {
+    backgroundColor = colors.bg || convert(sourceColors.bg)
+  }
+
+  const isLightOnDark = relativeLuminance(backgroundColor) < 0.5
   const mod = isLightOnDark ? 1 : -1
+
   let outputColor = null
   if (sourceColor) {
     // Color is defined in source color
@@ -280,7 +301,6 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
         opacity
       ).slice(0, -1)
       targetColor = {
-        // TODO: try to use alpha-blended background here
         ...alphaBlendLayers(
           convert('#FF00FF').rgb,
           layers
@@ -297,43 +317,24 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
       targetColor = convert(targetColor).rgb
     }
     outputColor = { ...targetColor }
-  } else if (isString && value.startsWith('#')) {
-    // slot: '#000000' shorthand
-    outputColor = convert(value).rgb
-  } else if (isObject && value.default) {
+  } else if (value.default) {
     // same as above except in object form
     outputColor = convert(value.default).rgb
   } else {
     // calculate color
     const defaultColorFunc = (mod, dep) => ({ ...dep })
-    const deps = getDependencies(key, SLOT_INHERITANCE)
-    const colorFunc = (isObject && value.color) || defaultColorFunc
+    const colorFunc = value.color || defaultColorFunc
 
     if (value.textColor) {
-      // textColor case
-      const bg = alphaBlendLayers(
-        { ...colors[deps[0]] },
-        getLayers(
-          value.layer,
-          value.variant || value.layer,
-          getOpacitySlot(value.variant || value.layer),
-          colors,
-          opacity
-        )
-      )
-      const isLightOnDark = relativeLuminance(bg) > 0.5
-      const mod = isLightOnDark ? 1 : -1
-
       if (value.textColor === 'bw') {
-        outputColor = contrastRatio(bg).rgb
+        outputColor = contrastRatio(backgroundColor).rgb
       } else {
         let color = { ...colors[deps[0]] }
         if (value.color) {
           color = colorFunc(mod, ...deps.map((dep) => ({ ...colors[dep] })))
         }
-
         outputColor = getTextColor(
-          bg,
+          backgroundColor,
           { ...color },
           value.textColor === 'preserve'
         )

From b4278ee517522f559751ab005a9521aa17ee07b6 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 11 Feb 2020 00:52:25 +0200
Subject: [PATCH 225/483] fix warning stylings

---
 src/components/style_switcher/style_switcher.scss | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/components/style_switcher/style_switcher.scss b/src/components/style_switcher/style_switcher.scss
index 71d0f05e..d2a40d13 100644
--- a/src/components/style_switcher/style_switcher.scss
+++ b/src/components/style_switcher/style_switcher.scss
@@ -3,6 +3,12 @@
   .theme-warning {
     display: flex;
     align-items: baseline;
+    margin-bottom: .5em;
+    .buttons {
+      .btn {
+        margin-bottom: .5em;
+      }
+    }
   }
   .preset-switcher {
     margin-right: 1em;

From e6f148b8a306bc24a2fa47da468c4e29b1fcb018 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 11 Feb 2020 00:56:51 +0200
Subject: [PATCH 226/483] removed base16-related code

---
 src/services/style_setter/style_setter.js | 51 ------------------
 static/css/base16-3024.css                | 33 ------------
 static/css/base16-apathy.css              | 33 ------------
 static/css/base16-ashes.css               | 33 ------------
 static/css/base16-atelier-cave.css        | 33 ------------
 static/css/base16-atelier-dune.css        | 33 ------------
 static/css/base16-atelier-estuary.css     | 33 ------------
 static/css/base16-atelier-forest.css      | 33 ------------
 static/css/base16-atelier-heath.css       | 33 ------------
 static/css/base16-atelier-lakeside.css    | 33 ------------
 static/css/base16-atelier-plateau.css     | 33 ------------
 static/css/base16-atelier-savanna.css     | 33 ------------
 static/css/base16-atelier-seaside.css     | 33 ------------
 static/css/base16-atelier-sulphurpool.css | 33 ------------
 static/css/base16-bespin.css              | 33 ------------
 static/css/base16-brewer.css              | 33 ------------
 static/css/base16-bright.css              | 33 ------------
 static/css/base16-chalk.css               | 33 ------------
 static/css/base16-codeschool.css          | 33 ------------
 static/css/base16-darktooth.css           | 33 ------------
 static/css/base16-default-dark.css        | 33 ------------
 static/css/base16-default-light.css       | 33 ------------
 static/css/base16-eighties.css            | 33 ------------
 static/css/base16-embers.css              | 33 ------------
 static/css/base16-flat.css                | 33 ------------
 static/css/base16-github.css              | 33 ------------
 static/css/base16-google-dark.css         | 33 ------------
 static/css/base16-google-light.css        | 33 ------------
 static/css/base16-grayscale-dark.css      | 33 ------------
 static/css/base16-grayscale-light.css     | 33 ------------
 static/css/base16-green-screen.css        | 33 ------------
 static/css/base16-harmonic16-dark.css     | 33 ------------
 static/css/base16-harmonic16-light.css    | 33 ------------
 static/css/base16-hopscotch.css           | 33 ------------
 static/css/base16-ir-black.css            | 33 ------------
 static/css/base16-isotope.css             | 33 ------------
 static/css/base16-london-tube.css         | 33 ------------
 static/css/base16-macintosh.css           | 33 ------------
 static/css/base16-marrakesh.css           | 33 ------------
 static/css/base16-materia.css             | 33 ------------
 static/css/base16-mexico-light.css        | 33 ------------
 static/css/base16-mocha.css               | 33 ------------
 static/css/base16-monokai.css             | 33 ------------
 static/css/base16-ocean.css               | 33 ------------
 static/css/base16-oceanicnext.css         | 33 ------------
 static/css/base16-paraiso.css             | 33 ------------
 static/css/base16-phd.css                 | 33 ------------
 static/css/base16-pico.css                | 33 ------------
 static/css/base16-pleroma-dark.css        | 33 ------------
 static/css/base16-pleroma-light.css       | 33 ------------
 static/css/base16-pop.css                 | 33 ------------
 static/css/base16-railscasts.css          | 33 ------------
 static/css/base16-seti-ui.css             | 33 ------------
 static/css/base16-shapeshifter.css        | 33 ------------
 static/css/base16-solar-flare.css         | 33 ------------
 static/css/base16-solarized-dark.css      | 33 ------------
 static/css/base16-solarized-light.css     | 33 ------------
 static/css/base16-spacemacs.css           | 33 ------------
 static/css/base16-summerfruit-dark.css    | 33 ------------
 static/css/base16-summerfruit-light.css   | 33 ------------
 static/css/base16-tomorrow-night.css      | 33 ------------
 static/css/base16-tomorrow.css            | 33 ------------
 static/css/base16-twilight.css            | 33 ------------
 static/css/base16-unikitty-dark.css       | 33 ------------
 static/css/base16-unikitty-light.css      | 33 ------------
 static/css/themes.json                    | 66 -----------------------
 66 files changed, 2229 deletions(-)
 delete mode 100644 static/css/base16-3024.css
 delete mode 100644 static/css/base16-apathy.css
 delete mode 100644 static/css/base16-ashes.css
 delete mode 100644 static/css/base16-atelier-cave.css
 delete mode 100644 static/css/base16-atelier-dune.css
 delete mode 100644 static/css/base16-atelier-estuary.css
 delete mode 100644 static/css/base16-atelier-forest.css
 delete mode 100644 static/css/base16-atelier-heath.css
 delete mode 100644 static/css/base16-atelier-lakeside.css
 delete mode 100644 static/css/base16-atelier-plateau.css
 delete mode 100644 static/css/base16-atelier-savanna.css
 delete mode 100644 static/css/base16-atelier-seaside.css
 delete mode 100644 static/css/base16-atelier-sulphurpool.css
 delete mode 100644 static/css/base16-bespin.css
 delete mode 100644 static/css/base16-brewer.css
 delete mode 100644 static/css/base16-bright.css
 delete mode 100644 static/css/base16-chalk.css
 delete mode 100644 static/css/base16-codeschool.css
 delete mode 100644 static/css/base16-darktooth.css
 delete mode 100644 static/css/base16-default-dark.css
 delete mode 100644 static/css/base16-default-light.css
 delete mode 100644 static/css/base16-eighties.css
 delete mode 100644 static/css/base16-embers.css
 delete mode 100644 static/css/base16-flat.css
 delete mode 100644 static/css/base16-github.css
 delete mode 100644 static/css/base16-google-dark.css
 delete mode 100644 static/css/base16-google-light.css
 delete mode 100644 static/css/base16-grayscale-dark.css
 delete mode 100644 static/css/base16-grayscale-light.css
 delete mode 100644 static/css/base16-green-screen.css
 delete mode 100644 static/css/base16-harmonic16-dark.css
 delete mode 100644 static/css/base16-harmonic16-light.css
 delete mode 100644 static/css/base16-hopscotch.css
 delete mode 100644 static/css/base16-ir-black.css
 delete mode 100644 static/css/base16-isotope.css
 delete mode 100644 static/css/base16-london-tube.css
 delete mode 100644 static/css/base16-macintosh.css
 delete mode 100644 static/css/base16-marrakesh.css
 delete mode 100644 static/css/base16-materia.css
 delete mode 100644 static/css/base16-mexico-light.css
 delete mode 100644 static/css/base16-mocha.css
 delete mode 100644 static/css/base16-monokai.css
 delete mode 100644 static/css/base16-ocean.css
 delete mode 100644 static/css/base16-oceanicnext.css
 delete mode 100644 static/css/base16-paraiso.css
 delete mode 100644 static/css/base16-phd.css
 delete mode 100644 static/css/base16-pico.css
 delete mode 100644 static/css/base16-pleroma-dark.css
 delete mode 100644 static/css/base16-pleroma-light.css
 delete mode 100644 static/css/base16-pop.css
 delete mode 100644 static/css/base16-railscasts.css
 delete mode 100644 static/css/base16-seti-ui.css
 delete mode 100644 static/css/base16-shapeshifter.css
 delete mode 100644 static/css/base16-solar-flare.css
 delete mode 100644 static/css/base16-solarized-dark.css
 delete mode 100644 static/css/base16-solarized-light.css
 delete mode 100644 static/css/base16-spacemacs.css
 delete mode 100644 static/css/base16-summerfruit-dark.css
 delete mode 100644 static/css/base16-summerfruit-light.css
 delete mode 100644 static/css/base16-tomorrow-night.css
 delete mode 100644 static/css/base16-tomorrow.css
 delete mode 100644 static/css/base16-twilight.css
 delete mode 100644 static/css/base16-unikitty-dark.css
 delete mode 100644 static/css/base16-unikitty-light.css
 delete mode 100644 static/css/themes.json

diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 32102152..57e3057a 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,58 +1,7 @@
-import { times } from 'lodash'
 import { convert } from 'chromatism'
 import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js'
 import { getColors, computeDynamicColor } from '../theme_data/theme_data.service.js'
 
-// While this is not used anymore right now, I left it in if we want to do custom
-// styles that aren't just colors, so user can pick from a few different distinct
-// styles as well as set their own colors in the future.
-
-export const setStyle = (href) => {
-  /***
-      What's going on here?
-      I want to make it easy for admins to style this application. To have
-      a good set of default themes, I chose the system from base16
-      (https://chriskempson.github.io/base16/) to style all elements. They
-      all have the base00..0F classes. So the only thing an admin needs to
-      do to style Pleroma is to change these colors in that one css file.
-      Some default things (body text color, link color) need to be set dy-
-      namically, so this is done here by waiting for the stylesheet to be
-      loaded and then creating an element with the respective classes.
-
-      It is a bit weird, but should make life for admins somewhat easier.
-  ***/
-  const head = document.head
-  const body = document.body
-  body.classList.add('hidden')
-  const cssEl = document.createElement('link')
-  cssEl.setAttribute('rel', 'stylesheet')
-  cssEl.setAttribute('href', href)
-  head.appendChild(cssEl)
-
-  const setDynamic = () => {
-    const baseEl = document.createElement('div')
-    body.appendChild(baseEl)
-
-    let colors = {}
-    times(16, (n) => {
-      const name = `base0${n.toString(16).toUpperCase()}`
-      baseEl.setAttribute('class', name)
-      const color = window.getComputedStyle(baseEl).getPropertyValue('color')
-      colors[name] = color
-    })
-
-    body.removeChild(baseEl)
-
-    const styleEl = document.createElement('style')
-    head.appendChild(styleEl)
-    // const styleSheet = styleEl.sheet
-
-    body.classList.remove('hidden')
-  }
-
-  cssEl.addEventListener('load', setDynamic)
-}
-
 export const applyTheme = (input) => {
   const { rules } = generatePreset(input)
   const head = document.head
diff --git a/static/css/base16-3024.css b/static/css/base16-3024.css
deleted file mode 100644
index 91859e27..00000000
--- a/static/css/base16-3024.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #090300; }
-.base01-background { background-color: #3a3432; }
-.base02-background { background-color: #4a4543; }
-.base03-background { background-color: #5c5855; }
-.base04-background { background-color: #807d7c; }
-.base05-background { background-color: #a5a2a2; }
-.base06-background { background-color: #d6d5d4; }
-.base07-background { background-color: #f7f7f7; }
-.base08-background { background-color: #db2d20; }
-.base09-background { background-color: #e8bbd0; }
-.base0A-background { background-color: #fded02; }
-.base0B-background { background-color: #01a252; }
-.base0C-background { background-color: #b5e4f4; }
-.base0D-background { background-color: #01a0e4; }
-.base0E-background { background-color: #a16a94; }
-.base0F-background { background-color: #cdab53; }
-
-.base00 { color: #090300; }
-.base01 { color: #3a3432; }
-.base02 { color: #4a4543; }
-.base03 { color: #5c5855; }
-.base04 { color: #807d7c; }
-.base05 { color: #a5a2a2; }
-.base06 { color: #d6d5d4; }
-.base07 { color: #f7f7f7; }
-.base08 { color: #db2d20; }
-.base09 { color: #e8bbd0; }
-.base0A { color: #fded02; }
-.base0B { color: #01a252; }
-.base0C { color: #b5e4f4; }
-.base0D { color: #01a0e4; }
-.base0E { color: #a16a94; }
-.base0F { color: #cdab53; }
diff --git a/static/css/base16-apathy.css b/static/css/base16-apathy.css
deleted file mode 100644
index 2e99ba1f..00000000
--- a/static/css/base16-apathy.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #031A16; }
-.base01-background { background-color: #0B342D; }
-.base02-background { background-color: #184E45; }
-.base03-background { background-color: #2B685E; }
-.base04-background { background-color: #5F9C92; }
-.base05-background { background-color: #81B5AC; }
-.base06-background { background-color: #A7CEC8; }
-.base07-background { background-color: #D2E7E4; }
-.base08-background { background-color: #3E9688; }
-.base09-background { background-color: #3E7996; }
-.base0A-background { background-color: #3E4C96; }
-.base0B-background { background-color: #883E96; }
-.base0C-background { background-color: #963E4C; }
-.base0D-background { background-color: #96883E; }
-.base0E-background { background-color: #4C963E; }
-.base0F-background { background-color: #3E965B; }
-
-.base00 { color: #031A16; }
-.base01 { color: #0B342D; }
-.base02 { color: #184E45; }
-.base03 { color: #2B685E; }
-.base04 { color: #5F9C92; }
-.base05 { color: #81B5AC; }
-.base06 { color: #A7CEC8; }
-.base07 { color: #D2E7E4; }
-.base08 { color: #3E9688; }
-.base09 { color: #3E7996; }
-.base0A { color: #3E4C96; }
-.base0B { color: #883E96; }
-.base0C { color: #963E4C; }
-.base0D { color: #96883E; }
-.base0E { color: #4C963E; }
-.base0F { color: #3E965B; }
diff --git a/static/css/base16-ashes.css b/static/css/base16-ashes.css
deleted file mode 100644
index d10e1918..00000000
--- a/static/css/base16-ashes.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #1C2023; }
-.base01-background { background-color: #393F45; }
-.base02-background { background-color: #565E65; }
-.base03-background { background-color: #747C84; }
-.base04-background { background-color: #ADB3BA; }
-.base05-background { background-color: #C7CCD1; }
-.base06-background { background-color: #DFE2E5; }
-.base07-background { background-color: #F3F4F5; }
-.base08-background { background-color: #C7AE95; }
-.base09-background { background-color: #C7C795; }
-.base0A-background { background-color: #AEC795; }
-.base0B-background { background-color: #95C7AE; }
-.base0C-background { background-color: #95AEC7; }
-.base0D-background { background-color: #AE95C7; }
-.base0E-background { background-color: #C795AE; }
-.base0F-background { background-color: #C79595; }
-
-.base00 { color: #1C2023; }
-.base01 { color: #393F45; }
-.base02 { color: #565E65; }
-.base03 { color: #747C84; }
-.base04 { color: #ADB3BA; }
-.base05 { color: #C7CCD1; }
-.base06 { color: #DFE2E5; }
-.base07 { color: #F3F4F5; }
-.base08 { color: #C7AE95; }
-.base09 { color: #C7C795; }
-.base0A { color: #AEC795; }
-.base0B { color: #95C7AE; }
-.base0C { color: #95AEC7; }
-.base0D { color: #AE95C7; }
-.base0E { color: #C795AE; }
-.base0F { color: #C79595; }
diff --git a/static/css/base16-atelier-cave.css b/static/css/base16-atelier-cave.css
deleted file mode 100644
index 5ac17f97..00000000
--- a/static/css/base16-atelier-cave.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #19171c; }
-.base01-background { background-color: #26232a; }
-.base02-background { background-color: #585260; }
-.base03-background { background-color: #655f6d; }
-.base04-background { background-color: #7e7887; }
-.base05-background { background-color: #8b8792; }
-.base06-background { background-color: #e2dfe7; }
-.base07-background { background-color: #efecf4; }
-.base08-background { background-color: #be4678; }
-.base09-background { background-color: #aa573c; }
-.base0A-background { background-color: #a06e3b; }
-.base0B-background { background-color: #2a9292; }
-.base0C-background { background-color: #398bc6; }
-.base0D-background { background-color: #576ddb; }
-.base0E-background { background-color: #955ae7; }
-.base0F-background { background-color: #bf40bf; }
-
-.base00 { color: #19171c; }
-.base01 { color: #26232a; }
-.base02 { color: #585260; }
-.base03 { color: #655f6d; }
-.base04 { color: #7e7887; }
-.base05 { color: #8b8792; }
-.base06 { color: #e2dfe7; }
-.base07 { color: #efecf4; }
-.base08 { color: #be4678; }
-.base09 { color: #aa573c; }
-.base0A { color: #a06e3b; }
-.base0B { color: #2a9292; }
-.base0C { color: #398bc6; }
-.base0D { color: #576ddb; }
-.base0E { color: #955ae7; }
-.base0F { color: #bf40bf; }
diff --git a/static/css/base16-atelier-dune.css b/static/css/base16-atelier-dune.css
deleted file mode 100644
index cfb2d9a1..00000000
--- a/static/css/base16-atelier-dune.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #20201d; }
-.base01-background { background-color: #292824; }
-.base02-background { background-color: #6e6b5e; }
-.base03-background { background-color: #7d7a68; }
-.base04-background { background-color: #999580; }
-.base05-background { background-color: #a6a28c; }
-.base06-background { background-color: #e8e4cf; }
-.base07-background { background-color: #fefbec; }
-.base08-background { background-color: #d73737; }
-.base09-background { background-color: #b65611; }
-.base0A-background { background-color: #ae9513; }
-.base0B-background { background-color: #60ac39; }
-.base0C-background { background-color: #1fad83; }
-.base0D-background { background-color: #6684e1; }
-.base0E-background { background-color: #b854d4; }
-.base0F-background { background-color: #d43552; }
-
-.base00 { color: #20201d; }
-.base01 { color: #292824; }
-.base02 { color: #6e6b5e; }
-.base03 { color: #7d7a68; }
-.base04 { color: #999580; }
-.base05 { color: #a6a28c; }
-.base06 { color: #e8e4cf; }
-.base07 { color: #fefbec; }
-.base08 { color: #d73737; }
-.base09 { color: #b65611; }
-.base0A { color: #ae9513; }
-.base0B { color: #60ac39; }
-.base0C { color: #1fad83; }
-.base0D { color: #6684e1; }
-.base0E { color: #b854d4; }
-.base0F { color: #d43552; }
diff --git a/static/css/base16-atelier-estuary.css b/static/css/base16-atelier-estuary.css
deleted file mode 100644
index 76d82c75..00000000
--- a/static/css/base16-atelier-estuary.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #22221b; }
-.base01-background { background-color: #302f27; }
-.base02-background { background-color: #5f5e4e; }
-.base03-background { background-color: #6c6b5a; }
-.base04-background { background-color: #878573; }
-.base05-background { background-color: #929181; }
-.base06-background { background-color: #e7e6df; }
-.base07-background { background-color: #f4f3ec; }
-.base08-background { background-color: #ba6236; }
-.base09-background { background-color: #ae7313; }
-.base0A-background { background-color: #a5980d; }
-.base0B-background { background-color: #7d9726; }
-.base0C-background { background-color: #5b9d48; }
-.base0D-background { background-color: #36a166; }
-.base0E-background { background-color: #5f9182; }
-.base0F-background { background-color: #9d6c7c; }
-
-.base00 { color: #22221b; }
-.base01 { color: #302f27; }
-.base02 { color: #5f5e4e; }
-.base03 { color: #6c6b5a; }
-.base04 { color: #878573; }
-.base05 { color: #929181; }
-.base06 { color: #e7e6df; }
-.base07 { color: #f4f3ec; }
-.base08 { color: #ba6236; }
-.base09 { color: #ae7313; }
-.base0A { color: #a5980d; }
-.base0B { color: #7d9726; }
-.base0C { color: #5b9d48; }
-.base0D { color: #36a166; }
-.base0E { color: #5f9182; }
-.base0F { color: #9d6c7c; }
diff --git a/static/css/base16-atelier-forest.css b/static/css/base16-atelier-forest.css
deleted file mode 100644
index 8108ed8f..00000000
--- a/static/css/base16-atelier-forest.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #1b1918; }
-.base01-background { background-color: #2c2421; }
-.base02-background { background-color: #68615e; }
-.base03-background { background-color: #766e6b; }
-.base04-background { background-color: #9c9491; }
-.base05-background { background-color: #a8a19f; }
-.base06-background { background-color: #e6e2e0; }
-.base07-background { background-color: #f1efee; }
-.base08-background { background-color: #f22c40; }
-.base09-background { background-color: #df5320; }
-.base0A-background { background-color: #c38418; }
-.base0B-background { background-color: #7b9726; }
-.base0C-background { background-color: #3d97b8; }
-.base0D-background { background-color: #407ee7; }
-.base0E-background { background-color: #6666ea; }
-.base0F-background { background-color: #c33ff3; }
-
-.base00 { color: #1b1918; }
-.base01 { color: #2c2421; }
-.base02 { color: #68615e; }
-.base03 { color: #766e6b; }
-.base04 { color: #9c9491; }
-.base05 { color: #a8a19f; }
-.base06 { color: #e6e2e0; }
-.base07 { color: #f1efee; }
-.base08 { color: #f22c40; }
-.base09 { color: #df5320; }
-.base0A { color: #c38418; }
-.base0B { color: #7b9726; }
-.base0C { color: #3d97b8; }
-.base0D { color: #407ee7; }
-.base0E { color: #6666ea; }
-.base0F { color: #c33ff3; }
diff --git a/static/css/base16-atelier-heath.css b/static/css/base16-atelier-heath.css
deleted file mode 100644
index 8858cb80..00000000
--- a/static/css/base16-atelier-heath.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #1b181b; }
-.base01-background { background-color: #292329; }
-.base02-background { background-color: #695d69; }
-.base03-background { background-color: #776977; }
-.base04-background { background-color: #9e8f9e; }
-.base05-background { background-color: #ab9bab; }
-.base06-background { background-color: #d8cad8; }
-.base07-background { background-color: #f7f3f7; }
-.base08-background { background-color: #ca402b; }
-.base09-background { background-color: #a65926; }
-.base0A-background { background-color: #bb8a35; }
-.base0B-background { background-color: #918b3b; }
-.base0C-background { background-color: #159393; }
-.base0D-background { background-color: #516aec; }
-.base0E-background { background-color: #7b59c0; }
-.base0F-background { background-color: #cc33cc; }
-
-.base00 { color: #1b181b; }
-.base01 { color: #292329; }
-.base02 { color: #695d69; }
-.base03 { color: #776977; }
-.base04 { color: #9e8f9e; }
-.base05 { color: #ab9bab; }
-.base06 { color: #d8cad8; }
-.base07 { color: #f7f3f7; }
-.base08 { color: #ca402b; }
-.base09 { color: #a65926; }
-.base0A { color: #bb8a35; }
-.base0B { color: #918b3b; }
-.base0C { color: #159393; }
-.base0D { color: #516aec; }
-.base0E { color: #7b59c0; }
-.base0F { color: #cc33cc; }
diff --git a/static/css/base16-atelier-lakeside.css b/static/css/base16-atelier-lakeside.css
deleted file mode 100644
index 77d44c5f..00000000
--- a/static/css/base16-atelier-lakeside.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #161b1d; }
-.base01-background { background-color: #1f292e; }
-.base02-background { background-color: #516d7b; }
-.base03-background { background-color: #5a7b8c; }
-.base04-background { background-color: #7195a8; }
-.base05-background { background-color: #7ea2b4; }
-.base06-background { background-color: #c1e4f6; }
-.base07-background { background-color: #ebf8ff; }
-.base08-background { background-color: #d22d72; }
-.base09-background { background-color: #935c25; }
-.base0A-background { background-color: #8a8a0f; }
-.base0B-background { background-color: #568c3b; }
-.base0C-background { background-color: #2d8f6f; }
-.base0D-background { background-color: #257fad; }
-.base0E-background { background-color: #6b6bb8; }
-.base0F-background { background-color: #b72dd2; }
-
-.base00 { color: #161b1d; }
-.base01 { color: #1f292e; }
-.base02 { color: #516d7b; }
-.base03 { color: #5a7b8c; }
-.base04 { color: #7195a8; }
-.base05 { color: #7ea2b4; }
-.base06 { color: #c1e4f6; }
-.base07 { color: #ebf8ff; }
-.base08 { color: #d22d72; }
-.base09 { color: #935c25; }
-.base0A { color: #8a8a0f; }
-.base0B { color: #568c3b; }
-.base0C { color: #2d8f6f; }
-.base0D { color: #257fad; }
-.base0E { color: #6b6bb8; }
-.base0F { color: #b72dd2; }
diff --git a/static/css/base16-atelier-plateau.css b/static/css/base16-atelier-plateau.css
deleted file mode 100644
index a7445030..00000000
--- a/static/css/base16-atelier-plateau.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #1b1818; }
-.base01-background { background-color: #292424; }
-.base02-background { background-color: #585050; }
-.base03-background { background-color: #655d5d; }
-.base04-background { background-color: #7e7777; }
-.base05-background { background-color: #8a8585; }
-.base06-background { background-color: #e7dfdf; }
-.base07-background { background-color: #f4ecec; }
-.base08-background { background-color: #ca4949; }
-.base09-background { background-color: #b45a3c; }
-.base0A-background { background-color: #a06e3b; }
-.base0B-background { background-color: #4b8b8b; }
-.base0C-background { background-color: #5485b6; }
-.base0D-background { background-color: #7272ca; }
-.base0E-background { background-color: #8464c4; }
-.base0F-background { background-color: #bd5187; }
-
-.base00 { color: #1b1818; }
-.base01 { color: #292424; }
-.base02 { color: #585050; }
-.base03 { color: #655d5d; }
-.base04 { color: #7e7777; }
-.base05 { color: #8a8585; }
-.base06 { color: #e7dfdf; }
-.base07 { color: #f4ecec; }
-.base08 { color: #ca4949; }
-.base09 { color: #b45a3c; }
-.base0A { color: #a06e3b; }
-.base0B { color: #4b8b8b; }
-.base0C { color: #5485b6; }
-.base0D { color: #7272ca; }
-.base0E { color: #8464c4; }
-.base0F { color: #bd5187; }
diff --git a/static/css/base16-atelier-savanna.css b/static/css/base16-atelier-savanna.css
deleted file mode 100644
index be728d07..00000000
--- a/static/css/base16-atelier-savanna.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #171c19; }
-.base01-background { background-color: #232a25; }
-.base02-background { background-color: #526057; }
-.base03-background { background-color: #5f6d64; }
-.base04-background { background-color: #78877d; }
-.base05-background { background-color: #87928a; }
-.base06-background { background-color: #dfe7e2; }
-.base07-background { background-color: #ecf4ee; }
-.base08-background { background-color: #b16139; }
-.base09-background { background-color: #9f713c; }
-.base0A-background { background-color: #a07e3b; }
-.base0B-background { background-color: #489963; }
-.base0C-background { background-color: #1c9aa0; }
-.base0D-background { background-color: #478c90; }
-.base0E-background { background-color: #55859b; }
-.base0F-background { background-color: #867469; }
-
-.base00 { color: #171c19; }
-.base01 { color: #232a25; }
-.base02 { color: #526057; }
-.base03 { color: #5f6d64; }
-.base04 { color: #78877d; }
-.base05 { color: #87928a; }
-.base06 { color: #dfe7e2; }
-.base07 { color: #ecf4ee; }
-.base08 { color: #b16139; }
-.base09 { color: #9f713c; }
-.base0A { color: #a07e3b; }
-.base0B { color: #489963; }
-.base0C { color: #1c9aa0; }
-.base0D { color: #478c90; }
-.base0E { color: #55859b; }
-.base0F { color: #867469; }
diff --git a/static/css/base16-atelier-seaside.css b/static/css/base16-atelier-seaside.css
deleted file mode 100644
index 8b391466..00000000
--- a/static/css/base16-atelier-seaside.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #131513; }
-.base01-background { background-color: #242924; }
-.base02-background { background-color: #5e6e5e; }
-.base03-background { background-color: #687d68; }
-.base04-background { background-color: #809980; }
-.base05-background { background-color: #8ca68c; }
-.base06-background { background-color: #cfe8cf; }
-.base07-background { background-color: #f4fbf4; }
-.base08-background { background-color: #e6193c; }
-.base09-background { background-color: #87711d; }
-.base0A-background { background-color: #98981b; }
-.base0B-background { background-color: #29a329; }
-.base0C-background { background-color: #1999b3; }
-.base0D-background { background-color: #3d62f5; }
-.base0E-background { background-color: #ad2bee; }
-.base0F-background { background-color: #e619c3; }
-
-.base00 { color: #131513; }
-.base01 { color: #242924; }
-.base02 { color: #5e6e5e; }
-.base03 { color: #687d68; }
-.base04 { color: #809980; }
-.base05 { color: #8ca68c; }
-.base06 { color: #cfe8cf; }
-.base07 { color: #f4fbf4; }
-.base08 { color: #e6193c; }
-.base09 { color: #87711d; }
-.base0A { color: #98981b; }
-.base0B { color: #29a329; }
-.base0C { color: #1999b3; }
-.base0D { color: #3d62f5; }
-.base0E { color: #ad2bee; }
-.base0F { color: #e619c3; }
diff --git a/static/css/base16-atelier-sulphurpool.css b/static/css/base16-atelier-sulphurpool.css
deleted file mode 100644
index fb44d6e0..00000000
--- a/static/css/base16-atelier-sulphurpool.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #202746; }
-.base01-background { background-color: #293256; }
-.base02-background { background-color: #5e6687; }
-.base03-background { background-color: #6b7394; }
-.base04-background { background-color: #898ea4; }
-.base05-background { background-color: #979db4; }
-.base06-background { background-color: #dfe2f1; }
-.base07-background { background-color: #f5f7ff; }
-.base08-background { background-color: #c94922; }
-.base09-background { background-color: #c76b29; }
-.base0A-background { background-color: #c08b30; }
-.base0B-background { background-color: #ac9739; }
-.base0C-background { background-color: #22a2c9; }
-.base0D-background { background-color: #3d8fd1; }
-.base0E-background { background-color: #6679cc; }
-.base0F-background { background-color: #9c637a; }
-
-.base00 { color: #202746; }
-.base01 { color: #293256; }
-.base02 { color: #5e6687; }
-.base03 { color: #6b7394; }
-.base04 { color: #898ea4; }
-.base05 { color: #979db4; }
-.base06 { color: #dfe2f1; }
-.base07 { color: #f5f7ff; }
-.base08 { color: #c94922; }
-.base09 { color: #c76b29; }
-.base0A { color: #c08b30; }
-.base0B { color: #ac9739; }
-.base0C { color: #22a2c9; }
-.base0D { color: #3d8fd1; }
-.base0E { color: #6679cc; }
-.base0F { color: #9c637a; }
diff --git a/static/css/base16-bespin.css b/static/css/base16-bespin.css
deleted file mode 100644
index 48a9dcf7..00000000
--- a/static/css/base16-bespin.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #28211c; }
-.base01-background { background-color: #36312e; }
-.base02-background { background-color: #5e5d5c; }
-.base03-background { background-color: #666666; }
-.base04-background { background-color: #797977; }
-.base05-background { background-color: #8a8986; }
-.base06-background { background-color: #9d9b97; }
-.base07-background { background-color: #baae9e; }
-.base08-background { background-color: #cf6a4c; }
-.base09-background { background-color: #cf7d34; }
-.base0A-background { background-color: #f9ee98; }
-.base0B-background { background-color: #54be0d; }
-.base0C-background { background-color: #afc4db; }
-.base0D-background { background-color: #5ea6ea; }
-.base0E-background { background-color: #9b859d; }
-.base0F-background { background-color: #937121; }
-
-.base00 { color: #28211c; }
-.base01 { color: #36312e; }
-.base02 { color: #5e5d5c; }
-.base03 { color: #666666; }
-.base04 { color: #797977; }
-.base05 { color: #8a8986; }
-.base06 { color: #9d9b97; }
-.base07 { color: #baae9e; }
-.base08 { color: #cf6a4c; }
-.base09 { color: #cf7d34; }
-.base0A { color: #f9ee98; }
-.base0B { color: #54be0d; }
-.base0C { color: #afc4db; }
-.base0D { color: #5ea6ea; }
-.base0E { color: #9b859d; }
-.base0F { color: #937121; }
diff --git a/static/css/base16-brewer.css b/static/css/base16-brewer.css
deleted file mode 100644
index c88f219b..00000000
--- a/static/css/base16-brewer.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #0c0d0e; }
-.base01-background { background-color: #2e2f30; }
-.base02-background { background-color: #515253; }
-.base03-background { background-color: #737475; }
-.base04-background { background-color: #959697; }
-.base05-background { background-color: #b7b8b9; }
-.base06-background { background-color: #dadbdc; }
-.base07-background { background-color: #fcfdfe; }
-.base08-background { background-color: #e31a1c; }
-.base09-background { background-color: #e6550d; }
-.base0A-background { background-color: #dca060; }
-.base0B-background { background-color: #31a354; }
-.base0C-background { background-color: #80b1d3; }
-.base0D-background { background-color: #3182bd; }
-.base0E-background { background-color: #756bb1; }
-.base0F-background { background-color: #b15928; }
-
-.base00 { color: #0c0d0e; }
-.base01 { color: #2e2f30; }
-.base02 { color: #515253; }
-.base03 { color: #737475; }
-.base04 { color: #959697; }
-.base05 { color: #b7b8b9; }
-.base06 { color: #dadbdc; }
-.base07 { color: #fcfdfe; }
-.base08 { color: #e31a1c; }
-.base09 { color: #e6550d; }
-.base0A { color: #dca060; }
-.base0B { color: #31a354; }
-.base0C { color: #80b1d3; }
-.base0D { color: #3182bd; }
-.base0E { color: #756bb1; }
-.base0F { color: #b15928; }
diff --git a/static/css/base16-bright.css b/static/css/base16-bright.css
deleted file mode 100644
index c2333b8d..00000000
--- a/static/css/base16-bright.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #000000; }
-.base01-background { background-color: #303030; }
-.base02-background { background-color: #505050; }
-.base03-background { background-color: #b0b0b0; }
-.base04-background { background-color: #d0d0d0; }
-.base05-background { background-color: #e0e0e0; }
-.base06-background { background-color: #f5f5f5; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #fb0120; }
-.base09-background { background-color: #fc6d24; }
-.base0A-background { background-color: #fda331; }
-.base0B-background { background-color: #a1c659; }
-.base0C-background { background-color: #76c7b7; }
-.base0D-background { background-color: #6fb3d2; }
-.base0E-background { background-color: #d381c3; }
-.base0F-background { background-color: #be643c; }
-
-.base00 { color: #000000; }
-.base01 { color: #303030; }
-.base02 { color: #505050; }
-.base03 { color: #b0b0b0; }
-.base04 { color: #d0d0d0; }
-.base05 { color: #e0e0e0; }
-.base06 { color: #f5f5f5; }
-.base07 { color: #ffffff; }
-.base08 { color: #fb0120; }
-.base09 { color: #fc6d24; }
-.base0A { color: #fda331; }
-.base0B { color: #a1c659; }
-.base0C { color: #76c7b7; }
-.base0D { color: #6fb3d2; }
-.base0E { color: #d381c3; }
-.base0F { color: #be643c; }
diff --git a/static/css/base16-chalk.css b/static/css/base16-chalk.css
deleted file mode 100644
index e3cb3c20..00000000
--- a/static/css/base16-chalk.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #151515; }
-.base01-background { background-color: #202020; }
-.base02-background { background-color: #303030; }
-.base03-background { background-color: #505050; }
-.base04-background { background-color: #b0b0b0; }
-.base05-background { background-color: #d0d0d0; }
-.base06-background { background-color: #e0e0e0; }
-.base07-background { background-color: #f5f5f5; }
-.base08-background { background-color: #fb9fb1; }
-.base09-background { background-color: #eda987; }
-.base0A-background { background-color: #ddb26f; }
-.base0B-background { background-color: #acc267; }
-.base0C-background { background-color: #12cfc0; }
-.base0D-background { background-color: #6fc2ef; }
-.base0E-background { background-color: #e1a3ee; }
-.base0F-background { background-color: #deaf8f; }
-
-.base00 { color: #151515; }
-.base01 { color: #202020; }
-.base02 { color: #303030; }
-.base03 { color: #505050; }
-.base04 { color: #b0b0b0; }
-.base05 { color: #d0d0d0; }
-.base06 { color: #e0e0e0; }
-.base07 { color: #f5f5f5; }
-.base08 { color: #fb9fb1; }
-.base09 { color: #eda987; }
-.base0A { color: #ddb26f; }
-.base0B { color: #acc267; }
-.base0C { color: #12cfc0; }
-.base0D { color: #6fc2ef; }
-.base0E { color: #e1a3ee; }
-.base0F { color: #deaf8f; }
diff --git a/static/css/base16-codeschool.css b/static/css/base16-codeschool.css
deleted file mode 100644
index 00194bbf..00000000
--- a/static/css/base16-codeschool.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #232c31; }
-.base01-background { background-color: #1c3657; }
-.base02-background { background-color: #2a343a; }
-.base03-background { background-color: #3f4944; }
-.base04-background { background-color: #84898c; }
-.base05-background { background-color: #9ea7a6; }
-.base06-background { background-color: #a7cfa3; }
-.base07-background { background-color: #b5d8f6; }
-.base08-background { background-color: #2a5491; }
-.base09-background { background-color: #43820d; }
-.base0A-background { background-color: #a03b1e; }
-.base0B-background { background-color: #237986; }
-.base0C-background { background-color: #b02f30; }
-.base0D-background { background-color: #484d79; }
-.base0E-background { background-color: #c59820; }
-.base0F-background { background-color: #c98344; }
-
-.base00 { color: #232c31; }
-.base01 { color: #1c3657; }
-.base02 { color: #2a343a; }
-.base03 { color: #3f4944; }
-.base04 { color: #84898c; }
-.base05 { color: #9ea7a6; }
-.base06 { color: #a7cfa3; }
-.base07 { color: #b5d8f6; }
-.base08 { color: #2a5491; }
-.base09 { color: #43820d; }
-.base0A { color: #a03b1e; }
-.base0B { color: #237986; }
-.base0C { color: #b02f30; }
-.base0D { color: #484d79; }
-.base0E { color: #c59820; }
-.base0F { color: #c98344; }
diff --git a/static/css/base16-darktooth.css b/static/css/base16-darktooth.css
deleted file mode 100644
index 53448706..00000000
--- a/static/css/base16-darktooth.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #1D2021; }
-.base01-background { background-color: #32302F; }
-.base02-background { background-color: #504945; }
-.base03-background { background-color: #665C54; }
-.base04-background { background-color: #928374; }
-.base05-background { background-color: #A89984; }
-.base06-background { background-color: #D5C4A1; }
-.base07-background { background-color: #FDF4C1; }
-.base08-background { background-color: #FB543F; }
-.base09-background { background-color: #FE8625; }
-.base0A-background { background-color: #FAC03B; }
-.base0B-background { background-color: #95C085; }
-.base0C-background { background-color: #8BA59B; }
-.base0D-background { background-color: #0D6678; }
-.base0E-background { background-color: #8F4673; }
-.base0F-background { background-color: #A87322; }
-
-.base00 { color: #1D2021; }
-.base01 { color: #32302F; }
-.base02 { color: #504945; }
-.base03 { color: #665C54; }
-.base04 { color: #928374; }
-.base05 { color: #A89984; }
-.base06 { color: #D5C4A1; }
-.base07 { color: #FDF4C1; }
-.base08 { color: #FB543F; }
-.base09 { color: #FE8625; }
-.base0A { color: #FAC03B; }
-.base0B { color: #95C085; }
-.base0C { color: #8BA59B; }
-.base0D { color: #0D6678; }
-.base0E { color: #8F4673; }
-.base0F { color: #A87322; }
diff --git a/static/css/base16-default-dark.css b/static/css/base16-default-dark.css
deleted file mode 100644
index 3cd7e860..00000000
--- a/static/css/base16-default-dark.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #181818; }
-.base01-background { background-color: #282828; }
-.base02-background { background-color: #383838; }
-.base03-background { background-color: #585858; }
-.base04-background { background-color: #b8b8b8; }
-.base05-background { background-color: #d8d8d8; }
-.base06-background { background-color: #e8e8e8; }
-.base07-background { background-color: #f8f8f8; }
-.base08-background { background-color: #ab4642; }
-.base09-background { background-color: #dc9656; }
-.base0A-background { background-color: #f7ca88; }
-.base0B-background { background-color: #a1b56c; }
-.base0C-background { background-color: #86c1b9; }
-.base0D-background { background-color: #7cafc2; }
-.base0E-background { background-color: #ba8baf; }
-.base0F-background { background-color: #a16946; }
-
-.base00 { color: #181818; }
-.base01 { color: #282828; }
-.base02 { color: #383838; }
-.base03 { color: #585858; }
-.base04 { color: #b8b8b8; }
-.base05 { color: #d8d8d8; }
-.base06 { color: #e8e8e8; }
-.base07 { color: #f8f8f8; }
-.base08 { color: #ab4642; }
-.base09 { color: #dc9656; }
-.base0A { color: #f7ca88; }
-.base0B { color: #a1b56c; }
-.base0C { color: #86c1b9; }
-.base0D { color: #7cafc2; }
-.base0E { color: #ba8baf; }
-.base0F { color: #a16946; }
diff --git a/static/css/base16-default-light.css b/static/css/base16-default-light.css
deleted file mode 100644
index 7e660c30..00000000
--- a/static/css/base16-default-light.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #f8f8f8; }
-.base01-background { background-color: #e8e8e8; }
-.base02-background { background-color: #d8d8d8; }
-.base03-background { background-color: #b8b8b8; }
-.base04-background { background-color: #585858; }
-.base05-background { background-color: #383838; }
-.base06-background { background-color: #282828; }
-.base07-background { background-color: #181818; }
-.base08-background { background-color: #ab4642; }
-.base09-background { background-color: #dc9656; }
-.base0A-background { background-color: #f7ca88; }
-.base0B-background { background-color: #a1b56c; }
-.base0C-background { background-color: #86c1b9; }
-.base0D-background { background-color: #7cafc2; }
-.base0E-background { background-color: #ba8baf; }
-.base0F-background { background-color: #a16946; }
-
-.base00 { color: #f8f8f8; }
-.base01 { color: #e8e8e8; }
-.base02 { color: #d8d8d8; }
-.base03 { color: #b8b8b8; }
-.base04 { color: #585858; }
-.base05 { color: #383838; }
-.base06 { color: #282828; }
-.base07 { color: #181818; }
-.base08 { color: #ab4642; }
-.base09 { color: #dc9656; }
-.base0A { color: #f7ca88; }
-.base0B { color: #a1b56c; }
-.base0C { color: #86c1b9; }
-.base0D { color: #7cafc2; }
-.base0E { color: #ba8baf; }
-.base0F { color: #a16946; }
diff --git a/static/css/base16-eighties.css b/static/css/base16-eighties.css
deleted file mode 100644
index 8ffcf04d..00000000
--- a/static/css/base16-eighties.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #2d2d2d; }
-.base01-background { background-color: #393939; }
-.base02-background { background-color: #515151; }
-.base03-background { background-color: #747369; }
-.base04-background { background-color: #a09f93; }
-.base05-background { background-color: #d3d0c8; }
-.base06-background { background-color: #e8e6df; }
-.base07-background { background-color: #f2f0ec; }
-.base08-background { background-color: #f2777a; }
-.base09-background { background-color: #f99157; }
-.base0A-background { background-color: #ffcc66; }
-.base0B-background { background-color: #99cc99; }
-.base0C-background { background-color: #66cccc; }
-.base0D-background { background-color: #6699cc; }
-.base0E-background { background-color: #cc99cc; }
-.base0F-background { background-color: #d27b53; }
-
-.base00 { color: #2d2d2d; }
-.base01 { color: #393939; }
-.base02 { color: #515151; }
-.base03 { color: #747369; }
-.base04 { color: #a09f93; }
-.base05 { color: #d3d0c8; }
-.base06 { color: #e8e6df; }
-.base07 { color: #f2f0ec; }
-.base08 { color: #f2777a; }
-.base09 { color: #f99157; }
-.base0A { color: #ffcc66; }
-.base0B { color: #99cc99; }
-.base0C { color: #66cccc; }
-.base0D { color: #6699cc; }
-.base0E { color: #cc99cc; }
-.base0F { color: #d27b53; }
diff --git a/static/css/base16-embers.css b/static/css/base16-embers.css
deleted file mode 100644
index 74e9b769..00000000
--- a/static/css/base16-embers.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #16130F; }
-.base01-background { background-color: #2C2620; }
-.base02-background { background-color: #433B32; }
-.base03-background { background-color: #5A5047; }
-.base04-background { background-color: #8A8075; }
-.base05-background { background-color: #A39A90; }
-.base06-background { background-color: #BEB6AE; }
-.base07-background { background-color: #DBD6D1; }
-.base08-background { background-color: #826D57; }
-.base09-background { background-color: #828257; }
-.base0A-background { background-color: #6D8257; }
-.base0B-background { background-color: #57826D; }
-.base0C-background { background-color: #576D82; }
-.base0D-background { background-color: #6D5782; }
-.base0E-background { background-color: #82576D; }
-.base0F-background { background-color: #825757; }
-
-.base00 { color: #16130F; }
-.base01 { color: #2C2620; }
-.base02 { color: #433B32; }
-.base03 { color: #5A5047; }
-.base04 { color: #8A8075; }
-.base05 { color: #A39A90; }
-.base06 { color: #BEB6AE; }
-.base07 { color: #DBD6D1; }
-.base08 { color: #826D57; }
-.base09 { color: #828257; }
-.base0A { color: #6D8257; }
-.base0B { color: #57826D; }
-.base0C { color: #576D82; }
-.base0D { color: #6D5782; }
-.base0E { color: #82576D; }
-.base0F { color: #825757; }
diff --git a/static/css/base16-flat.css b/static/css/base16-flat.css
deleted file mode 100644
index 72918a5d..00000000
--- a/static/css/base16-flat.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #2C3E50; }
-.base01-background { background-color: #34495E; }
-.base02-background { background-color: #7F8C8D; }
-.base03-background { background-color: #95A5A6; }
-.base04-background { background-color: #BDC3C7; }
-.base05-background { background-color: #e0e0e0; }
-.base06-background { background-color: #f5f5f5; }
-.base07-background { background-color: #ECF0F1; }
-.base08-background { background-color: #E74C3C; }
-.base09-background { background-color: #E67E22; }
-.base0A-background { background-color: #F1C40F; }
-.base0B-background { background-color: #2ECC71; }
-.base0C-background { background-color: #1ABC9C; }
-.base0D-background { background-color: #3498DB; }
-.base0E-background { background-color: #9B59B6; }
-.base0F-background { background-color: #be643c; }
-
-.base00 { color: #2C3E50; }
-.base01 { color: #34495E; }
-.base02 { color: #7F8C8D; }
-.base03 { color: #95A5A6; }
-.base04 { color: #BDC3C7; }
-.base05 { color: #e0e0e0; }
-.base06 { color: #f5f5f5; }
-.base07 { color: #ECF0F1; }
-.base08 { color: #E74C3C; }
-.base09 { color: #E67E22; }
-.base0A { color: #F1C40F; }
-.base0B { color: #2ECC71; }
-.base0C { color: #1ABC9C; }
-.base0D { color: #3498DB; }
-.base0E { color: #9B59B6; }
-.base0F { color: #be643c; }
diff --git a/static/css/base16-github.css b/static/css/base16-github.css
deleted file mode 100644
index 080ed34c..00000000
--- a/static/css/base16-github.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #ffffff; }
-.base01-background { background-color: #f5f5f5; }
-.base02-background { background-color: #c8c8fa; }
-.base03-background { background-color: #969896; }
-.base04-background { background-color: #e8e8e8; }
-.base05-background { background-color: #333333; }
-.base06-background { background-color: #ffffff; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #ed6a43; }
-.base09-background { background-color: #0086b3; }
-.base0A-background { background-color: #795da3; }
-.base0B-background { background-color: #183691; }
-.base0C-background { background-color: #183691; }
-.base0D-background { background-color: #795da3; }
-.base0E-background { background-color: #a71d5d; }
-.base0F-background { background-color: #333333; }
-
-.base00 { color: #ffffff; }
-.base01 { color: #f5f5f5; }
-.base02 { color: #c8c8fa; }
-.base03 { color: #969896; }
-.base04 { color: #e8e8e8; }
-.base05 { color: #333333; }
-.base06 { color: #ffffff; }
-.base07 { color: #ffffff; }
-.base08 { color: #ed6a43; }
-.base09 { color: #0086b3; }
-.base0A { color: #795da3; }
-.base0B { color: #183691; }
-.base0C { color: #183691; }
-.base0D { color: #795da3; }
-.base0E { color: #a71d5d; }
-.base0F { color: #333333; }
diff --git a/static/css/base16-google-dark.css b/static/css/base16-google-dark.css
deleted file mode 100644
index 988eac51..00000000
--- a/static/css/base16-google-dark.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #1d1f21; }
-.base01-background { background-color: #282a2e; }
-.base02-background { background-color: #373b41; }
-.base03-background { background-color: #969896; }
-.base04-background { background-color: #b4b7b4; }
-.base05-background { background-color: #c5c8c6; }
-.base06-background { background-color: #e0e0e0; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #CC342B; }
-.base09-background { background-color: #F96A38; }
-.base0A-background { background-color: #FBA922; }
-.base0B-background { background-color: #198844; }
-.base0C-background { background-color: #3971ED; }
-.base0D-background { background-color: #3971ED; }
-.base0E-background { background-color: #A36AC7; }
-.base0F-background { background-color: #3971ED; }
-
-.base00 { color: #1d1f21; }
-.base01 { color: #282a2e; }
-.base02 { color: #373b41; }
-.base03 { color: #969896; }
-.base04 { color: #b4b7b4; }
-.base05 { color: #c5c8c6; }
-.base06 { color: #e0e0e0; }
-.base07 { color: #ffffff; }
-.base08 { color: #CC342B; }
-.base09 { color: #F96A38; }
-.base0A { color: #FBA922; }
-.base0B { color: #198844; }
-.base0C { color: #3971ED; }
-.base0D { color: #3971ED; }
-.base0E { color: #A36AC7; }
-.base0F { color: #3971ED; }
diff --git a/static/css/base16-google-light.css b/static/css/base16-google-light.css
deleted file mode 100644
index 2ee2a606..00000000
--- a/static/css/base16-google-light.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #ffffff; }
-.base01-background { background-color: #e0e0e0; }
-.base02-background { background-color: #c5c8c6; }
-.base03-background { background-color: #b4b7b4; }
-.base04-background { background-color: #969896; }
-.base05-background { background-color: #373b41; }
-.base06-background { background-color: #282a2e; }
-.base07-background { background-color: #1d1f21; }
-.base08-background { background-color: #CC342B; }
-.base09-background { background-color: #F96A38; }
-.base0A-background { background-color: #FBA922; }
-.base0B-background { background-color: #198844; }
-.base0C-background { background-color: #3971ED; }
-.base0D-background { background-color: #3971ED; }
-.base0E-background { background-color: #A36AC7; }
-.base0F-background { background-color: #3971ED; }
-
-.base00 { color: #ffffff; }
-.base01 { color: #e0e0e0; }
-.base02 { color: #c5c8c6; }
-.base03 { color: #b4b7b4; }
-.base04 { color: #969896; }
-.base05 { color: #373b41; }
-.base06 { color: #282a2e; }
-.base07 { color: #1d1f21; }
-.base08 { color: #CC342B; }
-.base09 { color: #F96A38; }
-.base0A { color: #FBA922; }
-.base0B { color: #198844; }
-.base0C { color: #3971ED; }
-.base0D { color: #3971ED; }
-.base0E { color: #A36AC7; }
-.base0F { color: #3971ED; }
diff --git a/static/css/base16-grayscale-dark.css b/static/css/base16-grayscale-dark.css
deleted file mode 100644
index dc0dd03a..00000000
--- a/static/css/base16-grayscale-dark.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #101010; }
-.base01-background { background-color: #252525; }
-.base02-background { background-color: #464646; }
-.base03-background { background-color: #525252; }
-.base04-background { background-color: #ababab; }
-.base05-background { background-color: #b9b9b9; }
-.base06-background { background-color: #e3e3e3; }
-.base07-background { background-color: #f7f7f7; }
-.base08-background { background-color: #7c7c7c; }
-.base09-background { background-color: #999999; }
-.base0A-background { background-color: #a0a0a0; }
-.base0B-background { background-color: #8e8e8e; }
-.base0C-background { background-color: #868686; }
-.base0D-background { background-color: #686868; }
-.base0E-background { background-color: #747474; }
-.base0F-background { background-color: #5e5e5e; }
-
-.base00 { color: #101010; }
-.base01 { color: #252525; }
-.base02 { color: #464646; }
-.base03 { color: #525252; }
-.base04 { color: #ababab; }
-.base05 { color: #b9b9b9; }
-.base06 { color: #e3e3e3; }
-.base07 { color: #f7f7f7; }
-.base08 { color: #7c7c7c; }
-.base09 { color: #999999; }
-.base0A { color: #a0a0a0; }
-.base0B { color: #8e8e8e; }
-.base0C { color: #868686; }
-.base0D { color: #686868; }
-.base0E { color: #747474; }
-.base0F { color: #5e5e5e; }
diff --git a/static/css/base16-grayscale-light.css b/static/css/base16-grayscale-light.css
deleted file mode 100644
index f9fd213a..00000000
--- a/static/css/base16-grayscale-light.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #f7f7f7; }
-.base01-background { background-color: #e3e3e3; }
-.base02-background { background-color: #b9b9b9; }
-.base03-background { background-color: #ababab; }
-.base04-background { background-color: #525252; }
-.base05-background { background-color: #464646; }
-.base06-background { background-color: #252525; }
-.base07-background { background-color: #101010; }
-.base08-background { background-color: #7c7c7c; }
-.base09-background { background-color: #999999; }
-.base0A-background { background-color: #a0a0a0; }
-.base0B-background { background-color: #8e8e8e; }
-.base0C-background { background-color: #868686; }
-.base0D-background { background-color: #686868; }
-.base0E-background { background-color: #747474; }
-.base0F-background { background-color: #5e5e5e; }
-
-.base00 { color: #f7f7f7; }
-.base01 { color: #e3e3e3; }
-.base02 { color: #b9b9b9; }
-.base03 { color: #ababab; }
-.base04 { color: #525252; }
-.base05 { color: #464646; }
-.base06 { color: #252525; }
-.base07 { color: #101010; }
-.base08 { color: #7c7c7c; }
-.base09 { color: #999999; }
-.base0A { color: #a0a0a0; }
-.base0B { color: #8e8e8e; }
-.base0C { color: #868686; }
-.base0D { color: #686868; }
-.base0E { color: #747474; }
-.base0F { color: #5e5e5e; }
diff --git a/static/css/base16-green-screen.css b/static/css/base16-green-screen.css
deleted file mode 100644
index 205efeae..00000000
--- a/static/css/base16-green-screen.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #001100; }
-.base01-background { background-color: #003300; }
-.base02-background { background-color: #005500; }
-.base03-background { background-color: #007700; }
-.base04-background { background-color: #009900; }
-.base05-background { background-color: #00bb00; }
-.base06-background { background-color: #00dd00; }
-.base07-background { background-color: #00ff00; }
-.base08-background { background-color: #007700; }
-.base09-background { background-color: #009900; }
-.base0A-background { background-color: #007700; }
-.base0B-background { background-color: #00bb00; }
-.base0C-background { background-color: #005500; }
-.base0D-background { background-color: #009900; }
-.base0E-background { background-color: #00bb00; }
-.base0F-background { background-color: #005500; }
-
-.base00 { color: #001100; }
-.base01 { color: #003300; }
-.base02 { color: #005500; }
-.base03 { color: #007700; }
-.base04 { color: #009900; }
-.base05 { color: #00bb00; }
-.base06 { color: #00dd00; }
-.base07 { color: #00ff00; }
-.base08 { color: #007700; }
-.base09 { color: #009900; }
-.base0A { color: #007700; }
-.base0B { color: #00bb00; }
-.base0C { color: #005500; }
-.base0D { color: #009900; }
-.base0E { color: #00bb00; }
-.base0F { color: #005500; }
diff --git a/static/css/base16-harmonic16-dark.css b/static/css/base16-harmonic16-dark.css
deleted file mode 100644
index 0c2c7ce4..00000000
--- a/static/css/base16-harmonic16-dark.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #0b1c2c; }
-.base01-background { background-color: #223b54; }
-.base02-background { background-color: #405c79; }
-.base03-background { background-color: #627e99; }
-.base04-background { background-color: #aabcce; }
-.base05-background { background-color: #cbd6e2; }
-.base06-background { background-color: #e5ebf1; }
-.base07-background { background-color: #f7f9fb; }
-.base08-background { background-color: #bf8b56; }
-.base09-background { background-color: #bfbf56; }
-.base0A-background { background-color: #8bbf56; }
-.base0B-background { background-color: #56bf8b; }
-.base0C-background { background-color: #568bbf; }
-.base0D-background { background-color: #8b56bf; }
-.base0E-background { background-color: #bf568b; }
-.base0F-background { background-color: #bf5656; }
-
-.base00 { color: #0b1c2c; }
-.base01 { color: #223b54; }
-.base02 { color: #405c79; }
-.base03 { color: #627e99; }
-.base04 { color: #aabcce; }
-.base05 { color: #cbd6e2; }
-.base06 { color: #e5ebf1; }
-.base07 { color: #f7f9fb; }
-.base08 { color: #bf8b56; }
-.base09 { color: #bfbf56; }
-.base0A { color: #8bbf56; }
-.base0B { color: #56bf8b; }
-.base0C { color: #568bbf; }
-.base0D { color: #8b56bf; }
-.base0E { color: #bf568b; }
-.base0F { color: #bf5656; }
diff --git a/static/css/base16-harmonic16-light.css b/static/css/base16-harmonic16-light.css
deleted file mode 100644
index 37bb7679..00000000
--- a/static/css/base16-harmonic16-light.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #f7f9fb; }
-.base01-background { background-color: #e5ebf1; }
-.base02-background { background-color: #cbd6e2; }
-.base03-background { background-color: #aabcce; }
-.base04-background { background-color: #627e99; }
-.base05-background { background-color: #405c79; }
-.base06-background { background-color: #223b54; }
-.base07-background { background-color: #0b1c2c; }
-.base08-background { background-color: #bf8b56; }
-.base09-background { background-color: #bfbf56; }
-.base0A-background { background-color: #8bbf56; }
-.base0B-background { background-color: #56bf8b; }
-.base0C-background { background-color: #568bbf; }
-.base0D-background { background-color: #8b56bf; }
-.base0E-background { background-color: #bf568b; }
-.base0F-background { background-color: #bf5656; }
-
-.base00 { color: #f7f9fb; }
-.base01 { color: #e5ebf1; }
-.base02 { color: #cbd6e2; }
-.base03 { color: #aabcce; }
-.base04 { color: #627e99; }
-.base05 { color: #405c79; }
-.base06 { color: #223b54; }
-.base07 { color: #0b1c2c; }
-.base08 { color: #bf8b56; }
-.base09 { color: #bfbf56; }
-.base0A { color: #8bbf56; }
-.base0B { color: #56bf8b; }
-.base0C { color: #568bbf; }
-.base0D { color: #8b56bf; }
-.base0E { color: #bf568b; }
-.base0F { color: #bf5656; }
diff --git a/static/css/base16-hopscotch.css b/static/css/base16-hopscotch.css
deleted file mode 100644
index f2ad232c..00000000
--- a/static/css/base16-hopscotch.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #322931; }
-.base01-background { background-color: #433b42; }
-.base02-background { background-color: #5c545b; }
-.base03-background { background-color: #797379; }
-.base04-background { background-color: #989498; }
-.base05-background { background-color: #b9b5b8; }
-.base06-background { background-color: #d5d3d5; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #dd464c; }
-.base09-background { background-color: #fd8b19; }
-.base0A-background { background-color: #fdcc59; }
-.base0B-background { background-color: #8fc13e; }
-.base0C-background { background-color: #149b93; }
-.base0D-background { background-color: #1290bf; }
-.base0E-background { background-color: #c85e7c; }
-.base0F-background { background-color: #b33508; }
-
-.base00 { color: #322931; }
-.base01 { color: #433b42; }
-.base02 { color: #5c545b; }
-.base03 { color: #797379; }
-.base04 { color: #989498; }
-.base05 { color: #b9b5b8; }
-.base06 { color: #d5d3d5; }
-.base07 { color: #ffffff; }
-.base08 { color: #dd464c; }
-.base09 { color: #fd8b19; }
-.base0A { color: #fdcc59; }
-.base0B { color: #8fc13e; }
-.base0C { color: #149b93; }
-.base0D { color: #1290bf; }
-.base0E { color: #c85e7c; }
-.base0F { color: #b33508; }
diff --git a/static/css/base16-ir-black.css b/static/css/base16-ir-black.css
deleted file mode 100644
index 8d14ab9b..00000000
--- a/static/css/base16-ir-black.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #000000; }
-.base01-background { background-color: #242422; }
-.base02-background { background-color: #484844; }
-.base03-background { background-color: #6c6c66; }
-.base04-background { background-color: #918f88; }
-.base05-background { background-color: #b5b3aa; }
-.base06-background { background-color: #d9d7cc; }
-.base07-background { background-color: #fdfbee; }
-.base08-background { background-color: #ff6c60; }
-.base09-background { background-color: #e9c062; }
-.base0A-background { background-color: #ffffb6; }
-.base0B-background { background-color: #a8ff60; }
-.base0C-background { background-color: #c6c5fe; }
-.base0D-background { background-color: #96cbfe; }
-.base0E-background { background-color: #ff73fd; }
-.base0F-background { background-color: #b18a3d; }
-
-.base00 { color: #000000; }
-.base01 { color: #242422; }
-.base02 { color: #484844; }
-.base03 { color: #6c6c66; }
-.base04 { color: #918f88; }
-.base05 { color: #b5b3aa; }
-.base06 { color: #d9d7cc; }
-.base07 { color: #fdfbee; }
-.base08 { color: #ff6c60; }
-.base09 { color: #e9c062; }
-.base0A { color: #ffffb6; }
-.base0B { color: #a8ff60; }
-.base0C { color: #c6c5fe; }
-.base0D { color: #96cbfe; }
-.base0E { color: #ff73fd; }
-.base0F { color: #b18a3d; }
diff --git a/static/css/base16-isotope.css b/static/css/base16-isotope.css
deleted file mode 100644
index f7a4a0b4..00000000
--- a/static/css/base16-isotope.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #000000; }
-.base01-background { background-color: #404040; }
-.base02-background { background-color: #606060; }
-.base03-background { background-color: #808080; }
-.base04-background { background-color: #c0c0c0; }
-.base05-background { background-color: #d0d0d0; }
-.base06-background { background-color: #e0e0e0; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #ff0000; }
-.base09-background { background-color: #ff9900; }
-.base0A-background { background-color: #ff0099; }
-.base0B-background { background-color: #33ff00; }
-.base0C-background { background-color: #00ffff; }
-.base0D-background { background-color: #0066ff; }
-.base0E-background { background-color: #cc00ff; }
-.base0F-background { background-color: #3300ff; }
-
-.base00 { color: #000000; }
-.base01 { color: #404040; }
-.base02 { color: #606060; }
-.base03 { color: #808080; }
-.base04 { color: #c0c0c0; }
-.base05 { color: #d0d0d0; }
-.base06 { color: #e0e0e0; }
-.base07 { color: #ffffff; }
-.base08 { color: #ff0000; }
-.base09 { color: #ff9900; }
-.base0A { color: #ff0099; }
-.base0B { color: #33ff00; }
-.base0C { color: #00ffff; }
-.base0D { color: #0066ff; }
-.base0E { color: #cc00ff; }
-.base0F { color: #3300ff; }
diff --git a/static/css/base16-london-tube.css b/static/css/base16-london-tube.css
deleted file mode 100644
index 0537d1ad..00000000
--- a/static/css/base16-london-tube.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #231f20; }
-.base01-background { background-color: #1c3f95; }
-.base02-background { background-color: #5a5758; }
-.base03-background { background-color: #737171; }
-.base04-background { background-color: #959ca1; }
-.base05-background { background-color: #d9d8d8; }
-.base06-background { background-color: #e7e7e8; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #ee2e24; }
-.base09-background { background-color: #f386a1; }
-.base0A-background { background-color: #ffd204; }
-.base0B-background { background-color: #00853e; }
-.base0C-background { background-color: #85cebc; }
-.base0D-background { background-color: #009ddc; }
-.base0E-background { background-color: #98005d; }
-.base0F-background { background-color: #b06110; }
-
-.base00 { color: #231f20; }
-.base01 { color: #1c3f95; }
-.base02 { color: #5a5758; }
-.base03 { color: #737171; }
-.base04 { color: #959ca1; }
-.base05 { color: #d9d8d8; }
-.base06 { color: #e7e7e8; }
-.base07 { color: #ffffff; }
-.base08 { color: #ee2e24; }
-.base09 { color: #f386a1; }
-.base0A { color: #ffd204; }
-.base0B { color: #00853e; }
-.base0C { color: #85cebc; }
-.base0D { color: #009ddc; }
-.base0E { color: #98005d; }
-.base0F { color: #b06110; }
diff --git a/static/css/base16-macintosh.css b/static/css/base16-macintosh.css
deleted file mode 100644
index d5969fec..00000000
--- a/static/css/base16-macintosh.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #000000; }
-.base01-background { background-color: #404040; }
-.base02-background { background-color: #404040; }
-.base03-background { background-color: #808080; }
-.base04-background { background-color: #808080; }
-.base05-background { background-color: #c0c0c0; }
-.base06-background { background-color: #c0c0c0; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #dd0907; }
-.base09-background { background-color: #ff6403; }
-.base0A-background { background-color: #fbf305; }
-.base0B-background { background-color: #1fb714; }
-.base0C-background { background-color: #02abea; }
-.base0D-background { background-color: #0000d3; }
-.base0E-background { background-color: #4700a5; }
-.base0F-background { background-color: #90713a; }
-
-.base00 { color: #000000; }
-.base01 { color: #404040; }
-.base02 { color: #404040; }
-.base03 { color: #808080; }
-.base04 { color: #808080; }
-.base05 { color: #c0c0c0; }
-.base06 { color: #c0c0c0; }
-.base07 { color: #ffffff; }
-.base08 { color: #dd0907; }
-.base09 { color: #ff6403; }
-.base0A { color: #fbf305; }
-.base0B { color: #1fb714; }
-.base0C { color: #02abea; }
-.base0D { color: #0000d3; }
-.base0E { color: #4700a5; }
-.base0F { color: #90713a; }
diff --git a/static/css/base16-marrakesh.css b/static/css/base16-marrakesh.css
deleted file mode 100644
index 91f0471f..00000000
--- a/static/css/base16-marrakesh.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #201602; }
-.base01-background { background-color: #302e00; }
-.base02-background { background-color: #5f5b17; }
-.base03-background { background-color: #6c6823; }
-.base04-background { background-color: #86813b; }
-.base05-background { background-color: #948e48; }
-.base06-background { background-color: #ccc37a; }
-.base07-background { background-color: #faf0a5; }
-.base08-background { background-color: #c35359; }
-.base09-background { background-color: #b36144; }
-.base0A-background { background-color: #a88339; }
-.base0B-background { background-color: #18974e; }
-.base0C-background { background-color: #75a738; }
-.base0D-background { background-color: #477ca1; }
-.base0E-background { background-color: #8868b3; }
-.base0F-background { background-color: #b3588e; }
-
-.base00 { color: #201602; }
-.base01 { color: #302e00; }
-.base02 { color: #5f5b17; }
-.base03 { color: #6c6823; }
-.base04 { color: #86813b; }
-.base05 { color: #948e48; }
-.base06 { color: #ccc37a; }
-.base07 { color: #faf0a5; }
-.base08 { color: #c35359; }
-.base09 { color: #b36144; }
-.base0A { color: #a88339; }
-.base0B { color: #18974e; }
-.base0C { color: #75a738; }
-.base0D { color: #477ca1; }
-.base0E { color: #8868b3; }
-.base0F { color: #b3588e; }
diff --git a/static/css/base16-materia.css b/static/css/base16-materia.css
deleted file mode 100644
index 41d935dd..00000000
--- a/static/css/base16-materia.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #263238; }
-.base01-background { background-color: #2C393F; }
-.base02-background { background-color: #37474F; }
-.base03-background { background-color: #707880; }
-.base04-background { background-color: #C9CCD3; }
-.base05-background { background-color: #CDD3DE; }
-.base06-background { background-color: #D5DBE5; }
-.base07-background { background-color: #FFFFFF; }
-.base08-background { background-color: #EC5F67; }
-.base09-background { background-color: #EA9560; }
-.base0A-background { background-color: #FFCC00; }
-.base0B-background { background-color: #8BD649; }
-.base0C-background { background-color: #80CBC4; }
-.base0D-background { background-color: #89DDFF; }
-.base0E-background { background-color: #82AAFF; }
-.base0F-background { background-color: #EC5F67; }
-
-.base00 { color: #263238; }
-.base01 { color: #2C393F; }
-.base02 { color: #37474F; }
-.base03 { color: #707880; }
-.base04 { color: #C9CCD3; }
-.base05 { color: #CDD3DE; }
-.base06 { color: #D5DBE5; }
-.base07 { color: #FFFFFF; }
-.base08 { color: #EC5F67; }
-.base09 { color: #EA9560; }
-.base0A { color: #FFCC00; }
-.base0B { color: #8BD649; }
-.base0C { color: #80CBC4; }
-.base0D { color: #89DDFF; }
-.base0E { color: #82AAFF; }
-.base0F { color: #EC5F67; }
diff --git a/static/css/base16-mexico-light.css b/static/css/base16-mexico-light.css
deleted file mode 100644
index 1916c67b..00000000
--- a/static/css/base16-mexico-light.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #f8f8f8; }
-.base01-background { background-color: #e8e8e8; }
-.base02-background { background-color: #d8d8d8; }
-.base03-background { background-color: #b8b8b8; }
-.base04-background { background-color: #585858; }
-.base05-background { background-color: #383838; }
-.base06-background { background-color: #282828; }
-.base07-background { background-color: #181818; }
-.base08-background { background-color: #ab4642; }
-.base09-background { background-color: #dc9656; }
-.base0A-background { background-color: #f79a0e; }
-.base0B-background { background-color: #538947; }
-.base0C-background { background-color: #4b8093; }
-.base0D-background { background-color: #7cafc2; }
-.base0E-background { background-color: #96609e; }
-.base0F-background { background-color: #a16946; }
-
-.base00 { color: #f8f8f8; }
-.base01 { color: #e8e8e8; }
-.base02 { color: #d8d8d8; }
-.base03 { color: #b8b8b8; }
-.base04 { color: #585858; }
-.base05 { color: #383838; }
-.base06 { color: #282828; }
-.base07 { color: #181818; }
-.base08 { color: #ab4642; }
-.base09 { color: #dc9656; }
-.base0A { color: #f79a0e; }
-.base0B { color: #538947; }
-.base0C { color: #4b8093; }
-.base0D { color: #7cafc2; }
-.base0E { color: #96609e; }
-.base0F { color: #a16946; }
diff --git a/static/css/base16-mocha.css b/static/css/base16-mocha.css
deleted file mode 100644
index 6cb2fb58..00000000
--- a/static/css/base16-mocha.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #3B3228; }
-.base01-background { background-color: #534636; }
-.base02-background { background-color: #645240; }
-.base03-background { background-color: #7e705a; }
-.base04-background { background-color: #b8afad; }
-.base05-background { background-color: #d0c8c6; }
-.base06-background { background-color: #e9e1dd; }
-.base07-background { background-color: #f5eeeb; }
-.base08-background { background-color: #cb6077; }
-.base09-background { background-color: #d28b71; }
-.base0A-background { background-color: #f4bc87; }
-.base0B-background { background-color: #beb55b; }
-.base0C-background { background-color: #7bbda4; }
-.base0D-background { background-color: #8ab3b5; }
-.base0E-background { background-color: #a89bb9; }
-.base0F-background { background-color: #bb9584; }
-
-.base00 { color: #3B3228; }
-.base01 { color: #534636; }
-.base02 { color: #645240; }
-.base03 { color: #7e705a; }
-.base04 { color: #b8afad; }
-.base05 { color: #d0c8c6; }
-.base06 { color: #e9e1dd; }
-.base07 { color: #f5eeeb; }
-.base08 { color: #cb6077; }
-.base09 { color: #d28b71; }
-.base0A { color: #f4bc87; }
-.base0B { color: #beb55b; }
-.base0C { color: #7bbda4; }
-.base0D { color: #8ab3b5; }
-.base0E { color: #a89bb9; }
-.base0F { color: #bb9584; }
diff --git a/static/css/base16-monokai.css b/static/css/base16-monokai.css
deleted file mode 100644
index fc7ccf47..00000000
--- a/static/css/base16-monokai.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #272822; }
-.base01-background { background-color: #383830; }
-.base02-background { background-color: #49483e; }
-.base03-background { background-color: #75715e; }
-.base04-background { background-color: #a59f85; }
-.base05-background { background-color: #f8f8f2; }
-.base06-background { background-color: #f5f4f1; }
-.base07-background { background-color: #f9f8f5; }
-.base08-background { background-color: #f92672; }
-.base09-background { background-color: #fd971f; }
-.base0A-background { background-color: #f4bf75; }
-.base0B-background { background-color: #a6e22e; }
-.base0C-background { background-color: #a1efe4; }
-.base0D-background { background-color: #66d9ef; }
-.base0E-background { background-color: #ae81ff; }
-.base0F-background { background-color: #cc6633; }
-
-.base00 { color: #272822; }
-.base01 { color: #383830; }
-.base02 { color: #49483e; }
-.base03 { color: #75715e; }
-.base04 { color: #a59f85; }
-.base05 { color: #f8f8f2; }
-.base06 { color: #f5f4f1; }
-.base07 { color: #f9f8f5; }
-.base08 { color: #f92672; }
-.base09 { color: #fd971f; }
-.base0A { color: #f4bf75; }
-.base0B { color: #a6e22e; }
-.base0C { color: #a1efe4; }
-.base0D { color: #66d9ef; }
-.base0E { color: #ae81ff; }
-.base0F { color: #cc6633; }
diff --git a/static/css/base16-ocean.css b/static/css/base16-ocean.css
deleted file mode 100644
index 8622d17e..00000000
--- a/static/css/base16-ocean.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #2b303b; }
-.base01-background { background-color: #343d46; }
-.base02-background { background-color: #4f5b66; }
-.base03-background { background-color: #65737e; }
-.base04-background { background-color: #a7adba; }
-.base05-background { background-color: #c0c5ce; }
-.base06-background { background-color: #dfe1e8; }
-.base07-background { background-color: #eff1f5; }
-.base08-background { background-color: #bf616a; }
-.base09-background { background-color: #d08770; }
-.base0A-background { background-color: #ebcb8b; }
-.base0B-background { background-color: #a3be8c; }
-.base0C-background { background-color: #96b5b4; }
-.base0D-background { background-color: #8fa1b3; }
-.base0E-background { background-color: #b48ead; }
-.base0F-background { background-color: #ab7967; }
-
-.base00 { color: #2b303b; }
-.base01 { color: #343d46; }
-.base02 { color: #4f5b66; }
-.base03 { color: #65737e; }
-.base04 { color: #a7adba; }
-.base05 { color: #c0c5ce; }
-.base06 { color: #dfe1e8; }
-.base07 { color: #eff1f5; }
-.base08 { color: #bf616a; }
-.base09 { color: #d08770; }
-.base0A { color: #ebcb8b; }
-.base0B { color: #a3be8c; }
-.base0C { color: #96b5b4; }
-.base0D { color: #8fa1b3; }
-.base0E { color: #b48ead; }
-.base0F { color: #ab7967; }
diff --git a/static/css/base16-oceanicnext.css b/static/css/base16-oceanicnext.css
deleted file mode 100644
index df4d9ef5..00000000
--- a/static/css/base16-oceanicnext.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #1B2B34; }
-.base01-background { background-color: #343D46; }
-.base02-background { background-color: #4F5B66; }
-.base03-background { background-color: #65737E; }
-.base04-background { background-color: #A7ADBA; }
-.base05-background { background-color: #C0C5CE; }
-.base06-background { background-color: #CDD3DE; }
-.base07-background { background-color: #D8DEE9; }
-.base08-background { background-color: #EC5f67; }
-.base09-background { background-color: #F99157; }
-.base0A-background { background-color: #FAC863; }
-.base0B-background { background-color: #99C794; }
-.base0C-background { background-color: #5FB3B3; }
-.base0D-background { background-color: #6699CC; }
-.base0E-background { background-color: #C594C5; }
-.base0F-background { background-color: #AB7967; }
-
-.base00 { color: #1B2B34; }
-.base01 { color: #343D46; }
-.base02 { color: #4F5B66; }
-.base03 { color: #65737E; }
-.base04 { color: #A7ADBA; }
-.base05 { color: #C0C5CE; }
-.base06 { color: #CDD3DE; }
-.base07 { color: #D8DEE9; }
-.base08 { color: #EC5f67; }
-.base09 { color: #F99157; }
-.base0A { color: #FAC863; }
-.base0B { color: #99C794; }
-.base0C { color: #5FB3B3; }
-.base0D { color: #6699CC; }
-.base0E { color: #C594C5; }
-.base0F { color: #AB7967; }
diff --git a/static/css/base16-paraiso.css b/static/css/base16-paraiso.css
deleted file mode 100644
index b68c9407..00000000
--- a/static/css/base16-paraiso.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #2f1e2e; }
-.base01-background { background-color: #41323f; }
-.base02-background { background-color: #4f424c; }
-.base03-background { background-color: #776e71; }
-.base04-background { background-color: #8d8687; }
-.base05-background { background-color: #a39e9b; }
-.base06-background { background-color: #b9b6b0; }
-.base07-background { background-color: #e7e9db; }
-.base08-background { background-color: #ef6155; }
-.base09-background { background-color: #f99b15; }
-.base0A-background { background-color: #fec418; }
-.base0B-background { background-color: #48b685; }
-.base0C-background { background-color: #5bc4bf; }
-.base0D-background { background-color: #06b6ef; }
-.base0E-background { background-color: #815ba4; }
-.base0F-background { background-color: #e96ba8; }
-
-.base00 { color: #2f1e2e; }
-.base01 { color: #41323f; }
-.base02 { color: #4f424c; }
-.base03 { color: #776e71; }
-.base04 { color: #8d8687; }
-.base05 { color: #a39e9b; }
-.base06 { color: #b9b6b0; }
-.base07 { color: #e7e9db; }
-.base08 { color: #ef6155; }
-.base09 { color: #f99b15; }
-.base0A { color: #fec418; }
-.base0B { color: #48b685; }
-.base0C { color: #5bc4bf; }
-.base0D { color: #06b6ef; }
-.base0E { color: #815ba4; }
-.base0F { color: #e96ba8; }
diff --git a/static/css/base16-phd.css b/static/css/base16-phd.css
deleted file mode 100644
index 54276ab1..00000000
--- a/static/css/base16-phd.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #061229; }
-.base01-background { background-color: #2a3448; }
-.base02-background { background-color: #4d5666; }
-.base03-background { background-color: #717885; }
-.base04-background { background-color: #9a99a3; }
-.base05-background { background-color: #b8bbc2; }
-.base06-background { background-color: #dbdde0; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #d07346; }
-.base09-background { background-color: #f0a000; }
-.base0A-background { background-color: #fbd461; }
-.base0B-background { background-color: #99bf52; }
-.base0C-background { background-color: #72b9bf; }
-.base0D-background { background-color: #5299bf; }
-.base0E-background { background-color: #9989cc; }
-.base0F-background { background-color: #b08060; }
-
-.base00 { color: #061229; }
-.base01 { color: #2a3448; }
-.base02 { color: #4d5666; }
-.base03 { color: #717885; }
-.base04 { color: #9a99a3; }
-.base05 { color: #b8bbc2; }
-.base06 { color: #dbdde0; }
-.base07 { color: #ffffff; }
-.base08 { color: #d07346; }
-.base09 { color: #f0a000; }
-.base0A { color: #fbd461; }
-.base0B { color: #99bf52; }
-.base0C { color: #72b9bf; }
-.base0D { color: #5299bf; }
-.base0E { color: #9989cc; }
-.base0F { color: #b08060; }
diff --git a/static/css/base16-pico.css b/static/css/base16-pico.css
deleted file mode 100644
index 86482b72..00000000
--- a/static/css/base16-pico.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #000000; }
-.base01-background { background-color: #1d2b53; }
-.base02-background { background-color: #7e2553; }
-.base03-background { background-color: #008751; }
-.base04-background { background-color: #ab5236; }
-.base05-background { background-color: #5f574f; }
-.base06-background { background-color: #c2c3c7; }
-.base07-background { background-color: #fff1e8; }
-.base08-background { background-color: #ff004d; }
-.base09-background { background-color: #ffa300; }
-.base0A-background { background-color: #fff024; }
-.base0B-background { background-color: #00e756; }
-.base0C-background { background-color: #29adff; }
-.base0D-background { background-color: #83769c; }
-.base0E-background { background-color: #ff77a8; }
-.base0F-background { background-color: #ffccaa; }
-
-.base00 { color: #000000; }
-.base01 { color: #1d2b53; }
-.base02 { color: #7e2553; }
-.base03 { color: #008751; }
-.base04 { color: #ab5236; }
-.base05 { color: #5f574f; }
-.base06 { color: #c2c3c7; }
-.base07 { color: #fff1e8; }
-.base08 { color: #ff004d; }
-.base09 { color: #ffa300; }
-.base0A { color: #fff024; }
-.base0B { color: #00e756; }
-.base0C { color: #29adff; }
-.base0D { color: #83769c; }
-.base0E { color: #ff77a8; }
-.base0F { color: #ffccaa; }
diff --git a/static/css/base16-pleroma-dark.css b/static/css/base16-pleroma-dark.css
deleted file mode 100644
index e1d46f92..00000000
--- a/static/css/base16-pleroma-dark.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #161c20; }
-.base01-background { background-color: #282e32; }
-.base02-background { background-color: #343a3f; }
-.base03-background { background-color: #4e5256; }
-.base04-background { background-color: #ababab; }
-.base05-background { background-color: #b9b9b9; }
-.base06-background { background-color: #d0d0d0; }
-.base07-background { background-color: #e7e7e7; }
-.base08-background { background-color: #baaa9c; }
-.base09-background { background-color: #999999; }
-.base0A-background { background-color: #a0a0a0; }
-.base0B-background { background-color: #8e8e8e; }
-.base0C-background { background-color: #868686; }
-.base0D-background { background-color: #686868; }
-.base0E-background { background-color: #747474; }
-.base0F-background { background-color: #5e5e5e; }
-
-.base00 { color: #161c20; }
-.base01 { color: #282e32; }
-.base02 { color: #343a3f; }
-.base03 { color: #4e5256; }
-.base04 { color: #ababab; }
-.base05 { color: #b9b9b9; }
-.base06 { color: #d0d0d0; }
-.base07 { color: #e7e7e7; }
-.base08 { color: #baaa9c; }
-.base09 { color: #999999; }
-.base0A { color: #a0a0a0; }
-.base0B { color: #8e8e8e; }
-.base0C { color: #868686; }
-.base0D { color: #686868; }
-.base0E { color: #747474; }
-.base0F { color: #5e5e5e; }
diff --git a/static/css/base16-pleroma-light.css b/static/css/base16-pleroma-light.css
deleted file mode 100644
index 1a85689a..00000000
--- a/static/css/base16-pleroma-light.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #f2f4f6; }
-.base01-background { background-color: #dde2e6; }
-.base02-background { background-color: #c0c6cb; }
-.base03-background { background-color: #a4a4a4; }
-.base04-background { background-color: #545454; }
-.base05-background { background-color: #304055; }
-.base06-background { background-color: #040404; }
-.base07-background { background-color: #000000; }
-.base08-background { background-color: #e92f2f; }
-.base09-background { background-color: #e09448; }
-.base0A-background { background-color: #dddd13; }
-.base0B-background { background-color: #0ed839; }
-.base0C-background { background-color: #23edda; }
-.base0D-background { background-color: #3b48e3; }
-.base0E-background { background-color: #f996e2; }
-.base0F-background { background-color: #69542d; }
-
-.base00 { color: #f2f4f6; }
-.base01 { color: #dde2e6; }
-.base02 { color: #c0c6cb; }
-.base03 { color: #a4a4a4; }
-.base04 { color: #545454; }
-.base05 { color: #304055; }
-.base06 { color: #040404; }
-.base07 { color: #000000; }
-.base08 { color: #e46f0f; }
-.base09 { color: #e09448; }
-.base0A { color: #dddd13; }
-.base0B { color: #0ed839; }
-.base0C { color: #23edda; }
-.base0D { color: #3b48e3; }
-.base0E { color: #f996e2; }
-.base0F { color: #69542d; }
diff --git a/static/css/base16-pop.css b/static/css/base16-pop.css
deleted file mode 100644
index 14acac17..00000000
--- a/static/css/base16-pop.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #000000; }
-.base01-background { background-color: #202020; }
-.base02-background { background-color: #303030; }
-.base03-background { background-color: #505050; }
-.base04-background { background-color: #b0b0b0; }
-.base05-background { background-color: #d0d0d0; }
-.base06-background { background-color: #e0e0e0; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #eb008a; }
-.base09-background { background-color: #f29333; }
-.base0A-background { background-color: #f8ca12; }
-.base0B-background { background-color: #37b349; }
-.base0C-background { background-color: #00aabb; }
-.base0D-background { background-color: #0e5a94; }
-.base0E-background { background-color: #b31e8d; }
-.base0F-background { background-color: #7a2d00; }
-
-.base00 { color: #000000; }
-.base01 { color: #202020; }
-.base02 { color: #303030; }
-.base03 { color: #505050; }
-.base04 { color: #b0b0b0; }
-.base05 { color: #d0d0d0; }
-.base06 { color: #e0e0e0; }
-.base07 { color: #ffffff; }
-.base08 { color: #eb008a; }
-.base09 { color: #f29333; }
-.base0A { color: #f8ca12; }
-.base0B { color: #37b349; }
-.base0C { color: #00aabb; }
-.base0D { color: #0e5a94; }
-.base0E { color: #b31e8d; }
-.base0F { color: #7a2d00; }
diff --git a/static/css/base16-railscasts.css b/static/css/base16-railscasts.css
deleted file mode 100644
index 18f43bfd..00000000
--- a/static/css/base16-railscasts.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #2b2b2b; }
-.base01-background { background-color: #272935; }
-.base02-background { background-color: #3a4055; }
-.base03-background { background-color: #5a647e; }
-.base04-background { background-color: #d4cfc9; }
-.base05-background { background-color: #e6e1dc; }
-.base06-background { background-color: #f4f1ed; }
-.base07-background { background-color: #f9f7f3; }
-.base08-background { background-color: #da4939; }
-.base09-background { background-color: #cc7833; }
-.base0A-background { background-color: #ffc66d; }
-.base0B-background { background-color: #a5c261; }
-.base0C-background { background-color: #519f50; }
-.base0D-background { background-color: #6d9cbe; }
-.base0E-background { background-color: #b6b3eb; }
-.base0F-background { background-color: #bc9458; }
-
-.base00 { color: #2b2b2b; }
-.base01 { color: #272935; }
-.base02 { color: #3a4055; }
-.base03 { color: #5a647e; }
-.base04 { color: #d4cfc9; }
-.base05 { color: #e6e1dc; }
-.base06 { color: #f4f1ed; }
-.base07 { color: #f9f7f3; }
-.base08 { color: #da4939; }
-.base09 { color: #cc7833; }
-.base0A { color: #ffc66d; }
-.base0B { color: #a5c261; }
-.base0C { color: #519f50; }
-.base0D { color: #6d9cbe; }
-.base0E { color: #b6b3eb; }
-.base0F { color: #bc9458; }
diff --git a/static/css/base16-seti-ui.css b/static/css/base16-seti-ui.css
deleted file mode 100644
index bd4f9cc4..00000000
--- a/static/css/base16-seti-ui.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #151718; }
-.base01-background { background-color: #8ec43d; }
-.base02-background { background-color: #3B758C; }
-.base03-background { background-color: #41535B; }
-.base04-background { background-color: #43a5d5; }
-.base05-background { background-color: #d6d6d6; }
-.base06-background { background-color: #eeeeee; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #Cd3f45; }
-.base09-background { background-color: #db7b55; }
-.base0A-background { background-color: #e6cd69; }
-.base0B-background { background-color: #9fca56; }
-.base0C-background { background-color: #55dbbe; }
-.base0D-background { background-color: #55b5db; }
-.base0E-background { background-color: #a074c4; }
-.base0F-background { background-color: #8a553f; }
-
-.base00 { color: #151718; }
-.base01 { color: #8ec43d; }
-.base02 { color: #3B758C; }
-.base03 { color: #41535B; }
-.base04 { color: #43a5d5; }
-.base05 { color: #d6d6d6; }
-.base06 { color: #eeeeee; }
-.base07 { color: #ffffff; }
-.base08 { color: #Cd3f45; }
-.base09 { color: #db7b55; }
-.base0A { color: #e6cd69; }
-.base0B { color: #9fca56; }
-.base0C { color: #55dbbe; }
-.base0D { color: #55b5db; }
-.base0E { color: #a074c4; }
-.base0F { color: #8a553f; }
diff --git a/static/css/base16-shapeshifter.css b/static/css/base16-shapeshifter.css
deleted file mode 100644
index ded18069..00000000
--- a/static/css/base16-shapeshifter.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #f9f9f9; }
-.base01-background { background-color: #e0e0e0; }
-.base02-background { background-color: #ababab; }
-.base03-background { background-color: #555555; }
-.base04-background { background-color: #343434; }
-.base05-background { background-color: #102015; }
-.base06-background { background-color: #040404; }
-.base07-background { background-color: #000000; }
-.base08-background { background-color: #e92f2f; }
-.base09-background { background-color: #e09448; }
-.base0A-background { background-color: #dddd13; }
-.base0B-background { background-color: #0ed839; }
-.base0C-background { background-color: #23edda; }
-.base0D-background { background-color: #3b48e3; }
-.base0E-background { background-color: #f996e2; }
-.base0F-background { background-color: #69542d; }
-
-.base00 { color: #f9f9f9; }
-.base01 { color: #e0e0e0; }
-.base02 { color: #ababab; }
-.base03 { color: #555555; }
-.base04 { color: #343434; }
-.base05 { color: #102015; }
-.base06 { color: #040404; }
-.base07 { color: #000000; }
-.base08 { color: #e92f2f; }
-.base09 { color: #e09448; }
-.base0A { color: #dddd13; }
-.base0B { color: #0ed839; }
-.base0C { color: #23edda; }
-.base0D { color: #3b48e3; }
-.base0E { color: #f996e2; }
-.base0F { color: #69542d; }
diff --git a/static/css/base16-solar-flare.css b/static/css/base16-solar-flare.css
deleted file mode 100644
index 7d1d3862..00000000
--- a/static/css/base16-solar-flare.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #18262F; }
-.base01-background { background-color: #222E38; }
-.base02-background { background-color: #586875; }
-.base03-background { background-color: #667581; }
-.base04-background { background-color: #85939E; }
-.base05-background { background-color: #A6AFB8; }
-.base06-background { background-color: #E8E9ED; }
-.base07-background { background-color: #F5F7FA; }
-.base08-background { background-color: #EF5253; }
-.base09-background { background-color: #E66B2B; }
-.base0A-background { background-color: #E4B51C; }
-.base0B-background { background-color: #7CC844; }
-.base0C-background { background-color: #52CBB0; }
-.base0D-background { background-color: #33B5E1; }
-.base0E-background { background-color: #A363D5; }
-.base0F-background { background-color: #D73C9A; }
-
-.base00 { color: #18262F; }
-.base01 { color: #222E38; }
-.base02 { color: #586875; }
-.base03 { color: #667581; }
-.base04 { color: #85939E; }
-.base05 { color: #A6AFB8; }
-.base06 { color: #E8E9ED; }
-.base07 { color: #F5F7FA; }
-.base08 { color: #EF5253; }
-.base09 { color: #E66B2B; }
-.base0A { color: #E4B51C; }
-.base0B { color: #7CC844; }
-.base0C { color: #52CBB0; }
-.base0D { color: #33B5E1; }
-.base0E { color: #A363D5; }
-.base0F { color: #D73C9A; }
diff --git a/static/css/base16-solarized-dark.css b/static/css/base16-solarized-dark.css
deleted file mode 100644
index ac16f12c..00000000
--- a/static/css/base16-solarized-dark.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #002b36; }
-.base01-background { background-color: #073642; }
-.base02-background { background-color: #586e75; }
-.base03-background { background-color: #657b83; }
-.base04-background { background-color: #839496; }
-.base05-background { background-color: #93a1a1; }
-.base06-background { background-color: #eee8d5; }
-.base07-background { background-color: #fdf6e3; }
-.base08-background { background-color: #dc322f; }
-.base09-background { background-color: #cb4b16; }
-.base0A-background { background-color: #b58900; }
-.base0B-background { background-color: #859900; }
-.base0C-background { background-color: #2aa198; }
-.base0D-background { background-color: #268bd2; }
-.base0E-background { background-color: #6c71c4; }
-.base0F-background { background-color: #d33682; }
-
-.base00 { color: #002b36; }
-.base01 { color: #073642; }
-.base02 { color: #586e75; }
-.base03 { color: #657b83; }
-.base04 { color: #839496; }
-.base05 { color: #93a1a1; }
-.base06 { color: #eee8d5; }
-.base07 { color: #fdf6e3; }
-.base08 { color: #dc322f; }
-.base09 { color: #cb4b16; }
-.base0A { color: #b58900; }
-.base0B { color: #859900; }
-.base0C { color: #2aa198; }
-.base0D { color: #268bd2; }
-.base0E { color: #6c71c4; }
-.base0F { color: #d33682; }
diff --git a/static/css/base16-solarized-light.css b/static/css/base16-solarized-light.css
deleted file mode 100644
index 7164cb04..00000000
--- a/static/css/base16-solarized-light.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #fdf6e3; }
-.base01-background { background-color: #eee8d5; }
-.base02-background { background-color: #93a1a1; }
-.base03-background { background-color: #839496; }
-.base04-background { background-color: #657b83; }
-.base05-background { background-color: #586e75; }
-.base06-background { background-color: #073642; }
-.base07-background { background-color: #002b36; }
-.base08-background { background-color: #dc322f; }
-.base09-background { background-color: #cb4b16; }
-.base0A-background { background-color: #b58900; }
-.base0B-background { background-color: #859900; }
-.base0C-background { background-color: #2aa198; }
-.base0D-background { background-color: #268bd2; }
-.base0E-background { background-color: #6c71c4; }
-.base0F-background { background-color: #d33682; }
-
-.base00 { color: #fdf6e3; }
-.base01 { color: #eee8d5; }
-.base02 { color: #93a1a1; }
-.base03 { color: #839496; }
-.base04 { color: #657b83; }
-.base05 { color: #586e75; }
-.base06 { color: #073642; }
-.base07 { color: #002b36; }
-.base08 { color: #dc322f; }
-.base09 { color: #cb4b16; }
-.base0A { color: #b58900; }
-.base0B { color: #859900; }
-.base0C { color: #2aa198; }
-.base0D { color: #268bd2; }
-.base0E { color: #6c71c4; }
-.base0F { color: #d33682; }
diff --git a/static/css/base16-spacemacs.css b/static/css/base16-spacemacs.css
deleted file mode 100644
index 48737650..00000000
--- a/static/css/base16-spacemacs.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #1f2022; }
-.base01-background { background-color: #282828; }
-.base02-background { background-color: #444155; }
-.base03-background { background-color: #585858; }
-.base04-background { background-color: #b8b8b8; }
-.base05-background { background-color: #a3a3a3; }
-.base06-background { background-color: #e8e8e8; }
-.base07-background { background-color: #f8f8f8; }
-.base08-background { background-color: #f2241f; }
-.base09-background { background-color: #ffa500; }
-.base0A-background { background-color: #b1951d; }
-.base0B-background { background-color: #67b11d; }
-.base0C-background { background-color: #2d9574; }
-.base0D-background { background-color: #4f97d7; }
-.base0E-background { background-color: #a31db1; }
-.base0F-background { background-color: #b03060; }
-
-.base00 { color: #1f2022; }
-.base01 { color: #282828; }
-.base02 { color: #444155; }
-.base03 { color: #585858; }
-.base04 { color: #b8b8b8; }
-.base05 { color: #a3a3a3; }
-.base06 { color: #e8e8e8; }
-.base07 { color: #f8f8f8; }
-.base08 { color: #f2241f; }
-.base09 { color: #ffa500; }
-.base0A { color: #b1951d; }
-.base0B { color: #67b11d; }
-.base0C { color: #2d9574; }
-.base0D { color: #4f97d7; }
-.base0E { color: #a31db1; }
-.base0F { color: #b03060; }
diff --git a/static/css/base16-summerfruit-dark.css b/static/css/base16-summerfruit-dark.css
deleted file mode 100644
index 1c8f2332..00000000
--- a/static/css/base16-summerfruit-dark.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #151515; }
-.base01-background { background-color: #202020; }
-.base02-background { background-color: #303030; }
-.base03-background { background-color: #505050; }
-.base04-background { background-color: #B0B0B0; }
-.base05-background { background-color: #D0D0D0; }
-.base06-background { background-color: #E0E0E0; }
-.base07-background { background-color: #FFFFFF; }
-.base08-background { background-color: #FF0086; }
-.base09-background { background-color: #FD8900; }
-.base0A-background { background-color: #ABA800; }
-.base0B-background { background-color: #00C918; }
-.base0C-background { background-color: #1FAAAA; }
-.base0D-background { background-color: #3777E6; }
-.base0E-background { background-color: #AD00A1; }
-.base0F-background { background-color: #CC6633; }
-
-.base00 { color: #151515; }
-.base01 { color: #202020; }
-.base02 { color: #303030; }
-.base03 { color: #505050; }
-.base04 { color: #B0B0B0; }
-.base05 { color: #D0D0D0; }
-.base06 { color: #E0E0E0; }
-.base07 { color: #FFFFFF; }
-.base08 { color: #FF0086; }
-.base09 { color: #FD8900; }
-.base0A { color: #ABA800; }
-.base0B { color: #00C918; }
-.base0C { color: #1FAAAA; }
-.base0D { color: #3777E6; }
-.base0E { color: #AD00A1; }
-.base0F { color: #CC6633; }
diff --git a/static/css/base16-summerfruit-light.css b/static/css/base16-summerfruit-light.css
deleted file mode 100644
index cb54d4c5..00000000
--- a/static/css/base16-summerfruit-light.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #FFFFFF; }
-.base01-background { background-color: #E0E0E0; }
-.base02-background { background-color: #D0D0D0; }
-.base03-background { background-color: #B0B0B0; }
-.base04-background { background-color: #000000; }
-.base05-background { background-color: #101010; }
-.base06-background { background-color: #151515; }
-.base07-background { background-color: #202020; }
-.base08-background { background-color: #FF0086; }
-.base09-background { background-color: #FD8900; }
-.base0A-background { background-color: #ABA800; }
-.base0B-background { background-color: #00C918; }
-.base0C-background { background-color: #1FAAAA; }
-.base0D-background { background-color: #3777E6; }
-.base0E-background { background-color: #AD00A1; }
-.base0F-background { background-color: #CC6633; }
-
-.base00 { color: #FFFFFF; }
-.base01 { color: #E0E0E0; }
-.base02 { color: #D0D0D0; }
-.base03 { color: #B0B0B0; }
-.base04 { color: #000000; }
-.base05 { color: #101010; }
-.base06 { color: #151515; }
-.base07 { color: #202020; }
-.base08 { color: #FF0086; }
-.base09 { color: #FD8900; }
-.base0A { color: #ABA800; }
-.base0B { color: #00C918; }
-.base0C { color: #1FAAAA; }
-.base0D { color: #3777E6; }
-.base0E { color: #AD00A1; }
-.base0F { color: #CC6633; }
diff --git a/static/css/base16-tomorrow-night.css b/static/css/base16-tomorrow-night.css
deleted file mode 100644
index 09ecf08e..00000000
--- a/static/css/base16-tomorrow-night.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #1d1f21; }
-.base01-background { background-color: #282a2e; }
-.base02-background { background-color: #373b41; }
-.base03-background { background-color: #969896; }
-.base04-background { background-color: #b4b7b4; }
-.base05-background { background-color: #c5c8c6; }
-.base06-background { background-color: #e0e0e0; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #cc6666; }
-.base09-background { background-color: #de935f; }
-.base0A-background { background-color: #f0c674; }
-.base0B-background { background-color: #b5bd68; }
-.base0C-background { background-color: #8abeb7; }
-.base0D-background { background-color: #81a2be; }
-.base0E-background { background-color: #b294bb; }
-.base0F-background { background-color: #a3685a; }
-
-.base00 { color: #1d1f21; }
-.base01 { color: #282a2e; }
-.base02 { color: #373b41; }
-.base03 { color: #969896; }
-.base04 { color: #b4b7b4; }
-.base05 { color: #c5c8c6; }
-.base06 { color: #e0e0e0; }
-.base07 { color: #ffffff; }
-.base08 { color: #cc6666; }
-.base09 { color: #de935f; }
-.base0A { color: #f0c674; }
-.base0B { color: #b5bd68; }
-.base0C { color: #8abeb7; }
-.base0D { color: #81a2be; }
-.base0E { color: #b294bb; }
-.base0F { color: #a3685a; }
diff --git a/static/css/base16-tomorrow.css b/static/css/base16-tomorrow.css
deleted file mode 100644
index f1486823..00000000
--- a/static/css/base16-tomorrow.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #ffffff; }
-.base01-background { background-color: #e0e0e0; }
-.base02-background { background-color: #d6d6d6; }
-.base03-background { background-color: #8e908c; }
-.base04-background { background-color: #969896; }
-.base05-background { background-color: #4d4d4c; }
-.base06-background { background-color: #282a2e; }
-.base07-background { background-color: #1d1f21; }
-.base08-background { background-color: #c82829; }
-.base09-background { background-color: #f5871f; }
-.base0A-background { background-color: #eab700; }
-.base0B-background { background-color: #718c00; }
-.base0C-background { background-color: #3e999f; }
-.base0D-background { background-color: #4271ae; }
-.base0E-background { background-color: #8959a8; }
-.base0F-background { background-color: #a3685a; }
-
-.base00 { color: #ffffff; }
-.base01 { color: #e0e0e0; }
-.base02 { color: #d6d6d6; }
-.base03 { color: #8e908c; }
-.base04 { color: #969896; }
-.base05 { color: #4d4d4c; }
-.base06 { color: #282a2e; }
-.base07 { color: #1d1f21; }
-.base08 { color: #c82829; }
-.base09 { color: #f5871f; }
-.base0A { color: #eab700; }
-.base0B { color: #718c00; }
-.base0C { color: #3e999f; }
-.base0D { color: #4271ae; }
-.base0E { color: #8959a8; }
-.base0F { color: #a3685a; }
diff --git a/static/css/base16-twilight.css b/static/css/base16-twilight.css
deleted file mode 100644
index c8dfda3f..00000000
--- a/static/css/base16-twilight.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #1e1e1e; }
-.base01-background { background-color: #323537; }
-.base02-background { background-color: #464b50; }
-.base03-background { background-color: #5f5a60; }
-.base04-background { background-color: #838184; }
-.base05-background { background-color: #a7a7a7; }
-.base06-background { background-color: #c3c3c3; }
-.base07-background { background-color: #ffffff; }
-.base08-background { background-color: #cf6a4c; }
-.base09-background { background-color: #cda869; }
-.base0A-background { background-color: #f9ee98; }
-.base0B-background { background-color: #8f9d6a; }
-.base0C-background { background-color: #afc4db; }
-.base0D-background { background-color: #7587a6; }
-.base0E-background { background-color: #9b859d; }
-.base0F-background { background-color: #9b703f; }
-
-.base00 { color: #1e1e1e; }
-.base01 { color: #323537; }
-.base02 { color: #464b50; }
-.base03 { color: #5f5a60; }
-.base04 { color: #838184; }
-.base05 { color: #a7a7a7; }
-.base06 { color: #c3c3c3; }
-.base07 { color: #ffffff; }
-.base08 { color: #cf6a4c; }
-.base09 { color: #cda869; }
-.base0A { color: #f9ee98; }
-.base0B { color: #8f9d6a; }
-.base0C { color: #afc4db; }
-.base0D { color: #7587a6; }
-.base0E { color: #9b859d; }
-.base0F { color: #9b703f; }
diff --git a/static/css/base16-unikitty-dark.css b/static/css/base16-unikitty-dark.css
deleted file mode 100644
index e6ef32e3..00000000
--- a/static/css/base16-unikitty-dark.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #2e2a31; }
-.base01-background { background-color: #4a464d; }
-.base02-background { background-color: #666369; }
-.base03-background { background-color: #838085; }
-.base04-background { background-color: #9f9da2; }
-.base05-background { background-color: #bcbabe; }
-.base06-background { background-color: #d8d7da; }
-.base07-background { background-color: #f5f4f7; }
-.base08-background { background-color: #d8137f; }
-.base09-background { background-color: #d65407; }
-.base0A-background { background-color: #dc8a0e; }
-.base0B-background { background-color: #17ad98; }
-.base0C-background { background-color: #149bda; }
-.base0D-background { background-color: #796af5; }
-.base0E-background { background-color: #bb60ea; }
-.base0F-background { background-color: #c720ca; }
-
-.base00 { color: #2e2a31; }
-.base01 { color: #4a464d; }
-.base02 { color: #666369; }
-.base03 { color: #838085; }
-.base04 { color: #9f9da2; }
-.base05 { color: #bcbabe; }
-.base06 { color: #d8d7da; }
-.base07 { color: #f5f4f7; }
-.base08 { color: #d8137f; }
-.base09 { color: #d65407; }
-.base0A { color: #dc8a0e; }
-.base0B { color: #17ad98; }
-.base0C { color: #149bda; }
-.base0D { color: #796af5; }
-.base0E { color: #bb60ea; }
-.base0F { color: #c720ca; }
diff --git a/static/css/base16-unikitty-light.css b/static/css/base16-unikitty-light.css
deleted file mode 100644
index 7e4c51b7..00000000
--- a/static/css/base16-unikitty-light.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.base00-background { background-color: #ffffff; }
-.base01-background { background-color: #e1e1e2; }
-.base02-background { background-color: #c4c3c5; }
-.base03-background { background-color: #a7a5a8; }
-.base04-background { background-color: #89878b; }
-.base05-background { background-color: #6c696e; }
-.base06-background { background-color: #4f4b51; }
-.base07-background { background-color: #322d34; }
-.base08-background { background-color: #d8137f; }
-.base09-background { background-color: #d65407; }
-.base0A-background { background-color: #dc8a0e; }
-.base0B-background { background-color: #17ad98; }
-.base0C-background { background-color: #149bda; }
-.base0D-background { background-color: #775dff; }
-.base0E-background { background-color: #aa17e6; }
-.base0F-background { background-color: #e013d0; }
-
-.base00 { color: #ffffff; }
-.base01 { color: #e1e1e2; }
-.base02 { color: #c4c3c5; }
-.base03 { color: #a7a5a8; }
-.base04 { color: #89878b; }
-.base05 { color: #6c696e; }
-.base06 { color: #4f4b51; }
-.base07 { color: #322d34; }
-.base08 { color: #d8137f; }
-.base09 { color: #d65407; }
-.base0A { color: #dc8a0e; }
-.base0B { color: #17ad98; }
-.base0C { color: #149bda; }
-.base0D { color: #775dff; }
-.base0E { color: #aa17e6; }
-.base0F { color: #e013d0; }
diff --git a/static/css/themes.json b/static/css/themes.json
deleted file mode 100644
index ea8e5a0c..00000000
--- a/static/css/themes.json
+++ /dev/null
@@ -1,66 +0,0 @@
-[
-"base16-pleroma-dark.css",
-"base16-pleroma-light.css",
-"base16-3024.css",
-"base16-apathy.css",
-"base16-ashes.css",
-"base16-atelier-cave.css",
-"base16-atelier-dune.css",
-"base16-atelier-estuary.css",
-"base16-atelier-forest.css",
-"base16-atelier-heath.css",
-"base16-atelier-lakeside.css",
-"base16-atelier-plateau.css",
-"base16-atelier-savanna.css",
-"base16-atelier-seaside.css",
-"base16-atelier-sulphurpool.css",
-"base16-bespin.css",
-"base16-brewer.css",
-"base16-bright.css",
-"base16-chalk.css",
-"base16-codeschool.css",
-"base16-darktooth.css",
-"base16-default-dark.css",
-"base16-default-light.css",
-"base16-eighties.css",
-"base16-embers.css",
-"base16-flat.css",
-"base16-github.css",
-"base16-google-dark.css",
-"base16-google-light.css",
-"base16-grayscale-dark.css",
-"base16-grayscale-light.css",
-"base16-green-screen.css",
-"base16-harmonic16-dark.css",
-"base16-harmonic16-light.css",
-"base16-hopscotch.css",
-"base16-ir-black.css",
-"base16-isotope.css",
-"base16-london-tube.css",
-"base16-macintosh.css",
-"base16-marrakesh.css",
-"base16-materia.css",
-"base16-mexico-light.css",
-"base16-mocha.css",
-"base16-monokai.css",
-"base16-ocean.css",
-"base16-oceanicnext.css",
-"base16-paraiso.css",
-"base16-phd.css",
-"base16-pico.css",
-"base16-pop.css",
-"base16-railscasts.css",
-"base16-seti-ui.css",
-"base16-shapeshifter.css",
-"base16-solar-flare.css",
-"base16-solarized-dark.css",
-"base16-solarized-light.css",
-"base16-spacemacs.css",
-"base16-summerfruit-dark.css",
-"base16-summerfruit-light.css",
-"base16-tomorrow-night.css",
-"base16-tomorrow.css",
-"base16-twilight.css",
-"base16-unikitty-dark.css",
-"base16-unikitty-light.css"
-]

From 43bddc79e0abc35fd310d9a92051bb2d5b529984 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 11 Feb 2020 01:13:59 +0200
Subject: [PATCH 227/483] fix/remove contrast ratios

---
 src/components/style_switcher/style_switcher.vue | 6 +-----
 src/services/theme_data/pleromafe.js             | 6 +++++-
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 705a60a2..205f325c 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -252,9 +252,8 @@
               name="postLinkColor"
               :fallback="previewTheme.colors.accent"
               :label="$t('settings.links')"
-              :show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
             />
-            <ContrastRatio :contrast="previewContrast.bgPostLink" />
+            <ContrastRatio :contrast="previewContrast.postLink" />
             <h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
             <ColorInput
               v-model="alertErrorColorLocal"
@@ -485,21 +484,18 @@
               :fallback="previewTheme.colors.btnDisabledText"
               :label="$t('settings.text')"
             />
-            <ContrastRatio :contrast="previewContrast.btnDisabledText" />
             <ColorInput
               v-model="btnDisabledPanelTextColorLocal"
               name="btnDisabledPanelTextColor"
               :fallback="previewTheme.colors.btnDisabledPanelText"
               :label="$t('settings.style.advanced_colors.panel_header')"
             />
-            <ContrastRatio :contrast="previewContrast.btnDisabledPanelText" />
             <ColorInput
               v-model="btnDisabledTopBarTextColorLocal"
               name="btnDisabledTopBarTextColor"
               :fallback="previewTheme.colors.btnDisabledTopBarText"
               :label="$t('settings.style.advanced_colors.top_bar')"
             />
-            <ContrastRatio :contrast="previewContrast.btnDisabledTopBarText" />
             <h4>{{ $t('settings.style.advanced_colors.toggled') }}</h4>
             <ColorInput
               v-model="btnToggledColorLocal"
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index 98fba5ef..9b33e4a9 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -329,11 +329,15 @@ export const SLOT_INHERITANCE = {
 
   lightText: {
     depends: ['text'],
+    layer: 'bg',
+    textColor: 'preserve',
     color: (mod, text) => brightness(20 * mod, text).rgb
   },
 
   postLink: {
-    depends: ['link']
+    depends: ['link'],
+    layer: 'bg',
+    textColor: 'preserve'
   },
 
   border: {

From 6f3ac707f76b302e993eeb3eaeb415bc3d68502b Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 11 Feb 2020 09:59:26 +0200
Subject: [PATCH 228/483] fix and update changelog

---
 CHANGELOG.md | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9fae1acb..9028ebaf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
 ### Added
+- Tons of color slots including ones for hover/pressed/toggled buttons
+- Experimental `--variable[,mod]` syntax support for color slots in themes. the `mod` makes color brighter/darker depending on background color (makes darker color brighter/darker depending on background color)
+- Kenomo theme based on new UX proposal mockups
 - Icons in nav panel
 - Private mode support
 - Support for 'Move' type notifications
@@ -12,10 +15,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - User level domain mutes, under User Settings -> Mutes
 - Emoji reactions for statuses
 ### Changed
+- theme engine update to 3 (themes v2.1 introduction)
+- massive internal changes in theme engine - slowly away from "generate things separately with spaghetti code" towards "feed all data into single 'generateTheme' function and declare slot inheritance and all in a separate file"
+- Breezy theme updates to make it closer to actual Breeze in some aspects
+- when using `--variable` in shadows it no longer uses the actual CSS3 variable, instead it generates color from other slots
+- theme doesn't get saved to local storage when opening FE anonymously
 - Captcha now resets on failed registrations
 - Notifications column now cleans itself up to optimize performance when tab is left open for a long time
 - 403 messaging
 ### Fixed
+- anon viewers won't get theme data saved to local storage, so admin changing default theme will have an effect for users coming back to instance.
 - Single notifications left unread when hitting read on another device/tab
 - Registration fixed
 - Deactivation of remote accounts from frontend
@@ -30,9 +39,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - About page
 - Added remote user redirect
 ### Changed
-- theme engine update to 3
-- theme doesn't get saved to local storage when opening FE anonymously
 - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes
 ### Fixed
-- anon viewers won't get theme data saved to local storage, so admin changing default theme will have an effect for users coming back to instance.
 - improved hotkey behavior on autocomplete popup

From 29133fb0084f31a9ba3c50f0d34fd6ca398d1490 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 11 Feb 2020 10:42:15 +0200
Subject: [PATCH 229/483] don't use cache for theme data

---
 src/services/style_setter/style_setter.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 57e3057a..48f51c59 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -313,7 +313,9 @@ export const generatePreset = (input) => {
 }
 
 export const getThemes = () => {
-  return window.fetch('/static/styles.json')
+  const cache = 'no-store'
+
+  return window.fetch('/static/styles.json', { cache })
     .then((data) => data.json())
     .then((themes) => {
       return Object.entries(themes).map(([k, v]) => {
@@ -321,7 +323,7 @@ export const getThemes = () => {
         if (typeof v === 'object') {
           promise = Promise.resolve(v)
         } else if (typeof v === 'string') {
-          promise = window.fetch(v)
+          promise = window.fetch(v, { cache })
             .then((data) => data.json())
             .catch((e) => {
               console.error(e)

From f6b482be515ea4f0281050f71296ffe0ec2ab305 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Tue, 11 Feb 2020 12:24:51 +0000
Subject: [PATCH 230/483] Emoji Reactions - fixes and improvements

---
 src/App.scss                                  |   2 +-
 src/_variables.scss                           |   2 +
 .../emoji_reactions/emoji_reactions.js        |  48 +++++++-
 .../emoji_reactions/emoji_reactions.vue       | 103 ++++++++++++++++--
 src/components/notification/notification.vue  |   7 ++
 .../notifications/notifications.scss          |   4 +
 src/components/react_button/react_button.js   |   7 +-
 src/components/react_button/react_button.vue  |   4 +
 src/components/settings/settings.vue          |  10 ++
 src/components/status/status.vue              |   1 +
 src/i18n/en.json                              |   5 +-
 src/i18n/fi.json                              |   5 +-
 src/modules/config.js                         |   4 +-
 src/modules/statuses.js                       |  36 ++++--
 src/services/api/api.service.js               |  28 ++---
 .../entity_normalizer.service.js              |   1 +
 .../notification_utils/notification_utils.js  |   3 +-
 test/unit/specs/modules/statuses.spec.js      |  13 ++-
 18 files changed, 236 insertions(+), 47 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 754ca62e..922e39b6 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -75,7 +75,7 @@ button {
   border-radius: $fallback--btnRadius;
   border-radius: var(--btnRadius, $fallback--btnRadius);
   cursor: pointer;
-  box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
+  box-shadow: $fallback--buttonShadow;
   box-shadow: var(--buttonShadow);
   font-size: 14px;
   font-family: sans-serif;
diff --git a/src/_variables.scss b/src/_variables.scss
index e18101f0..30dc3e42 100644
--- a/src/_variables.scss
+++ b/src/_variables.scss
@@ -27,3 +27,5 @@ $fallback--tooltipRadius: 5px;
 $fallback--avatarRadius: 4px;
 $fallback--avatarAltRadius: 10px;
 $fallback--attachmentRadius: 10px;
+
+$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index 95d52cb6..b799ac9a 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -1,17 +1,55 @@
+import UserAvatar from '../user_avatar/user_avatar.vue'
+
+const EMOJI_REACTION_COUNT_CUTOFF = 12
 
 const EmojiReactions = {
   name: 'EmojiReactions',
+  components: {
+    UserAvatar
+  },
   props: ['status'],
+  data: () => ({
+    showAll: false,
+    popperOptions: {
+      modifiers: {
+        preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
+      }
+    }
+  }),
   computed: {
+    tooManyReactions () {
+      return this.status.emoji_reactions.length > EMOJI_REACTION_COUNT_CUTOFF
+    },
     emojiReactions () {
-      return this.status.emoji_reactions
+      return this.showAll
+        ? this.status.emoji_reactions
+        : this.status.emoji_reactions.slice(0, EMOJI_REACTION_COUNT_CUTOFF)
+    },
+    showMoreString () {
+      return `+${this.status.emoji_reactions.length - EMOJI_REACTION_COUNT_CUTOFF}`
+    },
+    accountsForEmoji () {
+      return this.status.emoji_reactions.reduce((acc, reaction) => {
+        acc[reaction.name] = reaction.accounts || []
+        return acc
+      }, {})
+    },
+    loggedIn () {
+      return !!this.$store.state.users.currentUser
     }
   },
   methods: {
+    toggleShowAll () {
+      this.showAll = !this.showAll
+    },
     reactedWith (emoji) {
-      const user = this.$store.state.users.currentUser
-      const reaction = this.status.emoji_reactions.find(r => r.emoji === emoji)
-      return reaction.accounts && reaction.accounts.find(u => u.id === user.id)
+      return this.status.emoji_reactions.find(r => r.name === emoji).me
+    },
+    fetchEmojiReactionsByIfMissing () {
+      const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
+      if (hasNoAccounts) {
+        this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
+      }
     },
     reactWith (emoji) {
       this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
@@ -20,6 +58,8 @@ const EmojiReactions = {
       this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
     },
     emojiOnClick (emoji, event) {
+      if (!this.loggedIn) return
+
       if (this.reactedWith(emoji)) {
         this.unreact(emoji)
       } else {
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 00d6d2b7..e5b6d9f5 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -1,16 +1,58 @@
 <template>
   <div class="emoji-reactions">
-    <button
+    <v-popover
       v-for="(reaction) in emojiReactions"
-      :key="reaction.emoji"
-      class="emoji-reaction btn btn-default"
-      :class="{ 'picked-reaction': reactedWith(reaction.emoji) }"
-      @click="emojiOnClick(reaction.emoji, $event)"
+      :key="reaction.name"
+      :popper-options="popperOptions"
+      trigger="hover"
+      placement="top"
     >
-      <span class="reaction-emoji">{{ reaction.emoji }}</span>
-      <span>{{ reaction.count }}</span>
-    </button>
+
+      <div
+        slot="popover"
+        class="reacted-users"
+      >
+        <div v-if="accountsForEmoji[reaction.name].length">
+          <div
+            v-for="(account) in accountsForEmoji[reaction.name]"
+            :key="account.id"
+            class="reacted-user"
+          >
+            <UserAvatar
+              :user="account"
+              class="avatar-small"
+              :compact="true"
+            />
+            <div class="reacted-user-names">
+              <span class="reacted-user-name" v-html="account.name_html" />
+              <span class="reacted-user-screen-name">{{ account.screen_name }}</span>
+            </div>
+          </div>
+        </div>
+        <div v-else>
+          <i class="icon-spin4 animate-spin" />
+        </div>
+      </div>
+      <button
+        class="emoji-reaction btn btn-default"
+        :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
+        @click="emojiOnClick(reaction.name, $event)"
+        @mouseenter="fetchEmojiReactionsByIfMissing()"
+      >
+        <span class="reaction-emoji">{{ reaction.name }}</span>
+        <span>{{ reaction.count }}</span>
+      </button>
+    </v-popover>
+    <a
+        v-if="tooManyReactions"
+        @click="toggleShowAll"
+        class="emoji-reaction-expand faint"
+        href='javascript:void(0)'
+      >
+        {{ showAll ? $t('general.show_less') : showMoreString }}
+      </a>
   </div>
+
 </template>
 
 <script src="./emoji_reactions.js" ></script>
@@ -23,6 +65,31 @@
   flex-wrap: wrap;
 }
 
+.reacted-users {
+  padding: 0.5em;
+}
+
+.reacted-user {
+  padding: 0.25em;
+  display: flex;
+  flex-direction: row;
+
+  .reacted-user-names {
+    display: flex;
+    flex-direction: column;
+    margin-left: 0.5em;
+
+    img {
+      width: 1em;
+      height: 1em;
+    }
+  }
+
+  .reacted-user-screen-name {
+    font-size: 9px;
+  }
+}
+
 .emoji-reaction {
   padding: 0 0.5em;
   margin-right: 0.5em;
@@ -38,6 +105,26 @@
   &:focus {
     outline: none;
   }
+
+  &.not-clickable {
+    cursor: default;
+    &:hover {
+      box-shadow: $fallback--buttonShadow;
+      box-shadow: var(--buttonShadow);
+    }
+  }
+}
+
+.emoji-reaction-expand {
+  padding: 0 0.5em;
+  margin-right: 0.5em;
+  margin-top: 0.5em;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  &:hover {
+    text-decoration: underline;
+  }
 }
 
 .picked-reaction {
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 16124e50..411c0271 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -78,6 +78,13 @@
               <i class="fa icon-arrow-curved lit" />
               <small>{{ $t('notifications.migrated_to') }}</small>
             </span>
+            <span v-if="notification.type === 'pleroma:emoji_reaction'">
+              <small>
+                <i18n path="notifications.reacted_with">
+                  <span class="emoji-reaction-emoji">{{ notification.emoji }}</span>
+                </i18n>
+              </small>
+            </span>
           </div>
           <div
             v-if="notification.type === 'follow' || notification.type === 'move'"
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 148ac7f2..8d819a56 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -94,6 +94,10 @@
     min-width: 0;
   }
 
+  .emoji-reaction-emoji {
+    font-size: 16px;
+  }
+
   .notification-details {
     min-width: 0px;
     word-wrap: break-word;
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index 6fb2a780..a6cf5b94 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -22,7 +22,12 @@ const ReactButton = {
       this.showTooltip = false
     },
     addReaction (event, emoji) {
-      this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
+      const existingReaction = this.status.emoji_reactions.find(r => r.name === emoji)
+      if (existingReaction && existingReaction.me) {
+        this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
+      } else {
+        this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
+      }
       this.closeReactionSelect()
     }
   },
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index c925dd71..fb43ebaf 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -54,6 +54,10 @@
 
 .reaction-picker-filter {
   padding: 0.5em;
+  display: flex;
+  input {
+    flex: 1;
+  }
 }
 
 .reaction-picker-divider {
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index cef492f3..60cb8a87 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -92,6 +92,11 @@
                     {{ $t('settings.reply_link_preview') }}
                   </Checkbox>
                 </li>
+                <li>
+                  <Checkbox v-model="emojiReactionsOnTimeline">
+                    {{ $t('settings.emoji_reactions_on_timeline') }}
+                  </Checkbox>
+                </li>
               </ul>
             </div>
 
@@ -328,6 +333,11 @@
                       {{ $t('settings.notification_visibility_moves') }}
                     </Checkbox>
                   </li>
+                  <li>
+                    <Checkbox v-model="notificationVisibility.emojiReactions">
+                      {{ $t('settings.notification_visibility_emoji_reactions') }}
+                    </Checkbox>
+                  </li>
                 </ul>
               </div>
               <div>
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 0a82dcbe..83f07dac 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -369,6 +369,7 @@
           </transition>
 
           <EmojiReactions
+            v-if="(mergedConfig.emojiReactionsOnTimeline || isFocused) && (!noHeading && !isPreview)"
             :status="status"
           />
 
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 74e71fc8..d0d654d3 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -126,7 +126,8 @@
     "read": "Read!",
     "repeated_you": "repeated your status",
     "no_more_notifications": "No more notifications",
-    "migrated_to": "migrated to"
+    "migrated_to": "migrated to",
+    "reacted_with": "reacted with {0}"
   },
   "polls": {
     "add_poll": "Add Poll",
@@ -283,6 +284,7 @@
     "domain_mutes": "Domains",
     "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
     "pad_emoji": "Pad emoji with spaces when adding from picker",
+    "emoji_reactions_on_timeline": "Show emoji reactions on timeline",
     "export_theme": "Save preset",
     "filtering": "Filtering",
     "filtering_explanation": "All statuses containing these words will be muted, one per line",
@@ -331,6 +333,7 @@
     "notification_visibility_mentions": "Mentions",
     "notification_visibility_repeats": "Repeats",
     "notification_visibility_moves": "User Migrates",
+    "notification_visibility_emoji_reactions": "Reactions",
     "no_rich_text_description": "Strip rich text formatting from all posts",
     "no_blocks": "No blocks",
     "no_mutes": "No mutes",
diff --git a/src/i18n/fi.json b/src/i18n/fi.json
index e7ed5408..ac8b2ac9 100644
--- a/src/i18n/fi.json
+++ b/src/i18n/fi.json
@@ -53,7 +53,8 @@
     "notifications": "Ilmoitukset",
     "read": "Lue!",
     "repeated_you": "toisti viestisi",
-    "no_more_notifications": "Ei enempää ilmoituksia"
+    "no_more_notifications": "Ei enempää ilmoituksia",
+    "reacted_with": "lisäsi reaktion {0}"
   },
   "polls": {
     "add_poll": "Lisää äänestys",
@@ -140,6 +141,7 @@
     "delete_account_description": "Poista tilisi ja viestisi pysyvästi.",
     "delete_account_error": "Virhe poistaessa tiliäsi. Jos virhe jatkuu, ota yhteyttä palvelimesi ylläpitoon.",
     "delete_account_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.",
+    "emoji_reactions_on_timeline": "Näytä emojireaktiot aikajanalla",
     "export_theme": "Tallenna teema",
     "filtering": "Suodatus",
     "filtering_explanation": "Kaikki viestit, jotka sisältävät näitä sanoja, suodatetaan. Yksi sana per rivi.",
@@ -183,6 +185,7 @@
     "notification_visibility_likes": "Tykkäykset",
     "notification_visibility_mentions": "Maininnat",
     "notification_visibility_repeats": "Toistot",
+    "notification_visibility_emoji_reactions": "Reaktiot",
     "no_rich_text_description": "Älä näytä tekstin muotoilua.",
     "hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
     "nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
diff --git a/src/modules/config.js b/src/modules/config.js
index de9f041b..8381fa53 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -20,6 +20,7 @@ export const defaultState = {
   autoLoad: true,
   streaming: false,
   hoverPreview: true,
+  emojiReactionsOnTimeline: true,
   autohideFloatingPostButton: false,
   pauseOnUnfocused: true,
   stopGifs: false,
@@ -29,7 +30,8 @@ export const defaultState = {
     mentions: true,
     likes: true,
     repeats: true,
-    moves: true
+    moves: true,
+    emojiReactions: false
   },
   webPushNotifications: false,
   muteWords: [],
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index ea0c1749..25b62ac7 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -81,7 +81,8 @@ const visibleNotificationTypes = (rootState) => {
     rootState.config.notificationVisibility.mentions && 'mention',
     rootState.config.notificationVisibility.repeats && 'repeat',
     rootState.config.notificationVisibility.follows && 'follow',
-    rootState.config.notificationVisibility.moves && 'move'
+    rootState.config.notificationVisibility.moves && 'move',
+    rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reactions'
   ].filter(_ => _)
 }
 
@@ -325,6 +326,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
       notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
     }
 
+    if (notification.type === 'pleroma:emoji_reaction') {
+      dispatch('fetchEmojiReactionsBy', notification.status.id)
+    }
+
     // Only add a new notification if we don't have one for the same action
     if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
       state.notifications.maxId = notification.id > state.notifications.maxId
@@ -358,7 +363,9 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
             break
         }
 
-        if (i18nString) {
+        if (notification.type === 'pleroma:emoji_reaction') {
+          notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji])
+        } else if (i18nString) {
           notifObj.body = rootGetters.i18n.t('notifications.' + i18nString)
         } else {
           notifObj.body = notification.status.text
@@ -371,10 +378,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
         }
 
         if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) {
-          let notification = new window.Notification(title, notifObj)
+          let desktopNotification = new window.Notification(title, notifObj)
           // Chrome is known for not closing notifications automatically
           // according to MDN, anyway.
-          setTimeout(notification.close.bind(notification), 5000)
+          setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
         }
       }
     } else if (notification.seen) {
@@ -537,12 +544,13 @@ export const mutations = {
   },
   addOwnReaction (state, { id, emoji, currentUser }) {
     const status = state.allStatusesObject[id]
-    const reactionIndex = findIndex(status.emoji_reactions, { emoji })
-    const reaction = status.emoji_reactions[reactionIndex] || { emoji, count: 0, accounts: [] }
+    const reactionIndex = findIndex(status.emoji_reactions, { name: emoji })
+    const reaction = status.emoji_reactions[reactionIndex] || { name: emoji, count: 0, accounts: [] }
 
     const newReaction = {
       ...reaction,
       count: reaction.count + 1,
+      me: true,
       accounts: [
         ...reaction.accounts,
         currentUser
@@ -558,21 +566,23 @@ export const mutations = {
   },
   removeOwnReaction (state, { id, emoji, currentUser }) {
     const status = state.allStatusesObject[id]
-    const reactionIndex = findIndex(status.emoji_reactions, { emoji })
+    const reactionIndex = findIndex(status.emoji_reactions, { name: emoji })
     if (reactionIndex < 0) return
 
     const reaction = status.emoji_reactions[reactionIndex]
+    const accounts = reaction.accounts || []
 
     const newReaction = {
       ...reaction,
       count: reaction.count - 1,
-      accounts: reaction.accounts.filter(acc => acc.id === currentUser.id)
+      me: false,
+      accounts: accounts.filter(acc => acc.id !== currentUser.id)
     }
 
     if (newReaction.count > 0) {
       set(status.emoji_reactions, reactionIndex, newReaction)
     } else {
-      set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.emoji !== emoji))
+      set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.name !== emoji))
     }
   },
   updateStatusWithPoll (state, { id, poll }) {
@@ -681,18 +691,22 @@ const statuses = {
     },
     reactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) {
       const currentUser = rootState.users.currentUser
+      if (!currentUser) return
+
       commit('addOwnReaction', { id, emoji, currentUser })
       rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(
-        status => {
+        ok => {
           dispatch('fetchEmojiReactionsBy', id)
         }
       )
     },
     unreactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) {
       const currentUser = rootState.users.currentUser
+      if (!currentUser) return
+
       commit('removeOwnReaction', { id, emoji, currentUser })
       rootState.api.backendInteractor.unreactWithEmoji({ id, emoji }).then(
-        status => {
+        ok => {
           dispatch('fetchEmojiReactionsBy', id)
         }
       )
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index b794fd58..20eaa9a0 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -74,9 +74,9 @@ const MASTODON_SEARCH_2 = `/api/v2/search`
 const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
 const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
 const MASTODON_STREAMING = '/api/v1/streaming'
-const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by`
-const PLEROMA_EMOJI_REACT_URL = id => `/api/v1/pleroma/statuses/${id}/react_with_emoji`
-const PLEROMA_EMOJI_UNREACT_URL = id => `/api/v1/pleroma/statuses/${id}/unreact_with_emoji`
+const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions`
+const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
+const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
 
 const oldfetch = window.fetch
 
@@ -888,25 +888,27 @@ const fetchRebloggedByUsers = ({ id }) => {
   return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser))
 }
 
-const fetchEmojiReactions = ({ id }) => {
-  return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) })
+const fetchEmojiReactions = ({ id, credentials }) => {
+  return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id), credentials })
+    .then((reactions) => reactions.map(r => {
+      r.accounts = r.accounts.map(parseUser)
+      return r
+    }))
 }
 
 const reactWithEmoji = ({ id, emoji, credentials }) => {
   return promisedRequest({
-    url: PLEROMA_EMOJI_REACT_URL(id),
-    method: 'POST',
-    credentials,
-    payload: { emoji }
+    url: PLEROMA_EMOJI_REACT_URL(id, emoji),
+    method: 'PUT',
+    credentials
   }).then(parseStatus)
 }
 
 const unreactWithEmoji = ({ id, emoji, credentials }) => {
   return promisedRequest({
-    url: PLEROMA_EMOJI_UNREACT_URL(id),
-    method: 'POST',
-    credentials,
-    payload: { emoji }
+    url: PLEROMA_EMOJI_UNREACT_URL(id, emoji),
+    method: 'DELETE',
+    credentials
   }).then(parseStatus)
 }
 
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 0a8abbbd..84169a7b 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -354,6 +354,7 @@ export const parseNotification = (data) => {
       ? null
       : parseUser(data.target)
     output.from_profile = parseUser(data.account)
+    output.emoji = data.emoji
   } else {
     const parsedNotice = parseStatus(data.notice)
     output.type = data.ntype
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index 860620fc..b17bd7bf 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -7,7 +7,8 @@ export const visibleTypes = store => ([
   store.state.config.notificationVisibility.mentions && 'mention',
   store.state.config.notificationVisibility.repeats && 'repeat',
   store.state.config.notificationVisibility.follows && 'follow',
-  store.state.config.notificationVisibility.moves && 'move'
+  store.state.config.notificationVisibility.moves && 'move',
+  store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction'
 ].filter(_ => _))
 
 const sortById = (a, b) => {
diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js
index e53aa388..fe42e85b 100644
--- a/test/unit/specs/modules/statuses.spec.js
+++ b/test/unit/specs/modules/statuses.spec.js
@@ -245,11 +245,12 @@ describe('Statuses module', () => {
     it('increments count in existing reaction', () => {
       const state = defaultState()
       const status = makeMockStatus({ id: '1' })
-      status.emoji_reactions = [ { emoji: '😂', count: 1, accounts: [] } ]
+      status.emoji_reactions = [ { name: '😂', count: 1, accounts: [] } ]
 
       mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
       mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
       expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(2)
+      expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(true)
       expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me')
     })
 
@@ -261,27 +262,29 @@ describe('Statuses module', () => {
       mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
       mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
       expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1)
+      expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(true)
       expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me')
     })
 
     it('decreases count in existing reaction', () => {
       const state = defaultState()
       const status = makeMockStatus({ id: '1' })
-      status.emoji_reactions = [ { emoji: '😂', count: 2, accounts: [{ id: 'me' }] } ]
+      status.emoji_reactions = [ { name: '😂', count: 2, accounts: [{ id: 'me' }] } ]
 
       mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
-      mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} })
+      mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
       expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1)
+      expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(false)
       expect(state.allStatusesObject['1'].emoji_reactions[0].accounts).to.eql([])
     })
 
     it('removes a reaction', () => {
       const state = defaultState()
       const status = makeMockStatus({ id: '1' })
-      status.emoji_reactions = [{ emoji: '😂', count: 1, accounts: [{ id: 'me' }] }]
+      status.emoji_reactions = [{ name: '😂', count: 1, accounts: [{ id: 'me' }] }]
 
       mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
-      mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} })
+      mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
       expect(state.allStatusesObject['1'].emoji_reactions.length).to.eql(0)
     })
   })

From 60446c56a5c8d4b4d669fbd4803e77ce686daac9 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 12 Feb 2020 00:45:37 +0200
Subject: [PATCH 231/483] fix v2 breezy themes having messed up pressed
 buttons. updated v2.1 breezy themes to have derived colors instead of fixed
 ones.

---
 .../style_switcher/style_switcher.js          |  7 +++++--
 src/services/color_convert/color_convert.js   |  2 --
 src/services/style_setter/style_setter.js     |  9 +++++----
 static/themes/breezy-dark.json                | 14 +++++++-------
 static/themes/breezy-light.json               | 19 ++++++++-----------
 5 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 17ae9f30..807f9efc 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -62,6 +62,7 @@ export default {
       selected: this.$store.getters.mergedConfig.theme,
       themeWarning: undefined,
       tempImportFile: undefined,
+      engineVersion: 0,
 
       previewShadows: {},
       previewColors: {},
@@ -510,7 +511,7 @@ export default {
         colors: this.currentColors
       })
       this.previewShadows = generateShadows(
-        { shadows: this.shadowsLocal },
+        { shadows: this.shadowsLocal, opacity: this.previewTheme.opacity, themeEngineVersion: this.engineVersion },
         this.previewColors.theme.colors,
         this.previewColors.mod
       )
@@ -607,6 +608,8 @@ export default {
         }
       }
 
+      this.engineVersion = version
+
       // Stuff that differs between V1 and V2
       if (version === 1) {
         this.fgColorLocal = rgb2hex(colors.btn)
@@ -653,7 +656,7 @@ export default {
       if (!this.keepShadows) {
         this.clearShadows()
         if (version === 2) {
-          this.shadowsLocal = shadows2to3(shadows)
+          this.shadowsLocal = shadows2to3(shadows, this.previewTheme.opacity)
         } else {
           this.shadowsLocal = shadows
         }
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 3b6fdcc7..ec104269 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -214,8 +214,6 @@ export const getCssColor = (input, a) => {
   } else if (typeof input === 'string') {
     if (input.startsWith('#')) {
       rgb = hex2rgb(input)
-    } else if (input.startsWith('--')) {
-      return `var(${input})`
     } else {
       return input
     }
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 48f51c59..c8610507 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,6 +1,6 @@
 import { convert } from 'chromatism'
 import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js'
-import { getColors, computeDynamicColor } from '../theme_data/theme_data.service.js'
+import { getColors, computeDynamicColor, getOpacitySlot } from '../theme_data/theme_data.service.js'
 
 export const applyTheme = (input) => {
   const { rules } = generatePreset(input)
@@ -242,7 +242,7 @@ export const generateShadows = (input, colors) => {
     input: 'input'
   }
   const inputShadows = input.shadows && !input.themeEngineVersion
-    ? shadows2to3(input.shadows)
+    ? shadows2to3(input.shadows, input.opacity)
     : input.shadows || {}
   const shadows = Object.entries({
     ...DEFAULT_SHADOWS,
@@ -368,14 +368,15 @@ export const colors2to3 = (colors) => {
  *
  * Back in v2 shadows allowed you to use dynamic colors however those used pure CSS3 variables
  */
-export const shadows2to3 = (shadows) => {
+export const shadows2to3 = (shadows, opacity) => {
   return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => {
     const isDynamic = ({ color }) => color.startsWith('--')
+    const getOpacity = ({ color }) => opacity[getOpacitySlot(color.substring(2).split(',')[0])]
     const newShadow = shadowDefs.reduce((shadowAcc, def) => [
       ...shadowAcc,
       {
         ...def,
-        alpha: isDynamic(def) ? 1 : def.alpha
+        alpha: isDynamic(def) ? getOpacity(def) || 1 : def.alpha
       }
     ], [])
     return { ...shadowsAcc, [slotName]: newShadow }
diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index 236b94ad..76b962c5 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -21,7 +21,7 @@
           "y": "0",
           "blur": "0",
           "spread": "1",
-          "color": "#ffffff",
+          "color": "--btn,900",
           "alpha": "0.15",
           "inset": true
         },
@@ -42,7 +42,7 @@
           "blur": "40",
           "spread": "-40",
           "inset": true,
-          "color": "#ffffff",
+          "color": "--panel,900",
           "alpha": "0.1"
         }
       ],
@@ -72,7 +72,7 @@
           "y": "0",
           "blur": 0,
           "spread": "1",
-          "color": "#ffffff",
+          "color": "--btn,900",
           "alpha": 0.2,
           "inset": true
         },
@@ -92,7 +92,7 @@
           "y": "0",
           "blur": 0,
           "spread": "1",
-          "color": "#FFFFFF",
+          "color": "--input,900",
           "alpha": "0.2",
           "inset": true
         }
@@ -105,9 +105,9 @@
       "link": "#3daee9",
       "fg": "#31363b",
       "panel": "transparent",
-      "input": "#232629",
-      "topBarLink": "#eff0f1",
-      "btn": "#31363b",
+      "input": "--bg,-6.47",
+      "topBarLink": "--topBarText",
+      "btn": "--bg",
       "border": "#4c545b",
       "cRed": "#da4453",
       "cBlue": "#3daee9",
diff --git a/static/themes/breezy-light.json b/static/themes/breezy-light.json
index d3f74cec..0968fff0 100644
--- a/static/themes/breezy-light.json
+++ b/static/themes/breezy-light.json
@@ -21,7 +21,7 @@
           "y": "0",
           "blur": "0",
           "spread": "1",
-          "color": "#000000",
+          "color": "--btn,900",
           "alpha": "0.3",
           "inset": true
         },
@@ -42,7 +42,7 @@
           "blur": "40",
           "spread": "-40",
           "inset": true,
-          "color": "#ffffff",
+          "color": "--panel,900",
           "alpha": "0.1"
         }
       ],
@@ -72,7 +72,7 @@
           "y": "0",
           "blur": 0,
           "spread": "1",
-          "color": "#ffffff",
+          "color": "--btn,900",
           "alpha": 0.2,
           "inset": true
         },
@@ -92,7 +92,7 @@
           "y": "0",
           "blur": 0,
           "spread": "1",
-          "color": "#000000",
+          "color": "--input,900",
           "alpha": "0.2",
           "inset": true
         }
@@ -104,14 +104,11 @@
     "colors": {
       "bg": "#eff0f1",
       "text": "#232627",
-      "fg": "#bcc2c7",
+      "fg": "#475057",
       "accent": "#2980b9",
-      "panel": "#475057",
-      "panelText": "#fcfcfc",
-      "input": "#fcfcfc",
-      "topBar": "#475057",
-      "topBarLink": "#eff0f1",
-      "btn": "#eff0f1",
+      "input": "--bg,-6.47",
+      "topBarLink": "--topBarText",
+      "btn": "--bg",
       "cRed": "#da4453",
       "cBlue": "#2980b9",
       "cGreen": "#27ae60",

From 2274976c09449838f3f6496c4d908a604d8d91d9 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 12 Feb 2020 01:10:00 +0200
Subject: [PATCH 232/483] post-merge fix

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

diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 290b25b3..77e08297 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -35,7 +35,7 @@
       </div>
       <button
         class="emoji-reaction btn btn-default"
-        :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
+        :class="{ 'toggled': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
         @click="emojiOnClick(reaction.name, $event)"
         @mouseenter="fetchEmojiReactionsByIfMissing()"
       >

From 73dd7209ddc45ab036b4b6ba37fee9d8909d95dd Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 11 Feb 2020 18:21:03 +0200
Subject: [PATCH 233/483] fixed transparent + semi-transparent inheritance case

---
 src/services/theme_data/theme_data.service.js | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 7479a55e..75768795 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -352,9 +352,8 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
   }
   const opacitySlot = getOpacitySlot(key)
   if (opacitySlot && outputColor.a === undefined) {
-    const deps = getDependencies(key, SLOT_INHERITANCE)
-    const dependencySlot = deps && deps[0]
-    if (dependencySlot && sourceColors[dependencySlot] === 'transparent') {
+    const dependencySlot = deps[0]
+    if (dependencySlot && colors[dependencySlot] === 'transparent') {
       outputColor.a = 0
     } else {
       outputColor.a = Number(sourceOpacity[opacitySlot]) || OPACITIES[opacitySlot].defaultValue || 1

From 0fd78e99f55416a765f7e55e132e59c85ff04ad1 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 12 Feb 2020 01:19:48 +0200
Subject: [PATCH 234/483] properly clear theme

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

diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 807f9efc..0c8a9c3b 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -618,7 +618,7 @@ export default {
 
       if (!this.keepColor) {
         this.clearV1()
-        const keys = new Set(version !== 1 ? Object.keys(colors) : [])
+        const keys = new Set(version !== 1 ? Object.keys(SLOT_INHERITANCE) : [])
         if (version === 1 || version === 'l1') {
           keys
             .add('bg')

From ba3c2780023e237f95fd05b04cca5a2e7bf696ca Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 12 Feb 2020 01:26:01 +0200
Subject: [PATCH 235/483] dismiss warning when selecting other themes, fix
 collapsed attachments link color

---
 src/components/attachment/attachment.vue        | 2 ++
 src/components/style_switcher/style_switcher.js | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index 0748b2f0..a7e217c1 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -130,6 +130,8 @@
   .placeholder {
     margin-right: 8px;
     margin-bottom: 4px;
+    color: $fallback--link;
+    color: var(--postLink, $fallback--link);
   }
 
   .nsfw-placeholder {
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 0c8a9c3b..a7f586f4 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -385,6 +385,7 @@ export default {
       origin,
       forceUseSource = false
     ) {
+      this.dismissWarning()
       if (!source && !theme) {
         throw new Error('Can\'t load theme: empty')
       }
@@ -723,6 +724,7 @@ export default {
       }
     },
     selected () {
+      this.dismissWarning()
       if (this.selectedVersion === 1) {
         if (!this.keepRoundness) {
           this.clearRoundness()

From 823a303f8a528bd899d9e2f0904756614de4854c Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo <hakabahitoyo@yahoo.co.jp>
Date: Thu, 13 Feb 2020 09:41:31 +0000
Subject: [PATCH 236/483] Improve default ToS

---
 static/terms-of-service.html | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/static/terms-of-service.html b/static/terms-of-service.html
index c02cb719..a6da539e 100644
--- a/static/terms-of-service.html
+++ b/static/terms-of-service.html
@@ -1,7 +1,4 @@
 <h4>Terms of Service</h4>
 
-<p>This is a placeholder ToS.</p>
-
-<p>Edit <code>"/static/terms-of-service.html"</code> to make it fit the needs of your instance.</p>
-<br>
-<img src="/static/logo.png"/ style="display: block; margin: auto;">
+<p>This is a placeholder ToS. Edit <code>"/static/terms-of-service.html"</code> to make it fit the needs of your instance.</p>
+<img src="/static/logo.png" style="display: block; margin: auto; max-width: 100%; height: 50px; object-fit: contain;" />

From 4aeba026e6166f7e8f44e8e33e4e84f687e9a642 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo <hakabahitoyo@yahoo.co.jp>
Date: Thu, 13 Feb 2020 09:43:56 +0000
Subject: [PATCH 237/483] Refactor i18n messages for MRF policy disclosure

---
 .../mrf_transparency_panel.vue                | 32 ++++++++--------
 src/i18n/en.json                              | 38 ++++++++++---------
 src/i18n/ja_easy.json                         | 38 ++++++++++---------
 3 files changed, 57 insertions(+), 51 deletions(-)

diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
index 8038e587..acdf822e 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
@@ -6,13 +6,13 @@
     <div class="panel panel-default base01-background">
       <div class="panel-heading timeline-heading base02-background">
         <div class="title">
-          {{ $t("about.federation") }}
+          {{ $t("about.mrf.federation") }}
         </div>
       </div>
       <div class="panel-body">
         <div class="mrf-section">
-          <h2>{{ $t("about.mrf_policies") }}</h2>
-          <p>{{ $t("about.mrf_policies_desc") }}</p>
+          <h2>{{ $t("about.mrf.mrf_policies") }}</h2>
+          <p>{{ $t("about.mrf.mrf_policies_desc") }}</p>
 
           <ul>
             <li
@@ -23,13 +23,13 @@
           </ul>
 
           <h2 v-if="hasInstanceSpecificPolicies">
-            {{ $t("about.mrf_policy_simple") }}
+            {{ $t("about.mrf.simple.simple_policies") }}
           </h2>
 
           <div v-if="acceptInstances.length">
-            <h4>{{ $t("about.mrf_policy_simple_accept") }}</h4>
+            <h4>{{ $t("about.mrf.simple.accept") }}</h4>
 
-            <p>{{ $t("about.mrf_policy_simple_accept_desc") }}</p>
+            <p>{{ $t("about.mrf.simple.accept_desc") }}</p>
 
             <ul>
               <li
@@ -41,9 +41,9 @@
           </div>
 
           <div v-if="rejectInstances.length">
-            <h4>{{ $t("about.mrf_policy_simple_reject") }}</h4>
+            <h4>{{ $t("about.mrf.simple.reject") }}</h4>
 
-            <p>{{ $t("about.mrf_policy_simple_reject_desc") }}</p>
+            <p>{{ $t("about.mrf.simple.reject_desc") }}</p>
 
             <ul>
               <li
@@ -55,9 +55,9 @@
           </div>
 
           <div v-if="quarantineInstances.length">
-            <h4>{{ $t("about.mrf_policy_simple_quarantine") }}</h4>
+            <h4>{{ $t("about.mrf.simple.quarantine") }}</h4>
 
-            <p>{{ $t("about.mrf_policy_simple_quarantine_desc") }}</p>
+            <p>{{ $t("about.mrf.simple.quarantine_desc") }}</p>
 
             <ul>
               <li
@@ -69,9 +69,9 @@
           </div>
 
           <div v-if="ftlRemovalInstances.length">
-            <h4>{{ $t("about.mrf_policy_simple_ftl_removal") }}</h4>
+            <h4>{{ $t("about.mrf.simple.ftl_removal") }}</h4>
 
-            <p>{{ $t("about.mrf_policy_simple_ftl_removal_desc") }}</p>
+            <p>{{ $t("about.mrf.simple.ftl_removal_desc") }}</p>
 
             <ul>
               <li
@@ -83,9 +83,9 @@
           </div>
 
           <div v-if="mediaNsfwInstances.length">
-            <h4>{{ $t("about.mrf_policy_simple_media_nsfw") }}</h4>
+            <h4>{{ $t("about.mrf.simple.media_nsfw") }}</h4>
 
-            <p>{{ $t("about.mrf_policy_simple_media_nsfw_desc") }}</p>
+            <p>{{ $t("about.mrf.simple.media_nsfw_desc") }}</p>
 
             <ul>
               <li
@@ -97,9 +97,9 @@
           </div>
 
           <div v-if="mediaRemovalInstances.length">
-            <h4>{{ $t("about.mrf_policy_simple_media_removal") }}</h4>
+            <h4>{{ $t("about.mrf.simple.media_removal") }}</h4>
 
-            <p>{{ $t("about.mrf_policy_simple_media_removal_desc") }}</p>
+            <p>{{ $t("about.mrf.simple.media_removal_desc") }}</p>
 
             <ul>
               <li
diff --git a/src/i18n/en.json b/src/i18n/en.json
index d0d654d3..b4d86052 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1,31 +1,33 @@
 {
   "about": {
-    "staff": "Staff",
-    "federation": "Federation",
-    "mrf_policies": "Enabled MRF Policies",
-    "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance.  The following policies are enabled:",
-    "mrf_policy_simple": "Instance-specific Policies",
-    "mrf_policy_simple_accept": "Accept",
-    "mrf_policy_simple_accept_desc": "This instance only accepts messages from the following instances:",
-    "mrf_policy_simple_reject": "Reject",
-    "mrf_policy_simple_reject_desc": "This instance will not accept messages from the following instances:",
-    "mrf_policy_simple_quarantine": "Quarantine",
-    "mrf_policy_simple_quarantine_desc": "This instance will send only public posts to the following instances:",
-    "mrf_policy_simple_ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
-    "mrf_policy_simple_ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
-    "mrf_policy_simple_media_removal": "Media Removal",
-    "mrf_policy_simple_media_removal_desc": "This instance removes media from posts on the following instances:",
-    "mrf_policy_simple_media_nsfw": "Media Force-set As Sensitive",
-    "mrf_policy_simple_media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:",
     "mrf": {
+      "federation": "Federation",
       "keyword": {
         "keyword_policies": "Keyword Policies",
         "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
         "reject": "Reject",
         "replace": "Replace",
         "is_replaced_by": "→"
+      },
+      "mrf_policies": "Enabled MRF Policies",
+      "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance.  The following policies are enabled:",
+      "simple": {
+        "simple_policies": "Instance-specific Policies",
+        "accept": "Accept",
+        "accept_desc": "This instance only accepts messages from the following instances:",
+        "reject": "Reject",
+        "reject_desc": "This instance will not accept messages from the following instances:",
+        "quarantine": "Quarantine",
+        "quarantine_desc": "This instance will send only public posts to the following instances:",
+        "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+        "ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
+        "media_removal": "Media Removal",
+        "media_removal_desc": "This instance removes media from posts on the following instances:",
+        "media_nsfw": "Media Force-set As Sensitive",
+        "media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
       }
-    }
+    },
+    "staff": "Staff"
   },
   "chat": {
     "title": "Chat"
diff --git a/src/i18n/ja_easy.json b/src/i18n/ja_easy.json
index be447f1c..978e43b3 100644
--- a/src/i18n/ja_easy.json
+++ b/src/i18n/ja_easy.json
@@ -1,22 +1,26 @@
 {
   "about": {
-    "staff": "スタッフ",
-    "federation": "フェデレーション",
-    "mrf_policies": "ゆうこうなMRFポリシー",
-    "mrf_policies_desc": "MRFポリシーは、このインスタンスのフェデレーションのふるまいを、いじります。これらのMRFポリシーがゆうこうになっています:",
-    "mrf_policy_simple": "インスタンスのポリシー",
-    "mrf_policy_simple_accept": "うけいれ",
-    "mrf_policy_simple_accept_desc": "このインスンスは、これらのインスタンスからのメッセージのみをうけいれます:",
-    "mrf_policy_simple_reject": "おことわり",
-    "mrf_policy_simple_reject_desc": "このインスタンスは、これらのインスタンスからのメッセージをうけいれません:",
-    "mrf_policy_simple_quarantine": "けんえき",
-    "mrf_policy_simple_quarantine_desc": "このインスタンスは、これらのインスタンスに、パブリックなとうこうのみを、おくります:",
-    "mrf_policy_simple_ftl_removal": "「つながっているすべてのネットワーク」タイムラインからのぞく",
-    "mrf_policy_simple_ftl_removal_desc": "このインスタンスは、つながっているすべてのネットワーク」タイムラインから、これらのインスタンスを、とりのぞきます:",
-    "mrf_policy_simple_media_removal": "メディアをのぞく",
-    "mrf_policy_simple_media_removal_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、とりのぞきます:",
-    "mrf_policy_simple_media_nsfw": "メディアをすべてセンシティブにする",
-    "mrf_policy_simple_media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:"
+    "mrf": {
+      "federation": "フェデレーション",
+      "mrf_policies": "ゆうこうなMRFポリシー",
+      "mrf_policies_desc": "MRFポリシーは、このインスタンスのフェデレーションのふるまいを、いじります。これらのMRFポリシーがゆうこうになっています:",
+      "simple": {
+        "simple_policies": "インスタンスのポリシー",
+        "accept": "うけいれ",
+        "accept_desc": "このインスンスは、これらのインスタンスからのメッセージのみをうけいれます:",
+        "reject": "おことわり",
+        "reject_desc": "このインスタンスは、これらのインスタンスからのメッセージをうけいれません:",
+        "quarantine": "けんえき",
+        "quarantine_desc": "このインスタンスは、これらのインスタンスに、パブリックなとうこうのみを、おくります:",
+        "ftl_removal": "「つながっているすべてのネットワーク」タイムラインからのぞく",
+        "ftl_removal_desc": "このインスタンスは、つながっているすべてのネットワーク」タイムラインから、これらのインスタンスを、とりのぞきます:",
+        "media_removal": "メディアをのぞく",
+        "media_removal_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、とりのぞきます:",
+        "media_nsfw": "メディアをすべてセンシティブにする",
+        "media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:"
+      }
+    },
+    "staff": "スタッフ"
   },
   "chat": {
     "title": "チャット"

From 0dcc3bf2fe78ec715a0d492281045426ab194457 Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Thu, 13 Feb 2020 22:35:46 +0300
Subject: [PATCH 238/483] after_store: Fix failing to parse nodeinfo when mrf
 transparency is disabled

Closes #772
---
 src/boot/after_store.js | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 0bb1b2b4..4e043b35 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -221,9 +221,16 @@ const getNodeInfo = async ({ store }) => {
 
       const frontendVersion = window.___pleromafe_commit_hash
       store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
-      store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') })
 
       const federation = metadata.federation
+
+      store.dispatch('setInstanceOption', {
+        name: 'tagPolicyAvailable',
+        value: typeof federation.mrf_policies === 'undefined'
+          ? false
+          : metadata.federation.mrf_policies.includes('TagPolicy')
+      })
+
       store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
       store.dispatch('setInstanceOption', {
         name: 'federating',

From c43325acd7dc2a11217b9c70b9a0d53159de26d3 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 13 Feb 2020 21:39:14 +0200
Subject: [PATCH 239/483] fix pressed button in top bar

---
 src/services/theme_data/pleromafe.js | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index 9b33e4a9..6d4f583b 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -470,6 +470,10 @@ export const SLOT_INHERITANCE = {
     variant: 'btnPressed',
     textColor: true
   },
+  btnPressedTopBar: {
+    depends: ['btnPressed'],
+    layer: 'btn'
+  },
   btnPressedTopBarText: {
     depends: ['btnTopBarText'],
     layer: 'btnTopBar',

From 59dd7f13202d3cfc20418e21d85cf96e18e65081 Mon Sep 17 00:00:00 2001
From: xenofem <xenofem@xeno.science>
Date: Sun, 16 Feb 2020 14:58:43 -0500
Subject: [PATCH 240/483] Add onInput() function as listener for input events,
 remove unnecessary compositionupdate listener

---
 src/components/emoji_input/emoji_input.js | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index 001a22e9..f4c3479c 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -147,7 +147,7 @@ const EmojiInput = {
     input.elm.addEventListener('keydown', this.onKeyDown)
     input.elm.addEventListener('click', this.onClickInput)
     input.elm.addEventListener('transitionend', this.onTransition)
-    input.elm.addEventListener('compositionupdate', this.onCompositionUpdate)
+    input.elm.addEventListener('input', this.onInput)
   },
   unmounted () {
     const { input } = this
@@ -159,7 +159,7 @@ const EmojiInput = {
       input.elm.removeEventListener('keydown', this.onKeyDown)
       input.elm.removeEventListener('click', this.onClickInput)
       input.elm.removeEventListener('transitionend', this.onTransition)
-      input.elm.removeEventListener('compositionupdate', this.onCompositionUpdate)
+      input.elm.removeEventListener('input', this.onInput)
     }
   },
   methods: {
@@ -406,12 +406,6 @@ const EmojiInput = {
       this.resize()
       this.$emit('input', e.target.value)
     },
-    onCompositionUpdate (e) {
-      this.showPicker = false
-      this.setCaret(e)
-      this.resize()
-      this.$emit('input', e.target.value)
-    },
     onClickInput (e) {
       this.showPicker = false
     },

From c1e38a44231d3fbd2624df2c06a2d25222f51e1c Mon Sep 17 00:00:00 2001
From: xenofem <xenofem@xeno.science>
Date: Sun, 16 Feb 2020 16:35:04 -0500
Subject: [PATCH 241/483] EmojiInput tests should be checking the input value
 on the last input event, not the first

---
 test/unit/specs/components/emoji_input.spec.js | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/test/unit/specs/components/emoji_input.spec.js b/test/unit/specs/components/emoji_input.spec.js
index b1b98802..045b47fd 100644
--- a/test/unit/specs/components/emoji_input.spec.js
+++ b/test/unit/specs/components/emoji_input.spec.js
@@ -36,7 +36,8 @@ describe('EmojiInput', () => {
       input.setValue(initialString)
       wrapper.setData({ caret: initialString.length })
       wrapper.vm.insert({ insertion: '(test)', keepOpen: false })
-      expect(wrapper.emitted().input[0][0]).to.eql('Testing (test) ')
+      const inputEvents = wrapper.emitted().input
+      expect(inputEvents[inputEvents.length - 1][0]).to.eql('Testing (test) ')
     })
 
     it('inserts string at the end with trailing space (source has a trailing space)', () => {
@@ -46,7 +47,8 @@ describe('EmojiInput', () => {
       input.setValue(initialString)
       wrapper.setData({ caret: initialString.length })
       wrapper.vm.insert({ insertion: '(test)', keepOpen: false })
-      expect(wrapper.emitted().input[0][0]).to.eql('Testing (test) ')
+      const inputEvents = wrapper.emitted().input
+      expect(inputEvents[inputEvents.length - 1][0]).to.eql('Testing (test) ')
     })
 
     it('inserts string at the begginning without leading space', () => {
@@ -56,7 +58,8 @@ describe('EmojiInput', () => {
       input.setValue(initialString)
       wrapper.setData({ caret: 0 })
       wrapper.vm.insert({ insertion: '(test)', keepOpen: false })
-      expect(wrapper.emitted().input[0][0]).to.eql('(test) Testing')
+      const inputEvents = wrapper.emitted().input
+      expect(inputEvents[inputEvents.length - 1][0]).to.eql('(test) Testing')
     })
 
     it('inserts string between words without creating extra spaces', () => {
@@ -66,7 +69,8 @@ describe('EmojiInput', () => {
       input.setValue(initialString)
       wrapper.setData({ caret: 6 })
       wrapper.vm.insert({ insertion: ':ebin:', keepOpen: false })
-      expect(wrapper.emitted().input[0][0]).to.eql('Spurdo :ebin: Sparde')
+      const inputEvents = wrapper.emitted().input
+      expect(inputEvents[inputEvents.length - 1][0]).to.eql('Spurdo :ebin: Sparde')
     })
 
     it('inserts string between words without creating extra spaces (other caret)', () => {
@@ -76,7 +80,8 @@ describe('EmojiInput', () => {
       input.setValue(initialString)
       wrapper.setData({ caret: 7 })
       wrapper.vm.insert({ insertion: ':ebin:', keepOpen: false })
-      expect(wrapper.emitted().input[0][0]).to.eql('Spurdo :ebin: Sparde')
+      const inputEvents = wrapper.emitted().input
+      expect(inputEvents[inputEvents.length - 1][0]).to.eql('Spurdo :ebin: Sparde')
     })
 
     it('inserts string without any padding if padEmoji setting is set to false', () => {
@@ -86,7 +91,8 @@ describe('EmojiInput', () => {
       input.setValue(initialString)
       wrapper.setData({ caret: initialString.length, keepOpen: false })
       wrapper.vm.insert({ insertion: ':spam:' })
-      expect(wrapper.emitted().input[0][0]).to.eql('Eat some spam!:spam:')
+      const inputEvents = wrapper.emitted().input
+      expect(inputEvents[inputEvents.length - 1][0]).to.eql('Eat some spam!:spam:')
     })
 
     it('correctly sets caret after insertion at beginning', (done) => {

From 695d6b6c5070f935aeec5c4db1f21fb715803b1b Mon Sep 17 00:00:00 2001
From: eugenijm <eugenijm@protonmail.com>
Date: Mon, 10 Feb 2020 23:53:56 +0300
Subject: [PATCH 242/483] Fix user activation/deactivation, deletion, and role
 assignment in the moderation menu

---
 src/modules/users.js            |  4 ++--
 src/services/api/api.service.js | 14 +++++++-------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/modules/users.js b/src/modules/users.js
index ce3e595d..df133be0 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -374,9 +374,9 @@ const users = {
       return rootState.api.backendInteractor.unsubscribeUser({ id })
         .then((relationship) => commit('updateUserRelationship', [relationship]))
     },
-    toggleActivationStatus ({ rootState, commit }, user) {
+    toggleActivationStatus ({ rootState, commit }, { user }) {
       const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser
-      api(user)
+      api({ user })
         .then(({ deactivated }) => commit('updateActivationStatus', { user, deactivated }))
     },
     registerPushNotifications (store) {
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 20eaa9a0..03e88ae2 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -402,8 +402,8 @@ const fetchStatus = ({ id, credentials }) => {
     .then((data) => parseStatus(data))
 }
 
-const tagUser = ({ tag, credentials, ...options }) => {
-  const screenName = options.screen_name
+const tagUser = ({ tag, credentials, user }) => {
+  const screenName = user.screen_name
   const form = {
     nicknames: [screenName],
     tags: [tag]
@@ -419,8 +419,8 @@ const tagUser = ({ tag, credentials, ...options }) => {
   })
 }
 
-const untagUser = ({ tag, credentials, ...options }) => {
-  const screenName = options.screen_name
+const untagUser = ({ tag, credentials, user }) => {
+  const screenName = user.screen_name
   const body = {
     nicknames: [screenName],
     tags: [tag]
@@ -436,7 +436,7 @@ const untagUser = ({ tag, credentials, ...options }) => {
   })
 }
 
-const addRight = ({ right, credentials, ...user }) => {
+const addRight = ({ right, credentials, user }) => {
   const screenName = user.screen_name
 
   return fetch(PERMISSION_GROUP_URL(screenName, right), {
@@ -446,7 +446,7 @@ const addRight = ({ right, credentials, ...user }) => {
   })
 }
 
-const deleteRight = ({ right, credentials, ...user }) => {
+const deleteRight = ({ right, credentials, user }) => {
   const screenName = user.screen_name
 
   return fetch(PERMISSION_GROUP_URL(screenName, right), {
@@ -478,7 +478,7 @@ const deactivateUser = ({ credentials, user: { screen_name: nickname } }) => {
   }).then(response => get(response, 'users.0'))
 }
 
-const deleteUser = ({ credentials, ...user }) => {
+const deleteUser = ({ credentials, user }) => {
   const screenName = user.screen_name
   const headers = authHeaders(credentials)
 

From e36647e95e78711398abdba224c945fd6570d1b6 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 17 Feb 2020 22:28:14 +0200
Subject: [PATCH 243/483] revert emoji reaction style

---
 src/components/emoji_reactions/emoji_reactions.vue | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 77e08297..b25c9716 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -35,7 +35,7 @@
       </div>
       <button
         class="emoji-reaction btn btn-default"
-        :class="{ 'toggled': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
+        :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
         @click="emojiOnClick(reaction.name, $event)"
         @mouseenter="fetchEmojiReactionsByIfMissing()"
       >
@@ -127,4 +127,10 @@
   }
 }
 
+.picked-reaction {
+  border: 1px solid var(--accent, $fallback--link);
+  margin-left: -1px; // offset the border, can't use inset shadows either
+  margin-right: calc(0.5em - 1px);
+}
+
 </style>

From 76323d6d9a6065f413ba1b918c75f6819a37a6a8 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 17 Feb 2020 22:38:24 +0200
Subject: [PATCH 244/483] fix #774

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

diff --git a/src/components/style_switcher/preview.vue b/src/components/style_switcher/preview.vue
index 8afbb123..9d984659 100644
--- a/src/components/style_switcher/preview.vue
+++ b/src/components/style_switcher/preview.vue
@@ -21,7 +21,7 @@
       </div>
       <div class="panel-body theme-preview-content">
         <div class="post">
-          <div class="avatar">
+          <div class="avatar still-image">
             ( ͡° ͜ʖ ͡°)
           </div>
           <div class="content">

From 5c6046ea0aaa13c492ff1f5b7b05ab497b614570 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 17 Feb 2020 23:43:35 +0200
Subject: [PATCH 245/483] attempt to fix some bugs with shadows control

---
 .../shadow_control/shadow_control.js          | 47 ++++++++++---------
 1 file changed, 24 insertions(+), 23 deletions(-)

diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js
index bdfb4429..f9e7b985 100644
--- a/src/components/shadow_control/shadow_control.js
+++ b/src/components/shadow_control/shadow_control.js
@@ -3,6 +3,17 @@ import OpacityInput from '../opacity_input/opacity_input.vue'
 import { getCssShadow } from '../../services/style_setter/style_setter.js'
 import { hex2rgb } from '../../services/color_convert/color_convert.js'
 
+const toModel = (object = {}) => ({
+  x: 0,
+  y: 0,
+  blur: 0,
+  spread: 0,
+  inset: false,
+  color: '#000000',
+  alpha: 1,
+  ...object
+})
+
 export default {
   // 'Value' and 'Fallback' can be undefined, but if they are
   // initially vue won't detect it when they become something else
@@ -15,7 +26,7 @@ export default {
     return {
       selectedId: 0,
       // TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason)
-      cValue: this.value || this.fallback || []
+      cValue: (this.value || this.fallback || []).map(toModel)
     }
   },
   components: {
@@ -24,12 +35,12 @@ export default {
   },
   methods: {
     add () {
-      this.cValue.push(Object.assign({}, this.selected))
+      this.cValue.push(toModel(this.selected))
       this.selectedId = this.cValue.length - 1
     },
     del () {
       this.cValue.splice(this.selectedId, 1)
-      this.selectedId = this.cValue.length === 0 ? undefined : this.selectedId - 1
+      this.selectedId = this.cValue.length === 0 ? undefined : Math.max(this.selectedId - 1, 0)
     },
     moveUp () {
       const movable = this.cValue.splice(this.selectedId, 1)[0]
@@ -46,34 +57,24 @@ export default {
     this.cValue = this.value || this.fallback
   },
   computed: {
+    anyShadows () {
+      return this.cValue.length > 0
+    },
+    anyShadowsFallback () {
+      return this.fallback.length > 0
+    },
     selected () {
-      if (this.ready && this.cValue.length > 0) {
+      if (this.ready && this.anyShadows) {
         return this.cValue[this.selectedId]
       } else {
-        return {
-          x: 0,
-          y: 0,
-          blur: 0,
-          spread: 0,
-          inset: false,
-          color: '#000000',
-          alpha: 1
-        }
+        return toModel({})
       }
     },
     currentFallback () {
-      if (this.ready && this.fallback.length > 0) {
+      if (this.ready && this.anyShadowsFallback) {
         return this.fallback[this.selectedId]
       } else {
-        return {
-          x: 0,
-          y: 0,
-          blur: 0,
-          spread: 0,
-          inset: false,
-          color: '#000000',
-          alpha: 1
-        }
+        return toModel({})
       }
     },
     moveUpValid () {

From 3b3a31b461b9c543697e9b8cb45c39430e9c8555 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 17 Feb 2020 23:43:56 +0200
Subject: [PATCH 246/483] improve the display of disabled buttons

---
 src/App.scss                         | 16 ++++++++++++++--
 src/services/theme_data/pleromafe.js |  8 ++++----
 2 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/src/App.scss b/src/App.scss
index 7dfbdb7d..89aa3215 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -104,7 +104,11 @@ button {
     color: $fallback--text;
     color: var(--btnPressedText, $fallback--text);
     background-color: $fallback--fg;
-    background-color: var(--btnPressed, $fallback--fg)
+    background-color: var(--btnPressed, $fallback--fg);
+    i {
+      color: $fallback--text;
+      color: var(--btnPressedText, $fallback--text);
+    }
   }
 
   &:disabled {
@@ -112,7 +116,11 @@ button {
     color: $fallback--text;
     color: var(--btnDisabledText, $fallback--text);
     background-color: $fallback--fg;
-    background-color: var(--btnDisabled, $fallback--fg)
+    background-color: var(--btnDisabled, $fallback--fg);
+    i {
+      color: $fallback--text;
+      color: var(--btnDisabledText, $fallback--text);
+    }
   }
 
   &.toggled {
@@ -122,6 +130,10 @@ button {
     background-color: var(--btnToggled, $fallback--fg);
     box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
     box-shadow: var(--buttonPressedShadow);
+    i {
+      color: $fallback--text;
+      color: var(--btnToggledText, $fallback--text);
+    }
   }
 
   &.danger {
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index 6d4f583b..33a2ed57 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -509,25 +509,25 @@ export const SLOT_INHERITANCE = {
   // Buttons: disabled
   btnDisabled: {
     depends: ['btn', 'bg'],
-    color: (mod, btn, bg) => alphaBlend(btn, 0.5, bg)
+    color: (mod, btn, bg) => alphaBlend(btn, 0.25, bg)
   },
   btnDisabledText: {
     depends: ['btnText', 'btnDisabled'],
     layer: 'btn',
     variant: 'btnDisabled',
-    color: (mod, text, btn) => alphaBlend(text, 0.5, btn)
+    color: (mod, text, btn) => alphaBlend(text, 0.25, btn)
   },
   btnDisabledPanelText: {
     depends: ['btnPanelText', 'btnDisabled'],
     layer: 'btnPanel',
     variant: 'btnDisabled',
-    color: (mod, text, btn) => alphaBlend(text, 0.5, btn)
+    color: (mod, text, btn) => alphaBlend(text, 0.25, btn)
   },
   btnDisabledTopBarText: {
     depends: ['btnTopBarText', 'btnDisabled'],
     layer: 'btnTopBar',
     variant: 'btnDisabled',
-    color: (mod, text, btn) => alphaBlend(text, 0.5, btn)
+    color: (mod, text, btn) => alphaBlend(text, 0.25, btn)
   },
 
   // Input fields

From 1e95a0795a6f7f1db49244430d1498987f6b3969 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 17 Feb 2020 23:59:52 +0200
Subject: [PATCH 247/483] paper theme, updated todo, lol

---
 src/services/style_setter/style_setter.js |   2 +-
 static/styles.json                        |   2 +-
 static/themes/kenomo.json                 |  71 ---------
 static/themes/paper.json                  | 172 ++++++++++++++++++++++
 4 files changed, 174 insertions(+), 73 deletions(-)
 delete mode 100644 static/themes/kenomo.json
 create mode 100644 static/themes/paper.json

diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index c8610507..fbdcf562 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -270,7 +270,7 @@ export const generateShadows = (input, colors) => {
     rules: {
       shadows: Object
         .entries(shadows)
-      // TODO for v2.1: if shadow doesn't have non-inset shadows with spread > 0 - optionally
+      // TODO for v2.2: if shadow doesn't have non-inset shadows with spread > 0 - optionally
       // convert all non-inset shadows into filter: drop-shadow() to boost performance
         .map(([k, v]) => [
           `--${k}Shadow: ${getCssShadow(v)}`,
diff --git a/static/styles.json b/static/styles.json
index 54c0335f..3349a837 100644
--- a/static/styles.json
+++ b/static/styles.json
@@ -13,5 +13,5 @@
   "breezy-dark": "/static/themes/breezy-dark.json",
   "breezy-light": "/static/themes/breezy-light.json",
   "mammal": "/static/themes/mammal.json",
-  "kenomo": "/static/themes/kenomo.json"
+  "paper": "/static/themes/paper.json"
 }
diff --git a/static/themes/kenomo.json b/static/themes/kenomo.json
deleted file mode 100644
index 98ddf974..00000000
--- a/static/themes/kenomo.json
+++ /dev/null
@@ -1,71 +0,0 @@
-{
-  "_pleroma_theme_version": 2,
-  "name": "Kenomo",
-  "source": {
-    "themeEngineVersion": 3,
-    "fonts": {},
-    "shadows": {
-      "panel": [],
-      "topBar": [],
-      "button": [
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#FFFFFF",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": -1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.2,
-          "inset": true
-        }
-      ],
-      "input": [
-        {
-          "x": 0,
-          "y": "0",
-          "blur": 0,
-          "spread": "1",
-          "color": "#576574",
-          "alpha": "1",
-          "inset": true
-        }
-      ]
-    },
-    "opacity": {
-      "underlay": "1",
-      "border": "0"
-    },
-    "colors": {
-      "bg": "#ffffff",
-      "fg": "#f6f6f6",
-      "text": "#494949",
-      "underlay": "#ffffff",
-      "link": "#818181",
-      "accent": "#818181",
-      "cBlue": "#2e86de",
-      "cRed": "#c96248",
-      "cGreen": "#0fa00f",
-      "cOrange": "#aa7623",
-      "postLink": "#2e86de",
-      "border": "#ffffff",
-      "icon": "#8a8a8a",
-      "panel": "transparent",
-      "topBarText": "#4b4b4b",
-      "tab": "--btn,-30",
-      "btn": "#576574"
-    },
-    "radii": {
-      "panel": "0",
-      "avatar": "6",
-      "avatarAlt": "6"
-    }
-  }
-}
diff --git a/static/themes/paper.json b/static/themes/paper.json
new file mode 100644
index 00000000..a3b90a0a
--- /dev/null
+++ b/static/themes/paper.json
@@ -0,0 +1,172 @@
+{
+  "_pleroma_theme_version": 2,
+  "name": "Paper",
+  "source": {
+    "themeEngineVersion": 3,
+    "fonts": {},
+    "shadows": {
+      "panel": [
+        {
+          "x": "0",
+          "y": "2",
+          "blur": "9",
+          "spread": 0,
+          "inset": false,
+          "color": "#668bb2",
+          "alpha": "0.1"
+        },
+        {
+          "x": "0",
+          "y": "1",
+          "blur": "2",
+          "spread": "-1",
+          "inset": false,
+          "color": "#668bb2",
+          "alpha": "0.1"
+        }
+      ],
+      "topBar": [
+        {
+          "x": 0,
+          "y": "3",
+          "blur": "8",
+          "spread": 0,
+          "inset": false,
+          "color": "#3e618e",
+          "alpha": "0.1"
+        },
+        {
+          "x": 0,
+          "y": "1",
+          "blur": "4",
+          "spread": 0,
+          "inset": false,
+          "color": "#3e618e",
+          "alpha": "0.1"
+        }
+      ],
+      "button": [
+        {
+          "x": 0,
+          "y": "2",
+          "blur": "5",
+          "spread": 0,
+          "color": "#463f78",
+          "alpha": "0.1",
+          "inset": false
+        }
+      ],
+      "input": [
+        {
+          "x": 0,
+          "y": "1",
+          "blur": "2",
+          "spread": 0,
+          "inset": true,
+          "color": "#6277b7",
+          "alpha": "0.1"
+        }
+      ],
+      "buttonHover": [
+        {
+          "x": 0,
+          "y": "2",
+          "blur": "5",
+          "spread": 0,
+          "color": "#494949",
+          "alpha": "0.1"
+        },
+        {
+          "x": 0,
+          "y": "2",
+          "blur": "0",
+          "spread": "20",
+          "color": "#ffffff",
+          "alpha": "1",
+          "inset": true
+        }
+      ],
+      "buttonPressed": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "4",
+          "spread": "0",
+          "color": "#494949",
+          "alpha": "0.8",
+          "inset": false
+        }
+      ],
+      "avatarStatus": [
+        {
+          "x": "0",
+          "y": "2",
+          "blur": "4",
+          "spread": "0",
+          "inset": false,
+          "color": "#3e618e",
+          "alpha": "0.1"
+        }
+      ],
+      "avatar": [
+        {
+          "x": 0,
+          "y": "2",
+          "blur": "5",
+          "spread": "0",
+          "color": "#3e618e",
+          "alpha": "0.9"
+        }
+      ],
+      "popup": [
+        {
+          "x": "0",
+          "y": "3",
+          "blur": "11",
+          "spread": 0,
+          "color": "#668bb2",
+          "alpha": "0.2"
+        },
+        {
+          "x": "0",
+          "y": "2",
+          "blur": "3",
+          "spread": "-1",
+          "color": "#668bb2",
+          "alpha": "0.2"
+        }
+      ]
+    },
+    "opacity": {
+      "underlay": "1",
+      "border": "0"
+    },
+    "colors": {
+      "bg": "#ffffff",
+      "fg": "#f6f6f6",
+      "text": "#494949",
+      "underlay": "#ffffff",
+      "link": "#788ca1",
+      "accent": "#97a0aa",
+      "cBlue": "#788ca1",
+      "cRed": "#eed7ce",
+      "cGreen": "#788ca1",
+      "cOrange": "#788ca1",
+      "postLink": "#788ca1",
+      "border": "#ffffff",
+      "icon": "#b6c9c4",
+      "panel": "#ffffff",
+      "topBarText": "#4b4b4b"
+    },
+    "radii": {
+      "btn": "0",
+      "input": "0",
+      "checkbox": "0",
+      "panel": "0",
+      "avatar": "2",
+      "avatarAlt": "2",
+      "tooltip": "0",
+      "attachment": "0"
+    }
+  }
+}

From 171f4c7ddbc2f55136ff57742470ea9752e4da05 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 18 Feb 2020 00:01:48 +0200
Subject: [PATCH 248/483] update headers in switcher to better separate the
 subsections

---
 src/components/style_switcher/style_switcher.vue | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 205f325c..62c8e634 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -443,7 +443,7 @@
               :label="$t('settings.style.advanced_colors.top_bar')"
             />
             <ContrastRatio :contrast="previewContrast.btnTopBarText" />
-            <h4>{{ $t('settings.style.advanced_colors.pressed') }}</h4>
+            <h5>{{ $t('settings.style.advanced_colors.pressed') }}</h5>
             <ColorInput
               v-model="btnPressedColorLocal"
               name="btnPressedColor"
@@ -471,7 +471,7 @@
               :label="$t('settings.style.advanced_colors.top_bar')"
             />
             <ContrastRatio :contrast="previewContrast.btnPressedTopBarText" />
-            <h4>{{ $t('settings.style.advanced_colors.disabled') }}</h4>
+            <h5>{{ $t('settings.style.advanced_colors.disabled') }}</h5>
             <ColorInput
               v-model="btnDisabledColorLocal"
               name="btnDisabledColor"
@@ -496,7 +496,7 @@
               :fallback="previewTheme.colors.btnDisabledTopBarText"
               :label="$t('settings.style.advanced_colors.top_bar')"
             />
-            <h4>{{ $t('settings.style.advanced_colors.toggled') }}</h4>
+            <h5>{{ $t('settings.style.advanced_colors.toggled') }}</h5>
             <ColorInput
               v-model="btnToggledColorLocal"
               name="btnToggledColor"

From 32902e01f80a2ff27e9f4fc5a922c47b63f397fd Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 18 Feb 2020 00:30:10 +0200
Subject: [PATCH 249/483] fix changelog. kenomo didn't happen.

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 22659950..1cdd604b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Added
 - Tons of color slots including ones for hover/pressed/toggled buttons
 - Experimental `--variable[,mod]` syntax support for color slots in themes. the `mod` makes color brighter/darker depending on background color (makes darker color brighter/darker depending on background color)
-- Kenomo theme based on new UX proposal mockups
+- Paper theme by Shpuld
 - Icons in nav panel
 - Private mode support
 - Support for 'Move' type notifications

From d19c64314ff08391a2649681e6b64d052e600059 Mon Sep 17 00:00:00 2001
From: Henry Jameson <spam@hjkos.com>
Date: Thu, 20 Feb 2020 18:13:40 +0200
Subject: [PATCH 250/483] stop using customTheme in user card, instead use
 color slots. fix for opacity inheritance polluting inheritors

---
 src/components/user_card/user_card.js         | 21 +++++--------------
 src/components/user_card/user_card.vue        |  1 +
 src/services/theme_data/pleromafe.js          | 16 ++++++++++++++
 src/services/theme_data/theme_data.service.js |  3 ++-
 4 files changed, 24 insertions(+), 17 deletions(-)

diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 2f649910..1cdbd3fa 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -4,7 +4,6 @@ import ProgressButton from '../progress_button/progress_button.vue'
 import FollowButton from '../follow_button/follow_button.vue'
 import ModerationTools from '../moderation_tools/moderation_tools.vue'
 import AccountActions from '../account_actions/account_actions.vue'
-import { hex2rgb } from '../../services/color_convert/color_convert.js'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 import { mapGetters } from 'vuex'
 
@@ -30,21 +29,11 @@ export default {
       }]
     },
     style () {
-      const color = this.$store.getters.mergedConfig.customTheme.colors
-        ? this.$store.getters.mergedConfig.customTheme.colors.bg // v2
-        : this.$store.getters.mergedConfig.colors.bg // v1
-
-      if (color) {
-        const rgb = (typeof color === 'string') ? hex2rgb(color) : color
-        const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)`
-
-        return {
-          backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`,
-          backgroundImage: [
-            `linear-gradient(to bottom, ${tintColor}, ${tintColor})`,
-            `url(${this.user.cover_photo})`
-          ].join(', ')
-        }
+      return {
+        backgroundImage: [
+          `linear-gradient(to bottom, var(--profileTint), var(--profileTint))`,
+          `url(${this.user.cover_photo})`
+        ].join(', ')
       }
     },
     isOtherUser () {
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 3988ff1c..4ee040e8 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -286,6 +286,7 @@
     mask-size: 100% 60%;
     border-top-left-radius: calc(var(--panelRadius) - 1px);
     border-top-right-radius: calc(var(--panelRadius) - 1px);
+    background-color: var(--profileBg);
 
     &.hide-bio {
       mask-size: 100% 40px;
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index 33a2ed57..0c1fe543 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -8,6 +8,7 @@ export const LAYERS = {
   undelay: null, // root
   topBar: null, // no transparency support
   badge: null, //  no transparency support
+  profileTint: null, // doesn't matter
   fg: null,
   bg: 'underlay',
   highlight: 'bg',
@@ -29,6 +30,7 @@ export const LAYERS = {
  * this allows redefining it to something else
  */
 export const DEFAULT_OPACITY = {
+  profileTint: 0.5,
   alert: 0.5,
   input: 0.5,
   faint: 0.5,
@@ -119,6 +121,20 @@ export const SLOT_INHERITANCE = {
   cGreen: '#00FF00',
   cOrange: '#E3FF00',
 
+  profileBg: {
+    depends: ['bg'],
+    color: (mod, bg) => ({
+      r: Math.floor(bg.r * 0.53),
+      g: Math.floor(bg.g * 0.56),
+      b: Math.floor(bg.b * 0.59)
+    })
+  },
+  profileTint: {
+    depends: ['bg'],
+    layer: 'profileTint',
+    opacity: 'profileTint'
+  },
+
   highlight: {
     depends: ['bg'],
     color: (mod, bg) => brightness(5 * mod, bg).rgb
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 75768795..e6ff82e6 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -351,7 +351,8 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
     throw new Error('Couldn\'t generate color for ' + key)
   }
   const opacitySlot = getOpacitySlot(key)
-  if (opacitySlot && outputColor.a === undefined) {
+  const ownOpacitySlot = value.opacity
+  if (opacitySlot && (outputColor.a === undefined || ownOpacitySlot)) {
     const dependencySlot = deps[0]
     if (dependencySlot && colors[dependencySlot] === 'transparent') {
       outputColor.a = 0

From 8bb5d775b4ce24193f8372a9b541e1ce270e839f Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 20 Feb 2020 20:47:24 +0200
Subject: [PATCH 251/483] update pleroma-dark and pleroma-light

---
 static/styles.json               |   4 +-
 static/themes/pleroma-dark.json  | 543 +++++++++++++++++++++++++++++
 static/themes/pleroma-light.json | 570 +++++++++++++++++++++++++++++++
 3 files changed, 1115 insertions(+), 2 deletions(-)
 create mode 100644 static/themes/pleroma-dark.json
 create mode 100644 static/themes/pleroma-light.json

diff --git a/static/styles.json b/static/styles.json
index 3349a837..23f57c65 100644
--- a/static/styles.json
+++ b/static/styles.json
@@ -1,6 +1,6 @@
 {
-  "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
-  "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
+  "pleroma-dark": "/static/themes/pleroma-dark.json",
+  "pleroma-light": "/static/themes/pleroma-light.json",
   "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
   "classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
   "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
diff --git a/static/themes/pleroma-dark.json b/static/themes/pleroma-dark.json
new file mode 100644
index 00000000..2de001da
--- /dev/null
+++ b/static/themes/pleroma-dark.json
@@ -0,0 +1,543 @@
+{
+  "_pleroma_theme_version": 2,
+  "name": "Pleroma Dark",
+  "theme": {
+    "themeEngineVersion": 3,
+    "shadows": {
+      "panel": [
+        {
+          "x": "0",
+          "y": "0",
+          "blur": "3",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.5"
+        },
+        {
+          "x": "0",
+          "y": "4",
+          "blur": "6",
+          "spread": "3",
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.3"
+        }
+      ],
+      "topBar": [
+        {
+          "x": 0,
+          "y": "1",
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.4"
+        },
+        {
+          "x": 0,
+          "y": "2",
+          "blur": "7",
+          "spread": 0,
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.3"
+        }
+      ],
+      "popup": [
+        {
+          "x": 2,
+          "y": 2,
+          "blur": 3,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.5
+        }
+      ],
+      "avatar": [
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 8,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.7
+        }
+      ],
+      "avatarStatus": [],
+      "panelHeader": [
+        {
+          "x": 0,
+          "y": "1",
+          "blur": "3",
+          "spread": 0,
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.4"
+        },
+        {
+          "x": "0",
+          "y": "1",
+          "blur": "0",
+          "spread": 0,
+          "inset": true,
+          "color": "#ffffff",
+          "alpha": "0.2"
+        }
+      ],
+      "button": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 2,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 1
+        },
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "buttonHover": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "1",
+          "spread": "2",
+          "color": "#b9b9ba",
+          "alpha": "0.4",
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "buttonPressed": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 1,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "2",
+          "spread": 0,
+          "inset": false,
+          "color": "#000000",
+          "alpha": 1
+        }
+      ],
+      "input": [
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 2,
+          "inset": true,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 1
+        }
+      ]
+    },
+    "colors": {
+      "underlay": "#090e14",
+      "bg": "#0f161e",
+      "fg": "#151e2b",
+      "cRed": "#d31014",
+      "cOrange": "#ffc459",
+      "cGreen": "#5dc94a",
+      "cBlue": "#81beea",
+      "accent": "#e2b188",
+      "link": "#e2b188",
+      "text": "#b9b9ba",
+      "badgeNotification": "#e15932",
+      "badgeNotificationText": "#ffffff",
+      "panel": "#151e2b",
+      "panelText": "#b9b9ba",
+      "alertNeutral": "#b9b9ba",
+      "alertNeutralPanelText": "#ffffff",
+      "alertNeutralText": "#ffffff",
+      "alertWarning": "#ffc459",
+      "alertWarningPanelText": "#ffffff",
+      "alertWarningText": "#ffffff",
+      "alertError": "#d31014",
+      "alertErrorPanelText": "#b9b9ba",
+      "alertErrorText": "#f0f0f0",
+      "fgText": "#b9b9ba",
+      "topBar": "#151e2b",
+      "topBarText": "#a0a5aa",
+      "input": "#151e2b",
+      "inputTopbarText": "#a0a5aa",
+      "inputPanelText": "#b9b9ba",
+      "inputText": "#b9b9ba",
+      "btn": "#151e2b",
+      "btnText": "#b9b9ba",
+      "btnTopBarText": "#b9b9ba",
+      "btnDisabled": "#111822",
+      "btnDisabledTopBarText": "#3b4148",
+      "btnPanelText": "#b9b9ba",
+      "btnDisabledPanelText": "#3b4148",
+      "btnDisabledText": "#3b4148",
+      "btnToggled": "#c08f60",
+      "btnToggledTopBarText": "#000000",
+      "btnToggledPanelText": "#000000",
+      "btnToggledText": "#000000",
+      "btnPressed": "#151e2b",
+      "btnPressedTopBarText": "#b9b9ba",
+      "btnPressedTopBar": "#151e2b",
+      "btnPressedPanelText": "#b9b9ba",
+      "btnPressedPanel": "#151e2b",
+      "btnPressedText": "#b9b9ba",
+      "tabActiveText": "#b9b9ba",
+      "tabText": "#b9b9ba",
+      "tab": "#151e2b",
+      "fgLink": "#e2b188",
+      "topBarLink": "#a0a5aa",
+      "panelLink": "#e2b188",
+      "panelFaint": "#b9b9ba",
+      "icon": "#64686c",
+      "poll": "#645449",
+      "pollText": "#ffffff",
+      "border": "#1b232d",
+      "postLink": "#e2b188",
+      "lightText": "#ededed",
+      "popover": "#0f161e",
+      "selectedMenuPopover": "#18232f",
+      "highlight": "#18232f",
+      "highlightText": "#b9b9ba",
+      "selectedMenu": "#18232f",
+      "selectedMenuText": "#b9b9ba",
+      "selectedMenuPopoverIcon": "#696e75",
+      "highlightLink": "#e2b188",
+      "selectedMenuLink": "#e2b188",
+      "selectedMenuPopoverLink": "#e2b188",
+      "selectedMenuPopoverText": "#b9b9ba",
+      "faintLink": "#e2b188",
+      "highlightFaintLink": "#e2b188",
+      "selectedMenuFaintLink": "#e2b188",
+      "selectedMenuPopoverFaintLink": "#e2b188",
+      "faint": "#b9b9ba",
+      "highlightFaintText": "#b9b9ba",
+      "selectedMenuFaintText": "#b9b9ba",
+      "selectedMenuPopoverFaintText": "#b9b9ba",
+      "highlightLightText": "#ededed",
+      "selectedMenuLightText": "#ededed",
+      "selectedMenuPopoverLightText": "#ededed",
+      "selectedMenuIcon": "#696e75",
+      "selectedPost": "#18232f",
+      "selectedPostText": "#b9b9ba",
+      "selectedPostIcon": "#696e75",
+      "selectedPostLink": "#e2b188",
+      "selectedPostFaintLink": "#e2b188",
+      "highlightPostLink": "#e2b188",
+      "selectedPostPostLink": "#e2b188",
+      "selectedPostLightText": "#ededed",
+      "selectedPostFaintText": "#b9b9ba",
+      "popoverText": "#b9b9ba",
+      "popoverIcon": "#64686c",
+      "popoverLink": "#e2b188",
+      "postFaintLink": "#e2b188",
+      "popoverPostFaintLink": "#e2b188",
+      "popoverFaintLink": "#e2b188",
+      "popoverFaintText": "#b9b9ba",
+      "popoverPostLink": "#e2b188",
+      "popoverLightText": "#ededed",
+      "highlightIcon": "#696e75",
+      "highlightPostFaintLink": "#e2b188",
+      "profileTint": "#0f161e",
+      "profileBg": "#070c11"
+    },
+    "opacity": {
+      "underlay": 0.6,
+      "bg": 1,
+      "panel": 1,
+      "alert": 0.5,
+      "input": 0.5,
+      "btn": 1,
+      "faint": 0.5,
+      "border": 1,
+      "popover": 1,
+      "profileTint": 0.5
+    },
+    "radii": {
+      "btn": "3",
+      "input": "3",
+      "checkbox": 2,
+      "panel": "3",
+      "avatar": "3",
+      "avatarAlt": 50,
+      "tooltip": 2,
+      "attachment": "3"
+    },
+    "fonts": {
+      "interface": {
+        "family": "sans-serif"
+      },
+      "input": {
+        "family": "inherit"
+      },
+      "post": {
+        "family": "inherit"
+      },
+      "postCode": {
+        "family": "monospace"
+      }
+    }
+  },
+  "source": {
+    "themeEngineVersion": 3,
+    "fonts": {},
+    "shadows": {
+      "buttonHover": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "1",
+          "spread": "2",
+          "color": "#b9b9ba",
+          "alpha": "0.4",
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "buttonPressed": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 1,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "2",
+          "spread": 0,
+          "inset": false,
+          "color": "#000000",
+          "alpha": 1
+        }
+      ],
+      "panelHeader": [
+        {
+          "x": 0,
+          "y": "1",
+          "blur": "3",
+          "spread": 0,
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.4"
+        },
+        {
+          "x": "0",
+          "y": "1",
+          "blur": "0",
+          "spread": 0,
+          "inset": true,
+          "color": "#ffffff",
+          "alpha": "0.2"
+        }
+      ],
+      "panel": [
+        {
+          "x": "0",
+          "y": "0",
+          "blur": "3",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.5"
+        },
+        {
+          "x": "0",
+          "y": "4",
+          "blur": "6",
+          "spread": "3",
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.3"
+        }
+      ],
+      "button": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 2,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 1
+        },
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "topBar": [
+        {
+          "x": 0,
+          "y": "1",
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.4"
+        },
+        {
+          "x": 0,
+          "y": "2",
+          "blur": "7",
+          "spread": 0,
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.3"
+        }
+      ]
+    },
+    "opacity": {
+      "underlay": "0.6"
+    },
+    "colors": {
+      "bg": "#0f161e",
+      "fg": "#151e2b",
+      "text": "#b9b9ba",
+      "underlay": "#090e14",
+      "accent": "#e2b188",
+      "cBlue": "#81beea",
+      "cRed": "#d31014",
+      "cGreen": "#5dc94a",
+      "cOrange": "#ffc459",
+      "border": "#1b232d",
+      "topBarText": "#a0a5aa",
+      "topBarLink": "#A0A5AA",
+      "btnToggled": "#c08f60",
+      "alertErrorText": "#f0f0f0",
+      "badgeNotification": "#e15932",
+      "badgeNotificationText": "#ffffff"
+    },
+    "radii": {
+      "btn": "3",
+      "input": "3",
+      "panel": "3",
+      "avatar": "3",
+      "attachment": "3"
+    }
+  }
+}
diff --git a/static/themes/pleroma-light.json b/static/themes/pleroma-light.json
new file mode 100644
index 00000000..f8cefc9b
--- /dev/null
+++ b/static/themes/pleroma-light.json
@@ -0,0 +1,570 @@
+{
+  "_pleroma_theme_version": 2,
+  "name": "Pleroma Light",
+  "theme": {
+    "themeEngineVersion": 3,
+    "shadows": {
+      "panel": [
+        {
+          "x": "0",
+          "y": 1,
+          "blur": "3",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.5"
+        },
+        {
+          "x": "0",
+          "y": "3",
+          "blur": "6",
+          "spread": "1",
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.2"
+        }
+      ],
+      "topBar": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.6
+        }
+      ],
+      "popup": [
+        {
+          "x": "1",
+          "y": "2",
+          "blur": "2",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.2"
+        },
+        {
+          "x": "1",
+          "y": "3",
+          "blur": "7",
+          "spread": "0",
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.2"
+        }
+      ],
+      "avatar": [
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 8,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.7
+        }
+      ],
+      "avatarStatus": [
+        {
+          "x": 0,
+          "y": "1",
+          "blur": "4",
+          "spread": "0",
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.2"
+        }
+      ],
+      "panelHeader": [
+        {
+          "x": 0,
+          "y": "1",
+          "blur": 0,
+          "spread": 0,
+          "inset": true,
+          "color": "#ffffff",
+          "alpha": "0.5"
+        },
+        {
+          "x": 0,
+          "y": "1",
+          "blur": "3",
+          "spread": 0,
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.3"
+        }
+      ],
+      "button": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 2,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.2"
+        },
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": "0.5",
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "buttonHover": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "2",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.2"
+        },
+        {
+          "x": 0,
+          "y": "0",
+          "blur": "1",
+          "spread": "2",
+          "color": "#ffc39f",
+          "alpha": "1",
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "buttonPressed": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.2"
+        },
+        {
+          "x": 0,
+          "y": 1,
+          "blur": "1",
+          "spread": "2",
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "input": [
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "2",
+          "inset": true,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.15"
+        }
+      ]
+    },
+    "colors": {
+      "underlay": "#5d6086",
+      "bg": "#f2f6f9",
+      "fg": "#d6dfed",
+      "cRed": "#d31014",
+      "cOrange": "#ffa500",
+      "cGreen": "#0fa00f",
+      "cBlue": "#0095ff",
+      "accent": "#f55b1b",
+      "link": "#f55b1b",
+      "text": "#304055",
+      "badgeNotification": "#e83802",
+      "badgeNotificationText": "#ffffff",
+      "panel": "#d6dfed",
+      "panelText": "#304055",
+      "alertNeutral": "#304055",
+      "alertNeutralPanelText": "#000000",
+      "alertNeutralText": "#000000",
+      "alertWarning": "#ffa500",
+      "alertWarningPanelText": "#304055",
+      "alertWarningText": "#304055",
+      "alertError": "#d31014",
+      "alertErrorPanelText": "#000000",
+      "alertErrorText": "#000000",
+      "fgText": "#304055",
+      "topBar": "#d6dfed",
+      "topBarText": "#304055",
+      "input": "#dee3ed",
+      "inputTopbarText": "#304055",
+      "inputPanelText": "#304055",
+      "inputText": "#304055",
+      "btn": "#d6dfed",
+      "btnText": "#304055",
+      "btnTopBarText": "#304055",
+      "btnDisabled": "#ebf1f6",
+      "btnDisabledTopBarText": "#bdc5ce",
+      "btnPanelText": "#304055",
+      "btnDisabledPanelText": "#bdc5ce",
+      "btnDisabledText": "#bdc5ce",
+      "btnToggled": "#db9d77",
+      "btnToggledTopBarText": "#304055",
+      "btnToggledPanelText": "#304055",
+      "btnToggledText": "#304055",
+      "btnPressed": "#d6dfed",
+      "btnPressedTopBarText": "#304055",
+      "btnPressedTopBar": "#d6dfed",
+      "btnPressedPanelText": "#304055",
+      "btnPressedPanel": "#d6dfed",
+      "btnPressedText": "#304055",
+      "tabActiveText": "#304055",
+      "tabText": "#304055",
+      "tab": "#d6dfed",
+      "fgLink": "#e44a0a",
+      "topBarLink": "#304055",
+      "panelLink": "#f55b1b",
+      "panelFaint": "#304055",
+      "icon": "#919ba7",
+      "poll": "#f4b8a1",
+      "pollText": "#304055",
+      "border": "#d8e6f9",
+      "postLink": "#e44a0a",
+      "lightText": "#0c0f14",
+      "popover": "#f2f6f9",
+      "selectedMenuPopover": "#e1eaf1",
+      "highlight": "#e1eaf1",
+      "highlightText": "#304055",
+      "selectedMenu": "#e1eaf1",
+      "selectedMenuText": "#304055",
+      "selectedMenuPopoverIcon": "#8995a3",
+      "highlightLink": "#e44a0a",
+      "selectedMenuLink": "#f55b1b",
+      "selectedMenuPopoverLink": "#e44a0a",
+      "selectedMenuPopoverText": "#304055",
+      "faintLink": "#f55b1b",
+      "highlightFaintLink": "#e44a0a",
+      "selectedMenuFaintLink": "#f55b1b",
+      "selectedMenuPopoverFaintLink": "#e44a0a",
+      "faint": "#304055",
+      "highlightFaintText": "#304055",
+      "selectedMenuFaintText": "#304055",
+      "selectedMenuPopoverFaintText": "#304055",
+      "highlightLightText": "#0c0f14",
+      "selectedMenuLightText": "#0c0f14",
+      "selectedMenuPopoverLightText": "#0c0f14",
+      "selectedMenuIcon": "#8995a3",
+      "selectedPost": "#e1eaf1",
+      "selectedPostText": "#304055",
+      "selectedPostIcon": "#8995a3",
+      "selectedPostLink": "#f55b1b",
+      "selectedPostFaintLink": "#f55b1b",
+      "highlightPostLink": "#f55b1b",
+      "selectedPostPostLink": "#e44a0a",
+      "selectedPostLightText": "#0c0f14",
+      "selectedPostFaintText": "#304055",
+      "popoverText": "#304055",
+      "popoverIcon": "#919ba7",
+      "popoverLink": "#e44a0a",
+      "postFaintLink": "#e44a0a",
+      "popoverPostFaintLink": "#f55b1b",
+      "popoverFaintLink": "#e44a0a",
+      "popoverFaintText": "#304055",
+      "popoverPostLink": "#f55b1b",
+      "popoverLightText": "#0c0f14",
+      "highlightIcon": "#8995a3",
+      "highlightPostFaintLink": "#f55b1b",
+      "profileTint": "#f2f6f9",
+      "profileBg": "#808992"
+    },
+    "opacity": {
+      "underlay": 0.4,
+      "bg": 1,
+      "panel": 1,
+      "alert": 0.5,
+      "input": 0.5,
+      "btn": 1,
+      "faint": 0.5,
+      "border": 1,
+      "popover": 1,
+      "profileTint": 0.5
+    },
+    "radii": {
+      "btn": "3",
+      "input": "3",
+      "checkbox": 2,
+      "panel": "3",
+      "avatar": "3",
+      "avatarAlt": 50,
+      "tooltip": 2,
+      "attachment": "3"
+    },
+    "fonts": {
+      "interface": {
+        "family": "sans-serif"
+      },
+      "input": {
+        "family": "inherit"
+      },
+      "post": {
+        "family": "inherit"
+      },
+      "postCode": {
+        "family": "monospace"
+      }
+    }
+  },
+  "source": {
+    "themeEngineVersion": 3,
+    "fonts": {},
+    "shadows": {
+      "button": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 2,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.2"
+        },
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": "0.5",
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "buttonHover": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "2",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.2"
+        },
+        {
+          "x": 0,
+          "y": "0",
+          "blur": "1",
+          "spread": "2",
+          "color": "#ffc39f",
+          "alpha": "1",
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "input": [
+        {
+          "x": 0,
+          "y": 1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": 0,
+          "blur": "2",
+          "inset": true,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.15"
+        }
+      ],
+      "panel": [
+        {
+          "x": "0",
+          "y": 1,
+          "blur": "3",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.5"
+        },
+        {
+          "x": "0",
+          "y": "3",
+          "blur": "6",
+          "spread": "1",
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.2"
+        }
+      ],
+      "panelHeader": [
+        {
+          "x": 0,
+          "y": "1",
+          "blur": 0,
+          "spread": 0,
+          "inset": true,
+          "color": "#ffffff",
+          "alpha": "0.5"
+        },
+        {
+          "x": 0,
+          "y": "1",
+          "blur": "3",
+          "spread": 0,
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.3"
+        }
+      ],
+      "buttonPressed": [
+        {
+          "x": 0,
+          "y": 0,
+          "blur": 4,
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.2"
+        },
+        {
+          "x": 0,
+          "y": 1,
+          "blur": "1",
+          "spread": "2",
+          "color": "#000000",
+          "alpha": "0.3",
+          "inset": true
+        },
+        {
+          "x": 0,
+          "y": -1,
+          "blur": 0,
+          "spread": 0,
+          "color": "#FFFFFF",
+          "alpha": 0.2,
+          "inset": true
+        }
+      ],
+      "popup": [
+        {
+          "x": "1",
+          "y": "2",
+          "blur": "2",
+          "spread": 0,
+          "color": "#000000",
+          "alpha": "0.2"
+        },
+        {
+          "x": "1",
+          "y": "3",
+          "blur": "7",
+          "spread": "0",
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.2"
+        }
+      ],
+      "avatarStatus": [
+        {
+          "x": 0,
+          "y": "1",
+          "blur": "4",
+          "spread": "0",
+          "inset": false,
+          "color": "#000000",
+          "alpha": "0.2"
+        }
+      ]
+    },
+    "opacity": {
+      "underlay": "0.4"
+    },
+    "colors": {
+      "bg": "#f2f6f9",
+      "fg": "#d6dfed",
+      "text": "#304055",
+      "underlay": "#5d6086",
+      "accent": "#f55b1b",
+      "cBlue": "#0095ff",
+      "cRed": "#d31014",
+      "cGreen": "#0fa00f",
+      "cOrange": "#ffa500",
+      "border": "#d8e6f9",
+      "topBarText": "#304055",
+      "topBarLink": "#304055",
+      "btnToggled": "#db9d77",
+      "input": "#dee3ed",
+      "badgeNotification": "#e83802"
+    },
+    "radii": {
+      "btn": "3",
+      "input": "3",
+      "panel": "3",
+      "avatar": "3",
+      "attachment": "3"
+    }
+  }
+}

From 64a6ba219ac9a1614e87d3ac979454e6d016cd5e Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 20 Feb 2020 21:14:42 +0200
Subject: [PATCH 252/483] make panel header highlight less harsh

---
 static/themes/pleroma-dark.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/static/themes/pleroma-dark.json b/static/themes/pleroma-dark.json
index 2de001da..c2ee9d1c 100644
--- a/static/themes/pleroma-dark.json
+++ b/static/themes/pleroma-dark.json
@@ -80,7 +80,7 @@
           "spread": 0,
           "inset": true,
           "color": "#ffffff",
-          "alpha": "0.2"
+          "alpha": "0.1"
         }
       ],
       "button": [

From e342dbcb10ad5da4a897ce683246b1aa2cd72b00 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 21 Feb 2020 14:09:21 +0000
Subject: [PATCH 253/483] Apply suggestion to static/themes/pleroma-dark.json

---
 static/themes/pleroma-dark.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/static/themes/pleroma-dark.json b/static/themes/pleroma-dark.json
index c2ee9d1c..aee4a4fe 100644
--- a/static/themes/pleroma-dark.json
+++ b/static/themes/pleroma-dark.json
@@ -528,7 +528,7 @@
       "topBarText": "#a0a5aa",
       "topBarLink": "#A0A5AA",
       "btnToggled": "#c08f60",
-      "alertErrorText": "#f0f0f0",
+      "alertErrorText": "--text,21.2",
       "badgeNotification": "#e15932",
       "badgeNotificationText": "#ffffff"
     },

From 19cc739598034b796956a1fadc91d83ec6074723 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 21 Feb 2020 14:09:25 +0000
Subject: [PATCH 254/483] Apply suggestion to static/themes/pleroma-dark.json

---
 static/themes/pleroma-dark.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/static/themes/pleroma-dark.json b/static/themes/pleroma-dark.json
index aee4a4fe..a1b9a468 100644
--- a/static/themes/pleroma-dark.json
+++ b/static/themes/pleroma-dark.json
@@ -525,8 +525,8 @@
       "cGreen": "#5dc94a",
       "cOrange": "#ffc459",
       "border": "#1b232d",
-      "topBarText": "#a0a5aa",
-      "topBarLink": "#A0A5AA",
+      "topBarText": "--text,-9.75",
+      "topBarLink": "--topBarText",
       "btnToggled": "#c08f60",
       "alertErrorText": "--text,21.2",
       "badgeNotification": "#e15932",

From c9935362de1effd03b06730cde1d6ebd81e5b777 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 21 Feb 2020 14:09:29 +0000
Subject: [PATCH 255/483] Apply suggestion to static/themes/pleroma-dark.json

---
 static/themes/pleroma-dark.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/static/themes/pleroma-dark.json b/static/themes/pleroma-dark.json
index a1b9a468..9120f55f 100644
--- a/static/themes/pleroma-dark.json
+++ b/static/themes/pleroma-dark.json
@@ -527,7 +527,7 @@
       "border": "#1b232d",
       "topBarText": "--text,-9.75",
       "topBarLink": "--topBarText",
-      "btnToggled": "#c08f60",
+      "btnToggled": "--accent,-24.2",
       "alertErrorText": "--text,21.2",
       "badgeNotification": "#e15932",
       "badgeNotificationText": "#ffffff"

From 8c454b94568218399b7805c1aa72cab0d5a18df7 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 21 Feb 2020 14:09:31 +0000
Subject: [PATCH 256/483] Apply suggestion to static/themes/pleroma-dark.json

---
 static/themes/pleroma-dark.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/static/themes/pleroma-dark.json b/static/themes/pleroma-dark.json
index 9120f55f..48e28c74 100644
--- a/static/themes/pleroma-dark.json
+++ b/static/themes/pleroma-dark.json
@@ -524,7 +524,7 @@
       "cRed": "#d31014",
       "cGreen": "#5dc94a",
       "cOrange": "#ffc459",
-      "border": "#1b232d",
+      "border": "--fg,3",
       "topBarText": "--text,-9.75",
       "topBarLink": "--topBarText",
       "btnToggled": "--accent,-24.2",

From b5c6b77ca80b635c907cd5f755ccdd7327396475 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Sat, 22 Feb 2020 10:03:16 +0200
Subject: [PATCH 257/483] update using variables

---
 static/themes/pleroma-light.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/static/themes/pleroma-light.json b/static/themes/pleroma-light.json
index f8cefc9b..55cbe28a 100644
--- a/static/themes/pleroma-light.json
+++ b/static/themes/pleroma-light.json
@@ -554,8 +554,8 @@
       "cOrange": "#ffa500",
       "border": "#d8e6f9",
       "topBarText": "#304055",
-      "topBarLink": "#304055",
-      "btnToggled": "#db9d77",
+      "topBarLink": "--topBarText",
+      "btnToggled": "--accent,-24.2",
       "input": "#dee3ed",
       "badgeNotification": "#e83802"
     },

From 1fb91b17955219fcf17320806a0d4bf61bf81f5b Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Sat, 22 Feb 2020 10:34:54 +0200
Subject: [PATCH 258/483] remove bloat from themes

---
 static/themes/pleroma-dark.json  | 352 -------------------------------
 static/themes/pleroma-light.json | 351 ------------------------------
 2 files changed, 703 deletions(-)

diff --git a/static/themes/pleroma-dark.json b/static/themes/pleroma-dark.json
index 48e28c74..2703fba1 100644
--- a/static/themes/pleroma-dark.json
+++ b/static/themes/pleroma-dark.json
@@ -1,358 +1,6 @@
 {
   "_pleroma_theme_version": 2,
   "name": "Pleroma Dark",
-  "theme": {
-    "themeEngineVersion": 3,
-    "shadows": {
-      "panel": [
-        {
-          "x": "0",
-          "y": "0",
-          "blur": "3",
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.5"
-        },
-        {
-          "x": "0",
-          "y": "4",
-          "blur": "6",
-          "spread": "3",
-          "inset": false,
-          "color": "#000000",
-          "alpha": "0.3"
-        }
-      ],
-      "topBar": [
-        {
-          "x": 0,
-          "y": "1",
-          "blur": 4,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.4"
-        },
-        {
-          "x": 0,
-          "y": "2",
-          "blur": "7",
-          "spread": 0,
-          "inset": false,
-          "color": "#000000",
-          "alpha": "0.3"
-        }
-      ],
-      "popup": [
-        {
-          "x": 2,
-          "y": 2,
-          "blur": 3,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.5
-        }
-      ],
-      "avatar": [
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 8,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.7
-        }
-      ],
-      "avatarStatus": [],
-      "panelHeader": [
-        {
-          "x": 0,
-          "y": "1",
-          "blur": "3",
-          "spread": 0,
-          "inset": false,
-          "color": "#000000",
-          "alpha": "0.4"
-        },
-        {
-          "x": "0",
-          "y": "1",
-          "blur": "0",
-          "spread": 0,
-          "inset": true,
-          "color": "#ffffff",
-          "alpha": "0.1"
-        }
-      ],
-      "button": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": 2,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 1
-        },
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#FFFFFF",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": -1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.2,
-          "inset": true
-        }
-      ],
-      "buttonHover": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": "1",
-          "spread": "2",
-          "color": "#b9b9ba",
-          "alpha": "0.4",
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#FFFFFF",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": -1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.2,
-          "inset": true
-        }
-      ],
-      "buttonPressed": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": 4,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 1,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": -1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#FFFFFF",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": 0,
-          "blur": "2",
-          "spread": 0,
-          "inset": false,
-          "color": "#000000",
-          "alpha": 1
-        }
-      ],
-      "input": [
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": -1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#FFFFFF",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": 0,
-          "blur": 2,
-          "inset": true,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 1
-        }
-      ]
-    },
-    "colors": {
-      "underlay": "#090e14",
-      "bg": "#0f161e",
-      "fg": "#151e2b",
-      "cRed": "#d31014",
-      "cOrange": "#ffc459",
-      "cGreen": "#5dc94a",
-      "cBlue": "#81beea",
-      "accent": "#e2b188",
-      "link": "#e2b188",
-      "text": "#b9b9ba",
-      "badgeNotification": "#e15932",
-      "badgeNotificationText": "#ffffff",
-      "panel": "#151e2b",
-      "panelText": "#b9b9ba",
-      "alertNeutral": "#b9b9ba",
-      "alertNeutralPanelText": "#ffffff",
-      "alertNeutralText": "#ffffff",
-      "alertWarning": "#ffc459",
-      "alertWarningPanelText": "#ffffff",
-      "alertWarningText": "#ffffff",
-      "alertError": "#d31014",
-      "alertErrorPanelText": "#b9b9ba",
-      "alertErrorText": "#f0f0f0",
-      "fgText": "#b9b9ba",
-      "topBar": "#151e2b",
-      "topBarText": "#a0a5aa",
-      "input": "#151e2b",
-      "inputTopbarText": "#a0a5aa",
-      "inputPanelText": "#b9b9ba",
-      "inputText": "#b9b9ba",
-      "btn": "#151e2b",
-      "btnText": "#b9b9ba",
-      "btnTopBarText": "#b9b9ba",
-      "btnDisabled": "#111822",
-      "btnDisabledTopBarText": "#3b4148",
-      "btnPanelText": "#b9b9ba",
-      "btnDisabledPanelText": "#3b4148",
-      "btnDisabledText": "#3b4148",
-      "btnToggled": "#c08f60",
-      "btnToggledTopBarText": "#000000",
-      "btnToggledPanelText": "#000000",
-      "btnToggledText": "#000000",
-      "btnPressed": "#151e2b",
-      "btnPressedTopBarText": "#b9b9ba",
-      "btnPressedTopBar": "#151e2b",
-      "btnPressedPanelText": "#b9b9ba",
-      "btnPressedPanel": "#151e2b",
-      "btnPressedText": "#b9b9ba",
-      "tabActiveText": "#b9b9ba",
-      "tabText": "#b9b9ba",
-      "tab": "#151e2b",
-      "fgLink": "#e2b188",
-      "topBarLink": "#a0a5aa",
-      "panelLink": "#e2b188",
-      "panelFaint": "#b9b9ba",
-      "icon": "#64686c",
-      "poll": "#645449",
-      "pollText": "#ffffff",
-      "border": "#1b232d",
-      "postLink": "#e2b188",
-      "lightText": "#ededed",
-      "popover": "#0f161e",
-      "selectedMenuPopover": "#18232f",
-      "highlight": "#18232f",
-      "highlightText": "#b9b9ba",
-      "selectedMenu": "#18232f",
-      "selectedMenuText": "#b9b9ba",
-      "selectedMenuPopoverIcon": "#696e75",
-      "highlightLink": "#e2b188",
-      "selectedMenuLink": "#e2b188",
-      "selectedMenuPopoverLink": "#e2b188",
-      "selectedMenuPopoverText": "#b9b9ba",
-      "faintLink": "#e2b188",
-      "highlightFaintLink": "#e2b188",
-      "selectedMenuFaintLink": "#e2b188",
-      "selectedMenuPopoverFaintLink": "#e2b188",
-      "faint": "#b9b9ba",
-      "highlightFaintText": "#b9b9ba",
-      "selectedMenuFaintText": "#b9b9ba",
-      "selectedMenuPopoverFaintText": "#b9b9ba",
-      "highlightLightText": "#ededed",
-      "selectedMenuLightText": "#ededed",
-      "selectedMenuPopoverLightText": "#ededed",
-      "selectedMenuIcon": "#696e75",
-      "selectedPost": "#18232f",
-      "selectedPostText": "#b9b9ba",
-      "selectedPostIcon": "#696e75",
-      "selectedPostLink": "#e2b188",
-      "selectedPostFaintLink": "#e2b188",
-      "highlightPostLink": "#e2b188",
-      "selectedPostPostLink": "#e2b188",
-      "selectedPostLightText": "#ededed",
-      "selectedPostFaintText": "#b9b9ba",
-      "popoverText": "#b9b9ba",
-      "popoverIcon": "#64686c",
-      "popoverLink": "#e2b188",
-      "postFaintLink": "#e2b188",
-      "popoverPostFaintLink": "#e2b188",
-      "popoverFaintLink": "#e2b188",
-      "popoverFaintText": "#b9b9ba",
-      "popoverPostLink": "#e2b188",
-      "popoverLightText": "#ededed",
-      "highlightIcon": "#696e75",
-      "highlightPostFaintLink": "#e2b188",
-      "profileTint": "#0f161e",
-      "profileBg": "#070c11"
-    },
-    "opacity": {
-      "underlay": 0.6,
-      "bg": 1,
-      "panel": 1,
-      "alert": 0.5,
-      "input": 0.5,
-      "btn": 1,
-      "faint": 0.5,
-      "border": 1,
-      "popover": 1,
-      "profileTint": 0.5
-    },
-    "radii": {
-      "btn": "3",
-      "input": "3",
-      "checkbox": 2,
-      "panel": "3",
-      "avatar": "3",
-      "avatarAlt": 50,
-      "tooltip": 2,
-      "attachment": "3"
-    },
-    "fonts": {
-      "interface": {
-        "family": "sans-serif"
-      },
-      "input": {
-        "family": "inherit"
-      },
-      "post": {
-        "family": "inherit"
-      },
-      "postCode": {
-        "family": "monospace"
-      }
-    }
-  },
   "source": {
     "themeEngineVersion": 3,
     "fonts": {},
diff --git a/static/themes/pleroma-light.json b/static/themes/pleroma-light.json
index 55cbe28a..05fc300a 100644
--- a/static/themes/pleroma-light.json
+++ b/static/themes/pleroma-light.json
@@ -1,357 +1,6 @@
 {
   "_pleroma_theme_version": 2,
   "name": "Pleroma Light",
-  "theme": {
-    "themeEngineVersion": 3,
-    "shadows": {
-      "panel": [
-        {
-          "x": "0",
-          "y": 1,
-          "blur": "3",
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.5"
-        },
-        {
-          "x": "0",
-          "y": "3",
-          "blur": "6",
-          "spread": "1",
-          "inset": false,
-          "color": "#000000",
-          "alpha": "0.2"
-        }
-      ],
-      "topBar": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": 4,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.6
-        }
-      ],
-      "popup": [
-        {
-          "x": "1",
-          "y": "2",
-          "blur": "2",
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.2"
-        },
-        {
-          "x": "1",
-          "y": "3",
-          "blur": "7",
-          "spread": "0",
-          "inset": false,
-          "color": "#000000",
-          "alpha": "0.2"
-        }
-      ],
-      "avatar": [
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 8,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.7
-        }
-      ],
-      "avatarStatus": [
-        {
-          "x": 0,
-          "y": "1",
-          "blur": "4",
-          "spread": "0",
-          "inset": false,
-          "color": "#000000",
-          "alpha": "0.2"
-        }
-      ],
-      "panelHeader": [
-        {
-          "x": 0,
-          "y": "1",
-          "blur": 0,
-          "spread": 0,
-          "inset": true,
-          "color": "#ffffff",
-          "alpha": "0.5"
-        },
-        {
-          "x": 0,
-          "y": "1",
-          "blur": "3",
-          "spread": 0,
-          "inset": false,
-          "color": "#000000",
-          "alpha": "0.3"
-        }
-      ],
-      "button": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": 2,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.2"
-        },
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#FFFFFF",
-          "alpha": "0.5",
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": -1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.2,
-          "inset": true
-        }
-      ],
-      "buttonHover": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": "2",
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.2"
-        },
-        {
-          "x": 0,
-          "y": "0",
-          "blur": "1",
-          "spread": "2",
-          "color": "#ffc39f",
-          "alpha": "1",
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": -1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.2,
-          "inset": true
-        }
-      ],
-      "buttonPressed": [
-        {
-          "x": 0,
-          "y": 0,
-          "blur": 4,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.2"
-        },
-        {
-          "x": 0,
-          "y": 1,
-          "blur": "1",
-          "spread": "2",
-          "color": "#000000",
-          "alpha": "0.3",
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": -1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#FFFFFF",
-          "alpha": 0.2,
-          "inset": true
-        }
-      ],
-      "input": [
-        {
-          "x": 0,
-          "y": 1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": -1,
-          "blur": 0,
-          "spread": 0,
-          "color": "#FFFFFF",
-          "alpha": 0.2,
-          "inset": true
-        },
-        {
-          "x": 0,
-          "y": 0,
-          "blur": "2",
-          "inset": true,
-          "spread": 0,
-          "color": "#000000",
-          "alpha": "0.15"
-        }
-      ]
-    },
-    "colors": {
-      "underlay": "#5d6086",
-      "bg": "#f2f6f9",
-      "fg": "#d6dfed",
-      "cRed": "#d31014",
-      "cOrange": "#ffa500",
-      "cGreen": "#0fa00f",
-      "cBlue": "#0095ff",
-      "accent": "#f55b1b",
-      "link": "#f55b1b",
-      "text": "#304055",
-      "badgeNotification": "#e83802",
-      "badgeNotificationText": "#ffffff",
-      "panel": "#d6dfed",
-      "panelText": "#304055",
-      "alertNeutral": "#304055",
-      "alertNeutralPanelText": "#000000",
-      "alertNeutralText": "#000000",
-      "alertWarning": "#ffa500",
-      "alertWarningPanelText": "#304055",
-      "alertWarningText": "#304055",
-      "alertError": "#d31014",
-      "alertErrorPanelText": "#000000",
-      "alertErrorText": "#000000",
-      "fgText": "#304055",
-      "topBar": "#d6dfed",
-      "topBarText": "#304055",
-      "input": "#dee3ed",
-      "inputTopbarText": "#304055",
-      "inputPanelText": "#304055",
-      "inputText": "#304055",
-      "btn": "#d6dfed",
-      "btnText": "#304055",
-      "btnTopBarText": "#304055",
-      "btnDisabled": "#ebf1f6",
-      "btnDisabledTopBarText": "#bdc5ce",
-      "btnPanelText": "#304055",
-      "btnDisabledPanelText": "#bdc5ce",
-      "btnDisabledText": "#bdc5ce",
-      "btnToggled": "#db9d77",
-      "btnToggledTopBarText": "#304055",
-      "btnToggledPanelText": "#304055",
-      "btnToggledText": "#304055",
-      "btnPressed": "#d6dfed",
-      "btnPressedTopBarText": "#304055",
-      "btnPressedTopBar": "#d6dfed",
-      "btnPressedPanelText": "#304055",
-      "btnPressedPanel": "#d6dfed",
-      "btnPressedText": "#304055",
-      "tabActiveText": "#304055",
-      "tabText": "#304055",
-      "tab": "#d6dfed",
-      "fgLink": "#e44a0a",
-      "topBarLink": "#304055",
-      "panelLink": "#f55b1b",
-      "panelFaint": "#304055",
-      "icon": "#919ba7",
-      "poll": "#f4b8a1",
-      "pollText": "#304055",
-      "border": "#d8e6f9",
-      "postLink": "#e44a0a",
-      "lightText": "#0c0f14",
-      "popover": "#f2f6f9",
-      "selectedMenuPopover": "#e1eaf1",
-      "highlight": "#e1eaf1",
-      "highlightText": "#304055",
-      "selectedMenu": "#e1eaf1",
-      "selectedMenuText": "#304055",
-      "selectedMenuPopoverIcon": "#8995a3",
-      "highlightLink": "#e44a0a",
-      "selectedMenuLink": "#f55b1b",
-      "selectedMenuPopoverLink": "#e44a0a",
-      "selectedMenuPopoverText": "#304055",
-      "faintLink": "#f55b1b",
-      "highlightFaintLink": "#e44a0a",
-      "selectedMenuFaintLink": "#f55b1b",
-      "selectedMenuPopoverFaintLink": "#e44a0a",
-      "faint": "#304055",
-      "highlightFaintText": "#304055",
-      "selectedMenuFaintText": "#304055",
-      "selectedMenuPopoverFaintText": "#304055",
-      "highlightLightText": "#0c0f14",
-      "selectedMenuLightText": "#0c0f14",
-      "selectedMenuPopoverLightText": "#0c0f14",
-      "selectedMenuIcon": "#8995a3",
-      "selectedPost": "#e1eaf1",
-      "selectedPostText": "#304055",
-      "selectedPostIcon": "#8995a3",
-      "selectedPostLink": "#f55b1b",
-      "selectedPostFaintLink": "#f55b1b",
-      "highlightPostLink": "#f55b1b",
-      "selectedPostPostLink": "#e44a0a",
-      "selectedPostLightText": "#0c0f14",
-      "selectedPostFaintText": "#304055",
-      "popoverText": "#304055",
-      "popoverIcon": "#919ba7",
-      "popoverLink": "#e44a0a",
-      "postFaintLink": "#e44a0a",
-      "popoverPostFaintLink": "#f55b1b",
-      "popoverFaintLink": "#e44a0a",
-      "popoverFaintText": "#304055",
-      "popoverPostLink": "#f55b1b",
-      "popoverLightText": "#0c0f14",
-      "highlightIcon": "#8995a3",
-      "highlightPostFaintLink": "#f55b1b",
-      "profileTint": "#f2f6f9",
-      "profileBg": "#808992"
-    },
-    "opacity": {
-      "underlay": 0.4,
-      "bg": 1,
-      "panel": 1,
-      "alert": 0.5,
-      "input": 0.5,
-      "btn": 1,
-      "faint": 0.5,
-      "border": 1,
-      "popover": 1,
-      "profileTint": 0.5
-    },
-    "radii": {
-      "btn": "3",
-      "input": "3",
-      "checkbox": 2,
-      "panel": "3",
-      "avatar": "3",
-      "avatarAlt": 50,
-      "tooltip": 2,
-      "attachment": "3"
-    },
-    "fonts": {
-      "interface": {
-        "family": "sans-serif"
-      },
-      "input": {
-        "family": "inherit"
-      },
-      "post": {
-        "family": "inherit"
-      },
-      "postCode": {
-        "family": "monospace"
-      }
-    }
-  },
   "source": {
     "themeEngineVersion": 3,
     "fonts": {},

From 5b7acca0bab12e88ad5b2c80b120859e440133de Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Sat, 22 Feb 2020 10:57:08 +0200
Subject: [PATCH 259/483] make theme loading work with source-only presets

---
 src/modules/instance.js | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/src/modules/instance.js b/src/modules/instance.js
index 8781646d..f96337e4 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -1,5 +1,6 @@
 import { set } from 'vue'
 import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
+import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
 import { instanceDefaultProperties } from './config.js'
 
 const defaultState = {
@@ -159,7 +160,14 @@ const instance = {
           // No need to apply theme if there's user theme already
           const { customTheme } = rootState.config
           if (customTheme) return
-          applyTheme(themeData.theme)
+
+          // New theme presets don't have 'theme' property, they use 'source'
+          const themeSource = themeData.source
+          if (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION) {
+            applyTheme(themeSource)
+          } else {
+            applyTheme(themeData.theme)
+          }
         })
     },
     fetchEmoji ({ dispatch, state }) {

From 23e0ce59e610a1f3f49f3f53e0a1015530110120 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Sat, 22 Feb 2020 09:42:22 -0600
Subject: [PATCH 260/483] Fix captcha input and disable ALL the helpers

---
 src/components/registration/registration.vue | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index fdbda007..a83ca1e5 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -187,6 +187,9 @@
                   class="form-control"
                   type="text"
                   autocomplete="off"
+                  autocorrect="off"
+                  autocapitalize="off"
+                  spellcheck="false"
                 >
               </template>
             </div>

From 86561592d002b08d6b2cd9549e8057a4ffd091cb Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Mon, 24 Feb 2020 11:19:00 -0600
Subject: [PATCH 261/483] First attempt at not requiring email address for
 registration

---
 src/boot/after_store.js                     | 3 +++
 src/components/registration/registration.js | 9 +++++++--
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index d70e1058..9fb9a853 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -241,6 +241,9 @@ const getNodeInfo = async ({ store }) => {
           : federation.enabled
       })
 
+      const accountActivationRequired = metadata.accountActivationRequired
+      store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
+
       const accounts = metadata.staffAccounts
       resolveStaffAccounts({ store, accounts })
     } else {
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index ace8cc7c..fd2942a5 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -1,5 +1,5 @@
 import { validationMixin } from 'vuelidate'
-import { required, sameAs } from 'vuelidate/lib/validators'
+import { required, requiredIf, sameAs } from 'vuelidate/lib/validators'
 import { mapActions, mapState } from 'vuex'
 
 const registration = {
@@ -16,7 +16,7 @@ const registration = {
   }),
   validations: {
     user: {
-      email: { required },
+      email: requiredIf('accountActivationRequired'),
       username: { required },
       fullname: { required },
       password: { required },
@@ -24,6 +24,11 @@ const registration = {
         required,
         sameAsPassword: sameAs('password')
       }
+    },
+    nested: {
+      required: requiredIf(function (nestedModel) {
+        return this.accountActivationRequired
+      })
     }
   },
   created () {

From 39e3917118293912b2af09f509457d718f0207c9 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Mon, 24 Feb 2020 11:23:16 -0600
Subject: [PATCH 262/483] Remove unneccessary nested

---
 src/components/registration/registration.js | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index fd2942a5..1d8109e4 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -24,11 +24,6 @@ const registration = {
         required,
         sameAsPassword: sameAs('password')
       }
-    },
-    nested: {
-      required: requiredIf(function (nestedModel) {
-        return this.accountActivationRequired
-      })
     }
   },
   created () {

From 7fa5eb07ddeb6d8c2b572e869d82a27bdd7a7fbf Mon Sep 17 00:00:00 2001
From: xenofem <xenofem@xeno.science>
Date: Mon, 24 Feb 2020 18:10:15 -0500
Subject: [PATCH 263/483] Refactor status showing/hiding code for better
 handling of edge cases and easier comprehension

---
 src/components/status/status.js | 35 ++++++++++++++-------------------
 1 file changed, 15 insertions(+), 20 deletions(-)

diff --git a/src/components/status/status.js b/src/components/status/status.js
index fc5956ec..61d66301 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -188,23 +188,22 @@ const Status = {
       }
       return this.status.attentions.length > 0
     },
+
+    // When a status has a subject and is also tall, we should only have one show more/less button. If the default is to collapse statuses with subjects, we just treat it like a status with a subject; otherwise, we just treat it like a tall status.
+    mightHideBecauseSubject () {
+      return this.status.summary && (!this.tallStatus || this.localCollapseSubjectDefault)
+    },
+    mightHideBecauseTall () {
+      return this.tallStatus && (!this.status.summary || !this.localCollapseSubjectDefault)
+    },
     hideSubjectStatus () {
-      if (this.tallStatus && !this.localCollapseSubjectDefault) {
-        return false
-      }
-      return !this.expandingSubject && this.status.summary
+      return this.mightHideBecauseSubject && !this.expandingSubject
     },
     hideTallStatus () {
-      if (this.status.summary && this.localCollapseSubjectDefault) {
-        return false
-      }
-      if (this.showingTall) {
-        return false
-      }
-      return this.tallStatus
+      return this.mightHideBecauseTall && !this.showingTall
     },
     showingMore () {
-      return (this.tallStatus && this.showingTall) || (this.status.summary && this.expandingSubject)
+      return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject)
     },
     nsfwClickthrough () {
       if (!this.status.nsfw) {
@@ -408,14 +407,10 @@ const Status = {
       this.userExpanded = !this.userExpanded
     },
     toggleShowMore () {
-      if (this.showingTall) {
-        this.showingTall = false
-      } else if (this.expandingSubject && this.status.summary) {
-        this.expandingSubject = false
-      } else if (this.hideTallStatus) {
-        this.showingTall = true
-      } else if (this.hideSubjectStatus && this.status.summary) {
-        this.expandingSubject = true
+      if (this.mightHideBecauseTall) {
+        this.showingTall = !this.showingTall
+      } else if (this.mightHideBecauseSubject) {
+        this.expandingSubject = !this.expandingSubject
       }
     },
     generateUserProfileLink (id, name) {

From 31225f5d142b51d52bed305f25a37288c9188062 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 28 Feb 2020 16:39:47 +0000
Subject: [PATCH 264/483] Fix/popover performance

---
 CHANGELOG.md                                  |   1 +
 package.json                                  |   1 -
 .../account_actions/account_actions.js        |   4 +-
 .../account_actions/account_actions.vue       |  21 ++-
 .../emoji_reactions/emoji_reactions.js        |  11 +-
 .../emoji_reactions/emoji_reactions.vue       |  33 ++--
 src/components/extra_buttons/extra_buttons.js |   3 +
 .../extra_buttons/extra_buttons.vue           |  14 +-
 .../moderation_tools/moderation_tools.js      |  11 +-
 .../moderation_tools/moderation_tools.vue     |  17 +-
 src/components/popover/popover.js             | 156 +++++++++++++++++
 src/components/popover/popover.vue            | 118 +++++++++++++
 src/components/popper/popper.scss             | 164 ------------------
 src/components/react_button/react_button.js   |  23 +--
 src/components/react_button/react_button.vue  |  32 ++--
 src/components/status/status.vue              |  11 +-
 .../status_popover/status_popover.js          |  12 +-
 .../status_popover/status_popover.vue         |  64 +++----
 src/main.js                                   |   8 -
 yarn.lock                                     |  19 --
 20 files changed, 393 insertions(+), 330 deletions(-)
 create mode 100644 src/components/popover/popover.js
 create mode 100644 src/components/popover/popover.vue
 delete mode 100644 src/components/popper/popper.scss

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1cdd604b..e77334b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Registration fixed
 - Deactivation of remote accounts from frontend
 - Fixed NSFW unhiding not working with videos when using one-click unhiding/displaying
+- Improved performance of anything that uses popovers (most notably statuses)
 
 ## [1.1.7 and earlier] - 2019-12-14
 ### Added
diff --git a/package.json b/package.json
index 5c7fa31e..542086b4 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,6 @@
     "portal-vue": "^2.1.4",
     "sanitize-html": "^1.13.0",
     "v-click-outside": "^2.1.1",
-    "v-tooltip": "^2.0.2",
     "vue": "^2.5.13",
     "vue-chat-scroll": "^1.2.1",
     "vue-i18n": "^7.3.2",
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
index d2153680..5d7ecf7e 100644
--- a/src/components/account_actions/account_actions.js
+++ b/src/components/account_actions/account_actions.js
@@ -1,4 +1,5 @@
 import ProgressButton from '../progress_button/progress_button.vue'
+import Popover from '../popover/popover.vue'
 
 const AccountActions = {
   props: [
@@ -8,7 +9,8 @@ const AccountActions = {
     return { }
   },
   components: {
-    ProgressButton
+    ProgressButton,
+    Popover
   },
   methods: {
     showRepeats () {
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index d3235be1..483783cf 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -1,13 +1,13 @@
 <template>
   <div class="account-actions">
-    <v-popover
+    <Popover
       trigger="click"
-      class="account-tools-popover"
-      :container="false"
-      placement="bottom-end"
-      :offset="5"
+      placement="bottom"
     >
-      <div slot="popover">
+      <div
+        slot="content"
+        class="account-tools-popover"
+      >
         <div class="dropdown-menu">
           <template v-if="user.following">
             <button
@@ -51,10 +51,13 @@
           </button>
         </div>
       </div>
-      <div class="btn btn-default ellipsis-button">
+      <div
+        slot="trigger"
+        class="btn btn-default ellipsis-button"
+      >
         <i class="icon-ellipsis trigger-button" />
       </div>
-    </v-popover>
+    </Popover>
   </div>
 </template>
 
@@ -62,7 +65,6 @@
 
 <style lang="scss">
 @import '../../_variables.scss';
-@import '../popper/popper.scss';
 .account-actions {
   margin: 0 .8em;
 }
@@ -70,6 +72,7 @@
 .account-actions button.dropdown-item {
   margin-left: 0;
 }
+
 .account-actions .trigger-button {
   color: $fallback--lightText;
   color: var(--lightText, $fallback--lightText);
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index b799ac9a..ae7f53be 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -1,20 +1,17 @@
 import UserAvatar from '../user_avatar/user_avatar.vue'
+import Popover from '../popover/popover.vue'
 
 const EMOJI_REACTION_COUNT_CUTOFF = 12
 
 const EmojiReactions = {
   name: 'EmojiReactions',
   components: {
-    UserAvatar
+    UserAvatar,
+    Popover
   },
   props: ['status'],
   data: () => ({
-    showAll: false,
-    popperOptions: {
-      modifiers: {
-        preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
-      }
-    }
+    showAll: false
   }),
   computed: {
     tooManyReactions () {
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index b25c9716..bac4c605 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -1,15 +1,14 @@
 <template>
   <div class="emoji-reactions">
-    <v-popover
+    <Popover
       v-for="(reaction) in emojiReactions"
       :key="reaction.name"
-      :popper-options="popperOptions"
       trigger="hover"
       placement="top"
+      :offset="{ y: 5 }"
     >
-
       <div
-        slot="popover"
+        slot="content"
         class="reacted-users"
       >
         <div v-if="accountsForEmoji[reaction.name].length">
@@ -24,7 +23,12 @@
               :compact="true"
             />
             <div class="reacted-user-names">
-              <span class="reacted-user-name" v-html="account.name_html" />
+              <!-- eslint-disable vue/no-v-html -->
+              <span
+                class="reacted-user-name"
+                v-html="account.name_html"
+              />
+              <!-- eslint-enable vue/no-v-html -->
               <span class="reacted-user-screen-name">{{ account.screen_name }}</span>
             </div>
           </div>
@@ -34,6 +38,7 @@
         </div>
       </div>
       <button
+        slot="trigger"
         class="emoji-reaction btn btn-default"
         :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
         @click="emojiOnClick(reaction.name, $event)"
@@ -42,17 +47,16 @@
         <span class="reaction-emoji">{{ reaction.name }}</span>
         <span>{{ reaction.count }}</span>
       </button>
-    </v-popover>
+    </Popover>
     <a
-        v-if="tooManyReactions"
-        @click="toggleShowAll"
-        class="emoji-reaction-expand faint"
-        href='javascript:void(0)'
-      >
-        {{ showAll ? $t('general.show_less') : showMoreString }}
-      </a>
+      v-if="tooManyReactions"
+      class="emoji-reaction-expand faint"
+      href="javascript:void(0)"
+      @click="toggleShowAll"
+    >
+      {{ showAll ? $t('general.show_less') : showMoreString }}
+    </a>
   </div>
-
 </template>
 
 <script src="./emoji_reactions.js" ></script>
@@ -78,6 +82,7 @@
     display: flex;
     flex-direction: column;
     margin-left: 0.5em;
+    min-width: 5em;
 
     img {
       width: 1em;
diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
index 5ac73e97..37485383 100644
--- a/src/components/extra_buttons/extra_buttons.js
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -1,5 +1,8 @@
+import Popover from '../popover/popover.vue'
+
 const ExtraButtons = {
   props: [ 'status' ],
+  components: { Popover },
   methods: {
     deleteStatus () {
       const confirmed = window.confirm(this.$t('status.delete_confirm'))
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index 746f1c91..3a7f1283 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -1,11 +1,11 @@
 <template>
-  <v-popover
+  <Popover
     v-if="canDelete || canMute || canPin"
     trigger="click"
     placement="top"
     class="extra-button-popover"
   >
-    <div slot="popover">
+    <div slot="content">
       <div class="dropdown-menu">
         <button
           v-if="canMute && !status.thread_muted"
@@ -47,17 +47,17 @@
         </button>
       </div>
     </div>
-    <div class="button-icon">
-      <i class="icon-ellipsis" />
-    </div>
-  </v-popover>
+    <i
+      slot="trigger"
+      class="icon-ellipsis button-icon"
+    />
+  </Popover>
 </template>
 
 <script src="./extra_buttons.js" ></script>
 
 <style lang="scss">
 @import '../../_variables.scss';
-@import '../popper/popper.scss';
 
 .icon-ellipsis {
   cursor: pointer;
diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
index 757166ed..d4fdc53e 100644
--- a/src/components/moderation_tools/moderation_tools.js
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -1,4 +1,5 @@
 import DialogModal from '../dialog_modal/dialog_modal.vue'
+import Popover from '../popover/popover.vue'
 
 const FORCE_NSFW = 'mrf_tag:media-force-nsfw'
 const STRIP_MEDIA = 'mrf_tag:media-strip'
@@ -14,7 +15,6 @@ const ModerationTools = {
   ],
   data () {
     return {
-      showDropDown: false,
       tags: {
         FORCE_NSFW,
         STRIP_MEDIA,
@@ -24,11 +24,13 @@ const ModerationTools = {
         SANDBOX,
         QUARANTINE
       },
-      showDeleteUserDialog: false
+      showDeleteUserDialog: false,
+      toggled: false
     }
   },
   components: {
-    DialogModal
+    DialogModal,
+    Popover
   },
   computed: {
     tagsSet () {
@@ -89,6 +91,9 @@ const ModerationTools = {
             window.history.back()
           }
         })
+    },
+    setToggled (value) {
+      this.toggled = value
     }
   }
 }
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
index e78e05f1..b2d5acc5 100644
--- a/src/components/moderation_tools/moderation_tools.vue
+++ b/src/components/moderation_tools/moderation_tools.vue
@@ -1,13 +1,14 @@
 <template>
   <div>
-    <v-popover
+    <Popover
       trigger="click"
       class="moderation-tools-popover"
-      placement="bottom-end"
-      @show="showDropDown = true"
-      @hide="showDropDown = false"
+      placement="bottom"
+      :offset="{ y: 5 }"
+      @show="setToggled(true)"
+      @close="setToggled(false)"
     >
-      <div slot="popover">
+      <div slot="content">
         <div class="dropdown-menu">
           <span v-if="user.is_local">
             <button
@@ -122,12 +123,13 @@
         </div>
       </div>
       <button
+        slot="trigger"
         class="btn btn-default btn-block"
-        :class="{ toggled: showDropDown }"
+        :class="{ toggled }"
       >
         {{ $t('user_card.admin_menu.moderation') }}
       </button>
-    </v-popover>
+    </Popover>
     <portal to="modal">
       <DialogModal
         v-if="showDeleteUserDialog"
@@ -160,7 +162,6 @@
 
 <style lang="scss">
 @import '../../_variables.scss';
-@import '../popper/popper.scss';
 
 .menu-checkbox {
   float: right;
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
new file mode 100644
index 00000000..5881d266
--- /dev/null
+++ b/src/components/popover/popover.js
@@ -0,0 +1,156 @@
+
+const Popover = {
+  name: 'Popover',
+  props: {
+    // Action to trigger popover: either 'hover' or 'click'
+    trigger: String,
+    // Either 'top' or 'bottom'
+    placement: String,
+    // Takes object with properties 'x' and 'y', values of these can be
+    // 'container' for using offsetParent as boundaries for either axis
+    // or 'viewport'
+    boundTo: Object,
+    // Takes a top/bottom/left/right object, how much space to leave
+    // between boundary and popover element
+    margin: Object,
+    // Takes a x/y object and tells how many pixels to offset from
+    // anchor point on either axis
+    offset: Object,
+    // Additional styles you may want for the popover container
+    popoverClass: String
+  },
+  data () {
+    return {
+      hidden: true,
+      styles: { opacity: 0 },
+      oldSize: { width: 0, height: 0 }
+    }
+  },
+  methods: {
+    updateStyles () {
+      if (this.hidden) {
+        this.styles = {
+          opacity: 0
+        }
+        return
+      }
+
+      // Popover will be anchored around this element, trigger ref is the container, so
+      // its children are what are inside the slot. Expect only one slot="trigger".
+      const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
+      const screenBox = anchorEl.getBoundingClientRect()
+      // Screen position of the origin point for popover
+      const origin = { x: screenBox.left + screenBox.width * 0.5, y: screenBox.top }
+      const content = this.$refs.content
+      // Minor optimization, don't call a slow reflow call if we don't have to
+      const parentBounds = this.boundTo &&
+        (this.boundTo.x === 'container' || this.boundTo.y === 'container') &&
+        this.$el.offsetParent.getBoundingClientRect()
+      const margin = this.margin || {}
+
+      // What are the screen bounds for the popover? Viewport vs container
+      // when using viewport, using default margin values to dodge the navbar
+      const xBounds = this.boundTo && this.boundTo.x === 'container' ? {
+        min: parentBounds.left + (margin.left || 0),
+        max: parentBounds.right - (margin.right || 0)
+      } : {
+        min: 0 + (margin.left || 10),
+        max: window.innerWidth - (margin.right || 10)
+      }
+
+      const yBounds = this.boundTo && this.boundTo.y === 'container' ? {
+        min: parentBounds.top + (margin.top || 0),
+        max: parentBounds.bottom - (margin.bottom || 0)
+      } : {
+        min: 0 + (margin.top || 50),
+        max: window.innerHeight - (margin.bottom || 5)
+      }
+
+      let horizOffset = 0
+
+      // If overflowing from left, move it so that it doesn't
+      if ((origin.x - content.offsetWidth * 0.5) < xBounds.min) {
+        horizOffset += -(origin.x - content.offsetWidth * 0.5) + xBounds.min
+      }
+
+      // If overflowing from right, move it so that it doesn't
+      if ((origin.x + horizOffset + content.offsetWidth * 0.5) > xBounds.max) {
+        horizOffset -= (origin.x + horizOffset + content.offsetWidth * 0.5) - xBounds.max
+      }
+
+      // Default to whatever user wished with placement prop
+      let usingTop = this.placement !== 'bottom'
+
+      // Handle special cases, first force to displaying on top if there's not space on bottom,
+      // regardless of what placement value was. Then check if there's not space on top, and
+      // force to bottom, again regardless of what placement value was.
+      if (origin.y + content.offsetHeight > yBounds.max) usingTop = true
+      if (origin.y - content.offsetHeight < yBounds.min) usingTop = false
+
+      const yOffset = (this.offset && this.offset.y) || 0
+      const translateY = usingTop
+        ? -anchorEl.offsetHeight - yOffset - content.offsetHeight
+        : yOffset
+
+      const xOffset = (this.offset && this.offset.x) || 0
+      const translateX = (anchorEl.offsetWidth * 0.5) - content.offsetWidth * 0.5 + horizOffset + xOffset
+
+      // Note, separate translateX and translateY avoids blurry text on chromium,
+      // single translate or translate3d resulted in blurry text.
+      this.styles = {
+        opacity: 1,
+        transform: `translateX(${Math.floor(translateX)}px) translateY(${Math.floor(translateY)}px)`
+      }
+    },
+    showPopover () {
+      if (this.hidden) this.$emit('show')
+      this.hidden = false
+      this.$nextTick(this.updateStyles)
+    },
+    hidePopover () {
+      if (!this.hidden) this.$emit('close')
+      this.hidden = true
+      this.styles = { opacity: 0 }
+    },
+    onMouseenter (e) {
+      if (this.trigger === 'hover') this.showPopover()
+    },
+    onMouseleave (e) {
+      if (this.trigger === 'hover') this.hidePopover()
+    },
+    onClick (e) {
+      if (this.trigger === 'click') {
+        if (this.hidden) {
+          this.showPopover()
+        } else {
+          this.hidePopover()
+        }
+      }
+    },
+    onClickOutside (e) {
+      if (this.hidden) return
+      if (this.$el.contains(e.target)) return
+      this.hidePopover()
+    }
+  },
+  updated () {
+    // Monitor changes to content size, update styles only when content sizes have changed,
+    // that should be the only time we need to move the popover box if we don't care about scroll
+    // or resize
+    const content = this.$refs.content
+    if (!content) return
+    if (this.oldSize.width !== content.offsetWidth || this.oldSize.height !== content.offsetHeight) {
+      this.updateStyles()
+      this.oldSize = { width: content.offsetWidth, height: content.offsetHeight }
+    }
+  },
+  created () {
+    document.addEventListener('click', this.onClickOutside)
+  },
+  destroyed () {
+    document.removeEventListener('click', this.onClickOutside)
+    this.hidePopover()
+  }
+}
+
+export default Popover
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
new file mode 100644
index 00000000..a271cb1b
--- /dev/null
+++ b/src/components/popover/popover.vue
@@ -0,0 +1,118 @@
+<template>
+  <div
+    @mouseenter="onMouseenter"
+    @mouseleave="onMouseleave"
+  >
+    <div
+      ref="trigger"
+      @click="onClick"
+    >
+      <slot name="trigger" />
+    </div>
+    <div
+      v-if="!hidden"
+      ref="content"
+      :style="styles"
+      class="popover"
+      :class="popoverClass"
+    >
+      <slot
+        name="content"
+        class="popover-inner"
+        :close="hidePopover"
+      />
+    </div>
+  </div>
+</template>
+
+<script src="./popover.js" />
+
+<style lang=scss>
+@import '../../_variables.scss';
+
+.popover {
+  z-index: 8;
+  position: absolute;
+  min-width: 0;
+  transition: opacity 0.3s;
+
+  box-shadow: 1px 1px 4px rgba(0,0,0,.6);
+  box-shadow: var(--panelShadow);
+  border-radius: $fallback--btnRadius;
+  border-radius: var(--btnRadius, $fallback--btnRadius);
+
+  background-color: $fallback--bg;
+  background-color: var(--popover, $fallback--bg);
+  color: $fallback--text;
+  color: var(--popoverText, $fallback--text);
+  --faint: var(--popoverFaintText, $fallback--faint);
+  --faintLink: var(--popoverFaintLink, $fallback--faint);
+  --lightText: var(--popoverLightText, $fallback--lightText);
+  --postLink: var(--popoverPostLink, $fallback--link);
+  --postFaintLink: var(--popoverPostFaintLink, $fallback--link);
+  --icon: var(--popoverIcon, $fallback--icon);
+}
+
+.dropdown-menu {
+  display: block;
+  padding: .5rem 0;
+  font-size: 1rem;
+  text-align: left;
+  list-style: none;
+  max-width: 100vw;
+  z-index: 10;
+  white-space: nowrap;
+
+  .dropdown-divider {
+    height: 0;
+    margin: .5rem 0;
+    overflow: hidden;
+    border-top: 1px solid $fallback--border;
+    border-top: 1px solid var(--border, $fallback--border);
+  }
+
+  .dropdown-item {
+    line-height: 21px;
+    margin-right: 5px;
+    overflow: auto;
+    display: block;
+    padding: .25rem 1.0rem .25rem 1.5rem;
+    clear: both;
+    font-weight: 400;
+    text-align: inherit;
+    white-space: nowrap;
+    border: none;
+    border-radius: 0px;
+    background-color: transparent;
+    box-shadow: none;
+    width: 100%;
+    height: 100%;
+
+    --btnText: var(--popoverText, $fallback--text);
+
+    &-icon {
+      padding-left: 0.5rem;
+
+      i {
+        margin-right: 0.25rem;
+        color: var(--menuPopoverIcon, $fallback--icon)
+      }
+    }
+
+    &:active, &:hover {
+      background-color: $fallback--lightBg;
+      background-color: var(--selectedMenuPopover, $fallback--lightBg);
+      color: $fallback--link;
+      color: var(--selectedMenuPopoverText, $fallback--link);
+      --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
+      --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
+      --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
+      --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
+      i {
+        color: var(--selectedMenuPopoverIcon, $fallback--icon);
+      }
+    }
+
+  }
+}
+</style>
diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss
deleted file mode 100644
index 99b7e6fc..00000000
--- a/src/components/popper/popper.scss
+++ /dev/null
@@ -1,164 +0,0 @@
-@import '../../_variables.scss';
-
-.tooltip.popover {
-  z-index: 8;
-
-  .popover-inner {
-    box-shadow: 1px 1px 4px rgba(0,0,0,.6);
-    box-shadow: var(--panelShadow);
-    border-radius: $fallback--btnRadius;
-    border-radius: var(--btnRadius, $fallback--btnRadius);
-    background-color: $fallback--bg;
-    background-color: var(--popover, $fallback--bg);
-    color: $fallback--text;
-    color: var(--popoverText, $fallback--text);
-    --faint: var(--popoverFaintText, $fallback--faint);
-    --faintLink: var(--popoverFaintLink, $fallback--faint);
-    --lightText: var(--popoverLightText, $fallback--lightText);
-    --postLink: var(--popoverPostLink, $fallback--link);
-    --postFaintLink: var(--popoverPostFaintLink, $fallback--link);
-    --icon: var(--popoverIcon, $fallback--icon);
-  }
-
-  .popover-arrow {
-    width: 0;
-    height: 0;
-    border-style: solid;
-    position: absolute;
-    margin: 5px;
-    border-color: $fallback--bg;
-    border-color: var(--bg, $fallback--bg);
-  }
-
-  &[x-placement^="top"] {
-    margin-bottom: 5px;
-
-    .popover-arrow {
-      border-width: 5px 5px 0 5px;
-      border-left-color: transparent !important;
-      border-right-color: transparent !important;
-      border-bottom-color: transparent !important;
-      bottom: -4px;
-      left: calc(50% - 5px);
-      margin-top: 0;
-      margin-bottom: 0;
-    }
-  }
-
-  &[x-placement^="bottom"] {
-    margin-top: 5px;
-
-    .popover-arrow {
-      border-width: 0 5px 5px 5px;
-      border-left-color: transparent !important;
-      border-right-color: transparent !important;
-      border-top-color: transparent !important;
-      top: -4px;
-      left: calc(50% - 5px);
-      margin-top: 0;
-      margin-bottom: 0;
-    }
-  }
-
-  &[x-placement^="right"] {
-    margin-left: 5px;
-
-    .popover-arrow {
-      border-width: 5px 5px 5px 0;
-      border-left-color: transparent !important;
-      border-top-color: transparent !important;
-      border-bottom-color: transparent !important;
-      left: -4px;
-      top: calc(50% - 5px);
-      margin-left: 0;
-      margin-right: 0;
-    }
-  }
-
-  &[x-placement^="left"] {
-    margin-right: 5px;
-
-    .popover-arrow {
-      border-width: 5px 0 5px 5px;
-      border-top-color: transparent !important;
-      border-right-color: transparent !important;
-      border-bottom-color: transparent !important;
-      right: -4px;
-      top: calc(50% - 5px);
-      margin-left: 0;
-      margin-right: 0;
-    }
-  }
-
-  &[aria-hidden='true'] {
-    visibility: hidden;
-    opacity: 0;
-    transition: opacity .15s, visibility .15s;
-  }
-
-  &[aria-hidden='false'] {
-    visibility: visible;
-    opacity: 1;
-    transition: opacity .15s;
-  }
-}
-
-.dropdown-menu {
-  display: block;
-  padding: .5rem 0;
-  font-size: 1rem;
-  text-align: left;
-  list-style: none;
-  max-width: 100vw;
-  z-index: 10;
-
-  .dropdown-divider {
-    height: 0;
-    margin: .5rem 0;
-    overflow: hidden;
-    border-top: 1px solid $fallback--border;
-    border-top: 1px solid var(--border, $fallback--border);
-  }
-
-  .dropdown-item {
-    line-height: 21px;
-    margin-right: 5px;
-    overflow: auto;
-    display: block;
-    padding: .25rem 1.0rem .25rem 1.5rem;
-    clear: both;
-    font-weight: 400;
-    text-align: inherit;
-    white-space: normal;
-    border: none;
-    border-radius: 0px;
-    background-color: transparent;
-    box-shadow: none;
-    width: 100%;
-    height: 100%;
-
-    --btnText: var(--popoverText, $fallback--text);
-
-    &-icon {
-      padding-left: 0.5rem;
-
-      i {
-        margin-right: 0.25rem;
-      }
-    }
-
-    &:active, &:hover {
-      background-color: $fallback--lightBg;
-      background-color: var(--selectedMenuPopover, $fallback--lightBg);
-      color: $fallback--link;
-      color: var(--selectedMenuPopoverText, $fallback--link);
-      --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
-      --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
-      --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
-      --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
-      i {
-        color: var(--selectedMenuPopoverIcon, $fallback--icon);
-      }
-    }
-  }
-}
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index a6cf5b94..19949563 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -1,34 +1,25 @@
+import Popover from '../popover/popover.vue'
 import { mapGetters } from 'vuex'
 
 const ReactButton = {
   props: ['status', 'loggedIn'],
   data () {
     return {
-      showTooltip: false,
-      filterWord: '',
-      popperOptions: {
-        modifiers: {
-          preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
-        }
-      }
+      filterWord: ''
     }
   },
+  components: {
+    Popover
+  },
   methods: {
-    openReactionSelect () {
-      this.showTooltip = true
-      this.filterWord = ''
-    },
-    closeReactionSelect () {
-      this.showTooltip = false
-    },
-    addReaction (event, emoji) {
+    addReaction (event, emoji, close) {
       const existingReaction = this.status.emoji_reactions.find(r => r.name === emoji)
       if (existingReaction && existingReaction.me) {
         this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
       } else {
         this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
       }
-      this.closeReactionSelect()
+      close()
     }
   },
   computed: {
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index fb43ebaf..ab4b4fcd 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -1,13 +1,14 @@
 <template>
-  <v-popover
-    :popper-options="popperOptions"
-    :open="showTooltip"
-    trigger="manual"
+  <Popover
+    trigger="click"
     placement="top"
+    :offset="{ y: 5 }"
     class="react-button-popover"
-    @hide="closeReactionSelect"
   >
-    <div slot="popover">
+    <div
+      slot="content"
+      slot-scope="{close}"
+    >
       <div class="reaction-picker-filter">
         <input
           v-model="filterWord"
@@ -19,7 +20,7 @@
           v-for="emoji in commonEmojis"
           :key="emoji"
           class="emoji-button"
-          @click="addReaction($event, emoji)"
+          @click="addReaction($event, emoji, close)"
         >
           {{ emoji }}
         </span>
@@ -28,23 +29,20 @@
           v-for="(emoji, key) in emojis"
           :key="key"
           class="emoji-button"
-          @click="addReaction($event, emoji.replacement)"
+          @click="addReaction($event, emoji.replacement, close)"
         >
           {{ emoji.replacement }}
         </span>
         <div class="reaction-bottom-fader" />
       </div>
     </div>
-    <div
+    <i
       v-if="loggedIn"
-      @click.prevent="openReactionSelect"
-    >
-      <i
-        class="icon-smile button-icon add-reaction-button"
-        :title="$t('tool_tip.add_reaction')"
-      />
-    </div>
-  </v-popover>
+      slot="trigger"
+      class="icon-smile button-icon add-reaction-button"
+      :title="$t('tool_tip.add_reaction')"
+    />
+  </Popover>
 </template>
 
 <script src="./react_button.js" ></script>
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 76b038d9..ca295640 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -177,6 +177,8 @@
                 <StatusPopover
                   v-if="!isPreview"
                   :status-id="status.in_reply_to_status_id"
+                  class="reply-to-popover"
+                  style="min-width: 0"
                 >
                   <a
                     class="reply-to"
@@ -572,11 +574,10 @@ $status-margin: 0.75em;
       align-items: stretch;
 
       > .reply-to-and-accountname > a {
+        overflow: hidden;
         max-width: 100%;
         text-overflow: ellipsis;
-        overflow: hidden;
         white-space: nowrap;
-        display: inline-block;
         word-break: break-all;
       }
     }
@@ -585,7 +586,6 @@ $status-margin: 0.75em;
       display: flex;
       height: 18px;
       margin-right: 0.5em;
-      overflow: hidden;
       max-width: 100%;
       .icon-reply {
         transform: scaleX(-1);
@@ -596,6 +596,10 @@ $status-margin: 0.75em;
       display: flex;
     }
 
+    .reply-to-popover {
+      min-width: 0;
+    }
+
     .reply-to {
       display: flex;
     }
@@ -603,6 +607,7 @@ $status-margin: 0.75em;
     .reply-to-text {
       overflow: hidden;
       text-overflow: ellipsis;
+      white-space: nowrap;
       margin: 0 0.4em 0 0.2em;
     }
 
diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js
index 19f16bd9..cb55f67e 100644
--- a/src/components/status_popover/status_popover.js
+++ b/src/components/status_popover/status_popover.js
@@ -5,22 +5,14 @@ const StatusPopover = {
   props: [
     'statusId'
   ],
-  data () {
-    return {
-      popperOptions: {
-        modifiers: {
-          preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
-        }
-      }
-    }
-  },
   computed: {
     status () {
       return find(this.$store.state.statuses.allStatuses, { id: this.statusId })
     }
   },
   components: {
-    Status: () => import('../status/status.vue')
+    Status: () => import('../status/status.vue'),
+    Popover: () => import('../popover/popover.vue')
   },
   methods: {
     enter () {
diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue
index eacf4c06..11f6cb5a 100644
--- a/src/components/status_popover/status_popover.vue
+++ b/src/components/status_popover/status_popover.vue
@@ -1,11 +1,16 @@
 <template>
-  <v-popover
+  <Popover
+    trigger="hover"
     popover-class="status-popover"
-    placement="top-start"
-    :popper-options="popperOptions"
-    @show="enter()"
+    :bound-to="{ x: 'container' }"
+    @show="enter"
   >
-    <template slot="popover">
+    <template slot="trigger">
+      <slot />
+    </template>
+    <div
+      slot="content"
+    >
       <Status
         v-if="status"
         :is-preview="true"
@@ -18,10 +23,8 @@
       >
         <i class="icon-spin4 animate-spin" />
       </div>
-    </template>
-
-    <slot />
-  </v-popover>
+    </div>
+  </Popover>
 </template>
 
 <script src="./status_popover.js" ></script>
@@ -29,44 +32,19 @@
 <style lang="scss">
 @import '../../_variables.scss';
 
-.tooltip.popover.status-popover {
+.status-popover {
   font-size: 1rem;
   min-width: 15em;
   max-width: 95%;
-  margin-left: 0.5em;
 
-  .popover-inner {
-    border-color: $fallback--border;
-    border-color: var(--border, $fallback--border);
-    border-style: solid;
-    border-width: 1px;
-    border-radius: $fallback--tooltipRadius;
-    border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
-    box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
-    box-shadow: var(--popupShadow);
-  }
-
-  .popover-arrow::before {
-    position: absolute;
-    content: '';
-    left: -7px;
-    border: solid 7px transparent;
-    z-index: -1;
-  }
-
-  &[x-placement^="bottom-start"] .popover-arrow::before {
-    top: -2px;
-    border-top-width: 0;
-    border-bottom-color: $fallback--border;
-    border-bottom-color: var(--border, $fallback--border);
-  }
-
-  &[x-placement^="top-start"] .popover-arrow::before {
-    bottom: -2px;
-    border-bottom-width: 0;
-    border-top-color: $fallback--border;
-    border-top-color: var(--border, $fallback--border);
-  }
+  border-color: $fallback--border;
+  border-color: var(--border, $fallback--border);
+  border-style: solid;
+  border-width: 1px;
+  border-radius: $fallback--tooltipRadius;
+  border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+  box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
+  box-shadow: var(--popupShadow);
 
   .status-el.status-el {
     border: none;
diff --git a/src/main.js b/src/main.js
index baf73ac8..be4213fa 100644
--- a/src/main.js
+++ b/src/main.js
@@ -31,7 +31,6 @@ import VueChatScroll from 'vue-chat-scroll'
 import VueClickOutside from 'v-click-outside'
 import PortalVue from 'portal-vue'
 import VBodyScrollLock from './directives/body_scroll_lock'
-import VTooltip from 'v-tooltip'
 
 import afterStoreSetup from './boot/after_store.js'
 
@@ -44,13 +43,6 @@ Vue.use(VueChatScroll)
 Vue.use(VueClickOutside)
 Vue.use(PortalVue)
 Vue.use(VBodyScrollLock)
-Vue.use(VTooltip, {
-  popover: {
-    defaultTrigger: 'hover click',
-    defaultContainer: false,
-    defaultOffset: 5
-  }
-})
 
 const i18n = new VueI18n({
   // By default, use the browser locale, we will update it if neccessary
diff --git a/yarn.lock b/yarn.lock
index b794042f..0defefcb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5941,11 +5941,6 @@ pngjs@^3.3.0:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b"
 
-popper.js@^1.15.0:
-  version "1.15.0"
-  resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"
-  integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==
-
 portal-vue@^2.1.4:
   version "2.1.4"
   resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.4.tgz#1fc679d77e294dc8d026f1eb84aa467de11b392e"
@@ -7823,15 +7818,6 @@ v-click-outside@^2.1.1:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/v-click-outside/-/v-click-outside-2.1.3.tgz#b7297abe833a439dc0895e6418a494381e64b5e7"
 
-v-tooltip@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.2.tgz#8610d9eece2cc44fd66c12ef2f12eec6435cab9b"
-  integrity sha512-xQ+qzOFfywkLdjHknRPgMMupQNS8yJtf9Utd5Dxiu/0n4HtrxqsgDtN2MLZ0LKbburtSAQgyypuE/snM8bBZhw==
-  dependencies:
-    lodash "^4.17.11"
-    popper.js "^1.15.0"
-    vue-resize "^0.4.5"
-
 validate-npm-package-license@^3.0.1:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
@@ -7906,11 +7892,6 @@ vue-loader@^14.0.0:
     vue-style-loader "^4.0.1"
     vue-template-es2015-compiler "^1.6.0"
 
-vue-resize@^0.4.5:
-  version "0.4.5"
-  resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-0.4.5.tgz#4777a23042e3c05620d9cbda01c0b3cc5e32dcea"
-  integrity sha512-bhP7MlgJQ8TIkZJXAfDf78uJO+mEI3CaLABLjv0WNzr4CcGRGPIAItyWYnP6LsPA4Oq0WE+suidNs6dgpO4RHg==
-
 vue-router@^3.0.1:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be"

From ee582855d2f6ea02d8ee3cc4536b0b71ccece920 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 28 Feb 2020 17:20:40 +0000
Subject: [PATCH 265/483] Apply suggestion to src/modules/instance.js

---
 src/modules/instance.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/instance.js b/src/modules/instance.js
index f96337e4..ffece311 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -163,7 +163,7 @@ const instance = {
 
           // New theme presets don't have 'theme' property, they use 'source'
           const themeSource = themeData.source
-          if (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION) {
+          if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) {
             applyTheme(themeSource)
           } else {
             applyTheme(themeData.theme)

From a2070fd781a0709392550ca2d3c6a99f2bc5476c Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 28 Feb 2020 19:57:22 +0000
Subject: [PATCH 266/483] Update CHANGELOG.md

---
 CHANGELOG.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e77334b0..cba50360 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
+
+## [2.0.0] - 2019-02-28
 ### Added
 - Tons of color slots including ones for hover/pressed/toggled buttons
 - Experimental `--variable[,mod]` syntax support for color slots in themes. the `mod` makes color brighter/darker depending on background color (makes darker color brighter/darker depending on background color)
@@ -16,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Emoji reactions for statuses
 - MRF keyword policy disclosure
 ### Changed
+- Updated Pleroma default themes
 - theme engine update to 3 (themes v2.1 introduction)
 - massive internal changes in theme engine - slowly away from "generate things separately with spaghetti code" towards "feed all data into single 'generateTheme' function and declare slot inheritance and all in a separate file"
 - Breezy theme updates to make it closer to actual Breeze in some aspects

From 57e72b48c1fec74a860fd5abc75f0e9c4986c3f6 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 28 Feb 2020 20:16:53 +0000
Subject: [PATCH 267/483] Update CHANGELOG.md

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index cba50360..1677a5ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
 
-## [2.0.0] - 2019-02-28
+## [2.0.0] - 2020-02-28
 ### Added
 - Tons of color slots including ones for hover/pressed/toggled buttons
 - Experimental `--variable[,mod]` syntax support for color slots in themes. the `mod` makes color brighter/darker depending on background color (makes darker color brighter/darker depending on background color)

From e4ded887964dc18513735d5d32f61a4e479314dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C4=99drzej=20Tomaszewski?= <jederow@hotmail.com>
Date: Sun, 1 Mar 2020 17:32:22 +0100
Subject: [PATCH 268/483] Update polish translation

---
 src/i18n/pl.json | 320 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 310 insertions(+), 10 deletions(-)

diff --git a/src/i18n/pl.json b/src/i18n/pl.json
index 51cadfb6..4a4b1e31 100644
--- a/src/i18n/pl.json
+++ b/src/i18n/pl.json
@@ -1,7 +1,47 @@
 {
+  "about": {
+    "mrf": {
+      "federation": "Federacja",
+      "keyword": {
+        "keyword_policies": "Zasady słów kluczowych",
+        "ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
+        "reject": "Odrzucanie",
+        "replace": "Zastąpienie",
+        "is_replaced_by": "→"
+      },
+      "mrf_policies": "Włączone zasady MRF",
+      "mrf_policies_desc": "Zasady MRF zmieniają zachowanie federowania instancji. Następujące zasady są włączone:",
+      "simple": {
+        "simple_policies": "Zasady specyficzne dla instancji",
+        "accept": "Akceptowanie",
+        "accept_desc": "Ta instancja akceptuje tylko posty z wymienionych instancji:",
+        "reject": "Odrzucanie",
+        "reject_desc": "Ta instancja odrzuca posty z wymienionych instancji:",
+        "quarantine": "Kwarantanna",
+        "quarantine_desc": "Ta instancja wysyła tylko publiczne posty do wymienionych instancji:",
+        "ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
+        "ftl_removal_desc": "Ta instancja usuwa te instancje z \"Całej znanej sieci\"",
+        "media_removal": "Usuwanie multimediów",
+        "media_removal_desc": "Ta instancja usuwa multimedia z postów od wymienionych instancji:",
+        "media_nsfw": "Multimedia ustawione jako wrażliwe",
+        "media_nsfw_desc": "Ta instancja wymusza, by multimedia z wymienionych instancji były ustawione jako wrażliwe:"
+      }
+    },
+    "staff": "Obsługa"
+  },
   "chat": {
     "title": "Czat"
   },
+  "domain_mute_card": {
+    "mute": "Wycisz",
+    "mute_progress": "Wyciszam...",
+    "unmute": "Odcisz",
+    "unmute_progress": "Odciszam..."
+  },
+  "exporter": {
+    "export": "Eksportuj",
+    "processing": "Przetwarzam, za chwilę zostaniesz zapytany o ściągnięcie pliku"
+  },
   "features_panel": {
     "chat": "Czat",
     "gopher": "Gopher",
@@ -20,7 +60,15 @@
     "submit": "Wyślij",
     "more": "Więcej",
     "generic_error": "Wystąpił błąd",
-    "optional": "nieobowiązkowe"
+    "optional": "nieobowiązkowe",
+    "show_more": "Pokaż więcej",
+    "show_less": "Pokaż mniej",
+    "dismiss": "Odrzuć",
+    "cancel": "Anuluj",
+    "disable": "Wyłącz",
+    "enable": "Włącz",
+    "confirm": "Potwierdź",
+    "verify": "Zweryfikuj"
   },
   "image_cropper": {
     "crop_picture": "Przytnij obrazek",
@@ -28,6 +76,11 @@
     "save_without_cropping": "Zapisz bez przycinania",
     "cancel": "Anuluj"
   },
+  "importer": {
+    "submit": "Wyślij",
+    "success": "Zaimportowano pomyślnie",
+    "error": "Wystąpił błąd podczas importowania pliku."
+  },
   "login": {
     "login": "Zaloguj",
     "description": "Zaloguj używając OAuth",
@@ -36,7 +89,15 @@
     "placeholder": "n.p. lain",
     "register": "Zarejestruj",
     "username": "Użytkownik",
-    "hint": "Zaloguj się, aby dołączyć do dyskusji"
+    "hint": "Zaloguj się, aby dołączyć do dyskusji",
+    "authentication_code": "Kod weryfikacyjny",
+    "enter_recovery_code": "Wprowadź kod zapasowy",
+    "enter_two_factor_code": "Wprowadź kod weryfikacyjny",
+    "recovery_code": "Kod zapasowy",
+    "heading" : {
+      "totp" : "Weryfikacja dwuetapowa",
+      "recovery" : "Zapasowa weryfikacja dwuetapowa"
+    }
   },
   "media_modal": {
     "previous": "Poprzednie",
@@ -44,15 +105,18 @@
   },
   "nav": {
     "about": "O nas",
+    "administration": "Administracja",
     "back": "Wróć",
     "chat": "Lokalny czat",
     "friend_requests": "Prośby o możliwość obserwacji",
     "mentions": "Wzmianki",
+    "interactions": "Interakcje",
     "dms": "Wiadomości prywatne",
     "public_tl": "Publiczna oś czasu",
     "timeline": "Oś czasu",
     "twkn": "Cała znana sieć",
     "user_search": "Wyszukiwanie użytkowników",
+    "search": "Wyszukiwanie",
     "who_to_follow": "Sugestie obserwacji",
     "preferences": "Preferencje"
   },
@@ -64,7 +128,40 @@
     "notifications": "Powiadomienia",
     "read": "Przeczytane!",
     "repeated_you": "powtórzył(-a) twój status",
-    "no_more_notifications": "Nie masz więcej powiadomień"
+    "no_more_notifications": "Nie masz więcej powiadomień",
+    "migrated_to": "wyemigrował do",
+    "reacted_with": "zareagował z {0}"
+  },
+  "polls": {
+    "add_poll": "Dodaj ankietę",
+    "add_option": "Dodaj opcję",
+    "option": "Opcja",
+    "votes": "głosów",
+    "vote": "Głosuj",
+    "type": "Typ ankiety",
+    "single_choice": "jednokrotnego wyboru",
+    "multiple_choices": "wielokrotnego wyboru",
+    "expiry": "Czas trwania ankiety",
+    "expires_in": "Ankieta kończy się za{0}",
+    "expired": "Ankieta skończyła się {0} temu",
+    "not_enough_options": "Zbyt mało unikalnych opcji w ankiecie"
+  },
+  "emoji": {
+    "stickers": "Naklejki",
+    "emoji": "Emoji",
+    "keep_open": "Zostaw selektor otwarty",
+    "search_emoji": "Wyszukaj emoji",
+    "add_emoji": "Wstaw emoji",
+    "custom": "Niestandardowe emoji",
+    "unicode": "Emoji unicode",
+    "load_all_hint": "Załadowano pierwsze {saneAmount} emoji, Załadowanie wszystkich emoji może spowodować problemy z wydajnością.",
+    "load_all": "Ładuję wszystkie {emojiAmount} emoji"
+  },
+  "interactions": {
+    "favs_repeats": "Powtórzenia i ulubione",
+    "follows": "Nowi obserwujący",
+    "moves": "Użytkownik migruje",
+    "load_older": "Załaduj starsze interakcje"
   },
   "post_status": {
     "new_status": "Dodaj nowy status",
@@ -79,8 +176,14 @@
     },
     "content_warning": "Temat (nieobowiązkowy)",
     "default": "Właśnie wróciłem z kościoła",
-    "direct_warning": "Ten wpis zobaczą tylko osoby, o których wspomniałeś(-aś).",
+    "direct_warning_to_all": "Ten wpis zobaczą wszystkie osoby, o których wspomniałeś(-aś).",
+    "direct_warning_to_first_only": "Ten wpis zobaczą tylko te osoby, o których wspomniałeś(-aś) na początku wiadomości.",
     "posting": "Wysyłanie",
+    "scope_notice": {
+      "public": "Ten post będzie widoczny dla każdego",
+      "private": "Ten post będzie widoczny tylko dla twoich obserwujących",
+      "unlisted": "Ten post nie będzie widoczny na publicznej osi czasu i całej znanej sieci"
+    },
     "scope": {
       "direct": "Bezpośredni – Tylko dla wspomnianych użytkowników",
       "private": "Tylko dla obserwujących – Umieść dla osób, które cię obserwują",
@@ -109,8 +212,40 @@
       "password_confirmation_match": "musi być takie jak hasło"
     }
   },
+  "remote_user_resolver": {
+    "remote_user_resolver": "Wyszukiwarka użytkowników nietutejszych",
+    "searching_for": "Szukam",
+    "error": "Nie znaleziono."
+  },
+  "selectable_list": {
+    "select_all": "Zaznacz wszystko"
+  },
   "settings": {
     "app_name": "Nazwa aplikacji",
+    "security": "Bezpieczeństwo",
+    "enter_current_password_to_confirm": "Wprowadź obecne hasło, by potwierdzić twoją tożsamość",
+    "mfa": {
+      "otp" : "OTP",
+      "setup_otp" : "Ustaw OTP",
+      "wait_pre_setup_otp" : "początkowe ustawianie OTP",
+      "confirm_and_enable" : "Potwierdź i włącz OTP",
+      "title": "Weryfikacja dwuetapowa",
+      "generate_new_recovery_codes" : "Wygeneruj nowe kody zapasowe",
+      "warning_of_generate_new_codes" : "Po tym gdy generujesz nowe kody zapasowe, stare przestaną działać.",
+      "recovery_codes" : "Kody zapasowe.",
+      "waiting_a_recovery_codes": "Otrzymuję kody zapasowe...",
+      "recovery_codes_warning" : "Spisz kody na kartce papieru, albo zapisz je w bezpiecznym miejscu - inaczej nie zobaczysz ich już nigdy. Jeśli stracisz dostęp do twojej aplikacji 2FA i kodów zapasowych, nie będziesz miał dostępu do swojego konta.",
+      "authentication_methods" : "Metody weryfikacji",
+      "scan": {
+        "title": "Skanuj",
+        "desc": "Zeskanuj ten kod QR używając twojej aplikacji 2FA albo wpisz ten klucz:",
+        "secret_code": "Klucz"
+      },
+      "verify": {
+        "desc": "By włączyć weryfikację dwuetapową, wpisz kod z twojej aplikacji 2FA:"
+      }
+    },
+    "allow_following_move": "Zezwalaj na automatyczną obserwację gdy obserwowane konto migruje",
     "attachmentRadius": "Załączniki",
     "attachments": "Załączniki",
     "autoload": "Włącz automatyczne ładowanie po przewinięciu do końca strony",
@@ -119,12 +254,20 @@
     "avatarRadius": "Awatary",
     "background": "Tło",
     "bio": "Bio",
+    "block_export": "Eksport blokad",
+    "block_export_button": "Eksportuj twoje blokady do pliku .csv",
+    "block_import": "Import blokad",
+    "block_import_error": "Wystąpił błąd podczas importowania blokad",
+    "blocks_imported": "Zaimportowano blokady, przetwarzanie może zająć trochę czasu.",
     "blocks_tab": "Bloki",
     "btnRadius": "Przyciski",
     "cBlue": "Niebieski (odpowiedz, obserwuj)",
     "cGreen": "Zielony (powtórzenia)",
     "cOrange": "Pomarańczowy (ulubione)",
     "cRed": "Czerwony (anuluj)",
+    "change_email": "Zmień email",
+    "change_email_error": "Wystąpił problem podczas zmiany emaila.",
+    "changed_email": "Pomyślnie zmieniono email!",
     "change_password": "Zmień hasło",
     "change_password_error": "Podczas zmiany hasła wystąpił problem.",
     "changed_password": "Pomyślnie zmieniono hasło!",
@@ -140,16 +283,20 @@
     "delete_account_description": "Trwale usuń konto i wszystkie posty.",
     "delete_account_error": "Wystąpił problem z usuwaniem twojego konta. Jeżeli problem powtarza się, poinformuj administratora swojej instancji.",
     "delete_account_instructions": "Wprowadź swoje hasło w poniższe pole aby potwierdzić usunięcie konta.",
+    "discoverable": "Zezwól na odkrywanie tego konta w wynikach wyszukiwania i innych usługa.",
+    "domain_mutes": "Domeny",
     "avatar_size_instruction": "Zalecany minimalny rozmiar awatarów to 150x150 pikseli.",
+    "pad_emoji": "Dodaj odstęp z obu stron emoji podczas dodawania selektorem",
+    "emoji_reactions_on_timeline": "Pokaż reakcje emoji na osi czasu",
     "export_theme": "Zapisz motyw",
     "filtering": "Filtrowanie",
     "filtering_explanation": "Wszystkie statusy zawierające te słowa będą wyciszone. Jedno słowo na linijkę.",
     "follow_export": "Eksport obserwowanych",
     "follow_export_button": "Eksportuj swoją listę obserwowanych do pliku CSV",
-    "follow_export_processing": "Przetwarzanie, wkrótce twój plik zacznie się ściągać.",
     "follow_import": "Import obserwowanych",
     "follow_import_error": "Błąd przy importowaniu obserwowanych",
     "follows_imported": "Obserwowani zaimportowani! Przetwarzanie może trochę potrwać.",
+    "accent": "Akcent",
     "foreground": "Pierwszy plan",
     "general": "Ogólne",
     "hide_attachments_in_convo": "Ukrywaj załączniki w rozmowach",
@@ -162,6 +309,7 @@
     "hide_post_stats": "Ukrywaj statysyki postów (np. liczbę polubień)",
     "hide_user_stats": "Ukrywaj statysyki użytkowników (np. liczbę obserwujących)",
     "hide_filtered_statuses": "Ukrywaj filtrowane statusy",
+    "import_blocks_from_a_csv_file": "Importuj blokady z pliku CSV",
     "import_followers_from_a_csv_file": "Importuj obserwowanych z pliku CSV",
     "import_theme": "Załaduj motyw",
     "inputRadius": "Pola tekstowe",
@@ -181,17 +329,22 @@
     "use_contain_fit": "Nie przycinaj załączników na miniaturach",
     "name": "Imię",
     "name_bio": "Imię i bio",
+    "new_email": "Nowy email",
     "new_password": "Nowe hasło",
     "notification_visibility": "Rodzaje powiadomień do wyświetlania",
     "notification_visibility_follows": "Obserwacje",
     "notification_visibility_likes": "Ulubione",
     "notification_visibility_mentions": "Wzmianki",
     "notification_visibility_repeats": "Powtórzenia",
+    "notification_visibility_moves": "Użytkownik migruje",
+    "notification_visibility_emoji_reactions": "Reakcje",
     "no_rich_text_description": "Usuwaj formatowanie ze wszystkich postów",
     "no_blocks": "Bez blokad",
     "no_mutes": "Bez wyciszeń",
     "hide_follows_description": "Nie pokazuj kogo obserwuję",
     "hide_followers_description": "Nie pokazuj kto mnie obserwuje",
+    "hide_follows_count_description": "Nie pokazuj licznika obserwowanych",
+    "hide_followers_count_description": "Nie pokazuj licznika obserwujących",
     "show_admin_badge": "Pokazuj odznakę Administrator na moim profilu",
     "show_moderator_badge": "Pokazuj odznakę Moderator na moim profilu",
     "nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)",
@@ -212,10 +365,14 @@
     "reply_visibility_all": "Pokazuj wszystkie odpowiedzi",
     "reply_visibility_following": "Pokazuj tylko odpowiedzi skierowane do mnie i osób które obserwuję",
     "reply_visibility_self": "Pokazuj tylko odpowiedzi skierowane do mnie",
+    "autohide_floating_post_button": "Ukryj automatycznie przycisk \"Nowy post\" (mobile)",
     "saving_err": "Nie udało się zapisać ustawień",
     "saving_ok": "Zapisano ustawienia",
+    "search_user_to_block": "Wyszukaj kogo chcesz zablokować",
+    "search_user_to_mute": "Wyszukaj kogo chcesz wyciszyć",
     "security_tab": "Bezpieczeństwo",
     "scope_copy": "Kopiuj zakres podczas odpowiadania (DM-y zawsze są kopiowane)",
+    "minimal_scopes_mode": "Zminimalizuj opcje wyboru zakresu postów",
     "set_new_avatar": "Ustaw nowy awatar",
     "set_new_profile_background": "Ustaw nowe tło profilu",
     "set_new_profile_banner": "Ustaw nowy banner profilu",
@@ -228,19 +385,32 @@
     "post_status_content_type": "Post status content type",
     "stop_gifs": "Odtwarzaj GIFy po najechaniu kursorem",
     "streaming": "Włącz automatycznie strumieniowanie nowych postów gdy jesteś na początku strony",
+    "user_mutes": "Users",
+    "useStreamingApi": "Otrzymuj posty i powiadomienia w czasie rzeczywistym",
+    "useStreamingApiWarning": "(Niezalecane, eksperymentalne, pomija posty)",
     "text": "Tekst",
     "theme": "Motyw",
     "theme_help": "Użyj kolorów w notacji szesnastkowej (#rrggbb), by stworzyć swój motyw.",
     "theme_help_v2_1": "Możesz też zastąpić kolory i widoczność poszczególnych komponentów przełączając pola wyboru, użyj „Wyczyść wszystko” aby usunąć wszystkie zastąpienia.",
     "theme_help_v2_2": "Ikony pod niektórych wpisami są wskaźnikami kontrastu pomiędzy tłem a tekstem, po najechaniu na nie otrzymasz szczegółowe informacje. Zapamiętaj, że jeżeli używasz przezroczystości, wskaźniki pokazują najgorszy możliwy przypadek.",
     "tooltipRadius": "Etykiety/alerty",
+    "type_domains_to_mute": "Wpisz domeny, które chcesz wyciszyć",
     "upload_a_photo": "Wyślij zdjęcie",
     "user_settings": "Ustawienia użytkownika",
     "values": {
       "false": "nie",
       "true": "tak"
     },
+    "fun": "Zabawa",
+    "greentext": "Memiczne strzałki",
     "notifications": "Powiadomienia",
+    "notification_setting": "Otrzymuj powiadomienia od:",
+    "notification_setting_follows": "Ludzi których obserwujesz",
+    "notification_setting_non_follows": "Ludzi których nie obserwujesz",
+    "notification_setting_followers": "Ludzi którzy obserwują ciebie",
+    "notification_setting_non_followers": "Ludzi którzy nie obserwują ciebie",
+    "notification_mutes": "By przestać otrzymywać powiadomienia od jednego użytkownika, wycisz go",
+    "notification_blocks": "Blokowanie uzytkownika zatrzymuje wszystkie powiadomienia i odsubskrybowuje go.",
     "enable_web_push_notifications": "Włącz powiadomienia push",
     "style": {
       "switcher": {
@@ -252,7 +422,24 @@
         "save_load_hint": "Opcje „zachowaj” pozwalają na pozostanie przy obecnych opcjach po wybraniu lub załadowaniu motywu, jak i przechowywanie ich podczas eksportowania motywu. Jeżeli wszystkie są odznaczone, eksportowanie motywu spowoduje zapisanie wszystkiego.",
         "reset": "Wyzeruj",
         "clear_all": "Wyczyść wszystko",
-        "clear_opacity": "Wyczyść widoczność"
+        "clear_opacity": "Wyczyść widoczność",
+        "load_theme": "Załaduj motyw",
+        "keep_as_is": "Zostaw po staremu",
+        "use_snapshot": "Stara wersja",
+        "use_source": "Nowa wersja",
+        "help": {
+          "upgraded_from_v2": "PleromaFE zostało zaaktualizowane, motyw może wyglądać nieco inaczej niż sobie zapamiętałeś.",
+          "v2_imported": "Plik który zaimportowałeś został stworzony dla starszego FE. Próbujemy zwiększyć kompatybiliność, lecz wciąż mogą występować rozbieżności.",
+          "future_version_imported": "Plik który zaimportowałeś został stworzony w nowszej wersji FE.",
+          "older_version_imported": "Plik który zaimportowałeś został stworzony w starszej wersji FE.",
+          "snapshot_present": "Migawka motywu jest załadowana, więc wszystkie wartości zostały nadpisane. Zamiast tego, możesz załadować właściwe dane motywu",
+          "snapshot_missing": "Nie znaleziono migawki motywu w pliku, więc motyw może wyglądać inaczej niż pierwotnie zaplanowano.",
+          "fe_upgraded": "Silnik motywów PleromaFE został zaaktualizowany.",
+          "fe_downgraded": "Wersja PleromaFE została cofnięta.",
+          "migration_snapshot_ok": "Żeby być bezpiecznym, migawka motywu została załadowana. Możesz spróbować załadować dane motywu.",
+          "migration_napshot_gone": "Z jakiegoś powodu migawka zniknęła, niektóre rzeczy mogą wyglądać inaczej niż sobie zapamiętałeś.",
+          "snapshot_source_mismatch": "Konflikt wersji: najprawdopodobniej FE zostało cofnięte do poprzedniej wersji i zaaktualizowane ponownie, jeśli zmieniłeś motyw używając starszej wersji FE, najprawdopodobniej chcesz używać starszej wersji, w przeciwnym razie użyj nowej wersji."
+        }
       },
       "common": {
         "color": "Kolor",
@@ -280,14 +467,28 @@
         "_tab_label": "Zaawansowane",
         "alert": "Tło alertu",
         "alert_error": "Błąd",
+        "alert_warning": "Ostrzeżenie",
+        "alert_neutral": "Neutralne",
+        "post": "Posty/Bio użytkowników",
         "badge": "Tło odznaki",
+        "popover": "Etykiety, menu, popovery",
         "badge_notification": "Powiadomienie",
         "panel_header": "Nagłówek panelu",
         "top_bar": "Górny pasek",
         "borders": "Granice",
         "buttons": "Przyciski",
         "inputs": "Pola wejścia",
-        "faint_text": "Zanikający tekst"
+        "faint_text": "Zanikający tekst",
+        "underlay": "Podkład",
+        "poll": "Wykres ankiety",
+        "icons": "Ikony",
+        "highlight": "Podświetlone elementy",
+        "pressed": "Naciśnięte",
+        "selectedPost": "Wybrany post",
+        "selectedMenu": "Wybrany element menu",
+        "disabled": "Wyłączone",
+        "toggled": "Przełączone",
+        "tabs": "Karty"
       },
       "radii": {
         "_tab_label": "Zaokrąglenie"
@@ -300,7 +501,7 @@
         "blur": "Rozmycie",
         "spread": "Szerokość",
         "inset": "Inset",
-        "hint": "Możesz też używać --zmiennych jako kolorów, aby wykorzystać zmienne CSS3. Pamiętaj, że ustawienie widoczności nie będzie wtedy działać.",
+        "hintV3": "Dla cieni możesz również użyć notacji {0} by użyć inny slot koloru.",
         "filter_hint": {
           "always_drop_shadow": "Ostrzeżenie, ten cień zawsze używa {0} jeżeli to obsługiwane przez przeglądarkę.",
           "drop_shadow_syntax": "{0} nie obsługuje parametru {1} i słowa kluczowego {2}.",
@@ -357,6 +558,40 @@
       "frontend_version": "Wersja front-endu"
     }
   },
+  "time": {
+    "day": "{0} dzień",
+    "days": "{0} dni",
+    "day_short": "{0}d",
+    "days_short": "{0}d",
+    "hour": "{0} godzina",
+    "hours": "{0} godzin",
+    "hour_short": "{0} godz.",
+    "hours_short": "{0} godz.",
+    "in_future": "za {0}",
+    "in_past": "{0} temu",
+    "minute": "{0} minuta",
+    "minutes": "{0} minut",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} miesiąc",
+    "months": "{0} miesięcy",
+    "month_short": "{0} mies.",
+    "months_short": "{0} mies.",
+    "now": "teraz",
+    "now_short": "teraz",
+    "second": "{0} sekunda",
+    "seconds": "{0} sekund",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} tydzień",
+    "weeks": "{0} tygodni",
+    "week_short": "{0} tydz.",
+    "weeks_short": "{0} tyg.",
+    "year": "{0} rok",
+    "years": "{0} lata",
+    "year_short": "{0} r.",
+    "years_short": "{0} lata"
+  },
   "timeline": {
     "collapse": "Zwiń",
     "conversation": "Rozmowa",
@@ -370,8 +605,17 @@
     "no_statuses": "Brak statusów"
   },
   "status": {
+    "favorites": "Ulubione",
+    "repeats": "Powtórzenia",
+    "delete": "Usuń status",
+    "pin": "Przypnij na profilu",
+    "unpin": "Odepnij z profilu",
+    "pinned": "Przypnięte",
+    "delete_confirm": "Czy naprawdę chcesz usunąć ten status?",
     "reply_to": "Odpowiedź dla",
-    "replies_list": "Odpowiedzi:"
+    "replies_list": "Odpowiedzi:",
+    "mute_conversation": "Wycisz konwersację",
+    "unmute_conversation": "Odcisz konwersację"
   },
   "user_card": {
     "approve": "Przyjmij",
@@ -388,25 +632,60 @@
     "followers": "Obserwujący",
     "following": "Obserwowany!",
     "follows_you": "Obserwuje cię!",
+    "hidden": "Ukryte",
     "its_you": "To ty!",
     "media": "Media",
+    "mention": "Wspomnienie",
     "mute": "Wycisz",
     "muted": "Wyciszony(-a)",
     "per_day": "dziennie",
     "remote_follow": "Zdalna obserwacja",
+    "report": "Raportuj",
     "statuses": "Statusy",
+    "subscribe": "Subskrybuj",
+    "unsubscribe": "Odsubskrybuj",
     "unblock": "Odblokuj",
     "unblock_progress": "Odblokowuję…",
     "block_progress": "Blokuję…",
     "unmute": "Cofnij wyciszenie",
     "unmute_progress": "Cofam wyciszenie…",
-    "mute_progress": "Wyciszam…"
+    "mute_progress": "Wyciszam…",
+    "hide_repeats": "Ukryj powtórzenia",
+    "show_repeats": "Pokaż powtórzenia",
+    "admin_menu": {
+      "moderation": "Moderacja",
+      "grant_admin": "Przyznaj admina",
+      "revoke_admin": "Odwołaj admina",
+      "grant_moderator": "Przyznaj moderatora",
+      "revoke_moderator": "Odwołaj moderatora",
+      "activate_account": "Aktywuj konto",
+      "deactivate_account": "Dezaktywuj konto",
+      "delete_account": "Usuń konto",
+      "force_nsfw": "Oznacz wszystkie posty jako NSFW",
+      "strip_media": "Usuń multimedia z postów",
+      "force_unlisted": "Wymuś posty na niepubliczne",
+      "sandbox": "Wymuś by posty były tylko dla obserwujących",
+      "disable_remote_subscription": "Zakaż obserwowania użytkownika ze zdalnych instancji",
+      "disable_any_subscription": "Zakaż całkowicie obserwowania użytkownika",
+      "quarantine": "Zakaż federowania postów od tego użytkownika",
+      "delete_user": "Usuń użytkownika",
+      "delete_user_confirmation": "Czy jesteś absolutnie pewny? Ta operacja nie może być cofnięta."
+    }
   },
   "user_profile": {
     "timeline_title": "Oś czasu użytkownika",
     "profile_does_not_exist": "Przepraszamy, ten profil nie istnieje.",
     "profile_loading_error": "Przepraszamy, wystąpił błąd podczas ładowania tego profilu."
   },
+  "user_reporting": {
+    "title": "Raportowanie {0}",
+    "add_comment_description": "Raport zostanie wysłany do moderatorów instancji. Możesz dodać powód dlaczego raportujesz to konto poniżej:",
+    "additional_comments": "Dodatkowe komentarze",
+    "forward_description": "To konto jest z innego serwera. Wysłać również tam kopię raportu?",
+    "forward_to": "Przekaż do{0}",
+    "submit": "Wyślij",
+    "generic_error": "Wystąpił błąd podczas przetwarzania twojej prośby."
+  },
   "who_to_follow": {
     "more": "Więcej",
     "who_to_follow": "Propozycje obserwacji"
@@ -416,6 +695,7 @@
     "repeat": "Powtórz",
     "reply": "Odpowiedz",
     "favorite": "Dodaj do ulubionych",
+    "add_reaction": "Dodaj reakcję",
     "user_settings": "Ustawienia użytkownika"
   },
   "upload":{
@@ -431,5 +711,25 @@
       "GiB": "GiB",
       "TiB": "TiB"
     }
+  },
+  "search": {
+    "people": "Ludzie",
+    "hashtags": "Hasztagi",
+    "person_talking": "{count} osoba rozmawia o tym",
+    "people_talking": "{count} osób rozmawia o tym",
+    "no_results": "Brak wyników"
+  },
+  "password_reset": {
+    "forgot_password": "Zapomniałeś hasła?",
+    "password_reset": "Reset hasła",
+    "instruction": "Wprowadź swój adres email lub nazwę użytkownika. Wyślemy ci link z którym możesz zresetować hasło.",
+    "placeholder": "Twój email lub nazwa użytkownika",
+    "check_email": "Sprawdź pocztę, aby uzyskać link do zresetowania hasła.",
+    "return_home": "Wróć do strony głównej",
+    "not_found": "Nie mogliśmy znaleźć tego emaila lub nazwy użytkownika.",
+    "too_many_requests": "Przekroczyłeś limit prób, spróbuj ponownie później.",
+    "password_reset_disabled": "Resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji.",
+    "password_reset_required": "Musisz zresetować hasło, by się zalogować.",
+    "password_reset_required_but_mailer_is_disabled": "Musisz zresetować hasło, ale resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji."
   }
 }

From ab4005add57f36cc78b774971f9942c5894362dc Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Mon, 2 Mar 2020 08:35:57 +0200
Subject: [PATCH 269/483] add status unavailable message when status can't be
 loaded in status preview

---
 src/components/status_popover/status_popover.js  |  7 +++++++
 src/components/status_popover/status_popover.vue | 10 ++++++++--
 src/i18n/en.json                                 |  3 ++-
 src/i18n/fi.json                                 |  3 ++-
 src/modules/statuses.js                          |  2 +-
 5 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js
index cb55f67e..159132a9 100644
--- a/src/components/status_popover/status_popover.js
+++ b/src/components/status_popover/status_popover.js
@@ -5,6 +5,11 @@ const StatusPopover = {
   props: [
     'statusId'
   ],
+  data () {
+    return {
+      error: false
+    }
+  },
   computed: {
     status () {
       return find(this.$store.state.statuses.allStatuses, { id: this.statusId })
@@ -18,6 +23,8 @@ const StatusPopover = {
     enter () {
       if (!this.status) {
         this.$store.dispatch('fetchStatus', this.statusId)
+          .then(data => (this.error = false))
+          .catch(e => (this.error = true))
       }
     }
   }
diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue
index 11f6cb5a..f5948207 100644
--- a/src/components/status_popover/status_popover.vue
+++ b/src/components/status_popover/status_popover.vue
@@ -17,9 +17,15 @@
         :statusoid="status"
         :compact="true"
       />
+      <div
+        v-else-if="error"
+        class="status-preview-no-content faint"
+      >
+        {{ $t('status.status_unavailable') }}
+      </div>
       <div
         v-else
-        class="status-preview-loading"
+        class="status-preview-no-content"
       >
         <i class="icon-spin4 animate-spin" />
       </div>
@@ -50,7 +56,7 @@
     border: none;
   }
 
-  .status-preview-loading {
+  .status-preview-no-content {
     padding: 1em;
     text-align: center;
 
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 82acc1ab..54d0608e 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -615,7 +615,8 @@
     "reply_to": "Reply to",
     "replies_list": "Replies:",
     "mute_conversation": "Mute conversation",
-    "unmute_conversation": "Unmute conversation"
+    "unmute_conversation": "Unmute conversation",
+    "status_unavailable": "Status unavailable"
   },
   "user_card": {
     "approve": "Approve",
diff --git a/src/i18n/fi.json b/src/i18n/fi.json
index ac8b2ac9..926e6087 100644
--- a/src/i18n/fi.json
+++ b/src/i18n/fi.json
@@ -289,7 +289,8 @@
     "reply_to": "Vastaus",
     "replies_list": "Vastaukset:",
     "mute_conversation": "Hiljennä keskustelu",
-    "unmute_conversation": "Poista hiljennys"
+    "unmute_conversation": "Poista hiljennys",
+    "status_unavailable": "Viesti ei saatavissa"
   },
   "user_card": {
     "approve": "Hyväksy",
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 25b62ac7..f1b7dcbd 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -616,7 +616,7 @@ const statuses = {
       commit('setNotificationsSilence', { value })
     },
     fetchStatus ({ rootState, dispatch }, id) {
-      rootState.api.backendInteractor.fetchStatus({ id })
+      return rootState.api.backendInteractor.fetchStatus({ id })
         .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
     },
     deleteStatus ({ rootState, commit }, status) {

From 0702934f4f05595194c73cc463b2e72a27e3b1e2 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 4 Mar 2020 00:23:14 +0200
Subject: [PATCH 270/483] fix trasparency problems in some cases (purple
 headers)

---
 src/modules/config.js                         | 1 +
 src/services/theme_data/theme_data.service.js | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/modules/config.js b/src/modules/config.js
index e6b373b4..7997521d 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -102,6 +102,7 @@ const config = {
           setPreset(value)
           break
         case 'customTheme':
+        case 'customThemeSource':
           applyTheme(value)
       }
     }
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index e6ff82e6..de6561cd 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -352,7 +352,8 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
   }
   const opacitySlot = getOpacitySlot(key)
   const ownOpacitySlot = value.opacity
-  if (opacitySlot && (outputColor.a === undefined || ownOpacitySlot)) {
+  const opacityOverriden = ownOpacitySlot && sourceOpacity[opacitySlot] !== undefined
+  if (opacitySlot && (outputColor.a === undefined || opacityOverriden)) {
     const dependencySlot = deps[0]
     if (dependencySlot && colors[dependencySlot] === 'transparent') {
       outputColor.a = 0

From 147364b80cae7a0fac1ccf00b5669a20de1b7e67 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 4 Mar 2020 17:30:59 +0200
Subject: [PATCH 271/483] mention status preview fix in changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1677a5ba..24c193b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Notifications column now cleans itself up to optimize performance when tab is left open for a long time
 - 403 messaging
 ### Fixed
+- Fixed loader-spinner not disappearing when a status preview fails to load
 - anon viewers won't get theme data saved to local storage, so admin changing default theme will have an effect for users coming back to instance.
 - Single notifications left unread when hitting read on another device/tab
 - Registration fixed

From 9f2c1b4008ef7bb77c98e80f75b41eaa9e92b90a Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 6 Mar 2020 21:17:24 +0200
Subject: [PATCH 272/483] fix several issues related to opacity

---
 src/services/theme_data/theme_data.service.js | 38 ++++++++++++++++---
 1 file changed, 32 insertions(+), 6 deletions(-)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index de6561cd..c3fdbd87 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -350,17 +350,43 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
   if (!outputColor) {
     throw new Error('Couldn\'t generate color for ' + key)
   }
-  const opacitySlot = getOpacitySlot(key)
+
+  const opacitySlot = value.opacity || getOpacitySlot(key)
   const ownOpacitySlot = value.opacity
-  const opacityOverriden = ownOpacitySlot && sourceOpacity[opacitySlot] !== undefined
-  if (opacitySlot && (outputColor.a === undefined || opacityOverriden)) {
+
+  if (sourceColor === 'transparent') {
+    outputColor.a = 0
+  } else if (ownOpacitySlot === null) {
+    outputColor.a = 1
+  } else {
+    const opacityOverriden = ownOpacitySlot && sourceOpacity[opacitySlot] !== undefined
+
     const dependencySlot = deps[0]
-    if (dependencySlot && colors[dependencySlot] === 'transparent') {
-      outputColor.a = 0
+    const dependencyColor = dependencySlot && colors[dependencySlot]
+
+    if (!ownOpacitySlot && dependencyColor && !value.textColor && ownOpacitySlot !== null) {
+      // Inheriting color from dependency (weird, i know)
+      // except if it's a text color or opacity slot is set to 'null'
+      outputColor.a = dependencyColor.a
+    } else if (!dependencyColor && !opacitySlot) {
+      // Remove any alpha channel if no dependency and no opacitySlot found
+      delete outputColor.a
     } else {
-      outputColor.a = Number(sourceOpacity[opacitySlot]) || OPACITIES[opacitySlot].defaultValue || 1
+      // Otherwise try to assign opacity
+      if (dependencyColor && dependencyColor.a === 0) {
+        // transparent dependency shall make dependents transparent too
+        outputColor.a = 0
+      } else {
+        // Otherwise check if opacity is overriden and use that or default value instead
+        outputColor.a = Number(
+          opacityOverriden
+            ? sourceOpacity[opacitySlot]
+            : (OPACITIES[opacitySlot] || {}).defaultValue
+        )
+      }
     }
   }
+
   if (opacitySlot) {
     return {
       colors: { ...colors, [key]: outputColor },

From 7aa5bf0896c045d5d4a4e19fe7eff2c61fd2acf9 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 6 Mar 2020 21:20:42 +0200
Subject: [PATCH 273/483] prioritize disabled opacity over transparent keyword

---
 src/services/theme_data/theme_data.service.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index c3fdbd87..44fb575c 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -354,10 +354,10 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
   const opacitySlot = value.opacity || getOpacitySlot(key)
   const ownOpacitySlot = value.opacity
 
-  if (sourceColor === 'transparent') {
-    outputColor.a = 0
-  } else if (ownOpacitySlot === null) {
+  if (ownOpacitySlot === null) {
     outputColor.a = 1
+  } else if (sourceColor === 'transparent') {
+    outputColor.a = 0
   } else {
     const opacityOverriden = ownOpacitySlot && sourceOpacity[opacitySlot] !== undefined
 

From 550080bd82bf39e35b2b481d8643bf1ac7461d7c Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 6 Mar 2020 21:39:17 +0200
Subject: [PATCH 274/483] fix last issue

---
 src/services/theme_data/theme_data.service.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 44fb575c..4d7e4f67 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -384,6 +384,9 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
             : (OPACITIES[opacitySlot] || {}).defaultValue
         )
       }
+      if (Number.isNaN(outputColor.a)) {
+        outputColor.a = 1
+      }
     }
   }
 

From a485386a3b07f831859fcefa9cd429fc801fd8eb Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 6 Mar 2020 21:48:40 +0200
Subject: [PATCH 275/483] fix tests

---
 src/services/theme_data/theme_data.service.js | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index 4d7e4f67..dd87e3cf 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -384,12 +384,13 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
             : (OPACITIES[opacitySlot] || {}).defaultValue
         )
       }
-      if (Number.isNaN(outputColor.a)) {
-        outputColor.a = 1
-      }
     }
   }
 
+  if (Number.isNaN(outputColor.a) || outputColor.a === undefined) {
+    outputColor.a = 1
+  }
+
   if (opacitySlot) {
     return {
       colors: { ...colors, [key]: outputColor },

From b9820b84a14119f3a7a4e1f663927b1ad455a5c2 Mon Sep 17 00:00:00 2001
From: Alex <caskd@420blaze.it>
Date: Sat, 14 Mar 2020 19:41:38 +0000
Subject: [PATCH 276/483] Prevent overflow for long usernames/domains

---
 src/components/notifications/notifications.scss | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index f5b13322..a8f4430f 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -81,6 +81,7 @@
 
   .follow-text, .move-text {
     padding: 0.5em 0;
+    overflow-wrap: break-word;
   }
 
   .status-el {

From 8c5946b72881c38ce43a4b25bda8a38d4f08aac3 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Mon, 30 Mar 2020 12:39:28 -0500
Subject: [PATCH 277/483] Add button in 3dot menu to copy status link to
 clipboard

---
 src/components/extra_buttons/extra_buttons.js  | 10 ++++++++++
 src/components/extra_buttons/extra_buttons.vue |  8 +++++++-
 src/i18n/en.json                               |  3 ++-
 static/fontello.json                           |  8 +++++++-
 4 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
index 37485383..2dd77c66 100644
--- a/src/components/extra_buttons/extra_buttons.js
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -3,6 +3,11 @@ import Popover from '../popover/popover.vue'
 const ExtraButtons = {
   props: [ 'status' ],
   components: { Popover },
+  data: function () {
+    return {
+      statusLink: `https://${this.$store.state.instance.name}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
+    }
+  },
   methods: {
     deleteStatus () {
       const confirmed = window.confirm(this.$t('status.delete_confirm'))
@@ -29,6 +34,11 @@ const ExtraButtons = {
       this.$store.dispatch('unmuteConversation', this.status.id)
         .then(() => this.$emit('onSuccess'))
         .catch(err => this.$emit('onError', err.error.error))
+    },
+    copyLink () {
+      navigator.clipboard.writeText(this.statusLink)
+        .then(() => this.$emit('onSuccess'))
+        .catch(err => this.$emit('onError', err.error.error))
     }
   },
   computed: {
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index 3a7f1283..c785a180 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -1,6 +1,5 @@
 <template>
   <Popover
-    v-if="canDelete || canMute || canPin"
     trigger="click"
     placement="top"
     class="extra-button-popover"
@@ -45,6 +44,13 @@
         >
           <i class="icon-cancel" /><span>{{ $t("status.delete") }}</span>
         </button>
+        <button
+          v-close-popover
+          class="dropdown-item dropdown-item-icon"
+          @click.prevent="copyLink"
+        >
+          <i class="icon-share" /><span>{{ $t("status.copy_link") }}</span>
+        </button>
       </div>
     </div>
     <i
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 54d0608e..3a16585d 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -616,7 +616,8 @@
     "replies_list": "Replies:",
     "mute_conversation": "Mute conversation",
     "unmute_conversation": "Unmute conversation",
-    "status_unavailable": "Status unavailable"
+    "status_unavailable": "Status unavailable",
+    "copy_link": "Copy link to status"
   },
   "user_card": {
     "approve": "Approve",
diff --git a/static/fontello.json b/static/fontello.json
index 5a7086a2..9e56232c 100755
--- a/static/fontello.json
+++ b/static/fontello.json
@@ -345,6 +345,12 @@
       "css": "link",
       "code": 59427,
       "src": "fontawesome"
+    },
+    {
+      "uid": "4aad6bb50b02c18508aae9cbe14e784e",
+      "css": "share",
+      "code": 61920,
+      "src": "fontawesome"
     }
   ]
-}
+}
\ No newline at end of file

From 40005240eb30ce59035299dc348a3962626e32c6 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Tue, 31 Mar 2020 14:46:38 -0500
Subject: [PATCH 278/483] Send credentials for favourited_by and reblogged_by
 API endpoints

This ensures the data is fetchable on private instances
---
 src/services/api/api.service.js | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 03e88ae2..7db1d094 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -880,12 +880,20 @@ const fetchPoll = ({ pollId, credentials }) => {
   )
 }
 
-const fetchFavoritedByUsers = ({ id }) => {
-  return promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id) }).then((users) => users.map(parseUser))
+const fetchFavoritedByUsers = ({ id, credentials }) => {
+  return promisedRequest({
+    url: MASTODON_STATUS_FAVORITEDBY_URL(id),
+    method: 'GET',
+    credentials
+  }).then((users) => users.map(parseUser))
 }
 
-const fetchRebloggedByUsers = ({ id }) => {
-  return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser))
+const fetchRebloggedByUsers = ({ id, credentials }) => {
+  return promisedRequest({
+    url: MASTODON_STATUS_REBLOGGEDBY_URL(id),
+    method: 'GET',
+    credentials
+  }).then((users) => users.map(parseUser))
 }
 
 const fetchEmojiReactions = ({ id, credentials }) => {

From b4e8b4554a69fcdb86a12484a2c6116207c0500b Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Wed, 8 Apr 2020 00:04:53 +0300
Subject: [PATCH 279/483] CHANGELOG.md: Add entries for upcoming 2.0.2 release

---
 CHANGELOG.md | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24c193b9..375e560f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
 
+## [2.0.2] - 2020-04-08
+### Fixed
+- Favorite/Repeat avatars not showing up on private instances/non-public posts
+- Autocorrect getting triggered in the captcha field
+- Overflow on long domains in follow/move notifications
+
+### Changed
+- Polish translation updated
+
 ## [2.0.0] - 2020-02-28
 ### Added
 - Tons of color slots including ones for hover/pressed/toggled buttons

From 18fa338d43b6b7b61b484ae6106ef3b95e5adeee Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 13 Apr 2020 15:26:55 +0400
Subject: [PATCH 280/483] Fix pagination

---
 src/services/api/api.service.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 7db1d094..ad2b2ad5 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -540,7 +540,7 @@ const fetchTimeline = ({
     params.push(['with_move', withMove])
   }
 
-  params.push(['count', 20])
+  params.push(['limit', 20])
   params.push(['with_muted', withMuted])
 
   const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')

From 2fbb94fe5c7f6743a699e6e1ccb2cd8de341ccca Mon Sep 17 00:00:00 2001
From: Karol Kosek <krkk@krkk.ct8.pl>
Date: Sat, 18 Apr 2020 18:48:45 +0200
Subject: [PATCH 281/483] Fix user names with the RTL char in notifications

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

diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 411c0271..51875747 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -47,7 +47,7 @@
         <span class="notification-details">
           <div class="name-and-action">
             <!-- eslint-disable vue/no-v-html -->
-            <span
+            <bdi
               v-if="!!notification.from_profile.name_html"
               class="username"
               :title="'@'+notification.from_profile.screen_name"

From 6bb75a3a6d8452a3e1b88085fe87cf27386f222c Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Tue, 21 Apr 2020 23:27:51 +0300
Subject: [PATCH 282/483] make relationships separate from users

---
 src/boot/after_store.js                       |  3 ++
 .../account_actions/account_actions.js        |  2 +-
 .../account_actions/account_actions.vue       |  8 ++--
 .../basic_user_card/basic_user_card.vue       |  2 +-
 src/components/block_card/block_card.js       |  5 ++-
 src/components/follow_button/follow_button.js | 14 +++----
 src/components/follow_card/follow_card.js     |  3 ++
 src/components/follow_card/follow_card.vue    |  5 ++-
 src/components/mute_card/mute_card.js         |  9 +++--
 src/components/notification/notification.js   |  2 +-
 src/components/notification/notification.vue  |  2 +-
 src/components/side_drawer/side_drawer.vue    |  2 +-
 src/components/status/status.js               | 15 ++++++--
 src/components/status/status.vue              |  2 +-
 src/components/user_card/user_card.js         |  8 +++-
 src/components/user_card/user_card.vue        | 15 +++++---
 src/components/user_panel/user_panel.vue      |  2 +-
 src/components/user_profile/user_profile.vue  |  2 +-
 src/components/user_settings/user_settings.js |  8 ++--
 src/modules/users.js                          | 37 +++++++------------
 src/services/api/api.service.js               |  4 +-
 .../entity_normalizer.service.js              | 25 ++++++++-----
 .../follow_manipulate/follow_manipulate.js    | 15 +++++---
 .../notifications_fetcher.service.js          |  1 -
 .../timeline_fetcher.service.js               |  2 +
 25 files changed, 112 insertions(+), 81 deletions(-)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index d70e1058..1522d4f0 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -304,6 +304,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
     getNodeInfo({ store })
   ])
 
+  // Start fetching things that don't need to block the UI
+  store.dispatch('fetchMutes')
+
   const router = new VueRouter({
     mode: 'history',
     routes: routes(store),
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
index 5d7ecf7e..0826c275 100644
--- a/src/components/account_actions/account_actions.js
+++ b/src/components/account_actions/account_actions.js
@@ -3,7 +3,7 @@ import Popover from '../popover/popover.vue'
 
 const AccountActions = {
   props: [
-    'user'
+    'user', 'relationship'
   ],
   data () {
     return { }
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index 483783cf..744b77d5 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -9,16 +9,16 @@
         class="account-tools-popover"
       >
         <div class="dropdown-menu">
-          <template v-if="user.following">
+          <template v-if="relationship.following">
             <button
-              v-if="user.showing_reblogs"
+              v-if="relationship.showing_reblogs"
               class="btn btn-default dropdown-item"
               @click="hideRepeats"
             >
               {{ $t('user_card.hide_repeats') }}
             </button>
             <button
-              v-if="!user.showing_reblogs"
+              v-if="!relationship.showing_reblogs"
               class="btn btn-default dropdown-item"
               @click="showRepeats"
             >
@@ -30,7 +30,7 @@
             />
           </template>
           <button
-            v-if="user.statusnet_blocking"
+            v-if="relationship.blocking"
             class="btn btn-default btn-block dropdown-item"
             @click="unblockUser"
           >
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
index 8a02174e..b5a11e2b 100644
--- a/src/components/basic_user_card/basic_user_card.vue
+++ b/src/components/basic_user_card/basic_user_card.vue
@@ -12,7 +12,7 @@
       class="basic-user-card-expanded-content"
     >
       <UserCard
-        :user="user"
+        :userId="user.id"
         :rounded="true"
         :bordered="true"
       />
diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js
index c459ff1b..659c75d8 100644
--- a/src/components/block_card/block_card.js
+++ b/src/components/block_card/block_card.js
@@ -11,8 +11,11 @@ const BlockCard = {
     user () {
       return this.$store.getters.findUser(this.userId)
     },
+    relationship () {
+      return this.$store.state.users.relationships[this.userId] || {}
+    },
     blocked () {
-      return this.user.statusnet_blocking
+      return this.relationship.blocking
     }
   },
   components: {
diff --git a/src/components/follow_button/follow_button.js b/src/components/follow_button/follow_button.js
index 12da2645..af7b1fef 100644
--- a/src/components/follow_button/follow_button.js
+++ b/src/components/follow_button/follow_button.js
@@ -1,6 +1,6 @@
 import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
 export default {
-  props: ['user', 'labelFollowing', 'buttonClass'],
+  props: ['relationship', 'labelFollowing', 'buttonClass'],
   data () {
     return {
       inProgress: false
@@ -8,12 +8,12 @@ export default {
   },
   computed: {
     isPressed () {
-      return this.inProgress || this.user.following
+      return this.inProgress || this.relationship.following
     },
     title () {
-      if (this.inProgress || this.user.following) {
+      if (this.inProgress || this.relationship.following) {
         return this.$t('user_card.follow_unfollow')
-      } else if (this.user.requested) {
+      } else if (this.relationship.requested) {
         return this.$t('user_card.follow_again')
       } else {
         return this.$t('user_card.follow')
@@ -22,9 +22,9 @@ export default {
     label () {
       if (this.inProgress) {
         return this.$t('user_card.follow_progress')
-      } else if (this.user.following) {
+      } else if (this.relationship.following) {
         return this.labelFollowing || this.$t('user_card.following')
-      } else if (this.user.requested) {
+      } else if (this.relationship.requested) {
         return this.$t('user_card.follow_sent')
       } else {
         return this.$t('user_card.follow')
@@ -33,7 +33,7 @@ export default {
   },
   methods: {
     onClick () {
-      this.user.following ? this.unfollow() : this.follow()
+      this.relationship.following ? this.unfollow() : this.follow()
     },
     follow () {
       this.inProgress = true
diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js
index aefd609e..620ae7fd 100644
--- a/src/components/follow_card/follow_card.js
+++ b/src/components/follow_card/follow_card.js
@@ -18,6 +18,9 @@ const FollowCard = {
     },
     loggedIn () {
       return this.$store.state.users.currentUser
+    },
+    relationship () {
+      return this.$store.state.users.relationships[this.user.id]
     }
   }
 }
diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue
index 81e6e6dc..d789a325 100644
--- a/src/components/follow_card/follow_card.vue
+++ b/src/components/follow_card/follow_card.vue
@@ -2,14 +2,14 @@
   <basic-user-card :user="user">
     <div class="follow-card-content-container">
       <span
-        v-if="!noFollowsYou && user.follows_you"
+        v-if="!noFollowsYou && relationship.followed_by"
         class="faint"
       >
         {{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}
       </span>
       <template v-if="!loggedIn">
         <div
-          v-if="!user.following"
+          v-if="!relationship.following"
           class="follow-card-follow-button"
         >
           <RemoteFollow :user="user" />
@@ -18,6 +18,7 @@
       <template v-else>
         <FollowButton
           :user="user"
+          :relationship="relationship"
           class="follow-card-follow-button"
           :label-following="$t('user_card.follow_unfollow')"
         />
diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js
index 65c9cfb5..be528d37 100644
--- a/src/components/mute_card/mute_card.js
+++ b/src/components/mute_card/mute_card.js
@@ -11,8 +11,11 @@ const MuteCard = {
     user () {
       return this.$store.getters.findUser(this.userId)
     },
+    relationship () {
+      return this.$store.state.users.relationships[this.userId]
+    },
     muted () {
-      return this.user.muted
+      return this.relationship.muting
     }
   },
   components: {
@@ -21,13 +24,13 @@ const MuteCard = {
   methods: {
     unmuteUser () {
       this.progress = true
-      this.$store.dispatch('unmuteUser', this.user.id).then(() => {
+      this.$store.dispatch('unmuteUser', this.userId).then(() => {
         this.progress = false
       })
     },
     muteUser () {
       this.progress = true
-      this.$store.dispatch('muteUser', this.user.id).then(() => {
+      this.$store.dispatch('muteUser', this.userId).then(() => {
         this.progress = false
       })
     }
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index e7bd769e..09554f54 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -56,7 +56,7 @@ const Notification = {
       return this.generateUserProfileLink(this.targetUser)
     },
     needMute () {
-      return this.user.muted
+      return (this.$store.state.users.relationships[this.user.id] || {}).muting
     }
   }
 }
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 411c0271..24d9fe90 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -40,7 +40,7 @@
       <div class="notification-right">
         <UserCard
           v-if="userExpanded"
-          :user="getUser(notification)"
+          :user-id="getUser(notification).id"
           :rounded="true"
           :bordered="true"
         />
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index df1d22a4..a9dbbeec 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -19,7 +19,7 @@
       >
         <UserCard
           v-if="currentUser"
-          :user="currentUser"
+          :userId="currentUser.id"
           :hide-bio="true"
         />
         <div
diff --git a/src/components/status/status.js b/src/components/status/status.js
index fc5956ec..a73e3ae2 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -118,7 +118,13 @@ const Status = {
 
       return hits
     },
-    muted () { return !this.unmuted && ((!(this.inProfile && this.status.user.id === this.profileUserId) && this.status.user.muted) || (!this.inConversation && this.status.thread_muted) || this.muteWordHits.length > 0) },
+    muted () {
+      const relationship = this.$store.state.users.relationships[this.status.user.id] || {}
+      return !this.unmuted && (
+        (!(this.inProfile && this.status.user.id === this.profileUserId) && relationship.muting) ||
+        (!this.inConversation && this.status.thread_muted) ||
+        this.muteWordHits.length > 0)
+    },
     hideFilteredStatuses () {
       return this.mergedConfig.hideFilteredStatuses
     },
@@ -178,8 +184,11 @@ const Status = {
         if (this.status.user.id === this.status.attentions[i].id) {
           continue
         }
-        const taggedUser = this.$store.getters.findUser(this.status.attentions[i].id)
-        if (checkFollowing && taggedUser && taggedUser.following) {
+        // There's zero guarantee of this working. If we happen to have that user and their
+        // relationship in store then it will work, but there's kinda little chance of having
+        // them for people you're not following.
+        const relationship = this.$store.state.users.relationships[this.status.attentions[i].id]
+        if (checkFollowing && relationship && relationship.following) {
           return false
         }
         if (this.status.attentions[i].id === this.currentUser.id) {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index ca295640..a04b501a 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -94,7 +94,7 @@
         <div class="status-body">
           <UserCard
             v-if="userExpanded"
-            :user="status.user"
+            :userId="status.user.id"
             :rounded="true"
             :bordered="true"
             class="status-usercard"
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 1cdbd3fa..fb3cfebc 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -9,7 +9,7 @@ import { mapGetters } from 'vuex'
 
 export default {
   props: [
-    'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar'
+    'userId', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar'
   ],
   data () {
     return {
@@ -21,6 +21,12 @@ export default {
     this.$store.dispatch('fetchUserRelationship', this.user.id)
   },
   computed: {
+    user () {
+      return this.$store.getters.findUser(this.userId)
+    },
+    relationship () {
+      return this.$store.state.users.relationships[this.userId] || {}
+    },
     classes () {
       return [{
         'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 4ee040e8..25fdd70e 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -8,7 +8,9 @@
       :style="style"
       class="background-image"
     />
-    <div class="panel-heading">
+    <div
+      class="panel-heading"
+    >
       <div class="user-info">
         <div class="container">
           <a
@@ -69,6 +71,7 @@
               <AccountActions
                 v-if="isOtherUser && loggedIn"
                 :user="user"
+                :relationship="relationship"
               />
             </div>
             <div class="bottom-line">
@@ -92,7 +95,7 @@
         </div>
         <div class="user-meta">
           <div
-            v-if="user.follows_you && loggedIn && isOtherUser"
+            v-if="relationship.followed_by && loggedIn && isOtherUser"
             class="following"
           >
             {{ $t('user_card.follows_you') }}
@@ -139,10 +142,10 @@
           class="user-interactions"
         >
           <div class="btn-group">
-            <FollowButton :user="user" />
-            <template v-if="user.following">
+            <FollowButton :relationship="relationship" />
+            <template v-if="relationship.following">
               <ProgressButton
-                v-if="!user.subscribed"
+                v-if="!relationship.subscribing"
                 class="btn btn-default"
                 :click="subscribeUser"
                 :title="$t('user_card.subscribe')"
@@ -161,7 +164,7 @@
           </div>
           <div>
             <button
-              v-if="user.muted"
+              v-if="relationship.muting"
               class="btn btn-default btn-block toggled"
               @click="unmuteUser"
             >
diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue
index e9f08015..ea4e2e9f 100644
--- a/src/components/user_panel/user_panel.vue
+++ b/src/components/user_panel/user_panel.vue
@@ -6,7 +6,7 @@
       class="panel panel-default signed-in"
     >
       <UserCard
-        :user="user"
+        :userId="user.id"
         :hide-bio="true"
         rounded="top"
       />
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 14082e83..7855c839 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -5,7 +5,7 @@
       class="user-profile panel panel-default"
     >
       <UserCard
-        :user="user"
+        :userId="user.id"
         :switcher="true"
         :selected="timeline.viewing"
         :allow-zooming-avatar="true"
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index eca6f9b1..adfab8fa 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -351,14 +351,14 @@ const UserSettings = {
     },
     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
+        const relationship = this.$store.state.users.relationships[userId] || {}
+        return relationship.blocking || userId === 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
+        const relationship = this.$store.state.users.relationships[userId] || {}
+        return relationship.muting || userId === this.$store.state.users.currentUser.id
       })
     },
     queryUserIds (query) {
diff --git a/src/modules/users.js b/src/modules/users.js
index df133be0..7e73ab18 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -146,26 +146,19 @@ export const mutations = {
     }
   },
   addNewUsers (state, users) {
-    each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
+    each(users, (user) => {
+      // console.log(user)
+      if (user.relationship) {
+        set(state.relationships, user.relationship.id, user.relationship)
+      }
+      mergeOrAdd(state.users, state.usersObject, user)
+    })
   },
   updateUserRelationship (state, relationships) {
     relationships.forEach((relationship) => {
-      const user = state.usersObject[relationship.id]
-      if (user) {
-        user.follows_you = relationship.followed_by
-        user.following = relationship.following
-        user.muted = relationship.muting
-        user.statusnet_blocking = relationship.blocking
-        user.subscribed = relationship.subscribing
-        user.showing_reblogs = relationship.showing_reblogs
-      }
+      set(state.relationships, relationship.id, relationship)
     })
   },
-  updateBlocks (state, blockedUsers) {
-    // Reset statusnet_blocking of all fetched users
-    each(state.users, (user) => { user.statusnet_blocking = false })
-    each(blockedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user))
-  },
   saveBlockIds (state, blockIds) {
     state.currentUser.blockIds = blockIds
   },
@@ -174,11 +167,6 @@ export const mutations = {
       state.currentUser.blockIds.push(blockId)
     }
   },
-  updateMutes (state, mutedUsers) {
-    // Reset muted of all fetched users
-    each(state.users, (user) => { user.muted = false })
-    each(mutedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user))
-  },
   saveMuteIds (state, muteIds) {
     state.currentUser.muteIds = muteIds
   },
@@ -254,7 +242,8 @@ export const defaultState = {
   users: [],
   usersObject: {},
   signUpPending: false,
-  signUpErrors: []
+  signUpErrors: [],
+  relationships: {}
 }
 
 const users = {
@@ -279,7 +268,7 @@ const users = {
       return store.rootState.api.backendInteractor.fetchBlocks()
         .then((blocks) => {
           store.commit('saveBlockIds', map(blocks, 'id'))
-          store.commit('updateBlocks', blocks)
+          store.commit('addNewUsers', blocks)
           return blocks
         })
     },
@@ -298,8 +287,8 @@ const users = {
     fetchMutes (store) {
       return store.rootState.api.backendInteractor.fetchMutes()
         .then((mutes) => {
-          store.commit('updateMutes', mutes)
           store.commit('saveMuteIds', map(mutes, 'id'))
+          store.commit('addNewUsers', mutes)
           return mutes
         })
     },
@@ -416,7 +405,7 @@ const users = {
     },
     addNewNotifications (store, { notifications }) {
       const users = map(notifications, 'from_profile')
-      const targetUsers = map(notifications, 'target')
+      const targetUsers = map(notifications, 'target').filter(_ => _)
       const notificationIds = notifications.map(_ => _.id)
       store.commit('addNewUsers', users)
       store.commit('addNewUsers', targetUsers)
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 7db1d094..ee6bf151 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -496,7 +496,8 @@ const fetchTimeline = ({
   userId = false,
   tag = false,
   withMuted = false,
-  withMove = false
+  withMove = false,
+  withRelationships = false
 }) => {
   const timelineUrls = {
     public: MASTODON_PUBLIC_TIMELINE,
@@ -542,6 +543,7 @@ const fetchTimeline = ({
 
   params.push(['count', 20])
   params.push(['with_muted', withMuted])
+  params.push(['with_relationships', withRelationships])
 
   const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
   url += `?${queryString}`
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 84169a7b..97f9f2ae 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -73,7 +73,7 @@ export const parseUser = (data) => {
       output.background_image = data.pleroma.background_image
       output.token = data.pleroma.chat_token
 
-      if (relationship) {
+      if (relationship && !relationship) {
         output.follows_you = relationship.followed_by
         output.requested = relationship.requested
         output.following = relationship.following
@@ -82,6 +82,9 @@ export const parseUser = (data) => {
         output.showing_reblogs = relationship.showing_reblogs
         output.subscribed = relationship.subscribing
       }
+      if (relationship) {
+        output.relationship = relationship
+      }
 
       output.allow_following_move = data.pleroma.allow_following_move
 
@@ -137,16 +140,10 @@ export const parseUser = (data) => {
 
     output.statusnet_profile_url = data.statusnet_profile_url
 
-    output.statusnet_blocking = data.statusnet_blocking
-
     output.is_local = data.is_local
     output.role = data.role
     output.show_role = data.show_role
 
-    output.follows_you = data.follows_you
-
-    output.muted = data.muted
-
     if (data.rights) {
       output.rights = {
         moderator: data.rights.delete_others_notice,
@@ -160,10 +157,16 @@ export const parseUser = (data) => {
     output.hide_follows_count = data.hide_follows_count
     output.hide_followers_count = data.hide_followers_count
     output.background_image = data.background_image
-    // on mastoapi this info is contained in a "relationship"
-    output.following = data.following
     // Websocket token
     output.token = data.token
+
+    // Convert relationsip data to expected format
+    output.relationship = {
+      muting: data.muted,
+      blocking: data.statusnet_blocking,
+      followed_by: data.follows_you,
+      following: data.following
+    }
   }
 
   output.created_at = new Date(data.created_at)
@@ -317,6 +320,9 @@ export const parseStatus = (data) => {
     ? String(output.in_reply_to_user_id)
     : null
 
+  if (data.account.pleroma.relationship) {
+    data.account.pleroma.relationship = undefined
+  }
   output.user = parseUser(masto ? data.account : data.user)
 
   output.attentions = ((masto ? data.mentions : data.attentions) || []).map(parseUser)
@@ -342,7 +348,6 @@ export const parseNotification = (data) => {
   }
   const masto = !data.hasOwnProperty('ntype')
   const output = {}
-
   if (masto) {
     output.type = mastoDict[data.type] || data.type
     output.seen = data.pleroma.is_seen
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index 29b38a0f..c5d86afa 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -1,15 +1,18 @@
-const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => {
+const fetchRelationship = (attempt, user, store) => new Promise((resolve, reject) => {
   setTimeout(() => {
-    store.state.api.backendInteractor.fetchUser({ id: user.id })
-      .then((user) => store.commit('addNewUsers', [user]))
-      .then(() => resolve([user.following, user.requested, user.locked, attempt]))
+    store.state.api.backendInteractor.fetchUserRelationship({ id: user.id })
+      .then((relationship) => {
+        store.commit('updateUserRelationship', [relationship])
+        return relationship
+      })
+      .then((relationship) => resolve([relationship.following, relationship.requested, user.locked, attempt]))
       .catch((e) => reject(e))
   }, 500)
 }).then(([following, sent, locked, attempt]) => {
   if (!following && !(locked && sent) && attempt <= 3) {
     // If we BE reports that we still not following that user - retry,
     // increment attempts by one
-    fetchUser(++attempt, user, store)
+    fetchRelationship(++attempt, user, store)
   }
 })
 
@@ -31,7 +34,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
       // don't know that yet.
       // Recursive Promise, it will call itself up to 3 times.
 
-      return fetchUser(1, user, store)
+      return fetchRelationship(1, user, store)
         .then(() => {
           resolve()
         })
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index 864e32f8..eb6d84c4 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -48,7 +48,6 @@ const fetchNotifications = ({ store, args, older }) => {
       update({ store, notifications, older })
       return notifications
     }, () => store.dispatch('setNotificationsError', { value: true }))
-    .catch(() => store.dispatch('setNotificationsError', { value: true }))
 }
 
 const startFetching = ({ credentials, store }) => {
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index c6b28ad5..96fafff9 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -31,6 +31,7 @@ const fetchAndUpdate = ({
   const { getters } = store
   const timelineData = rootState.statuses.timelines[camelCase(timeline)]
   const hideMutedPosts = getters.mergedConfig.hideMutedPosts
+  const replyVisibility = getters.mergedConfig.replyVisibility
 
   if (older) {
     args['until'] = until || timelineData.minId
@@ -41,6 +42,7 @@ const fetchAndUpdate = ({
   args['userId'] = userId
   args['tag'] = tag
   args['withMuted'] = !hideMutedPosts
+  args['withRelationships'] = replyVisibility === 'following'
 
   const numStatusesBeforeFetch = timelineData.statuses.length
 

From 4b7007bc7dc4af8601454d8a84cf9f68e47545dd Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Tue, 21 Apr 2020 23:56:48 +0300
Subject: [PATCH 283/483] fix mistakes

---
 src/components/user_card/user_card.vue                      | 4 +---
 src/modules/users.js                                        | 1 -
 src/services/entity_normalizer/entity_normalizer.service.js | 3 ---
 3 files changed, 1 insertion(+), 7 deletions(-)

diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 25fdd70e..b40435cd 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -8,9 +8,7 @@
       :style="style"
       class="background-image"
     />
-    <div
-      class="panel-heading"
-    >
+    <div class="panel-heading">
       <div class="user-info">
         <div class="container">
           <a
diff --git a/src/modules/users.js b/src/modules/users.js
index 7e73ab18..591d4634 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -147,7 +147,6 @@ export const mutations = {
   },
   addNewUsers (state, users) {
     each(users, (user) => {
-      // console.log(user)
       if (user.relationship) {
         set(state.relationships, user.relationship.id, user.relationship)
       }
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 97f9f2ae..66a7a8b7 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -320,9 +320,6 @@ export const parseStatus = (data) => {
     ? String(output.in_reply_to_user_id)
     : null
 
-  if (data.account.pleroma.relationship) {
-    data.account.pleroma.relationship = undefined
-  }
   output.user = parseUser(masto ? data.account : data.user)
 
   output.attentions = ((masto ? data.mentions : data.attentions) || []).map(parseUser)

From 576ad9750be4a76df7f7cc4d7062aa4546d7f61f Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 22 Apr 2020 00:07:01 +0300
Subject: [PATCH 284/483] make tests not fail

---
 test/unit/specs/components/user_profile.spec.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js
index 1b6a47d7..0a3f2d27 100644
--- a/test/unit/specs/components/user_profile.spec.js
+++ b/test/unit/specs/components/user_profile.spec.js
@@ -96,7 +96,8 @@ const externalProfileStore = new Vuex.Store({
         credentials: ''
       },
       usersObject: { 100: extUser },
-      users: [extUser]
+      users: [extUser],
+      relationships: {}
     }
   }
 })
@@ -164,7 +165,8 @@ const localProfileStore = new Vuex.Store({
         credentials: ''
       },
       usersObject: { 100: localUser, 'testuser': localUser },
-      users: [localUser]
+      users: [localUser],
+      relationships: {}
     }
   }
 })

From aa561473226d22d71c37756265df6949795a6cab Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 22 Apr 2020 15:06:10 +0300
Subject: [PATCH 285/483] fix follow

---
 src/components/follow_button/follow_button.js |  6 +++---
 .../follow_manipulate/follow_manipulate.js    | 20 +++++++++----------
 2 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/components/follow_button/follow_button.js b/src/components/follow_button/follow_button.js
index af7b1fef..95e7cb6b 100644
--- a/src/components/follow_button/follow_button.js
+++ b/src/components/follow_button/follow_button.js
@@ -37,16 +37,16 @@ export default {
     },
     follow () {
       this.inProgress = true
-      requestFollow(this.user, this.$store).then(() => {
+      requestFollow(this.relationship.id, this.$store).then(() => {
         this.inProgress = false
       })
     },
     unfollow () {
       const store = this.$store
       this.inProgress = true
-      requestUnfollow(this.user, store).then(() => {
+      requestUnfollow(this.relationship.id, store).then(() => {
         this.inProgress = false
-        store.commit('removeStatus', { timeline: 'friends', userId: this.user.id })
+        store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
       })
     }
   }
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index c5d86afa..08f4c4d6 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -1,27 +1,27 @@
-const fetchRelationship = (attempt, user, store) => new Promise((resolve, reject) => {
+const fetchRelationship = (attempt, userId, store) => new Promise((resolve, reject) => {
   setTimeout(() => {
-    store.state.api.backendInteractor.fetchUserRelationship({ id: user.id })
+    store.state.api.backendInteractor.fetchUserRelationship({ id: userId })
       .then((relationship) => {
         store.commit('updateUserRelationship', [relationship])
         return relationship
       })
-      .then((relationship) => resolve([relationship.following, relationship.requested, user.locked, attempt]))
+      .then((relationship) => resolve([relationship.following, relationship.requested, relationship.locked, attempt]))
       .catch((e) => reject(e))
   }, 500)
 }).then(([following, sent, locked, attempt]) => {
   if (!following && !(locked && sent) && attempt <= 3) {
     // If we BE reports that we still not following that user - retry,
     // increment attempts by one
-    fetchRelationship(++attempt, user, store)
+    fetchRelationship(++attempt, userId, store)
   }
 })
 
-export const requestFollow = (user, store) => new Promise((resolve, reject) => {
-  store.state.api.backendInteractor.followUser({ id: user.id })
+export const requestFollow = (userId, store) => new Promise((resolve, reject) => {
+  store.state.api.backendInteractor.followUser({ id: userId })
     .then((updated) => {
       store.commit('updateUserRelationship', [updated])
 
-      if (updated.following || (user.locked && user.requested)) {
+      if (updated.following || (updated.locked && updated.requested)) {
         // If we get result immediately or the account is locked, just stop.
         resolve()
         return
@@ -34,15 +34,15 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
       // don't know that yet.
       // Recursive Promise, it will call itself up to 3 times.
 
-      return fetchRelationship(1, user, store)
+      return fetchRelationship(1, updated, store)
         .then(() => {
           resolve()
         })
     })
 })
 
-export const requestUnfollow = (user, store) => new Promise((resolve, reject) => {
-  store.state.api.backendInteractor.unfollowUser({ id: user.id })
+export const requestUnfollow = (userId, store) => new Promise((resolve, reject) => {
+  store.state.api.backendInteractor.unfollowUser({ id: userId })
     .then((updated) => {
       store.commit('updateUserRelationship', [updated])
       resolve({

From cda298c8223851d50edcd2761391d4ddb8932ed1 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 23 Apr 2020 11:17:52 +0300
Subject: [PATCH 286/483] remove unused mutation and test for it

---
 src/modules/users.js                               |  4 ----
 .../entity_normalizer/entity_normalizer.service.js |  9 ---------
 test/unit/specs/modules/users.spec.js              | 14 --------------
 3 files changed, 27 deletions(-)

diff --git a/src/modules/users.js b/src/modules/users.js
index 591d4634..6b19fc97 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -83,10 +83,6 @@ const unmuteDomain = (store, domain) => {
 }
 
 export const mutations = {
-  setMuted (state, { user: { id }, muted }) {
-    const user = state.usersObject[id]
-    set(user, 'muted', muted)
-  },
   tagUser (state, { user: { id }, tag }) {
     const user = state.usersObject[id]
     const tags = user.tags || []
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 66a7a8b7..7237fdfc 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -73,15 +73,6 @@ export const parseUser = (data) => {
       output.background_image = data.pleroma.background_image
       output.token = data.pleroma.chat_token
 
-      if (relationship && !relationship) {
-        output.follows_you = relationship.followed_by
-        output.requested = relationship.requested
-        output.following = relationship.following
-        output.statusnet_blocking = relationship.blocking
-        output.muted = relationship.muting
-        output.showing_reblogs = relationship.showing_reblogs
-        output.subscribed = relationship.subscribing
-      }
       if (relationship) {
         output.relationship = relationship
       }
diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js
index eeb7afef..670acfc8 100644
--- a/test/unit/specs/modules/users.spec.js
+++ b/test/unit/specs/modules/users.spec.js
@@ -18,20 +18,6 @@ describe('The users module', () => {
       expect(state.users).to.eql([user])
       expect(state.users[0].name).to.eql('Dude')
     })
-
-    it('sets a mute bit on users', () => {
-      const state = cloneDeep(defaultState)
-      const user = { id: '1', name: 'Guy' }
-
-      mutations.addNewUsers(state, [user])
-      mutations.setMuted(state, { user, muted: true })
-
-      expect(user.muted).to.eql(true)
-
-      mutations.setMuted(state, { user, muted: false })
-
-      expect(user.muted).to.eql(false)
-    })
   })
 
   describe('findUser', () => {

From ce0a1e7ad134089f62835fc0f9f015fed673766b Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 23 Apr 2020 14:08:33 +0300
Subject: [PATCH 287/483] add back missing catch

---
 .../notifications_fetcher/notifications_fetcher.service.js       | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index eb6d84c4..864e32f8 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -48,6 +48,7 @@ const fetchNotifications = ({ store, args, older }) => {
       update({ store, notifications, older })
       return notifications
     }, () => store.dispatch('setNotificationsError', { value: true }))
+    .catch(() => store.dispatch('setNotificationsError', { value: true }))
 }
 
 const startFetching = ({ credentials, store }) => {

From 99d8e16e4d19acbd811a9e19e087eeed04f7b638 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 23 Apr 2020 14:11:48 +0300
Subject: [PATCH 288/483] remove with_relationships as it doesn't help

---
 src/services/api/api.service.js                           | 4 +---
 src/services/timeline_fetcher/timeline_fetcher.service.js | 2 --
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index ee6bf151..7db1d094 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -496,8 +496,7 @@ const fetchTimeline = ({
   userId = false,
   tag = false,
   withMuted = false,
-  withMove = false,
-  withRelationships = false
+  withMove = false
 }) => {
   const timelineUrls = {
     public: MASTODON_PUBLIC_TIMELINE,
@@ -543,7 +542,6 @@ const fetchTimeline = ({
 
   params.push(['count', 20])
   params.push(['with_muted', withMuted])
-  params.push(['with_relationships', withRelationships])
 
   const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
   url += `?${queryString}`
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index 96fafff9..c6b28ad5 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -31,7 +31,6 @@ const fetchAndUpdate = ({
   const { getters } = store
   const timelineData = rootState.statuses.timelines[camelCase(timeline)]
   const hideMutedPosts = getters.mergedConfig.hideMutedPosts
-  const replyVisibility = getters.mergedConfig.replyVisibility
 
   if (older) {
     args['until'] = until || timelineData.minId
@@ -42,7 +41,6 @@ const fetchAndUpdate = ({
   args['userId'] = userId
   args['tag'] = tag
   args['withMuted'] = !hideMutedPosts
-  args['withRelationships'] = replyVisibility === 'following'
 
   const numStatusesBeforeFetch = timelineData.statuses.length
 

From ca00e93b6092c855b0109e1e6dab93c29a28716a Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 23 Apr 2020 14:27:27 +0300
Subject: [PATCH 289/483] minor fixes

---
 src/components/follow_card/follow_card.vue                  | 3 +--
 src/components/side_drawer/side_drawer.vue                  | 2 +-
 src/components/status/status.vue                            | 2 +-
 src/services/entity_normalizer/entity_normalizer.service.js | 1 +
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue
index d789a325..76a70730 100644
--- a/src/components/follow_card/follow_card.vue
+++ b/src/components/follow_card/follow_card.vue
@@ -17,10 +17,9 @@
       </template>
       <template v-else>
         <FollowButton
-          :user="user"
           :relationship="relationship"
-          class="follow-card-follow-button"
           :label-following="$t('user_card.follow_unfollow')"
+          class="follow-card-follow-button"
         />
       </template>
     </div>
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index a9dbbeec..2958a386 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -19,7 +19,7 @@
       >
         <UserCard
           v-if="currentUser"
-          :userId="currentUser.id"
+          :user-id="currentUser.id"
           :hide-bio="true"
         />
         <div
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index a04b501a..dd7cd579 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -94,7 +94,7 @@
         <div class="status-body">
           <UserCard
             v-if="userExpanded"
-            :userId="status.user.id"
+            :user-id="status.user.id"
             :rounded="true"
             :bordered="true"
             class="status-usercard"
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 7237fdfc..724dcc4b 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -336,6 +336,7 @@ export const parseNotification = (data) => {
   }
   const masto = !data.hasOwnProperty('ntype')
   const output = {}
+
   if (masto) {
     output.type = mastoDict[data.type] || data.type
     output.seen = data.pleroma.is_seen

From c476193fd9e5bbb783ad4d3be80839caf180f598 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 23 Apr 2020 14:44:55 +0300
Subject: [PATCH 290/483] minor lint fixes

---
 src/components/user_panel/user_panel.vue     | 2 +-
 src/components/user_profile/user_profile.vue | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue
index ea4e2e9f..1db4f648 100644
--- a/src/components/user_panel/user_panel.vue
+++ b/src/components/user_panel/user_panel.vue
@@ -6,7 +6,7 @@
       class="panel panel-default signed-in"
     >
       <UserCard
-        :userId="user.id"
+        :user-id="user.id"
         :hide-bio="true"
         rounded="top"
       />
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 7855c839..1871d46c 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -5,7 +5,7 @@
       class="user-profile panel panel-default"
     >
       <UserCard
-        :userId="user.id"
+        :user-id="userId"
         :switcher="true"
         :selected="timeline.viewing"
         :allow-zooming-avatar="true"

From f6fce92cf7a463fbdf270d494404dca5aae06045 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 23 Apr 2020 14:48:15 +0300
Subject: [PATCH 291/483] last lint warning

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

diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
index b5a11e2b..9e410610 100644
--- a/src/components/basic_user_card/basic_user_card.vue
+++ b/src/components/basic_user_card/basic_user_card.vue
@@ -12,7 +12,7 @@
       class="basic-user-card-expanded-content"
     >
       <UserCard
-        :userId="user.id"
+        :user-id="user.id"
         :rounded="true"
         :bordered="true"
       />

From af9492977aaa10903d54add3187b5cf9d9a00d6c Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Fri, 24 Apr 2020 18:53:17 +0300
Subject: [PATCH 292/483] add back mute prediction, add getter for
 relationships

---
 src/components/block_card/block_card.js         |  2 +-
 src/components/follow_card/follow_card.js       |  2 +-
 src/components/mute_card/mute_card.js           |  2 +-
 src/components/notification/notification.js     |  2 +-
 src/components/status/status.js                 |  2 +-
 src/components/user_card/user_card.js           |  2 +-
 src/components/user_settings/user_settings.js   |  4 ++--
 src/modules/users.js                            | 12 ++++++++++++
 test/unit/specs/components/user_profile.spec.js |  1 +
 9 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js
index 659c75d8..0bf4e37b 100644
--- a/src/components/block_card/block_card.js
+++ b/src/components/block_card/block_card.js
@@ -12,7 +12,7 @@ const BlockCard = {
       return this.$store.getters.findUser(this.userId)
     },
     relationship () {
-      return this.$store.state.users.relationships[this.userId] || {}
+      return this.$store.getters.relationship(this.userId)
     },
     blocked () {
       return this.relationship.blocking
diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js
index 620ae7fd..6dcb6d47 100644
--- a/src/components/follow_card/follow_card.js
+++ b/src/components/follow_card/follow_card.js
@@ -20,7 +20,7 @@ const FollowCard = {
       return this.$store.state.users.currentUser
     },
     relationship () {
-      return this.$store.state.users.relationships[this.user.id]
+      return this.$store.getters.relationship(this.user.id)
     }
   }
 }
diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js
index be528d37..cbec0e9b 100644
--- a/src/components/mute_card/mute_card.js
+++ b/src/components/mute_card/mute_card.js
@@ -12,7 +12,7 @@ const MuteCard = {
       return this.$store.getters.findUser(this.userId)
     },
     relationship () {
-      return this.$store.state.users.relationships[this.userId]
+      return this.$store.getters.relationship(this.userId)
     },
     muted () {
       return this.relationship.muting
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 09554f54..ff1c2817 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -56,7 +56,7 @@ const Notification = {
       return this.generateUserProfileLink(this.targetUser)
     },
     needMute () {
-      return (this.$store.state.users.relationships[this.user.id] || {}).muting
+      return this.$store.getters.relationship(this.user.id).muting
     }
   }
 }
diff --git a/src/components/status/status.js b/src/components/status/status.js
index a73e3ae2..a36de028 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -119,7 +119,7 @@ const Status = {
       return hits
     },
     muted () {
-      const relationship = this.$store.state.users.relationships[this.status.user.id] || {}
+      const relationship = this.$store.getters.relationship(this.userId)
       return !this.unmuted && (
         (!(this.inProfile && this.status.user.id === this.profileUserId) && relationship.muting) ||
         (!this.inConversation && this.status.thread_muted) ||
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index fb3cfebc..8e6b9d7f 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -25,7 +25,7 @@ export default {
       return this.$store.getters.findUser(this.userId)
     },
     relationship () {
-      return this.$store.state.users.relationships[this.userId] || {}
+      return this.$store.getters.relationship(this.userId)
     },
     classes () {
       return [{
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index adfab8fa..5338c974 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -351,13 +351,13 @@ const UserSettings = {
     },
     filterUnblockedUsers (userIds) {
       return reject(userIds, (userId) => {
-        const relationship = this.$store.state.users.relationships[userId] || {}
+        const relationship = this.$store.getters.relationship(this.userId)
         return relationship.blocking || userId === this.$store.state.users.currentUser.id
       })
     },
     filterUnMutedUsers (userIds) {
       return reject(userIds, (userId) => {
-        const relationship = this.$store.state.users.relationships[userId] || {}
+        const relationship = this.$store.getters.relationship(this.userId)
         return relationship.muting || userId === this.$store.state.users.currentUser.id
       })
     },
diff --git a/src/modules/users.js b/src/modules/users.js
index 6b19fc97..fb04ebd3 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -48,6 +48,11 @@ const unblockUser = (store, id) => {
 }
 
 const muteUser = (store, id) => {
+  const predictedRelationship = store.state.relationships[id] || { id }
+  predictedRelationship.muting = true
+  store.commit('updateUserRelationship', [predictedRelationship])
+  store.commit('addMuteId', id)
+
   return store.rootState.api.backendInteractor.muteUser({ id })
     .then((relationship) => {
       store.commit('updateUserRelationship', [relationship])
@@ -56,6 +61,10 @@ const muteUser = (store, id) => {
 }
 
 const unmuteUser = (store, id) => {
+  const predictedRelationship = store.state.relationships[id] || { id }
+  predictedRelationship.muting = false
+  store.commit('updateUserRelationship', [predictedRelationship])
+
   return store.rootState.api.backendInteractor.unmuteUser({ id })
     .then((relationship) => store.commit('updateUserRelationship', [relationship]))
 }
@@ -227,6 +236,9 @@ export const getters = {
       return state.usersObject[query.toLowerCase()]
     }
     return result
+  },
+  relationship: state => id => {
+    return state.relationships[id] || { id, loading: true }
   }
 }
 
diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js
index 0a3f2d27..dcf066f9 100644
--- a/test/unit/specs/components/user_profile.spec.js
+++ b/test/unit/specs/components/user_profile.spec.js
@@ -19,6 +19,7 @@ const actions = {
 
 const testGetters = {
   findUser: state => getters.findUser(state.users),
+  relationship: state => getters.relationship(state.users),
   mergedConfig: state => ({
     colors: '',
     highlight: {},

From 8b1aa593a46869ac1ea26de8a1f31d9fa2f44e56 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Mon, 27 Apr 2020 10:06:17 +0300
Subject: [PATCH 293/483] fix status mutes

---
 src/components/status/status.js | 2 +-
 src/modules/users.js            | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/components/status/status.js b/src/components/status/status.js
index a36de028..890f4b91 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -119,7 +119,7 @@ const Status = {
       return hits
     },
     muted () {
-      const relationship = this.$store.getters.relationship(this.userId)
+      const relationship = this.$store.getters.relationship(this.status.user.id)
       return !this.unmuted && (
         (!(this.inProfile && this.status.user.id === this.profileUserId) && relationship.muting) ||
         (!this.inConversation && this.status.thread_muted) ||
diff --git a/src/modules/users.js b/src/modules/users.js
index fb04ebd3..1d1b415c 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -238,7 +238,8 @@ export const getters = {
     return result
   },
   relationship: state => id => {
-    return state.relationships[id] || { id, loading: true }
+    const rel = id && state.relationships[id]
+    return rel || { id, loading: true }
   }
 }
 

From dea7e2f6acd06839c89cd4a14a101ec162ca4e58 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Mon, 27 Apr 2020 08:09:31 +0000
Subject: [PATCH 294/483] Update CHANGELOG.md

---
 CHANGELOG.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 375e560f..f45561d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
+### Fixed
+- Show more/less works correctly with auto-collapsed subjects and long posts
+- RTL characters won't look messed up in notifications
+
+### Changed
+- Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach:
 
 ## [2.0.2] - 2020-04-08
 ### Fixed

From 7a25160ddc15a97a02366dff60ec12801348f229 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Mon, 27 Apr 2020 12:53:04 +0300
Subject: [PATCH 295/483] Separate status content from status like in direct
 conversations mr

---
 src/components/status/status.js               | 188 +-------------
 src/components/status/status.vue              | 230 +----------------
 .../status_content/status_content.js          | 210 +++++++++++++++
 .../status_content/status_content.vue         | 240 ++++++++++++++++++
 4 files changed, 460 insertions(+), 408 deletions(-)
 create mode 100644 src/components/status_content/status_content.js
 create mode 100644 src/components/status_content/status_content.vue

diff --git a/src/components/status/status.js b/src/components/status/status.js
index 61d66301..ab3de5fc 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -1,23 +1,17 @@
-import Attachment from '../attachment/attachment.vue'
 import FavoriteButton from '../favorite_button/favorite_button.vue'
 import ReactButton from '../react_button/react_button.vue'
 import RetweetButton from '../retweet_button/retweet_button.vue'
-import Poll from '../poll/poll.vue'
 import ExtraButtons from '../extra_buttons/extra_buttons.vue'
 import PostStatusForm from '../post_status_form/post_status_form.vue'
 import UserCard from '../user_card/user_card.vue'
 import UserAvatar from '../user_avatar/user_avatar.vue'
-import Gallery from '../gallery/gallery.vue'
-import LinkPreview from '../link-preview/link-preview.vue'
 import AvatarList from '../avatar_list/avatar_list.vue'
 import Timeago from '../timeago/timeago.vue'
+import StatusContent from '../status_content/status_content.vue'
 import StatusPopover from '../status_popover/status_popover.vue'
 import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
-import fileType from 'src/services/file_type/file_type.service'
-import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
 import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
-import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
 import { filter, unescape, uniqBy } from 'lodash'
 import { mapGetters, mapState } from 'vuex'
 
@@ -43,17 +37,10 @@ const Status = {
       replying: false,
       unmuted: false,
       userExpanded: false,
-      showingTall: this.inConversation && this.focused,
-      showingLongSubject: false,
-      error: null,
-      // not as computed because it sets the initial state which will be changed later
-      expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject
+      error: null
     }
   },
   computed: {
-    localCollapseSubjectDefault () {
-      return this.mergedConfig.collapseMessageWithSubject
-    },
     muteWords () {
       return this.mergedConfig.muteWords
     },
@@ -79,10 +66,6 @@ const Status = {
       const highlight = this.mergedConfig.highlight
       return highlightStyle(highlight[user.screen_name])
     },
-    hideAttachments () {
-      return (this.mergedConfig.hideAttachments && !this.inConversation) ||
-        (this.mergedConfig.hideAttachmentsInConv && this.inConversation)
-    },
     userProfileLink () {
       return this.generateUserProfileLink(this.status.user.id, this.status.user.screen_name)
     },
@@ -135,20 +118,6 @@ const Status = {
       // use conversation highlight only when in conversation
       return this.status.id === this.highlight
     },
-    // This is a bit hacky, but we want to approximate post height before rendering
-    // so we count newlines (masto uses <p> for paragraphs, GS uses <br> between them)
-    // as well as approximate line count by counting characters and approximating ~80
-    // per line.
-    //
-    // Using max-height + overflow: auto for status components resulted in false positives
-    // very often with japanese characters, and it was very annoying.
-    tallStatus () {
-      const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80
-      return lengthScore > 20
-    },
-    longSubject () {
-      return this.status.summary.length > 900
-    },
     isReply () {
       return !!(this.status.in_reply_to_status_id && this.status.in_reply_to_user_id)
     },
@@ -188,32 +157,6 @@ const Status = {
       }
       return this.status.attentions.length > 0
     },
-
-    // When a status has a subject and is also tall, we should only have one show more/less button. If the default is to collapse statuses with subjects, we just treat it like a status with a subject; otherwise, we just treat it like a tall status.
-    mightHideBecauseSubject () {
-      return this.status.summary && (!this.tallStatus || this.localCollapseSubjectDefault)
-    },
-    mightHideBecauseTall () {
-      return this.tallStatus && (!this.status.summary || !this.localCollapseSubjectDefault)
-    },
-    hideSubjectStatus () {
-      return this.mightHideBecauseSubject && !this.expandingSubject
-    },
-    hideTallStatus () {
-      return this.mightHideBecauseTall && !this.showingTall
-    },
-    showingMore () {
-      return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject)
-    },
-    nsfwClickthrough () {
-      if (!this.status.nsfw) {
-        return false
-      }
-      if (this.status.summary && this.localCollapseSubjectDefault) {
-        return false
-      }
-      return true
-    },
     replySubject () {
       if (!this.status.summary) return ''
       const decodedSummary = unescape(this.status.summary)
@@ -227,83 +170,6 @@ const Status = {
         return ''
       }
     },
-    attachmentSize () {
-      if ((this.mergedConfig.hideAttachments && !this.inConversation) ||
-        (this.mergedConfig.hideAttachmentsInConv && this.inConversation) ||
-        (this.status.attachments.length > this.maxThumbnails)) {
-        return 'hide'
-      } else if (this.compact) {
-        return 'small'
-      }
-      return 'normal'
-    },
-    galleryTypes () {
-      if (this.attachmentSize === 'hide') {
-        return []
-      }
-      return this.mergedConfig.playVideosInModal
-        ? ['image', 'video']
-        : ['image']
-    },
-    galleryAttachments () {
-      return this.status.attachments.filter(
-        file => fileType.fileMatchesSomeType(this.galleryTypes, file)
-      )
-    },
-    nonGalleryAttachments () {
-      return this.status.attachments.filter(
-        file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
-      )
-    },
-    hasImageAttachments () {
-      return this.status.attachments.some(
-        file => fileType.fileType(file.mimetype) === 'image'
-      )
-    },
-    hasVideoAttachments () {
-      return this.status.attachments.some(
-        file => fileType.fileType(file.mimetype) === 'video'
-      )
-    },
-    maxThumbnails () {
-      return this.mergedConfig.maxThumbnails
-    },
-    postBodyHtml () {
-      const html = this.status.statusnet_html
-
-      if (this.mergedConfig.greentext) {
-        try {
-          if (html.includes('&gt;')) {
-            // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works
-            return processHtml(html, (string) => {
-              if (string.includes('&gt;') &&
-                  string
-                    .replace(/<[^>]+?>/gi, '') // remove all tags
-                    .replace(/@\w+/gi, '') // remove mentions (even failed ones)
-                    .trim()
-                    .startsWith('&gt;')) {
-                return `<span class='greentext'>${string}</span>`
-              } else {
-                return string
-              }
-            })
-          } else {
-            return html
-          }
-        } catch (e) {
-          console.err('Failed to process status html', e)
-          return html
-        }
-      } else {
-        return html
-      }
-    },
-    contentHtml () {
-      if (!this.status.summary_html) {
-        return this.postBodyHtml
-      }
-      return this.status.summary_html + '<br />' + this.postBodyHtml
-    },
     combinedFavsAndRepeatsUsers () {
       // Use the status from the global status repository since favs and repeats are saved in it
       const combinedUsers = [].concat(
@@ -312,9 +178,6 @@ const Status = {
       )
       return uniqBy(combinedUsers, 'id')
     },
-    ownStatus () {
-      return this.status.user.id === this.currentUser.id
-    },
     tags () {
       return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')
     },
@@ -328,21 +191,18 @@ const Status = {
     })
   },
   components: {
-    Attachment,
     FavoriteButton,
     ReactButton,
     RetweetButton,
     ExtraButtons,
     PostStatusForm,
-    Poll,
     UserCard,
     UserAvatar,
-    Gallery,
-    LinkPreview,
     AvatarList,
     Timeago,
     StatusPopover,
-    EmojiReactions
+    EmojiReactions,
+    StatusContent
   },
   methods: {
     visibilityIcon (visibility) {
@@ -363,32 +223,6 @@ const Status = {
     clearError () {
       this.error = undefined
     },
-    linkClicked (event) {
-      const target = event.target.closest('.status-content a')
-      if (target) {
-        if (target.className.match(/mention/)) {
-          const href = target.href
-          const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href))
-          if (attn) {
-            event.stopPropagation()
-            event.preventDefault()
-            const link = this.generateUserProfileLink(attn.id, attn.screen_name)
-            this.$router.push(link)
-            return
-          }
-        }
-        if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) {
-          // Extract tag name from link url
-          const tag = extractTagFromUrl(target.href)
-          if (tag) {
-            const link = this.generateTagLink(tag)
-            this.$router.push(link)
-            return
-          }
-        }
-        window.open(target.href, '_blank')
-      }
-    },
     toggleReplying () {
       this.replying = !this.replying
     },
@@ -406,22 +240,8 @@ const Status = {
     toggleUserExpanded () {
       this.userExpanded = !this.userExpanded
     },
-    toggleShowMore () {
-      if (this.mightHideBecauseTall) {
-        this.showingTall = !this.showingTall
-      } else if (this.mightHideBecauseSubject) {
-        this.expandingSubject = !this.expandingSubject
-      }
-    },
     generateUserProfileLink (id, name) {
       return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames)
-    },
-    generateTagLink (tag) {
-      return `/tag/${tag}`
-    },
-    setMedia () {
-      const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
-      return () => this.$store.dispatch('setMedia', attachments)
     }
   },
   watch: {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index ca295640..1ee4f27c 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -226,118 +226,12 @@
             </div>
           </div>
 
-          <div
-            v-if="longSubject"
-            class="status-content-wrapper"
-            :class="{ 'tall-status': !showingLongSubject }"
-          >
-            <a
-              v-if="!showingLongSubject"
-              class="tall-status-hider"
-              :class="{ 'tall-status-hider_focused': isFocused }"
-              href="#"
-              @click.prevent="showingLongSubject=true"
-            >{{ $t("general.show_more") }}</a>
-            <div
-              class="status-content media-body"
-              @click.prevent="linkClicked"
-              v-html="contentHtml"
-            />
-            <a
-              v-if="showingLongSubject"
-              href="#"
-              class="status-unhider"
-              @click.prevent="showingLongSubject=false"
-            >{{ $t("general.show_less") }}</a>
-          </div>
-          <div
-            v-else
-            :class="{'tall-status': hideTallStatus}"
-            class="status-content-wrapper"
-          >
-            <a
-              v-if="hideTallStatus"
-              class="tall-status-hider"
-              :class="{ 'tall-status-hider_focused': isFocused }"
-              href="#"
-              @click.prevent="toggleShowMore"
-            >{{ $t("general.show_more") }}</a>
-            <div
-              v-if="!hideSubjectStatus"
-              class="status-content media-body"
-              @click.prevent="linkClicked"
-              v-html="contentHtml"
-            />
-            <div
-              v-else
-              class="status-content media-body"
-              @click.prevent="linkClicked"
-              v-html="status.summary_html"
-            />
-            <a
-              v-if="hideSubjectStatus"
-              href="#"
-              class="cw-status-hider"
-              @click.prevent="toggleShowMore"
-            >
-              {{ $t("general.show_more") }}
-              <span
-                v-if="hasImageAttachments"
-                class="icon-picture"
-              />
-              <span
-                v-if="hasVideoAttachments"
-                class="icon-video"
-              />
-              <span
-                v-if="status.card"
-                class="icon-link"
-              />
-            </a>
-            <a
-              v-if="showingMore"
-              href="#"
-              class="status-unhider"
-              @click.prevent="toggleShowMore"
-            >{{ $t("general.show_less") }}</a>
-          </div>
-
-          <div v-if="status.poll && status.poll.options">
-            <poll :base-poll="status.poll" />
-          </div>
-
-          <div
-            v-if="status.attachments && (!hideSubjectStatus || showingLongSubject)"
-            class="attachments media-body"
-          >
-            <attachment
-              v-for="attachment in nonGalleryAttachments"
-              :key="attachment.id"
-              class="non-gallery"
-              :size="attachmentSize"
-              :nsfw="nsfwClickthrough"
-              :attachment="attachment"
-              :allow-play="true"
-              :set-media="setMedia()"
-            />
-            <gallery
-              v-if="galleryAttachments.length > 0"
-              :nsfw="nsfwClickthrough"
-              :attachments="galleryAttachments"
-              :set-media="setMedia()"
-            />
-          </div>
-
-          <div
-            v-if="status.card && !hideSubjectStatus && !noHeading"
-            class="link-preview media-body"
-          >
-            <link-preview
-              :card="status.card"
-              :size="attachmentSize"
-              :nsfw="nsfwClickthrough"
-            />
-          </div>
+          <StatusContent
+            :status="status"
+            :no-heading="noHeading"
+            :highlight="highlight"
+            :focused="isFocused"
+          />
 
           <transition name="fade">
             <div
@@ -630,105 +524,6 @@ $status-margin: 0.75em;
     }
   }
 
-  .tall-status {
-    position: relative;
-    height: 220px;
-    overflow-x: hidden;
-    overflow-y: hidden;
-    z-index: 1;
-    .status-content {
-      height: 100%;
-      mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
-            linear-gradient(to top, white, white);
-      /* Autoprefixed seem to ignore this one, and also syntax is different */
-      -webkit-mask-composite: xor;
-      mask-composite: exclude;
-    }
-  }
-
-  .tall-status-hider {
-    display: inline-block;
-    word-break: break-all;
-    position: absolute;
-    height: 70px;
-    margin-top: 150px;
-    width: 100%;
-    text-align: center;
-    line-height: 110px;
-    z-index: 2;
-  }
-
-  .status-unhider, .cw-status-hider {
-    width: 100%;
-    text-align: center;
-    display: inline-block;
-    word-break: break-all;
-  }
-
-  .status-content {
-    font-family: var(--postFont, sans-serif);
-    line-height: 1.4em;
-    white-space: pre-wrap;
-
-    a {
-      color: $fallback--link;
-      color: var(--postLink, $fallback--link);
-    }
-
-    img, video {
-      max-width: 100%;
-      max-height: 400px;
-      vertical-align: middle;
-      object-fit: contain;
-
-      &.emoji {
-        width: 32px;
-        height: 32px;
-      }
-    }
-
-    blockquote {
-      margin: 0.2em 0 0.2em 2em;
-      font-style: italic;
-    }
-
-    pre {
-      overflow: auto;
-    }
-
-    code, samp, kbd, var, pre {
-      font-family: var(--postCodeFont, monospace);
-    }
-
-    p {
-      margin: 0 0 1em 0;
-    }
-
-    p:last-child {
-      margin: 0 0 0 0;
-    }
-
-    h1 {
-      font-size: 1.1em;
-      line-height: 1.2em;
-      margin: 1.4em 0;
-    }
-
-    h2 {
-      font-size: 1.1em;
-      margin: 1.0em 0;
-    }
-
-    h3 {
-      font-size: 1em;
-      margin: 1.2em 0;
-    }
-
-    h4 {
-      margin: 1.1em 0;
-    }
-  }
-
   .retweet-info {
     padding: 0.4em $status-margin;
     margin: 0;
@@ -790,11 +585,6 @@ $status-margin: 0.75em;
   }
 }
 
-.greentext {
-  color: $fallback--cGreen;
-  color: var(--cGreen, $fallback--cGreen);
-}
-
 .status-conversation {
   border-left-style: solid;
 }
@@ -866,14 +656,6 @@ a.unmute {
   flex: 1;
 }
 
-.timeline :not(.panel-disabled) > {
-  .status-el:last-child {
-    border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
-    border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
-    border-bottom: none;
-  }
-}
-
 .favs-repeated-users {
   margin-top: $status-margin;
 
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
new file mode 100644
index 00000000..ccc01b6f
--- /dev/null
+++ b/src/components/status_content/status_content.js
@@ -0,0 +1,210 @@
+import Attachment from '../attachment/attachment.vue'
+import Poll from '../poll/poll.vue'
+import Gallery from '../gallery/gallery.vue'
+import LinkPreview from '../link-preview/link-preview.vue'
+import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
+import fileType from 'src/services/file_type/file_type.service'
+import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
+import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
+import { mapGetters, mapState } from 'vuex'
+
+const StatusContent = {
+  name: 'StatusContent',
+  props: [
+    'status',
+    'focused',
+    'noHeading',
+    'fullContent'
+  ],
+  data () {
+    return {
+      showingTall: this.inConversation && this.focused,
+      showingLongSubject: false,
+      // not as computed because it sets the initial state which will be changed later
+      expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject
+    }
+  },
+  computed: {
+    localCollapseSubjectDefault () {
+      return this.mergedConfig.collapseMessageWithSubject
+    },
+    hideAttachments () {
+      return (this.mergedConfig.hideAttachments && !this.inConversation) ||
+        (this.mergedConfig.hideAttachmentsInConv && this.inConversation)
+    },
+    // This is a bit hacky, but we want to approximate post height before rendering
+    // so we count newlines (masto uses <p> for paragraphs, GS uses <br> between them)
+    // as well as approximate line count by counting characters and approximating ~80
+    // per line.
+    //
+    // Using max-height + overflow: auto for status components resulted in false positives
+    // very often with japanese characters, and it was very annoying.
+    tallStatus () {
+      const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80
+      return lengthScore > 20
+    },
+    longSubject () {
+      return this.status.summary.length > 900
+    },
+    // When a status has a subject and is also tall, we should only have one show more/less button. If the default is to collapse statuses with subjects, we just treat it like a status with a subject; otherwise, we just treat it like a tall status.
+    mightHideBecauseSubject () {
+      return this.status.summary && (!this.tallStatus || this.localCollapseSubjectDefault)
+    },
+    mightHideBecauseTall () {
+      return this.tallStatus && (!this.status.summary || !this.localCollapseSubjectDefault)
+    },
+    hideSubjectStatus () {
+      return this.mightHideBecauseSubject && !this.expandingSubject
+    },
+    hideTallStatus () {
+      return this.mightHideBecauseTall && !this.showingTall
+    },
+    showingMore () {
+      return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject)
+    },
+    nsfwClickthrough () {
+      if (!this.status.nsfw) {
+        return false
+      }
+      if (this.status.summary && this.localCollapseSubjectDefault) {
+        return false
+      }
+      return true
+    },
+    attachmentSize () {
+      if ((this.mergedConfig.hideAttachments && !this.inConversation) ||
+        (this.mergedConfig.hideAttachmentsInConv && this.inConversation) ||
+        (this.status.attachments.length > this.maxThumbnails)) {
+        return 'hide'
+      } else if (this.compact) {
+        return 'small'
+      }
+      return 'normal'
+    },
+    galleryTypes () {
+      if (this.attachmentSize === 'hide') {
+        return []
+      }
+      return this.mergedConfig.playVideosInModal
+        ? ['image', 'video']
+        : ['image']
+    },
+    galleryAttachments () {
+      return this.status.attachments.filter(
+        file => fileType.fileMatchesSomeType(this.galleryTypes, file)
+      )
+    },
+    nonGalleryAttachments () {
+      return this.status.attachments.filter(
+        file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
+      )
+    },
+    hasImageAttachments () {
+      return this.status.attachments.some(
+        file => fileType.fileType(file.mimetype) === 'image'
+      )
+    },
+    hasVideoAttachments () {
+      return this.status.attachments.some(
+        file => fileType.fileType(file.mimetype) === 'video'
+      )
+    },
+    maxThumbnails () {
+      return this.mergedConfig.maxThumbnails
+    },
+    postBodyHtml () {
+      const html = this.status.statusnet_html
+
+      if (this.mergedConfig.greentext) {
+        try {
+          if (html.includes('&gt;')) {
+            // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works
+            return processHtml(html, (string) => {
+              if (string.includes('&gt;') &&
+                  string
+                    .replace(/<[^>]+?>/gi, '') // remove all tags
+                    .replace(/@\w+/gi, '') // remove mentions (even failed ones)
+                    .trim()
+                    .startsWith('&gt;')) {
+                return `<span class='greentext'>${string}</span>`
+              } else {
+                return string
+              }
+            })
+          } else {
+            return html
+          }
+        } catch (e) {
+          console.err('Failed to process status html', e)
+          return html
+        }
+      } else {
+        return html
+      }
+    },
+    contentHtml () {
+      if (!this.status.summary_html) {
+        return this.postBodyHtml
+      }
+      return this.status.summary_html + '<br />' + this.postBodyHtml
+    },
+    ...mapGetters(['mergedConfig']),
+    ...mapState({
+      betterShadow: state => state.interface.browserSupport.cssFilter,
+      currentUser: state => state.users.currentUser
+    })
+  },
+  components: {
+    Attachment,
+    Poll,
+    Gallery,
+    LinkPreview
+  },
+  methods: {
+    linkClicked (event) {
+      const target = event.target.closest('.status-content a')
+      if (target) {
+        if (target.className.match(/mention/)) {
+          const href = target.href
+          const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href))
+          if (attn) {
+            event.stopPropagation()
+            event.preventDefault()
+            const link = this.generateUserProfileLink(attn.id, attn.screen_name)
+            this.$router.push(link)
+            return
+          }
+        }
+        if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) {
+          // Extract tag name from link url
+          const tag = extractTagFromUrl(target.href)
+          if (tag) {
+            const link = this.generateTagLink(tag)
+            this.$router.push(link)
+            return
+          }
+        }
+        window.open(target.href, '_blank')
+      }
+    },
+    toggleShowMore () {
+      if (this.mightHideBecauseTall) {
+        this.showingTall = !this.showingTall
+      } else if (this.mightHideBecauseSubject) {
+        this.expandingSubject = !this.expandingSubject
+      }
+    },
+    generateUserProfileLink (id, name) {
+      return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames)
+    },
+    generateTagLink (tag) {
+      return `/tag/${tag}`
+    },
+    setMedia () {
+      const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
+      return () => this.$store.dispatch('setMedia', attachments)
+    }
+  }
+}
+
+export default StatusContent
diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue
new file mode 100644
index 00000000..8c2e8749
--- /dev/null
+++ b/src/components/status_content/status_content.vue
@@ -0,0 +1,240 @@
+<template>
+  <!-- eslint-disable vue/no-v-html -->
+  <div class="status-body">
+    <slot name="header" />
+    <div
+      v-if="longSubject"
+      class="status-content-wrapper"
+      :class="{ 'tall-status': !showingLongSubject }"
+    >
+      <a
+        v-if="!showingLongSubject"
+        class="tall-status-hider"
+        :class="{ 'tall-status-hider_focused': focused }"
+        href="#"
+        @click.prevent="showingLongSubject=true"
+      >
+        {{ $t("general.show_more") }}
+        <span
+          v-if="hasImageAttachments"
+          class="icon-picture"
+        />
+        <span
+          v-if="hasVideoAttachments"
+          class="icon-video"
+        />
+        <span
+          v-if="status.card"
+          class="icon-link"
+        />
+      </a>
+      <div
+        class="status-content media-body"
+        @click.prevent="linkClicked"
+        v-html="contentHtml"
+      />
+      <a
+        v-if="showingLongSubject"
+        href="#"
+        class="status-unhider"
+        @click.prevent="showingLongSubject=false"
+      >{{ $t("general.show_less") }}</a>
+    </div>
+    <div
+      v-else
+      :class="{'tall-status': hideTallStatus}"
+      class="status-content-wrapper"
+    >
+      <a
+        v-if="hideTallStatus"
+        class="tall-status-hider"
+        :class="{ 'tall-status-hider_focused': focused }"
+        href="#"
+        @click.prevent="toggleShowMore"
+      >{{ $t("general.show_more") }}</a>
+      <div
+        v-if="!hideSubjectStatus"
+        class="status-content media-body"
+        @click.prevent="linkClicked"
+        v-html="contentHtml"
+      />
+      <div
+        v-else
+        class="status-content media-body"
+        @click.prevent="linkClicked"
+        v-html="status.summary_html"
+      />
+      <a
+        v-if="hideSubjectStatus"
+        href="#"
+        class="cw-status-hider"
+        @click.prevent="toggleShowMore"
+      >{{ $t("general.show_more") }}</a>
+      <a
+        v-if="showingMore"
+        href="#"
+        class="status-unhider"
+        @click.prevent="toggleShowMore"
+      >{{ $t("general.show_less") }}</a>
+    </div>
+
+    <div v-if="status.poll && status.poll.options">
+      <poll :base-poll="status.poll" />
+    </div>
+
+    <div
+      v-if="status.attachments.length !== 0 && (!hideSubjectStatus || showingLongSubject)"
+      class="attachments media-body"
+    >
+      <attachment
+        v-for="attachment in nonGalleryAttachments"
+        :key="attachment.id"
+        class="non-gallery"
+        :size="attachmentSize"
+        :nsfw="nsfwClickthrough"
+        :attachment="attachment"
+        :allow-play="true"
+        :set-media="setMedia()"
+      />
+      <gallery
+        v-if="galleryAttachments.length > 0"
+        :nsfw="nsfwClickthrough"
+        :attachments="galleryAttachments"
+        :set-media="setMedia()"
+      />
+    </div>
+
+    <div
+      v-if="status.card && !hideSubjectStatus && !noHeading"
+      class="link-preview media-body"
+    >
+      <link-preview
+        :card="status.card"
+        :size="attachmentSize"
+        :nsfw="nsfwClickthrough"
+      />
+    </div>
+    <slot name="footer" />
+  </div>
+  <!-- eslint-enable vue/no-v-html -->
+</template>
+
+<script src="./status_content.js" ></script>
+<style lang="scss">
+@import '../../_variables.scss';
+
+$status-margin: 0.75em;
+
+.status-body {
+  flex: 1;
+  min-width: 0;
+
+  .tall-status {
+    position: relative;
+    height: 220px;
+    overflow-x: hidden;
+    overflow-y: hidden;
+    z-index: 1;
+    .status-content {
+      height: 100%;
+      mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
+            linear-gradient(to top, white, white);
+      /* Autoprefixed seem to ignore this one, and also syntax is different */
+      -webkit-mask-composite: xor;
+      mask-composite: exclude;
+    }
+  }
+
+  .tall-status-hider {
+    display: inline-block;
+    word-break: break-all;
+    position: absolute;
+    height: 70px;
+    margin-top: 150px;
+    width: 100%;
+    text-align: center;
+    line-height: 110px;
+    z-index: 2;
+  }
+
+  .status-unhider, .cw-status-hider {
+    width: 100%;
+    text-align: center;
+    display: inline-block;
+    word-break: break-all;
+  }
+
+  .status-content {
+    font-family: var(--postFont, sans-serif);
+    line-height: 1.4em;
+    white-space: pre-wrap;
+
+    img, video {
+      max-width: 100%;
+      max-height: 400px;
+      vertical-align: middle;
+      object-fit: contain;
+
+      &.emoji {
+        width: 32px;
+        height: 32px;
+      }
+    }
+
+    blockquote {
+      margin: 0.2em 0 0.2em 2em;
+      font-style: italic;
+    }
+
+    pre {
+      overflow: auto;
+    }
+
+    code, samp, kbd, var, pre {
+      font-family: var(--postCodeFont, monospace);
+    }
+
+    p {
+      margin: 0 0 1em 0;
+    }
+
+    p:last-child {
+      margin: 0 0 0 0;
+    }
+
+    h1 {
+      font-size: 1.1em;
+      line-height: 1.2em;
+      margin: 1.4em 0;
+    }
+
+    h2 {
+      font-size: 1.1em;
+      margin: 1.0em 0;
+    }
+
+    h3 {
+      font-size: 1em;
+      margin: 1.2em 0;
+    }
+
+    h4 {
+      margin: 1.1em 0;
+    }
+  }
+}
+
+.greentext {
+  color: $fallback--cGreen;
+  color: var(--cGreen, $fallback--cGreen);
+}
+
+.timeline :not(.panel-disabled) > {
+  .status-el:last-child {
+    border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
+    border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
+    border-bottom: none;
+  }
+}
+
+</style>

From 01b07f01e9340935faf51e5a3c8034cc90423989 Mon Sep 17 00:00:00 2001
From: eugenijm <eugenijm@protonmail.com>
Date: Sat, 25 Apr 2020 07:04:39 +0300
Subject: [PATCH 296/483] Add support for follow request notifications

---
 CHANGELOG.md                                  |  3 ++
 src/components/notification/notification.js   | 19 +++++++
 src/components/notification/notification.vue  | 50 +++++++++++++------
 .../notifications/notifications.scss          | 15 ++++++
 src/i18n/en.json                              |  5 +-
 src/modules/config.js                         |  3 +-
 src/modules/statuses.js                       | 22 +++++++-
 src/services/api/api.service.js               | 11 ++++
 .../entity_normalizer.service.js              |  5 +-
 .../notification_utils/notification_utils.js  |  7 ++-
 static/fontello.json                          | 14 +++++-
 11 files changed, 131 insertions(+), 23 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f45561d0..685fe629 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Changed
 - Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach:
 
+### Add
+- Follow request notification support
+
 ## [2.0.2] - 2020-04-08
 ### Fixed
 - Favorite/Repeat avatars not showing up on private instances/non-public posts
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index e7bd769e..6deee7d5 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -2,6 +2,7 @@ import Status from '../status/status.vue'
 import UserAvatar from '../user_avatar/user_avatar.vue'
 import UserCard from '../user_card/user_card.vue'
 import Timeago from '../timeago/timeago.vue'
+import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
 import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 
@@ -32,6 +33,21 @@ const Notification = {
     },
     toggleMute () {
       this.unmuted = !this.unmuted
+    },
+    approveUser () {
+      this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
+      this.$store.dispatch('removeFollowRequest', this.user)
+      this.$store.dispatch('updateNotification', {
+        id: this.notification.id,
+        updater: notification => {
+          notification.type = 'follow'
+        }
+      })
+    },
+    denyUser () {
+      this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
+      this.$store.dispatch('removeFollowRequest', this.user)
+      this.$store.dispatch('dismissNotification', { id: this.notification.id })
     }
   },
   computed: {
@@ -57,6 +73,9 @@ const Notification = {
     },
     needMute () {
       return this.user.muted
+    },
+    isStatusNotification () {
+      return isStatusNotification(this.notification.type)
     }
   }
 }
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 51875747..02802776 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -74,6 +74,10 @@
               <i class="fa icon-user-plus lit" />
               <small>{{ $t('notifications.followed_you') }}</small>
             </span>
+            <span v-if="notification.type === 'follow_request'">
+              <i class="fa icon-user lit" />
+              <small>{{ $t('notifications.follow_request') }}</small>
+            </span>
             <span v-if="notification.type === 'move'">
               <i class="fa icon-arrow-curved lit" />
               <small>{{ $t('notifications.migrated_to') }}</small>
@@ -87,18 +91,7 @@
             </span>
           </div>
           <div
-            v-if="notification.type === 'follow' || notification.type === 'move'"
-            class="timeago"
-          >
-            <span class="faint">
-              <Timeago
-                :time="notification.created_at"
-                :auto-update="240"
-              />
-            </span>
-          </div>
-          <div
-            v-else
+            v-if="isStatusNotification"
             class="timeago"
           >
             <router-link
@@ -112,6 +105,17 @@
               />
             </router-link>
           </div>
+          <div
+            v-else
+            class="timeago"
+          >
+            <span class="faint">
+              <Timeago
+                :time="notification.created_at"
+                :auto-update="240"
+              />
+            </span>
+          </div>
           <a
             v-if="needMute"
             href="#"
@@ -119,12 +123,30 @@
           ><i class="button-icon icon-eye-off" /></a>
         </span>
         <div
-          v-if="notification.type === 'follow'"
+          v-if="notification.type === 'follow' || notification.type === 'follow_request'"
           class="follow-text"
         >
-          <router-link :to="userProfileLink">
+          <router-link
+            :to="userProfileLink"
+            class="follow-name"
+          >
             @{{ notification.from_profile.screen_name }}
           </router-link>
+          <div
+            v-if="notification.type === 'follow_request'"
+            style="white-space: nowrap;"
+          >
+            <i
+              class="icon-ok button-icon add-reaction-button"
+              :title="$t('tool_tip.accept_follow_request')"
+              @click="approveUser()"
+            />
+            <i
+              class="icon-cancel button-icon add-reaction-button"
+              :title="$t('tool_tip.accept_follow_request')"
+              @click="denyUser()"
+            />
+          </div>
         </div>
         <div
           v-else-if="notification.type === 'move'"
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index a8f4430f..80dad28b 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -82,6 +82,16 @@
   .follow-text, .move-text {
     padding: 0.5em 0;
     overflow-wrap: break-word;
+    display: flex;
+    justify-content: space-between;
+
+    .follow-name {
+      display: block;
+      max-width: 100%;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
   }
 
   .status-el {
@@ -143,6 +153,11 @@
       color: var(--cGreen, $fallback--cGreen);
     }
 
+    .icon-user.lit {
+      color: $fallback--cBlue;
+      color: var(--cBlue, $fallback--cBlue);
+    }
+
     .icon-user-plus.lit {
       color: $fallback--cBlue;
       color: var(--cBlue, $fallback--cBlue);
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 54d0608e..37d9591c 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -124,6 +124,7 @@
     "broken_favorite": "Unknown status, searching for it...",
     "favorited_you": "favorited your status",
     "followed_you": "followed you",
+    "follow_request": "wants to follow you",
     "load_older": "Load older notifications",
     "notifications": "Notifications",
     "read": "Read!",
@@ -697,7 +698,9 @@
     "reply": "Reply",
     "favorite": "Favorite",
     "add_reaction": "Add Reaction",
-    "user_settings": "User Settings"
+    "user_settings": "User Settings",
+    "accept_follow_request": "Accept follow request",
+    "reject_follow_request": "Reject follow request"
   },
   "upload":{
     "error": {
diff --git a/src/modules/config.js b/src/modules/config.js
index 7997521d..8f4638f5 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -34,7 +34,8 @@ export const defaultState = {
     likes: true,
     repeats: true,
     moves: true,
-    emojiReactions: false
+    emojiReactions: false,
+    followRequest: true
   },
   webPushNotifications: false,
   muteWords: [],
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index f1b7dcbd..239f41eb 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -13,6 +13,7 @@ import {
   omitBy
 } from 'lodash'
 import { set } from 'vue'
+import { isStatusNotification } from '../services/notification_utils/notification_utils.js'
 import apiService from '../services/api/api.service.js'
 // import parse from '../services/status_parser/status_parser.js'
 
@@ -321,7 +322,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
 
 const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => {
   each(notifications, (notification) => {
-    if (notification.type !== 'follow' && notification.type !== 'move') {
+    if (isStatusNotification(notification.type)) {
       notification.action = addStatusToGlobalStorage(state, notification.action).item
       notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
     }
@@ -361,13 +362,16 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
           case 'move':
             i18nString = 'migrated_to'
             break
+          case 'follow_request':
+            i18nString = 'follow_request'
+            break
         }
 
         if (notification.type === 'pleroma:emoji_reaction') {
           notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji])
         } else if (i18nString) {
           notifObj.body = rootGetters.i18n.t('notifications.' + i18nString)
-        } else {
+        } else if (isStatusNotification(notification.type)) {
           notifObj.body = notification.status.text
         }
 
@@ -521,6 +525,13 @@ export const mutations = {
       notification.seen = true
     })
   },
+  dismissNotification (state, { id }) {
+    state.notifications.data = state.notifications.data.filter(n => n.id !== id)
+  },
+  updateNotification (state, { id, updater }) {
+    const notification = find(state.notifications.data, n => n.id === id)
+    notification && updater(notification)
+  },
   queueFlush (state, { timeline, id }) {
     state.timelines[timeline].flushMarker = id
   },
@@ -680,6 +691,13 @@ const statuses = {
         credentials: rootState.users.currentUser.credentials
       })
     },
+    dismissNotification ({ rootState, commit }, { id }) {
+      rootState.api.backendInteractor.dismissNotification({ id })
+        .then(() => commit('dismissNotification', { id }))
+    },
+    updateNotification ({ rootState, commit }, { id, updater }) {
+      commit('updateNotification', { id, updater })
+    },
     fetchFavsAndRepeats ({ rootState, commit }, id) {
       Promise.all([
         rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index ad2b2ad5..cda61ee2 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -29,6 +29,7 @@ const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
 const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
 const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
 const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
+const MASTODON_DISMISS_NOTIFICATION_URL = id => `/api/v1/notifications/${id}/dismiss`
 const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
 const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite`
 const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog`
@@ -1010,6 +1011,15 @@ const unmuteDomain = ({ domain, credentials }) => {
   })
 }
 
+const dismissNotification = ({ credentials, id }) => {
+  return promisedRequest({
+    url: MASTODON_DISMISS_NOTIFICATION_URL(id),
+    method: 'POST',
+    payload: { id },
+    credentials
+  })
+}
+
 export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
   return Object.entries({
     ...(credentials
@@ -1165,6 +1175,7 @@ const apiService = {
   denyUser,
   suggestions,
   markNotificationsAsSeen,
+  dismissNotification,
   vote,
   fetchPoll,
   fetchFavoritedByUsers,
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 84169a7b..6cacd0b8 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -1,4 +1,5 @@
 import escape from 'escape-html'
+import { isStatusNotification } from '../notification_utils/notification_utils.js'
 
 const qvitterStatusType = (status) => {
   if (status.is_post_verb) {
@@ -346,9 +347,7 @@ export const parseNotification = (data) => {
   if (masto) {
     output.type = mastoDict[data.type] || data.type
     output.seen = data.pleroma.is_seen
-    output.status = output.type === 'follow' || output.type === 'move'
-      ? null
-      : parseStatus(data.status)
+    output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null
     output.action = output.status // TODO: Refactor, this is unneeded
     output.target = output.type !== 'move'
       ? null
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index b17bd7bf..eb479227 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -1,4 +1,4 @@
-import { filter, sortBy } from 'lodash'
+import { filter, sortBy, includes } from 'lodash'
 
 export const notificationsFromStore = store => store.state.statuses.notifications.data
 
@@ -7,10 +7,15 @@ export const visibleTypes = store => ([
   store.state.config.notificationVisibility.mentions && 'mention',
   store.state.config.notificationVisibility.repeats && 'repeat',
   store.state.config.notificationVisibility.follows && 'follow',
+  store.state.config.notificationVisibility.followRequest && 'follow_request',
   store.state.config.notificationVisibility.moves && 'move',
   store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction'
 ].filter(_ => _))
 
+const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction']
+
+export const isStatusNotification = (type) => includes(statusNotifications, type)
+
 const sortById = (a, b) => {
   const seqA = Number(a.id)
   const seqB = Number(b.id)
diff --git a/static/fontello.json b/static/fontello.json
index 5a7086a2..5963b68b 100755
--- a/static/fontello.json
+++ b/static/fontello.json
@@ -345,6 +345,18 @@
       "css": "link",
       "code": 59427,
       "src": "fontawesome"
+    },
+    {
+      "uid": "8b80d36d4ef43889db10bc1f0dc9a862",
+      "css": "user",
+      "code": 59428,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "12f4ece88e46abd864e40b35e05b11cd",
+      "css": "ok",
+      "code": 59431,
+      "src": "fontawesome"
     }
   ]
-}
+}
\ No newline at end of file

From 02c8a9e3143f2b12f44d24f307e2718dec22987b Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Fri, 1 May 2020 17:26:07 +0300
Subject: [PATCH 297/483] remove with_move param

---
 src/services/api/api.service.js                             | 6 +-----
 .../notifications_fetcher/notifications_fetcher.service.js  | 3 ---
 2 files changed, 1 insertion(+), 8 deletions(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index ad2b2ad5..3c6b8f4e 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -495,8 +495,7 @@ const fetchTimeline = ({
   until = false,
   userId = false,
   tag = false,
-  withMuted = false,
-  withMove = false
+  withMuted = false
 }) => {
   const timelineUrls = {
     public: MASTODON_PUBLIC_TIMELINE,
@@ -536,9 +535,6 @@ const fetchTimeline = ({
   if (timeline === 'public' || timeline === 'publicAndExternal') {
     params.push(['only_media', false])
   }
-  if (timeline === 'notifications') {
-    params.push(['with_move', withMove])
-  }
 
   params.push(['limit', 20])
   params.push(['with_muted', withMuted])
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index 864e32f8..64499a1b 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -11,12 +11,9 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
   const rootState = store.rootState || store.state
   const timelineData = rootState.statuses.notifications
   const hideMutedPosts = getters.mergedConfig.hideMutedPosts
-  const allowFollowingMove = rootState.users.currentUser.allow_following_move
 
   args['withMuted'] = !hideMutedPosts
 
-  args['withMove'] = !allowFollowingMove
-
   args['timeline'] = 'notifications'
   if (older) {
     if (timelineData.minId !== Number.POSITIVE_INFINITY) {

From af3e69743e3192898f185fbc867defa1d155a4d4 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 1 May 2020 19:28:26 +0000
Subject: [PATCH 298/483] Update CHANGELOG.md

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f45561d0..86d981da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
-## [Unreleased]
+## [2.0.3] - 2020-05-02
 ### Fixed
 - Show more/less works correctly with auto-collapsed subjects and long posts
 - RTL characters won't look messed up in notifications

From 406fdd8edec210d14589e0ff684a166250236779 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Sat, 2 May 2020 10:19:47 +0300
Subject: [PATCH 299/483] follow request bugfixes, wrong text, notifs not being
 marked as read, approving from follow request view

---
 .../follow_request_card.js                    | 19 +++++++++++++++++++
 src/components/notification/notification.js   |  1 +
 src/components/notification/notification.vue  |  6 +++---
 .../notifications/notifications.scss          | 19 +++++++++++++++++++
 4 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
index a8931787..2a9d3db5 100644
--- a/src/components/follow_request_card/follow_request_card.js
+++ b/src/components/follow_request_card/follow_request_card.js
@@ -1,4 +1,5 @@
 import BasicUserCard from '../basic_user_card/basic_user_card.vue'
+import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
 
 const FollowRequestCard = {
   props: ['user'],
@@ -6,13 +7,31 @@ const FollowRequestCard = {
     BasicUserCard
   },
   methods: {
+    findFollowRequestNotificationId () {
+      const notif = notificationsFromStore(this.$store).find(
+        (notif) => notif.from_profile.id === this.user.id && notif.type === 'follow_request'
+      )
+      return notif && notif.id
+    },
     approveUser () {
       this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
       this.$store.dispatch('removeFollowRequest', this.user)
+
+      const notifId = this.findFollowRequestNotificationId()
+      this.$store.dispatch('updateNotification', {
+        id: notifId,
+        updater: notification => {
+          notification.type = 'follow'
+          notification.seen = true
+        }
+      })
     },
     denyUser () {
       this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
       this.$store.dispatch('removeFollowRequest', this.user)
+
+      const notifId = this.findFollowRequestNotificationId()
+      this.$store.dispatch('dismissNotification', { id: notifId })
     }
   }
 }
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 6deee7d5..8c20ff09 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -41,6 +41,7 @@ const Notification = {
         id: this.notification.id,
         updater: notification => {
           notification.type = 'follow'
+          notification.seen = true
         }
       })
     },
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 02802776..f6da07dd 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -137,13 +137,13 @@
             style="white-space: nowrap;"
           >
             <i
-              class="icon-ok button-icon add-reaction-button"
+              class="icon-ok button-icon follow-request-accept"
               :title="$t('tool_tip.accept_follow_request')"
               @click="approveUser()"
             />
             <i
-              class="icon-cancel button-icon add-reaction-button"
-              :title="$t('tool_tip.accept_follow_request')"
+              class="icon-cancel button-icon follow-request-reject"
+              :title="$t('tool_tip.reject_follow_request')"
               @click="denyUser()"
             />
           </div>
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 80dad28b..9efcfcf8 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -79,6 +79,25 @@
     }
   }
 
+  .follow-request-accept {
+    cursor: pointer;
+
+    &:hover {
+      color: $fallback--text;
+      color: var(--text, $fallback--text);
+    }
+  }
+
+  .follow-request-reject {
+    cursor: pointer;
+
+    &:hover {
+      color: $fallback--cRed;
+      color: var(--cRed, $fallback--cRed);
+    }
+  }
+
+
   .follow-text, .move-text {
     padding: 0.5em 0;
     overflow-wrap: break-word;

From 75519223f9a715aacb99d3780ee681089a479292 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Sat, 2 May 2020 10:52:57 +0300
Subject: [PATCH 300/483] mark single notifs as seen properly on server

---
 .../follow_request_card/follow_request_card.js       |  2 +-
 src/components/notification/notification.js          |  2 +-
 src/modules/statuses.js                              | 12 ++++++++++++
 src/services/api/api.service.js                      | 12 ++++++++----
 4 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
index 2a9d3db5..33e2699e 100644
--- a/src/components/follow_request_card/follow_request_card.js
+++ b/src/components/follow_request_card/follow_request_card.js
@@ -18,11 +18,11 @@ const FollowRequestCard = {
       this.$store.dispatch('removeFollowRequest', this.user)
 
       const notifId = this.findFollowRequestNotificationId()
+      this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId })
       this.$store.dispatch('updateNotification', {
         id: notifId,
         updater: notification => {
           notification.type = 'follow'
-          notification.seen = true
         }
       })
     },
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 8c20ff09..abe3bebe 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -37,11 +37,11 @@ const Notification = {
     approveUser () {
       this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
       this.$store.dispatch('removeFollowRequest', this.user)
+      this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
       this.$store.dispatch('updateNotification', {
         id: this.notification.id,
         updater: notification => {
           notification.type = 'follow'
-          notification.seen = true
         }
       })
     },
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 239f41eb..2a8b9581 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -525,6 +525,10 @@ export const mutations = {
       notification.seen = true
     })
   },
+  markSingleNotificationAsSeen (state, { id }) {
+    const notification = find(state.notifications.data, n => n.id === id)
+    if (notification) notification.seen = true
+  },
   dismissNotification (state, { id }) {
     state.notifications.data = state.notifications.data.filter(n => n.id !== id)
   },
@@ -691,6 +695,14 @@ const statuses = {
         credentials: rootState.users.currentUser.credentials
       })
     },
+    markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
+      commit('markSingleNotificationAsSeen', { id })
+      apiService.markNotificationsAsSeen({
+        single: true,
+        id,
+        credentials: rootState.users.currentUser.credentials
+      })
+    },
     dismissNotification ({ rootState, commit }, { id }) {
       rootState.api.backendInteractor.dismissNotification({ id })
         .then(() => commit('dismissNotification', { id }))
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 3a58c38d..72c8874f 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -4,7 +4,6 @@ import 'whatwg-fetch'
 import { RegistrationError, StatusCodeError } from '../errors/errors'
 
 /* eslint-env browser */
-const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
 const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
 const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
 const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
@@ -17,6 +16,7 @@ const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
 const ADMIN_USERS_URL = '/api/pleroma/admin/users'
 const SUGGESTIONS_URL = '/api/v1/suggestions'
 const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
+const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
 
 const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
 const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
@@ -841,12 +841,16 @@ const suggestions = ({ credentials }) => {
   }).then((data) => data.json())
 }
 
-const markNotificationsAsSeen = ({ id, credentials }) => {
+const markNotificationsAsSeen = ({ id, credentials, single = false }) => {
   const body = new FormData()
 
-  body.append('latest_id', id)
+  if (single) {
+    body.append('id', id)
+  } else {
+    body.append('max_id', id)
+  }
 
-  return fetch(QVITTER_USER_NOTIFICATIONS_READ_URL, {
+  return fetch(NOTIFICATION_READ_URL, {
     body,
     headers: authHeaders(credentials),
     method: 'POST'

From 92ccaa97bb0a2c15b96e2fbcf03823ba902dc516 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Sat, 2 May 2020 11:51:39 +0300
Subject: [PATCH 301/483] don't dismiss a rejected follow request on server

---
 .../follow_request_card/follow_request_card.js           | 9 +++++----
 src/components/notification/notification.js              | 6 ++++--
 src/modules/statuses.js                                  | 5 ++++-
 3 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
index 33e2699e..cbd75311 100644
--- a/src/components/follow_request_card/follow_request_card.js
+++ b/src/components/follow_request_card/follow_request_card.js
@@ -27,11 +27,12 @@ const FollowRequestCard = {
       })
     },
     denyUser () {
-      this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
-      this.$store.dispatch('removeFollowRequest', this.user)
-
       const notifId = this.findFollowRequestNotificationId()
-      this.$store.dispatch('dismissNotification', { id: notifId })
+      this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
+        .then(() => {
+          this.$store.dispatch('dismissNotificationLocal', { id: notifId })
+          this.$store.dispatch('removeFollowRequest', this.user)
+        })
     }
   }
 }
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index abe3bebe..1ae81ce4 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -47,8 +47,10 @@ const Notification = {
     },
     denyUser () {
       this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
-      this.$store.dispatch('removeFollowRequest', this.user)
-      this.$store.dispatch('dismissNotification', { id: this.notification.id })
+        .then(() => {
+          this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
+          this.$store.dispatch('removeFollowRequest', this.user)
+        })
     }
   },
   computed: {
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 2a8b9581..cd8c1dba 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -703,9 +703,12 @@ const statuses = {
         credentials: rootState.users.currentUser.credentials
       })
     },
+    dismissNotificationLocal ({ rootState, commit }, { id }) {
+      commit('dismissNotification', { id })
+    },
     dismissNotification ({ rootState, commit }, { id }) {
+      commit('dismissNotification', { id })
       rootState.api.backendInteractor.dismissNotification({ id })
-        .then(() => commit('dismissNotification', { id }))
     },
     updateNotification ({ rootState, commit }, { id, updater }) {
       commit('updateNotification', { id, updater })

From 068abb4d268d20ab6265a0a1a2520ea74fc45aa2 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Sat, 2 May 2020 13:05:45 +0000
Subject: [PATCH 302/483] Update CHANGELOG.md

---
 CHANGELOG.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32f9a2eb..ebd0e613 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
 All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## [Unreleased]
+### Changed
+- Removed the use of with_move parameters when fetching notifications
 
 ## [2.0.3] - 2020-05-02
 ### Fixed

From 0ba34eeca58ae5caf13faf244bd3004800291051 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 13 Apr 2020 15:26:55 +0400
Subject: [PATCH 303/483] Fix pagination

---
 src/services/api/api.service.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 7db1d094..ad2b2ad5 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -540,7 +540,7 @@ const fetchTimeline = ({
     params.push(['with_move', withMove])
   }
 
-  params.push(['count', 20])
+  params.push(['limit', 20])
   params.push(['with_muted', withMuted])
 
   const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')

From c4d1c2131ccd79bae84f668b64116d8fbf16840d Mon Sep 17 00:00:00 2001
From: Karol Kosek <krkk@krkk.ct8.pl>
Date: Sat, 18 Apr 2020 18:48:45 +0200
Subject: [PATCH 304/483] Fix user names with the RTL char in notifications

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

diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 411c0271..51875747 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -47,7 +47,7 @@
         <span class="notification-details">
           <div class="name-and-action">
             <!-- eslint-disable vue/no-v-html -->
-            <span
+            <bdi
               v-if="!!notification.from_profile.name_html"
               class="username"
               :title="'@'+notification.from_profile.screen_name"

From eae0bce3201ddca452320f2c00d4328bc808e548 Mon Sep 17 00:00:00 2001
From: xenofem <xenofem@xeno.science>
Date: Mon, 24 Feb 2020 18:10:15 -0500
Subject: [PATCH 305/483] Refactor status showing/hiding code for better
 handling of edge cases and easier comprehension

---
 src/components/status/status.js | 35 ++++++++++++++-------------------
 1 file changed, 15 insertions(+), 20 deletions(-)

diff --git a/src/components/status/status.js b/src/components/status/status.js
index fc5956ec..61d66301 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -188,23 +188,22 @@ const Status = {
       }
       return this.status.attentions.length > 0
     },
+
+    // When a status has a subject and is also tall, we should only have one show more/less button. If the default is to collapse statuses with subjects, we just treat it like a status with a subject; otherwise, we just treat it like a tall status.
+    mightHideBecauseSubject () {
+      return this.status.summary && (!this.tallStatus || this.localCollapseSubjectDefault)
+    },
+    mightHideBecauseTall () {
+      return this.tallStatus && (!this.status.summary || !this.localCollapseSubjectDefault)
+    },
     hideSubjectStatus () {
-      if (this.tallStatus && !this.localCollapseSubjectDefault) {
-        return false
-      }
-      return !this.expandingSubject && this.status.summary
+      return this.mightHideBecauseSubject && !this.expandingSubject
     },
     hideTallStatus () {
-      if (this.status.summary && this.localCollapseSubjectDefault) {
-        return false
-      }
-      if (this.showingTall) {
-        return false
-      }
-      return this.tallStatus
+      return this.mightHideBecauseTall && !this.showingTall
     },
     showingMore () {
-      return (this.tallStatus && this.showingTall) || (this.status.summary && this.expandingSubject)
+      return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject)
     },
     nsfwClickthrough () {
       if (!this.status.nsfw) {
@@ -408,14 +407,10 @@ const Status = {
       this.userExpanded = !this.userExpanded
     },
     toggleShowMore () {
-      if (this.showingTall) {
-        this.showingTall = false
-      } else if (this.expandingSubject && this.status.summary) {
-        this.expandingSubject = false
-      } else if (this.hideTallStatus) {
-        this.showingTall = true
-      } else if (this.hideSubjectStatus && this.status.summary) {
-        this.expandingSubject = true
+      if (this.mightHideBecauseTall) {
+        this.showingTall = !this.showingTall
+      } else if (this.mightHideBecauseSubject) {
+        this.expandingSubject = !this.expandingSubject
       }
     },
     generateUserProfileLink (id, name) {

From aef03d53b2082f7a1198f63940a18dd112021982 Mon Sep 17 00:00:00 2001
From: xenofem <xenofem@xeno.science>
Date: Sun, 9 Feb 2020 17:25:24 -0500
Subject: [PATCH 306/483] Allow emoji suggestions based on a match anywhere in
 the emoji name, but improve sorting

---
 src/components/emoji_input/suggestor.js | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
index aec5c39d..9e437ccc 100644
--- a/src/components/emoji_input/suggestor.js
+++ b/src/components/emoji_input/suggestor.js
@@ -29,17 +29,21 @@ export default data => input => {
 export const suggestEmoji = emojis => input => {
   const noPrefix = input.toLowerCase().substr(1)
   return emojis
-    .filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix))
+    .filter(({ displayText }) => displayText.toLowerCase().match(noPrefix))
     .sort((a, b) => {
       let aScore = 0
       let bScore = 0
 
-      // Make custom emojis a priority
-      aScore += a.imageUrl ? 10 : 0
-      bScore += b.imageUrl ? 10 : 0
+      // Prioritize emoji that start with the input string
+      aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
+      bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
 
-      // Sort alphabetically
-      const alphabetically = a.displayText > b.displayText ? 1 : -1
+      // Sort by length
+      aScore -= a.displayText.length
+      bScore -= b.displayText.length
+
+      // Break ties alphabetically
+      const alphabetically = a.displayText > b.displayText ? 0.5 : -0.5
 
       return bScore - aScore + alphabetically
     })

From fe4282f44b4aff457c9b7473cb815b310fe6cb54 Mon Sep 17 00:00:00 2001
From: xenofem <xenofem@xeno.science>
Date: Mon, 10 Feb 2020 09:32:07 -0500
Subject: [PATCH 307/483] Prioritize custom emoji a lot and boost exact matches
 to the top

---
 src/components/emoji_input/suggestor.js | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
index 9e437ccc..15a71eff 100644
--- a/src/components/emoji_input/suggestor.js
+++ b/src/components/emoji_input/suggestor.js
@@ -34,7 +34,15 @@ export const suggestEmoji = emojis => input => {
       let aScore = 0
       let bScore = 0
 
-      // Prioritize emoji that start with the input string
+      // An exact match always wins
+      aScore += a.displayText.toLowerCase() === noPrefix ? 200 : 0
+      bScore += b.displayText.toLowerCase() === noPrefix ? 200 : 0
+
+      // Prioritize custom emoji a lot
+      aScore += a.imageUrl ? 100 : 0
+      bScore += b.imageUrl ? 100 : 0
+
+      // Prioritize prefix matches somewhat
       aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
       bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
 

From 372eb723dba981550a4b258ad74c727e0d40ba60 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Mon, 27 Apr 2020 08:09:31 +0000
Subject: [PATCH 308/483] Update CHANGELOG.md

---
 CHANGELOG.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 375e560f..f45561d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
+### Fixed
+- Show more/less works correctly with auto-collapsed subjects and long posts
+- RTL characters won't look messed up in notifications
+
+### Changed
+- Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach:
 
 ## [2.0.2] - 2020-04-08
 ### Fixed

From 4d1a67463436c963288d7c65a5856dee69fa4c93 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 1 May 2020 19:28:26 +0000
Subject: [PATCH 309/483] Update CHANGELOG.md

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f45561d0..86d981da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
-## [Unreleased]
+## [2.0.3] - 2020-05-02
 ### Fixed
 - Show more/less works correctly with auto-collapsed subjects and long posts
 - RTL characters won't look messed up in notifications

From ab3c0e8512e231a5bfb9464379d5090b876034fe Mon Sep 17 00:00:00 2001
From: eugenijm <eugenijm@protonmail.com>
Date: Sat, 25 Apr 2020 07:04:39 +0300
Subject: [PATCH 310/483] Add support for follow request notifications

---
 CHANGELOG.md                                  |  3 ++
 src/components/notification/notification.js   | 19 +++++++
 src/components/notification/notification.vue  | 50 +++++++++++++------
 .../notifications/notifications.scss          | 15 ++++++
 src/i18n/en.json                              |  5 +-
 src/modules/config.js                         |  3 +-
 src/modules/statuses.js                       | 22 +++++++-
 src/services/api/api.service.js               | 11 ++++
 .../entity_normalizer.service.js              |  5 +-
 .../notification_utils/notification_utils.js  |  7 ++-
 static/fontello.json                          | 14 +++++-
 11 files changed, 131 insertions(+), 23 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 86d981da..32f9a2eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Changed
 - Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach:
 
+### Add
+- Follow request notification support
+
 ## [2.0.2] - 2020-04-08
 ### Fixed
 - Favorite/Repeat avatars not showing up on private instances/non-public posts
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index e7bd769e..6deee7d5 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -2,6 +2,7 @@ import Status from '../status/status.vue'
 import UserAvatar from '../user_avatar/user_avatar.vue'
 import UserCard from '../user_card/user_card.vue'
 import Timeago from '../timeago/timeago.vue'
+import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
 import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 
@@ -32,6 +33,21 @@ const Notification = {
     },
     toggleMute () {
       this.unmuted = !this.unmuted
+    },
+    approveUser () {
+      this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
+      this.$store.dispatch('removeFollowRequest', this.user)
+      this.$store.dispatch('updateNotification', {
+        id: this.notification.id,
+        updater: notification => {
+          notification.type = 'follow'
+        }
+      })
+    },
+    denyUser () {
+      this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
+      this.$store.dispatch('removeFollowRequest', this.user)
+      this.$store.dispatch('dismissNotification', { id: this.notification.id })
     }
   },
   computed: {
@@ -57,6 +73,9 @@ const Notification = {
     },
     needMute () {
       return this.user.muted
+    },
+    isStatusNotification () {
+      return isStatusNotification(this.notification.type)
     }
   }
 }
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 51875747..02802776 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -74,6 +74,10 @@
               <i class="fa icon-user-plus lit" />
               <small>{{ $t('notifications.followed_you') }}</small>
             </span>
+            <span v-if="notification.type === 'follow_request'">
+              <i class="fa icon-user lit" />
+              <small>{{ $t('notifications.follow_request') }}</small>
+            </span>
             <span v-if="notification.type === 'move'">
               <i class="fa icon-arrow-curved lit" />
               <small>{{ $t('notifications.migrated_to') }}</small>
@@ -87,18 +91,7 @@
             </span>
           </div>
           <div
-            v-if="notification.type === 'follow' || notification.type === 'move'"
-            class="timeago"
-          >
-            <span class="faint">
-              <Timeago
-                :time="notification.created_at"
-                :auto-update="240"
-              />
-            </span>
-          </div>
-          <div
-            v-else
+            v-if="isStatusNotification"
             class="timeago"
           >
             <router-link
@@ -112,6 +105,17 @@
               />
             </router-link>
           </div>
+          <div
+            v-else
+            class="timeago"
+          >
+            <span class="faint">
+              <Timeago
+                :time="notification.created_at"
+                :auto-update="240"
+              />
+            </span>
+          </div>
           <a
             v-if="needMute"
             href="#"
@@ -119,12 +123,30 @@
           ><i class="button-icon icon-eye-off" /></a>
         </span>
         <div
-          v-if="notification.type === 'follow'"
+          v-if="notification.type === 'follow' || notification.type === 'follow_request'"
           class="follow-text"
         >
-          <router-link :to="userProfileLink">
+          <router-link
+            :to="userProfileLink"
+            class="follow-name"
+          >
             @{{ notification.from_profile.screen_name }}
           </router-link>
+          <div
+            v-if="notification.type === 'follow_request'"
+            style="white-space: nowrap;"
+          >
+            <i
+              class="icon-ok button-icon add-reaction-button"
+              :title="$t('tool_tip.accept_follow_request')"
+              @click="approveUser()"
+            />
+            <i
+              class="icon-cancel button-icon add-reaction-button"
+              :title="$t('tool_tip.accept_follow_request')"
+              @click="denyUser()"
+            />
+          </div>
         </div>
         <div
           v-else-if="notification.type === 'move'"
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index a8f4430f..80dad28b 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -82,6 +82,16 @@
   .follow-text, .move-text {
     padding: 0.5em 0;
     overflow-wrap: break-word;
+    display: flex;
+    justify-content: space-between;
+
+    .follow-name {
+      display: block;
+      max-width: 100%;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
   }
 
   .status-el {
@@ -143,6 +153,11 @@
       color: var(--cGreen, $fallback--cGreen);
     }
 
+    .icon-user.lit {
+      color: $fallback--cBlue;
+      color: var(--cBlue, $fallback--cBlue);
+    }
+
     .icon-user-plus.lit {
       color: $fallback--cBlue;
       color: var(--cBlue, $fallback--cBlue);
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 54d0608e..37d9591c 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -124,6 +124,7 @@
     "broken_favorite": "Unknown status, searching for it...",
     "favorited_you": "favorited your status",
     "followed_you": "followed you",
+    "follow_request": "wants to follow you",
     "load_older": "Load older notifications",
     "notifications": "Notifications",
     "read": "Read!",
@@ -697,7 +698,9 @@
     "reply": "Reply",
     "favorite": "Favorite",
     "add_reaction": "Add Reaction",
-    "user_settings": "User Settings"
+    "user_settings": "User Settings",
+    "accept_follow_request": "Accept follow request",
+    "reject_follow_request": "Reject follow request"
   },
   "upload":{
     "error": {
diff --git a/src/modules/config.js b/src/modules/config.js
index 7997521d..8f4638f5 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -34,7 +34,8 @@ export const defaultState = {
     likes: true,
     repeats: true,
     moves: true,
-    emojiReactions: false
+    emojiReactions: false,
+    followRequest: true
   },
   webPushNotifications: false,
   muteWords: [],
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index f1b7dcbd..239f41eb 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -13,6 +13,7 @@ import {
   omitBy
 } from 'lodash'
 import { set } from 'vue'
+import { isStatusNotification } from '../services/notification_utils/notification_utils.js'
 import apiService from '../services/api/api.service.js'
 // import parse from '../services/status_parser/status_parser.js'
 
@@ -321,7 +322,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
 
 const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => {
   each(notifications, (notification) => {
-    if (notification.type !== 'follow' && notification.type !== 'move') {
+    if (isStatusNotification(notification.type)) {
       notification.action = addStatusToGlobalStorage(state, notification.action).item
       notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
     }
@@ -361,13 +362,16 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
           case 'move':
             i18nString = 'migrated_to'
             break
+          case 'follow_request':
+            i18nString = 'follow_request'
+            break
         }
 
         if (notification.type === 'pleroma:emoji_reaction') {
           notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji])
         } else if (i18nString) {
           notifObj.body = rootGetters.i18n.t('notifications.' + i18nString)
-        } else {
+        } else if (isStatusNotification(notification.type)) {
           notifObj.body = notification.status.text
         }
 
@@ -521,6 +525,13 @@ export const mutations = {
       notification.seen = true
     })
   },
+  dismissNotification (state, { id }) {
+    state.notifications.data = state.notifications.data.filter(n => n.id !== id)
+  },
+  updateNotification (state, { id, updater }) {
+    const notification = find(state.notifications.data, n => n.id === id)
+    notification && updater(notification)
+  },
   queueFlush (state, { timeline, id }) {
     state.timelines[timeline].flushMarker = id
   },
@@ -680,6 +691,13 @@ const statuses = {
         credentials: rootState.users.currentUser.credentials
       })
     },
+    dismissNotification ({ rootState, commit }, { id }) {
+      rootState.api.backendInteractor.dismissNotification({ id })
+        .then(() => commit('dismissNotification', { id }))
+    },
+    updateNotification ({ rootState, commit }, { id, updater }) {
+      commit('updateNotification', { id, updater })
+    },
     fetchFavsAndRepeats ({ rootState, commit }, id) {
       Promise.all([
         rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index ad2b2ad5..cda61ee2 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -29,6 +29,7 @@ const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
 const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
 const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
 const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
+const MASTODON_DISMISS_NOTIFICATION_URL = id => `/api/v1/notifications/${id}/dismiss`
 const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
 const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite`
 const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog`
@@ -1010,6 +1011,15 @@ const unmuteDomain = ({ domain, credentials }) => {
   })
 }
 
+const dismissNotification = ({ credentials, id }) => {
+  return promisedRequest({
+    url: MASTODON_DISMISS_NOTIFICATION_URL(id),
+    method: 'POST',
+    payload: { id },
+    credentials
+  })
+}
+
 export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
   return Object.entries({
     ...(credentials
@@ -1165,6 +1175,7 @@ const apiService = {
   denyUser,
   suggestions,
   markNotificationsAsSeen,
+  dismissNotification,
   vote,
   fetchPoll,
   fetchFavoritedByUsers,
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 84169a7b..6cacd0b8 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -1,4 +1,5 @@
 import escape from 'escape-html'
+import { isStatusNotification } from '../notification_utils/notification_utils.js'
 
 const qvitterStatusType = (status) => {
   if (status.is_post_verb) {
@@ -346,9 +347,7 @@ export const parseNotification = (data) => {
   if (masto) {
     output.type = mastoDict[data.type] || data.type
     output.seen = data.pleroma.is_seen
-    output.status = output.type === 'follow' || output.type === 'move'
-      ? null
-      : parseStatus(data.status)
+    output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null
     output.action = output.status // TODO: Refactor, this is unneeded
     output.target = output.type !== 'move'
       ? null
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index b17bd7bf..eb479227 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -1,4 +1,4 @@
-import { filter, sortBy } from 'lodash'
+import { filter, sortBy, includes } from 'lodash'
 
 export const notificationsFromStore = store => store.state.statuses.notifications.data
 
@@ -7,10 +7,15 @@ export const visibleTypes = store => ([
   store.state.config.notificationVisibility.mentions && 'mention',
   store.state.config.notificationVisibility.repeats && 'repeat',
   store.state.config.notificationVisibility.follows && 'follow',
+  store.state.config.notificationVisibility.followRequest && 'follow_request',
   store.state.config.notificationVisibility.moves && 'move',
   store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction'
 ].filter(_ => _))
 
+const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction']
+
+export const isStatusNotification = (type) => includes(statusNotifications, type)
+
 const sortById = (a, b) => {
   const seqA = Number(a.id)
   const seqB = Number(b.id)
diff --git a/static/fontello.json b/static/fontello.json
index 5a7086a2..5963b68b 100755
--- a/static/fontello.json
+++ b/static/fontello.json
@@ -345,6 +345,18 @@
       "css": "link",
       "code": 59427,
       "src": "fontawesome"
+    },
+    {
+      "uid": "8b80d36d4ef43889db10bc1f0dc9a862",
+      "css": "user",
+      "code": 59428,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "12f4ece88e46abd864e40b35e05b11cd",
+      "css": "ok",
+      "code": 59431,
+      "src": "fontawesome"
     }
   ]
-}
+}
\ No newline at end of file

From 36dcfa8cc1315be65a47c041a8c926ff961b3ae4 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Sat, 2 May 2020 10:19:47 +0300
Subject: [PATCH 311/483] follow request bugfixes, wrong text, notifs not being
 marked as read, approving from follow request view

---
 .../follow_request_card.js                    | 19 +++++++++++++++++++
 src/components/notification/notification.js   |  1 +
 src/components/notification/notification.vue  |  6 +++---
 .../notifications/notifications.scss          | 19 +++++++++++++++++++
 4 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
index a8931787..2a9d3db5 100644
--- a/src/components/follow_request_card/follow_request_card.js
+++ b/src/components/follow_request_card/follow_request_card.js
@@ -1,4 +1,5 @@
 import BasicUserCard from '../basic_user_card/basic_user_card.vue'
+import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
 
 const FollowRequestCard = {
   props: ['user'],
@@ -6,13 +7,31 @@ const FollowRequestCard = {
     BasicUserCard
   },
   methods: {
+    findFollowRequestNotificationId () {
+      const notif = notificationsFromStore(this.$store).find(
+        (notif) => notif.from_profile.id === this.user.id && notif.type === 'follow_request'
+      )
+      return notif && notif.id
+    },
     approveUser () {
       this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
       this.$store.dispatch('removeFollowRequest', this.user)
+
+      const notifId = this.findFollowRequestNotificationId()
+      this.$store.dispatch('updateNotification', {
+        id: notifId,
+        updater: notification => {
+          notification.type = 'follow'
+          notification.seen = true
+        }
+      })
     },
     denyUser () {
       this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
       this.$store.dispatch('removeFollowRequest', this.user)
+
+      const notifId = this.findFollowRequestNotificationId()
+      this.$store.dispatch('dismissNotification', { id: notifId })
     }
   }
 }
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 6deee7d5..8c20ff09 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -41,6 +41,7 @@ const Notification = {
         id: this.notification.id,
         updater: notification => {
           notification.type = 'follow'
+          notification.seen = true
         }
       })
     },
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 02802776..f6da07dd 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -137,13 +137,13 @@
             style="white-space: nowrap;"
           >
             <i
-              class="icon-ok button-icon add-reaction-button"
+              class="icon-ok button-icon follow-request-accept"
               :title="$t('tool_tip.accept_follow_request')"
               @click="approveUser()"
             />
             <i
-              class="icon-cancel button-icon add-reaction-button"
-              :title="$t('tool_tip.accept_follow_request')"
+              class="icon-cancel button-icon follow-request-reject"
+              :title="$t('tool_tip.reject_follow_request')"
               @click="denyUser()"
             />
           </div>
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 80dad28b..9efcfcf8 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -79,6 +79,25 @@
     }
   }
 
+  .follow-request-accept {
+    cursor: pointer;
+
+    &:hover {
+      color: $fallback--text;
+      color: var(--text, $fallback--text);
+    }
+  }
+
+  .follow-request-reject {
+    cursor: pointer;
+
+    &:hover {
+      color: $fallback--cRed;
+      color: var(--cRed, $fallback--cRed);
+    }
+  }
+
+
   .follow-text, .move-text {
     padding: 0.5em 0;
     overflow-wrap: break-word;

From 20b53d58b753075bb1bda49d0595eba02ed5f755 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Sat, 2 May 2020 10:52:57 +0300
Subject: [PATCH 312/483] mark single notifs as seen properly on server

---
 .../follow_request_card/follow_request_card.js       |  2 +-
 src/components/notification/notification.js          |  2 +-
 src/modules/statuses.js                              | 12 ++++++++++++
 src/services/api/api.service.js                      | 12 ++++++++----
 4 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
index 2a9d3db5..33e2699e 100644
--- a/src/components/follow_request_card/follow_request_card.js
+++ b/src/components/follow_request_card/follow_request_card.js
@@ -18,11 +18,11 @@ const FollowRequestCard = {
       this.$store.dispatch('removeFollowRequest', this.user)
 
       const notifId = this.findFollowRequestNotificationId()
+      this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId })
       this.$store.dispatch('updateNotification', {
         id: notifId,
         updater: notification => {
           notification.type = 'follow'
-          notification.seen = true
         }
       })
     },
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 8c20ff09..abe3bebe 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -37,11 +37,11 @@ const Notification = {
     approveUser () {
       this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
       this.$store.dispatch('removeFollowRequest', this.user)
+      this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
       this.$store.dispatch('updateNotification', {
         id: this.notification.id,
         updater: notification => {
           notification.type = 'follow'
-          notification.seen = true
         }
       })
     },
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 239f41eb..2a8b9581 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -525,6 +525,10 @@ export const mutations = {
       notification.seen = true
     })
   },
+  markSingleNotificationAsSeen (state, { id }) {
+    const notification = find(state.notifications.data, n => n.id === id)
+    if (notification) notification.seen = true
+  },
   dismissNotification (state, { id }) {
     state.notifications.data = state.notifications.data.filter(n => n.id !== id)
   },
@@ -691,6 +695,14 @@ const statuses = {
         credentials: rootState.users.currentUser.credentials
       })
     },
+    markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
+      commit('markSingleNotificationAsSeen', { id })
+      apiService.markNotificationsAsSeen({
+        single: true,
+        id,
+        credentials: rootState.users.currentUser.credentials
+      })
+    },
     dismissNotification ({ rootState, commit }, { id }) {
       rootState.api.backendInteractor.dismissNotification({ id })
         .then(() => commit('dismissNotification', { id }))
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index cda61ee2..9d1ce393 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -4,7 +4,6 @@ import 'whatwg-fetch'
 import { RegistrationError, StatusCodeError } from '../errors/errors'
 
 /* eslint-env browser */
-const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
 const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
 const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
 const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
@@ -17,6 +16,7 @@ const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
 const ADMIN_USERS_URL = '/api/pleroma/admin/users'
 const SUGGESTIONS_URL = '/api/v1/suggestions'
 const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
+const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
 
 const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
 const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
@@ -845,12 +845,16 @@ const suggestions = ({ credentials }) => {
   }).then((data) => data.json())
 }
 
-const markNotificationsAsSeen = ({ id, credentials }) => {
+const markNotificationsAsSeen = ({ id, credentials, single = false }) => {
   const body = new FormData()
 
-  body.append('latest_id', id)
+  if (single) {
+    body.append('id', id)
+  } else {
+    body.append('max_id', id)
+  }
 
-  return fetch(QVITTER_USER_NOTIFICATIONS_READ_URL, {
+  return fetch(NOTIFICATION_READ_URL, {
     body,
     headers: authHeaders(credentials),
     method: 'POST'

From b095d2e17e03189adb62227da95ca5431bc64f5a Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Sat, 2 May 2020 11:51:39 +0300
Subject: [PATCH 313/483] don't dismiss a rejected follow request on server

---
 .../follow_request_card/follow_request_card.js           | 9 +++++----
 src/components/notification/notification.js              | 6 ++++--
 src/modules/statuses.js                                  | 5 ++++-
 3 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
index 33e2699e..cbd75311 100644
--- a/src/components/follow_request_card/follow_request_card.js
+++ b/src/components/follow_request_card/follow_request_card.js
@@ -27,11 +27,12 @@ const FollowRequestCard = {
       })
     },
     denyUser () {
-      this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
-      this.$store.dispatch('removeFollowRequest', this.user)
-
       const notifId = this.findFollowRequestNotificationId()
-      this.$store.dispatch('dismissNotification', { id: notifId })
+      this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
+        .then(() => {
+          this.$store.dispatch('dismissNotificationLocal', { id: notifId })
+          this.$store.dispatch('removeFollowRequest', this.user)
+        })
     }
   }
 }
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index abe3bebe..1ae81ce4 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -47,8 +47,10 @@ const Notification = {
     },
     denyUser () {
       this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
-      this.$store.dispatch('removeFollowRequest', this.user)
-      this.$store.dispatch('dismissNotification', { id: this.notification.id })
+        .then(() => {
+          this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
+          this.$store.dispatch('removeFollowRequest', this.user)
+        })
     }
   },
   computed: {
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 2a8b9581..cd8c1dba 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -703,9 +703,12 @@ const statuses = {
         credentials: rootState.users.currentUser.credentials
       })
     },
+    dismissNotificationLocal ({ rootState, commit }, { id }) {
+      commit('dismissNotification', { id })
+    },
     dismissNotification ({ rootState, commit }, { id }) {
+      commit('dismissNotification', { id })
       rootState.api.backendInteractor.dismissNotification({ id })
-        .then(() => commit('dismissNotification', { id }))
     },
     updateNotification ({ rootState, commit }, { id, updater }) {
       commit('updateNotification', { id, updater })

From 2618c1b702d1881970cd1ee1109c421b24f2229e Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Sat, 2 May 2020 13:05:45 +0000
Subject: [PATCH 314/483] Update CHANGELOG.md

---
 CHANGELOG.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32f9a2eb..ebd0e613 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
 All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## [Unreleased]
+### Changed
+- Removed the use of with_move parameters when fetching notifications
 
 ## [2.0.3] - 2020-05-02
 ### Fixed

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: [PATCH 315/483] 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 -
 .../settings_modal/settings_modal.js          |  39 ++
 .../settings_modal/settings_modal.scss        |  59 ++
 .../settings_modal/settings_modal.vue         |  31 +
 .../settings_modal/tabs/data_import_export.js |  65 ++
 .../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 ++++++
 .../settings_modal/tabs/profile.scss          |  82 +++
 .../settings_modal/tabs/profile.vue           | 213 +++++++
 .../settings_modal/tabs/security.js           | 106 ++++
 .../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 +-------
 .../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

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-wrapper {
+    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;
-        }
-      }
-
-      &: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')
     }
   }
 }

From f7f8a579fa17102a994dc7bd7a4c7808e0964d55 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Mon, 4 May 2020 12:56:39 +0300
Subject: [PATCH 316/483] make email validation conditional work

---
 src/components/registration/registration.js | 23 ++++++++++++---------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index 1d8109e4..dab06e1e 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -14,15 +14,17 @@ const registration = {
     },
     captcha: {}
   }),
-  validations: {
-    user: {
-      email: requiredIf('accountActivationRequired'),
-      username: { required },
-      fullname: { required },
-      password: { required },
-      confirm: {
-        required,
-        sameAsPassword: sameAs('password')
+  validations () {
+    return {
+      user: {
+        email: { required: requiredIf(() => this.accountActivationRequired) },
+        username: { required },
+        fullname: { required },
+        password: { required },
+        confirm: {
+          required,
+          sameAsPassword: sameAs('password')
+        }
       }
     }
   },
@@ -43,7 +45,8 @@ const registration = {
       signedIn: (state) => !!state.users.currentUser,
       isPending: (state) => state.users.signUpPending,
       serverValidationErrors: (state) => state.users.signUpErrors,
-      termsOfService: (state) => state.instance.tos
+      termsOfService: (state) => state.instance.tos,
+      accountActivationRequired: (state) => state.instance.accountActivationRequired
     })
   },
   methods: {

From b3003d4e8de46ebf0ade12e6c6527bbfdb016e1d Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 6 May 2020 11:46:40 -0500
Subject: [PATCH 317/483] Add notification privacy option to user settings

---
 src/components/user_settings/user_settings.vue | 12 ++++++++++++
 src/i18n/en.json                               |  3 +++
 2 files changed, 15 insertions(+)

diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 8b2336b4..ad184520 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -379,6 +379,7 @@
           :label="$t('settings.notifications')"
         >
           <div class="setting-item">
+            <h2>{{ $t('settings.notification_setting_filters') }}</h2>
             <div class="select-multiple">
               <span class="label">{{ $t('settings.notification_setting') }}</span>
               <ul class="option-list">
@@ -404,6 +405,17 @@
                 </li>
               </ul>
             </div>
+          </div>
+
+          <div class="setting-item">
+            <h2>{{ $t('settings.notification_setting_privacy') }}</h2>
+            <p>
+              <Checkbox v-model="notificationSettings.privacy_option">
+                {{ $t('settings.notification_setting_privacy_option') }}
+              </Checkbox>
+            </p>
+          </div>
+          <div class="setting-item">
             <p>{{ $t('settings.notification_mutes') }}</p>
             <p>{{ $t('settings.notification_blocks') }}</p>
             <button
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 37d9591c..e42754ea 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -405,11 +405,14 @@
     "fun": "Fun",
     "greentext": "Meme arrows",
     "notifications": "Notifications",
+    "notification_setting_filters": "Filters",
     "notification_setting": "Receive notifications from:",
     "notification_setting_follows": "Users you follow",
     "notification_setting_non_follows": "Users you do not follow",
     "notification_setting_followers": "Users who follow you",
     "notification_setting_non_followers": "Users who do not follow you",
+    "notification_setting_privacy": "Privacy",
+    "notification_setting_privacy_option": "Hide the sender and contents of push notifications",
     "notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
     "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
     "enable_web_push_notifications": "Enable web push notifications",

From 93baa8b664ed7769f5a562953b4b9b93d21ff043 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Fri, 8 May 2020 00:10:49 +0300
Subject: [PATCH 318/483] fix gap when not logged in

---
 src/components/react_button/react_button.js  | 2 +-
 src/components/react_button/react_button.vue | 1 -
 src/components/status/status.vue             | 2 +-
 3 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index 19949563..abc3bf07 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -2,7 +2,7 @@ import Popover from '../popover/popover.vue'
 import { mapGetters } from 'vuex'
 
 const ReactButton = {
-  props: ['status', 'loggedIn'],
+  props: ['status'],
   data () {
     return {
       filterWord: ''
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index ab4b4fcd..0b34add1 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -37,7 +37,6 @@
       </div>
     </div>
     <i
-      v-if="loggedIn"
       slot="trigger"
       class="icon-smile button-icon add-reaction-button"
       :title="$t('tool_tip.add_reaction')"
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index ca295640..76b5c5ab 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -404,7 +404,7 @@
               :status="status"
             />
             <ReactButton
-              :logged-in="loggedIn"
+              v-if="loggedIn"
               :status="status"
             />
             <extra-buttons

From 41fc26869f4ce9e93da94f1e441c69e2e37b0124 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 7 May 2020 16:33:21 -0500
Subject: [PATCH 319/483] Correctly resolve the URI of the server

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

diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
index 2dd77c66..e1bf7e20 100644
--- a/src/components/extra_buttons/extra_buttons.js
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -5,7 +5,7 @@ const ExtraButtons = {
   components: { Popover },
   data: function () {
     return {
-      statusLink: `https://${this.$store.state.instance.name}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
+      statusLink: `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
     }
   },
   methods: {

From ddc3b86d24249021cc1634dbdfb476684265f293 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Fri, 8 May 2020 10:46:00 +0300
Subject: [PATCH 320/483] fix popover not closing on pressing the buttons

---
 src/components/extra_buttons/extra_buttons.js  |  8 +++-----
 src/components/extra_buttons/extra_buttons.vue | 13 ++++++++-----
 2 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
index e1bf7e20..e4b19d01 100644
--- a/src/components/extra_buttons/extra_buttons.js
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -3,11 +3,6 @@ import Popover from '../popover/popover.vue'
 const ExtraButtons = {
   props: [ 'status' ],
   components: { Popover },
-  data: function () {
-    return {
-      statusLink: `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
-    }
-  },
   methods: {
     deleteStatus () {
       const confirmed = window.confirm(this.$t('status.delete_confirm'))
@@ -56,6 +51,9 @@ const ExtraButtons = {
     },
     canMute () {
       return !!this.currentUser
+    },
+    statusLink () {
+      return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
     }
   }
 }
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index c785a180..bca93ea7 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -4,7 +4,10 @@
     placement="top"
     class="extra-button-popover"
   >
-    <div slot="content">
+    <div
+      slot="content"
+      slot-scope="{close}"
+    >
       <div class="dropdown-menu">
         <button
           v-if="canMute && !status.thread_muted"
@@ -22,32 +25,32 @@
         </button>
         <button
           v-if="!status.pinned && canPin"
-          v-close-popover
           class="dropdown-item dropdown-item-icon"
           @click.prevent="pinStatus"
+          @click="close"
         >
           <i class="icon-pin" /><span>{{ $t("status.pin") }}</span>
         </button>
         <button
           v-if="status.pinned && canPin"
-          v-close-popover
           class="dropdown-item dropdown-item-icon"
           @click.prevent="unpinStatus"
+          @click="close"
         >
           <i class="icon-pin" /><span>{{ $t("status.unpin") }}</span>
         </button>
         <button
           v-if="canDelete"
-          v-close-popover
           class="dropdown-item dropdown-item-icon"
           @click.prevent="deleteStatus"
+          @click="close"
         >
           <i class="icon-cancel" /><span>{{ $t("status.delete") }}</span>
         </button>
         <button
-          v-close-popover
           class="dropdown-item dropdown-item-icon"
           @click.prevent="copyLink"
+          @click="close"
         >
           <i class="icon-share" /><span>{{ $t("status.copy_link") }}</span>
         </button>

From 7a0e554daf843fe9e98053e79ec0114c380ededb Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Fri, 8 May 2020 13:25:18 +0300
Subject: [PATCH 321/483] update changelog

---
 CHANGELOG.md | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ebd0e613..a44fb163 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Changed
 - Removed the use of with_move parameters when fetching notifications
 
+## [Unreleased patch]
+### Add
+- Added private notifications option for push notifications
+- 'Copy link' button for statuses (in the ellipsis menu)
+
+### Changed
+- Registration page no longer requires email if the server is configured not to require it
+
+### Fixed
+- Status ellipsis menu closes properly when selecting certain options
+
 ## [2.0.3] - 2020-05-02
 ### Fixed
 - Show more/less works correctly with auto-collapsed subjects and long posts

From 9180fdb4927af12c123751a30ef55d6f56ffa173 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Fri, 8 May 2020 15:56:53 -0500
Subject: [PATCH 322/483] Clarify that we only delete data, not the account

---
 src/i18n/en.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/i18n/en.json b/src/i18n/en.json
index d5748719..c7f8839d 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -281,7 +281,7 @@
     "data_import_export_tab": "Data Import / Export",
     "default_vis": "Default visibility scope",
     "delete_account": "Delete Account",
-    "delete_account_description": "Permanently delete your account and all your messages.",
+    "delete_account_description": "Permanently delete your data and deactivate your account.",
     "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
     "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
     "discoverable": "Allow discovery of this account in search results and other services",

From bcebec478e43b3851e85c94335940e8fc7546cc8 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 10 May 2020 06:46:06 +0300
Subject: [PATCH 323/483] moved stuff from settings, cleaned up naming for
 tabs, added close and peek

---
 src/App.scss                                  |  48 --
 src/components/modal/modal.vue                |  16 +-
 src/components/settings/settings.js           | 128 ------
 src/components/settings/settings.vue          | 424 ------------------
 .../settings_modal/settings_modal.js          |  42 +-
 .../settings_modal/settings_modal.scss        |  30 +-
 .../settings_modal/settings_modal.vue         |  26 +-
 ...rt_export.js => data_import_export_tab.js} |  10 +-
 ..._export.vue => data_import_export_tab.vue} |   2 +-
 .../settings_modal/tabs/filtering_tab.js      |  26 ++
 .../settings_modal/tabs/filtering_tab.vue     |  86 ++++
 .../settings_modal/tabs/general_tab.js        |  32 ++
 .../settings_modal/tabs/general_tab.vue       | 272 +++++++++++
 .../tabs/helpers/shared_computed_object.js    |  69 +++
 ..._and_blocks.js => mutes_and_blocks_tab.js} |  18 +-
 ...nd_blocks.vue => mutes_and_blocks_tab.vue} |   8 +-
 ...{notifications.js => notifications_tab.js} |   6 +-
 ...otifications.vue => notifications_tab.vue} |   2 +-
 .../tabs/{profile.js => profile_tab.js}       |  16 +-
 .../tabs/{profile.scss => profile_tab.scss}   |   0
 .../tabs/{profile.vue => profile_tab.vue}     |   4 +-
 .../tabs/security_tab}/confirm.js             |   0
 .../tabs/security_tab}/confirm.vue            |   0
 .../tabs/security_tab}/mfa.js                 |   0
 .../tabs/security_tab}/mfa.vue                |  11 +-
 .../tabs/security_tab}/mfa_backup_codes.js    |   0
 .../tabs/security_tab}/mfa_backup_codes.vue   |  18 +-
 .../tabs/security_tab}/mfa_totp.js            |   0
 .../tabs/security_tab}/mfa_totp.vue           |   0
 .../security_tab.js}                          |  10 +-
 .../security_tab.vue}                         |   2 +-
 .../tabs/theme_tab}/preview.vue               |   0
 .../tabs/theme_tab/theme_tab.js}              |  27 +-
 .../tabs/theme_tab/theme_tab.scss}            |  18 +-
 .../tabs/theme_tab/theme_tab.vue}             | 168 +++----
 .../settings_modal/tabs/version_tab.js        |  24 +
 .../settings_modal/tabs/version_tab.vue       |  31 ++
 src/components/tab_switcher/tab_switcher.scss |   6 +
 src/components/user_card/user_card.vue        |  11 +-
 src/components/user_settings/user_settings.js | 140 ------
 .../user_settings/user_settings.vue           | 119 -----
 src/i18n/en.json                              |   4 +-
 42 files changed, 801 insertions(+), 1053 deletions(-)
 delete mode 100644 src/components/settings/settings.js
 delete mode 100644 src/components/settings/settings.vue
 rename src/components/settings_modal/tabs/{data_import_export.js => data_import_export_tab.js} (86%)
 rename src/components/settings_modal/tabs/{data_import_export.vue => data_import_export_tab.vue} (96%)
 create mode 100644 src/components/settings_modal/tabs/filtering_tab.js
 create mode 100644 src/components/settings_modal/tabs/filtering_tab.vue
 create mode 100644 src/components/settings_modal/tabs/general_tab.js
 create mode 100644 src/components/settings_modal/tabs/general_tab.vue
 create mode 100644 src/components/settings_modal/tabs/helpers/shared_computed_object.js
 rename src/components/settings_modal/tabs/{mutes_and_blocks.js => mutes_and_blocks_tab.js} (83%)
 rename src/components/settings_modal/tabs/{mutes_and_blocks.vue => mutes_and_blocks_tab.vue} (97%)
 rename src/components/settings_modal/tabs/{notifications.js => notifications_tab.js} (80%)
 rename src/components/settings_modal/tabs/{notifications.vue => notifications_tab.vue} (96%)
 rename src/components/settings_modal/tabs/{profile.js => profile_tab.js} (90%)
 rename src/components/settings_modal/tabs/{profile.scss => profile_tab.scss} (100%)
 rename src/components/settings_modal/tabs/{profile.vue => profile_tab.vue} (98%)
 rename src/components/{user_settings => settings_modal/tabs/security_tab}/confirm.js (100%)
 rename src/components/{user_settings => settings_modal/tabs/security_tab}/confirm.vue (100%)
 rename src/components/{user_settings => settings_modal/tabs/security_tab}/mfa.js (100%)
 rename src/components/{user_settings => settings_modal/tabs/security_tab}/mfa.vue (97%)
 rename src/components/{user_settings => settings_modal/tabs/security_tab}/mfa_backup_codes.js (100%)
 rename src/components/{user_settings => settings_modal/tabs/security_tab}/mfa_backup_codes.vue (69%)
 rename src/components/{user_settings => settings_modal/tabs/security_tab}/mfa_totp.js (100%)
 rename src/components/{user_settings => settings_modal/tabs/security_tab}/mfa_totp.vue (100%)
 rename src/components/settings_modal/tabs/{security.js => security_tab/security_tab.js} (92%)
 rename src/components/settings_modal/tabs/{security.vue => security_tab/security_tab.vue} (98%)
 rename src/components/{style_switcher => settings_modal/tabs/theme_tab}/preview.vue (100%)
 rename src/components/{style_switcher/style_switcher.js => settings_modal/tabs/theme_tab/theme_tab.js} (96%)
 rename src/components/{style_switcher/style_switcher.scss => settings_modal/tabs/theme_tab/theme_tab.scss} (97%)
 rename src/components/{style_switcher/style_switcher.vue => settings_modal/tabs/theme_tab/theme_tab.vue} (92%)
 create mode 100644 src/components/settings_modal/tabs/version_tab.js
 create mode 100644 src/components/settings_modal/tabs/version_tab.vue
 delete mode 100644 src/components/user_settings/user_settings.js
 delete mode 100644 src/components/user_settings/user_settings.vue

diff --git a/src/App.scss b/src/App.scss
index 7db9461c..120eea53 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -860,54 +860,6 @@ nav {
   }
 }
 
-// DELETE
-.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;
-  }
-}
-// DELETE
-
 .select-multiple {
   display: flex;
   .option-list {
diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue
index cee24241..e5ecc0c0 100644
--- a/src/components/modal/modal.vue
+++ b/src/components/modal/modal.vue
@@ -3,6 +3,7 @@
     v-show="isOpen"
     v-body-scroll-lock="isOpen"
     class="modal-view"
+    :class="{ 'modal-background': !noBackground }"
     @click.self="$emit('backdropClicked')"
   >
     <slot />
@@ -15,6 +16,10 @@ export default {
     isOpen: {
       type: Boolean,
       default: true
+    },
+    noBackground: {
+      type: Boolean,
+      default: false
     }
   }
 }
@@ -32,10 +37,19 @@ export default {
   justify-content: center;
   align-items: center;
   overflow: auto;
+  pointer-events: none;
   animation-duration: 0.2s;
-  background-color: rgba(0, 0, 0, 0.5);
   animation-name: modal-background-fadein;
 
+  > * {
+    pointer-events: initial;
+  }
+
+  &.modal-background {
+    pointer-events: initial;
+    background-color: rgba(0, 0, 0, 0.5);
+  }
+
   body:not(.scroll-locked) & {
     opacity: 0;
   }
diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
deleted file mode 100644
index 31a9e9be..00000000
--- a/src/components/settings/settings.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/* eslint-env browser */
-import { filter, trim } from 'lodash'
-
-import TabSwitcher from '../tab_switcher/tab_switcher.js'
-import StyleSwitcher from '../style_switcher/style_switcher.vue'
-import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
-import { extractCommit } from '../../services/version/version.service'
-import { instanceDefaultProperties, defaultState as configDefaultState } from '../../modules/config.js'
-import Checkbox from '../checkbox/checkbox.vue'
-
-const pleromaFeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
-const pleromaBeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma/commit/'
-
-const multiChoiceProperties = [
-  'postContentType',
-  'subjectLineBehavior'
-]
-
-const settings = {
-  data () {
-    const instance = this.$store.state.instance
-
-    return {
-      loopSilentAvailable:
-        // Firefox
-        Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
-        // Chrome-likes
-        Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
-        // Future spec, still not supported in Nightly 63 as of 08/2018
-        Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks'),
-
-      backendVersion: instance.backendVersion,
-      frontendVersion: instance.frontendVersion
-    }
-  },
-  components: {
-    TabSwitcher,
-    StyleSwitcher,
-    InterfaceLanguageSwitcher,
-    Checkbox
-  },
-  computed: {
-    user () {
-      return this.$store.state.users.currentUser
-    },
-    currentSaveStateNotice () {
-      return this.$store.state.interface.settings.currentSaveStateNotice
-    },
-    postFormats () {
-      return this.$store.state.instance.postFormats || []
-    },
-    instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
-    frontendVersionLink () {
-      return pleromaFeCommitUrl + this.frontendVersion
-    },
-    backendVersionLink () {
-      return pleromaBeCommitUrl + extractCommit(this.backendVersion)
-    },
-    // Getting localized values for instance-default properties
-    ...instanceDefaultProperties
-      .filter(key => multiChoiceProperties.includes(key))
-      .map(key => [
-        key + 'DefaultValue',
-        function () {
-          return this.$store.getters.instanceDefaultConfig[key]
-        }
-      ])
-      .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
-    ...instanceDefaultProperties
-      .filter(key => !multiChoiceProperties.includes(key))
-      .map(key => [
-        key + 'LocalizedValue',
-        function () {
-          return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key])
-        }
-      ])
-      .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
-    // Generating computed values for vuex properties
-    ...Object.keys(configDefaultState)
-      .map(key => [key, {
-        get () { return this.$store.getters.mergedConfig[key] },
-        set (value) {
-          this.$store.dispatch('setOption', { name: key, value })
-        }
-      }])
-      .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
-    // Special cases (need to transform values or perform actions first)
-    muteWordsString: {
-      get () { return this.$store.getters.mergedConfig.muteWords.join('\n') },
-      set (value) {
-        this.$store.dispatch('setOption', {
-          name: 'muteWords',
-          value: filter(value.split('\n'), (word) => trim(word).length > 0)
-        })
-      }
-    },
-    useStreamingApi: {
-      get () { return this.$store.getters.mergedConfig.useStreamingApi },
-      set (value) {
-        const promise = value
-          ? this.$store.dispatch('enableMastoSockets')
-          : this.$store.dispatch('disableMastoSockets')
-
-        promise.then(() => {
-          this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
-        }).catch((e) => {
-          console.error('Failed starting MastoAPI Streaming socket', e)
-          this.$store.dispatch('disableMastoSockets')
-          this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
-        })
-      }
-    }
-  },
-  // Updating nested properties
-  watch: {
-    notificationVisibility: {
-      handler (value) {
-        this.$store.dispatch('setOption', {
-          name: 'notificationVisibility',
-          value: this.$store.getters.mergedConfig.notificationVisibility
-        })
-      },
-      deep: true
-    }
-  }
-}
-
-export default settings
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
deleted file mode 100644
index 9e14b449..00000000
--- a/src/components/settings/settings.vue
+++ /dev/null
@@ -1,424 +0,0 @@
-<template>
-  <div class="settings panel panel-default">
-    <div class="panel-heading">
-      <div class="title">
-        {{ $t('settings.settings') }}
-      </div>
-
-      <transition name="fade">
-        <template v-if="currentSaveStateNotice">
-          <div
-            v-if="currentSaveStateNotice.error"
-            class="alert error"
-            @click.prevent
-          >
-            {{ $t('settings.saving_err') }}
-          </div>
-
-          <div
-            v-if="!currentSaveStateNotice.error"
-            class="alert transparent"
-            @click.prevent
-          >
-            {{ $t('settings.saving_ok') }}
-          </div>
-        </template>
-      </transition>
-    </div>
-    <div class="panel-body">
-      <keep-alive>
-        <tab-switcher>
-          <div :label="$t('settings.general')">
-            <div class="setting-item">
-              <h2>{{ $t('settings.interface') }}</h2>
-              <ul class="setting-list">
-                <li>
-                  <interface-language-switcher />
-                </li>
-                <li v-if="instanceSpecificPanelPresent">
-                  <Checkbox v-model="hideISP">
-                    {{ $t('settings.hide_isp') }}
-                  </Checkbox>
-                </li>
-              </ul>
-            </div>
-            <div class="setting-item">
-              <h2>{{ $t('nav.timeline') }}</h2>
-              <ul class="setting-list">
-                <li>
-                  <Checkbox v-model="hideMutedPosts">
-                    {{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="collapseMessageWithSubject">
-                    {{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="streaming">
-                    {{ $t('settings.streaming') }}
-                  </Checkbox>
-                  <ul
-                    class="setting-list suboptions"
-                    :class="[{disabled: !streaming}]"
-                  >
-                    <li>
-                      <Checkbox
-                        v-model="pauseOnUnfocused"
-                        :disabled="!streaming"
-                      >
-                        {{ $t('settings.pause_on_unfocused') }}
-                      </Checkbox>
-                    </li>
-                  </ul>
-                </li>
-                <li>
-                  <Checkbox v-model="useStreamingApi">
-                    {{ $t('settings.useStreamingApi') }}
-                    <br>
-                    <small>
-                      {{ $t('settings.useStreamingApiWarning') }}
-                    </small>
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="autoLoad">
-                    {{ $t('settings.autoload') }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="hoverPreview">
-                    {{ $t('settings.reply_link_preview') }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="emojiReactionsOnTimeline">
-                    {{ $t('settings.emoji_reactions_on_timeline') }}
-                  </Checkbox>
-                </li>
-              </ul>
-            </div>
-
-            <div class="setting-item">
-              <h2>{{ $t('settings.composing') }}</h2>
-              <ul class="setting-list">
-                <li>
-                  <Checkbox v-model="scopeCopy">
-                    {{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="alwaysShowSubjectInput">
-                    {{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <div>
-                    {{ $t('settings.subject_line_behavior') }}
-                    <label
-                      for="subjectLineBehavior"
-                      class="select"
-                    >
-                      <select
-                        id="subjectLineBehavior"
-                        v-model="subjectLineBehavior"
-                      >
-                        <option value="email">
-                          {{ $t('settings.subject_line_email') }}
-                          {{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }}
-                        </option>
-                        <option value="masto">
-                          {{ $t('settings.subject_line_mastodon') }}
-                          {{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
-                        </option>
-                        <option value="noop">
-                          {{ $t('settings.subject_line_noop') }}
-                          {{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
-                        </option>
-                      </select>
-                      <i class="icon-down-open" />
-                    </label>
-                  </div>
-                </li>
-                <li v-if="postFormats.length > 0">
-                  <div>
-                    {{ $t('settings.post_status_content_type') }}
-                    <label
-                      for="postContentType"
-                      class="select"
-                    >
-                      <select
-                        id="postContentType"
-                        v-model="postContentType"
-                      >
-                        <option
-                          v-for="postFormat in postFormats"
-                          :key="postFormat"
-                          :value="postFormat"
-                        >
-                          {{ $t(`post_status.content_type["${postFormat}"]`) }}
-                          {{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
-                        </option>
-                      </select>
-                      <i class="icon-down-open" />
-                    </label>
-                  </div>
-                </li>
-                <li>
-                  <Checkbox v-model="minimalScopesMode">
-                    {{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="autohideFloatingPostButton">
-                    {{ $t('settings.autohide_floating_post_button') }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="padEmoji">
-                    {{ $t('settings.pad_emoji') }}
-                  </Checkbox>
-                </li>
-              </ul>
-            </div>
-
-            <div class="setting-item">
-              <h2>{{ $t('settings.attachments') }}</h2>
-              <ul class="setting-list">
-                <li>
-                  <Checkbox v-model="hideAttachments">
-                    {{ $t('settings.hide_attachments_in_tl') }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="hideAttachmentsInConv">
-                    {{ $t('settings.hide_attachments_in_convo') }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <label for="maxThumbnails">
-                    {{ $t('settings.max_thumbnails') }}
-                  </label>
-                  <input
-                    id="maxThumbnails"
-                    v-model.number="maxThumbnails"
-                    class="number-input"
-                    type="number"
-                    min="0"
-                    step="1"
-                  >
-                </li>
-                <li>
-                  <Checkbox v-model="hideNsfw">
-                    {{ $t('settings.nsfw_clickthrough') }}
-                  </Checkbox>
-                </li>
-                <ul class="setting-list suboptions">
-                  <li>
-                    <Checkbox
-                      v-model="preloadImage"
-                      :disabled="!hideNsfw"
-                    >
-                      {{ $t('settings.preload_images') }}
-                    </Checkbox>
-                  </li>
-                  <li>
-                    <Checkbox
-                      v-model="useOneClickNsfw"
-                      :disabled="!hideNsfw"
-                    >
-                      {{ $t('settings.use_one_click_nsfw') }}
-                    </Checkbox>
-                  </li>
-                </ul>
-                <li>
-                  <Checkbox v-model="stopGifs">
-                    {{ $t('settings.stop_gifs') }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="loopVideo">
-                    {{ $t('settings.loop_video') }}
-                  </Checkbox>
-                  <ul
-                    class="setting-list suboptions"
-                    :class="[{disabled: !streaming}]"
-                  >
-                    <li>
-                      <Checkbox
-                        v-model="loopVideoSilentOnly"
-                        :disabled="!loopVideo || !loopSilentAvailable"
-                      >
-                        {{ $t('settings.loop_video_silent_only') }}
-                      </Checkbox>
-                      <div
-                        v-if="!loopSilentAvailable"
-                        class="unavailable"
-                      >
-                        <i class="icon-globe" />! {{ $t('settings.limited_availability') }}
-                      </div>
-                    </li>
-                  </ul>
-                </li>
-                <li>
-                  <Checkbox v-model="playVideosInModal">
-                    {{ $t('settings.play_videos_in_modal') }}
-                  </Checkbox>
-                </li>
-                <li>
-                  <Checkbox v-model="useContainFit">
-                    {{ $t('settings.use_contain_fit') }}
-                  </Checkbox>
-                </li>
-              </ul>
-            </div>
-
-            <div class="setting-item">
-              <h2>{{ $t('settings.notifications') }}</h2>
-              <ul class="setting-list">
-                <li>
-                  <Checkbox v-model="webPushNotifications">
-                    {{ $t('settings.enable_web_push_notifications') }}
-                  </Checkbox>
-                </li>
-              </ul>
-            </div>
-
-            <div class="setting-item">
-              <h2>{{ $t('settings.fun') }}</h2>
-              <ul class="setting-list">
-                <li>
-                  <Checkbox v-model="greentext">
-                    {{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
-                  </Checkbox>
-                </li>
-              </ul>
-            </div>
-          </div>
-
-          <div :label="$t('settings.theme')">
-            <div class="setting-item">
-              <style-switcher />
-            </div>
-          </div>
-
-          <div :label="$t('settings.filtering')">
-            <div class="setting-item">
-              <div class="select-multiple">
-                <span class="label">{{ $t('settings.notification_visibility') }}</span>
-                <ul class="option-list">
-                  <li>
-                    <Checkbox v-model="notificationVisibility.likes">
-                      {{ $t('settings.notification_visibility_likes') }}
-                    </Checkbox>
-                  </li>
-                  <li>
-                    <Checkbox v-model="notificationVisibility.repeats">
-                      {{ $t('settings.notification_visibility_repeats') }}
-                    </Checkbox>
-                  </li>
-                  <li>
-                    <Checkbox v-model="notificationVisibility.follows">
-                      {{ $t('settings.notification_visibility_follows') }}
-                    </Checkbox>
-                  </li>
-                  <li>
-                    <Checkbox v-model="notificationVisibility.mentions">
-                      {{ $t('settings.notification_visibility_mentions') }}
-                    </Checkbox>
-                  </li>
-                  <li>
-                    <Checkbox v-model="notificationVisibility.moves">
-                      {{ $t('settings.notification_visibility_moves') }}
-                    </Checkbox>
-                  </li>
-                  <li>
-                    <Checkbox v-model="notificationVisibility.emojiReactions">
-                      {{ $t('settings.notification_visibility_emoji_reactions') }}
-                    </Checkbox>
-                  </li>
-                </ul>
-              </div>
-              <div>
-                {{ $t('settings.replies_in_timeline') }}
-                <label
-                  for="replyVisibility"
-                  class="select"
-                >
-                  <select
-                    id="replyVisibility"
-                    v-model="replyVisibility"
-                  >
-                    <option
-                      value="all"
-                      selected
-                    >{{ $t('settings.reply_visibility_all') }}</option>
-                    <option value="following">{{ $t('settings.reply_visibility_following') }}</option>
-                    <option value="self">{{ $t('settings.reply_visibility_self') }}</option>
-                  </select>
-                  <i class="icon-down-open" />
-                </label>
-              </div>
-              <div>
-                <Checkbox v-model="hidePostStats">
-                  {{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }}
-                </Checkbox>
-              </div>
-              <div>
-                <Checkbox v-model="hideUserStats">
-                  {{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }}
-                </Checkbox>
-              </div>
-            </div>
-            <div class="setting-item">
-              <div>
-                <p>{{ $t('settings.filtering_explanation') }}</p>
-                <textarea
-                  id="muteWords"
-                  v-model="muteWordsString"
-                />
-              </div>
-              <div>
-                <Checkbox v-model="hideFilteredStatuses">
-                  {{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }}
-                </Checkbox>
-              </div>
-            </div>
-          </div>
-          <div :label="$t('settings.version.title')">
-            <div class="setting-item">
-              <ul class="setting-list">
-                <li>
-                  <p>{{ $t('settings.version.backend_version') }}</p>
-                  <ul class="option-list">
-                    <li>
-                      <a
-                        :href="backendVersionLink"
-                        target="_blank"
-                      >{{ backendVersion }}</a>
-                    </li>
-                  </ul>
-                </li>
-                <li>
-                  <p>{{ $t('settings.version.frontend_version') }}</p>
-                  <ul class="option-list">
-                    <li>
-                      <a
-                        :href="frontendVersionLink"
-                        target="_blank"
-                      >{{ frontendVersion }}</a>
-                    </li>
-                  </ul>
-                </li>
-              </ul>
-            </div>
-          </div>
-        </tab-switcher>
-      </keep-alive>
-    </div>
-  </div>
-</template>
-
-<script src="./settings.js">
-</script>
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index 1f4c038f..d60babf6 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -1,21 +1,30 @@
-import Modal from '../modal/modal.vue'
-import TabSwitcher from '../tab_switcher/tab_switcher.js'
+import Modal from 'src/components/modal/modal.vue'
+import TabSwitcher from 'src/components/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'
+import DataImportExportTab from './tabs/data_import_export_tab.vue'
+import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue'
+import NotificationsTab from './tabs/notifications_tab.vue'
+import FilteringTab from './tabs/filtering_tab.vue'
+import SecurityTab from './tabs/security_tab/security_tab.vue'
+import ProfileTab from './tabs/profile_tab.vue'
+import GeneralTab from './tabs/general_tab.vue'
+import VersionTab from './tabs/version_tab.vue'
+import ThemeTab from './tabs/theme_tab/theme_tab.vue'
 
 const SettingsModal = {
   components: {
     Modal,
     TabSwitcher,
-    Profile,
-    Security,
-    Notifications,
-    DataImportExport,
-    MutesAndBlocks
+
+    DataImportExportTab,
+    MutesAndBlocksTab,
+    NotificationsTab,
+    FilteringTab,
+    SecurityTab,
+    ProfileTab,
+    GeneralTab,
+    VersionTab,
+    ThemeTab
   },
   data () {
     return {
@@ -28,11 +37,20 @@ const SettingsModal = {
     },
     modalActivated () {
       return this.$store.state.interface.settingsModalState !== 'hidden'
+    },
+    modalPeeked () {
+      return this.$store.state.interface.settingsModalState === 'minimized'
     }
   },
   watch: {
   },
   methods: {
+    closeModal () {
+      this.$store.dispatch('closeSettingsModal')
+    },
+    peekModal () {
+      this.$store.dispatch('togglePeekSettingsModal')
+    }
   }
 }
 
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index 8cea52d2..3efbe205 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -1,14 +1,29 @@
-@import '../../_variables.scss';
+@import 'src/_variables.scss';
 .settings-modal {
+
   .settings_tab-switcher {
     height: 100%;
   }
+  &.peek {
+    .settings-modal-panel {
+      transform: translateY(calc(100% - 50px));
+    }
+  }
+
   .settings-modal-panel {
+    transition: transform;
+    transition-timing-function: ease-in-out;
+    transition-duration: 300ms;
     width: 1000px;
     max-width: 90vw;
     height: 90vh;
+    @media all and (max-width: 800px) {
+      max-width: 100vw;
+      height: 100vh;
+    }
   }
   .panel-body {
+    height: 100%;
     overflow-y: hidden;
   }
   .setting-item {
@@ -16,6 +31,12 @@
     margin: 1em 1em 1.4em;
     padding-bottom: 1.4em;
 
+    .btn {
+      min-height: 28px;
+      min-width: 10em;
+      padding: 0 2em;
+    }
+
     > div {
       margin-bottom: .5em;
       &:last-child {
@@ -33,7 +54,6 @@
       min-width: 10em;
     }
 
-
     textarea {
       width: 100%;
       max-width: 100%;
@@ -46,12 +66,6 @@
       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
index 9e35d3f6..53481bdd 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -3,10 +3,20 @@
   v-if="isLoggedIn && !resettingForm"
   :is-open="modalActivated"
   class="settings-modal"
+  :class="{ peek: modalPeeked }"
+  :no-background="modalPeeked"
   >
   <div class="settings-modal-panel panel">
     <div class="panel-heading">
-      {{ $t('settings.settings') }}
+      <span class="title">
+        {{ $t('settings.settings') }}
+      </span>
+      <button class="btn" @click="peekModal">
+        {{ $t('general.peek') }}
+      </button>
+      <button class="btn" @click="closeModal">
+        {{ $t('general.close') }}
+      </button>
     </div>
     <div class="panel-body">
       <tab-switcher
@@ -15,11 +25,15 @@
         :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>
+        <div :label="$t('settings.general')"><GeneralTab /></div>
+        <div :label="$t('settings.profile_tab')"><ProfileTab /></div>
+        <div :label="$t('settings.security_tab')"><SecurityTab /></div>
+        <div :label="$t('settings.filtering')"><FilteringTab /></div>
+        <div :label="$t('settings.theme')"><ThemeTab /></div>
+        <div :label="$t('settings.notifications')"><NotificationsTab /></div>
+        <div :label="$t('settings.data_import_export_tab')"><DataImportExportTab /></div>
+        <div :label="$t('settings.mutes_and_blocks')"><MutesAndBlocksTab /></div>
+        <div :label="$t('settings.version.title')"><VersionTab /></div>
       </tab-switcher>
     </div>
   </div>
diff --git a/src/components/settings_modal/tabs/data_import_export.js b/src/components/settings_modal/tabs/data_import_export_tab.js
similarity index 86%
rename from src/components/settings_modal/tabs/data_import_export.js
rename to src/components/settings_modal/tabs/data_import_export_tab.js
index f68d12e9..168f89e1 100644
--- a/src/components/settings_modal/tabs/data_import_export.js
+++ b/src/components/settings_modal/tabs/data_import_export_tab.js
@@ -1,8 +1,8 @@
-import Importer from '../../importer/importer.vue'
-import Exporter from '../../exporter/exporter.vue'
-import Checkbox from '../../checkbox/checkbox.vue'
+import Importer from 'src/components/importer/importer.vue'
+import Exporter from 'src/components/exporter/exporter.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
 
-const DataImportExport = {
+const DataImportExportTab = {
   data () {
     return {
       activeTab: 'profile',
@@ -62,4 +62,4 @@ const DataImportExport = {
   }
 }
 
-export default DataImportExport
+export default DataImportExportTab
diff --git a/src/components/settings_modal/tabs/data_import_export.vue b/src/components/settings_modal/tabs/data_import_export_tab.vue
similarity index 96%
rename from src/components/settings_modal/tabs/data_import_export.vue
rename to src/components/settings_modal/tabs/data_import_export_tab.vue
index 464df6d3..3ddc8b03 100644
--- a/src/components/settings_modal/tabs/data_import_export.vue
+++ b/src/components/settings_modal/tabs/data_import_export_tab.vue
@@ -39,5 +39,5 @@
 </div>
 </template>
 
-<script src="./data_import_export.js"></script>
+<script src="./data_import_export_tab.js"></script>
 <!-- <style lang="scss" src="./profile.scss"></style> -->
diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js
new file mode 100644
index 00000000..ec330667
--- /dev/null
+++ b/src/components/settings_modal/tabs/filtering_tab.js
@@ -0,0 +1,26 @@
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+
+import SharedComputedObject from './helpers/shared_computed_object.js'
+
+const FilteringTab = {
+  components: {
+    Checkbox
+  },
+  computed: {
+    ...SharedComputedObject()
+  },
+  // Updating nested properties
+  watch: {
+    notificationVisibility: {
+      handler (value) {
+        this.$store.dispatch('setOption', {
+          name: 'notificationVisibility',
+          value: this.$store.getters.mergedConfig.notificationVisibility
+        })
+      },
+      deep: true
+    }
+  }
+}
+
+export default FilteringTab
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
new file mode 100644
index 00000000..647ec7b4
--- /dev/null
+++ b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -0,0 +1,86 @@
+<template>
+<div :label="$t('settings.filtering')">
+  <div class="setting-item">
+    <div class="select-multiple">
+      <span class="label">{{ $t('settings.notification_visibility') }}</span>
+      <ul class="option-list">
+        <li>
+          <Checkbox v-model="notificationVisibility.likes">
+            {{ $t('settings.notification_visibility_likes') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="notificationVisibility.repeats">
+            {{ $t('settings.notification_visibility_repeats') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="notificationVisibility.follows">
+            {{ $t('settings.notification_visibility_follows') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="notificationVisibility.mentions">
+            {{ $t('settings.notification_visibility_mentions') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="notificationVisibility.moves">
+            {{ $t('settings.notification_visibility_moves') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="notificationVisibility.emojiReactions">
+            {{ $t('settings.notification_visibility_emoji_reactions') }}
+          </Checkbox>
+        </li>
+      </ul>
+    </div>
+    <div>
+      {{ $t('settings.replies_in_timeline') }}
+      <label
+        for="replyVisibility"
+        class="select"
+        >
+        <select
+          id="replyVisibility"
+          v-model="replyVisibility"
+          >
+          <option
+            value="all"
+            selected
+            >{{ $t('settings.reply_visibility_all') }}</option>
+          <option value="following">{{ $t('settings.reply_visibility_following') }}</option>
+          <option value="self">{{ $t('settings.reply_visibility_self') }}</option>
+        </select>
+        <i class="icon-down-open" />
+      </label>
+    </div>
+    <div>
+      <Checkbox v-model="hidePostStats">
+        {{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }}
+      </Checkbox>
+    </div>
+    <div>
+      <Checkbox v-model="hideUserStats">
+        {{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }}
+      </Checkbox>
+    </div>
+  </div>
+  <div class="setting-item">
+    <div>
+      <p>{{ $t('settings.filtering_explanation') }}</p>
+      <textarea
+        id="muteWords"
+        v-model="muteWordsString"
+        />
+    </div>
+    <div>
+      <Checkbox v-model="hideFilteredStatuses">
+        {{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }}
+      </Checkbox>
+    </div>
+  </div>
+</div>
+</template>
+<script src="./filtering_tab.js"></script>
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
new file mode 100644
index 00000000..82bf6862
--- /dev/null
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -0,0 +1,32 @@
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
+
+import SharedComputedObject from './helpers/shared_computed_object.js'
+
+const GeneralTab = {
+  data () {
+    const instance = this.$store.state.instance
+    return {
+      loopSilentAvailable:
+      // Firefox
+      Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
+      // Chrome-likes
+      Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
+      // Future spec, still not supported in Nightly 63 as of 08/2018
+      Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks'),
+    }
+  },
+  components: {
+    Checkbox,
+    InterfaceLanguageSwitcher
+  },
+  computed: {
+    postFormats () {
+      return this.$store.state.instance.postFormats || []
+    },
+    instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
+    ...SharedComputedObject()
+  }
+}
+
+export default GeneralTab
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
new file mode 100644
index 00000000..0d2da07a
--- /dev/null
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -0,0 +1,272 @@
+<template>
+<div :label="$t('settings.general')">
+  <div class="setting-item">
+    <h2>{{ $t('settings.interface') }}</h2>
+    <ul class="setting-list">
+      <li>
+        <interface-language-switcher />
+      </li>
+      <li v-if="instanceSpecificPanelPresent">
+        <Checkbox v-model="hideISP">
+          {{ $t('settings.hide_isp') }}
+        </Checkbox>
+      </li>
+    </ul>
+  </div>
+  <div class="setting-item">
+    <h2>{{ $t('nav.timeline') }}</h2>
+    <ul class="setting-list">
+      <li>
+        <Checkbox v-model="hideMutedPosts">
+          {{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }}
+        </Checkbox>
+      </li>
+      <li>
+        <Checkbox v-model="collapseMessageWithSubject">
+          {{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }}
+        </Checkbox>
+      </li>
+      <li>
+        <Checkbox v-model="streaming">
+          {{ $t('settings.streaming') }}
+        </Checkbox>
+        <ul
+          class="setting-list suboptions"
+          :class="[{disabled: !streaming}]"
+          >
+          <li>
+            <Checkbox
+              v-model="pauseOnUnfocused"
+              :disabled="!streaming"
+              >
+              {{ $t('settings.pause_on_unfocused') }}
+            </Checkbox>
+          </li>
+        </ul>
+      </li>
+      <li>
+        <Checkbox v-model="useStreamingApi">
+          {{ $t('settings.useStreamingApi') }}
+          <br>
+          <small>
+            {{ $t('settings.useStreamingApiWarning') }}
+          </small>
+        </Checkbox>
+      </li>
+      <li>
+        <Checkbox v-model="autoLoad">
+          {{ $t('settings.autoload') }}
+        </Checkbox>
+      </li>
+      <li>
+        <Checkbox v-model="hoverPreview">
+          {{ $t('settings.reply_link_preview') }}
+        </Checkbox>
+      </li>
+      <li>
+        <Checkbox v-model="emojiReactionsOnTimeline">
+          {{ $t('settings.emoji_reactions_on_timeline') }}
+        </Checkbox>
+      </li>
+    </ul>
+  </div>
+
+  <div class="setting-item">
+    <h2>{{ $t('settings.composing') }}</h2>
+    <ul class="setting-list">
+      <li>
+        <Checkbox v-model="scopeCopy">
+          {{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }}
+        </Checkbox>
+      </li>
+      <li>
+        <Checkbox v-model="alwaysShowSubjectInput">
+          {{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }}
+        </Checkbox>
+      </li>
+      <li>
+        <div>
+          {{ $t('settings.subject_line_behavior') }}
+          <label
+            for="subjectLineBehavior"
+            class="select"
+            >
+            <select
+              id="subjectLineBehavior"
+              v-model="subjectLineBehavior"
+              >
+              <option value="email">
+                {{ $t('settings.subject_line_email') }}
+                {{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }}
+              </option>
+              <option value="masto">
+                {{ $t('settings.subject_line_mastodon') }}
+                {{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
+              </option>
+              <option value="noop">
+                {{ $t('settings.subject_line_noop') }}
+                {{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
+              </option>
+            </select>
+            <i class="icon-down-open" />
+          </label>
+        </div>
+      </li>
+      <li v-if="postFormats.length > 0">
+        <div>
+          {{ $t('settings.post_status_content_type') }}
+          <label
+            for="postContentType"
+            class="select"
+            >
+            <select
+              id="postContentType"
+              v-model="postContentType"
+              >
+              <option
+                v-for="postFormat in postFormats"
+                :key="postFormat"
+                :value="postFormat"
+                >
+                {{ $t(`post_status.content_type["${postFormat}"]`) }}
+                {{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
+              </option>
+            </select>
+            <i class="icon-down-open" />
+          </label>
+        </div>
+      </li>
+      <li>
+        <Checkbox v-model="minimalScopesMode">
+          {{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }}
+        </Checkbox>
+      </li>
+      <li>
+        <Checkbox v-model="autohideFloatingPostButton">
+          {{ $t('settings.autohide_floating_post_button') }}
+        </Checkbox>
+      </li>
+      <li>
+        <Checkbox v-model="padEmoji">
+          {{ $t('settings.pad_emoji') }}
+        </Checkbox>
+      </li>
+    </ul>
+  </div>
+
+  <div class="setting-item">
+      <h2>{{ $t('settings.attachments') }}</h2>
+      <ul class="setting-list">
+        <li>
+          <Checkbox v-model="hideAttachments">
+            {{ $t('settings.hide_attachments_in_tl') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="hideAttachmentsInConv">
+            {{ $t('settings.hide_attachments_in_convo') }}
+          </Checkbox>
+        </li>
+        <li>
+          <label for="maxThumbnails">
+            {{ $t('settings.max_thumbnails') }}
+          </label>
+          <input
+            id="maxThumbnails"
+            v-model.number="maxThumbnails"
+            class="number-input"
+            type="number"
+            min="0"
+            step="1"
+            >
+        </li>
+        <li>
+          <Checkbox v-model="hideNsfw">
+            {{ $t('settings.nsfw_clickthrough') }}
+          </Checkbox>
+        </li>
+        <ul class="setting-list suboptions">
+          <li>
+            <Checkbox
+              v-model="preloadImage"
+              :disabled="!hideNsfw"
+              >
+              {{ $t('settings.preload_images') }}
+            </Checkbox>
+          </li>
+          <li>
+            <Checkbox
+              v-model="useOneClickNsfw"
+              :disabled="!hideNsfw"
+              >
+              {{ $t('settings.use_one_click_nsfw') }}
+            </Checkbox>
+          </li>
+        </ul>
+        <li>
+          <Checkbox v-model="stopGifs">
+            {{ $t('settings.stop_gifs') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="loopVideo">
+            {{ $t('settings.loop_video') }}
+          </Checkbox>
+          <ul
+            class="setting-list suboptions"
+            :class="[{disabled: !streaming}]"
+            >
+            <li>
+              <Checkbox
+                v-model="loopVideoSilentOnly"
+                :disabled="!loopVideo || !loopSilentAvailable"
+                >
+                {{ $t('settings.loop_video_silent_only') }}
+              </Checkbox>
+              <div
+                v-if="!loopSilentAvailable"
+                class="unavailable"
+                >
+                <i class="icon-globe" />! {{ $t('settings.limited_availability') }}
+              </div>
+            </li>
+          </ul>
+        </li>
+        <li>
+          <Checkbox v-model="playVideosInModal">
+            {{ $t('settings.play_videos_in_modal') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="useContainFit">
+            {{ $t('settings.use_contain_fit') }}
+          </Checkbox>
+        </li>
+      </ul>
+    </div>
+
+    <div class="setting-item">
+      <h2>{{ $t('settings.notifications') }}</h2>
+      <ul class="setting-list">
+        <li>
+          <Checkbox v-model="webPushNotifications">
+            {{ $t('settings.enable_web_push_notifications') }}
+          </Checkbox>
+        </li>
+      </ul>
+    </div>
+
+    <div class="setting-item">
+      <h2>{{ $t('settings.fun') }}</h2>
+      <ul class="setting-list">
+        <li>
+          <Checkbox v-model="greentext">
+            {{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
+          </Checkbox>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script src="./general_tab.js"></script>
diff --git a/src/components/settings_modal/tabs/helpers/shared_computed_object.js b/src/components/settings_modal/tabs/helpers/shared_computed_object.js
new file mode 100644
index 00000000..61643e3b
--- /dev/null
+++ b/src/components/settings_modal/tabs/helpers/shared_computed_object.js
@@ -0,0 +1,69 @@
+import { filter, trim } from 'lodash'
+import { instanceDefaultProperties, defaultState as configDefaultState } from 'src/modules/config.js'
+
+const multiChoiceProperties = [
+  'postContentType',
+  'subjectLineBehavior'
+]
+
+const SharedComputedObject = () => ({
+  user () {
+    return this.$store.state.users.currentUser
+  },
+  // Getting localized values for instance-default properties
+  ...instanceDefaultProperties
+    .filter(key => multiChoiceProperties.includes(key))
+    .map(key => [
+      key + 'DefaultValue',
+      function () {
+        return this.$store.getters.instanceDefaultConfig[key]
+      }
+    ])
+    .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
+  ...instanceDefaultProperties
+    .filter(key => !multiChoiceProperties.includes(key))
+    .map(key => [
+      key + 'LocalizedValue',
+      function () {
+        return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key])
+      }
+    ])
+    .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
+  // Generating computed values for vuex properties
+  ...Object.keys(configDefaultState)
+    .map(key => [key, {
+      get () { return this.$store.getters.mergedConfig[key] },
+      set (value) {
+        this.$store.dispatch('setOption', { name: key, value })
+      }
+    }])
+    .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
+  // Special cases (need to transform values or perform actions first)
+  muteWordsString: {
+    get () { return this.$store.getters.mergedConfig.muteWords.join('\n') },
+    set (value) {
+      this.$store.dispatch('setOption', {
+        name: 'muteWords',
+        value: filter(value.split('\n'), (word) => trim(word).length > 0)
+      })
+    }
+  },
+  useStreamingApi: {
+    get () { return this.$store.getters.mergedConfig.useStreamingApi },
+    set (value) {
+      const promise = value
+        ? this.$store.dispatch('enableMastoSockets')
+        : this.$store.dispatch('disableMastoSockets')
+
+      promise.then(() => {
+        this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
+      }).catch((e) => {
+        console.error('Failed starting MastoAPI Streaming socket', e)
+        this.$store.dispatch('disableMastoSockets')
+        this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
+      })
+    }
+  }
+})
+
+export default SharedComputedObject
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
similarity index 83%
rename from src/components/settings_modal/tabs/mutes_and_blocks.js
rename to src/components/settings_modal/tabs/mutes_and_blocks_tab.js
index 51895ddc..3f6b7205 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks.js
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
@@ -1,15 +1,15 @@
 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'
+import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
+import BlockCard from 'src/components/block_card/block_card.vue'
+import MuteCard from 'src/components/mute_card/mute_card.vue'
+import DomainMuteCard from 'src/components/domain_mute_card/domain_mute_card.vue'
+import SelectableList from 'src/components/selectable_list/selectable_list.vue'
+import ProgressButton from 'src/components/progress_button/progress_button.vue'
+import withSubscription from 'src/components/../hocs/with_subscription/with_subscription'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
 
 const BlockList = withSubscription({
   fetch: (props, $store) => $store.dispatch('fetchBlocks'),
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
similarity index 97%
rename from src/components/settings_modal/tabs/mutes_and_blocks.vue
rename to src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
index 3aff47a0..7fce7b78 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks.vue
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
@@ -15,7 +15,7 @@
       </div>
       <BlockList
         :refresh="true"
-        :get-key="identity"
+        :get-key="i => i"
         >
         <template
           slot="header"
@@ -73,7 +73,7 @@
           </div>
           <MuteList
             :refresh="true"
-            :get-key="identity"
+            :get-key="i => i"
             >
             <template
               slot="header"
@@ -134,7 +134,7 @@
           </div>
           <DomainMuteList
             :refresh="true"
-            :get-key="identity"
+            :get-key="i => i"
             >
             <template
               slot="header"
@@ -169,5 +169,5 @@
   </tab-switcher>
 </template>
 
-<script src="./mutes_and_blocks.js"></script>
+<script src="./mutes_and_blocks_tab.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_tab.js
similarity index 80%
rename from src/components/settings_modal/tabs/notifications.js
rename to src/components/settings_modal/tabs/notifications_tab.js
index 0a870b3f..3e44c95d 100644
--- a/src/components/settings_modal/tabs/notifications.js
+++ b/src/components/settings_modal/tabs/notifications_tab.js
@@ -1,6 +1,6 @@
-import Checkbox from '../../checkbox/checkbox.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
 
-const Notifications = {
+const NotificationsTab = {
   data () {
     return {
       activeTab: 'profile',
@@ -24,4 +24,4 @@ const Notifications = {
   }
 }
 
-export default Notifications
+export default NotificationsTab
diff --git a/src/components/settings_modal/tabs/notifications.vue b/src/components/settings_modal/tabs/notifications_tab.vue
similarity index 96%
rename from src/components/settings_modal/tabs/notifications.vue
rename to src/components/settings_modal/tabs/notifications_tab.vue
index f9a7c17b..ab33a6a5 100644
--- a/src/components/settings_modal/tabs/notifications.vue
+++ b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -38,5 +38,5 @@
 </div>
 </template>
 
-<script src="./notifications.js"></script>
+<script src="./notifications_tab.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_tab.js
similarity index 90%
rename from src/components/settings_modal/tabs/profile.js
rename to src/components/settings_modal/tabs/profile_tab.js
index 18c44024..949b480b 100644
--- a/src/components/settings_modal/tabs/profile.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -1,12 +1,12 @@
 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'
+import ImageCropper from 'src/components/image_cropper/image_cropper.vue'
+import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
+import fileSizeFormatService from 'src/components/../services/file_size_format/file_size_format.js'
+import ProgressButton from 'src/components/progress_button/progress_button.vue'
+import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
+import suggestor from 'src/components/emoji_input/suggestor.js'
+import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
 
 const ProfileTab = {
   data () {
diff --git a/src/components/settings_modal/tabs/profile.scss b/src/components/settings_modal/tabs/profile_tab.scss
similarity index 100%
rename from src/components/settings_modal/tabs/profile.scss
rename to src/components/settings_modal/tabs/profile_tab.scss
diff --git a/src/components/settings_modal/tabs/profile.vue b/src/components/settings_modal/tabs/profile_tab.vue
similarity index 98%
rename from src/components/settings_modal/tabs/profile.vue
rename to src/components/settings_modal/tabs/profile_tab.vue
index 335fc12e..9dd89b99 100644
--- a/src/components/settings_modal/tabs/profile.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -209,5 +209,5 @@
 </div>
 </template>
 
-<script src="./profile.js"></script>
-<style lang="scss" src="./profile.scss"></style>
+<script src="./profile_tab.js"></script>
+<style lang="scss" src="./profile_tab.scss"></style>
diff --git a/src/components/user_settings/confirm.js b/src/components/settings_modal/tabs/security_tab/confirm.js
similarity index 100%
rename from src/components/user_settings/confirm.js
rename to src/components/settings_modal/tabs/security_tab/confirm.js
diff --git a/src/components/user_settings/confirm.vue b/src/components/settings_modal/tabs/security_tab/confirm.vue
similarity index 100%
rename from src/components/user_settings/confirm.vue
rename to src/components/settings_modal/tabs/security_tab/confirm.vue
diff --git a/src/components/user_settings/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js
similarity index 100%
rename from src/components/user_settings/mfa.js
rename to src/components/settings_modal/tabs/security_tab/mfa.js
diff --git a/src/components/user_settings/mfa.vue b/src/components/settings_modal/tabs/security_tab/mfa.vue
similarity index 97%
rename from src/components/user_settings/mfa.vue
rename to src/components/settings_modal/tabs/security_tab/mfa.vue
index 14ea10a1..25c4d1dc 100644
--- a/src/components/user_settings/mfa.vue
+++ b/src/components/settings_modal/tabs/security_tab/mfa.vue
@@ -137,11 +137,7 @@
 
 <script src="./mfa.js"></script>
 <style lang="scss">
-@import '../../_variables.scss';
-.warning {
-  color: $fallback--cOrange;
-  color: var(--cOrange, $fallback--cOrange);
-}
+@import '../../../../_variables.scss';
 .mfa-settings {
   .mfa-heading, .method-item {
     overflow: hidden;
@@ -151,6 +147,11 @@
     align-items: baseline;
   }
 
+  .warning {
+    color: $fallback--cOrange;
+    color: var(--cOrange, $fallback--cOrange);
+  }
+
   .setup-otp {
     display: flex;
     justify-content: center;
diff --git a/src/components/user_settings/mfa_backup_codes.js b/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.js
similarity index 100%
rename from src/components/user_settings/mfa_backup_codes.js
rename to src/components/settings_modal/tabs/security_tab/mfa_backup_codes.js
diff --git a/src/components/user_settings/mfa_backup_codes.vue b/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue
similarity index 69%
rename from src/components/user_settings/mfa_backup_codes.vue
rename to src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue
index e6c8ede2..d7e98b3c 100644
--- a/src/components/user_settings/mfa_backup_codes.vue
+++ b/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue
@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div class="mfa-backup-codes">
     <h4 v-if="displayTitle">
       {{ $t('settings.mfa.recovery_codes') }}
     </h4>
@@ -21,13 +21,15 @@
 </template>
 <script src="./mfa_backup_codes.js"></script>
 <style lang="scss">
-@import '../../_variables.scss';
+@import '../../../../_variables.scss';
 
-.warning {
-  color: $fallback--cOrange;
-  color: var(--cOrange, $fallback--cOrange);
-}
-.backup-codes {
-  font-family: var(--postCodeFont, monospace);
+.mfa-backup-codes {
+  .warning {
+    color: $fallback--cOrange;
+    color: var(--cOrange, $fallback--cOrange);
+  }
+  .backup-codes {
+    font-family: var(--postCodeFont, monospace);
+  }
 }
 </style>
diff --git a/src/components/user_settings/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js
similarity index 100%
rename from src/components/user_settings/mfa_totp.js
rename to src/components/settings_modal/tabs/security_tab/mfa_totp.js
diff --git a/src/components/user_settings/mfa_totp.vue b/src/components/settings_modal/tabs/security_tab/mfa_totp.vue
similarity index 100%
rename from src/components/user_settings/mfa_totp.vue
rename to src/components/settings_modal/tabs/security_tab/mfa_totp.vue
diff --git a/src/components/settings_modal/tabs/security.js b/src/components/settings_modal/tabs/security_tab/security_tab.js
similarity index 92%
rename from src/components/settings_modal/tabs/security.js
rename to src/components/settings_modal/tabs/security_tab/security_tab.js
index cc791b7a..811161a5 100644
--- a/src/components/settings_modal/tabs/security.js
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.js
@@ -1,8 +1,8 @@
-import ProgressButton from '../../progress_button/progress_button.vue'
-import Checkbox from '../../checkbox/checkbox.vue'
-import Mfa from '../../user_settings/mfa.vue'
+import ProgressButton from 'src/components/progress_button/progress_button.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+import Mfa from './mfa.vue'
 
-const Security = {
+const SecurityTab = {
   data () {
     return {
       newEmail: '',
@@ -103,4 +103,4 @@ const Security = {
   }
 }
 
-export default Security
+export default SecurityTab
diff --git a/src/components/settings_modal/tabs/security.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue
similarity index 98%
rename from src/components/settings_modal/tabs/security.vue
rename to src/components/settings_modal/tabs/security_tab/security_tab.vue
index 603c9a04..45bacec1 100644
--- a/src/components/settings_modal/tabs/security.vue
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue
@@ -139,5 +139,5 @@
 </div>
 </template>
 
-<script src="./security.js"></script>
+<script src="./security_tab.js"></script>
 <!-- <style lang="scss" src="./profile.scss"></style> -->
diff --git a/src/components/style_switcher/preview.vue b/src/components/settings_modal/tabs/theme_tab/preview.vue
similarity index 100%
rename from src/components/style_switcher/preview.vue
rename to src/components/settings_modal/tabs/theme_tab/preview.vue
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
similarity index 96%
rename from src/components/style_switcher/style_switcher.js
rename to src/components/settings_modal/tabs/theme_tab/theme_tab.js
index a7f586f4..9d61b0c4 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
@@ -3,7 +3,7 @@ import {
   rgb2hex,
   hex2rgb,
   getContrastRatioLayers
-} from '../../services/color_convert/color_convert.js'
+} from 'src/services/color_convert/color_convert.js'
 import {
   DEFAULT_SHADOWS,
   generateColors,
@@ -14,26 +14,27 @@ import {
   getThemes,
   shadows2to3,
   colors2to3
-} from '../../services/style_setter/style_setter.js'
+} from 'src/services/style_setter/style_setter.js'
 import {
   SLOT_INHERITANCE
-} from '../../services/theme_data/pleromafe.js'
+} from 'src/services/theme_data/pleromafe.js'
 import {
   CURRENT_VERSION,
   OPACITIES,
   getLayers,
   getOpacitySlot
-} from '../../services/theme_data/theme_data.service.js'
-import ColorInput from '../color_input/color_input.vue'
-import RangeInput from '../range_input/range_input.vue'
-import OpacityInput from '../opacity_input/opacity_input.vue'
-import ShadowControl from '../shadow_control/shadow_control.vue'
-import FontControl from '../font_control/font_control.vue'
-import ContrastRatio from '../contrast_ratio/contrast_ratio.vue'
-import TabSwitcher from '../tab_switcher/tab_switcher.js'
+} from 'src/services/theme_data/theme_data.service.js'
+import ColorInput from 'src/components/color_input/color_input.vue'
+import RangeInput from 'src/components/range_input/range_input.vue'
+import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
+import ShadowControl from 'src/components/shadow_control/shadow_control.vue'
+import FontControl from 'src/components/font_control/font_control.vue'
+import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
+import ExportImport from 'src/components/export_import/export_import.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+
 import Preview from './preview.vue'
-import ExportImport from '../export_import/export_import.vue'
-import Checkbox from '../checkbox/checkbox.vue'
 
 // List of color values used in v1
 const v1OnlyNames = [
diff --git a/src/components/style_switcher/style_switcher.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
similarity index 97%
rename from src/components/style_switcher/style_switcher.scss
rename to src/components/settings_modal/tabs/theme_tab/theme_tab.scss
index d2a40d13..75b3017d 100644
--- a/src/components/style_switcher/style_switcher.scss
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
@@ -1,5 +1,5 @@
-@import '../../_variables.scss';
-.style-switcher {
+@import 'src/_variables.scss';
+.theme-tab {
   .theme-warning {
     display: flex;
     align-items: baseline;
@@ -54,10 +54,6 @@
     }
   }
 
-  .tab-switcher {
-    margin: 0 -1em;
-  }
-
   .reset-container {
     flex-wrap: wrap;
   }
@@ -161,7 +157,7 @@
     border-bottom: 1px dashed;
     border-color: $fallback--border;
     border-color: var(--border, $fallback--border);
-    margin: 1em -1em 0;
+    margin: 1em 0;
     padding: 1em;
     background: var(--body-background-image);
     background-size: cover;
@@ -328,6 +324,14 @@
     padding: 20px;
   }
 
+  .apply-container {
+    .btn {
+      min-height: 28px;
+      min-width: 10em;
+      padding: 0 2em;
+    }
+  }
+
   .btn {
     margin-left: .25em;
     margin-right: .25em;
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
similarity index 92%
rename from src/components/style_switcher/style_switcher.vue
rename to src/components/settings_modal/tabs/theme_tab/theme_tab.vue
index 62c8e634..6f6cf1d6 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
@@ -1,90 +1,90 @@
 <template>
-  <div class="style-switcher">
-    <div class="presets-container">
-      <div class="save-load">
-        <div
-          v-if="themeWarning"
-          class="theme-warning"
+<div class="theme-tab">
+  <div class="presets-container">
+    <div class="save-load">
+      <div
+        v-if="themeWarning"
+        class="theme-warning"
         >
-          <div class="alert warning">
-            {{ themeWarningHelp }}
-          </div>
-          <div class="buttons">
-            <template v-if="themeWarning.type === 'snapshot_source_mismatch'">
-              <button
-                class="btn"
-                @click="forceLoad"
-              >
-                {{ $t('settings.style.switcher.use_source') }}
-              </button>
-              <button
-                class="btn"
-                @click="forceSnapshot"
-              >
-                {{ $t('settings.style.switcher.use_snapshot') }}
-              </button>
-            </template>
-            <template v-else-if="themeWarning.noActionsPossible">
-              <button
-                class="btn"
-                @click="dismissWarning"
-              >
-                {{ $t('general.dismiss') }}
-              </button>
-            </template>
-            <template v-else>
-              <button
-                class="btn"
-                @click="forceLoad"
-              >
-                {{ $t('settings.style.switcher.load_theme') }}
-              </button>
-              <button
-                class="btn"
-                @click="dismissWarning"
-              >
-                {{ $t('settings.style.switcher.keep_as_is') }}
-              </button>
-            </template>
-          </div>
+        <div class="alert warning">
+          {{ themeWarningHelp }}
         </div>
-        <ExportImport
-          :export-object="exportedTheme"
-          :export-label="$t(&quot;settings.export_theme&quot;)"
-          :import-label="$t(&quot;settings.import_theme&quot;)"
-          :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
-          :on-import="onImport"
-          :validator="importValidator"
-        >
-          <template slot="before">
-            <div class="presets">
-              {{ $t('settings.presets') }}
-              <label
-                for="preset-switcher"
-                class="select"
+        <div class="buttons">
+          <template v-if="themeWarning.type === 'snapshot_source_mismatch'">
+            <button
+              class="btn"
+              @click="forceLoad"
               >
-                <select
-                  id="preset-switcher"
-                  v-model="selected"
-                  class="preset-switcher"
-                >
-                  <option
-                    v-for="style in availableStyles"
-                    :key="style.name"
-                    :value="style"
-                    :style="{
-                      backgroundColor: style[1] || (style.theme || style.source).colors.bg,
-                      color: style[3] || (style.theme || style.source).colors.text
-                    }"
-                  >
-                    {{ style[0] || style.name }}
-                  </option>
-                </select>
-                <i class="icon-down-open" />
-              </label>
-            </div>
+              {{ $t('settings.style.switcher.use_source') }}
+            </button>
+            <button
+              class="btn"
+              @click="forceSnapshot"
+              >
+              {{ $t('settings.style.switcher.use_snapshot') }}
+            </button>
           </template>
-        </ExportImport>
+          <template v-else-if="themeWarning.noActionsPossible">
+            <button
+              class="btn"
+              @click="dismissWarning"
+              >
+              {{ $t('general.dismiss') }}
+            </button>
+          </template>
+          <template v-else>
+            <button
+              class="btn"
+              @click="forceLoad"
+              >
+              {{ $t('settings.style.switcher.load_theme') }}
+            </button>
+            <button
+              class="btn"
+              @click="dismissWarning"
+              >
+              {{ $t('settings.style.switcher.keep_as_is') }}
+            </button>
+          </template>
+        </div>
+      </div>
+      <ExportImport
+        :export-object="exportedTheme"
+        :export-label="$t(&quot;settings.export_theme&quot;)"
+        :import-label="$t(&quot;settings.import_theme&quot;)"
+        :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
+        :on-import="onImport"
+        :validator="importValidator"
+        >
+        <template slot="before">
+          <div class="presets">
+            {{ $t('settings.presets') }}
+            <label
+              for="preset-switcher"
+              class="select"
+              >
+              <select
+                id="preset-switcher"
+                v-model="selected"
+                class="preset-switcher"
+                >
+                <option
+                  v-for="style in availableStyles"
+                  :key="style.name"
+                  :value="style"
+                  :style="{
+                          backgroundColor: style[1] || (style.theme || style.source).colors.bg,
+                          color: style[3] || (style.theme || style.source).colors.text
+                          }"
+                  >
+                  {{ style[0] || style.name }}
+                </option>
+              </select>
+              <i class="icon-down-open" />
+            </label>
+          </div>
+        </template>
+      </ExportImport>
       </div>
       <div class="save-load-options">
         <span class="keep-option">
@@ -951,6 +951,6 @@
   </div>
 </template>
 
-<script src="./style_switcher.js"></script>
+<script src="./theme_tab.js"></script>
 
-<style src="./style_switcher.scss" lang="scss"></style>
+<style src="./theme_tab.scss" lang="scss"></style>
diff --git a/src/components/settings_modal/tabs/version_tab.js b/src/components/settings_modal/tabs/version_tab.js
new file mode 100644
index 00000000..616bdadf
--- /dev/null
+++ b/src/components/settings_modal/tabs/version_tab.js
@@ -0,0 +1,24 @@
+import { extractCommit } from 'src/services/version/version.service'
+
+const pleromaFeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
+const pleromaBeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma/commit/'
+
+const VersionTab = {
+  data () {
+    const instance = this.$store.state.instance
+    return {
+      backendVersion: instance.backendVersion,
+      frontendVersion: instance.frontendVersion
+    }
+  },
+  computed: {
+    frontendVersionLink () {
+      return pleromaFeCommitUrl + this.frontendVersion
+    },
+    backendVersionLink () {
+      return pleromaBeCommitUrl + extractCommit(this.backendVersion)
+    }
+  }
+}
+
+export default VersionTab
diff --git a/src/components/settings_modal/tabs/version_tab.vue b/src/components/settings_modal/tabs/version_tab.vue
new file mode 100644
index 00000000..acc43569
--- /dev/null
+++ b/src/components/settings_modal/tabs/version_tab.vue
@@ -0,0 +1,31 @@
+<template>
+<div :label="$t('settings.version.title')">
+  <div class="setting-item">
+    <ul class="setting-list">
+      <li>
+        <p>{{ $t('settings.version.backend_version') }}</p>
+        <ul class="option-list">
+          <li>
+            <a
+              :href="backendVersionLink"
+              target="_blank"
+              >{{ backendVersion }}</a>
+          </li>
+        </ul>
+      </li>
+      <li>
+        <p>{{ $t('settings.version.frontend_version') }}</p>
+        <ul class="option-list">
+          <li>
+            <a
+              :href="frontendVersionLink"
+              target="_blank"
+              >{{ frontendVersion }}</a>
+          </li>
+        </ul>
+      </li>
+    </ul>
+  </div>
+</div>
+</template>
+<script src="./version_tab.js">
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index a443531e..a7b790a3 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -46,8 +46,14 @@
 
   &.side-tabs {
     flex-direction: row;
+    @media all and (max-width: 800px) {
+      overflow-x: auto;
+    }
     > .contents {
       flex: 0 1 80%;
+      @media all and (max-width: 800px) {
+        min-width: 96vw;
+      }
     }
     > .tabs {
       flex: 1 0 auto;
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 4ee040e8..b4f275d9 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -50,15 +50,6 @@
               >
                 {{ user.name }}
               </div>
-              <router-link
-                v-if="!isOtherUser"
-                :to="{ name: 'user-settings' }"
-              >
-                <i
-                  class="button-icon icon-wrench usersettings"
-                  :title="$t('tool_tip.user_settings')"
-                />
-              </router-link>
               <a
                 v-if="isOtherUser && !user.is_local"
                 :href="user.statusnet_profile_url"
@@ -117,7 +108,7 @@
               type="color"
             >
             <label
-              for="style-switcher"
+              for="theme_tab"
               class="userHighlightSel select"
             >
               <select
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
deleted file mode 100644
index e07d4e56..00000000
--- a/src/components/user_settings/user_settings.js
+++ /dev/null
@@ -1,140 +0,0 @@
-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 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'
-
-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 UserSettings = {
-  data () {
-    return {
-      activeTab: 'profile',
-      newDomainToMute: ''
-    }
-  },
-  created () {
-    this.$store.dispatch('fetchTokens')
-  },
-  components: {
-    TabSwitcher,
-    BlockList,
-    MuteList,
-    DomainMuteList,
-    BlockCard,
-    MuteCard,
-    DomainMuteCard,
-    ProgressButton,
-    Autosuggest,
-    Checkbox
-  },
-  computed: {
-    user () {
-      return this.$store.state.users.currentUser
-    },
-    pleromaBackend () {
-      return this.$store.state.instance.pleromaBackend
-    },
-    currentSaveStateNotice () {
-      return this.$store.state.interface.settings.currentSaveStateNotice
-    }
-  },
-  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 = '' })
-    },
-    identity (value) {
-      return value
-    }
-  }
-}
-
-export default UserSettings
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
deleted file mode 100644
index 2a88714f..00000000
--- a/src/components/user_settings/user_settings.vue
+++ /dev/null
@@ -1,119 +0,0 @@
-<template>
-  <div class="settings panel panel-default">
-    <div class="panel-heading">
-      <div class="title">
-        {{ $t('settings.user_settings') }}
-      </div>
-      <transition name="fade">
-        <template v-if="currentSaveStateNotice">
-          <div
-            v-if="currentSaveStateNotice.error"
-            class="alert error"
-            @click.prevent
-          >
-            {{ $t('settings.saving_err') }}
-          </div>
-
-          <div
-            v-if="!currentSaveStateNotice.error"
-            class="alert transparent"
-            @click.prevent
-          >
-            {{ $t('settings.saving_ok') }}
-          </div>
-        </template>
-      </transition>
-    </div>
-    <div class="panel-body profile-edit">
-    </div>
-  </div>
-</template>
-
-<script src="./user_settings.js">
-</script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-
-.profile-edit {
-  .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;
-  }
-}
-</style>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 312f7283..d42be00f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -68,7 +68,9 @@
     "disable": "Disable",
     "enable": "Enable",
     "confirm": "Confirm",
-    "verify": "Verify"
+    "verify": "Verify",
+    "close": "Close",
+    "peek": "Peek"
   },
   "image_cropper": {
     "crop_picture": "Crop picture",

From c412856716d0b16f9e3aac57a4bb6dad3755c9ae Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
 <contact+translate.pleroma.social@hacktivis.me>
Date: Mon, 11 May 2020 22:33:44 +0000
Subject: [PATCH 324/483] Translated using Weblate (French)

Currently translated at 97.3% (597 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: http://translate.pleroma.social/projects/pleroma/pleroma-fe/fr/
---
 src/i18n/fr.json | 215 +++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 197 insertions(+), 18 deletions(-)

diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 5f0053d5..61877a3a 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -79,7 +79,9 @@
         "twkn": "Ensemble du réseau connu",
         "user_search": "Recherche d'utilisateur·ice",
         "who_to_follow": "Qui suivre",
-        "preferences": "Préférences"
+        "preferences": "Préférences",
+        "search": "Recherche",
+        "administration": "Administration"
     },
     "notifications": {
         "broken_favorite": "Chargement d'un message inconnu…",
@@ -89,12 +91,16 @@
         "notifications": "Notifications",
         "read": "Lu !",
         "repeated_you": "a partagé votre statut",
-        "no_more_notifications": "Aucune notification supplémentaire"
+        "no_more_notifications": "Aucune notification supplémentaire",
+        "migrated_to": "a migré à",
+        "reacted_with": "a réagi avec {0}",
+        "follow_request": "veut vous suivre"
     },
     "interactions": {
         "favs_repeats": "Partages et favoris",
-        "follows": "Nouveaux⋅elles abonné⋅e⋅s ?",
-        "load_older": "Chargez d'anciennes interactions"
+        "follows": "Nouveaux suivis",
+        "load_older": "Chargez d'anciennes interactions",
+        "moves": "Migrations de comptes"
     },
     "post_status": {
         "new_status": "Poster un nouveau statut",
@@ -170,7 +176,7 @@
                 "secret_code": "Clé"
             },
             "verify": {
-                "desc": "Pour activer la double authentification, entrez le code depuis votre application:"
+                "desc": "Pour activer la double authentification, entrez le code depuis votre application :"
             }
         },
         "attachmentRadius": "Pièces jointes",
@@ -185,7 +191,7 @@
         "block_export_button": "Export des comptes bloqués vers un fichier csv",
         "block_import": "Import des comptes bloqués",
         "block_import_error": "Erreur lors de l'import des comptes bloqués",
-        "blocks_imported": "Blocks importés! Le traitement va prendre un moment.",
+        "blocks_imported": "Blocks importés ! Le traitement va prendre un moment.",
         "blocks_tab": "Bloqué·e·s",
         "btnRadius": "Boutons",
         "cBlue": "Bleu (répondre, suivre)",
@@ -233,7 +239,7 @@
         "import_theme": "Charger le thème",
         "inputRadius": "Champs de texte",
         "checkboxRadius": "Cases à cocher",
-        "instance_default": "(default: {value})",
+        "instance_default": "(default : {value})",
         "instance_default_simple": "(default)",
         "interface": "Interface",
         "interfaceLanguage": "Langue de l'interface",
@@ -264,7 +270,7 @@
         "nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible",
         "oauth_tokens": "Jetons OAuth",
         "token": "Jeton",
-        "refresh_token": "Refresh Token",
+        "refresh_token": "Rafraichir le jeton",
         "valid_until": "Valable jusque",
         "revoke_token": "Révoquer",
         "panelRadius": "Fenêtres",
@@ -293,8 +299,8 @@
         "settings": "Paramètres",
         "subject_input_always_show": "Toujours copier le champ de sujet",
         "subject_line_behavior": "Copier le sujet en répondant",
-        "subject_line_email": "Comme les mails: « re: sujet »",
-        "subject_line_mastodon": "Comme mastodon: copier tel quel",
+        "subject_line_email": "Similaire au courriel : « re : sujet »",
+        "subject_line_mastodon": "Comme mastodon : copier tel quel",
         "subject_line_noop": "Ne pas copier",
         "post_status_content_type": "Type de contenu du statuts",
         "stop_gifs": "N'animer les GIFS que lors du survol du curseur de la souris",
@@ -312,7 +318,7 @@
             "true": "oui"
         },
         "notifications": "Notifications",
-        "notification_setting": "Reçevoir les notifications de:",
+        "notification_setting": "Reçevoir les notifications de :",
         "notification_setting_follows": "Utilisateurs que vous suivez",
         "notification_setting_non_follows": "Utilisateurs que vous ne suivez pas",
         "notification_setting_followers": "Utilisateurs qui vous suivent",
@@ -330,7 +336,17 @@
                 "save_load_hint": "L'option « Garder » préserve les options activés en cours lors de la séléction ou chargement des thèmes, il sauve aussi les dites options lors de l'export d'un thème. Quand toutes les cases sont décochés, exporter un thème sauvera tout.",
                 "reset": "Remise à zéro",
                 "clear_all": "Tout vider",
-                "clear_opacity": "Vider la transparence"
+                "clear_opacity": "Vider la transparence",
+                "load_theme": "Charger le thème",
+                "use_snapshot": "Ancienne version",
+                "help": {
+                    "upgraded_from_v2": "PleromaFE à été mis à jour, le thème peut être un peu différent que dans vos souvenirs.",
+                    "v2_imported": "Le fichier que vous avez importé vient d'un version antérieure. Nous essayons de maximizer la compatibilité mais il peu y avoir quelques incohérences.",
+                    "future_version_imported": "Le fichier importé viens d'une version postérieure de PleromaFE.",
+                    "older_version_imported": "Le fichier importé viens d'une version antérieure de PleromaFE."
+                },
+                "keep_as_is": "Garder tel-quel",
+                "use_source": "Nouvelle version"
             },
             "common": {
                 "color": "Couleur",
@@ -365,7 +381,18 @@
                 "borders": "Bordures",
                 "buttons": "Boutons",
                 "inputs": "Champs de saisie",
-                "faint_text": "Texte en fondu"
+                "faint_text": "Texte en fondu",
+                "underlay": "sous-calque",
+                "pressed": "Appuyé",
+                "alert_warning": "Avertissement",
+                "alert_neutral": "Neutre",
+                "post": "Messages/Bios des comptes",
+                "poll": "Graphique de Sondage",
+                "icons": "Icônes",
+                "selectedPost": "Message sélectionné",
+                "selectedMenu": "Objet sélectionné du menu",
+                "disabled": "Désactivé",
+                "tabs": "Onglets"
             },
             "radii": {
                 "_tab_label": "Rondeur"
@@ -398,7 +425,8 @@
                     "buttonPressed": "Bouton (cliqué)",
                     "buttonPressedHover": "Bouton (cliqué+survol)",
                     "input": "Champ de saisie"
-                }
+                },
+                "hintV3": "Pour les ombres vous pouvez aussi utiliser la notation {0} pour utiliser un autre emplacement de couleur."
             },
             "fonts": {
                 "_tab_label": "Polices",
@@ -433,7 +461,28 @@
             "title": "Version",
             "backend_version": "Version du Backend",
             "frontend_version": "Version du Frontend"
-        }
+        },
+        "change_email": "Changer de courriel",
+        "domain_mutes": "Domaines",
+        "pad_emoji": "Rajouter un espace autour de l'émoji après l’avoir choisit",
+        "notification_visibility_emoji_reactions": "Réactions",
+        "hide_follows_count_description": "Masquer le nombre de suivis",
+        "useStreamingApiWarning": "(Non recommandé, expérimental, connu pour rater des messages)",
+        "type_domains_to_mute": "Écrire les domaines à masquer",
+        "fun": "Rigolo",
+        "greentext": "greentexting",
+        "allow_following_move": "Suivre automatiquement quand ce compte migre",
+        "change_email_error": "Il y a eu un problème pour charger votre courriel.",
+        "changed_email": "Courriel changé avec succès !",
+        "discoverable": "Permettre de découvrir ce compte dans les résultats de recherche web et autres services",
+        "emoji_reactions_on_timeline": "Montrer les émojis-réactions dans le flux",
+        "new_email": "Nouveau courriel",
+        "notification_visibility_moves": "Migrations de compte",
+        "user_mutes": "Comptes",
+        "useStreamingApi": "Recevoir les messages et notifications en temps réel",
+        "notification_setting_filters": "Filtres",
+        "notification_setting_privacy_option": "Masquer l'expéditeur et le contenu des notifications push",
+        "notification_setting_privacy": "Intimité"
     },
     "timeline": {
         "collapse": "Fermer",
@@ -456,7 +505,11 @@
         "pinned": "Agraffé",
         "delete_confirm": "Voulez-vous vraiment supprimer ce statuts ?",
         "reply_to": "Réponse à",
-        "replies_list": "Réponses:"
+        "replies_list": "Réponses :",
+        "mute_conversation": "Masquer la conversation",
+        "unmute_conversation": "Démasquer la conversation",
+        "status_unavailable": "Status indisponible",
+        "copy_link": "Copier le lien au status"
     },
     "user_card": {
         "approve": "Accepter",
@@ -505,7 +558,13 @@
             "quarantine": "Interdir les statuts de l'utilisateur à fédérer",
             "delete_user": "Supprimer l'utilisateur",
             "delete_user_confirmation": "Êtes-vous absolument-sûr⋅e ? Cette action ne peut être annulée."
-        }
+        },
+        "mention": "Mention",
+        "hidden": "Caché",
+        "subscribe": "Abonner",
+        "unsubscribe": "Désabonner",
+        "hide_repeats": "Cacher les partages",
+        "show_repeats": "Montrer les partages"
     },
     "user_profile": {
         "timeline_title": "Journal de l'utilisateur⋅ice",
@@ -530,7 +589,10 @@
         "repeat": "Répéter",
         "reply": "Répondre",
         "favorite": "Favoriser",
-        "user_settings": "Paramètres utilisateur"
+        "user_settings": "Paramètres utilisateur",
+        "add_reaction": "Ajouter une réaction",
+        "accept_follow_request": "Accepter la demande de suivit",
+        "reject_follow_request": "Rejeter la demande de suivit"
     },
     "upload": {
         "error": {
@@ -545,5 +607,122 @@
             "GiB": "GiO",
             "TiB": "TiO"
         }
+    },
+    "about": {
+        "mrf": {
+            "keyword": {
+                "reject": "Rejeté",
+                "replace": "Remplacer",
+                "keyword_policies": "Politiques par mot-clés",
+                "ftl_removal": "Suppression du flux \"Ensemble du réseau connu\"",
+                "is_replaced_by": "→"
+            },
+            "simple": {
+                "simple_policies": "Politiques par instances",
+                "accept": "Accepter",
+                "accept_desc": "Cette instance accepte des messages seulement depuis ces instances :",
+                "reject": "Rejeter",
+                "reject_desc": "Cette instance n'acceptera pas de message de ces instances :",
+                "quarantine": "Quarantaine",
+                "quarantine_desc": "Cette instance enverras seulement des messages publics à ces instances :",
+                "ftl_removal_desc": "Cette instance supprime ces instance du flux fédéré :",
+                "media_removal": "Suppression multimédia",
+                "media_removal_desc": "Cette instance supprime le contenu multimédia des instances suivantes :",
+                "media_nsfw": "Force le contenu multimédia comme sensible",
+                "ftl_removal": "Suppression du flux fédéré",
+                "media_nsfw_desc": "Cette instance force le contenu multimédia comme sensible pour les messages des instances suivantes :"
+            },
+            "federation": "Fédération",
+            "mrf_policies": "Politiques MRF activées",
+            "mrf_policies_desc": "Les politiques MRF modifient la fédération entre les instances. Les politiques suivantes sont activées :"
+        },
+        "staff": "Staff"
+    },
+    "domain_mute_card": {
+        "mute": "Muet",
+        "mute_progress": "Masquage…",
+        "unmute": "Démasquer",
+        "unmute_progress": "Démasquage…"
+    },
+    "polls": {
+        "add_poll": "Ajouter un Sondage",
+        "add_option": "Ajouter une option",
+        "option": "Option",
+        "votes": "votes",
+        "type": "Type de Sondage",
+        "single_choice": "Choix unique",
+        "multiple_choices": "Choix multiples",
+        "expiry": "Age du sondage",
+        "expires_in": "Fin du sondage dans {0}",
+        "not_enough_options": "Trop peu d'options unique au sondage",
+        "vote": "Voter",
+        "expired": "Sondage terminé il y a {0}"
+    },
+    "emoji": {
+        "emoji": "Émoji",
+        "search_emoji": "Rechercher un émoji",
+        "add_emoji": "Insérer un émoji",
+        "custom": "émoji personnalisé",
+        "unicode": "émoji unicode",
+        "load_all": "Charger tout les {emojiAmount} émojis",
+        "load_all_hint": "{saneAmount} émojis chargé, charger tout les émojis peuvent causer des problèmes de performances.",
+        "stickers": "Stickers"
+    },
+    "remote_user_resolver": {
+        "error": "Non trouvé."
+    },
+    "time": {
+        "minutes_short": "{0}min",
+        "second_short": "{0}s",
+        "day": "{0} jour",
+        "days": "{0} jours",
+        "months": "{0} mois",
+        "month_short": "{0}m",
+        "months_short": "{0}m",
+        "now": "tout de suite",
+        "now_short": "maintenant",
+        "second": "{0} seconde",
+        "seconds": "{0} secondes",
+        "seconds_short": "{0}s",
+        "day_short": "{0}j",
+        "days_short": "{0}j",
+        "hour": "{0} heure",
+        "hours": "{0} heures",
+        "hour_short": "{0}h",
+        "hours_short": "{0}h",
+        "in_future": "dans {0}",
+        "in_past": "il y a {0}",
+        "minute": "{0} minute",
+        "minutes": "{0} minutes",
+        "minute_short": "{0}min",
+        "month": "{0} mois",
+        "week": "{0} semaine",
+        "weeks": "{0} semaines",
+        "week_short": "{0}s",
+        "weeks_short": "{0}s",
+        "year": "{0} année",
+        "years": "{0} années",
+        "year_short": "{0}a",
+        "years_short": "{0}a"
+    },
+    "search": {
+        "people": "Comptes",
+        "person_talking": "{count} personnes discutant",
+        "hashtags": "Mot-dièses",
+        "people_talking": "{count} personnes discutant",
+        "no_results": "Aucun résultats"
+    },
+    "password_reset": {
+        "forgot_password": "Mot de passe oublié ?",
+        "check_email": "Vérifiez vos courriels pour le lien permettant de changer votre mot de passe.",
+        "password_reset_disabled": "Le changement de mot de passe est désactivé. Veuillez contacter l'administration de votre instance.",
+        "password_reset_required_but_mailer_is_disabled": "Vous devez changer votre mot de passe mais sont changement est désactivé. Veuillez contacter l’administration de votre instance.",
+        "password_reset": "Nouveau mot de passe",
+        "instruction": "Entrer votre address de courriel ou votre nom utilisateur. Nous enverrons un lien pour changer votre mot de passe.",
+        "placeholder": "Votre email ou nom d'utilisateur",
+        "return_home": "Retourner à la page d'accueil",
+        "not_found": "Email ou nom d'utilisateur inconnu.",
+        "too_many_requests": "Vos avez atteint la limite d'essais, essayez plus tard.",
+        "password_reset_required": "Vous devez changer votre mot de passe pour vous authentifier."
     }
 }

From 26bcfea727d2fa8134fcc7e1f250695a5ffb886d Mon Sep 17 00:00:00 2001
From: Egor <egor@kislitsyn.com>
Date: Tue, 12 May 2020 11:39:08 +0000
Subject: [PATCH 325/483] Translated using Weblate (Russian)

Currently translated at 53.8% (330 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
---
 src/i18n/ru.json | 816 ++++++++++++++++++++++++-----------------------
 1 file changed, 412 insertions(+), 404 deletions(-)

diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 4cb2d497..afaba444 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -1,414 +1,422 @@
 {
-  "chat": {
-    "title": "Чат"
-  },
-  "finder": {
-    "error_fetching_user": "Пользователь не найден",
-    "find_user": "Найти пользователя"
-  },
-  "general": {
-    "apply": "Применить",
-    "submit": "Отправить",
-    "cancel": "Отмена",
-    "disable": "Оключить",
-    "enable": "Включить",
-    "confirm": "Подтвердить",
-    "verify": "Проверить"
-  },
-  "login": {
-    "login": "Войти",
-    "logout": "Выйти",
-    "password": "Пароль",
-    "placeholder": "e.c. lain",
-    "register": "Зарегистрироваться",
-    "username": "Имя пользователя",
-    "authentication_code": "Код аутентификации",
-    "enter_recovery_code": "Ввести код восстановления",
-    "enter_two_factor_code": "Ввести код аутентификации",
-    "recovery_code": "Код восстановления",
-    "heading" : {
-      "TotpForm" : "Двухфакторная аутентификация",
-      "RecoveryForm" : "Two-factor recovery"
-    }
-  },
-  "nav": {
-    "back": "Назад",
-    "chat": "Локальный чат",
-    "mentions": "Упоминания",
-    "interactions": "Взаимодействия",
-    "public_tl": "Публичная лента",
-    "timeline": "Лента",
-    "twkn": "Федеративная лента",
-    "search": "Поиск"
-  },
-  "notifications": {
-    "broken_favorite": "Неизвестный статус, ищем...",
-    "favorited_you": "нравится ваш статус",
-    "followed_you": "начал(а) читать вас",
-    "load_older": "Загрузить старые уведомления",
-    "notifications": "Уведомления",
-    "read": "Прочесть",
-    "repeated_you": "повторил(а) ваш статус"
-  },
-  "interactions": {
-    "favs_repeats": "Повторы и фавориты",
-    "follows": "Новые подписки",
-    "load_older": "Загрузить старые взаимодействия"
-  },
-  "post_status": {
-    "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков",
-    "account_not_locked_warning_link": "залочен",
-    "attachments_sensitive": "Вложения содержат чувствительный контент",
-    "content_warning": "Тема (не обязательно)",
-    "default": "Что нового?",
-    "direct_warning": "Этот пост будет виден только упомянутым пользователям",
-    "posting": "Отправляется",
-    "scope_notice": {
-      "public": "Этот пост будет виден всем",
-      "private": "Этот пост будет виден только вашим подписчикам",
-      "unlisted": "Этот пост не будет виден в публичной и федеративной ленте"
+    "chat": {
+        "title": "Чат"
     },
-    "scope": {
-      "direct": "Личное - этот пост видят только те кто в нём упомянут",
-      "private": "Для подписчиков - этот пост видят только подписчики",
-      "public": "Публичный - этот пост виден всем",
-      "unlisted": "Непубличный - этот пост не виден на публичных лентах"
-    }
-  },
-  "registration": {
-    "bio": "Описание",
-    "email": "Email",
-    "fullname": "Отображаемое имя",
-    "password_confirm": "Подтверждение пароля",
-    "registration": "Регистрация",
-    "token": "Код приглашения",
-    "validations": {
-      "username_required": "не должно быть пустым",
-      "fullname_required": "не должно быть пустым",
-      "email_required": "не должен быть пустым",
-      "password_required": "не должен быть пустым",
-      "password_confirmation_required": "не должно быть пустым",
-      "password_confirmation_match": "должно совпадать с паролем"
-    }
-  },
-  "settings": {
-    "enter_current_password_to_confirm": "Введите свой текущий пароль",
-    "mfa": {
-      "otp" : "OTP",
-      "setup_otp" : "Настройка OTP",
-      "wait_pre_setup_otp" : "предварительная настройка OTP",
-      "confirm_and_enable" : "Подтвердить и включить OTP",
-      "title": "Двухфакторная аутентификация",
-      "generate_new_recovery_codes" : "Получить новые коды востановления",
-      "warning_of_generate_new_codes" : "После получения новых кодов восстановления, старые больше не будут работать.",
-      "recovery_codes" : "Коды восстановления.",
-      "waiting_a_recovery_codes": "Получение кодов восстановления ...",
-      "recovery_codes_warning" : "Запишите эти коды и держите в безопасном месте - иначе вы их больше не увидите. Если вы потеряете доступ к OTP приложению - без резервных кодов вы больше не сможете залогиниться.",
-      "authentication_methods" : "Методы аутентификации",
-      "scan": {
-        "title": "Сканирование",
-        "desc": "Используйте приложение для двухэтапной аутентификации для сканирования этого QR-код или введите текстовый ключ:",
-        "secret_code": "Ключ"
-      },
-      "verify": {
-        "desc": "Чтобы включить двухэтапную аутентификации, введите код из вашего приложение для двухэтапной аутентификации:"
-      }
+    "finder": {
+        "error_fetching_user": "Пользователь не найден",
+        "find_user": "Найти пользователя"
     },
-    "attachmentRadius": "Прикреплённые файлы",
-    "attachments": "Вложения",
-    "autoload": "Включить автоматическую загрузку при прокрутке вниз",
-    "avatar": "Аватар",
-    "avatarAltRadius": "Аватары в уведомлениях",
-    "avatarRadius": "Аватары",
-    "background": "Фон",
-    "bio": "Описание",
-    "btnRadius": "Кнопки",
-    "cBlue": "Ответить, читать",
-    "cGreen": "Повторить",
-    "cOrange": "Нравится",
-    "cRed": "Отменить",
-    "change_email": "Сменить email",
-    "change_email_error": "Произошла ошибка при попытке изменить email.",
-    "changed_email": "Email изменён успешно.",
-    "change_password": "Сменить пароль",
-    "change_password_error": "Произошла ошибка при попытке изменить пароль.",
-    "changed_password": "Пароль изменён успешно.",
-    "collapse_subject": "Сворачивать посты с темой",
-    "confirm_new_password": "Подтверждение нового пароля",
-    "current_avatar": "Текущий аватар",
-    "current_password": "Текущий пароль",
-    "current_profile_banner": "Текущий баннер профиля",
-    "data_import_export_tab": "Импорт / Экспорт данных",
-    "delete_account": "Удалить аккаунт",
-    "delete_account_description": "Удалить ваш аккаунт и все ваши сообщения.",
-    "delete_account_error": "Возникла ошибка в процессе удаления вашего аккаунта. Если это повторяется, свяжитесь с администратором вашего сервера.",
-    "delete_account_instructions": "Введите ваш пароль в поле ниже для подтверждения удаления.",
-    "export_theme": "Сохранить Тему",
-    "filtering": "Фильтрация",
-    "filtering_explanation": "Все статусы, содержащие данные слова, будут игнорироваться, по одному в строке",
-    "follow_export": "Экспортировать читаемых",
-    "follow_export_button": "Экспортировать читаемых в файл .csv",
-    "follow_export_processing": "Ведётся обработка, скоро вам будет предложено загрузить файл",
-    "follow_import": "Импортировать читаемых",
-    "follow_import_error": "Ошибка при импортировании читаемых.",
-    "follows_imported": "Список читаемых импортирован. Обработка займёт некоторое время..",
-    "foreground": "Передний план",
-    "general": "Общие",
-    "hide_attachments_in_convo": "Прятать вложения в разговорах",
-    "hide_attachments_in_tl": "Прятать вложения в ленте",
-    "hide_isp": "Скрыть серверную панель",
-    "import_followers_from_a_csv_file": "Импортировать читаемых из файла .csv",
-    "import_theme": "Загрузить Тему",
-    "inputRadius": "Поля ввода",
-    "checkboxRadius": "Чекбоксы",
-    "instance_default": "(по умолчанию: {value})",
-    "instance_default_simple": "(по умолчанию)",
-    "interface": "Интерфейс",
-    "interfaceLanguage": "Язык интерфейса",
-    "limited_availability": "Не доступно в вашем браузере",
-    "links": "Ссылки",
-    "lock_account_description": "Аккаунт доступен только подтверждённым подписчикам",
-    "loop_video": "Зациливать видео",
-    "loop_video_silent_only": "Зацикливать только беззвучные видео (т.е. \"гифки\" с Mastodon)",
-    "name": "Имя",
-    "name_bio": "Имя и описание",
-    "new_email": "Новый email",
-    "new_password": "Новый пароль",
-    "fun": "Потешное",
-    "greentext": "Мемные стрелочки",
-    "notification_visibility": "Показывать уведомления",
-    "notification_visibility_follows": "Подписки",
-    "notification_visibility_likes": "Лайки",
-    "notification_visibility_mentions": "Упоминания",
-    "notification_visibility_repeats": "Повторы",
-    "no_rich_text_description": "Убрать форматирование из всех постов",
-    "hide_follows_description": "Не показывать кого я читаю",
-    "hide_followers_description": "Не показывать кто читает меня",
-    "hide_follows_count_description": "Не показывать число читаемых пользователей",
-    "hide_followers_count_description": "Не показывать число моих подписчиков",
-    "show_admin_badge": "Показывать значок администратора в моем профиле",
-    "show_moderator_badge": "Показывать значок модератора в моем профиле",
-    "nsfw_clickthrough": "Включить скрытие NSFW вложений",
-    "oauth_tokens": "OAuth токены",
-    "token": "Токен",
-    "refresh_token": "Рефреш токен",
-    "valid_until": "Годен до",
-    "revoke_token": "Удалить",
-    "panelRadius": "Панели",
-    "pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе",
-    "presets": "Пресеты",
-    "profile_background": "Фон профиля",
-    "profile_banner": "Баннер профиля",
-    "profile_tab": "Профиль",
-    "radii_help": "Скругление углов элементов интерфейса (в пикселях)",
-    "replies_in_timeline": "Ответы в ленте",
-    "reply_link_preview": "Включить предварительный просмотр ответа при наведении мыши",
-    "reply_visibility_all": "Показывать все ответы",
-    "reply_visibility_following": "Показывать только ответы мне и тех на кого я подписан",
-    "reply_visibility_self": "Показывать только ответы мне",
-    "autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)",
-    "saving_err": "Не удалось сохранить настройки",
-    "saving_ok": "Сохранено",
-    "security_tab": "Безопасность",
-    "scope_copy": "Копировать видимость поста при ответе (всегда включено для Личных Сообщений)",
-    "minimal_scopes_mode": "Минимизировать набор опций видимости поста",
-    "set_new_avatar": "Загрузить новый аватар",
-    "set_new_profile_background": "Загрузить новый фон профиля",
-    "set_new_profile_banner": "Загрузить новый баннер профиля",
-    "settings": "Настройки",
-    "subject_input_always_show": "Всегда показывать поле ввода темы",
-    "stop_gifs": "Проигрывать GIF анимации только при наведении",
-    "streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
-    "useStreamingApi": "Получать сообщения и уведомления в реальном времени",
-    "useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)",
-    "text": "Текст",
-    "theme": "Тема",
-    "theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",
-    "theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения",
-    "theme_help_v2_2": "Под некоторыми полями ввода это идикаторы контрастности, наведите на них мышью чтобы узнать больше. Приспользовании прозрачности контраст расчитывается для наихудшего варианта.",
-    "tooltipRadius": "Всплывающие подсказки/уведомления",
-    "user_settings": "Настройки пользователя",
-    "values": {
-      "false": "нет",
-      "true": "да"
+    "general": {
+        "apply": "Применить",
+        "submit": "Отправить",
+        "cancel": "Отмена",
+        "disable": "Оключить",
+        "enable": "Включить",
+        "confirm": "Подтвердить",
+        "verify": "Проверить"
     },
-    "style": {
-      "switcher": {
-        "keep_color": "Оставить цвета",
-        "keep_shadows": "Оставить тени",
-        "keep_opacity": "Оставить прозрачность",
-        "keep_roundness": "Оставить скругление",
-        "keep_fonts": "Оставить шрифты",
-        "save_load_hint": "Опции \"оставить...\" позволяют сохранить текущие настройки при выборе другой темы или импорта её из файла. Так же они влияют на то какие компоненты будут сохранены при экспорте темы. Когда все галочки сняты все компоненты будут экспортированы.",
-        "reset": "Сбросить",
-        "clear_all": "Очистить всё",
-        "clear_opacity": "Очистить прозрачность"
-      },
-      "common": {
-        "color": "Цвет",
-        "opacity": "Прозрачность",
-        "contrast": {
-          "hint": "Уровень контраста: {ratio}, что {level} {context}",
-          "level": {
-            "aa": "соответствует гайдлайну Level AA (минимальный)",
-            "aaa": "соответствует гайдлайну Level AAA (рекомендуемый)",
-            "bad": "не соответствует каким либо гайдлайнам"
-          },
-          "context": {
-            "18pt": "для крупного (18pt+) текста",
-            "text": "для текста"
-          }
+    "login": {
+        "login": "Войти",
+        "logout": "Выйти",
+        "password": "Пароль",
+        "placeholder": "e.c. lain",
+        "register": "Зарегистрироваться",
+        "username": "Имя пользователя",
+        "authentication_code": "Код аутентификации",
+        "enter_recovery_code": "Ввести код восстановления",
+        "enter_two_factor_code": "Ввести код аутентификации",
+        "recovery_code": "Код восстановления",
+        "heading": {
+            "TotpForm": "Двухфакторная аутентификация",
+            "RecoveryForm": "Two-factor recovery"
         }
-      },
-      "common_colors": {
-        "_tab_label": "Общие",
-        "main": "Общие цвета",
-        "foreground_hint": "См. вкладку \"Дополнительно\" для более детального контроля",
-        "rgbo": "Иконки, акценты, ярылки"
-      },
-      "advanced_colors": {
-        "_tab_label": "Дополнительно",
-        "alert": "Фон уведомлений",
-        "alert_error": "Ошибки",
-        "badge": "Фон значков",
-        "badge_notification": "Уведомления",
-        "panel_header": "Заголовок панели",
-        "top_bar": "Верняя полоска",
-        "borders": "Границы",
-        "buttons": "Кнопки",
-        "inputs": "Поля ввода",
-        "faint_text": "Маловажный текст"
-      },
-      "radii": {
-        "_tab_label": "Скругление"
-      },
-      "shadows": {
-        "_tab_label": "Светотень",
-        "component": "Компонент",
-        "override": "Переопределить",
-        "shadow_id": "Тень №{value}",
-        "blur": "Размытие",
-        "spread": "Разброс",
-        "inset": "Внутренняя",
-        "hint": "Для теней вы так же можете использовать --variable в качестве цвета чтобы использовать CSS3-переменные. В таком случае прозрачность работать не будет.",
-        "filter_hint": {
-          "always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это",
-          "drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}",
-          "avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете",
-          "spread_zero": "Тени с разбросом > 0 будут выглядеть как если бы разброс установлен в 0",
-          "inset_classic": "Внутренние тени будут использовать {0}"
+    },
+    "nav": {
+        "back": "Назад",
+        "chat": "Локальный чат",
+        "mentions": "Упоминания",
+        "interactions": "Взаимодействия",
+        "public_tl": "Публичная лента",
+        "timeline": "Лента",
+        "twkn": "Федеративная лента",
+        "search": "Поиск"
+    },
+    "notifications": {
+        "broken_favorite": "Неизвестный статус, ищем...",
+        "favorited_you": "нравится ваш статус",
+        "followed_you": "начал(а) читать вас",
+        "load_older": "Загрузить старые уведомления",
+        "notifications": "Уведомления",
+        "read": "Прочесть",
+        "repeated_you": "повторил(а) ваш статус"
+    },
+    "interactions": {
+        "favs_repeats": "Повторы и фавориты",
+        "follows": "Новые подписки",
+        "load_older": "Загрузить старые взаимодействия"
+    },
+    "post_status": {
+        "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков",
+        "account_not_locked_warning_link": "залочен",
+        "attachments_sensitive": "Вложения содержат чувствительный контент",
+        "content_warning": "Тема (не обязательно)",
+        "default": "Что нового?",
+        "direct_warning": "Этот пост будет виден только упомянутым пользователям",
+        "posting": "Отправляется",
+        "scope_notice": {
+            "public": "Этот пост будет виден всем",
+            "private": "Этот пост будет виден только вашим подписчикам",
+            "unlisted": "Этот пост не будет виден в публичной и федеративной ленте"
         },
-        "components": {
-          "panel": "Панель",
-          "panelHeader": "Заголовок панели",
-          "topBar": "Верхняя полоска",
-          "avatar": "Аватарка (профиль)",
-          "avatarStatus": "Аватарка (в ленте)",
-          "popup": "Всплывающие подсказки",
-          "button": "Кнопки",
-          "buttonHover": "Кнопки (наведен курсор)",
-          "buttonPressed": "Кнопки (нажата)",
-          "buttonPressedHover": "Кнопки (нажата+наведен курсор)",
-          "input": "Поля ввода"
+        "scope": {
+            "direct": "Личное - этот пост видят только те кто в нём упомянут",
+            "private": "Для подписчиков - этот пост видят только подписчики",
+            "public": "Публичный - этот пост виден всем",
+            "unlisted": "Непубличный - этот пост не виден на публичных лентах"
         }
-      },
-      "fonts": {
-        "_tab_label": "Шрифты",
-        "help": "Выберите тип шрифта для использования в интерфейсе. При выборе варианта \"другой\" надо ввести название шрифта в точности как он называется в системе.",
-        "components": {
-          "interface": "Интерфейс",
-          "input": "Поля ввода",
-          "post": "Текст постов",
-          "postCode": "Моноширинный текст в посте (форматирование)"
+    },
+    "registration": {
+        "bio": "Описание",
+        "email": "Email",
+        "fullname": "Отображаемое имя",
+        "password_confirm": "Подтверждение пароля",
+        "registration": "Регистрация",
+        "token": "Код приглашения",
+        "validations": {
+            "username_required": "не должно быть пустым",
+            "fullname_required": "не должно быть пустым",
+            "email_required": "не должен быть пустым",
+            "password_required": "не должен быть пустым",
+            "password_confirmation_required": "не должно быть пустым",
+            "password_confirmation_match": "должно совпадать с паролем"
+        }
+    },
+    "settings": {
+        "enter_current_password_to_confirm": "Введите свой текущий пароль",
+        "mfa": {
+            "otp": "OTP",
+            "setup_otp": "Настройка OTP",
+            "wait_pre_setup_otp": "предварительная настройка OTP",
+            "confirm_and_enable": "Подтвердить и включить OTP",
+            "title": "Двухфакторная аутентификация",
+            "generate_new_recovery_codes": "Получить новые коды востановления",
+            "warning_of_generate_new_codes": "После получения новых кодов восстановления, старые больше не будут работать.",
+            "recovery_codes": "Коды восстановления.",
+            "waiting_a_recovery_codes": "Получение кодов восстановления ...",
+            "recovery_codes_warning": "Запишите эти коды и держите в безопасном месте - иначе вы их больше не увидите. Если вы потеряете доступ к OTP приложению - без резервных кодов вы больше не сможете залогиниться.",
+            "authentication_methods": "Методы аутентификации",
+            "scan": {
+                "title": "Сканирование",
+                "desc": "Используйте приложение для двухэтапной аутентификации для сканирования этого QR-код или введите текстовый ключ:",
+                "secret_code": "Ключ"
+            },
+            "verify": {
+                "desc": "Чтобы включить двухэтапную аутентификации, введите код из вашего приложение для двухэтапной аутентификации:"
+            }
         },
-        "family": "Шрифт",
-        "size": "Размер (в пикселях)",
-        "weight": "Ширина",
-        "custom": "Другой"
-      },
-      "preview": {
-        "header": "Пример",
-        "content": "Контент",
-        "error": "Ошибка стоп 000",
-        "button": "Кнопка",
-        "text": "Еще немного {0} и масенькая {1}",
-        "mono": "контента",
-        "input": "Что нового?",
-        "faint_link": "Его придется убрать",
-        "fine_print": "Если проблемы остались — ваш гуртовщик мыши плохо стоит. {0}.",
-        "header_faint": "Все идет по плану",
-        "checkbox": "Я подтверждаю что не было ни единого разрыва",
-        "link": "ссылка"
-      }
+        "attachmentRadius": "Прикреплённые файлы",
+        "attachments": "Вложения",
+        "autoload": "Включить автоматическую загрузку при прокрутке вниз",
+        "avatar": "Аватар",
+        "avatarAltRadius": "Аватары в уведомлениях",
+        "avatarRadius": "Аватары",
+        "background": "Фон",
+        "bio": "Описание",
+        "btnRadius": "Кнопки",
+        "cBlue": "Ответить, читать",
+        "cGreen": "Повторить",
+        "cOrange": "Нравится",
+        "cRed": "Отменить",
+        "change_email": "Сменить email",
+        "change_email_error": "Произошла ошибка при попытке изменить email.",
+        "changed_email": "Email изменён успешно.",
+        "change_password": "Сменить пароль",
+        "change_password_error": "Произошла ошибка при попытке изменить пароль.",
+        "changed_password": "Пароль изменён успешно.",
+        "collapse_subject": "Сворачивать посты с темой",
+        "confirm_new_password": "Подтверждение нового пароля",
+        "current_avatar": "Текущий аватар",
+        "current_password": "Текущий пароль",
+        "current_profile_banner": "Текущий баннер профиля",
+        "data_import_export_tab": "Импорт / Экспорт данных",
+        "delete_account": "Удалить аккаунт",
+        "delete_account_description": "Удалить ваш аккаунт и все ваши сообщения.",
+        "delete_account_error": "Возникла ошибка в процессе удаления вашего аккаунта. Если это повторяется, свяжитесь с администратором вашего сервера.",
+        "delete_account_instructions": "Введите ваш пароль в поле ниже для подтверждения удаления.",
+        "export_theme": "Сохранить Тему",
+        "filtering": "Фильтрация",
+        "filtering_explanation": "Все статусы, содержащие данные слова, будут игнорироваться, по одному в строке",
+        "follow_export": "Экспортировать читаемых",
+        "follow_export_button": "Экспортировать читаемых в файл .csv",
+        "follow_export_processing": "Ведётся обработка, скоро вам будет предложено загрузить файл",
+        "follow_import": "Импортировать читаемых",
+        "follow_import_error": "Ошибка при импортировании читаемых.",
+        "follows_imported": "Список читаемых импортирован. Обработка займёт некоторое время..",
+        "foreground": "Передний план",
+        "general": "Общие",
+        "hide_attachments_in_convo": "Прятать вложения в разговорах",
+        "hide_attachments_in_tl": "Прятать вложения в ленте",
+        "hide_isp": "Скрыть серверную панель",
+        "import_followers_from_a_csv_file": "Импортировать читаемых из файла .csv",
+        "import_theme": "Загрузить Тему",
+        "inputRadius": "Поля ввода",
+        "checkboxRadius": "Чекбоксы",
+        "instance_default": "(по умолчанию: {value})",
+        "instance_default_simple": "(по умолчанию)",
+        "interface": "Интерфейс",
+        "interfaceLanguage": "Язык интерфейса",
+        "limited_availability": "Не доступно в вашем браузере",
+        "links": "Ссылки",
+        "lock_account_description": "Аккаунт доступен только подтверждённым подписчикам",
+        "loop_video": "Зациливать видео",
+        "loop_video_silent_only": "Зацикливать только беззвучные видео (т.е. \"гифки\" с Mastodon)",
+        "name": "Имя",
+        "name_bio": "Имя и описание",
+        "new_email": "Новый email",
+        "new_password": "Новый пароль",
+        "fun": "Потешное",
+        "greentext": "Мемные стрелочки",
+        "notification_visibility": "Показывать уведомления",
+        "notification_visibility_follows": "Подписки",
+        "notification_visibility_likes": "Лайки",
+        "notification_visibility_mentions": "Упоминания",
+        "notification_visibility_repeats": "Повторы",
+        "no_rich_text_description": "Убрать форматирование из всех постов",
+        "hide_follows_description": "Не показывать кого я читаю",
+        "hide_followers_description": "Не показывать кто читает меня",
+        "hide_follows_count_description": "Не показывать число читаемых пользователей",
+        "hide_followers_count_description": "Не показывать число моих подписчиков",
+        "show_admin_badge": "Показывать значок администратора в моем профиле",
+        "show_moderator_badge": "Показывать значок модератора в моем профиле",
+        "nsfw_clickthrough": "Включить скрытие NSFW вложений",
+        "oauth_tokens": "OAuth токены",
+        "token": "Токен",
+        "refresh_token": "Рефреш токен",
+        "valid_until": "Годен до",
+        "revoke_token": "Удалить",
+        "panelRadius": "Панели",
+        "pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе",
+        "presets": "Пресеты",
+        "profile_background": "Фон профиля",
+        "profile_banner": "Баннер профиля",
+        "profile_tab": "Профиль",
+        "radii_help": "Скругление углов элементов интерфейса (в пикселях)",
+        "replies_in_timeline": "Ответы в ленте",
+        "reply_link_preview": "Включить предварительный просмотр ответа при наведении мыши",
+        "reply_visibility_all": "Показывать все ответы",
+        "reply_visibility_following": "Показывать только ответы мне и тех на кого я подписан",
+        "reply_visibility_self": "Показывать только ответы мне",
+        "autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)",
+        "saving_err": "Не удалось сохранить настройки",
+        "saving_ok": "Сохранено",
+        "security_tab": "Безопасность",
+        "scope_copy": "Копировать видимость поста при ответе (всегда включено для Личных Сообщений)",
+        "minimal_scopes_mode": "Минимизировать набор опций видимости поста",
+        "set_new_avatar": "Загрузить новый аватар",
+        "set_new_profile_background": "Загрузить новый фон профиля",
+        "set_new_profile_banner": "Загрузить новый баннер профиля",
+        "settings": "Настройки",
+        "subject_input_always_show": "Всегда показывать поле ввода темы",
+        "stop_gifs": "Проигрывать GIF анимации только при наведении",
+        "streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
+        "useStreamingApi": "Получать сообщения и уведомления в реальном времени",
+        "useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)",
+        "text": "Текст",
+        "theme": "Тема",
+        "theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",
+        "theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения",
+        "theme_help_v2_2": "Под некоторыми полями ввода это идикаторы контрастности, наведите на них мышью чтобы узнать больше. Приспользовании прозрачности контраст расчитывается для наихудшего варианта.",
+        "tooltipRadius": "Всплывающие подсказки/уведомления",
+        "user_settings": "Настройки пользователя",
+        "values": {
+            "false": "нет",
+            "true": "да"
+        },
+        "style": {
+            "switcher": {
+                "keep_color": "Оставить цвета",
+                "keep_shadows": "Оставить тени",
+                "keep_opacity": "Оставить прозрачность",
+                "keep_roundness": "Оставить скругление",
+                "keep_fonts": "Оставить шрифты",
+                "save_load_hint": "Опции \"оставить...\" позволяют сохранить текущие настройки при выборе другой темы или импорта её из файла. Так же они влияют на то какие компоненты будут сохранены при экспорте темы. Когда все галочки сняты все компоненты будут экспортированы.",
+                "reset": "Сбросить",
+                "clear_all": "Очистить всё",
+                "clear_opacity": "Очистить прозрачность"
+            },
+            "common": {
+                "color": "Цвет",
+                "opacity": "Прозрачность",
+                "contrast": {
+                    "hint": "Уровень контраста: {ratio}, что {level} {context}",
+                    "level": {
+                        "aa": "соответствует гайдлайну Level AA (минимальный)",
+                        "aaa": "соответствует гайдлайну Level AAA (рекомендуемый)",
+                        "bad": "не соответствует каким либо гайдлайнам"
+                    },
+                    "context": {
+                        "18pt": "для крупного (18pt+) текста",
+                        "text": "для текста"
+                    }
+                }
+            },
+            "common_colors": {
+                "_tab_label": "Общие",
+                "main": "Общие цвета",
+                "foreground_hint": "См. вкладку \"Дополнительно\" для более детального контроля",
+                "rgbo": "Иконки, акценты, ярылки"
+            },
+            "advanced_colors": {
+                "_tab_label": "Дополнительно",
+                "alert": "Фон уведомлений",
+                "alert_error": "Ошибки",
+                "badge": "Фон значков",
+                "badge_notification": "Уведомления",
+                "panel_header": "Заголовок панели",
+                "top_bar": "Верняя полоска",
+                "borders": "Границы",
+                "buttons": "Кнопки",
+                "inputs": "Поля ввода",
+                "faint_text": "Маловажный текст"
+            },
+            "radii": {
+                "_tab_label": "Скругление"
+            },
+            "shadows": {
+                "_tab_label": "Светотень",
+                "component": "Компонент",
+                "override": "Переопределить",
+                "shadow_id": "Тень №{value}",
+                "blur": "Размытие",
+                "spread": "Разброс",
+                "inset": "Внутренняя",
+                "hint": "Для теней вы так же можете использовать --variable в качестве цвета чтобы использовать CSS3-переменные. В таком случае прозрачность работать не будет.",
+                "filter_hint": {
+                    "always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это",
+                    "drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}",
+                    "avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете",
+                    "spread_zero": "Тени с разбросом > 0 будут выглядеть как если бы разброс установлен в 0",
+                    "inset_classic": "Внутренние тени будут использовать {0}"
+                },
+                "components": {
+                    "panel": "Панель",
+                    "panelHeader": "Заголовок панели",
+                    "topBar": "Верхняя полоска",
+                    "avatar": "Аватарка (профиль)",
+                    "avatarStatus": "Аватарка (в ленте)",
+                    "popup": "Всплывающие подсказки",
+                    "button": "Кнопки",
+                    "buttonHover": "Кнопки (наведен курсор)",
+                    "buttonPressed": "Кнопки (нажата)",
+                    "buttonPressedHover": "Кнопки (нажата+наведен курсор)",
+                    "input": "Поля ввода"
+                }
+            },
+            "fonts": {
+                "_tab_label": "Шрифты",
+                "help": "Выберите тип шрифта для использования в интерфейсе. При выборе варианта \"другой\" надо ввести название шрифта в точности как он называется в системе.",
+                "components": {
+                    "interface": "Интерфейс",
+                    "input": "Поля ввода",
+                    "post": "Текст постов",
+                    "postCode": "Моноширинный текст в посте (форматирование)"
+                },
+                "family": "Шрифт",
+                "size": "Размер (в пикселях)",
+                "weight": "Ширина",
+                "custom": "Другой"
+            },
+            "preview": {
+                "header": "Пример",
+                "content": "Контент",
+                "error": "Ошибка стоп 000",
+                "button": "Кнопка",
+                "text": "Еще немного {0} и масенькая {1}",
+                "mono": "контента",
+                "input": "Что нового?",
+                "faint_link": "Его придется убрать",
+                "fine_print": "Если проблемы остались — ваш гуртовщик мыши плохо стоит. {0}.",
+                "header_faint": "Все идет по плану",
+                "checkbox": "Я подтверждаю что не было ни единого разрыва",
+                "link": "ссылка"
+            }
+        }
+    },
+    "timeline": {
+        "collapse": "Свернуть",
+        "conversation": "Разговор",
+        "error_fetching": "Ошибка при обновлении",
+        "load_older": "Загрузить старые статусы",
+        "no_retweet_hint": "Пост помечен как \"только для подписчиков\" или \"личное\" и поэтому не может быть повторён",
+        "repeated": "повторил(а)",
+        "show_new": "Показать новые",
+        "up_to_date": "Обновлено"
+    },
+    "user_card": {
+        "block": "Заблокировать",
+        "blocked": "Заблокирован",
+        "favorites": "Понравившиеся",
+        "follow": "Читать",
+        "follow_sent": "Запрос отправлен!",
+        "follow_progress": "Запрашиваем…",
+        "follow_again": "Запросить еще заново?",
+        "follow_unfollow": "Перестать читать",
+        "followees": "Читаемые",
+        "followers": "Читатели",
+        "following": "Читаю",
+        "follows_you": "Читает вас",
+        "mute": "Игнорировать",
+        "muted": "Игнорирую",
+        "per_day": "в день",
+        "remote_follow": "Читать удалённо",
+        "statuses": "Статусы",
+        "admin_menu": {
+            "moderation": "Опции модератора",
+            "grant_admin": "Сделать администратором",
+            "revoke_admin": "Забрать права администратора",
+            "grant_moderator": "Сделать модератором",
+            "revoke_moderator": "Забрать права модератора",
+            "activate_account": "Активировать аккаунт",
+            "deactivate_account": "Деактивировать аккаунт",
+            "delete_account": "Удалить аккаунт",
+            "force_nsfw": "Отмечать посты пользователя как NSFW",
+            "strip_media": "Убирать вложения из постов пользователя",
+            "force_unlisted": "Не добавлять посты в публичные ленты",
+            "sandbox": "Посты доступны только для подписчиков",
+            "disable_remote_subscription": "Запретить подписываться с удаленных серверов",
+            "disable_any_subscription": "Запретить подписываться на пользователя",
+            "quarantine": "Не федерировать посты пользователя",
+            "delete_user": "Удалить пользователя",
+            "delete_user_confirmation": "Вы уверены? Это действие нельзя отменить."
+        }
+    },
+    "user_profile": {
+        "timeline_title": "Лента пользователя"
+    },
+    "search": {
+        "people": "Люди",
+        "hashtags": "Хэштэги",
+        "person_talking": "Популярно у {count} человека",
+        "people_talking": "Популярно у {count} человек",
+        "no_results": "Ничего не найдено"
+    },
+    "password_reset": {
+        "forgot_password": "Забыли пароль?",
+        "password_reset": "Сброс пароля",
+        "instruction": "Введите ваш email или имя пользователя, и мы отправим вам ссылку для сброса пароля.",
+        "placeholder": "Ваш email или имя пользователя",
+        "check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.",
+        "return_home": "Вернуться на главную страницу",
+        "not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.",
+        "too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.",
+        "password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера."
+    },
+    "about": {
+        "mrf": {
+            "federation": "Федерация",
+            "simple": {
+                "accept_desc": ""
+            }
+        }
     }
-  },
-  "timeline": {
-    "collapse": "Свернуть",
-    "conversation": "Разговор",
-    "error_fetching": "Ошибка при обновлении",
-    "load_older": "Загрузить старые статусы",
-    "no_retweet_hint": "Пост помечен как \"только для подписчиков\" или \"личное\" и поэтому не может быть повторён",
-    "repeated": "повторил(а)",
-    "show_new": "Показать новые",
-    "up_to_date": "Обновлено"
-  },
-  "user_card": {
-    "block": "Заблокировать",
-    "blocked": "Заблокирован",
-    "favorites": "Понравившиеся",
-    "follow": "Читать",
-    "follow_sent": "Запрос отправлен!",
-    "follow_progress": "Запрашиваем…",
-    "follow_again": "Запросить еще заново?",
-    "follow_unfollow": "Перестать читать",
-    "followees": "Читаемые",
-    "followers": "Читатели",
-    "following": "Читаю",
-    "follows_you": "Читает вас",
-    "mute": "Игнорировать",
-    "muted": "Игнорирую",
-    "per_day": "в день",
-    "remote_follow": "Читать удалённо",
-    "statuses": "Статусы",
-    "admin_menu": {
-      "moderation": "Опции модератора",
-      "grant_admin": "Сделать администратором",
-      "revoke_admin": "Забрать права администратора",
-      "grant_moderator": "Сделать модератором",
-      "revoke_moderator": "Забрать права модератора",
-      "activate_account": "Активировать аккаунт",
-      "deactivate_account": "Деактивировать аккаунт",
-      "delete_account": "Удалить аккаунт",
-      "force_nsfw": "Отмечать посты пользователя как NSFW",
-      "strip_media": "Убирать вложения из постов пользователя",
-      "force_unlisted": "Не добавлять посты в публичные ленты",
-      "sandbox": "Посты доступны только для подписчиков",
-      "disable_remote_subscription": "Запретить подписываться с удаленных серверов",
-      "disable_any_subscription": "Запретить подписываться на пользователя",
-      "quarantine": "Не федерировать посты пользователя",
-      "delete_user": "Удалить пользователя",
-      "delete_user_confirmation": "Вы уверены? Это действие нельзя отменить."
-    }
-  },
-  "user_profile": {
-    "timeline_title": "Лента пользователя"
-  },
-  "search": {
-    "people": "Люди",
-    "hashtags": "Хэштэги",
-    "person_talking": "Популярно у {count} человека",
-    "people_talking": "Популярно у {count} человек",
-    "no_results": "Ничего не найдено"
-  },
-  "password_reset": {
-    "forgot_password": "Забыли пароль?",
-    "password_reset": "Сброс пароля",
-    "instruction": "Введите ваш email или имя пользователя, и мы отправим вам ссылку для сброса пароля.",
-    "placeholder": "Ваш email или имя пользователя",
-    "check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.",
-    "return_home": "Вернуться на главную страницу",
-    "not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.",
-    "too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.",
-    "password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера."
-  }
 }

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: [PATCH 326/483] 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(-)

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
 }

From 8e399710988dd874bd921a9ced76daf90034c29f Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 13 May 2020 17:48:31 +0300
Subject: [PATCH 327/483] add with_relationships where necessary

---
 src/components/post_status_form/post_status_form.js |  2 +-
 src/components/user_settings/user_settings.js       |  4 ++--
 src/modules/users.js                                |  4 ++--
 src/services/api/api.service.js                     | 13 +++++++++----
 4 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 74067fef..a98e1e31 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -102,7 +102,7 @@ const PostStatusForm = {
           ...this.$store.state.instance.customEmoji
         ],
         users: this.$store.state.users.users,
-        updateUsersList: (input) => this.$store.dispatch('searchUsers', input)
+        updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
       })
     },
     emojiSuggestor () {
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 5338c974..a1ec2997 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -112,7 +112,7 @@ const UserSettings = {
           ...this.$store.state.instance.customEmoji
         ],
         users: this.$store.state.users.users,
-        updateUsersList: (input) => this.$store.dispatch('searchUsers', input)
+        updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
       })
     },
     emojiSuggestor () {
@@ -362,7 +362,7 @@ const UserSettings = {
       })
     },
     queryUserIds (query) {
-      return this.$store.dispatch('searchUsers', query)
+      return this.$store.dispatch('searchUsers', { query })
         .then((users) => map(users, 'id'))
     },
     blockUsers (ids) {
diff --git a/src/modules/users.js b/src/modules/users.js
index 1d1b415c..f377da75 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -428,8 +428,8 @@ const users = {
         store.commit('setUserForNotification', notification)
       })
     },
-    searchUsers (store, query) {
-      return store.rootState.api.backendInteractor.searchUsers({ query })
+    searchUsers (store, { query, withRelationships }) {
+      return store.rootState.api.backendInteractor.searchUsers({ query, withRelationships })
         .then((users) => {
           store.commit('addNewUsers', users)
           return users
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 72c8874f..94ff4623 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -324,7 +324,8 @@ const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => {
   const args = [
     maxId && `max_id=${maxId}`,
     sinceId && `since_id=${sinceId}`,
-    limit && `limit=${limit}`
+    limit && `limit=${limit}`,
+    `with_relationships=true`
   ].filter(_ => _).join('&')
 
   url = url + (args ? '?' + args : '')
@@ -358,7 +359,8 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => {
   const args = [
     maxId && `max_id=${maxId}`,
     sinceId && `since_id=${sinceId}`,
-    limit && `limit=${limit}`
+    limit && `limit=${limit}`,
+    `with_relationships=true`
   ].filter(_ => _).join('&')
 
   url += args ? '?' + args : ''
@@ -935,12 +937,13 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
   })
 }
 
-const searchUsers = ({ credentials, query }) => {
+const searchUsers = ({ credentials, query, withRelationships }) => {
   return promisedRequest({
     url: MASTODON_USER_SEARCH_URL,
     params: {
       q: query,
-      resolve: true
+      resolve: true,
+      with_relationships: withRelationships
     },
     credentials
   })
@@ -971,6 +974,8 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
     params.push(['following', true])
   }
 
+  params.push(['with_relationships', true])
+
   let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
   url += `?${queryString}`
 

From 355281081a044c950ce7d07fa1081149eb4aec0d Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 13 May 2020 17:53:43 +0300
Subject: [PATCH 328/483] don't send undefined

---
 src/services/api/api.service.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 94ff4623..37ccfd6b 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -943,7 +943,7 @@ const searchUsers = ({ credentials, query, withRelationships }) => {
     params: {
       q: query,
       resolve: true,
-      with_relationships: withRelationships
+      with_relationships: !!withRelationships
     },
     credentials
   })

From 9c7cb3a95431bbea44391f79da465f77565a4b49 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 13 May 2020 18:04:30 +0300
Subject: [PATCH 329/483] remove search1 with_relationships

---
 src/modules/users.js            | 4 ++--
 src/services/api/api.service.js | 5 ++---
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/modules/users.js b/src/modules/users.js
index f377da75..f9329f2a 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -428,8 +428,8 @@ const users = {
         store.commit('setUserForNotification', notification)
       })
     },
-    searchUsers (store, { query, withRelationships }) {
-      return store.rootState.api.backendInteractor.searchUsers({ query, withRelationships })
+    searchUsers (store, { query }) {
+      return store.rootState.api.backendInteractor.searchUsers({ query })
         .then((users) => {
           store.commit('addNewUsers', users)
           return users
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 37ccfd6b..7f82d2fa 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -937,13 +937,12 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
   })
 }
 
-const searchUsers = ({ credentials, query, withRelationships }) => {
+const searchUsers = ({ credentials, query }) => {
   return promisedRequest({
     url: MASTODON_USER_SEARCH_URL,
     params: {
       q: query,
-      resolve: true,
-      with_relationships: !!withRelationships
+      resolve: true
     },
     credentials
   })

From fa403bbcbdd82770ff41d31295938e1f01c12604 Mon Sep 17 00:00:00 2001
From: Alibek Omarov <a1ba.omarov@gmail.com>
Date: Wed, 13 May 2020 16:42:17 +0000
Subject: [PATCH 330/483] Translated using Weblate (Russian)

Currently translated at 54.4% (334 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
---
 src/i18n/ru.json | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index afaba444..22125853 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -56,7 +56,7 @@
         "load_older": "Загрузить старые взаимодействия"
     },
     "post_status": {
-        "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков",
+        "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков.",
         "account_not_locked_warning_link": "залочен",
         "attachments_sensitive": "Вложения содержат чувствительный контент",
         "content_warning": "Тема (не обязательно)",
@@ -129,10 +129,10 @@
         "cRed": "Отменить",
         "change_email": "Сменить email",
         "change_email_error": "Произошла ошибка при попытке изменить email.",
-        "changed_email": "Email изменён успешно.",
+        "changed_email": "Email изменён успешно!",
         "change_password": "Сменить пароль",
         "change_password_error": "Произошла ошибка при попытке изменить пароль.",
-        "changed_password": "Пароль изменён успешно.",
+        "changed_password": "Пароль изменён успешно!",
         "collapse_subject": "Сворачивать посты с темой",
         "confirm_new_password": "Подтверждение нового пароля",
         "current_avatar": "Текущий аватар",
@@ -150,7 +150,7 @@
         "follow_export_button": "Экспортировать читаемых в файл .csv",
         "follow_export_processing": "Ведётся обработка, скоро вам будет предложено загрузить файл",
         "follow_import": "Импортировать читаемых",
-        "follow_import_error": "Ошибка при импортировании читаемых.",
+        "follow_import_error": "Ошибка при импортировании читаемых",
         "follows_imported": "Список читаемых импортирован. Обработка займёт некоторое время..",
         "foreground": "Передний план",
         "general": "Общие",
@@ -224,7 +224,7 @@
         "text": "Текст",
         "theme": "Тема",
         "theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",
-        "theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения",
+        "theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения.",
         "theme_help_v2_2": "Под некоторыми полями ввода это идикаторы контрастности, наведите на них мышью чтобы узнать больше. Приспользовании прозрачности контраст расчитывается для наихудшего варианта.",
         "tooltipRadius": "Всплывающие подсказки/уведомления",
         "user_settings": "Настройки пользователя",
@@ -292,9 +292,9 @@
                 "inset": "Внутренняя",
                 "hint": "Для теней вы так же можете использовать --variable в качестве цвета чтобы использовать CSS3-переменные. В таком случае прозрачность работать не будет.",
                 "filter_hint": {
-                    "always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это",
-                    "drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}",
-                    "avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете",
+                    "always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это.",
+                    "drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}.",
+                    "avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете.",
                     "spread_zero": "Тени с разбросом > 0 будут выглядеть как если бы разброс установлен в 0",
                     "inset_classic": "Внутренние тени будут использовать {0}"
                 },

From 1a944efa33b00842f3f9d3fb01323b0d143903cf Mon Sep 17 00:00:00 2001
From: hj <spam@hjkos.com>
Date: Wed, 13 May 2020 16:48:47 +0000
Subject: [PATCH 331/483] Translated using Weblate (Russian)

Currently translated at 54.4% (334 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
---
 src/i18n/ru.json | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 22125853..e6fab7e8 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -416,7 +416,11 @@
             "federation": "Федерация",
             "simple": {
                 "accept_desc": ""
+            },
+            "keyword": {
+                "ftl_removal": "Убраны из федеративной ленты"
             }
-        }
+        },
+        "staff": "Администрация"
     }
 }

From 625d9b73208a614a20d3b9e05378fadf5b41f232 Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@airmail.cc>
Date: Wed, 13 May 2020 17:14:53 +0000
Subject: [PATCH 332/483] Translated using Weblate (Russian)

Currently translated at 54.4% (334 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
---
 src/i18n/ru.json | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index e6fab7e8..a34c2e3f 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -418,7 +418,9 @@
                 "accept_desc": ""
             },
             "keyword": {
-                "ftl_removal": "Убраны из федеративной ленты"
+                "ftl_removal": "Убраны из федеративной ленты",
+                "reject": "Отклонить",
+                "keyword_policies": "Действия на ключевые слова"
             }
         },
         "staff": "Администрация"

From 433d64827d6c1cb0c8110f091bce08d71dd5719c Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Wed, 13 May 2020 17:40:57 +0000
Subject: [PATCH 333/483] Translated using Weblate (Italian)

Currently translated at 27.5% (169 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 409 ++++++++++++++++++++++++-----------------------
 1 file changed, 207 insertions(+), 202 deletions(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index f441292e..3a49d96f 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -1,206 +1,211 @@
 {
-  "general": {
-    "submit": "Invia",
-    "apply": "Applica"
-  },
-  "nav": {
-    "mentions": "Menzioni",
-    "public_tl": "Sequenza temporale pubblica",
-    "timeline": "Sequenza temporale",
-    "twkn": "L'intera rete conosciuta",
-    "chat": "Chat Locale",
-    "friend_requests": "Richieste di Seguirti"
-  },
-  "notifications": {
-    "followed_you": "ti segue",
-    "notifications": "Notifiche",
-    "read": "Leggi!",
-    "broken_favorite": "Stato sconosciuto, lo sto cercando...",
-    "favorited_you": "ha messo mi piace al tuo stato",
-    "load_older": "Carica notifiche più vecchie",
-    "repeated_you": "ha condiviso il tuo stato"
-  },
-  "settings": {
-    "attachments": "Allegati",
-    "autoload": "Abilita caricamento automatico quando si raggiunge fondo pagina",
-    "avatar": "Avatar",
-    "bio": "Introduzione",
-    "current_avatar": "Il tuo avatar attuale",
-    "current_profile_banner": "Il tuo banner attuale",
-    "filtering": "Filtri",
-    "filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, uno per linea",
-    "hide_attachments_in_convo": "Nascondi gli allegati presenti nelle conversazioni",
-    "hide_attachments_in_tl": "Nascondi gli allegati presenti nella sequenza temporale",
-    "name": "Nome",
-    "name_bio": "Nome & Introduzione",
-    "nsfw_clickthrough": "Abilita il click per visualizzare gli allegati segnati come NSFW",
-    "profile_background": "Sfondo della tua pagina",
-    "profile_banner": "Banner del tuo profilo",
-    "reply_link_preview": "Abilita il link per la risposta al passaggio del mouse",
-    "set_new_avatar": "Scegli un nuovo avatar",
-    "set_new_profile_background": "Scegli un nuovo sfondo per la tua pagina",
-    "set_new_profile_banner": "Scegli un nuovo banner per il tuo profilo",
-    "settings": "Impostazioni",
-    "theme": "Tema",
-    "user_settings": "Impostazioni Utente",
-    "attachmentRadius": "Allegati",
-    "avatarAltRadius": "Avatar (Notifiche)",
-    "avatarRadius": "Avatar",
-    "background": "Sfondo",
-    "btnRadius": "Pulsanti",
-    "cBlue": "Blu (Rispondere, seguire)",
-    "cGreen": "Verde (Condividi)",
-    "cOrange": "Arancio (Mi piace)",
-    "cRed": "Rosso (Annulla)",
-    "change_password": "Cambia Password",
-    "change_password_error": "C'è stato un problema durante il cambiamento della password.",
-    "changed_password": "Password cambiata correttamente!",
-    "collapse_subject": "Riduci post che hanno un oggetto",
-    "confirm_new_password": "Conferma la nuova password",
-    "current_password": "Password attuale",
-    "data_import_export_tab": "Importa / Esporta Dati",
-    "default_vis": "Visibilità predefinita dei post",
-    "delete_account": "Elimina Account",
-    "delete_account_description": "Elimina definitivamente il tuo account e tutti i tuoi messaggi.",
-    "delete_account_error": "C'è stato un problema durante l'eliminazione del tuo account. Se il problema persiste contatta l'amministratore della tua istanza.",
-    "delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione dell'account.",
-    "export_theme": "Salva settaggi",
-    "follow_export": "Esporta la lista di chi segui",
-    "follow_export_button": "Esporta la lista di chi segui in un file csv",
-    "follow_export_processing": "Sto elaborando, presto ti sarà chiesto di scaricare il tuo file",
-    "follow_import": "Importa la lista di chi segui",
-    "follow_import_error": "Errore nell'importazione della lista di chi segui",
-    "follows_imported": "Importazione riuscita! L'elaborazione richiederà un po' di tempo.",
-    "foreground": "In primo piano",
-    "general": "Generale",
-    "hide_post_stats": "Nascondi statistiche dei post (es. il numero di mi piace)",
-    "hide_user_stats": "Nascondi statistiche dell'utente (es. il numero di chi ti segue)",
-    "import_followers_from_a_csv_file": "Importa una lista di chi segui da un file csv",
-    "import_theme": "Carica settaggi",
-    "inputRadius": "Campi di testo",
-    "instance_default": "(predefinito: {value})",
-    "interfaceLanguage": "Linguaggio dell'interfaccia",
-    "invalid_theme_imported": "Il file selezionato non è un file di tema per Pleroma supportato. Il tuo tema non è stato modificato.",
-    "limited_availability": "Non disponibile nel tuo browser",
-    "links": "Collegamenti",
-    "lock_account_description": "Limita il tuo account solo per contatti approvati",
-    "loop_video": "Riproduci video in ciclo continuo",
-    "loop_video_silent_only": "Riproduci solo video senza audio in ciclo continuo (es. le gif di Mastodon)",
-    "new_password": "Nuova password",
-    "notification_visibility": "Tipi di notifiche da mostrare",
-    "notification_visibility_follows": "Nuove persone ti seguono",
-    "notification_visibility_likes": "Mi piace",
-    "notification_visibility_mentions": "Menzioni",
-    "notification_visibility_repeats": "Condivisioni",
-    "no_rich_text_description": "Togli la formattazione del testo da tutti i post",
-    "oauth_tokens": "Token OAuth",
-    "token": "Token",
-    "refresh_token": "Aggiorna token",
-    "valid_until": "Valido fino a",
-    "revoke_token": "Revocare",
-    "panelRadius": "Pannelli",
-    "pause_on_unfocused": "Metti in pausa l'aggiornamento continuo quando la scheda non è in primo piano",
-    "presets": "Valori predefiniti",
-    "profile_tab": "Profilo",
-    "radii_help": "Imposta l'arrotondamento dei bordi (in pixel)",
-    "replies_in_timeline": "Risposte nella sequenza temporale",
-    "reply_visibility_all": "Mostra tutte le risposte",
-    "reply_visibility_following": "Mostra solo le risposte dirette a me o agli utenti che seguo",
-    "reply_visibility_self": "Mostra solo risposte dirette a me",
-    "saving_err": "Errore nel salvataggio delle impostazioni",
-    "saving_ok": "Impostazioni salvate",
-    "security_tab": "Sicurezza",
-    "stop_gifs": "Riproduci GIF al passaggio del cursore del mouse",
-    "streaming": "Abilita aggiornamento automatico dei nuovi post quando si è in alto alla pagina",
-    "text": "Testo",
-    "theme_help": "Usa codici colore esadecimali (#rrggbb) per personalizzare il tuo schema di colori.",
-    "tooltipRadius": "Descrizioni/avvisi",
-    "values": {
-      "false": "no",
-      "true": "si"
-    }
-  },
-  "timeline": {
-    "error_fetching": "Errore nel prelievo aggiornamenti",
-    "load_older": "Carica messaggi più vecchi",
-    "show_new": "Mostra nuovi",
-    "up_to_date": "Aggiornato",
-    "collapse": "Riduci",
-    "conversation": "Conversazione",
-    "no_retweet_hint": "La visibilità del post è impostata solo per chi ti segue o messaggio diretto e non può essere condiviso",
-    "repeated": "condiviso"
-  },
-  "user_card": {
-    "follow": "Segui",
-    "followees": "Chi stai seguendo",
-    "followers": "Chi ti segue",
-    "following": "Lo stai seguendo!",
-    "follows_you": "Ti segue!",
-    "mute": "Silenzia",
-    "muted": "Silenziato",
-    "per_day": "al giorno",
-    "statuses": "Messaggi",
-    "approve": "Approva",
-    "block": "Blocca",
-    "blocked": "Bloccato!",
-    "deny": "Nega",
-    "remote_follow": "Segui da remoto"
-  },
-  "chat": {
-    "title": "Chat"
-  },
-  "features_panel": {
-    "chat": "Chat",
-    "gopher": "Gopher",
-    "media_proxy": "Media proxy",
-    "scope_options": "Opzioni di visibilità",
-    "text_limit": "Lunghezza limite",
-    "title": "Caratteristiche",
-    "who_to_follow": "Chi seguire"
-  },
-  "finder": {
-    "error_fetching_user": "Errore nel recupero dell'utente",
-    "find_user": "Trova utente"
-  },
-  "login": {
-    "login": "Accedi",
-    "logout": "Disconnettiti",
-    "password": "Password",
-    "placeholder": "es. lain",
-    "register": "Registrati",
-    "username": "Nome utente"
-  },
-  "post_status": {
-    "account_not_locked_warning": "Il tuo account non è {0}. Chiunque può seguirti e vedere i tuoi post riservati a chi ti segue.",
-    "account_not_locked_warning_link": "bloccato",
-    "attachments_sensitive": "Segna allegati come sensibili",
-    "content_type": {
-      "text/plain": "Testo normale"
+    "general": {
+        "submit": "Invia",
+        "apply": "Applica"
     },
-    "content_warning": "Oggetto (facoltativo)",
-    "default": "Appena atterrato in L.A.",
-    "direct_warning": "Questo post sarà visibile solo dagli utenti menzionati.",
-    "posting": "Pubblica",
-    "scope": {
-      "direct": "Diretto - Pubblicato solo per gli utenti menzionati",
-      "private": "Solo per chi ti segue - Visibile solo da chi ti segue",
-      "public": "Pubblico - Visibile sulla sequenza temporale pubblica",
-      "unlisted": "Non elencato - Non visibile sulla sequenza temporale pubblica"
+    "nav": {
+        "mentions": "Menzioni",
+        "public_tl": "Sequenza temporale pubblica",
+        "timeline": "Sequenza temporale",
+        "twkn": "L'intera rete conosciuta",
+        "chat": "Chat Locale",
+        "friend_requests": "Richieste di Seguirti"
+    },
+    "notifications": {
+        "followed_you": "ti segue",
+        "notifications": "Notifiche",
+        "read": "Leggi!",
+        "broken_favorite": "Stato sconosciuto, lo sto cercando...",
+        "favorited_you": "ha messo mi piace al tuo stato",
+        "load_older": "Carica notifiche più vecchie",
+        "repeated_you": "ha condiviso il tuo stato"
+    },
+    "settings": {
+        "attachments": "Allegati",
+        "autoload": "Abilita caricamento automatico quando si raggiunge fondo pagina",
+        "avatar": "Avatar",
+        "bio": "Introduzione",
+        "current_avatar": "Il tuo avatar attuale",
+        "current_profile_banner": "Il tuo banner attuale",
+        "filtering": "Filtri",
+        "filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, uno per linea",
+        "hide_attachments_in_convo": "Nascondi gli allegati presenti nelle conversazioni",
+        "hide_attachments_in_tl": "Nascondi gli allegati presenti nella sequenza temporale",
+        "name": "Nome",
+        "name_bio": "Nome & Introduzione",
+        "nsfw_clickthrough": "Abilita il click per visualizzare gli allegati segnati come NSFW",
+        "profile_background": "Sfondo della tua pagina",
+        "profile_banner": "Banner del tuo profilo",
+        "reply_link_preview": "Abilita il link per la risposta al passaggio del mouse",
+        "set_new_avatar": "Scegli un nuovo avatar",
+        "set_new_profile_background": "Scegli un nuovo sfondo per la tua pagina",
+        "set_new_profile_banner": "Scegli un nuovo banner per il tuo profilo",
+        "settings": "Impostazioni",
+        "theme": "Tema",
+        "user_settings": "Impostazioni Utente",
+        "attachmentRadius": "Allegati",
+        "avatarAltRadius": "Avatar (Notifiche)",
+        "avatarRadius": "Avatar",
+        "background": "Sfondo",
+        "btnRadius": "Pulsanti",
+        "cBlue": "Blu (Rispondere, seguire)",
+        "cGreen": "Verde (Condividi)",
+        "cOrange": "Arancio (Mi piace)",
+        "cRed": "Rosso (Annulla)",
+        "change_password": "Cambia Password",
+        "change_password_error": "C'è stato un problema durante il cambiamento della password.",
+        "changed_password": "Password cambiata correttamente!",
+        "collapse_subject": "Riduci post che hanno un oggetto",
+        "confirm_new_password": "Conferma la nuova password",
+        "current_password": "Password attuale",
+        "data_import_export_tab": "Importa / Esporta Dati",
+        "default_vis": "Visibilità predefinita dei post",
+        "delete_account": "Elimina Account",
+        "delete_account_description": "Elimina definitivamente il tuo account e tutti i tuoi messaggi.",
+        "delete_account_error": "C'è stato un problema durante l'eliminazione del tuo account. Se il problema persiste contatta l'amministratore della tua istanza.",
+        "delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione dell'account.",
+        "export_theme": "Salva settaggi",
+        "follow_export": "Esporta la lista di chi segui",
+        "follow_export_button": "Esporta la lista di chi segui in un file csv",
+        "follow_export_processing": "Sto elaborando, presto ti sarà chiesto di scaricare il tuo file",
+        "follow_import": "Importa la lista di chi segui",
+        "follow_import_error": "Errore nell'importazione della lista di chi segui",
+        "follows_imported": "Importazione riuscita! L'elaborazione richiederà un po' di tempo.",
+        "foreground": "In primo piano",
+        "general": "Generale",
+        "hide_post_stats": "Nascondi statistiche dei post (es. il numero di mi piace)",
+        "hide_user_stats": "Nascondi statistiche dell'utente (es. il numero di chi ti segue)",
+        "import_followers_from_a_csv_file": "Importa una lista di chi segui da un file csv",
+        "import_theme": "Carica settaggi",
+        "inputRadius": "Campi di testo",
+        "instance_default": "(predefinito: {value})",
+        "interfaceLanguage": "Linguaggio dell'interfaccia",
+        "invalid_theme_imported": "Il file selezionato non è un file di tema per Pleroma supportato. Il tuo tema non è stato modificato.",
+        "limited_availability": "Non disponibile nel tuo browser",
+        "links": "Collegamenti",
+        "lock_account_description": "Limita il tuo account solo per contatti approvati",
+        "loop_video": "Riproduci video in ciclo continuo",
+        "loop_video_silent_only": "Riproduci solo video senza audio in ciclo continuo (es. le gif di Mastodon)",
+        "new_password": "Nuova password",
+        "notification_visibility": "Tipi di notifiche da mostrare",
+        "notification_visibility_follows": "Nuove persone ti seguono",
+        "notification_visibility_likes": "Mi piace",
+        "notification_visibility_mentions": "Menzioni",
+        "notification_visibility_repeats": "Condivisioni",
+        "no_rich_text_description": "Togli la formattazione del testo da tutti i post",
+        "oauth_tokens": "Token OAuth",
+        "token": "Token",
+        "refresh_token": "Aggiorna token",
+        "valid_until": "Valido fino a",
+        "revoke_token": "Revocare",
+        "panelRadius": "Pannelli",
+        "pause_on_unfocused": "Metti in pausa l'aggiornamento continuo quando la scheda non è in primo piano",
+        "presets": "Valori predefiniti",
+        "profile_tab": "Profilo",
+        "radii_help": "Imposta l'arrotondamento dei bordi (in pixel)",
+        "replies_in_timeline": "Risposte nella sequenza temporale",
+        "reply_visibility_all": "Mostra tutte le risposte",
+        "reply_visibility_following": "Mostra solo le risposte dirette a me o agli utenti che seguo",
+        "reply_visibility_self": "Mostra solo risposte dirette a me",
+        "saving_err": "Errore nel salvataggio delle impostazioni",
+        "saving_ok": "Impostazioni salvate",
+        "security_tab": "Sicurezza",
+        "stop_gifs": "Riproduci GIF al passaggio del cursore del mouse",
+        "streaming": "Abilita aggiornamento automatico dei nuovi post quando si è in alto alla pagina",
+        "text": "Testo",
+        "theme_help": "Usa codici colore esadecimali (#rrggbb) per personalizzare il tuo schema di colori.",
+        "tooltipRadius": "Descrizioni/avvisi",
+        "values": {
+            "false": "no",
+            "true": "si"
+        }
+    },
+    "timeline": {
+        "error_fetching": "Errore nel prelievo aggiornamenti",
+        "load_older": "Carica messaggi più vecchi",
+        "show_new": "Mostra nuovi",
+        "up_to_date": "Aggiornato",
+        "collapse": "Riduci",
+        "conversation": "Conversazione",
+        "no_retweet_hint": "La visibilità del post è impostata solo per chi ti segue o messaggio diretto e non può essere condiviso",
+        "repeated": "condiviso"
+    },
+    "user_card": {
+        "follow": "Segui",
+        "followees": "Chi stai seguendo",
+        "followers": "Chi ti segue",
+        "following": "Lo stai seguendo!",
+        "follows_you": "Ti segue!",
+        "mute": "Silenzia",
+        "muted": "Silenziato",
+        "per_day": "al giorno",
+        "statuses": "Messaggi",
+        "approve": "Approva",
+        "block": "Blocca",
+        "blocked": "Bloccato!",
+        "deny": "Nega",
+        "remote_follow": "Segui da remoto"
+    },
+    "chat": {
+        "title": "Chat"
+    },
+    "features_panel": {
+        "chat": "Chat",
+        "gopher": "Gopher",
+        "media_proxy": "Media proxy",
+        "scope_options": "Opzioni di visibilità",
+        "text_limit": "Lunghezza limite",
+        "title": "Caratteristiche",
+        "who_to_follow": "Chi seguire"
+    },
+    "finder": {
+        "error_fetching_user": "Errore nel recupero dell'utente",
+        "find_user": "Trova utente"
+    },
+    "login": {
+        "login": "Accedi",
+        "logout": "Disconnettiti",
+        "password": "Password",
+        "placeholder": "es. lain",
+        "register": "Registrati",
+        "username": "Nome utente"
+    },
+    "post_status": {
+        "account_not_locked_warning": "Il tuo account non è {0}. Chiunque può seguirti e vedere i tuoi post riservati a chi ti segue.",
+        "account_not_locked_warning_link": "bloccato",
+        "attachments_sensitive": "Segna allegati come sensibili",
+        "content_type": {
+            "text/plain": "Testo normale"
+        },
+        "content_warning": "Oggetto (facoltativo)",
+        "default": "Appena atterrato in L.A.",
+        "direct_warning": "Questo post sarà visibile solo dagli utenti menzionati.",
+        "posting": "Pubblica",
+        "scope": {
+            "direct": "Diretto - Pubblicato solo per gli utenti menzionati",
+            "private": "Solo per chi ti segue - Visibile solo da chi ti segue",
+            "public": "Pubblico - Visibile sulla sequenza temporale pubblica",
+            "unlisted": "Non elencato - Non visibile sulla sequenza temporale pubblica"
+        }
+    },
+    "registration": {
+        "bio": "Introduzione",
+        "email": "Email",
+        "fullname": "Nome visualizzato",
+        "password_confirm": "Conferma password",
+        "registration": "Registrazione",
+        "token": "Codice d'invito"
+    },
+    "user_profile": {
+        "timeline_title": "Sequenza Temporale dell'Utente"
+    },
+    "who_to_follow": {
+        "more": "Più",
+        "who_to_follow": "Chi seguire"
+    },
+    "about": {
+        "mrf": {
+            "federation": "Federazione"
+        }
     }
-  },
-  "registration": {
-    "bio": "Introduzione",
-    "email": "Email",
-    "fullname": "Nome visualizzato",
-    "password_confirm": "Conferma password",
-    "registration": "Registrazione",
-    "token": "Codice d'invito"
-  },
-  "user_profile": {
-    "timeline_title": "Sequenza Temporale dell'Utente"
-  },
-  "who_to_follow": {
-    "more": "Più",
-    "who_to_follow": "Chi seguire"
-  }
 }

From c360a2384578b15664bc461dddb1a193d5e0b09f Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 13 May 2020 16:47:13 +0000
Subject: [PATCH 334/483] Translated using Weblate (Finnish)

Currently translated at 78.7% (483 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fi/
---
 src/i18n/fi.json | 923 ++++++++++++++++++++++++++++++-----------------
 1 file changed, 584 insertions(+), 339 deletions(-)

diff --git a/src/i18n/fi.json b/src/i18n/fi.json
index 926e6087..386578fb 100644
--- a/src/i18n/fi.json
+++ b/src/i18n/fi.json
@@ -1,344 +1,589 @@
 {
-  "chat": {
-    "title": "Chat"
-  },
-  "features_panel": {
-    "chat": "Chat",
-    "gopher": "Gopher",
-    "media_proxy": "Media-välityspalvelin",
-    "scope_options": "Näkyvyyden rajaus",
-    "text_limit": "Tekstin pituusraja",
-    "title": "Ominaisuudet",
-    "who_to_follow": "Seurausehdotukset"
-  },
-  "finder": {
-    "error_fetching_user": "Virhe hakiessa käyttäjää",
-    "find_user": "Hae käyttäjä"
-  },
-  "general": {
-    "apply": "Aseta",
-    "submit": "Lähetä",
-    "more": "Lisää",
-    "generic_error": "Virhe tapahtui"
-  },
-  "login": {
-    "login": "Kirjaudu sisään",
-    "description": "Kirjaudu sisään OAuthilla",
-    "logout": "Kirjaudu ulos",
-    "password": "Salasana",
-    "placeholder": "esim. Seppo",
-    "register": "Rekisteröidy",
-    "username": "Käyttäjänimi"
-  },
-  "nav": {
-    "about": "Tietoja",
-    "back": "Takaisin",
-    "chat": "Paikallinen Chat",
-    "friend_requests": "Seurauspyynnöt",
-    "mentions": "Maininnat",
-    "interactions": "Interaktiot",
-    "dms": "Yksityisviestit",
-    "public_tl": "Julkinen Aikajana",
-    "timeline": "Aikajana",
-    "twkn": "Koko Tunnettu Verkosto",
-    "user_search": "Käyttäjähaku",
-    "who_to_follow": "Seurausehdotukset",
-    "preferences": "Asetukset"
-  },
-  "notifications": {
-    "broken_favorite": "Viestiä ei löydetty...",
-    "favorited_you": "tykkäsi viestistäsi",
-    "followed_you": "seuraa sinua",
-    "load_older": "Lataa vanhempia ilmoituksia",
-    "notifications": "Ilmoitukset",
-    "read": "Lue!",
-    "repeated_you": "toisti viestisi",
-    "no_more_notifications": "Ei enempää ilmoituksia",
-    "reacted_with": "lisäsi reaktion {0}"
-  },
-  "polls": {
-    "add_poll": "Lisää äänestys",
-    "add_option": "Lisää vaihtoehto",
-    "option": "Vaihtoehto",
-    "votes": "ääntä",
-    "vote": "Äänestä",
-    "type": "Äänestyksen tyyppi",
-    "single_choice": "Yksi valinta",
-    "multiple_choices": "Monivalinta",
-    "expiry": "Äänestyksen kesto",
-    "expires_in": "Päättyy {0} päästä",
-    "expired": "Päättyi {0} sitten",
-    "not_enough_option": "Liian vähän uniikkeja vaihtoehtoja äänestyksessä"
-  },
-  "interactions": {
-    "favs_repeats": "Toistot ja tykkäykset",
-    "follows": "Uudet seuraukset",
-    "load_older": "Lataa vanhempia interaktioita"
-  },
-  "post_status": {
-    "new_status": "Uusi viesti",
-    "account_not_locked_warning": "Tilisi ei ole {0}. Kuka vain voi seurata sinua nähdäksesi 'vain-seuraajille' -viestisi",
-    "account_not_locked_warning_link": "lukittu",
-    "attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi",
-    "content_type": {
-      "text/plain": "Tavallinen teksti"
+    "chat": {
+        "title": "Chat"
     },
-    "content_warning": "Aihe (valinnainen)",
-    "default": "Tulin juuri saunasta.",
-    "direct_warning": "Tämä viesti näkyy vain mainituille käyttäjille.",
-    "posting": "Lähetetään",
-    "scope": {
-      "direct": "Yksityisviesti - Näkyy vain mainituille käyttäjille",
-      "private": "Vain-seuraajille - Näkyy vain seuraajillesi",
-      "public": "Julkinen - Näkyy julkisilla aikajanoilla",
-      "unlisted": "Listaamaton - Ei näy julkisilla aikajanoilla"
-    }
-  },
-  "registration": {
-    "bio": "Kuvaus",
-    "email": "Sähköposti",
-    "fullname": "Koko nimi",
-    "password_confirm": "Salasanan vahvistaminen",
-    "registration": "Rekisteröityminen",
-    "token": "Kutsuvaltuus",
-    "captcha": "Varmenne",
-    "new_captcha": "Paina kuvaa saadaksesi uuden varmenteen",
-    "validations": {
-      "username_required": "ei voi olla tyhjä",
-      "fullname_required": "ei voi olla tyhjä",
-      "email_required": "ei voi olla tyhjä",
-      "password_required": "ei voi olla tyhjä",
-      "password_confirmation_required": "ei voi olla tyhjä",
-      "password_confirmation_match": "pitää vastata salasanaa"
-    }
-  },
-  "settings": {
-    "attachmentRadius": "Liitteet",
-    "attachments": "Liitteet",
-    "autoload": "Lataa vanhempia viestejä automaattisesti ruudun pohjalla",
-    "avatar": "Profiilikuva",
-    "avatarAltRadius": "Profiilikuvat (ilmoitukset)",
-    "avatarRadius": "Profiilikuvat",
-    "background": "Tausta",
-    "bio": "Kuvaus",
-    "btnRadius": "Napit",
-    "cBlue": "Sininen (Vastaukset, seuraukset)",
-    "cGreen": "Vihreä (Toistot)",
-    "cOrange": "Oranssi (Tykkäykset)",
-    "cRed": "Punainen (Peruminen)",
-    "change_password": "Vaihda salasana",
-    "change_password_error": "Virhe vaihtaessa salasanaa.",
-    "changed_password": "Salasana vaihdettu!",
-    "collapse_subject": "Minimoi viestit, joille on asetettu aihe",
-    "composing": "Viestien laatiminen",
-    "confirm_new_password": "Vahvista uusi salasana",
-    "current_avatar": "Nykyinen profiilikuvasi",
-    "current_password": "Nykyinen salasana",
-    "current_profile_banner": "Nykyinen julisteesi",
-    "data_import_export_tab": "Tietojen tuonti / vienti",
-    "default_vis": "Oletusnäkyvyysrajaus",
-    "delete_account": "Poista tili",
-    "delete_account_description": "Poista tilisi ja viestisi pysyvästi.",
-    "delete_account_error": "Virhe poistaessa tiliäsi. Jos virhe jatkuu, ota yhteyttä palvelimesi ylläpitoon.",
-    "delete_account_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.",
-    "emoji_reactions_on_timeline": "Näytä emojireaktiot aikajanalla",
-    "export_theme": "Tallenna teema",
-    "filtering": "Suodatus",
-    "filtering_explanation": "Kaikki viestit, jotka sisältävät näitä sanoja, suodatetaan. Yksi sana per rivi.",
-    "follow_export": "Seurausten vienti",
-    "follow_export_button": "Vie seurauksesi CSV-tiedostoon",
-    "follow_export_processing": "Käsitellään, sinua pyydetään lataamaan tiedosto hetken päästä",
-    "follow_import": "Seurausten tuonti",
-    "follow_import_error": "Virhe tuodessa seuraksia",
-    "follows_imported": "Seuraukset tuotu! Niiden käsittely vie hetken.",
-    "foreground": "Korostus",
-    "general": "Yleinen",
-    "hide_attachments_in_convo": "Piilota liitteet keskusteluissa",
-    "hide_attachments_in_tl": "Piilota liitteet aikajanalla",
-    "max_thumbnails": "Suurin sallittu määrä liitteitä esikatselussa",
-    "hide_isp": "Piilota palvelimenkohtainen ruutu",
-    "preload_images": "Esilataa kuvat",
-    "use_one_click_nsfw": "Avaa NSFW-liitteet yhdellä painalluksella",
-    "hide_post_stats": "Piilota viestien statistiikka (esim. tykkäysten määrä)",
-    "hide_user_stats": "Piilota käyttäjien statistiikka (esim. seuraajien määrä)",
-    "import_followers_from_a_csv_file": "Tuo seuraukset CSV-tiedostosta",
-    "import_theme": "Tuo tallennettu teema",
-    "inputRadius": "Syöttökentät",
-    "checkboxRadius": "Valintalaatikot",
-    "instance_default": "(oletus: {value})",
-    "instance_default_simple": "(oletus)",
-    "interface": "Käyttöliittymä",
-    "interfaceLanguage": "Käyttöliittymän kieli",
-    "invalid_theme_imported": "Tuotu tallennettu teema on epäkelpo, muutoksia ei tehty nykyiseen teemaasi.",
-    "limited_availability": "Ei saatavilla selaimessasi",
-    "links": "Linkit",
-    "lock_account_description": "Vain erikseen hyväksytyt käyttäjät voivat seurata tiliäsi",
-    "loop_video": "Uudelleentoista videot",
-    "loop_video_silent_only": "Uudelleentoista ainoastaan äänettömät videot (Video-\"giffit\")",
-    "play_videos_in_modal": "Toista videot modaalissa",
-    "use_contain_fit": "Älä rajaa liitteitä esikatselussa",
-    "name": "Nimi",
-    "name_bio": "Nimi ja kuvaus",
-    "new_password": "Uusi salasana",
-    "notification_visibility": "Ilmoitusten näkyvyys",
-    "notification_visibility_follows": "Seuraukset",
-    "notification_visibility_likes": "Tykkäykset",
-    "notification_visibility_mentions": "Maininnat",
-    "notification_visibility_repeats": "Toistot",
-    "notification_visibility_emoji_reactions": "Reaktiot",
-    "no_rich_text_description": "Älä näytä tekstin muotoilua.",
-    "hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
-    "nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
-    "oauth_tokens": "OAuth-merkit",
-    "token": "Token",
-    "refresh_token": "Päivitä token",
-    "valid_until": "Voimassa asti",
-    "revoke_token": "Peruuttaa",
-    "panelRadius": "Ruudut",
-    "pause_on_unfocused": "Pysäytä automaattinen viestien näyttö välilehden ollessa pois fokuksesta",
-    "presets": "Valmiit teemat",
-    "profile_background": "Taustakuva",
-    "profile_banner": "Juliste",
-    "profile_tab": "Profiili",
-    "radii_help": "Aseta reunojen pyöristys (pikseleinä)",
-    "replies_in_timeline": "Keskustelut aikajanalla",
-    "reply_link_preview": "Keskusteluiden vastauslinkkien esikatselu",
-    "reply_visibility_all": "Näytä kaikki vastaukset",
-    "reply_visibility_following": "Näytä vain vastaukset minulle tai seuraamilleni käyttäjille",
-    "reply_visibility_self": "Näytä vain vastaukset minulle",
-    "saving_err": "Virhe tallentaessa asetuksia",
-    "saving_ok": "Asetukset tallennettu",
-    "security_tab": "Tietoturva",
-    "scope_copy": "Kopioi näkyvyysrajaus vastatessa (Yksityisviestit aina kopioivat)",
-    "set_new_avatar": "Aseta uusi profiilikuva",
-    "set_new_profile_background": "Aseta uusi taustakuva",
-    "set_new_profile_banner": "Aseta uusi juliste",
-    "settings": "Asetukset",
-    "subject_input_always_show": "Näytä aihe-kenttä",
-    "subject_line_behavior": "Aihe-kentän kopiointi",
-    "subject_line_email": "Kuten sähköposti: \"re: aihe\"",
-    "subject_line_mastodon": "Kopioi sellaisenaan",
-    "subject_line_noop": "Älä kopioi",
-    "stop_gifs": "Toista giffit vain kohdistaessa",
-    "streaming": "Näytä uudet viestit automaattisesti ollessasi ruudun huipulla",
-    "text": "Teksti",
-    "theme": "Teema",
-    "theme_help": "Käytä heksadesimaalivärejä muokataksesi väriteemaasi.",
-    "theme_help_v2_1": "Voit asettaa tiettyjen osien värin tai läpinäkyvyyden täyttämällä valintalaatikon, käytä \"Tyhjennä kaikki\"-nappia tyhjentääksesi kaiken.",
-    "theme_help_v2_2": "Ikonit kenttien alla ovat kontrasti-indikaattoreita, lisätietoa kohdistamalla. Käyttäessä läpinäkyvyyttä ne näyttävät pahimman skenaarion.",
-    "tooltipRadius": "Ohje- tai huomioviestit",
-    "user_settings": "Käyttäjän asetukset",
-    "values": {
-      "false": "pois päältä",
-      "true": "päällä"
-    }
-  },
-  "time": {
-    "day": "{0} päivä",
-    "days": "{0} päivää",
-    "day_short": "{0}pv",
-    "days_short": "{0}pv",
-    "hour": "{0} tunti",
-    "hours": "{0} tuntia",
-    "hour_short": "{0}t",
-    "hours_short": "{0}t",
-    "in_future": "{0} tulevaisuudessa",
-    "in_past": "{0} sitten",
-    "minute": "{0} minuutti",
-    "minutes": "{0} minuuttia",
-    "minute_short": "{0}min",
-    "minutes_short": "{0}min",
-    "month": "{0} kuukausi",
-    "months": "{0} kuukautta",
-    "month_short": "{0}kk",
-    "months_short": "{0}kk",
-    "now": "nyt",
-    "now_short": "juuri nyt",
-    "second": "{0} sekunti",
-    "seconds": "{0} sekuntia",
-    "second_short": "{0}s",
-    "seconds_short": "{0}s",
-    "week": "{0} viikko",
-    "weeks": "{0} viikkoa",
-    "week_short": "{0}vk",
-    "weeks_short": "{0}vk",
-    "year": "{0} vuosi",
-    "years": "{0} vuotta",
-    "year_short": "{0}v",
-    "years_short": "{0}v"
-  },
-  "timeline": {
-    "collapse": "Sulje",
-    "conversation": "Keskustelu",
-    "error_fetching": "Virhe ladatessa viestejä",
-    "load_older": "Lataa vanhempia viestejä",
-    "no_retweet_hint": "Viesti ei ole julkinen, eikä sitä voi toistaa",
-    "repeated": "toisti",
-    "show_new": "Näytä uudet",
-    "up_to_date": "Ajantasalla",
-    "no_more_statuses": "Ei enempää viestejä"
-  },
-  "status": {
-    "favorites": "Tykkäykset",
-    "repeats": "Toistot",
-    "delete": "Poista",
-    "pin": "Kiinnitä profiiliisi",
-    "unpin": "Poista kiinnitys",
-    "pinned": "Kiinnitetty",
-    "delete_confirm": "Haluatko varmasti postaa viestin?",
-    "reply_to": "Vastaus",
-    "replies_list": "Vastaukset:",
-    "mute_conversation": "Hiljennä keskustelu",
-    "unmute_conversation": "Poista hiljennys",
-    "status_unavailable": "Viesti ei saatavissa"
-  },
-  "user_card": {
-    "approve": "Hyväksy",
-    "block": "Estä",
-    "blocked": "Estetty!",
-    "deny": "Älä hyväksy",
-    "follow": "Seuraa",
-    "follow_sent": "Pyyntö lähetetty!",
-    "follow_progress": "Pyydetään...",
-    "follow_again": "Lähetä pyyntö uudestaan",
-    "follow_unfollow": "Älä seuraa",
-    "followees": "Seuraa",
-    "followers": "Seuraajat",
-    "following": "Seuraat!",
-    "follows_you": "Seuraa sinua!",
-    "its_you": "Sinun tili!",
-    "mute": "Hiljennä",
-    "muted": "Hiljennetty",
-    "per_day": "päivässä",
-    "remote_follow": "Seuraa muualta",
-    "statuses": "Viestit"
-  },
-  "user_profile": {
-    "timeline_title": "Käyttäjän aikajana"
-  },
-  "who_to_follow": {
-    "more": "Lisää",
-    "who_to_follow": "Seurausehdotukset"
-  },
-  "tool_tip": {
-    "media_upload": "Lataa tiedosto",
-    "repeat": "Toista",
-    "reply": "Vastaa",
-    "favorite": "Tykkää",
-    "user_settings": "Käyttäjäasetukset"
-  },
-  "upload":{
-    "error": {
-      "base": "Lataus epäonnistui.",
-      "file_too_big": "Tiedosto liian suuri [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-      "default": "Yritä uudestaan myöhemmin"
+    "features_panel": {
+        "chat": "Chat",
+        "gopher": "Gopher",
+        "media_proxy": "Media-välityspalvelin",
+        "scope_options": "Näkyvyyden rajaus",
+        "text_limit": "Tekstin pituusraja",
+        "title": "Ominaisuudet",
+        "who_to_follow": "Seurausehdotukset"
     },
-    "file_size_units": {
-      "B": "tavua",
-      "KiB": "kt",
-      "MiB": "Mt",
-      "GiB": "Gt",
-      "TiB": "Tt"
+    "finder": {
+        "error_fetching_user": "Virhe hakiessa käyttäjää",
+        "find_user": "Hae käyttäjä"
+    },
+    "general": {
+        "apply": "Aseta",
+        "submit": "Lähetä",
+        "more": "Lisää",
+        "generic_error": "Virhe tapahtui",
+        "optional": "valinnainen",
+        "show_more": "Näytä lisää",
+        "show_less": "Näytä vähemmän",
+        "dismiss": "Sulje",
+        "cancel": "Peruuta",
+        "disable": "Poista käytöstä",
+        "confirm": "Hyväksy",
+        "verify": "Varmenna",
+        "enable": "Ota käyttöön"
+    },
+    "login": {
+        "login": "Kirjaudu sisään",
+        "description": "Kirjaudu sisään OAuthilla",
+        "logout": "Kirjaudu ulos",
+        "password": "Salasana",
+        "placeholder": "esim. Seppo",
+        "register": "Rekisteröidy",
+        "username": "Käyttäjänimi",
+        "hint": "Kirjaudu sisään liittyäksesi keskusteluun",
+        "authentication_code": "Todennuskoodi",
+        "enter_recovery_code": "Syötä palautuskoodi",
+        "recovery_code": "Palautuskoodi",
+        "heading": {
+            "totp": "Monivaihetodennus",
+            "recovery": "Monivaihepalautus"
+        },
+        "enter_two_factor_code": "Syötä monivaihetodennuskoodi"
+    },
+    "nav": {
+        "about": "Tietoja",
+        "back": "Takaisin",
+        "chat": "Paikallinen Chat",
+        "friend_requests": "Seurauspyynnöt",
+        "mentions": "Maininnat",
+        "interactions": "Interaktiot",
+        "dms": "Yksityisviestit",
+        "public_tl": "Julkinen Aikajana",
+        "timeline": "Aikajana",
+        "twkn": "Koko Tunnettu Verkosto",
+        "user_search": "Käyttäjähaku",
+        "who_to_follow": "Seurausehdotukset",
+        "preferences": "Asetukset",
+        "administration": "Ylläpito",
+        "search": "Haku"
+    },
+    "notifications": {
+        "broken_favorite": "Viestiä ei löydetty...",
+        "favorited_you": "tykkäsi viestistäsi",
+        "followed_you": "seuraa sinua",
+        "load_older": "Lataa vanhempia ilmoituksia",
+        "notifications": "Ilmoitukset",
+        "read": "Lue!",
+        "repeated_you": "toisti viestisi",
+        "no_more_notifications": "Ei enempää ilmoituksia",
+        "reacted_with": "lisäsi reaktion {0}",
+        "migrated_to": "siirtyi sivulle",
+        "follow_request": "haluaa seurata sinua"
+    },
+    "polls": {
+        "add_poll": "Lisää äänestys",
+        "add_option": "Lisää vaihtoehto",
+        "option": "Vaihtoehto",
+        "votes": "ääntä",
+        "vote": "Äänestä",
+        "type": "Äänestyksen tyyppi",
+        "single_choice": "Yksi valinta",
+        "multiple_choices": "Monivalinta",
+        "expiry": "Äänestyksen kesto",
+        "expires_in": "Päättyy {0} päästä",
+        "expired": "Päättyi {0} sitten",
+        "not_enough_option": "Liian vähän uniikkeja vaihtoehtoja äänestyksessä",
+        "not_enough_options": "Liian vähän ainutkertaisia vaihtoehtoja"
+    },
+    "interactions": {
+        "favs_repeats": "Toistot ja tykkäykset",
+        "follows": "Uudet seuraukset",
+        "load_older": "Lataa vanhempia interaktioita",
+        "moves": "Käyttäjien siirtymiset"
+    },
+    "post_status": {
+        "new_status": "Uusi viesti",
+        "account_not_locked_warning": "Tilisi ei ole {0}. Kuka vain voi seurata sinua nähdäksesi 'vain-seuraajille' -viestisi",
+        "account_not_locked_warning_link": "lukittu",
+        "attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi",
+        "content_type": {
+            "text/plain": "Tavallinen teksti",
+            "text/html": "HTML",
+            "text/markdown": "Markdown",
+            "text/bbcode": "BBCode"
+        },
+        "content_warning": "Aihe (valinnainen)",
+        "default": "Tulin juuri saunasta.",
+        "direct_warning": "Tämä viesti näkyy vain mainituille käyttäjille.",
+        "posting": "Lähetetään",
+        "scope": {
+            "direct": "Yksityisviesti - Näkyy vain mainituille käyttäjille",
+            "private": "Vain-seuraajille - Näkyy vain seuraajillesi",
+            "public": "Julkinen - Näkyy julkisilla aikajanoilla",
+            "unlisted": "Listaamaton - Ei näy julkisilla aikajanoilla"
+        },
+        "direct_warning_to_all": "Tämä viesti näkyy vain viestissä mainituille käyttäjille.",
+        "direct_warning_to_first_only": "Tämä viesti näkyy vain viestin alussa mainituille käyttäjille.",
+        "scope_notice": {
+            "public": "Tämä viesti näkyy kaikille",
+            "private": "Tämä viesti näkyy vain sinun seuraajillesi",
+            "unlisted": "Tämä viesti ei näy Julkisella Aikajanalla tai Koko Tunnettu Verkosto -aikajanalla"
+        }
+    },
+    "registration": {
+        "bio": "Kuvaus",
+        "email": "Sähköposti",
+        "fullname": "Koko nimi",
+        "password_confirm": "Salasanan vahvistaminen",
+        "registration": "Rekisteröityminen",
+        "token": "Kutsuvaltuus",
+        "captcha": "Varmenne",
+        "new_captcha": "Paina kuvaa saadaksesi uuden varmenteen",
+        "validations": {
+            "username_required": "ei voi olla tyhjä",
+            "fullname_required": "ei voi olla tyhjä",
+            "email_required": "ei voi olla tyhjä",
+            "password_required": "ei voi olla tyhjä",
+            "password_confirmation_required": "ei voi olla tyhjä",
+            "password_confirmation_match": "pitää vastata salasanaa"
+        },
+        "username_placeholder": "esim. peke",
+        "fullname_placeholder": "esim. Pekka Postaaja",
+        "bio_placeholder": "esim.\nHei, olen Pekka.\nOlen esimerkkikäyttäjä tässä verkostossa."
+    },
+    "settings": {
+        "attachmentRadius": "Liitteet",
+        "attachments": "Liitteet",
+        "autoload": "Lataa vanhempia viestejä automaattisesti ruudun pohjalla",
+        "avatar": "Profiilikuva",
+        "avatarAltRadius": "Profiilikuvat (ilmoitukset)",
+        "avatarRadius": "Profiilikuvat",
+        "background": "Tausta",
+        "bio": "Kuvaus",
+        "btnRadius": "Napit",
+        "cBlue": "Sininen (Vastaukset, seuraukset)",
+        "cGreen": "Vihreä (Toistot)",
+        "cOrange": "Oranssi (Tykkäykset)",
+        "cRed": "Punainen (Peruminen)",
+        "change_password": "Vaihda salasana",
+        "change_password_error": "Virhe vaihtaessa salasanaa.",
+        "changed_password": "Salasana vaihdettu!",
+        "collapse_subject": "Minimoi viestit, joille on asetettu aihe",
+        "composing": "Viestien laatiminen",
+        "confirm_new_password": "Vahvista uusi salasana",
+        "current_avatar": "Nykyinen profiilikuvasi",
+        "current_password": "Nykyinen salasana",
+        "current_profile_banner": "Nykyinen julisteesi",
+        "data_import_export_tab": "Tietojen tuonti / vienti",
+        "default_vis": "Oletusnäkyvyysrajaus",
+        "delete_account": "Poista tili",
+        "delete_account_description": "Poista tilisi ja viestisi pysyvästi.",
+        "delete_account_error": "Virhe poistaessa tiliäsi. Jos virhe jatkuu, ota yhteyttä palvelimesi ylläpitoon.",
+        "delete_account_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.",
+        "emoji_reactions_on_timeline": "Näytä emojireaktiot aikajanalla",
+        "export_theme": "Tallenna teema",
+        "filtering": "Suodatus",
+        "filtering_explanation": "Kaikki viestit, jotka sisältävät näitä sanoja, suodatetaan. Yksi sana per rivi.",
+        "follow_export": "Seurausten vienti",
+        "follow_export_button": "Vie seurauksesi CSV-tiedostoon",
+        "follow_export_processing": "Käsitellään, sinua pyydetään lataamaan tiedosto hetken päästä",
+        "follow_import": "Seurausten tuonti",
+        "follow_import_error": "Virhe tuodessa seuraksia",
+        "follows_imported": "Seuraukset tuotu! Niiden käsittely vie hetken.",
+        "foreground": "Etuala",
+        "general": "Yleinen",
+        "hide_attachments_in_convo": "Piilota liitteet keskusteluissa",
+        "hide_attachments_in_tl": "Piilota liitteet aikajanalla",
+        "max_thumbnails": "Suurin sallittu määrä liitteitä esikatselussa",
+        "hide_isp": "Piilota palvelimenkohtainen ruutu",
+        "preload_images": "Esilataa kuvat",
+        "use_one_click_nsfw": "Avaa NSFW-liitteet yhdellä painalluksella",
+        "hide_post_stats": "Piilota viestien statistiikka (esim. tykkäysten määrä)",
+        "hide_user_stats": "Piilota käyttäjien statistiikka (esim. seuraajien määrä)",
+        "import_followers_from_a_csv_file": "Tuo seuraukset CSV-tiedostosta",
+        "import_theme": "Tuo tallennettu teema",
+        "inputRadius": "Syöttökentät",
+        "checkboxRadius": "Valintalaatikot",
+        "instance_default": "(oletus: {value})",
+        "instance_default_simple": "(oletus)",
+        "interface": "Käyttöliittymä",
+        "interfaceLanguage": "Käyttöliittymän kieli",
+        "invalid_theme_imported": "Tuotu tallennettu teema on epäkelpo, muutoksia ei tehty nykyiseen teemaasi.",
+        "limited_availability": "Ei saatavilla selaimessasi",
+        "links": "Linkit",
+        "lock_account_description": "Vain erikseen hyväksytyt käyttäjät voivat seurata tiliäsi",
+        "loop_video": "Uudelleentoista videot",
+        "loop_video_silent_only": "Uudelleentoista ainoastaan äänettömät videot (Video-\"giffit\")",
+        "play_videos_in_modal": "Toista videot modaalissa",
+        "use_contain_fit": "Älä rajaa liitteitä esikatselussa",
+        "name": "Nimi",
+        "name_bio": "Nimi ja kuvaus",
+        "new_password": "Uusi salasana",
+        "notification_visibility": "Ilmoitusten näkyvyys",
+        "notification_visibility_follows": "Seuraukset",
+        "notification_visibility_likes": "Tykkäykset",
+        "notification_visibility_mentions": "Maininnat",
+        "notification_visibility_repeats": "Toistot",
+        "notification_visibility_emoji_reactions": "Reaktiot",
+        "no_rich_text_description": "Älä näytä tekstin muotoilua",
+        "hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
+        "nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
+        "oauth_tokens": "OAuth-merkit",
+        "token": "Token",
+        "refresh_token": "Päivitä token",
+        "valid_until": "Voimassa asti",
+        "revoke_token": "Peruuta",
+        "panelRadius": "Ruudut",
+        "pause_on_unfocused": "Pysäytä automaattinen viestien näyttö välilehden ollessa pois fokuksesta",
+        "presets": "Valmiit teemat",
+        "profile_background": "Taustakuva",
+        "profile_banner": "Juliste",
+        "profile_tab": "Profiili",
+        "radii_help": "Aseta reunojen pyöristys (pikseleinä)",
+        "replies_in_timeline": "Keskustelut aikajanalla",
+        "reply_link_preview": "Keskusteluiden vastauslinkkien esikatselu",
+        "reply_visibility_all": "Näytä kaikki vastaukset",
+        "reply_visibility_following": "Näytä vain vastaukset minulle tai seuraamilleni käyttäjille",
+        "reply_visibility_self": "Näytä vain vastaukset minulle",
+        "saving_err": "Virhe tallentaessa asetuksia",
+        "saving_ok": "Asetukset tallennettu",
+        "security_tab": "Tietoturva",
+        "scope_copy": "Kopioi näkyvyysrajaus vastatessa (Yksityisviestit aina kopioivat)",
+        "set_new_avatar": "Aseta uusi profiilikuva",
+        "set_new_profile_background": "Aseta uusi taustakuva",
+        "set_new_profile_banner": "Aseta uusi juliste",
+        "settings": "Asetukset",
+        "subject_input_always_show": "Näytä aihe-kenttä",
+        "subject_line_behavior": "Aihe-kentän kopiointi",
+        "subject_line_email": "Kuten sähköposti: \"re: aihe\"",
+        "subject_line_mastodon": "Kopioi sellaisenaan",
+        "subject_line_noop": "Älä kopioi",
+        "stop_gifs": "Toista giffit vain kohdistaessa",
+        "streaming": "Näytä uudet viestit automaattisesti ollessasi ruudun huipulla",
+        "text": "Teksti",
+        "theme": "Teema",
+        "theme_help": "Käytä heksadesimaalivärejä muokataksesi väriteemaasi.",
+        "theme_help_v2_1": "Voit asettaa tiettyjen osien värin tai läpinäkyvyyden täyttämällä valintalaatikon, käytä \"Tyhjennä kaikki\"-nappia tyhjentääksesi kaiken.",
+        "theme_help_v2_2": "Ikonit kenttien alla ovat kontrasti-indikaattoreita, lisätietoa kohdistamalla. Käyttäessä läpinäkyvyyttä ne näyttävät pahimman skenaarion.",
+        "tooltipRadius": "Ohje- tai huomioviestit",
+        "user_settings": "Käyttäjän asetukset",
+        "values": {
+            "false": "pois päältä",
+            "true": "päällä"
+        },
+        "hide_follows_description": "Älä näytä ketä seuraan",
+        "show_moderator_badge": "Näytä Moderaattori-merkki profiilissani",
+        "useStreamingApi": "Vastaanota viestiejä ja ilmoituksia reaaliajassa",
+        "notification_setting_filters": "Suodattimet",
+        "notification_setting": "Vastaanota ilmoituksia seuraavista:",
+        "notification_setting_privacy_option": "Piilota lähettäjä ja sisältö sovelluksen ulkopuolisista ilmoituksista",
+        "enable_web_push_notifications": "Ota käyttöön sovelluksen ulkopuoliset ilmoitukset",
+        "app_name": "Sovelluksen nimi",
+        "security": "Turvallisuus",
+        "mfa": {
+            "otp": "OTP",
+            "setup_otp": "OTP-asetukset",
+            "wait_pre_setup_otp": "esiasetetaan OTP:ta",
+            "confirm_and_enable": "Hyväksy ja käytä OTP",
+            "title": "Monivaihetodennus",
+            "generate_new_recovery_codes": "Luo uudet palautuskoodit",
+            "authentication_methods": "Todennus",
+            "warning_of_generate_new_codes": "Luodessasi uudet palautuskoodit, vanhat koodisi lakkaavat toimimasta.",
+            "recovery_codes": "Palautuskoodit.",
+            "waiting_a_recovery_codes": "Odotetaan palautuskoodeja...",
+            "recovery_codes_warning": "Kirjoita koodit ylös tai tallenna ne turvallisesti, muuten et näe niitä uudestaan. Jos et voi käyttää monivaihetodennusta ja sinulla ei ole palautuskoodeja, et voi enää kirjautua sisään tilillesi."
+        },
+        "allow_following_move": "Salli automaattinen seuraaminen kun käyttäjä siirtää tilinsä",
+        "block_export": "Estojen vienti",
+        "block_export_button": "Vie estosi CSV-tiedostoon",
+        "block_import": "Estojen tuonti",
+        "block_import_error": "Virhe tuodessa estoja",
+        "blocks_imported": "Estot tuotu! Käsittely vie hetken.",
+        "blocks_tab": "Estot",
+        "change_email": "Vaihda sähköpostiosoite",
+        "change_email_error": "Virhe vaihtaessa sähköpostiosoitetta.",
+        "changed_email": "Sähköpostiosoite vaihdettu!",
+        "domain_mutes": "Sivut",
+        "avatar_size_instruction": "Suositeltu vähimmäiskoko profiilikuville on 150x150 pikseliä.",
+        "accent": "Korostus",
+        "hide_muted_posts": "Piilota mykistettyjen käyttäjien viestit",
+        "hide_filtered_statuses": "Piilota mykistetyt viestit",
+        "import_blocks_from_a_csv_file": "Tuo estot CSV-tiedostosta",
+        "no_blocks": "Ei estoja",
+        "no_mutes": "Ei mykistyksiä",
+        "notification_visibility_moves": "Käyttäjien siirtymiset",
+        "hide_followers_description": "Älä näytä ketkä seuraavat minua",
+        "hide_follows_count_description": "Älä näytä seurauksien määrää",
+        "hide_followers_count_description": "Älä näytä seuraajien määrää",
+        "show_admin_badge": "Näytä Ylläpitäjä-merkki proofilissani",
+        "autohide_floating_post_button": "Piilota Uusi Viesti -nappi automaattisesti (mobiili)",
+        "search_user_to_block": "Hae estettäviä käyttäjiä",
+        "search_user_to_mute": "Hae mykistettäviä käyttäjiä",
+        "minimal_scopes_mode": "Yksinkertaista näkyvyydenrajauksen vaihtoehdot",
+        "post_status_content_type": "Uuden viestin sisällön muoto",
+        "user_mutes": "Käyttäjät",
+        "useStreamingApiWarning": "(Kokeellinen)",
+        "type_domains_to_mute": "Syötä mykistettäviä sivustoja",
+        "upload_a_photo": "Lataa kuva",
+        "fun": "Hupi",
+        "greentext": "Meeminuolet",
+        "notifications": "Ilmoitukset",
+        "style": {
+            "switcher": {
+                "save_load_hint": "\"Säilytä\" asetukset säilyttävät tällä hetkellä asetetut asetukset valittaessa tai ladatessa teemaa, se myös tallentaa kyseiset asetukset viedessä teemaa. Kun kaikki laatikot ovat tyhjänä, viety teema tallentaa kaiken.",
+                "help": {
+                    "older_version_imported": "Tuomasi tiedosto on luotu vanhemmalla versiolla.",
+                    "fe_upgraded": "PleromaFE:n teemaus päivitetty versiopäivityksen yhteydessä.",
+                    "migration_snapshot_ok": "Varmuuden vuoksi teeman kaappaus ladattu. Voit koittaa ladata teeman sisällön.",
+                    "migration_napshot_gone": "Jostain syystä teeman kaappaus puuttuu, kaikki asiat eivät välttämättä näytä oikealta.",
+                    "snapshot_source_mismatch": "Versiot eivät täsmää: todennäköisesti versio vaihdettu vanhempaan ja päivitetty uudestaan, jos vaihdoit teemaa vanhalla versiolla, sinun tulisi käyttää vanhaa versiota, muutoin uutta.",
+                    "upgraded_from_v2": "PleromaFE on päivitetty, teemasi saattaa näyttää erilaiselta kuin muistat.",
+                    "v2_imported": "Tuomasi tiedosto on luotu vanhemmalla versiolla. Yhteensopivuus ei välttämättä ole täydellinen.",
+                    "future_version_imported": "Tuomasi tiedosto on luotu uudemmalla versiolla.",
+                    "snapshot_present": "Teeman kaappaus ladattu, joten kaikki arvot ovat ylikirjoitettu. Voit sen sijaan ladata teeman sisällön.",
+                    "snapshot_missing": "Teeman kaappausta ei tiedostossa, joten se voi näyttää erilaiselta kuin suunniteltu.",
+                    "fe_downgraded": "PleromaFE:n versio vaihtunut vanhempaan."
+                },
+                "keep_color": "Säilytä värit",
+                "keep_shadows": "Säilytä varjot",
+                "keep_opacity": "Säilytä läpinäkyvyys",
+                "keep_roundness": "Säilytä pyöristys",
+                "keep_fonts": "Säilytä fontit",
+                "reset": "Palauta",
+                "clear_all": "Tyhjennä kaikki",
+                "clear_opacity": "Tyhjennä läpinäkyvyys",
+                "load_theme": "Lataa teema",
+                "keep_as_is": "Pidä sellaisenaan",
+                "use_snapshot": "Vanha",
+                "use_source": "Uusi"
+            },
+            "advanced_colors": {
+                "selectedPost": "Valittu viesti",
+                "_tab_label": "Edistynyt",
+                "alert": "Varoituksen tausta",
+                "alert_error": "Virhe",
+                "alert_warning": "Varoitus",
+                "alert_neutral": "Neutraali",
+                "post": "Viestit/Käyttäjien kuvaukset",
+                "badge": "Merkin tausta",
+                "badge_notification": "Ilmoitus",
+                "panel_header": "Ruudun otsikko",
+                "top_bar": "Yläpalkki",
+                "borders": "Reunat",
+                "buttons": "Napit",
+                "inputs": "Syöttökentät",
+                "faint_text": "Häivytetty teksti",
+                "underlay": "Taustapeite",
+                "poll": "Äänestyksen kuvaaja",
+                "icons": "Ikonit",
+                "highlight": "Korostetut elementit",
+                "pressed": "Painettu"
+            },
+            "common": {
+                "color": "Väri",
+                "opacity": "Läpinäkyvyys",
+                "contrast": {
+                    "level": {
+                        "aaa": "saavuttaa AAA-tason (suositeltu)",
+                        "aa": "saavuttaa AA-tason (minimi)",
+                        "bad": "ei saavuta mitään helppokäyttöisyyssuosituksia"
+                    },
+                    "hint": "Kontrastisuhde on {ratio}, se {level} {context}",
+                    "context": {
+                        "18pt": "suurella (18pt+) tekstillä",
+                        "text": "tekstillä"
+                    }
+                }
+            },
+            "common_colors": {
+                "_tab_label": "Yleinen",
+                "main": "Yleiset värit",
+                "foreground_hint": "Löydät \"Edistynyt\"-välilehdeltä tarkemmat asetukset",
+                "rgbo": "Ikonit, korostukset, merkit"
+            }
+        },
+        "enter_current_password_to_confirm": "Syötä nykyinen salasanasi todentaaksesi henkilöllisyytesi",
+        "discoverable": "Salli tilisi näkyvyys hakukoneisiin ja muihin palveluihin",
+        "pad_emoji": "Välistä emojit välilyönneillä lisätessäsi niitä valitsimesta",
+        "mutes_tab": "Mykistykset",
+        "new_email": "Uusi sähköpostiosoite",
+        "notification_setting_follows": "Käyttäjät joita seuraat",
+        "notification_setting_non_follows": "Käyttäjät joita et seuraa",
+        "notification_setting_followers": "Käyttäjät jotka seuraavat sinua",
+        "notification_setting_non_followers": "Käyttäjät jotka eivät seuraa sinua",
+        "notification_setting_privacy": "Yksityisyys",
+        "notification_mutes": "Jos et halua ilmoituksia joltain käyttäjältä, käytä mykistystä.",
+        "notification_blocks": "Estäminen pysäyttää kaikki ilmoitukset käyttäjältä ja poistaa seurauksen."
+    },
+    "time": {
+        "day": "{0} päivä",
+        "days": "{0} päivää",
+        "day_short": "{0}pv",
+        "days_short": "{0}pv",
+        "hour": "{0} tunti",
+        "hours": "{0} tuntia",
+        "hour_short": "{0}t",
+        "hours_short": "{0}t",
+        "in_future": "{0} tulevaisuudessa",
+        "in_past": "{0} sitten",
+        "minute": "{0} minuutti",
+        "minutes": "{0} minuuttia",
+        "minute_short": "{0}min",
+        "minutes_short": "{0}min",
+        "month": "{0} kuukausi",
+        "months": "{0} kuukautta",
+        "month_short": "{0}kk",
+        "months_short": "{0}kk",
+        "now": "nyt",
+        "now_short": "juuri nyt",
+        "second": "{0} sekunti",
+        "seconds": "{0} sekuntia",
+        "second_short": "{0}s",
+        "seconds_short": "{0}s",
+        "week": "{0} viikko",
+        "weeks": "{0} viikkoa",
+        "week_short": "{0}vk",
+        "weeks_short": "{0}vk",
+        "year": "{0} vuosi",
+        "years": "{0} vuotta",
+        "year_short": "{0}v",
+        "years_short": "{0}v"
+    },
+    "timeline": {
+        "collapse": "Sulje",
+        "conversation": "Keskustelu",
+        "error_fetching": "Virhe ladatessa viestejä",
+        "load_older": "Lataa vanhempia viestejä",
+        "no_retweet_hint": "Viesti ei ole julkinen, eikä sitä voi toistaa",
+        "repeated": "toisti",
+        "show_new": "Näytä uudet",
+        "up_to_date": "Ajantasalla",
+        "no_more_statuses": "Ei enempää viestejä"
+    },
+    "status": {
+        "favorites": "Tykkäykset",
+        "repeats": "Toistot",
+        "delete": "Poista",
+        "pin": "Kiinnitä profiiliisi",
+        "unpin": "Poista kiinnitys",
+        "pinned": "Kiinnitetty",
+        "delete_confirm": "Haluatko varmasti postaa viestin?",
+        "reply_to": "Vastaus",
+        "replies_list": "Vastaukset:",
+        "mute_conversation": "Hiljennä keskustelu",
+        "unmute_conversation": "Poista hiljennys",
+        "status_unavailable": "Viesti ei saatavissa"
+    },
+    "user_card": {
+        "approve": "Hyväksy",
+        "block": "Estä",
+        "blocked": "Estetty!",
+        "deny": "Älä hyväksy",
+        "follow": "Seuraa",
+        "follow_sent": "Pyyntö lähetetty!",
+        "follow_progress": "Pyydetään...",
+        "follow_again": "Lähetä pyyntö uudestaan",
+        "follow_unfollow": "Älä seuraa",
+        "followees": "Seuraa",
+        "followers": "Seuraajat",
+        "following": "Seuraat!",
+        "follows_you": "Seuraa sinua!",
+        "its_you": "Sinun tili!",
+        "mute": "Hiljennä",
+        "muted": "Hiljennetty",
+        "per_day": "päivässä",
+        "remote_follow": "Seuraa muualta",
+        "statuses": "Viestit"
+    },
+    "user_profile": {
+        "timeline_title": "Käyttäjän aikajana"
+    },
+    "who_to_follow": {
+        "more": "Lisää",
+        "who_to_follow": "Seurausehdotukset"
+    },
+    "tool_tip": {
+        "media_upload": "Lataa tiedosto",
+        "repeat": "Toista",
+        "reply": "Vastaa",
+        "favorite": "Tykkää",
+        "user_settings": "Käyttäjäasetukset"
+    },
+    "upload": {
+        "error": {
+            "base": "Lataus epäonnistui.",
+            "file_too_big": "Tiedosto liian suuri [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+            "default": "Yritä uudestaan myöhemmin"
+        },
+        "file_size_units": {
+            "B": "tavua",
+            "KiB": "kt",
+            "MiB": "Mt",
+            "GiB": "Gt",
+            "TiB": "Tt"
+        }
+    },
+    "about": {
+        "mrf": {
+            "keyword": {
+                "keyword_policies": "Avainsanasäännöt",
+                "ftl_removal": "Poistettu \"Koko Tunnettu Verkosto\" -aikajanalta",
+                "reject": "Hylkää",
+                "replace": "Korvaa",
+                "is_replaced_by": "→"
+            },
+            "simple": {
+                "accept": "Hyväksy",
+                "reject": "Hylkää",
+                "quarantine": "Karanteeni",
+                "ftl_removal": "Poisto \"Koko Tunnettu Verkosto\" -aikajanalta",
+                "media_removal": "Media-tiedostojen poisto"
+            },
+            "federation": "Federaatio",
+            "mrf_policies": "Aktivoidut MRF-säännöt"
+        },
+        "staff": "Henkilökunta"
+    },
+    "domain_mute_card": {
+        "mute": "Mykistä",
+        "unmute": "Poista mykistys",
+        "mute_progress": "Mykistetään...",
+        "unmute_progress": "Poistetaan mykistyst..."
+    },
+    "exporter": {
+        "export": "Vie",
+        "processing": "Käsitellään, hetken päästä voit tallentaa tiedoston"
+    },
+    "image_cropper": {
+        "crop_picture": "Rajaa kuva",
+        "save": "Tallenna",
+        "save_without_cropping": "Tallenna rajaamatta",
+        "cancel": "Peruuta"
+    },
+    "importer": {
+        "submit": "Hyväksy",
+        "error": "Virhe tapahtui tietoja tuodessa.",
+        "success": "Tuonti onnistui."
+    },
+    "media_modal": {
+        "previous": "Edellinen",
+        "next": "Seuraava"
+    },
+    "emoji": {
+        "stickers": "Tarrat",
+        "emoji": "Emoji",
+        "keep_open": "Pidä valitsin auki",
+        "search_emoji": "Hae emojia",
+        "add_emoji": "Lisää emoji",
+        "custom": "Custom-emoji",
+        "load_all": "Ladataan kaikkia {emojiAmount} emojia",
+        "unicode": "Unicode-emoji",
+        "load_all_hint": "Ensimmäiset {saneAmount} emojia ladattu, kaikkien emojien lataaminen voi aiheuttaa hidastelua."
+    },
+    "remote_user_resolver": {
+        "remote_user_resolver": "Ulkopuolinen käyttäjä",
+        "searching_for": "Etsitään käyttäjää",
+        "error": "Ei löytynyt."
+    },
+    "selectable_list": {
+        "select_all": "Valitse kaikki"
     }
-  }
 }

From a26731de300209ab3aa6a1e61fe6415f6facca94 Mon Sep 17 00:00:00 2001
From: Toro Mino <mail@dennisbuchholz.net>
Date: Wed, 13 May 2020 17:16:29 +0000
Subject: [PATCH 335/483] Translated using Weblate (German)

Currently translated at 66.7% (409 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 913 ++++++++++++++++++++++++++---------------------
 1 file changed, 508 insertions(+), 405 deletions(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index a4b4c16f..4037b4b3 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -1,413 +1,516 @@
 {
-  "chat": {
-    "title": "Chat"
-  },
-  "features_panel": {
-    "chat": "Chat",
-    "gopher": "Gopher",
-    "media_proxy": "Medienproxy",
-    "scope_options": "Reichweitenoptionen",
-    "text_limit": "Textlimit",
-    "title": "Features",
-    "who_to_follow": "Wem folgen?"
-  },
-  "finder": {
-    "error_fetching_user": "Fehler beim Suchen des Benutzers",
-    "find_user": "Finde Benutzer"
-  },
-  "general": {
-    "apply": "Anwenden",
-    "submit": "Absenden"
-  },
-  "login": {
-    "login": "Anmelden",
-    "description": "Mit OAuth anmelden",
-    "logout": "Abmelden",
-    "password": "Passwort",
-    "placeholder": "z.B. lain",
-    "register": "Registrieren",
-    "username": "Benutzername"
-  },
-  "nav": {
-    "about": "Über",
-    "back": "Zurück",
-    "chat": "Lokaler Chat",
-    "friend_requests": "Followanfragen",
-    "mentions": "Erwähnungen",
-    "interactions": "Interaktionen",
-    "dms": "Direktnachrichten",
-    "public_tl": "Öffentliche Zeitleiste",
-    "timeline": "Zeitleiste",
-    "twkn": "Das gesamte bekannte Netzwerk",
-    "user_search": "Benutzersuche",
-    "search": "Suche",
-    "preferences": "Voreinstellungen"
-  },
-  "notifications": {
-    "broken_favorite": "Unbekannte Nachricht, suche danach...",
-    "favorited_you": "favorisierte deine Nachricht",
-    "followed_you": "folgt dir",
-    "load_older": "Ältere Benachrichtigungen laden",
-    "notifications": "Benachrichtigungen",
-    "read": "Gelesen!",
-    "repeated_you": "wiederholte deine Nachricht"
-  },
-  "post_status": {
-    "new_status": "Neuen Status veröffentlichen",
-    "account_not_locked_warning": "Dein Profil ist nicht {0}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.",
-    "account_not_locked_warning_link": "gesperrt",
-    "attachments_sensitive": "Anhänge als heikel markieren",
-    "content_type": {
-      "text/plain": "Nur Text"
+    "chat": {
+        "title": "Chat"
     },
-    "content_warning": "Betreff (optional)",
-    "default": "Sitze gerade im Hofbräuhaus.",
-    "direct_warning": "Dieser Beitrag wird nur für die erwähnten Nutzer sichtbar sein.",
-    "posting": "Veröffentlichen",
-    "scope": {
-      "direct": "Direkt - Beitrag nur an erwähnte Profile",
-      "private": "Nur Follower - Beitrag nur für Follower sichtbar",
-      "public": "Öffentlich - Beitrag an öffentliche Zeitleisten",
-      "unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
-    }
-  },
-  "registration": {
-    "bio": "Bio",
-    "email": "Email",
-    "fullname": "Angezeigter Name",
-    "password_confirm": "Passwort bestätigen",
-    "registration": "Registrierung",
-    "token": "Einladungsschlüssel",
-    "captcha": "CAPTCHA",
-    "new_captcha": "Zum Erstellen eines neuen Captcha auf das Bild klicken.",
-    "validations": {
-      "username_required": "darf nicht leer sein",
-      "fullname_required": "darf nicht leer sein",
-      "email_required": "darf nicht leer sein",
-      "password_required": "darf nicht leer sein",
-      "password_confirmation_required": "darf nicht leer sein",
-      "password_confirmation_match": "sollte mit dem Passwort identisch sein."
-    }
-  },
-  "settings": {
-    "attachmentRadius": "Anhänge",
-    "attachments": "Anhänge",
-    "autoload": "Aktiviere automatisches Laden von älteren Beiträgen beim scrollen",
-    "avatar": "Avatar",
-    "avatarAltRadius": "Avatare (Benachrichtigungen)",
-    "avatarRadius": "Avatare",
-    "background": "Hintergrund",
-    "bio": "Bio",
-    "btnRadius": "Buttons",
-    "cBlue": "Blau (Antworten, Folgt dir)",
-    "cGreen": "Grün (Retweet)",
-    "cOrange": "Orange (Favorisieren)",
-    "cRed": "Rot (Abbrechen)",
-    "change_password": "Passwort ändern",
-    "change_password_error": "Es gab ein Problem bei der Änderung des Passworts.",
-    "changed_password": "Passwort erfolgreich geändert!",
-    "collapse_subject": "Beiträge mit Betreff einklappen",
-    "composing": "Verfassen",
-    "confirm_new_password": "Neues Passwort bestätigen",
-    "current_avatar": "Dein derzeitiger Avatar",
-    "current_password": "Aktuelles Passwort",
-    "current_profile_banner": "Der derzeitige Banner deines Profils",
-    "data_import_export_tab": "Datenimport/-export",
-    "default_vis": "Standard-Sichtbarkeitsumfang",
-    "delete_account": "Account löschen",
-    "delete_account_description": "Lösche deinen Account und alle deine Nachrichten unwiderruflich.",
-    "delete_account_error": "Es ist ein Fehler beim Löschen deines Accounts aufgetreten. Tritt dies weiterhin auf, wende dich an den Administrator der Instanz.",
-    "delete_account_instructions": "Tippe dein Passwort unten in das Feld ein, um die Löschung deines Accounts zu bestätigen.",
-    "discoverable": "Erlaubnis für automatisches Suchen nach diesem Account",
-    "avatar_size_instruction": "Die empfohlene minimale Größe für Avatare ist 150x150 Pixel.",
-    "pad_emoji": "Emojis mit Leerzeichen umrahmen",
-    "export_theme": "Farbschema speichern",
-    "filtering": "Filtern",
-    "filtering_explanation": "Alle Beiträge die diese Wörter enthalten werden ausgeblendet. Ein Wort pro Zeile.",
-    "follow_export": "Follower exportieren",
-    "follow_export_button": "Exportiere deine Follows in eine csv-Datei",
-    "follow_export_processing": "In Bearbeitung. Die Liste steht gleich zum herunterladen bereit.",
-    "follow_import": "Followers importieren",
-    "follow_import_error": "Fehler beim importieren der Follower",
-    "follows_imported": "Followers importiert! Die Bearbeitung kann eine Zeit lang dauern.",
-    "foreground": "Vordergrund",
-    "general": "Allgemein",
-    "hide_attachments_in_convo": "Anhänge in Unterhaltungen ausblenden",
-    "hide_attachments_in_tl": "Anhänge in der Zeitleiste ausblenden",
-    "hide_muted_posts": "Verberge Beiträge stummgeschalteter Nutzer",
-    "max_thumbnails": "Maximale Anzahl von Vorschaubildern pro Beitrag",
-    "hide_isp": "Instanz-spezifisches Panel ausblenden",
-    "preload_images": "Bilder vorausladen",
-    "use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen",
-    "hide_post_stats": "Beitragsstatistiken verbergen (z.B. die Anzahl der Favoriten)",
-    "hide_user_stats": "Benutzerstatistiken verbergen (z.B. die Anzahl der Follower)",
-    "hide_filtered_statuses": "Gefilterte Beiträge verbergen",
-    "import_followers_from_a_csv_file": "Importiere Follower, denen du folgen möchtest, aus einer CSV-Datei",
-    "import_theme": "Farbschema laden",
-    "inputRadius": "Eingabefelder",
-    "checkboxRadius": "Auswahlfelder",
-    "instance_default": "(Standard: {value})",
-    "instance_default_simple": "(Standard)",
-    "interface": "Oberfläche",
-    "interfaceLanguage": "Sprache der Oberfläche",
-    "invalid_theme_imported": "Die ausgewählte Datei ist kein unterstütztes Pleroma-Theme. Keine Änderungen wurden vorgenommen.",
-    "limited_availability": "In deinem Browser nicht verfügbar",
-    "links": "Links",
-    "lock_account_description": "Sperre deinen Account, um neue Follower zu genehmigen oder abzulehnen",
-    "loop_video": "Videos wiederholen",
-    "loop_video_silent_only": "Nur Videos ohne Ton wiederholen (z.B. Mastodons \"gifs\")",
-    "mutes_tab": "Mutes",
-    "play_videos_in_modal": "Videos in größerem Medienfenster abspielen",
-    "use_contain_fit": "Vorschaubilder nicht zuschneiden",
-    "name": "Name",
-    "name_bio": "Name & Bio",
-    "new_password": "Neues Passwort",
-    "notification_visibility": "Benachrichtigungstypen, die angezeigt werden sollen",
-    "notification_visibility_follows": "Follows",
-    "notification_visibility_likes": "Favoriten",
-    "notification_visibility_mentions": "Erwähnungen",
-    "notification_visibility_repeats": "Wiederholungen",
-    "no_rich_text_description": "Rich-Text Formatierungen von allen Beiträgen entfernen",
-    "hide_follows_description": "Zeige nicht, wem ich folge",
-    "hide_followers_description": "Zeige nicht, wer mir folgt",
-    "hide_follows_count_description": "Verberge die Anzahl deiner Gefolgten",
-    "hide_followers_count_description": "Verberge die Anzahl deiner Folgenden",
-    "nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind",
-    "oauth_tokens": "OAuth-Token",
-    "token": "Zeichen",
-    "refresh_token": "Token aktualisieren",
-    "valid_until": "Gültig bis",
-    "revoke_token": "Widerrufen",
-    "panelRadius": "Panel",
-    "pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist",
-    "presets": "Voreinstellungen",
-    "profile_background": "Profilhintergrund",
-    "profile_banner": "Profilbanner",
-    "profile_tab": "Profil",
-    "radii_help": "Kantenrundung (in Pixel) der Oberfläche anpassen",
-    "replies_in_timeline": "Antworten in der Zeitleiste",
-    "reply_link_preview": "Antwortlink-Vorschau beim Überfahren mit der Maus aktivieren",
-    "reply_visibility_all": "Alle Antworten zeigen",
-    "reply_visibility_following": "Zeige nur Antworten an mich oder an Benutzer, denen ich folge",
-    "reply_visibility_self": "Nur Antworten an mich anzeigen",
-    "autohide_floating_post_button": "Automatisches Verbergen des Knopfs für neue Beiträge (mobil)",
-    "saving_err": "Fehler beim Speichern der Einstellungen",
-    "saving_ok": "Einstellungen gespeichert",
-    "security_tab": "Sicherheit",
-    "scope_copy": "Reichweite beim Antworten übernehmen (Direktnachrichten werden immer kopiert)",
-    "minimal_scopes_mode": "Minimiere Reichweitenoptionen",
-    "set_new_avatar": "Setze einen neuen Avatar",
-    "set_new_profile_background": "Setze einen neuen Hintergrund für dein Profil",
-    "set_new_profile_banner": "Setze einen neuen Banner für dein Profil",
-    "settings": "Einstellungen",
-    "subject_input_always_show": "Betreff-Feld immer anzeigen",
-    "subject_line_behavior": "Betreff beim Antworten kopieren",
-    "subject_line_email": "Wie Email: \"re: Betreff\"",
-    "subject_line_mastodon": "Wie Mastodon: unverändert kopieren",
-    "subject_line_noop": "Nicht kopieren",
-    "post_status_content_type": "Beitragsart",
-    "stop_gifs": "Animationen nur beim Darüberfahren abspielen",
-    "streaming": "Aktiviere automatisches Laden (Streaming) von neuen Beiträgen",
-    "text": "Text",
-    "theme": "Farbschema",
-    "theme_help": "Benutze HTML-Farbcodes (#rrggbb) um dein Farbschema anzupassen",
-    "theme_help_v2_1": "Du kannst auch die Farben und die Deckkraft bestimmter Komponenten überschreiben, indem du das Kontrollkästchen umschaltest. Verwende die Schaltfläche \"Alle löschen\", um alle Überschreibungen zurückzusetzen.",
-    "theme_help_v2_2": "Unter einigen Einträgen befinden sich Symbole für Hintergrund-/Textkontrastindikatoren, für detaillierte Informationen fahre mit der Maus darüber. Bitte beachte, dass bei der Verwendung von Transparenz Kontrastindikatoren den schlechtest möglichen Fall darstellen.",
-    "tooltipRadius": "Tooltips/Warnungen",
-    "user_settings": "Benutzereinstellungen",
-    "values": {
-      "false": "nein",
-      "true": "Ja"
+    "features_panel": {
+        "chat": "Chat",
+        "gopher": "Gopher",
+        "media_proxy": "Medienproxy",
+        "scope_options": "Reichweitenoptionen",
+        "text_limit": "Zeichenlimit",
+        "title": "Funktionen",
+        "who_to_follow": "Wem folgen?"
     },
-    "notifications": "Benachrichtigungen",
-    "enable_web_push_notifications": "Web-Pushbenachrichtigungen aktivieren",
+    "finder": {
+        "error_fetching_user": "Fehler beim Suchen des Benutzers",
+        "find_user": "Finde Benutzer"
+    },
+    "general": {
+        "apply": "Anwenden",
+        "submit": "Absenden",
+        "more": "Mehr",
+        "generic_error": "Ein Fehler ist aufgetreten",
+        "optional": "Optional",
+        "show_more": "Zeige mehr",
+        "show_less": "Zeige weniger",
+        "dismiss": "Ablehnen",
+        "cancel": "Abbrechen",
+        "disable": "Deaktivieren",
+        "enable": "Aktivieren",
+        "confirm": "Bestätigen",
+        "verify": "Verifizieren"
+    },
+    "login": {
+        "login": "Anmelden",
+        "description": "Mit OAuth anmelden",
+        "logout": "Abmelden",
+        "password": "Passwort",
+        "placeholder": "z.B. lain",
+        "register": "Registrieren",
+        "username": "Benutzername",
+        "authentication_code": "Authentifizierungscode",
+        "enter_recovery_code": "Gebe einen Wiederherstellungscode ein",
+        "recovery_code": "Wiederherstellungscode",
+        "heading": {
+            "totp": "Zwei-Faktor Authentifizierung",
+            "recovery": "Zwei-Faktor Wiederherstellung"
+        },
+        "hint": "Anmelden um an der Diskussion teilzunehmen",
+        "enter_two_factor_code": "Gebe einen Zwei-Faktor-Code ein"
+    },
+    "nav": {
+        "about": "Über",
+        "back": "Zurück",
+        "chat": "Lokaler Chat",
+        "friend_requests": "Followanfragen",
+        "mentions": "Erwähnungen",
+        "interactions": "Interaktionen",
+        "dms": "Direktnachrichten",
+        "public_tl": "Öffentliche Zeitleiste",
+        "timeline": "Zeitleiste",
+        "twkn": "Das gesamte bekannte Netzwerk",
+        "user_search": "Benutzersuche",
+        "search": "Suche",
+        "preferences": "Voreinstellungen",
+        "administration": "Administration",
+        "who_to_follow": "Wem folgen"
+    },
+    "notifications": {
+        "broken_favorite": "Unbekannte Nachricht, suche danach...",
+        "favorited_you": "favorisierte deine Nachricht",
+        "followed_you": "folgt dir",
+        "load_older": "Ältere Benachrichtigungen laden",
+        "notifications": "Benachrichtigungen",
+        "read": "Gelesen!",
+        "repeated_you": "wiederholte deine Nachricht",
+        "follow_request": "möchte dir folgen",
+        "migrated_to": "migrierte zu",
+        "reacted_with": "reagierte mit {0}",
+        "no_more_notifications": "Keine Benachrichtigungen mehr"
+    },
+    "post_status": {
+        "new_status": "Neuen Status veröffentlichen",
+        "account_not_locked_warning": "Dein Profil ist nicht {0}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.",
+        "account_not_locked_warning_link": "gesperrt",
+        "attachments_sensitive": "Anhänge als heikel markieren",
+        "content_type": {
+            "text/plain": "Nur Text"
+        },
+        "content_warning": "Betreff (optional)",
+        "default": "Sitze gerade im Hofbräuhaus.",
+        "direct_warning": "Dieser Beitrag wird nur für die erwähnten Nutzer sichtbar sein.",
+        "posting": "Veröffentlichen",
+        "scope": {
+            "direct": "Direkt - Beitrag nur an erwähnte Profile",
+            "private": "Nur Follower - Beitrag nur für Follower sichtbar",
+            "public": "Öffentlich - Beitrag an öffentliche Zeitleisten",
+            "unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
+        }
+    },
+    "registration": {
+        "bio": "Bio",
+        "email": "Email",
+        "fullname": "Angezeigter Name",
+        "password_confirm": "Passwort bestätigen",
+        "registration": "Registrierung",
+        "token": "Einladungsschlüssel",
+        "captcha": "CAPTCHA",
+        "new_captcha": "Zum Erstellen eines neuen Captcha auf das Bild klicken.",
+        "validations": {
+            "username_required": "darf nicht leer sein",
+            "fullname_required": "darf nicht leer sein",
+            "email_required": "darf nicht leer sein",
+            "password_required": "darf nicht leer sein",
+            "password_confirmation_required": "darf nicht leer sein",
+            "password_confirmation_match": "sollte mit dem Passwort identisch sein."
+        }
+    },
+    "settings": {
+        "attachmentRadius": "Anhänge",
+        "attachments": "Anhänge",
+        "autoload": "Aktiviere automatisches Laden von älteren Beiträgen beim scrollen",
+        "avatar": "Avatar",
+        "avatarAltRadius": "Avatare (Benachrichtigungen)",
+        "avatarRadius": "Avatare",
+        "background": "Hintergrund",
+        "bio": "Bio",
+        "btnRadius": "Buttons",
+        "cBlue": "Blau (Antworten, Folgt dir)",
+        "cGreen": "Grün (Retweet)",
+        "cOrange": "Orange (Favorisieren)",
+        "cRed": "Rot (Abbrechen)",
+        "change_password": "Passwort ändern",
+        "change_password_error": "Es gab ein Problem bei der Änderung des Passworts.",
+        "changed_password": "Passwort erfolgreich geändert!",
+        "collapse_subject": "Beiträge mit Betreff einklappen",
+        "composing": "Verfassen",
+        "confirm_new_password": "Neues Passwort bestätigen",
+        "current_avatar": "Dein derzeitiger Avatar",
+        "current_password": "Aktuelles Passwort",
+        "current_profile_banner": "Der derzeitige Banner deines Profils",
+        "data_import_export_tab": "Datenimport/-export",
+        "default_vis": "Standard-Sichtbarkeitsumfang",
+        "delete_account": "Account löschen",
+        "delete_account_description": "Lösche deinen Account und alle deine Nachrichten unwiderruflich.",
+        "delete_account_error": "Es ist ein Fehler beim Löschen deines Accounts aufgetreten. Tritt dies weiterhin auf, wende dich an den Administrator der Instanz.",
+        "delete_account_instructions": "Tippe dein Passwort unten in das Feld ein, um die Löschung deines Accounts zu bestätigen.",
+        "discoverable": "Erlaubnis für automatisches Suchen nach diesem Account",
+        "avatar_size_instruction": "Die empfohlene minimale Größe für Avatare ist 150x150 Pixel.",
+        "pad_emoji": "Emojis mit Leerzeichen umrahmen",
+        "export_theme": "Farbschema speichern",
+        "filtering": "Filtern",
+        "filtering_explanation": "Alle Beiträge die diese Wörter enthalten werden ausgeblendet. Ein Wort pro Zeile.",
+        "follow_export": "Follower exportieren",
+        "follow_export_button": "Exportiere deine Follows in eine csv-Datei",
+        "follow_export_processing": "In Bearbeitung. Die Liste steht gleich zum herunterladen bereit.",
+        "follow_import": "Followers importieren",
+        "follow_import_error": "Fehler beim importieren der Follower",
+        "follows_imported": "Followers importiert! Die Bearbeitung kann eine Zeit lang dauern.",
+        "foreground": "Vordergrund",
+        "general": "Allgemein",
+        "hide_attachments_in_convo": "Anhänge in Unterhaltungen ausblenden",
+        "hide_attachments_in_tl": "Anhänge in der Zeitleiste ausblenden",
+        "hide_muted_posts": "Verberge Beiträge stummgeschalteter Nutzer",
+        "max_thumbnails": "Maximale Anzahl von Vorschaubildern pro Beitrag",
+        "hide_isp": "Instanz-spezifisches Panel ausblenden",
+        "preload_images": "Bilder vorausladen",
+        "use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen",
+        "hide_post_stats": "Beitragsstatistiken verbergen (z.B. die Anzahl der Favoriten)",
+        "hide_user_stats": "Benutzerstatistiken verbergen (z.B. die Anzahl der Follower)",
+        "hide_filtered_statuses": "Gefilterte Beiträge verbergen",
+        "import_followers_from_a_csv_file": "Importiere Follower, denen du folgen möchtest, aus einer CSV-Datei",
+        "import_theme": "Farbschema laden",
+        "inputRadius": "Eingabefelder",
+        "checkboxRadius": "Auswahlfelder",
+        "instance_default": "(Standard: {value})",
+        "instance_default_simple": "(Standard)",
+        "interface": "Oberfläche",
+        "interfaceLanguage": "Sprache der Oberfläche",
+        "invalid_theme_imported": "Die ausgewählte Datei ist kein unterstütztes Pleroma-Theme. Keine Änderungen wurden vorgenommen.",
+        "limited_availability": "In deinem Browser nicht verfügbar",
+        "links": "Links",
+        "lock_account_description": "Sperre deinen Account, um neue Follower zu genehmigen oder abzulehnen",
+        "loop_video": "Videos wiederholen",
+        "loop_video_silent_only": "Nur Videos ohne Ton wiederholen (z.B. Mastodons \"gifs\")",
+        "mutes_tab": "Mutes",
+        "play_videos_in_modal": "Videos in größerem Medienfenster abspielen",
+        "use_contain_fit": "Vorschaubilder nicht zuschneiden",
+        "name": "Name",
+        "name_bio": "Name & Bio",
+        "new_password": "Neues Passwort",
+        "notification_visibility": "Benachrichtigungstypen, die angezeigt werden sollen",
+        "notification_visibility_follows": "Follows",
+        "notification_visibility_likes": "Favoriten",
+        "notification_visibility_mentions": "Erwähnungen",
+        "notification_visibility_repeats": "Wiederholungen",
+        "no_rich_text_description": "Rich-Text Formatierungen von allen Beiträgen entfernen",
+        "hide_follows_description": "Zeige nicht, wem ich folge",
+        "hide_followers_description": "Zeige nicht, wer mir folgt",
+        "hide_follows_count_description": "Verberge die Anzahl deiner Gefolgten",
+        "hide_followers_count_description": "Verberge die Anzahl deiner Folgenden",
+        "nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind",
+        "oauth_tokens": "OAuth-Token",
+        "token": "Zeichen",
+        "refresh_token": "Token aktualisieren",
+        "valid_until": "Gültig bis",
+        "revoke_token": "Widerrufen",
+        "panelRadius": "Panel",
+        "pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist",
+        "presets": "Voreinstellungen",
+        "profile_background": "Profilhintergrund",
+        "profile_banner": "Profilbanner",
+        "profile_tab": "Profil",
+        "radii_help": "Kantenrundung (in Pixel) der Oberfläche anpassen",
+        "replies_in_timeline": "Antworten in der Zeitleiste",
+        "reply_link_preview": "Antwortlink-Vorschau beim Überfahren mit der Maus aktivieren",
+        "reply_visibility_all": "Alle Antworten zeigen",
+        "reply_visibility_following": "Zeige nur Antworten an mich oder an Benutzer, denen ich folge",
+        "reply_visibility_self": "Nur Antworten an mich anzeigen",
+        "autohide_floating_post_button": "Automatisches Verbergen des Knopfs für neue Beiträge (mobil)",
+        "saving_err": "Fehler beim Speichern der Einstellungen",
+        "saving_ok": "Einstellungen gespeichert",
+        "security_tab": "Sicherheit",
+        "scope_copy": "Reichweite beim Antworten übernehmen (Direktnachrichten werden immer kopiert)",
+        "minimal_scopes_mode": "Minimiere Reichweitenoptionen",
+        "set_new_avatar": "Setze einen neuen Avatar",
+        "set_new_profile_background": "Setze einen neuen Hintergrund für dein Profil",
+        "set_new_profile_banner": "Setze einen neuen Banner für dein Profil",
+        "settings": "Einstellungen",
+        "subject_input_always_show": "Betreff-Feld immer anzeigen",
+        "subject_line_behavior": "Betreff beim Antworten kopieren",
+        "subject_line_email": "Wie Email: \"re: Betreff\"",
+        "subject_line_mastodon": "Wie Mastodon: unverändert kopieren",
+        "subject_line_noop": "Nicht kopieren",
+        "post_status_content_type": "Beitragsart",
+        "stop_gifs": "Animationen nur beim Darüberfahren abspielen",
+        "streaming": "Aktiviere automatisches Laden (Streaming) von neuen Beiträgen",
+        "text": "Text",
+        "theme": "Farbschema",
+        "theme_help": "Benutze HTML-Farbcodes (#rrggbb) um dein Farbschema anzupassen",
+        "theme_help_v2_1": "Du kannst auch die Farben und die Deckkraft bestimmter Komponenten überschreiben, indem du das Kontrollkästchen umschaltest. Verwende die Schaltfläche \"Alle löschen\", um alle Überschreibungen zurückzusetzen.",
+        "theme_help_v2_2": "Unter einigen Einträgen befinden sich Symbole für Hintergrund-/Textkontrastindikatoren, für detaillierte Informationen fahre mit der Maus darüber. Bitte beachte, dass bei der Verwendung von Transparenz Kontrastindikatoren den schlechtest möglichen Fall darstellen.",
+        "tooltipRadius": "Tooltips/Warnungen",
+        "user_settings": "Benutzereinstellungen",
+        "values": {
+            "false": "nein",
+            "true": "Ja"
+        },
+        "notifications": "Benachrichtigungen",
+        "enable_web_push_notifications": "Web-Pushbenachrichtigungen aktivieren",
         "style": {
-      "switcher": {
-        "keep_color": "Farben beibehalten",
-        "keep_shadows": "Schatten beibehalten",
-        "keep_opacity": "Deckkraft beibehalten",
-        "keep_roundness": "Abrundungen beibehalten",
-        "keep_fonts": "Schriften beibehalten",
-        "save_load_hint": "Die \"Beibehalten\"-Optionen behalten die aktuell eingestellten Optionen beim Auswählen oder Laden von Designs bei, sie speichern diese Optionen auch beim Exportieren eines Designs. Wenn alle Kontrollkästchen deaktiviert sind, wird beim Exportieren des Designs alles gespeichert.",
-        "reset": "Zurücksetzen",
-        "clear_all": "Alles leeren",
-        "clear_opacity": "Deckkraft leeren"
-      },
-      "common": {
-        "color": "Farbe",
-        "opacity": "Deckkraft",
-        "contrast": {
-          "hint": "Das Kontrastverhältnis ist {ratio}, es {level} {context}",
-          "level": {
-            "aa": "entspricht Level AA Richtlinie (minimum)",
-            "aaa": "entspricht Level AAA Richtlinie (empfohlen)",
-            "bad": "entspricht keiner Richtlinien zur Barrierefreiheit"
-          },
-          "context": {
-            "18pt": "für großen (18pt+) Text",
-            "text": "für Text"
-          }
+            "switcher": {
+                "keep_color": "Farben beibehalten",
+                "keep_shadows": "Schatten beibehalten",
+                "keep_opacity": "Deckkraft beibehalten",
+                "keep_roundness": "Abrundungen beibehalten",
+                "keep_fonts": "Schriften beibehalten",
+                "save_load_hint": "Die \"Beibehalten\"-Optionen behalten die aktuell eingestellten Optionen beim Auswählen oder Laden von Designs bei, sie speichern diese Optionen auch beim Exportieren eines Designs. Wenn alle Kontrollkästchen deaktiviert sind, wird beim Exportieren des Designs alles gespeichert.",
+                "reset": "Zurücksetzen",
+                "clear_all": "Alles leeren",
+                "clear_opacity": "Deckkraft leeren"
+            },
+            "common": {
+                "color": "Farbe",
+                "opacity": "Deckkraft",
+                "contrast": {
+                    "hint": "Das Kontrastverhältnis ist {ratio}, es {level} {context}",
+                    "level": {
+                        "aa": "entspricht Level AA Richtlinie (minimum)",
+                        "aaa": "entspricht Level AAA Richtlinie (empfohlen)",
+                        "bad": "entspricht keiner Richtlinien zur Barrierefreiheit"
+                    },
+                    "context": {
+                        "18pt": "für großen (18pt+) Text",
+                        "text": "für Text"
+                    }
+                }
+            },
+            "common_colors": {
+                "_tab_label": "Allgemein",
+                "main": "Allgemeine Farben",
+                "foreground_hint": "Siehe Reiter \"Erweitert\" für eine detailliertere Einstellungen",
+                "rgbo": "Symbole, Betonungen, Kennzeichnungen"
+            },
+            "advanced_colors": {
+                "_tab_label": "Erweitert",
+                "alert": "Warnhinweis-Hintergrund",
+                "alert_error": "Fehler",
+                "badge": "Kennzeichnungs-Hintergrund",
+                "badge_notification": "Benachrichtigung",
+                "panel_header": "Panel-Kopf",
+                "top_bar": "Obere Leiste",
+                "borders": "Rahmen",
+                "buttons": "Schaltflächen",
+                "inputs": "Eingabefelder",
+                "faint_text": "Verblasster Text"
+            },
+            "radii": {
+                "_tab_label": "Abrundungen"
+            },
+            "shadows": {
+                "_tab_label": "Schatten und Beleuchtung",
+                "component": "Komponente",
+                "override": "Überschreiben",
+                "shadow_id": "Schatten #{value}",
+                "blur": "Unschärfe",
+                "spread": "Streuung",
+                "inset": "Einsatz",
+                "hint": "Für Schatten kannst du auch --variable als Farbwert verwenden, um CSS3-Variablen zu verwenden. Bitte beachte, dass die Einstellung der Deckkraft in diesem Fall nicht funktioniert.",
+                "filter_hint": {
+                    "always_drop_shadow": "Achtung, dieser Schatten verwendet immer {0}, wenn der Browser dies unterstützt.",
+                    "drop_shadow_syntax": "{0} unterstützt Parameter {1} und Schlüsselwort {2} nicht.",
+                    "avatar_inset": "Bitte beachte, dass die Kombination von eingesetzten und nicht eingesetzten Schatten auf Avataren zu unerwarteten Ergebnissen bei transparenten Avataren führen kann.",
+                    "spread_zero": "Schatten mit einer Streuung > 0 erscheinen so, als ob sie auf Null gesetzt wären.",
+                    "inset_classic": "Eingesetzte Schatten werden mit {0} verwendet"
+                },
+                "components": {
+                    "panel": "Panel",
+                    "panelHeader": "Panel-Kopf",
+                    "topBar": "Obere Leiste",
+                    "avatar": "Benutzer-Avatar (in der Profilansicht)",
+                    "avatarStatus": "Benutzer-Avatar (in der Beitragsanzeige)",
+                    "popup": "Dialogfenster und Hinweistexte",
+                    "button": "Schaltfläche",
+                    "buttonHover": "Schaltfläche (hover)",
+                    "buttonPressed": "Schaltfläche (gedrückt)",
+                    "buttonPressedHover": "Schaltfläche (gedrückt+hover)",
+                    "input": "Input field"
+                }
+            },
+            "fonts": {
+                "_tab_label": "Schriften",
+                "help": "Wähl die Schriftart, die für Elemente der Benutzeroberfläche verwendet werden soll. Für \" Benutzerdefiniert\" musst du den genauen Schriftnamen eingeben, wie er im System angezeigt wird.",
+                "components": {
+                    "interface": "Oberfläche",
+                    "input": "Eingabefelder",
+                    "post": "Beitragstext",
+                    "postCode": "Dicktengleicher Text in einem Beitrag (Rich-Text)"
+                },
+                "family": "Schriftname",
+                "size": "Größe (in px)",
+                "weight": "Gewicht (Dicke)",
+                "custom": "Benutzerdefiniert"
+            },
+            "preview": {
+                "header": "Vorschau",
+                "content": "Inhalt",
+                "error": "Beispielfehler",
+                "button": "Schaltfläche",
+                "text": "Ein Haufen mehr von {0} und {1}",
+                "mono": "Inhalt",
+                "input": "Sitze gerade im Hofbräuhaus.",
+                "faint_link": "Hilfreiche Anleitung",
+                "fine_print": "Lies unser {0}, um nichts Nützliches zu lernen!",
+                "header_faint": "Das ist in Ordnung",
+                "checkbox": "Ich habe die Allgemeinen Geschäftsbedingungen überflogen",
+                "link": "ein netter kleiner Link"
+            }
         }
-      },
-      "common_colors": {
-        "_tab_label": "Allgemein",
-        "main": "Allgemeine Farben",
-        "foreground_hint": "Siehe Reiter \"Erweitert\" für eine detailliertere Einstellungen",
-        "rgbo": "Symbole, Betonungen, Kennzeichnungen"
-      },
-      "advanced_colors": {
-        "_tab_label": "Erweitert",
-        "alert": "Warnhinweis-Hintergrund",
-        "alert_error": "Fehler",
-        "badge": "Kennzeichnungs-Hintergrund",
-        "badge_notification": "Benachrichtigung",
-        "panel_header": "Panel-Kopf",
-        "top_bar": "Obere Leiste",
-        "borders": "Rahmen",
-        "buttons": "Schaltflächen",
-        "inputs": "Eingabefelder",
-        "faint_text": "Verblasster Text"
-      },
-      "radii": {
-        "_tab_label": "Abrundungen"
-      },
-      "shadows": {
-        "_tab_label": "Schatten und Beleuchtung",
-        "component": "Komponente",
-        "override": "Überschreiben",
-        "shadow_id": "Schatten #{value}",
-        "blur": "Unschärfe",
-        "spread": "Streuung",
-        "inset": "Einsatz",
-        "hint": "Für Schatten kannst du auch --variable als Farbwert verwenden, um CSS3-Variablen zu verwenden. Bitte beachte, dass die Einstellung der Deckkraft in diesem Fall nicht funktioniert.",
-        "filter_hint": {
-          "always_drop_shadow": "Achtung, dieser Schatten verwendet immer {0}, wenn der Browser dies unterstützt.",
-          "drop_shadow_syntax": "{0} unterstützt Parameter {1} und Schlüsselwort {2} nicht.",
-          "avatar_inset": "Bitte beachte, dass die Kombination von eingesetzten und nicht eingesetzten Schatten auf Avataren zu unerwarteten Ergebnissen bei transparenten Avataren führen kann.",
-          "spread_zero": "Schatten mit einer Streuung > 0 erscheinen so, als ob sie auf Null gesetzt wären.",
-          "inset_classic": "Eingesetzte Schatten werden mit {0} verwendet"
-        },
-        "components": {
-          "panel": "Panel",
-          "panelHeader": "Panel-Kopf",
-          "topBar": "Obere Leiste",
-          "avatar": "Benutzer-Avatar (in der Profilansicht)",
-          "avatarStatus": "Benutzer-Avatar (in der Beitragsanzeige)",
-          "popup": "Dialogfenster und Hinweistexte",
-          "button": "Schaltfläche",
-          "buttonHover": "Schaltfläche (hover)",
-          "buttonPressed": "Schaltfläche (gedrückt)",
-          "buttonPressedHover": "Schaltfläche (gedrückt+hover)",
-          "input": "Input field"
-        }
-      },
-      "fonts": {
-        "_tab_label": "Schriften",
-        "help": "Wähl die Schriftart, die für Elemente der Benutzeroberfläche verwendet werden soll. Für \" Benutzerdefiniert\" musst du den genauen Schriftnamen eingeben, wie er im System angezeigt wird.",
-        "components": {
-          "interface": "Oberfläche",
-          "input": "Eingabefelder",
-          "post": "Beitragstext",
-          "postCode": "Dicktengleicher Text in einem Beitrag (Rich-Text)"
-        },
-        "family": "Schriftname",
-        "size": "Größe (in px)",
-        "weight": "Gewicht (Dicke)",
-        "custom": "Benutzerdefiniert"
-      },
-      "preview": {
-        "header": "Vorschau",
-        "content": "Inhalt",
-        "error": "Beispielfehler",
-        "button": "Schaltfläche",
-        "text": "Ein Haufen mehr von {0} und {1}",
-        "mono": "Inhalt",
-        "input": "Sitze gerade im Hofbräuhaus.",
-        "faint_link": "Hilfreiche Anleitung",
-        "fine_print": "Lies unser {0}, um nichts Nützliches zu lernen!",
-        "header_faint": "Das ist in Ordnung",
-        "checkbox": "Ich habe die Allgemeinen Geschäftsbedingungen überflogen",
-        "link": "ein netter kleiner Link"
-      }
-    }
-  },
-  "timeline": {
-    "collapse": "Einklappen",
-    "conversation": "Unterhaltung",
-    "error_fetching": "Fehler beim Laden",
-    "load_older": "Lade ältere Beiträge",
-    "no_retweet_hint": "Der Beitrag ist als nur-für-Follower oder als Direktnachricht markiert und kann nicht wiederholt werden.",
-    "repeated": "wiederholte",
-    "show_new": "Zeige Neuere",
-    "up_to_date": "Aktuell"
-  },
-  "user_card": {
-    "approve": "Genehmigen",
-    "block": "Blockieren",
-    "blocked": "Blockiert!",
-    "deny": "Ablehnen",
-    "follow": "Folgen",
-    "follow_sent": "Anfrage gesendet!",
-    "follow_progress": "Anfragen…",
-    "follow_again": "Anfrage erneut senden?",
-    "follow_unfollow": "Folgen beenden",
-    "followees": "Folgt",
-    "followers": "Followers",
-    "following": "Folgst du!",
-    "follows_you": "Folgt dir!",
-    "its_you": "Das bist du!",
-    "mute": "Stummschalten",
-    "muted": "Stummgeschaltet",
-    "per_day": "pro Tag",
-    "remote_follow": "Folgen",
-    "statuses": "Beiträge"
-  },
-  "user_profile": {
-    "timeline_title": "Beiträge"
-  },
-  "who_to_follow": {
-    "more": "Mehr",
-    "who_to_follow": "Wem soll ich folgen"
-  },
-  "tool_tip": {
-    "media_upload": "Medien hochladen",
-    "repeat": "Wiederholen",
-    "reply": "Antworten",
-    "favorite": "Favorisieren",
-    "user_settings": "Benutzereinstellungen"
-  },
-  "upload":{
-    "error": {
-    "base": "Hochladen fehlgeschlagen.",
-    "file_too_big": "Datei ist zu groß [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-    "default": "Bitte versuche es später erneut"
     },
-    "file_size_units": {
-      "B": "B",
-      "KiB": "KiB",
-      "MiB": "MiB",
-      "GiB": "GiB",
-      "TiB": "TiB"
+    "timeline": {
+        "collapse": "Einklappen",
+        "conversation": "Unterhaltung",
+        "error_fetching": "Fehler beim Laden",
+        "load_older": "Lade ältere Beiträge",
+        "no_retweet_hint": "Der Beitrag ist als nur-für-Follower oder als Direktnachricht markiert und kann nicht wiederholt werden.",
+        "repeated": "wiederholte",
+        "show_new": "Zeige Neuere",
+        "up_to_date": "Aktuell"
+    },
+    "user_card": {
+        "approve": "Genehmigen",
+        "block": "Blockieren",
+        "blocked": "Blockiert!",
+        "deny": "Ablehnen",
+        "follow": "Folgen",
+        "follow_sent": "Anfrage gesendet!",
+        "follow_progress": "Anfragen…",
+        "follow_again": "Anfrage erneut senden?",
+        "follow_unfollow": "Folgen beenden",
+        "followees": "Folgt",
+        "followers": "Followers",
+        "following": "Folgst du!",
+        "follows_you": "Folgt dir!",
+        "its_you": "Das bist du!",
+        "mute": "Stummschalten",
+        "muted": "Stummgeschaltet",
+        "per_day": "pro Tag",
+        "remote_follow": "Folgen",
+        "statuses": "Beiträge"
+    },
+    "user_profile": {
+        "timeline_title": "Beiträge"
+    },
+    "who_to_follow": {
+        "more": "Mehr",
+        "who_to_follow": "Wem soll ich folgen"
+    },
+    "tool_tip": {
+        "media_upload": "Medien hochladen",
+        "repeat": "Wiederholen",
+        "reply": "Antworten",
+        "favorite": "Favorisieren",
+        "user_settings": "Benutzereinstellungen"
+    },
+    "upload": {
+        "error": {
+            "base": "Hochladen fehlgeschlagen.",
+            "file_too_big": "Datei ist zu groß [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+            "default": "Bitte versuche es später erneut"
+        },
+        "file_size_units": {
+            "B": "B",
+            "KiB": "KiB",
+            "MiB": "MiB",
+            "GiB": "GiB",
+            "TiB": "TiB"
+        }
+    },
+    "search": {
+        "people": "Leute",
+        "hashtags": "Hashtags",
+        "person_talking": "{count} Person spricht darüber",
+        "people_talking": "{count} Leute sprechen darüber",
+        "no_results": "Keine Ergebnisse"
+    },
+    "password_reset": {
+        "forgot_password": "Passwort vergessen?",
+        "password_reset": "Password zurücksetzen",
+        "instruction": "Wenn du hier deinen Benutznamen oder die zugehörige E-Mail-Adresse eingibst, kann dir der Server einen Link zum Passwortzurücksetzen zuschicken.",
+        "placeholder": "Dein Benutzername oder die zugehörige E-Mail-Adresse",
+        "check_email": "Im E-Mail-Posteingang des angebenen Kontos müsste sich jetzt (oder zumindest in Kürze) die E-Mail mit dem Link zum Passwortzurücksetzen befinden.",
+        "return_home": "Zurück zur Heimseite",
+        "not_found": "Benutzername/E-Mail-Adresse nicht gefunden. Vertippt?",
+        "too_many_requests": "Kurze Pause. Zu viele Versuche. Bitte, später nochmal probieren.",
+        "password_reset_disabled": "Passwortzurücksetzen deaktiviert. Bitte Administrator kontaktieren.",
+        "password_reset_required": "Passwortzurücksetzen erforderlich",
+        "password_reset_required_but_mailer_is_disabled": "Passwortzurücksetzen wäre erforderlich, ist aber deaktiviert. Bitte Administrator kontaktieren."
+    },
+    "about": {
+        "mrf": {
+            "federation": "Föderation",
+            "mrf_policies": "Aktivierte MRF Richtlinien",
+            "simple": {
+                "simple_policies": "Instanzspezifische Richtlinien",
+                "accept": "Akzeptieren",
+                "reject": "Ablehnen",
+                "reject_desc": "Diese Instanz akzeptiert keine Nachrichten der folgenden Instanzen:",
+                "quarantine": "Quarantäne",
+                "ftl_removal": "Von der Zeitleiste \"Das gesamte bekannte Netzwerk\" entfernen",
+                "media_removal": "Medienentfernung",
+                "media_removal_desc": "Diese Instanz entfernt Medien von den Beiträgen der folgenden Instanzen:",
+                "media_nsfw": "Erzwingen Medien als heikel zu makieren",
+                "media_nsfw_desc": "Diese Instanz makiert die Medien in Beiträgen der folgenden Instanzen als heikel:",
+                "accept_desc": "Diese Instanz akzeptiert nur Nachrichten von den folgenden Instanzen:",
+                "quarantine_desc": "Diese Instanz sendet nur öffentliche Beiträge zu den folgenden Instanzen:",
+                "ftl_removal_desc": "Dieser Instanz entfernt folgende Instanzen von der \"Das gesamte bekannte Netzwerk\" Zeitleiste:"
+            },
+            "keyword": {
+                "keyword_policies": "Keyword Richtlinien",
+                "reject": "Ablehnen",
+                "replace": "Ersetzen",
+                "is_replaced_by": "→",
+                "ftl_removal": "Von der Zeitleiste \"Das gesamte bekannte Netzwerk\" entfernen"
+            },
+            "mrf_policies_desc": "MRF Richtlinien manipulieren das Föderationsverhalten dieser Instanz. Die folgenden Richtlinien sind aktiv:"
+        },
+        "staff": "Mitarbeiter"
+    },
+    "domain_mute_card": {
+        "mute": "Stummschalten",
+        "mute_progress": "Wird stummgeschaltet..",
+        "unmute": "Stummschaltung aufheben",
+        "unmute_progress": "Stummschaltung wird aufgehoben.."
+    },
+    "exporter": {
+        "export": "Exportieren",
+        "processing": "Verarbeitung läuft, bald wird Du dazu aufgefordert, deine Datei herunterzuladen"
+    },
+    "image_cropper": {
+        "crop_picture": "Bild zuschneiden",
+        "save": "Speichern",
+        "cancel": "Abbrechen",
+        "save_without_cropping": "Ohne Zuschneiden speichern"
+    },
+    "importer": {
+        "submit": "Absenden",
+        "success": "Erfolgreich importiert.",
+        "error": "Ein Fehler ist beim Verabeiten der Datei aufgetreten."
+    },
+    "media_modal": {
+        "previous": "Zurück",
+        "next": "Weiter"
+    },
+    "polls": {
+        "add_poll": "Umfrage hinzufügen",
+        "add_option": "Option hinzufügen",
+        "option": "Option",
+        "votes": "Stimmen",
+        "vote": "Abstimmen",
+        "type": "Umfragetyp",
+        "multiple_choices": "Mehrere Auswahlmöglichkeiten",
+        "single_choice": "Eine Auswahlmöglichkeit",
+        "expiry": "Alter der Umfrage",
+        "expired": "Die Umfrage endete vor {0}",
+        "not_enough_options": "Zu wenig einzigartige Auswahlmöglichkeiten in der Umfrage",
+        "expires_in": "Die Umfrage endet in {0}"
+    },
+    "emoji": {
+        "stickers": "Sticker",
+        "emoji": "Emoji",
+        "search_emoji": "Nach einem Emoji suchen",
+        "custom": "Benutzerdefinierter Emoji",
+        "keep_open": "Auswahlfenster offen halten",
+        "add_emoji": "Emoji einfügen"
     }
-  },
-  "search": {
-    "people": "Leute",
-    "hashtags": "Hashtags",
-    "person_talking": "{count} Person spricht darüber",
-    "people_talking": "{count} Leute sprechen darüber",
-    "no_results": "Keine Ergebnisse"
-  },
-  "password_reset": {
-    "forgot_password": "Passwort vergessen?",
-    "password_reset": "Password zurücksetzen",
-    "instruction": "Wenn du hier deinen Benutznamen oder die zugehörige E-Mail-Adresse eingibst, kann dir der Server einen Link zum Passwortzurücksetzen zuschicken.",
-    "placeholder": "Dein Benutzername oder die zugehörige E-Mail-Adresse",
-    "check_email": "Im E-Mail-Posteingang des angebenen Kontos müsste sich jetzt (oder zumindest in Kürze) die E-Mail mit dem Link zum Passwortzurücksetzen befinden.",
-    "return_home": "Zurück zur Heimseite",
-    "not_found": "Benutzername/E-Mail-Adresse nicht gefunden. Vertippt?",
-    "too_many_requests": "Kurze Pause. Zu viele Versuche. Bitte, später nochmal probieren.",
-    "password_reset_disabled": "Passwortzurücksetzen deaktiviert. Bitte Administrator kontaktieren.",
-    "password_reset_required": "Passwortzurücksetzen erforderlich",
-    "password_reset_required_but_mailer_is_disabled": "Passwortzurücksetzen wäre erforderlich, ist aber deaktiviert. Bitte Administrator kontaktieren."
-  }
 }

From 21ecca4c62eb465d364155dd0ca415878ec28ab0 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Wed, 13 May 2020 17:42:56 +0000
Subject: [PATCH 336/483] Translated using Weblate (Italian)

Currently translated at 28.3% (174 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 117 +++++++++++++++++++++++++----------------------
 1 file changed, 63 insertions(+), 54 deletions(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index 3a49d96f..817a6465 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -5,34 +5,34 @@
     },
     "nav": {
         "mentions": "Menzioni",
-        "public_tl": "Sequenza temporale pubblica",
-        "timeline": "Sequenza temporale",
-        "twkn": "L'intera rete conosciuta",
-        "chat": "Chat Locale",
-        "friend_requests": "Richieste di Seguirti"
+        "public_tl": "Sequenza pubblica",
+        "timeline": "Sequenza personale",
+        "twkn": "Tutte le stanze accessibili",
+        "chat": "Chat della stanza",
+        "friend_requests": "Vogliono seguirti"
     },
     "notifications": {
         "followed_you": "ti segue",
         "notifications": "Notifiche",
-        "read": "Leggi!",
+        "read": "Letto!",
         "broken_favorite": "Stato sconosciuto, lo sto cercando...",
-        "favorited_you": "ha messo mi piace al tuo stato",
-        "load_older": "Carica notifiche più vecchie",
-        "repeated_you": "ha condiviso il tuo stato"
+        "favorited_you": "ha gradito il tuo messaggio",
+        "load_older": "Carica notifiche precedenti",
+        "repeated_you": "ha condiviso il tuo messaggio"
     },
     "settings": {
         "attachments": "Allegati",
-        "autoload": "Abilita caricamento automatico quando si raggiunge fondo pagina",
-        "avatar": "Avatar",
+        "autoload": "Abilita caricamento automatico quando si raggiunge il fondo pagina",
+        "avatar": "Icona utente",
         "bio": "Introduzione",
-        "current_avatar": "Il tuo avatar attuale",
-        "current_profile_banner": "Il tuo banner attuale",
+        "current_avatar": "La tua icona attuale",
+        "current_profile_banner": "Il tuo stendardo attuale",
         "filtering": "Filtri",
-        "filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, uno per linea",
+        "filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, una per linea",
         "hide_attachments_in_convo": "Nascondi gli allegati presenti nelle conversazioni",
-        "hide_attachments_in_tl": "Nascondi gli allegati presenti nella sequenza temporale",
+        "hide_attachments_in_tl": "Nascondi gli allegati presenti nelle sequenze",
         "name": "Nome",
-        "name_bio": "Nome & Introduzione",
+        "name_bio": "Nome ed introduzione",
         "nsfw_clickthrough": "Abilita il click per visualizzare gli allegati segnati come NSFW",
         "profile_background": "Sfondo della tua pagina",
         "profile_banner": "Banner del tuo profilo",
@@ -44,55 +44,55 @@
         "theme": "Tema",
         "user_settings": "Impostazioni Utente",
         "attachmentRadius": "Allegati",
-        "avatarAltRadius": "Avatar (Notifiche)",
-        "avatarRadius": "Avatar",
+        "avatarAltRadius": "Icona utente (Notifiche)",
+        "avatarRadius": "Icone utente",
         "background": "Sfondo",
         "btnRadius": "Pulsanti",
-        "cBlue": "Blu (Rispondere, seguire)",
-        "cGreen": "Verde (Condividi)",
-        "cOrange": "Arancio (Mi piace)",
-        "cRed": "Rosso (Annulla)",
+        "cBlue": "Blu (risposte, seguire)",
+        "cGreen": "Verde (ripeti)",
+        "cOrange": "Arancio (gradire)",
+        "cRed": "Rosso (annulla)",
         "change_password": "Cambia Password",
         "change_password_error": "C'è stato un problema durante il cambiamento della password.",
         "changed_password": "Password cambiata correttamente!",
-        "collapse_subject": "Riduci post che hanno un oggetto",
+        "collapse_subject": "Collassa messaggi con Oggetto",
         "confirm_new_password": "Conferma la nuova password",
-        "current_password": "Password attuale",
-        "data_import_export_tab": "Importa / Esporta Dati",
-        "default_vis": "Visibilità predefinita dei post",
-        "delete_account": "Elimina Account",
-        "delete_account_description": "Elimina definitivamente il tuo account e tutti i tuoi messaggi.",
-        "delete_account_error": "C'è stato un problema durante l'eliminazione del tuo account. Se il problema persiste contatta l'amministratore della tua istanza.",
-        "delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione dell'account.",
-        "export_theme": "Salva settaggi",
+        "current_password": "La tua password attuale",
+        "data_import_export_tab": "Importa o esporta dati",
+        "default_vis": "Visibilità predefinita dei messaggi",
+        "delete_account": "Elimina profilo",
+        "delete_account_description": "Elimina definitivamente il tuo profilo e tutti i tuoi messaggi.",
+        "delete_account_error": "C'è stato un problema durante l'eliminazione del tuo profilo. Se il problema persiste contatta l'amministratore della tua stanza.",
+        "delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione del tuo profilo.",
+        "export_theme": "Salva impostazioni",
         "follow_export": "Esporta la lista di chi segui",
         "follow_export_button": "Esporta la lista di chi segui in un file csv",
         "follow_export_processing": "Sto elaborando, presto ti sarà chiesto di scaricare il tuo file",
         "follow_import": "Importa la lista di chi segui",
         "follow_import_error": "Errore nell'importazione della lista di chi segui",
         "follows_imported": "Importazione riuscita! L'elaborazione richiederà un po' di tempo.",
-        "foreground": "In primo piano",
+        "foreground": "Primo piano",
         "general": "Generale",
-        "hide_post_stats": "Nascondi statistiche dei post (es. il numero di mi piace)",
-        "hide_user_stats": "Nascondi statistiche dell'utente (es. il numero di chi ti segue)",
+        "hide_post_stats": "Nascondi statistiche dei messaggi (es. il numero di preferenze)",
+        "hide_user_stats": "Nascondi statistiche dell'utente (es. il numero dei tuoi seguaci)",
         "import_followers_from_a_csv_file": "Importa una lista di chi segui da un file csv",
-        "import_theme": "Carica settaggi",
+        "import_theme": "Carica impostazioni",
         "inputRadius": "Campi di testo",
         "instance_default": "(predefinito: {value})",
-        "interfaceLanguage": "Linguaggio dell'interfaccia",
-        "invalid_theme_imported": "Il file selezionato non è un file di tema per Pleroma supportato. Il tuo tema non è stato modificato.",
+        "interfaceLanguage": "Lingua dell'interfaccia",
+        "invalid_theme_imported": "Il file selezionato non è un tema supportato da Pleroma. Il tuo tema non è stato modificato.",
         "limited_availability": "Non disponibile nel tuo browser",
         "links": "Collegamenti",
-        "lock_account_description": "Limita il tuo account solo per contatti approvati",
+        "lock_account_description": "Limita il tuo account solo a seguaci approvati",
         "loop_video": "Riproduci video in ciclo continuo",
-        "loop_video_silent_only": "Riproduci solo video senza audio in ciclo continuo (es. le gif di Mastodon)",
+        "loop_video_silent_only": "Riproduci solo video senza audio in ciclo continuo (es. le \"gif\" di Mastodon)",
         "new_password": "Nuova password",
         "notification_visibility": "Tipi di notifiche da mostrare",
         "notification_visibility_follows": "Nuove persone ti seguono",
-        "notification_visibility_likes": "Mi piace",
+        "notification_visibility_likes": "Preferiti",
         "notification_visibility_mentions": "Menzioni",
         "notification_visibility_repeats": "Condivisioni",
-        "no_rich_text_description": "Togli la formattazione del testo da tutti i post",
+        "no_rich_text_description": "Togli l'impaginazione del testo da tutti i messaggi",
         "oauth_tokens": "Token OAuth",
         "token": "Token",
         "refresh_token": "Aggiorna token",
@@ -152,9 +152,9 @@
     "features_panel": {
         "chat": "Chat",
         "gopher": "Gopher",
-        "media_proxy": "Media proxy",
-        "scope_options": "Opzioni di visibilità",
-        "text_limit": "Lunghezza limite",
+        "media_proxy": "Proxy multimedia",
+        "scope_options": "Opzioni raggio d'azione",
+        "text_limit": "Lunghezza massima",
         "title": "Caratteristiche",
         "who_to_follow": "Chi seguire"
     },
@@ -171,21 +171,21 @@
         "username": "Nome utente"
     },
     "post_status": {
-        "account_not_locked_warning": "Il tuo account non è {0}. Chiunque può seguirti e vedere i tuoi post riservati a chi ti segue.",
-        "account_not_locked_warning_link": "bloccato",
-        "attachments_sensitive": "Segna allegati come sensibili",
+        "account_not_locked_warning": "Il tuo profilo non è {0}. Chiunque può seguirti e vedere i tuoi messaggi riservati ai tuoi seguaci.",
+        "account_not_locked_warning_link": "protetto",
+        "attachments_sensitive": "Nascondi gli allegati",
         "content_type": {
             "text/plain": "Testo normale"
         },
         "content_warning": "Oggetto (facoltativo)",
-        "default": "Appena atterrato in L.A.",
+        "default": "Sono appena atterrato a Fiumicino.",
         "direct_warning": "Questo post sarà visibile solo dagli utenti menzionati.",
-        "posting": "Pubblica",
+        "posting": "Sto pubblicando",
         "scope": {
-            "direct": "Diretto - Pubblicato solo per gli utenti menzionati",
-            "private": "Solo per chi ti segue - Visibile solo da chi ti segue",
-            "public": "Pubblico - Visibile sulla sequenza temporale pubblica",
-            "unlisted": "Non elencato - Non visibile sulla sequenza temporale pubblica"
+            "direct": "Diretto - Visibile solo agli utenti menzionati",
+            "private": "Solo per seguaci - Visibile solo dai tuoi seguaci",
+            "public": "Pubblico - Visibile sulla sequenza pubblica",
+            "unlisted": "Non elencato - Non visibile sulla sequenza pubblica"
         }
     },
     "registration": {
@@ -205,7 +205,16 @@
     },
     "about": {
         "mrf": {
-            "federation": "Federazione"
+            "federation": "Federazione",
+            "keyword": {
+                "reject": "Rifiuta",
+                "replace": "Sostituisci",
+                "is_replaced_by": "→"
+            },
+            "simple": {
+                "reject": "Rifiuta",
+                "accept": "Accetta"
+            }
         }
     }
 }

From 8ac78e92f8ce2306d03e922412183f0377dcbc01 Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Wed, 13 May 2020 17:17:13 +0000
Subject: [PATCH 337/483] Translated using Weblate (Russian)

Currently translated at 59.3% (364 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
---
 src/i18n/ru.json | 46 +++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 41 insertions(+), 5 deletions(-)

diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index a34c2e3f..790f2f3c 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -13,7 +13,10 @@
         "disable": "Оключить",
         "enable": "Включить",
         "confirm": "Подтвердить",
-        "verify": "Проверить"
+        "verify": "Проверить",
+        "more": "Больше",
+        "generic_error": "Произошла ошибка",
+        "optional": "не обязательно"
     },
     "login": {
         "login": "Войти",
@@ -415,14 +418,47 @@
         "mrf": {
             "federation": "Федерация",
             "simple": {
-                "accept_desc": ""
+                "accept_desc": "Данный сервер принимает сообщения только со следующих серверов:",
+                "ftl_removal_desc": "Данный сервер скрывает следующие сервера с федеративной ленты:",
+                "media_nsfw_desc": "Данный сервер принужденно помечает вложения со следущих серверов как NSFW:",
+                "simple_policies": "Правила для определенных серверов",
+                "accept": "Принимаемые сообщения",
+                "reject": "Отклоняемые сообщения",
+                "reject_desc": "Данный сервер не принимает сообщения со следующих серверов:",
+                "quarantine": "Зона карантина",
+                "quarantine_desc": "Данный сервер отправляет только публичные посты следующим серверам:",
+                "ftl_removal": "Скрытие с федеративной ленты",
+                "media_removal": "Удаление вложений",
+                "media_removal_desc": "Данный сервер удаляет вложения со следующих серверов:",
+                "media_nsfw": "Принужденно помеченно как NSFW"
             },
             "keyword": {
-                "ftl_removal": "Убраны из федеративной ленты",
+                "ftl_removal": "Убрать из федеративной ленты",
                 "reject": "Отклонить",
-                "keyword_policies": "Действия на ключевые слова"
-            }
+                "keyword_policies": "Действия на ключевые слова",
+                "replace": "Заменить",
+                "is_replaced_by": "→"
+            },
+            "mrf_policies": "Активные правила MRF (модуль переписывания сообщений)",
+            "mrf_policies_desc": "Правила MRF (модуль переписывания сообщений) влияют на федерацию данного сервера. Следующие правила активны:"
         },
         "staff": "Администрация"
+    },
+    "domain_mute_card": {
+        "mute": "Игнорировать",
+        "mute_progress": "В процессе...",
+        "unmute": "Прекратить игнорирование",
+        "unmute_progress": "В процессе..."
+    },
+    "exporter": {
+        "export": "Экспорт",
+        "processing": "Запрос в обработке, вам скоро будет предложено загрузить файл"
+    },
+    "features_panel": {
+        "chat": "Чат",
+        "media_proxy": "Прокси для внешних вложений",
+        "text_limit": "Лимит символов",
+        "title": "Особенности",
+        "gopher": "Gopher"
     }
 }

From bc215d70f5047539bae2f61f1298e3c282970c34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Sidor?= <pleromeme@meekchopp.es>
Date: Wed, 13 May 2020 16:41:19 +0000
Subject: [PATCH 338/483] Translated using Weblate (Polish)

Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/pl/
---
 src/i18n/pl.json | 1462 +++++++++++++++++++++++-----------------------
 1 file changed, 735 insertions(+), 727 deletions(-)

diff --git a/src/i18n/pl.json b/src/i18n/pl.json
index 4a4b1e31..89f2fdc2 100644
--- a/src/i18n/pl.json
+++ b/src/i18n/pl.json
@@ -1,735 +1,743 @@
 {
-  "about": {
-    "mrf": {
-      "federation": "Federacja",
-      "keyword": {
-        "keyword_policies": "Zasady słów kluczowych",
-        "ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
-        "reject": "Odrzucanie",
-        "replace": "Zastąpienie",
-        "is_replaced_by": "→"
-      },
-      "mrf_policies": "Włączone zasady MRF",
-      "mrf_policies_desc": "Zasady MRF zmieniają zachowanie federowania instancji. Następujące zasady są włączone:",
-      "simple": {
-        "simple_policies": "Zasady specyficzne dla instancji",
-        "accept": "Akceptowanie",
-        "accept_desc": "Ta instancja akceptuje tylko posty z wymienionych instancji:",
-        "reject": "Odrzucanie",
-        "reject_desc": "Ta instancja odrzuca posty z wymienionych instancji:",
-        "quarantine": "Kwarantanna",
-        "quarantine_desc": "Ta instancja wysyła tylko publiczne posty do wymienionych instancji:",
-        "ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
-        "ftl_removal_desc": "Ta instancja usuwa te instancje z \"Całej znanej sieci\"",
-        "media_removal": "Usuwanie multimediów",
-        "media_removal_desc": "Ta instancja usuwa multimedia z postów od wymienionych instancji:",
-        "media_nsfw": "Multimedia ustawione jako wrażliwe",
-        "media_nsfw_desc": "Ta instancja wymusza, by multimedia z wymienionych instancji były ustawione jako wrażliwe:"
-      }
-    },
-    "staff": "Obsługa"
-  },
-  "chat": {
-    "title": "Czat"
-  },
-  "domain_mute_card": {
-    "mute": "Wycisz",
-    "mute_progress": "Wyciszam...",
-    "unmute": "Odcisz",
-    "unmute_progress": "Odciszam..."
-  },
-  "exporter": {
-    "export": "Eksportuj",
-    "processing": "Przetwarzam, za chwilę zostaniesz zapytany o ściągnięcie pliku"
-  },
-  "features_panel": {
-    "chat": "Czat",
-    "gopher": "Gopher",
-    "media_proxy": "Proxy mediów",
-    "scope_options": "Ustawienia zakresu",
-    "text_limit": "Limit tekstu",
-    "title": "Funkcje",
-    "who_to_follow": "Propozycje obserwacji"
-  },
-  "finder": {
-    "error_fetching_user": "Błąd przy pobieraniu profilu",
-    "find_user": "Znajdź użytkownika"
-  },
-  "general": {
-    "apply": "Zastosuj",
-    "submit": "Wyślij",
-    "more": "Więcej",
-    "generic_error": "Wystąpił błąd",
-    "optional": "nieobowiązkowe",
-    "show_more": "Pokaż więcej",
-    "show_less": "Pokaż mniej",
-    "dismiss": "Odrzuć",
-    "cancel": "Anuluj",
-    "disable": "Wyłącz",
-    "enable": "Włącz",
-    "confirm": "Potwierdź",
-    "verify": "Zweryfikuj"
-  },
-  "image_cropper": {
-    "crop_picture": "Przytnij obrazek",
-    "save": "Zapisz",
-    "save_without_cropping": "Zapisz bez przycinania",
-    "cancel": "Anuluj"
-  },
-  "importer": {
-    "submit": "Wyślij",
-    "success": "Zaimportowano pomyślnie",
-    "error": "Wystąpił błąd podczas importowania pliku."
-  },
-  "login": {
-    "login": "Zaloguj",
-    "description": "Zaloguj używając OAuth",
-    "logout": "Wyloguj",
-    "password": "Hasło",
-    "placeholder": "n.p. lain",
-    "register": "Zarejestruj",
-    "username": "Użytkownik",
-    "hint": "Zaloguj się, aby dołączyć do dyskusji",
-    "authentication_code": "Kod weryfikacyjny",
-    "enter_recovery_code": "Wprowadź kod zapasowy",
-    "enter_two_factor_code": "Wprowadź kod weryfikacyjny",
-    "recovery_code": "Kod zapasowy",
-    "heading" : {
-      "totp" : "Weryfikacja dwuetapowa",
-      "recovery" : "Zapasowa weryfikacja dwuetapowa"
-    }
-  },
-  "media_modal": {
-    "previous": "Poprzednie",
-    "next": "Następne"
-  },
-  "nav": {
-    "about": "O nas",
-    "administration": "Administracja",
-    "back": "Wróć",
-    "chat": "Lokalny czat",
-    "friend_requests": "Prośby o możliwość obserwacji",
-    "mentions": "Wzmianki",
-    "interactions": "Interakcje",
-    "dms": "Wiadomości prywatne",
-    "public_tl": "Publiczna oś czasu",
-    "timeline": "Oś czasu",
-    "twkn": "Cała znana sieć",
-    "user_search": "Wyszukiwanie użytkowników",
-    "search": "Wyszukiwanie",
-    "who_to_follow": "Sugestie obserwacji",
-    "preferences": "Preferencje"
-  },
-  "notifications": {
-    "broken_favorite": "Nieznany status, szukam go…",
-    "favorited_you": "dodał(-a) twój status do ulubionych",
-    "followed_you": "obserwuje cię",
-    "load_older": "Załaduj starsze powiadomienia",
-    "notifications": "Powiadomienia",
-    "read": "Przeczytane!",
-    "repeated_you": "powtórzył(-a) twój status",
-    "no_more_notifications": "Nie masz więcej powiadomień",
-    "migrated_to": "wyemigrował do",
-    "reacted_with": "zareagował z {0}"
-  },
-  "polls": {
-    "add_poll": "Dodaj ankietę",
-    "add_option": "Dodaj opcję",
-    "option": "Opcja",
-    "votes": "głosów",
-    "vote": "Głosuj",
-    "type": "Typ ankiety",
-    "single_choice": "jednokrotnego wyboru",
-    "multiple_choices": "wielokrotnego wyboru",
-    "expiry": "Czas trwania ankiety",
-    "expires_in": "Ankieta kończy się za{0}",
-    "expired": "Ankieta skończyła się {0} temu",
-    "not_enough_options": "Zbyt mało unikalnych opcji w ankiecie"
-  },
-  "emoji": {
-    "stickers": "Naklejki",
-    "emoji": "Emoji",
-    "keep_open": "Zostaw selektor otwarty",
-    "search_emoji": "Wyszukaj emoji",
-    "add_emoji": "Wstaw emoji",
-    "custom": "Niestandardowe emoji",
-    "unicode": "Emoji unicode",
-    "load_all_hint": "Załadowano pierwsze {saneAmount} emoji, Załadowanie wszystkich emoji może spowodować problemy z wydajnością.",
-    "load_all": "Ładuję wszystkie {emojiAmount} emoji"
-  },
-  "interactions": {
-    "favs_repeats": "Powtórzenia i ulubione",
-    "follows": "Nowi obserwujący",
-    "moves": "Użytkownik migruje",
-    "load_older": "Załaduj starsze interakcje"
-  },
-  "post_status": {
-    "new_status": "Dodaj nowy status",
-    "account_not_locked_warning": "Twoje konto nie jest {0}. Każdy może cię zaobserwować aby zobaczyć wpisy tylko dla obserwujących.",
-    "account_not_locked_warning_link": "zablokowane",
-    "attachments_sensitive": "Oznacz załączniki jako wrażliwe",
-    "content_type": {
-      "text/plain": "Czysty tekst",
-      "text/html": "HTML",
-      "text/markdown": "Markdown",
-      "text/bbcode": "BBCode"
-    },
-    "content_warning": "Temat (nieobowiązkowy)",
-    "default": "Właśnie wróciłem z kościoła",
-    "direct_warning_to_all": "Ten wpis zobaczą wszystkie osoby, o których wspomniałeś(-aś).",
-    "direct_warning_to_first_only": "Ten wpis zobaczą tylko te osoby, o których wspomniałeś(-aś) na początku wiadomości.",
-    "posting": "Wysyłanie",
-    "scope_notice": {
-      "public": "Ten post będzie widoczny dla każdego",
-      "private": "Ten post będzie widoczny tylko dla twoich obserwujących",
-      "unlisted": "Ten post nie będzie widoczny na publicznej osi czasu i całej znanej sieci"
-    },
-    "scope": {
-      "direct": "Bezpośredni – Tylko dla wspomnianych użytkowników",
-      "private": "Tylko dla obserwujących – Umieść dla osób, które cię obserwują",
-      "public": "Publiczny – Umieść na publicznych osiach czasu",
-      "unlisted": "Niewidoczny – Nie umieszczaj na publicznych osiach czasu"
-    }
-  },
-  "registration": {
-    "bio": "Bio",
-    "email": "E-mail",
-    "fullname": "Wyświetlana nazwa profilu",
-    "password_confirm": "Potwierdzenie hasła",
-    "registration": "Rejestracja",
-    "token": "Token zaproszenia",
-    "captcha": "CAPTCHA",
-    "new_captcha": "Naciśnij na obrazek, aby dostać nowy kod captcha",
-    "username_placeholder": "np. lain",
-    "fullname_placeholder": "np. Lain Iwakura",
-    "bio_placeholder": "e.g.\nCześć, jestem Lain.\nJestem dziewczynką z anime żyjącą na peryferiach Japonii. Możesz znać mnie z Wired.",
-    "validations": {
-      "username_required": "nie może być pusta",
-      "fullname_required": "nie może być pusta",
-      "email_required": "nie może być pusty",
-      "password_required": "nie może być puste",
-      "password_confirmation_required": "nie może być puste",
-      "password_confirmation_match": "musi być takie jak hasło"
-    }
-  },
-  "remote_user_resolver": {
-    "remote_user_resolver": "Wyszukiwarka użytkowników nietutejszych",
-    "searching_for": "Szukam",
-    "error": "Nie znaleziono."
-  },
-  "selectable_list": {
-    "select_all": "Zaznacz wszystko"
-  },
-  "settings": {
-    "app_name": "Nazwa aplikacji",
-    "security": "Bezpieczeństwo",
-    "enter_current_password_to_confirm": "Wprowadź obecne hasło, by potwierdzić twoją tożsamość",
-    "mfa": {
-      "otp" : "OTP",
-      "setup_otp" : "Ustaw OTP",
-      "wait_pre_setup_otp" : "początkowe ustawianie OTP",
-      "confirm_and_enable" : "Potwierdź i włącz OTP",
-      "title": "Weryfikacja dwuetapowa",
-      "generate_new_recovery_codes" : "Wygeneruj nowe kody zapasowe",
-      "warning_of_generate_new_codes" : "Po tym gdy generujesz nowe kody zapasowe, stare przestaną działać.",
-      "recovery_codes" : "Kody zapasowe.",
-      "waiting_a_recovery_codes": "Otrzymuję kody zapasowe...",
-      "recovery_codes_warning" : "Spisz kody na kartce papieru, albo zapisz je w bezpiecznym miejscu - inaczej nie zobaczysz ich już nigdy. Jeśli stracisz dostęp do twojej aplikacji 2FA i kodów zapasowych, nie będziesz miał dostępu do swojego konta.",
-      "authentication_methods" : "Metody weryfikacji",
-      "scan": {
-        "title": "Skanuj",
-        "desc": "Zeskanuj ten kod QR używając twojej aplikacji 2FA albo wpisz ten klucz:",
-        "secret_code": "Klucz"
-      },
-      "verify": {
-        "desc": "By włączyć weryfikację dwuetapową, wpisz kod z twojej aplikacji 2FA:"
-      }
-    },
-    "allow_following_move": "Zezwalaj na automatyczną obserwację gdy obserwowane konto migruje",
-    "attachmentRadius": "Załączniki",
-    "attachments": "Załączniki",
-    "autoload": "Włącz automatyczne ładowanie po przewinięciu do końca strony",
-    "avatar": "Awatar",
-    "avatarAltRadius": "Awatary (powiadomienia)",
-    "avatarRadius": "Awatary",
-    "background": "Tło",
-    "bio": "Bio",
-    "block_export": "Eksport blokad",
-    "block_export_button": "Eksportuj twoje blokady do pliku .csv",
-    "block_import": "Import blokad",
-    "block_import_error": "Wystąpił błąd podczas importowania blokad",
-    "blocks_imported": "Zaimportowano blokady, przetwarzanie może zająć trochę czasu.",
-    "blocks_tab": "Bloki",
-    "btnRadius": "Przyciski",
-    "cBlue": "Niebieski (odpowiedz, obserwuj)",
-    "cGreen": "Zielony (powtórzenia)",
-    "cOrange": "Pomarańczowy (ulubione)",
-    "cRed": "Czerwony (anuluj)",
-    "change_email": "Zmień email",
-    "change_email_error": "Wystąpił problem podczas zmiany emaila.",
-    "changed_email": "Pomyślnie zmieniono email!",
-    "change_password": "Zmień hasło",
-    "change_password_error": "Podczas zmiany hasła wystąpił problem.",
-    "changed_password": "Pomyślnie zmieniono hasło!",
-    "collapse_subject": "Zwijaj posty z tematami",
-    "composing": "Pisanie",
-    "confirm_new_password": "Potwierdź nowe hasło",
-    "current_avatar": "Twój obecny awatar",
-    "current_password": "Obecne hasło",
-    "current_profile_banner": "Twój obecny banner profilu",
-    "data_import_export_tab": "Import/eksport danych",
-    "default_vis": "Domyślny zakres widoczności",
-    "delete_account": "Usuń konto",
-    "delete_account_description": "Trwale usuń konto i wszystkie posty.",
-    "delete_account_error": "Wystąpił problem z usuwaniem twojego konta. Jeżeli problem powtarza się, poinformuj administratora swojej instancji.",
-    "delete_account_instructions": "Wprowadź swoje hasło w poniższe pole aby potwierdzić usunięcie konta.",
-    "discoverable": "Zezwól na odkrywanie tego konta w wynikach wyszukiwania i innych usługa.",
-    "domain_mutes": "Domeny",
-    "avatar_size_instruction": "Zalecany minimalny rozmiar awatarów to 150x150 pikseli.",
-    "pad_emoji": "Dodaj odstęp z obu stron emoji podczas dodawania selektorem",
-    "emoji_reactions_on_timeline": "Pokaż reakcje emoji na osi czasu",
-    "export_theme": "Zapisz motyw",
-    "filtering": "Filtrowanie",
-    "filtering_explanation": "Wszystkie statusy zawierające te słowa będą wyciszone. Jedno słowo na linijkę.",
-    "follow_export": "Eksport obserwowanych",
-    "follow_export_button": "Eksportuj swoją listę obserwowanych do pliku CSV",
-    "follow_import": "Import obserwowanych",
-    "follow_import_error": "Błąd przy importowaniu obserwowanych",
-    "follows_imported": "Obserwowani zaimportowani! Przetwarzanie może trochę potrwać.",
-    "accent": "Akcent",
-    "foreground": "Pierwszy plan",
-    "general": "Ogólne",
-    "hide_attachments_in_convo": "Ukrywaj załączniki w rozmowach",
-    "hide_attachments_in_tl": "Ukrywaj załączniki w osi czasu",
-    "hide_muted_posts": "Ukrywaj wpisy wyciszonych użytkowników",
-    "max_thumbnails": "Maksymalna liczba miniatur w poście",
-    "hide_isp": "Ukryj panel informacji o instancji",
-    "preload_images": "Ładuj wstępnie obrazy",
-    "use_one_click_nsfw": "Otwieraj załączniki NSFW jednym kliknięciem",
-    "hide_post_stats": "Ukrywaj statysyki postów (np. liczbę polubień)",
-    "hide_user_stats": "Ukrywaj statysyki użytkowników (np. liczbę obserwujących)",
-    "hide_filtered_statuses": "Ukrywaj filtrowane statusy",
-    "import_blocks_from_a_csv_file": "Importuj blokady z pliku CSV",
-    "import_followers_from_a_csv_file": "Importuj obserwowanych z pliku CSV",
-    "import_theme": "Załaduj motyw",
-    "inputRadius": "Pola tekstowe",
-    "checkboxRadius": "Pola wyboru",
-    "instance_default": "(domyślny: {value})",
-    "instance_default_simple": "(domyślny)",
-    "interface": "Interfejs",
-    "interfaceLanguage": "Język interfejsu",
-    "invalid_theme_imported": "Wybrany plik nie jest obsługiwanym motywem Pleromy. Nie dokonano zmian w twoim motywie.",
-    "limited_availability": "Niedostępne w twojej przeglądarce",
-    "links": "Łącza",
-    "lock_account_description": "Ogranicz swoje konto dla zatwierdzonych obserwowanych",
-    "loop_video": "Zapętlaj filmy",
-    "loop_video_silent_only": "Zapętlaj tylko filmy bez dźwięku (np. mastodonowe „gify”)",
-    "mutes_tab": "Wyciszenia",
-    "play_videos_in_modal": "Odtwarzaj filmy bezpośrednio w przeglądarce mediów",
-    "use_contain_fit": "Nie przycinaj załączników na miniaturach",
-    "name": "Imię",
-    "name_bio": "Imię i bio",
-    "new_email": "Nowy email",
-    "new_password": "Nowe hasło",
-    "notification_visibility": "Rodzaje powiadomień do wyświetlania",
-    "notification_visibility_follows": "Obserwacje",
-    "notification_visibility_likes": "Ulubione",
-    "notification_visibility_mentions": "Wzmianki",
-    "notification_visibility_repeats": "Powtórzenia",
-    "notification_visibility_moves": "Użytkownik migruje",
-    "notification_visibility_emoji_reactions": "Reakcje",
-    "no_rich_text_description": "Usuwaj formatowanie ze wszystkich postów",
-    "no_blocks": "Bez blokad",
-    "no_mutes": "Bez wyciszeń",
-    "hide_follows_description": "Nie pokazuj kogo obserwuję",
-    "hide_followers_description": "Nie pokazuj kto mnie obserwuje",
-    "hide_follows_count_description": "Nie pokazuj licznika obserwowanych",
-    "hide_followers_count_description": "Nie pokazuj licznika obserwujących",
-    "show_admin_badge": "Pokazuj odznakę Administrator na moim profilu",
-    "show_moderator_badge": "Pokazuj odznakę Moderator na moim profilu",
-    "nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)",
-    "oauth_tokens": "Tokeny OAuth",
-    "token": "Token",
-    "refresh_token": "Odśwież token",
-    "valid_until": "Ważne do",
-    "revoke_token": "Odwołać",
-    "panelRadius": "Panele",
-    "pause_on_unfocused": "Wstrzymuj strumieniowanie kiedy karta nie jest aktywna",
-    "presets": "Gotowe motywy",
-    "profile_background": "Tło profilu",
-    "profile_banner": "Banner profilu",
-    "profile_tab": "Profil",
-    "radii_help": "Ustaw zaokrąglenie krawędzi interfejsu (w pikselach)",
-    "replies_in_timeline": "Odpowiedzi na osi czasu",
-    "reply_link_preview": "Włącz dymek z podglądem postu po najechaniu na znak odpowiedzi",
-    "reply_visibility_all": "Pokazuj wszystkie odpowiedzi",
-    "reply_visibility_following": "Pokazuj tylko odpowiedzi skierowane do mnie i osób które obserwuję",
-    "reply_visibility_self": "Pokazuj tylko odpowiedzi skierowane do mnie",
-    "autohide_floating_post_button": "Ukryj automatycznie przycisk \"Nowy post\" (mobile)",
-    "saving_err": "Nie udało się zapisać ustawień",
-    "saving_ok": "Zapisano ustawienia",
-    "search_user_to_block": "Wyszukaj kogo chcesz zablokować",
-    "search_user_to_mute": "Wyszukaj kogo chcesz wyciszyć",
-    "security_tab": "Bezpieczeństwo",
-    "scope_copy": "Kopiuj zakres podczas odpowiadania (DM-y zawsze są kopiowane)",
-    "minimal_scopes_mode": "Zminimalizuj opcje wyboru zakresu postów",
-    "set_new_avatar": "Ustaw nowy awatar",
-    "set_new_profile_background": "Ustaw nowe tło profilu",
-    "set_new_profile_banner": "Ustaw nowy banner profilu",
-    "settings": "Ustawienia",
-    "subject_input_always_show": "Zawsze pokazuj pole tematu",
-    "subject_line_behavior": "Kopiuj temat podczas odpowiedzi",
-    "subject_line_email": "Jak w mailach – „re: temat”",
-    "subject_line_mastodon": "Jak na Mastodonie – po prostu kopiuj",
-    "subject_line_noop": "Nie kopiuj",
-    "post_status_content_type": "Post status content type",
-    "stop_gifs": "Odtwarzaj GIFy po najechaniu kursorem",
-    "streaming": "Włącz automatycznie strumieniowanie nowych postów gdy jesteś na początku strony",
-    "user_mutes": "Users",
-    "useStreamingApi": "Otrzymuj posty i powiadomienia w czasie rzeczywistym",
-    "useStreamingApiWarning": "(Niezalecane, eksperymentalne, pomija posty)",
-    "text": "Tekst",
-    "theme": "Motyw",
-    "theme_help": "Użyj kolorów w notacji szesnastkowej (#rrggbb), by stworzyć swój motyw.",
-    "theme_help_v2_1": "Możesz też zastąpić kolory i widoczność poszczególnych komponentów przełączając pola wyboru, użyj „Wyczyść wszystko” aby usunąć wszystkie zastąpienia.",
-    "theme_help_v2_2": "Ikony pod niektórych wpisami są wskaźnikami kontrastu pomiędzy tłem a tekstem, po najechaniu na nie otrzymasz szczegółowe informacje. Zapamiętaj, że jeżeli używasz przezroczystości, wskaźniki pokazują najgorszy możliwy przypadek.",
-    "tooltipRadius": "Etykiety/alerty",
-    "type_domains_to_mute": "Wpisz domeny, które chcesz wyciszyć",
-    "upload_a_photo": "Wyślij zdjęcie",
-    "user_settings": "Ustawienia użytkownika",
-    "values": {
-      "false": "nie",
-      "true": "tak"
-    },
-    "fun": "Zabawa",
-    "greentext": "Memiczne strzałki",
-    "notifications": "Powiadomienia",
-    "notification_setting": "Otrzymuj powiadomienia od:",
-    "notification_setting_follows": "Ludzi których obserwujesz",
-    "notification_setting_non_follows": "Ludzi których nie obserwujesz",
-    "notification_setting_followers": "Ludzi którzy obserwują ciebie",
-    "notification_setting_non_followers": "Ludzi którzy nie obserwują ciebie",
-    "notification_mutes": "By przestać otrzymywać powiadomienia od jednego użytkownika, wycisz go",
-    "notification_blocks": "Blokowanie uzytkownika zatrzymuje wszystkie powiadomienia i odsubskrybowuje go.",
-    "enable_web_push_notifications": "Włącz powiadomienia push",
-    "style": {
-      "switcher": {
-        "keep_color": "Zachowaj kolory",
-        "keep_shadows": "Zachowaj cienie",
-        "keep_opacity": "Zachowaj widoczność",
-        "keep_roundness": "Zachowaj zaokrąglenie",
-        "keep_fonts": "Zachowaj czcionki",
-        "save_load_hint": "Opcje „zachowaj” pozwalają na pozostanie przy obecnych opcjach po wybraniu lub załadowaniu motywu, jak i przechowywanie ich podczas eksportowania motywu. Jeżeli wszystkie są odznaczone, eksportowanie motywu spowoduje zapisanie wszystkiego.",
-        "reset": "Wyzeruj",
-        "clear_all": "Wyczyść wszystko",
-        "clear_opacity": "Wyczyść widoczność",
-        "load_theme": "Załaduj motyw",
-        "keep_as_is": "Zostaw po staremu",
-        "use_snapshot": "Stara wersja",
-        "use_source": "Nowa wersja",
-        "help": {
-          "upgraded_from_v2": "PleromaFE zostało zaaktualizowane, motyw może wyglądać nieco inaczej niż sobie zapamiętałeś.",
-          "v2_imported": "Plik który zaimportowałeś został stworzony dla starszego FE. Próbujemy zwiększyć kompatybiliność, lecz wciąż mogą występować rozbieżności.",
-          "future_version_imported": "Plik który zaimportowałeś został stworzony w nowszej wersji FE.",
-          "older_version_imported": "Plik który zaimportowałeś został stworzony w starszej wersji FE.",
-          "snapshot_present": "Migawka motywu jest załadowana, więc wszystkie wartości zostały nadpisane. Zamiast tego, możesz załadować właściwe dane motywu",
-          "snapshot_missing": "Nie znaleziono migawki motywu w pliku, więc motyw może wyglądać inaczej niż pierwotnie zaplanowano.",
-          "fe_upgraded": "Silnik motywów PleromaFE został zaaktualizowany.",
-          "fe_downgraded": "Wersja PleromaFE została cofnięta.",
-          "migration_snapshot_ok": "Żeby być bezpiecznym, migawka motywu została załadowana. Możesz spróbować załadować dane motywu.",
-          "migration_napshot_gone": "Z jakiegoś powodu migawka zniknęła, niektóre rzeczy mogą wyglądać inaczej niż sobie zapamiętałeś.",
-          "snapshot_source_mismatch": "Konflikt wersji: najprawdopodobniej FE zostało cofnięte do poprzedniej wersji i zaaktualizowane ponownie, jeśli zmieniłeś motyw używając starszej wersji FE, najprawdopodobniej chcesz używać starszej wersji, w przeciwnym razie użyj nowej wersji."
-        }
-      },
-      "common": {
-        "color": "Kolor",
-        "opacity": "Widoczność",
-        "contrast": {
-          "hint": "Współczynnik kontrastu wynosi {ratio}, {level} {context}",
-          "level": {
-            "aa": "spełnia wymogi poziomu AA (minimalne)",
-            "aaa": "spełnia wymogi poziomu AAA (zalecane)",
-            "bad": "nie spełnia żadnych wymogów dostępności"
-          },
-          "context": {
-            "18pt": "dla dużego tekstu (18pt+)",
-            "text": "dla tekstu"
-          }
-        }
-      },
-      "common_colors": {
-        "_tab_label": "Ogólne",
-        "main": "Ogólne kolory",
-        "foreground_hint": "Zajrzyj do karty „Zaawansowane”, aby uzyskać dokładniejszą kontrolę",
-        "rgbo": "Ikony, wyróżnienia, odznaki"
-      },
-      "advanced_colors": {
-        "_tab_label": "Zaawansowane",
-        "alert": "Tło alertu",
-        "alert_error": "Błąd",
-        "alert_warning": "Ostrzeżenie",
-        "alert_neutral": "Neutralne",
-        "post": "Posty/Bio użytkowników",
-        "badge": "Tło odznaki",
-        "popover": "Etykiety, menu, popovery",
-        "badge_notification": "Powiadomienie",
-        "panel_header": "Nagłówek panelu",
-        "top_bar": "Górny pasek",
-        "borders": "Granice",
-        "buttons": "Przyciski",
-        "inputs": "Pola wejścia",
-        "faint_text": "Zanikający tekst",
-        "underlay": "Podkład",
-        "poll": "Wykres ankiety",
-        "icons": "Ikony",
-        "highlight": "Podświetlone elementy",
-        "pressed": "Naciśnięte",
-        "selectedPost": "Wybrany post",
-        "selectedMenu": "Wybrany element menu",
-        "disabled": "Wyłączone",
-        "toggled": "Przełączone",
-        "tabs": "Karty"
-      },
-      "radii": {
-        "_tab_label": "Zaokrąglenie"
-      },
-      "shadows": {
-        "_tab_label": "Cień i podświetlenie",
-        "component": "Komponent",
-        "override": "Zastąp",
-        "shadow_id": "Cień #{value}",
-        "blur": "Rozmycie",
-        "spread": "Szerokość",
-        "inset": "Inset",
-        "hintV3": "Dla cieni możesz również użyć notacji {0} by użyć inny slot koloru.",
-        "filter_hint": {
-          "always_drop_shadow": "Ostrzeżenie, ten cień zawsze używa {0} jeżeli to obsługiwane przez przeglądarkę.",
-          "drop_shadow_syntax": "{0} nie obsługuje parametru {1} i słowa kluczowego {2}.",
-          "avatar_inset": "Pamiętaj że użycie jednocześnie cieni inset i nie inset na awatarach może daćnieoczekiwane wyniki z przezroczystymi awatarami.",
-          "spread_zero": "Cienie o ujemnej szerokości będą widoczne tak, jakby wynosiła ona zero",
-          "inset_classic": "Cienie inset będą używały {0}"
+    "about": {
+        "mrf": {
+            "federation": "Federacja",
+            "keyword": {
+                "keyword_policies": "Zasady słów kluczowych",
+                "ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
+                "reject": "Odrzucanie",
+                "replace": "Zastąpienie",
+                "is_replaced_by": "→"
+            },
+            "mrf_policies": "Włączone zasady MRF",
+            "mrf_policies_desc": "Zasady MRF zmieniają zachowanie federowania instancji. Następujące zasady są włączone:",
+            "simple": {
+                "simple_policies": "Zasady specyficzne dla instancji",
+                "accept": "Akceptowanie",
+                "accept_desc": "Ta instancja akceptuje tylko posty z wymienionych instancji:",
+                "reject": "Odrzucanie",
+                "reject_desc": "Ta instancja odrzuca posty z wymienionych instancji:",
+                "quarantine": "Kwarantanna",
+                "quarantine_desc": "Ta instancja wysyła tylko publiczne posty do wymienionych instancji:",
+                "ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
+                "ftl_removal_desc": "Ta instancja usuwa wymienionych instancje z \"Całej znanej sieci\":",
+                "media_removal": "Usuwanie multimediów",
+                "media_removal_desc": "Ta instancja usuwa multimedia z postów od wymienionych instancji:",
+                "media_nsfw": "Multimedia ustawione jako wrażliwe",
+                "media_nsfw_desc": "Ta instancja wymusza, by multimedia z wymienionych instancji były ustawione jako wrażliwe:"
+            }
         },
-        "components": {
-          "panel": "Panel",
-          "panelHeader": "Nagłówek panelu",
-          "topBar": "Górny pasek",
-          "avatar": "Awatar użytkownika (w widoku profilu)",
-          "avatarStatus": "Awatar użytkownika (w widoku wpisu)",
-          "popup": "Wyskakujące okna i podpowiedzi",
-          "button": "Przycisk",
-          "buttonHover": "Przycisk (po najechaniu)",
-          "buttonPressed": "Przycisk (naciśnięty)",
-          "buttonPressedHover": "Przycisk(naciśnięty+najechany)",
-          "input": "Pole wejścia"
+        "staff": "Obsługa"
+    },
+    "chat": {
+        "title": "Czat"
+    },
+    "domain_mute_card": {
+        "mute": "Wycisz",
+        "mute_progress": "Wyciszam...",
+        "unmute": "Odcisz",
+        "unmute_progress": "Odciszam..."
+    },
+    "exporter": {
+        "export": "Eksportuj",
+        "processing": "Przetwarzam, za chwilę zostaniesz zapytany o ściągnięcie pliku"
+    },
+    "features_panel": {
+        "chat": "Czat",
+        "gopher": "Gopher",
+        "media_proxy": "Proxy mediów",
+        "scope_options": "Ustawienia zakresu",
+        "text_limit": "Limit tekstu",
+        "title": "Funkcje",
+        "who_to_follow": "Propozycje obserwacji"
+    },
+    "finder": {
+        "error_fetching_user": "Błąd przy pobieraniu profilu",
+        "find_user": "Znajdź użytkownika"
+    },
+    "general": {
+        "apply": "Zastosuj",
+        "submit": "Wyślij",
+        "more": "Więcej",
+        "generic_error": "Wystąpił błąd",
+        "optional": "nieobowiązkowe",
+        "show_more": "Pokaż więcej",
+        "show_less": "Pokaż mniej",
+        "dismiss": "Odrzuć",
+        "cancel": "Anuluj",
+        "disable": "Wyłącz",
+        "enable": "Włącz",
+        "confirm": "Potwierdź",
+        "verify": "Zweryfikuj"
+    },
+    "image_cropper": {
+        "crop_picture": "Przytnij obrazek",
+        "save": "Zapisz",
+        "save_without_cropping": "Zapisz bez przycinania",
+        "cancel": "Anuluj"
+    },
+    "importer": {
+        "submit": "Wyślij",
+        "success": "Zaimportowano pomyślnie.",
+        "error": "Wystąpił błąd podczas importowania pliku."
+    },
+    "login": {
+        "login": "Zaloguj",
+        "description": "Zaloguj używając OAuth",
+        "logout": "Wyloguj",
+        "password": "Hasło",
+        "placeholder": "n.p. lain",
+        "register": "Zarejestruj",
+        "username": "Użytkownik",
+        "hint": "Zaloguj się, aby dołączyć do dyskusji",
+        "authentication_code": "Kod weryfikacyjny",
+        "enter_recovery_code": "Wprowadź kod zapasowy",
+        "enter_two_factor_code": "Wprowadź kod weryfikacyjny",
+        "recovery_code": "Kod zapasowy",
+        "heading": {
+            "totp": "Weryfikacja dwuetapowa",
+            "recovery": "Zapasowa weryfikacja dwuetapowa"
         }
-      },
-      "fonts": {
-        "_tab_label": "Czcionki",
-        "help": "Wybierz czcionkę używaną przez elementy UI. Jeżeli wybierzesz niestandardową, musisz wpisać dokładnie tę nazwę, pod którą pojawia się w systemie.",
-        "components": {
-          "interface": "Interfejs",
-          "input": "Pola wejścia",
-          "post": "Tekst postu",
-          "postCode": "Tekst o stałej szerokości znaków w sformatowanym poście"
+    },
+    "media_modal": {
+        "previous": "Poprzednie",
+        "next": "Następne"
+    },
+    "nav": {
+        "about": "O nas",
+        "administration": "Administracja",
+        "back": "Wróć",
+        "chat": "Lokalny czat",
+        "friend_requests": "Prośby o możliwość obserwacji",
+        "mentions": "Wzmianki",
+        "interactions": "Interakcje",
+        "dms": "Wiadomości prywatne",
+        "public_tl": "Publiczna oś czasu",
+        "timeline": "Oś czasu",
+        "twkn": "Cała znana sieć",
+        "user_search": "Wyszukiwanie użytkowników",
+        "search": "Wyszukiwanie",
+        "who_to_follow": "Sugestie obserwacji",
+        "preferences": "Preferencje"
+    },
+    "notifications": {
+        "broken_favorite": "Nieznany status, szukam go…",
+        "favorited_you": "dodał(-a) twój status do ulubionych",
+        "followed_you": "obserwuje cię",
+        "load_older": "Załaduj starsze powiadomienia",
+        "notifications": "Powiadomienia",
+        "read": "Przeczytane!",
+        "repeated_you": "powtórzył(-a) twój status",
+        "no_more_notifications": "Nie masz więcej powiadomień",
+        "migrated_to": "wyemigrował do",
+        "reacted_with": "zareagował z {0}",
+        "follow_request": "chce cię obserwować"
+    },
+    "polls": {
+        "add_poll": "Dodaj ankietę",
+        "add_option": "Dodaj opcję",
+        "option": "Opcja",
+        "votes": "głosów",
+        "vote": "Głosuj",
+        "type": "Typ ankiety",
+        "single_choice": "jednokrotnego wyboru",
+        "multiple_choices": "wielokrotnego wyboru",
+        "expiry": "Czas trwania ankiety",
+        "expires_in": "Ankieta kończy się za{0}",
+        "expired": "Ankieta skończyła się {0} temu",
+        "not_enough_options": "Zbyt mało unikalnych opcji w ankiecie"
+    },
+    "emoji": {
+        "stickers": "Naklejki",
+        "emoji": "Emoji",
+        "keep_open": "Zostaw selektor otwarty",
+        "search_emoji": "Wyszukaj emoji",
+        "add_emoji": "Wstaw emoji",
+        "custom": "Niestandardowe emoji",
+        "unicode": "Emoji unicode",
+        "load_all_hint": "Załadowano pierwsze {saneAmount} emoji, Załadowanie wszystkich emoji może spowodować problemy z wydajnością.",
+        "load_all": "Ładuję wszystkie {emojiAmount} emoji"
+    },
+    "interactions": {
+        "favs_repeats": "Powtórzenia i ulubione",
+        "follows": "Nowi obserwujący",
+        "moves": "Użytkownik migruje",
+        "load_older": "Załaduj starsze interakcje"
+    },
+    "post_status": {
+        "new_status": "Dodaj nowy status",
+        "account_not_locked_warning": "Twoje konto nie jest {0}. Każdy może cię zaobserwować aby zobaczyć wpisy tylko dla obserwujących.",
+        "account_not_locked_warning_link": "zablokowane",
+        "attachments_sensitive": "Oznacz załączniki jako wrażliwe",
+        "content_type": {
+            "text/plain": "Czysty tekst",
+            "text/html": "HTML",
+            "text/markdown": "Markdown",
+            "text/bbcode": "BBCode"
         },
-        "family": "Nazwa czcionki",
-        "size": "Rozmiar (w pikselach)",
-        "weight": "Grubość",
-        "custom": "Niestandardowa"
-      },
-      "preview": {
-        "header": "Podgląd",
-        "content": "Zawartość",
-        "error": "Przykładowy błąd",
-        "button": "Przycisk",
-        "text": "Trochę więcej {0} i {1}",
-        "mono": "treści",
-        "input": "Właśnie wróciłem z kościoła",
-        "faint_link": "pomocny podręcznik",
-        "fine_print": "Przeczytaj nasz {0}, aby nie nauczyć się niczego przydatnego!",
-        "header_faint": "W porządku",
-        "checkbox": "Przeleciałem przez zasady użytkowania",
-        "link": "i fajny mały odnośnik"
-      }
+        "content_warning": "Temat (nieobowiązkowy)",
+        "default": "Właśnie wróciłem z kościoła",
+        "direct_warning_to_all": "Ten wpis zobaczą wszystkie osoby, o których wspomniałeś(-aś).",
+        "direct_warning_to_first_only": "Ten wpis zobaczą tylko te osoby, o których wspomniałeś(-aś) na początku wiadomości.",
+        "posting": "Wysyłanie",
+        "scope_notice": {
+            "public": "Ten post będzie widoczny dla każdego",
+            "private": "Ten post będzie widoczny tylko dla twoich obserwujących",
+            "unlisted": "Ten post nie będzie widoczny na publicznej osi czasu i całej znanej sieci"
+        },
+        "scope": {
+            "direct": "Bezpośredni – Tylko dla wspomnianych użytkowników",
+            "private": "Tylko dla obserwujących – Umieść dla osób, które cię obserwują",
+            "public": "Publiczny – Umieść na publicznych osiach czasu",
+            "unlisted": "Niewidoczny – Nie umieszczaj na publicznych osiach czasu"
+        }
     },
-    "version": {
-      "title": "Wersja",
-      "backend_version": "Wersja back-endu",
-      "frontend_version": "Wersja front-endu"
-    }
-  },
-  "time": {
-    "day": "{0} dzień",
-    "days": "{0} dni",
-    "day_short": "{0}d",
-    "days_short": "{0}d",
-    "hour": "{0} godzina",
-    "hours": "{0} godzin",
-    "hour_short": "{0} godz.",
-    "hours_short": "{0} godz.",
-    "in_future": "za {0}",
-    "in_past": "{0} temu",
-    "minute": "{0} minuta",
-    "minutes": "{0} minut",
-    "minute_short": "{0}min",
-    "minutes_short": "{0}min",
-    "month": "{0} miesiąc",
-    "months": "{0} miesięcy",
-    "month_short": "{0} mies.",
-    "months_short": "{0} mies.",
-    "now": "teraz",
-    "now_short": "teraz",
-    "second": "{0} sekunda",
-    "seconds": "{0} sekund",
-    "second_short": "{0}s",
-    "seconds_short": "{0}s",
-    "week": "{0} tydzień",
-    "weeks": "{0} tygodni",
-    "week_short": "{0} tydz.",
-    "weeks_short": "{0} tyg.",
-    "year": "{0} rok",
-    "years": "{0} lata",
-    "year_short": "{0} r.",
-    "years_short": "{0} lata"
-  },
-  "timeline": {
-    "collapse": "Zwiń",
-    "conversation": "Rozmowa",
-    "error_fetching": "Błąd pobierania",
-    "load_older": "Załaduj starsze statusy",
-    "no_retweet_hint": "Wpis oznaczony jako tylko dla obserwujących lub bezpośredni nie może zostać powtórzony",
-    "repeated": "powtórzył(-a)",
-    "show_new": "Pokaż nowe",
-    "up_to_date": "Na bieżąco",
-    "no_more_statuses": "Brak kolejnych statusów",
-    "no_statuses": "Brak statusów"
-  },
-  "status": {
-    "favorites": "Ulubione",
-    "repeats": "Powtórzenia",
-    "delete": "Usuń status",
-    "pin": "Przypnij na profilu",
-    "unpin": "Odepnij z profilu",
-    "pinned": "Przypnięte",
-    "delete_confirm": "Czy naprawdę chcesz usunąć ten status?",
-    "reply_to": "Odpowiedź dla",
-    "replies_list": "Odpowiedzi:",
-    "mute_conversation": "Wycisz konwersację",
-    "unmute_conversation": "Odcisz konwersację"
-  },
-  "user_card": {
-    "approve": "Przyjmij",
-    "block": "Zablokuj",
-    "blocked": "Zablokowany!",
-    "deny": "Odrzuć",
-    "favorites": "Ulubione",
-    "follow": "Obserwuj",
-    "follow_sent": "Wysłano prośbę!",
-    "follow_progress": "Wysyłam prośbę…",
-    "follow_again": "Wysłać prośbę ponownie?",
-    "follow_unfollow": "Przestań obserwować",
-    "followees": "Obserwowani",
-    "followers": "Obserwujący",
-    "following": "Obserwowany!",
-    "follows_you": "Obserwuje cię!",
-    "hidden": "Ukryte",
-    "its_you": "To ty!",
-    "media": "Media",
-    "mention": "Wspomnienie",
-    "mute": "Wycisz",
-    "muted": "Wyciszony(-a)",
-    "per_day": "dziennie",
-    "remote_follow": "Zdalna obserwacja",
-    "report": "Raportuj",
-    "statuses": "Statusy",
-    "subscribe": "Subskrybuj",
-    "unsubscribe": "Odsubskrybuj",
-    "unblock": "Odblokuj",
-    "unblock_progress": "Odblokowuję…",
-    "block_progress": "Blokuję…",
-    "unmute": "Cofnij wyciszenie",
-    "unmute_progress": "Cofam wyciszenie…",
-    "mute_progress": "Wyciszam…",
-    "hide_repeats": "Ukryj powtórzenia",
-    "show_repeats": "Pokaż powtórzenia",
-    "admin_menu": {
-      "moderation": "Moderacja",
-      "grant_admin": "Przyznaj admina",
-      "revoke_admin": "Odwołaj admina",
-      "grant_moderator": "Przyznaj moderatora",
-      "revoke_moderator": "Odwołaj moderatora",
-      "activate_account": "Aktywuj konto",
-      "deactivate_account": "Dezaktywuj konto",
-      "delete_account": "Usuń konto",
-      "force_nsfw": "Oznacz wszystkie posty jako NSFW",
-      "strip_media": "Usuń multimedia z postów",
-      "force_unlisted": "Wymuś posty na niepubliczne",
-      "sandbox": "Wymuś by posty były tylko dla obserwujących",
-      "disable_remote_subscription": "Zakaż obserwowania użytkownika ze zdalnych instancji",
-      "disable_any_subscription": "Zakaż całkowicie obserwowania użytkownika",
-      "quarantine": "Zakaż federowania postów od tego użytkownika",
-      "delete_user": "Usuń użytkownika",
-      "delete_user_confirmation": "Czy jesteś absolutnie pewny? Ta operacja nie może być cofnięta."
-    }
-  },
-  "user_profile": {
-    "timeline_title": "Oś czasu użytkownika",
-    "profile_does_not_exist": "Przepraszamy, ten profil nie istnieje.",
-    "profile_loading_error": "Przepraszamy, wystąpił błąd podczas ładowania tego profilu."
-  },
-  "user_reporting": {
-    "title": "Raportowanie {0}",
-    "add_comment_description": "Raport zostanie wysłany do moderatorów instancji. Możesz dodać powód dlaczego raportujesz to konto poniżej:",
-    "additional_comments": "Dodatkowe komentarze",
-    "forward_description": "To konto jest z innego serwera. Wysłać również tam kopię raportu?",
-    "forward_to": "Przekaż do{0}",
-    "submit": "Wyślij",
-    "generic_error": "Wystąpił błąd podczas przetwarzania twojej prośby."
-  },
-  "who_to_follow": {
-    "more": "Więcej",
-    "who_to_follow": "Propozycje obserwacji"
-  },
-  "tool_tip": {
-    "media_upload": "Wyślij media",
-    "repeat": "Powtórz",
-    "reply": "Odpowiedz",
-    "favorite": "Dodaj do ulubionych",
-    "add_reaction": "Dodaj reakcję",
-    "user_settings": "Ustawienia użytkownika"
-  },
-  "upload":{
-    "error": {
-      "base": "Wysyłanie nie powiodło się.",
-      "file_too_big": "Zbyt duży plik [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-      "default": "Spróbuj ponownie później"
+    "registration": {
+        "bio": "Bio",
+        "email": "E-mail",
+        "fullname": "Wyświetlana nazwa profilu",
+        "password_confirm": "Potwierdzenie hasła",
+        "registration": "Rejestracja",
+        "token": "Token zaproszenia",
+        "captcha": "CAPTCHA",
+        "new_captcha": "Naciśnij na obrazek, aby dostać nowy kod captcha",
+        "username_placeholder": "np. lain",
+        "fullname_placeholder": "np. Lain Iwakura",
+        "bio_placeholder": "e.g.\nCześć, jestem Lain.\nJestem dziewczynką z anime żyjącą na peryferiach Japonii. Możesz znać mnie z Wired.",
+        "validations": {
+            "username_required": "nie może być pusta",
+            "fullname_required": "nie może być pusta",
+            "email_required": "nie może być pusty",
+            "password_required": "nie może być puste",
+            "password_confirmation_required": "nie może być puste",
+            "password_confirmation_match": "musi być takie jak hasło"
+        }
     },
-    "file_size_units": {
-      "B": "B",
-      "KiB": "KiB",
-      "MiB": "MiB",
-      "GiB": "GiB",
-      "TiB": "TiB"
+    "remote_user_resolver": {
+        "remote_user_resolver": "Wyszukiwarka użytkowników nietutejszych",
+        "searching_for": "Szukam",
+        "error": "Nie znaleziono."
+    },
+    "selectable_list": {
+        "select_all": "Zaznacz wszystko"
+    },
+    "settings": {
+        "app_name": "Nazwa aplikacji",
+        "security": "Bezpieczeństwo",
+        "enter_current_password_to_confirm": "Wprowadź obecne hasło, by potwierdzić twoją tożsamość",
+        "mfa": {
+            "otp": "OTP",
+            "setup_otp": "Ustaw OTP",
+            "wait_pre_setup_otp": "początkowe ustawianie OTP",
+            "confirm_and_enable": "Potwierdź i włącz OTP",
+            "title": "Weryfikacja dwuetapowa",
+            "generate_new_recovery_codes": "Wygeneruj nowe kody zapasowe",
+            "warning_of_generate_new_codes": "Po tym gdy generujesz nowe kody zapasowe, stare przestaną działać.",
+            "recovery_codes": "Kody zapasowe.",
+            "waiting_a_recovery_codes": "Otrzymuję kody zapasowe...",
+            "recovery_codes_warning": "Spisz kody na kartce papieru, albo zapisz je w bezpiecznym miejscu - inaczej nie zobaczysz ich już nigdy. Jeśli stracisz dostęp do twojej aplikacji 2FA i kodów zapasowych, nie będziesz miał dostępu do swojego konta.",
+            "authentication_methods": "Metody weryfikacji",
+            "scan": {
+                "title": "Skanuj",
+                "desc": "Zeskanuj ten kod QR używając twojej aplikacji 2FA albo wpisz ten klucz:",
+                "secret_code": "Klucz"
+            },
+            "verify": {
+                "desc": "By włączyć weryfikację dwuetapową, wpisz kod z twojej aplikacji 2FA:"
+            }
+        },
+        "allow_following_move": "Zezwalaj na automatyczną obserwację gdy obserwowane konto migruje",
+        "attachmentRadius": "Załączniki",
+        "attachments": "Załączniki",
+        "autoload": "Włącz automatyczne ładowanie po przewinięciu do końca strony",
+        "avatar": "Awatar",
+        "avatarAltRadius": "Awatary (powiadomienia)",
+        "avatarRadius": "Awatary",
+        "background": "Tło",
+        "bio": "Bio",
+        "block_export": "Eksport blokad",
+        "block_export_button": "Eksportuj twoje blokady do pliku .csv",
+        "block_import": "Import blokad",
+        "block_import_error": "Wystąpił błąd podczas importowania blokad",
+        "blocks_imported": "Zaimportowano blokady, przetwarzanie może zająć trochę czasu.",
+        "blocks_tab": "Bloki",
+        "btnRadius": "Przyciski",
+        "cBlue": "Niebieski (odpowiedz, obserwuj)",
+        "cGreen": "Zielony (powtórzenia)",
+        "cOrange": "Pomarańczowy (ulubione)",
+        "cRed": "Czerwony (anuluj)",
+        "change_email": "Zmień email",
+        "change_email_error": "Wystąpił problem podczas zmiany emaila.",
+        "changed_email": "Pomyślnie zmieniono email!",
+        "change_password": "Zmień hasło",
+        "change_password_error": "Podczas zmiany hasła wystąpił problem.",
+        "changed_password": "Pomyślnie zmieniono hasło!",
+        "collapse_subject": "Zwijaj posty z tematami",
+        "composing": "Pisanie",
+        "confirm_new_password": "Potwierdź nowe hasło",
+        "current_avatar": "Twój obecny awatar",
+        "current_password": "Obecne hasło",
+        "current_profile_banner": "Twój obecny banner profilu",
+        "data_import_export_tab": "Import/eksport danych",
+        "default_vis": "Domyślny zakres widoczności",
+        "delete_account": "Usuń konto",
+        "delete_account_description": "Trwale usuń konto i wszystkie posty.",
+        "delete_account_error": "Wystąpił problem z usuwaniem twojego konta. Jeżeli problem powtarza się, poinformuj administratora swojej instancji.",
+        "delete_account_instructions": "Wprowadź swoje hasło w poniższe pole aby potwierdzić usunięcie konta.",
+        "discoverable": "Zezwól na odkrywanie tego konta w wynikach wyszukiwania i innych usługach",
+        "domain_mutes": "Domeny",
+        "avatar_size_instruction": "Zalecany minimalny rozmiar awatarów to 150x150 pikseli.",
+        "pad_emoji": "Dodaj odstęp z obu stron emoji podczas dodawania selektorem",
+        "emoji_reactions_on_timeline": "Pokaż reakcje emoji na osi czasu",
+        "export_theme": "Zapisz motyw",
+        "filtering": "Filtrowanie",
+        "filtering_explanation": "Wszystkie statusy zawierające te słowa będą wyciszone. Jedno słowo na linijkę.",
+        "follow_export": "Eksport obserwowanych",
+        "follow_export_button": "Eksportuj swoją listę obserwowanych do pliku CSV",
+        "follow_import": "Import obserwowanych",
+        "follow_import_error": "Błąd przy importowaniu obserwowanych",
+        "follows_imported": "Obserwowani zaimportowani! Przetwarzanie może trochę potrwać.",
+        "accent": "Akcent",
+        "foreground": "Pierwszy plan",
+        "general": "Ogólne",
+        "hide_attachments_in_convo": "Ukrywaj załączniki w rozmowach",
+        "hide_attachments_in_tl": "Ukrywaj załączniki w osi czasu",
+        "hide_muted_posts": "Ukrywaj wpisy wyciszonych użytkowników",
+        "max_thumbnails": "Maksymalna liczba miniatur w poście",
+        "hide_isp": "Ukryj panel informacji o instancji",
+        "preload_images": "Ładuj wstępnie obrazy",
+        "use_one_click_nsfw": "Otwieraj załączniki NSFW jednym kliknięciem",
+        "hide_post_stats": "Ukrywaj statysyki postów (np. liczbę polubień)",
+        "hide_user_stats": "Ukrywaj statysyki użytkowników (np. liczbę obserwujących)",
+        "hide_filtered_statuses": "Ukrywaj filtrowane statusy",
+        "import_blocks_from_a_csv_file": "Importuj blokady z pliku CSV",
+        "import_followers_from_a_csv_file": "Importuj obserwowanych z pliku CSV",
+        "import_theme": "Załaduj motyw",
+        "inputRadius": "Pola tekstowe",
+        "checkboxRadius": "Pola wyboru",
+        "instance_default": "(domyślny: {value})",
+        "instance_default_simple": "(domyślny)",
+        "interface": "Interfejs",
+        "interfaceLanguage": "Język interfejsu",
+        "invalid_theme_imported": "Wybrany plik nie jest obsługiwanym motywem Pleromy. Nie dokonano zmian w twoim motywie.",
+        "limited_availability": "Niedostępne w twojej przeglądarce",
+        "links": "Łącza",
+        "lock_account_description": "Ogranicz swoje konto dla zatwierdzonych obserwowanych",
+        "loop_video": "Zapętlaj filmy",
+        "loop_video_silent_only": "Zapętlaj tylko filmy bez dźwięku (np. mastodonowe „gify”)",
+        "mutes_tab": "Wyciszenia",
+        "play_videos_in_modal": "Odtwarzaj filmy bezpośrednio w przeglądarce mediów",
+        "use_contain_fit": "Nie przycinaj załączników na miniaturach",
+        "name": "Imię",
+        "name_bio": "Imię i bio",
+        "new_email": "Nowy email",
+        "new_password": "Nowe hasło",
+        "notification_visibility": "Rodzaje powiadomień do wyświetlania",
+        "notification_visibility_follows": "Obserwacje",
+        "notification_visibility_likes": "Ulubione",
+        "notification_visibility_mentions": "Wzmianki",
+        "notification_visibility_repeats": "Powtórzenia",
+        "notification_visibility_moves": "Użytkownik migruje",
+        "notification_visibility_emoji_reactions": "Reakcje",
+        "no_rich_text_description": "Usuwaj formatowanie ze wszystkich postów",
+        "no_blocks": "Bez blokad",
+        "no_mutes": "Bez wyciszeń",
+        "hide_follows_description": "Nie pokazuj kogo obserwuję",
+        "hide_followers_description": "Nie pokazuj kto mnie obserwuje",
+        "hide_follows_count_description": "Nie pokazuj licznika obserwowanych",
+        "hide_followers_count_description": "Nie pokazuj licznika obserwujących",
+        "show_admin_badge": "Pokazuj odznakę Administrator na moim profilu",
+        "show_moderator_badge": "Pokazuj odznakę Moderator na moim profilu",
+        "nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)",
+        "oauth_tokens": "Tokeny OAuth",
+        "token": "Token",
+        "refresh_token": "Odśwież token",
+        "valid_until": "Ważne do",
+        "revoke_token": "Odwołać",
+        "panelRadius": "Panele",
+        "pause_on_unfocused": "Wstrzymuj strumieniowanie kiedy karta nie jest aktywna",
+        "presets": "Gotowe motywy",
+        "profile_background": "Tło profilu",
+        "profile_banner": "Banner profilu",
+        "profile_tab": "Profil",
+        "radii_help": "Ustaw zaokrąglenie krawędzi interfejsu (w pikselach)",
+        "replies_in_timeline": "Odpowiedzi na osi czasu",
+        "reply_link_preview": "Włącz dymek z podglądem postu po najechaniu na znak odpowiedzi",
+        "reply_visibility_all": "Pokazuj wszystkie odpowiedzi",
+        "reply_visibility_following": "Pokazuj tylko odpowiedzi skierowane do mnie i osób które obserwuję",
+        "reply_visibility_self": "Pokazuj tylko odpowiedzi skierowane do mnie",
+        "autohide_floating_post_button": "Ukryj automatycznie przycisk \"Nowy post\" (mobile)",
+        "saving_err": "Nie udało się zapisać ustawień",
+        "saving_ok": "Zapisano ustawienia",
+        "search_user_to_block": "Wyszukaj kogo chcesz zablokować",
+        "search_user_to_mute": "Wyszukaj kogo chcesz wyciszyć",
+        "security_tab": "Bezpieczeństwo",
+        "scope_copy": "Kopiuj zakres podczas odpowiadania (DM-y zawsze są kopiowane)",
+        "minimal_scopes_mode": "Zminimalizuj opcje wyboru zakresu postów",
+        "set_new_avatar": "Ustaw nowy awatar",
+        "set_new_profile_background": "Ustaw nowe tło profilu",
+        "set_new_profile_banner": "Ustaw nowy banner profilu",
+        "settings": "Ustawienia",
+        "subject_input_always_show": "Zawsze pokazuj pole tematu",
+        "subject_line_behavior": "Kopiuj temat podczas odpowiedzi",
+        "subject_line_email": "Jak w mailach – „re: temat”",
+        "subject_line_mastodon": "Jak na Mastodonie – po prostu kopiuj",
+        "subject_line_noop": "Nie kopiuj",
+        "post_status_content_type": "Post status content type",
+        "stop_gifs": "Odtwarzaj GIFy po najechaniu kursorem",
+        "streaming": "Włącz automatycznie strumieniowanie nowych postów gdy jesteś na początku strony",
+        "user_mutes": "Użytkownicy",
+        "useStreamingApi": "Otrzymuj posty i powiadomienia w czasie rzeczywistym",
+        "useStreamingApiWarning": "(Niezalecane, eksperymentalne, pomija posty)",
+        "text": "Tekst",
+        "theme": "Motyw",
+        "theme_help": "Użyj kolorów w notacji szesnastkowej (#rrggbb), by stworzyć swój motyw.",
+        "theme_help_v2_1": "Możesz też zastąpić kolory i widoczność poszczególnych komponentów przełączając pola wyboru, użyj „Wyczyść wszystko” aby usunąć wszystkie zastąpienia.",
+        "theme_help_v2_2": "Ikony pod niektórych wpisami są wskaźnikami kontrastu pomiędzy tłem a tekstem, po najechaniu na nie otrzymasz szczegółowe informacje. Zapamiętaj, że jeżeli używasz przezroczystości, wskaźniki pokazują najgorszy możliwy przypadek.",
+        "tooltipRadius": "Etykiety/alerty",
+        "type_domains_to_mute": "Wpisz domeny, które chcesz wyciszyć",
+        "upload_a_photo": "Wyślij zdjęcie",
+        "user_settings": "Ustawienia użytkownika",
+        "values": {
+            "false": "nie",
+            "true": "tak"
+        },
+        "fun": "Zabawa",
+        "greentext": "Memiczne strzałki",
+        "notifications": "Powiadomienia",
+        "notification_setting": "Otrzymuj powiadomienia od:",
+        "notification_setting_follows": "Ludzi których obserwujesz",
+        "notification_setting_non_follows": "Ludzi których nie obserwujesz",
+        "notification_setting_followers": "Ludzi którzy obserwują ciebie",
+        "notification_setting_non_followers": "Ludzi którzy nie obserwują ciebie",
+        "notification_mutes": "By przestać otrzymywać powiadomienia od jednego użytkownika, wycisz go.",
+        "notification_blocks": "Blokowanie uzytkownika zatrzymuje wszystkie powiadomienia i odsubskrybowuje go.",
+        "enable_web_push_notifications": "Włącz powiadomienia push",
+        "style": {
+            "switcher": {
+                "keep_color": "Zachowaj kolory",
+                "keep_shadows": "Zachowaj cienie",
+                "keep_opacity": "Zachowaj widoczność",
+                "keep_roundness": "Zachowaj zaokrąglenie",
+                "keep_fonts": "Zachowaj czcionki",
+                "save_load_hint": "Opcje „zachowaj” pozwalają na pozostanie przy obecnych opcjach po wybraniu lub załadowaniu motywu, jak i przechowywanie ich podczas eksportowania motywu. Jeżeli wszystkie są odznaczone, eksportowanie motywu spowoduje zapisanie wszystkiego.",
+                "reset": "Wyzeruj",
+                "clear_all": "Wyczyść wszystko",
+                "clear_opacity": "Wyczyść widoczność",
+                "load_theme": "Załaduj motyw",
+                "keep_as_is": "Zostaw po staremu",
+                "use_snapshot": "Stara wersja",
+                "use_source": "Nowa wersja",
+                "help": {
+                    "upgraded_from_v2": "PleromaFE zostało zaaktualizowane, motyw może wyglądać nieco inaczej niż sobie zapamiętałeś.",
+                    "v2_imported": "Plik który zaimportowałeś został stworzony dla starszego FE. Próbujemy zwiększyć kompatybiliność, lecz wciąż mogą występować rozbieżności.",
+                    "future_version_imported": "Plik który zaimportowałeś został stworzony w nowszej wersji FE.",
+                    "older_version_imported": "Plik który zaimportowałeś został stworzony w starszej wersji FE.",
+                    "snapshot_present": "Migawka motywu jest załadowana, więc wszystkie wartości zostały nadpisane. Zamiast tego możesz załadować właściwe dane motywu.",
+                    "snapshot_missing": "Nie znaleziono migawki motywu w pliku, więc motyw może wyglądać inaczej niż pierwotnie zaplanowano.",
+                    "fe_upgraded": "Silnik motywów PleromaFE został zaaktualizowany.",
+                    "fe_downgraded": "Wersja PleromaFE została cofnięta.",
+                    "migration_snapshot_ok": "Żeby być bezpiecznym, migawka motywu została załadowana. Możesz spróbować załadować dane motywu.",
+                    "migration_napshot_gone": "Z jakiegoś powodu migawka zniknęła, niektóre rzeczy mogą wyglądać inaczej niż sobie zapamiętałeś.",
+                    "snapshot_source_mismatch": "Konflikt wersji: najprawdopodobniej FE zostało cofnięte do poprzedniej wersji i zaaktualizowane ponownie, jeśli zmieniłeś motyw używając starszej wersji FE, najprawdopodobniej chcesz używać starszej wersji, w przeciwnym razie użyj nowej wersji."
+                }
+            },
+            "common": {
+                "color": "Kolor",
+                "opacity": "Widoczność",
+                "contrast": {
+                    "hint": "Współczynnik kontrastu wynosi {ratio}, {level} {context}",
+                    "level": {
+                        "aa": "spełnia wymogi poziomu AA (minimalne)",
+                        "aaa": "spełnia wymogi poziomu AAA (zalecane)",
+                        "bad": "nie spełnia żadnych wymogów dostępności"
+                    },
+                    "context": {
+                        "18pt": "dla dużego tekstu (18pt+)",
+                        "text": "dla tekstu"
+                    }
+                }
+            },
+            "common_colors": {
+                "_tab_label": "Ogólne",
+                "main": "Ogólne kolory",
+                "foreground_hint": "Zajrzyj do karty „Zaawansowane”, aby uzyskać dokładniejszą kontrolę",
+                "rgbo": "Ikony, wyróżnienia, odznaki"
+            },
+            "advanced_colors": {
+                "_tab_label": "Zaawansowane",
+                "alert": "Tło alertu",
+                "alert_error": "Błąd",
+                "alert_warning": "Ostrzeżenie",
+                "alert_neutral": "Neutralne",
+                "post": "Posty/Bio użytkowników",
+                "badge": "Tło odznaki",
+                "popover": "Etykiety, menu, popovery",
+                "badge_notification": "Powiadomienie",
+                "panel_header": "Nagłówek panelu",
+                "top_bar": "Górny pasek",
+                "borders": "Granice",
+                "buttons": "Przyciski",
+                "inputs": "Pola wejścia",
+                "faint_text": "Zanikający tekst",
+                "underlay": "Podkład",
+                "poll": "Wykres ankiety",
+                "icons": "Ikony",
+                "highlight": "Podświetlone elementy",
+                "pressed": "Naciśnięte",
+                "selectedPost": "Wybrany post",
+                "selectedMenu": "Wybrany element menu",
+                "disabled": "Wyłączone",
+                "toggled": "Przełączone",
+                "tabs": "Karty"
+            },
+            "radii": {
+                "_tab_label": "Zaokrąglenie"
+            },
+            "shadows": {
+                "_tab_label": "Cień i podświetlenie",
+                "component": "Komponent",
+                "override": "Zastąp",
+                "shadow_id": "Cień #{value}",
+                "blur": "Rozmycie",
+                "spread": "Szerokość",
+                "inset": "Inset",
+                "hintV3": "Dla cieni możesz również użyć notacji {0} by użyć inny slot koloru.",
+                "filter_hint": {
+                    "always_drop_shadow": "Ostrzeżenie, ten cień zawsze używa {0} jeżeli to obsługiwane przez przeglądarkę.",
+                    "drop_shadow_syntax": "{0} nie obsługuje parametru {1} i słowa kluczowego {2}.",
+                    "avatar_inset": "Pamiętaj że użycie jednocześnie cieni inset i nie inset na awatarach może daćnieoczekiwane wyniki z przezroczystymi awatarami.",
+                    "spread_zero": "Cienie o ujemnej szerokości będą widoczne tak, jakby wynosiła ona zero",
+                    "inset_classic": "Cienie inset będą używały {0}"
+                },
+                "components": {
+                    "panel": "Panel",
+                    "panelHeader": "Nagłówek panelu",
+                    "topBar": "Górny pasek",
+                    "avatar": "Awatar użytkownika (w widoku profilu)",
+                    "avatarStatus": "Awatar użytkownika (w widoku wpisu)",
+                    "popup": "Wyskakujące okna i podpowiedzi",
+                    "button": "Przycisk",
+                    "buttonHover": "Przycisk (po najechaniu)",
+                    "buttonPressed": "Przycisk (naciśnięty)",
+                    "buttonPressedHover": "Przycisk(naciśnięty+najechany)",
+                    "input": "Pole wejścia"
+                }
+            },
+            "fonts": {
+                "_tab_label": "Czcionki",
+                "help": "Wybierz czcionkę używaną przez elementy UI. Jeżeli wybierzesz niestandardową, musisz wpisać dokładnie tę nazwę, pod którą pojawia się w systemie.",
+                "components": {
+                    "interface": "Interfejs",
+                    "input": "Pola wejścia",
+                    "post": "Tekst postu",
+                    "postCode": "Tekst o stałej szerokości znaków w sformatowanym poście"
+                },
+                "family": "Nazwa czcionki",
+                "size": "Rozmiar (w pikselach)",
+                "weight": "Grubość",
+                "custom": "Niestandardowa"
+            },
+            "preview": {
+                "header": "Podgląd",
+                "content": "Zawartość",
+                "error": "Przykładowy błąd",
+                "button": "Przycisk",
+                "text": "Trochę więcej {0} i {1}",
+                "mono": "treści",
+                "input": "Właśnie wróciłem z kościoła",
+                "faint_link": "pomocny podręcznik",
+                "fine_print": "Przeczytaj nasz {0}, aby nie nauczyć się niczego przydatnego!",
+                "header_faint": "W porządku",
+                "checkbox": "Przeleciałem przez zasady użytkowania",
+                "link": "i fajny mały odnośnik"
+            }
+        },
+        "version": {
+            "title": "Wersja",
+            "backend_version": "Wersja back-endu",
+            "frontend_version": "Wersja front-endu"
+        },
+        "notification_setting_privacy": "Prywatność",
+        "notification_setting_filters": "Filtry",
+        "notification_setting_privacy_option": "Ukryj nadawcę i zawartość powiadomień push"
+    },
+    "time": {
+        "day": "{0} dzień",
+        "days": "{0} dni",
+        "day_short": "{0}d",
+        "days_short": "{0}d",
+        "hour": "{0} godzina",
+        "hours": "{0} godzin",
+        "hour_short": "{0} godz.",
+        "hours_short": "{0} godz.",
+        "in_future": "za {0}",
+        "in_past": "{0} temu",
+        "minute": "{0} minuta",
+        "minutes": "{0} minut",
+        "minute_short": "{0}min",
+        "minutes_short": "{0}min",
+        "month": "{0} miesiąc",
+        "months": "{0} miesięcy",
+        "month_short": "{0} mies.",
+        "months_short": "{0} mies.",
+        "now": "teraz",
+        "now_short": "teraz",
+        "second": "{0} sekunda",
+        "seconds": "{0} sekund",
+        "second_short": "{0}s",
+        "seconds_short": "{0}s",
+        "week": "{0} tydzień",
+        "weeks": "{0} tygodni",
+        "week_short": "{0} tydz.",
+        "weeks_short": "{0} tyg.",
+        "year": "{0} rok",
+        "years": "{0} lata",
+        "year_short": "{0} r.",
+        "years_short": "{0} lata"
+    },
+    "timeline": {
+        "collapse": "Zwiń",
+        "conversation": "Rozmowa",
+        "error_fetching": "Błąd pobierania",
+        "load_older": "Załaduj starsze statusy",
+        "no_retweet_hint": "Wpis oznaczony jako tylko dla obserwujących lub bezpośredni nie może zostać powtórzony",
+        "repeated": "powtórzył(-a)",
+        "show_new": "Pokaż nowe",
+        "up_to_date": "Na bieżąco",
+        "no_more_statuses": "Brak kolejnych statusów",
+        "no_statuses": "Brak statusów"
+    },
+    "status": {
+        "favorites": "Ulubione",
+        "repeats": "Powtórzenia",
+        "delete": "Usuń status",
+        "pin": "Przypnij na profilu",
+        "unpin": "Odepnij z profilu",
+        "pinned": "Przypnięte",
+        "delete_confirm": "Czy naprawdę chcesz usunąć ten status?",
+        "reply_to": "Odpowiedź dla",
+        "replies_list": "Odpowiedzi:",
+        "mute_conversation": "Wycisz konwersację",
+        "unmute_conversation": "Odcisz konwersację",
+        "status_unavailable": "Status niedostępny",
+        "copy_link": "Kopiuj link do statusu"
+    },
+    "user_card": {
+        "approve": "Przyjmij",
+        "block": "Zablokuj",
+        "blocked": "Zablokowany!",
+        "deny": "Odrzuć",
+        "favorites": "Ulubione",
+        "follow": "Obserwuj",
+        "follow_sent": "Wysłano prośbę!",
+        "follow_progress": "Wysyłam prośbę…",
+        "follow_again": "Wysłać prośbę ponownie?",
+        "follow_unfollow": "Przestań obserwować",
+        "followees": "Obserwowani",
+        "followers": "Obserwujący",
+        "following": "Obserwowany!",
+        "follows_you": "Obserwuje cię!",
+        "hidden": "Ukryte",
+        "its_you": "To ty!",
+        "media": "Media",
+        "mention": "Wspomnienie",
+        "mute": "Wycisz",
+        "muted": "Wyciszony(-a)",
+        "per_day": "dziennie",
+        "remote_follow": "Zdalna obserwacja",
+        "report": "Raportuj",
+        "statuses": "Statusy",
+        "subscribe": "Subskrybuj",
+        "unsubscribe": "Odsubskrybuj",
+        "unblock": "Odblokuj",
+        "unblock_progress": "Odblokowuję…",
+        "block_progress": "Blokuję…",
+        "unmute": "Cofnij wyciszenie",
+        "unmute_progress": "Cofam wyciszenie…",
+        "mute_progress": "Wyciszam…",
+        "hide_repeats": "Ukryj powtórzenia",
+        "show_repeats": "Pokaż powtórzenia",
+        "admin_menu": {
+            "moderation": "Moderacja",
+            "grant_admin": "Przyznaj admina",
+            "revoke_admin": "Odwołaj admina",
+            "grant_moderator": "Przyznaj moderatora",
+            "revoke_moderator": "Odwołaj moderatora",
+            "activate_account": "Aktywuj konto",
+            "deactivate_account": "Dezaktywuj konto",
+            "delete_account": "Usuń konto",
+            "force_nsfw": "Oznacz wszystkie posty jako NSFW",
+            "strip_media": "Usuń multimedia z postów",
+            "force_unlisted": "Wymuś posty na niepubliczne",
+            "sandbox": "Wymuś by posty były tylko dla obserwujących",
+            "disable_remote_subscription": "Zakaż obserwowania użytkownika ze zdalnych instancji",
+            "disable_any_subscription": "Zakaż całkowicie obserwowania użytkownika",
+            "quarantine": "Zakaż federowania postów od tego użytkownika",
+            "delete_user": "Usuń użytkownika",
+            "delete_user_confirmation": "Czy jesteś absolutnie pewny? Ta operacja nie może być cofnięta."
+        }
+    },
+    "user_profile": {
+        "timeline_title": "Oś czasu użytkownika",
+        "profile_does_not_exist": "Przepraszamy, ten profil nie istnieje.",
+        "profile_loading_error": "Przepraszamy, wystąpił błąd podczas ładowania tego profilu."
+    },
+    "user_reporting": {
+        "title": "Raportowanie {0}",
+        "add_comment_description": "Raport zostanie wysłany do moderatorów instancji. Możesz dodać powód dlaczego raportujesz to konto poniżej:",
+        "additional_comments": "Dodatkowe komentarze",
+        "forward_description": "To konto jest z innego serwera. Wysłać również tam kopię raportu?",
+        "forward_to": "Przekaż do{0}",
+        "submit": "Wyślij",
+        "generic_error": "Wystąpił błąd podczas przetwarzania twojej prośby."
+    },
+    "who_to_follow": {
+        "more": "Więcej",
+        "who_to_follow": "Propozycje obserwacji"
+    },
+    "tool_tip": {
+        "media_upload": "Wyślij media",
+        "repeat": "Powtórz",
+        "reply": "Odpowiedz",
+        "favorite": "Dodaj do ulubionych",
+        "add_reaction": "Dodaj reakcję",
+        "user_settings": "Ustawienia użytkownika",
+        "accept_follow_request": "Akceptuj prośbę o możliwość obserwacji",
+        "reject_follow_request": "Odrzuć prośbę o możliwość obserwacji"
+    },
+    "upload": {
+        "error": {
+            "base": "Wysyłanie nie powiodło się.",
+            "file_too_big": "Zbyt duży plik [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+            "default": "Spróbuj ponownie później"
+        },
+        "file_size_units": {
+            "B": "B",
+            "KiB": "KiB",
+            "MiB": "MiB",
+            "GiB": "GiB",
+            "TiB": "TiB"
+        }
+    },
+    "search": {
+        "people": "Ludzie",
+        "hashtags": "Hasztagi",
+        "person_talking": "{count} osoba rozmawia o tym",
+        "people_talking": "{count} osób rozmawia o tym",
+        "no_results": "Brak wyników"
+    },
+    "password_reset": {
+        "forgot_password": "Zapomniałeś hasła?",
+        "password_reset": "Reset hasła",
+        "instruction": "Wprowadź swój adres email lub nazwę użytkownika. Wyślemy ci link z którym możesz zresetować hasło.",
+        "placeholder": "Twój email lub nazwa użytkownika",
+        "check_email": "Sprawdź pocztę, aby uzyskać link do zresetowania hasła.",
+        "return_home": "Wróć do strony głównej",
+        "not_found": "Nie mogliśmy znaleźć tego emaila lub nazwy użytkownika.",
+        "too_many_requests": "Przekroczyłeś limit prób, spróbuj ponownie później.",
+        "password_reset_disabled": "Resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji.",
+        "password_reset_required": "Musisz zresetować hasło, by się zalogować.",
+        "password_reset_required_but_mailer_is_disabled": "Musisz zresetować hasło, ale resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji."
     }
-  },
-  "search": {
-    "people": "Ludzie",
-    "hashtags": "Hasztagi",
-    "person_talking": "{count} osoba rozmawia o tym",
-    "people_talking": "{count} osób rozmawia o tym",
-    "no_results": "Brak wyników"
-  },
-  "password_reset": {
-    "forgot_password": "Zapomniałeś hasła?",
-    "password_reset": "Reset hasła",
-    "instruction": "Wprowadź swój adres email lub nazwę użytkownika. Wyślemy ci link z którym możesz zresetować hasło.",
-    "placeholder": "Twój email lub nazwa użytkownika",
-    "check_email": "Sprawdź pocztę, aby uzyskać link do zresetowania hasła.",
-    "return_home": "Wróć do strony głównej",
-    "not_found": "Nie mogliśmy znaleźć tego emaila lub nazwy użytkownika.",
-    "too_many_requests": "Przekroczyłeś limit prób, spróbuj ponownie później.",
-    "password_reset_disabled": "Resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji.",
-    "password_reset_required": "Musisz zresetować hasło, by się zalogować.",
-    "password_reset_required_but_mailer_is_disabled": "Musisz zresetować hasło, ale resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji."
-  }
 }

From c2bba3f5addf4c6b851bd2d2e7e53d7e897c0115 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 14:28:35 -0500
Subject: [PATCH 339/483] Remove unused noAttachmentLinks option

---
 src/boot/after_store.js | 1 -
 src/modules/instance.js | 1 -
 static/config.json      | 1 -
 3 files changed, 3 deletions(-)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 34f6d6e7..abdba305 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -108,7 +108,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
   copyInstanceOption('subjectLineBehavior')
   copyInstanceOption('postContentType')
   copyInstanceOption('alwaysShowSubjectInput')
-  copyInstanceOption('noAttachmentLinks')
   copyInstanceOption('showFeaturesPanel')
   copyInstanceOption('hideSitename')
 
diff --git a/src/modules/instance.js b/src/modules/instance.js
index ffece311..0f430e81 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -32,7 +32,6 @@ const defaultState = {
   hideSitename: false,
   nsfwCensorImage: undefined,
   vapidPublicKey: undefined,
-  noAttachmentLinks: false,
   showFeaturesPanel: true,
   minimalScopesMode: false,
   greentext: false,
diff --git a/static/config.json b/static/config.json
index c8267869..0ce85f99 100644
--- a/static/config.json
+++ b/static/config.json
@@ -16,7 +16,6 @@
   "hideUserStats": false,
   "loginMethod": "password",
   "webPushNotifications": false,
-  "noAttachmentLinks": false,
   "nsfwCensorImage": "",
   "showFeaturesPanel": true,
   "minimalScopesMode": false

From eea6d772ad53665ea926d714ed2df6059dec197d Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 14:31:00 -0500
Subject: [PATCH 340/483] Also remove from docs

---
 docs/CONFIGURATION.md | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 0a9bbd7a..f9de664d 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -68,9 +68,6 @@ Hide counters for posts and users respectively, i.e. hiding repeats/favorites co
 ### `webPushNotifications`
 Enables [PushAPI](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) - based notifications for users. Instance-default.
 
-### `noAttachmentLinks`
-**TODO Currently doesn't seem to be doing anything code-wise**, but implication is to disable adding links for attachments, which looks nicer but breaks compatibility with old GNU/Social servers.
-
 ### `nsfwCensorImage`
 Use custom image for NSFW'd images
 

From 98d332793ccb8b73a8b5f6ec8893d0459412b242 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 14:40:46 -0500
Subject: [PATCH 341/483] alpha sort

---
 src/modules/instance.js | 47 ++++++++++++++++++++---------------------
 1 file changed, 23 insertions(+), 24 deletions(-)

diff --git a/src/modules/instance.js b/src/modules/instance.js
index ffece311..eeee115c 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -5,37 +5,36 @@ import { instanceDefaultProperties } from './config.js'
 
 const defaultState = {
   // Stuff from static/config.json and apiConfig
+  alwaysShowSubjectInput: true,
+  background: '/static/aurora_borealis.jpg',
+  collapseMessageWithSubject: false,
+  disableChat: false,
+  greentext: false,
+  hideFilteredStatuses: false,
+  hideMutedPosts: false,
+  hidePostStats: false,
+  hideSitename: false,
+  hideUserStats: false,
+  logo: '/static/logo.png',
+  logoMargin: '.2em',
+  logoMask: true,
+  minimalScopesMode: false,
   name: 'Pleroma FE',
+  nsfwCensorImage: undefined,
+  postContentType: 'text/plain',
+  redirectRootLogin: '/main/friends',
+  redirectRootNoLogin: '/main/all',
   registrationOpen: true,
   safeDM: true,
-  textlimit: 5000,
+  scopeCopy: true,
   server: 'http://localhost:4040/',
+  showFeaturesPanel: true,
+  showInstanceSpecificPanel: false,
+  subjectLineBehavior: 'email',
+  textlimit: 5000,
   theme: 'pleroma-dark',
   themeData: undefined,
-  background: '/static/aurora_borealis.jpg',
-  logo: '/static/logo.png',
-  logoMask: true,
-  logoMargin: '.2em',
-  redirectRootNoLogin: '/main/all',
-  redirectRootLogin: '/main/friends',
-  showInstanceSpecificPanel: false,
-  alwaysShowSubjectInput: true,
-  hideMutedPosts: false,
-  collapseMessageWithSubject: false,
-  hidePostStats: false,
-  hideUserStats: false,
-  hideFilteredStatuses: false,
-  disableChat: false,
-  scopeCopy: true,
-  subjectLineBehavior: 'email',
-  postContentType: 'text/plain',
-  hideSitename: false,
-  nsfwCensorImage: undefined,
   vapidPublicKey: undefined,
-  noAttachmentLinks: false,
-  showFeaturesPanel: true,
-  minimalScopesMode: false,
-  greentext: false,
 
   // Nasty stuff
   pleromaBackend: true,

From a4a25105babae42b04d173b44dcb8a2d797b87fc Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 14:43:36 -0500
Subject: [PATCH 342/483] Separate the user configurable section

---
 src/modules/instance.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/modules/instance.js b/src/modules/instance.js
index eeee115c..1b04032b 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -4,6 +4,9 @@ import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
 import { instanceDefaultProperties } from './config.js'
 
 const defaultState = {
+  // not user configurable
+  name: 'Pleroma FE',
+
   // Stuff from static/config.json and apiConfig
   alwaysShowSubjectInput: true,
   background: '/static/aurora_borealis.jpg',
@@ -19,7 +22,6 @@ const defaultState = {
   logoMargin: '.2em',
   logoMask: true,
   minimalScopesMode: false,
-  name: 'Pleroma FE',
   nsfwCensorImage: undefined,
   postContentType: 'text/plain',
   redirectRootLogin: '/main/friends',

From 62e0fda5978c8cf592fb0cdf28e485e87378f467 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 14:46:31 -0500
Subject: [PATCH 343/483] loginMethod was missing

---
 src/modules/instance.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/modules/instance.js b/src/modules/instance.js
index 1b04032b..8be8ba63 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -18,6 +18,7 @@ const defaultState = {
   hidePostStats: false,
   hideSitename: false,
   hideUserStats: false,
+  loginMethod: 'password',
   logo: '/static/logo.png',
   logoMargin: '.2em',
   logoMask: true,

From 1db2fc3f41207c2af363c2017efb633a60e9a042 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 14:52:21 -0500
Subject: [PATCH 344/483] alpha sort

---
 static/config.json | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/static/config.json b/static/config.json
index c8267869..9aeed8f1 100644
--- a/static/config.json
+++ b/static/config.json
@@ -1,23 +1,23 @@
 {
-  "theme": "pleroma-dark",
-  "background": "/static/aurora_borealis.jpg",
-  "logo": "/static/logo.png",
-  "logoMask": true,
-  "logoMargin": ".1em",
-  "redirectRootNoLogin": "/main/all",
-  "redirectRootLogin": "/main/friends",
-  "showInstanceSpecificPanel": false,
-  "collapseMessageWithSubject": false,
-  "scopeCopy": true,
-  "subjectLineBehavior": "email",
-  "postContentType": "text/plain",
   "alwaysShowSubjectInput": true,
+  "background": "/static/aurora_borealis.jpg",
+  "collapseMessageWithSubject": false,
   "hidePostStats": false,
   "hideUserStats": false,
   "loginMethod": "password",
-  "webPushNotifications": false,
+  "logo": "/static/logo.png",
+  "logoMargin": ".1em",
+  "logoMask": true,
+  "minimalScopesMode": false,
   "noAttachmentLinks": false,
   "nsfwCensorImage": "",
+  "postContentType": "text/plain",
+  "redirectRootLogin": "/main/friends",
+  "redirectRootNoLogin": "/main/all",
+  "scopeCopy": true,
   "showFeaturesPanel": true,
-  "minimalScopesMode": false
+  "showInstanceSpecificPanel": false,
+  "subjectLineBehavior": "email",
+  "theme": "pleroma-dark",
+  "webPushNotifications": false
 }

From 0ef5965b3bfb6e9f8a305714f60a54b43066473a Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 14:52:48 -0500
Subject: [PATCH 345/483] Add missing settings: disableChat, greentext,
 hideFilteredStatuses, hideMutedPosts, hidePostStats, hideSitename

---
 static/config.json | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/static/config.json b/static/config.json
index 9aeed8f1..727dde73 100644
--- a/static/config.json
+++ b/static/config.json
@@ -2,7 +2,12 @@
   "alwaysShowSubjectInput": true,
   "background": "/static/aurora_borealis.jpg",
   "collapseMessageWithSubject": false,
+  "disableChat": false,
+  "greentext": false,
+  "hideFilteredStatuses": false,
+  "hideMutedPosts": false,
   "hidePostStats": false,
+  "hideSitename": false,
   "hideUserStats": false,
   "loginMethod": "password",
   "logo": "/static/logo.png",

From fc12f44f7a13581ec08d6440f1cebbb230cfc32f Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 13 May 2020 18:41:08 +0000
Subject: [PATCH 346/483] Translated using Weblate (Finnish)

Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fi/
---
 src/i18n/fi.json | 192 ++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 175 insertions(+), 17 deletions(-)

diff --git a/src/i18n/fi.json b/src/i18n/fi.json
index 386578fb..7248c049 100644
--- a/src/i18n/fi.json
+++ b/src/i18n/fi.json
@@ -289,7 +289,15 @@
             "warning_of_generate_new_codes": "Luodessasi uudet palautuskoodit, vanhat koodisi lakkaavat toimimasta.",
             "recovery_codes": "Palautuskoodit.",
             "waiting_a_recovery_codes": "Odotetaan palautuskoodeja...",
-            "recovery_codes_warning": "Kirjoita koodit ylös tai tallenna ne turvallisesti, muuten et näe niitä uudestaan. Jos et voi käyttää monivaihetodennusta ja sinulla ei ole palautuskoodeja, et voi enää kirjautua sisään tilillesi."
+            "recovery_codes_warning": "Kirjoita koodit ylös tai tallenna ne turvallisesti, muuten et näe niitä uudestaan. Jos et voi käyttää monivaihetodennusta ja sinulla ei ole palautuskoodeja, et voi enää kirjautua sisään tilillesi.",
+            "scan": {
+                "title": "Skannaa",
+                "secret_code": "Avain",
+                "desc": "Käytä monivaihetodennus-sovellusta skannakksesi tämän QR-kooding, tai syötä avain:"
+            },
+            "verify": {
+                "desc": "Kytkeäksesi päälle monivaihetodennuksen, syötä koodi monivaihetodennussovellksesta:"
+            }
         },
         "allow_following_move": "Salli automaattinen seuraaminen kun käyttäjä siirtää tilinsä",
         "block_export": "Estojen vienti",
@@ -375,7 +383,12 @@
                 "poll": "Äänestyksen kuvaaja",
                 "icons": "Ikonit",
                 "highlight": "Korostetut elementit",
-                "pressed": "Painettu"
+                "pressed": "Painettu",
+                "selectedMenu": "Valikon valinta",
+                "disabled": "Pois käytöstä",
+                "toggled": "Kytketty",
+                "tabs": "Välilehdet",
+                "popover": "Työkaluvinkit, valikot, ponnahdusviestit"
             },
             "common": {
                 "color": "Väri",
@@ -398,6 +411,67 @@
                 "main": "Yleiset värit",
                 "foreground_hint": "Löydät \"Edistynyt\"-välilehdeltä tarkemmat asetukset",
                 "rgbo": "Ikonit, korostukset, merkit"
+            },
+            "shadows": {
+                "filter_hint": {
+                    "always_drop_shadow": "Varoitus, tämä varjo käyttää aina {0} kun selain tukee sitä.",
+                    "avatar_inset": "Huom. sisennettyjen ja ei-sisennettyjen varjojen yhdistelmät saattavat luoda ei-odotettuja lopputuloksia läpinäkyvillä profiilikuvilla.",
+                    "drop_shadow_syntax": "{0} ei tue {1} parametria ja {2} avainsanaa.",
+                    "spread_zero": "Varjot joiden levitys > 0 näyttävät samalta kuin se olisi nolla",
+                    "inset_classic": "Sisennetyt varjot käyttävät {0}"
+                },
+                "components": {
+                    "buttonPressedHover": "Nappi (painettu ja kohdistettu)",
+                    "panel": "Ruutu",
+                    "panelHeader": "Ruudun otsikko",
+                    "topBar": "Yläpalkki",
+                    "avatar": "Profiilikuva (profiilinäkymässä)",
+                    "avatarStatus": "Profiilikuva (viestin yhtyedessä)",
+                    "popup": "Ponnahdusviestit ja työkaluvinkit",
+                    "button": "Nappi",
+                    "buttonHover": "Nappi (kohdistus)",
+                    "buttonPressed": "Nappi (painettu)",
+                    "input": "Syöttökenttä"
+                },
+                "hintV3": "Voit käyttää {0} merkintää varjoille käyttääksesi väriä toisesta asetuksesta.",
+                "_tab_label": "Valo ja varjostus",
+                "component": "Komponentti",
+                "override": "Ylikirjoita",
+                "shadow_id": "Varjo #{value}",
+                "blur": "Sumennus",
+                "spread": "Levitys",
+                "inset": "Sisennys"
+            },
+            "fonts": {
+                "help": "Valitse fontti käyttöliittymälle. \"Oma\"-vaihtohdolle on syötettävä fontin nimi tarkalleen samana kuin se on järjestelmässäsi.",
+                "_tab_label": "Fontit",
+                "components": {
+                    "interface": "Käyttöliittymä",
+                    "input": "Syöttökentät",
+                    "post": "Viestin teksti",
+                    "postCode": "Tasavälistetty teksti viestissä"
+                },
+                "family": "Fontin nimi",
+                "size": "Koko (pikseleissä)",
+                "weight": "Painostus (paksuus)",
+                "custom": "Oma"
+            },
+            "preview": {
+                "input": "Tulin juuri saunasta.",
+                "header": "Esikatselu",
+                "content": "Sisältö",
+                "error": "Esimerkkivirhe",
+                "button": "Nappi",
+                "text": "Vähän lisää {0} ja {1}",
+                "mono": "sisältöä",
+                "faint_link": "manuaali",
+                "fine_print": "Lue meidän {0} vaikka huvin vuoksi!",
+                "header_faint": "Tämä on OK",
+                "checkbox": "Olen silmäillyt käyttöehdot",
+                "link": "kiva linkki"
+            },
+            "radii": {
+                "_tab_label": "Pyöristys"
             }
         },
         "enter_current_password_to_confirm": "Syötä nykyinen salasanasi todentaaksesi henkilöllisyytesi",
@@ -411,7 +485,12 @@
         "notification_setting_non_followers": "Käyttäjät jotka eivät seuraa sinua",
         "notification_setting_privacy": "Yksityisyys",
         "notification_mutes": "Jos et halua ilmoituksia joltain käyttäjältä, käytä mykistystä.",
-        "notification_blocks": "Estäminen pysäyttää kaikki ilmoitukset käyttäjältä ja poistaa seurauksen."
+        "notification_blocks": "Estäminen pysäyttää kaikki ilmoitukset käyttäjältä ja poistaa seurauksen.",
+        "version": {
+            "title": "Versio",
+            "backend_version": "Palvelimen versio",
+            "frontend_version": "Käyttöliittymän versio"
+        }
     },
     "time": {
         "day": "{0} päivä",
@@ -432,8 +511,8 @@
         "months": "{0} kuukautta",
         "month_short": "{0}kk",
         "months_short": "{0}kk",
-        "now": "nyt",
-        "now_short": "juuri nyt",
+        "now": "juuri nyt",
+        "now_short": "nyt",
         "second": "{0} sekunti",
         "seconds": "{0} sekuntia",
         "second_short": "{0}s",
@@ -456,7 +535,8 @@
         "repeated": "toisti",
         "show_new": "Näytä uudet",
         "up_to_date": "Ajantasalla",
-        "no_more_statuses": "Ei enempää viestejä"
+        "no_more_statuses": "Ei enempää viestejä",
+        "no_statuses": "Ei viestejä"
     },
     "status": {
         "favorites": "Tykkäykset",
@@ -468,9 +548,10 @@
         "delete_confirm": "Haluatko varmasti postaa viestin?",
         "reply_to": "Vastaus",
         "replies_list": "Vastaukset:",
-        "mute_conversation": "Hiljennä keskustelu",
-        "unmute_conversation": "Poista hiljennys",
-        "status_unavailable": "Viesti ei saatavissa"
+        "mute_conversation": "Mykistä keskustelu",
+        "unmute_conversation": "Poista mykistys",
+        "status_unavailable": "Viesti ei saatavissa",
+        "copy_link": "Kopioi linkki"
     },
     "user_card": {
         "approve": "Hyväksy",
@@ -479,7 +560,7 @@
         "deny": "Älä hyväksy",
         "follow": "Seuraa",
         "follow_sent": "Pyyntö lähetetty!",
-        "follow_progress": "Pyydetään...",
+        "follow_progress": "Pyydetään…",
         "follow_again": "Lähetä pyyntö uudestaan",
         "follow_unfollow": "Älä seuraa",
         "followees": "Seuraa",
@@ -487,14 +568,50 @@
         "following": "Seuraat!",
         "follows_you": "Seuraa sinua!",
         "its_you": "Sinun tili!",
-        "mute": "Hiljennä",
-        "muted": "Hiljennetty",
+        "mute": "Mykistä",
+        "muted": "Mykistetty",
         "per_day": "päivässä",
         "remote_follow": "Seuraa muualta",
-        "statuses": "Viestit"
+        "statuses": "Viestit",
+        "hidden": "Piilotettu",
+        "media": "Media",
+        "block_progress": "Estetään...",
+        "admin_menu": {
+            "grant_admin": "Anna Ylläpitöoikeudet",
+            "force_nsfw": "Merkitse kaikki viestit NSFW:nä",
+            "disable_any_subscription": "Estä käyttäjän seuraaminen",
+            "moderation": "Moderaatio",
+            "revoke_admin": "Poista Ylläpitöoikeudet",
+            "grant_moderator": "Anna Moderaattorioikeudet",
+            "revoke_moderator": "Poista Moderaattorioikeudet",
+            "activate_account": "Aktivoi tili",
+            "deactivate_account": "Deaktivoi tili",
+            "delete_account": "Poista tili",
+            "strip_media": "Poista media viesteistä",
+            "force_unlisted": "Pakota viestit listaamattomiksi",
+            "sandbox": "Pakota viestit vain seuraajille",
+            "disable_remote_subscription": "Estä seuraaminen ulkopuolisilta sivuilta",
+            "quarantine": "Estä käyttäjän viestin federoituminen",
+            "delete_user": "Poista käyttäjä",
+            "delete_user_confirmation": "Oletko aivan varma? Tätä ei voi kumota."
+        },
+        "favorites": "Tykkäykset",
+        "mention": "Mainitse",
+        "report": "Ilmianna",
+        "subscribe": "Tilaa",
+        "unsubscribe": "Poista tilaus",
+        "unblock": "Poista esto",
+        "unblock_progress": "Postetaan estoa...",
+        "unmute": "Poista mykistys",
+        "unmute_progress": "Poistetaan mykistystä...",
+        "mute_progress": "Mykistetään...",
+        "hide_repeats": "Piilota toistot",
+        "show_repeats": "Näytä toistot"
     },
     "user_profile": {
-        "timeline_title": "Käyttäjän aikajana"
+        "timeline_title": "Käyttäjän aikajana",
+        "profile_does_not_exist": "Tätä profiilia ei ole.",
+        "profile_loading_error": "Virhe ladatessa profiilia."
     },
     "who_to_follow": {
         "more": "Lisää",
@@ -505,7 +622,10 @@
         "repeat": "Toista",
         "reply": "Vastaa",
         "favorite": "Tykkää",
-        "user_settings": "Käyttäjäasetukset"
+        "user_settings": "Käyttäjäasetukset",
+        "add_reaction": "Lisää Reaktio",
+        "accept_follow_request": "Hyväksy seurauspyyntö",
+        "reject_follow_request": "Hylkää seurauspyyntö"
     },
     "upload": {
         "error": {
@@ -535,10 +655,19 @@
                 "reject": "Hylkää",
                 "quarantine": "Karanteeni",
                 "ftl_removal": "Poisto \"Koko Tunnettu Verkosto\" -aikajanalta",
-                "media_removal": "Media-tiedostojen poisto"
+                "media_removal": "Media-tiedostojen poisto",
+                "simple_policies": "Palvelinkohtaiset Säännöt",
+                "accept_desc": "Tämä palvelin hyväksyy viestit vain seuraavilta palvelimilta:",
+                "reject_desc": "Tämä palvelin ei hyväksy viestejä seuraavilta palvelimilta:",
+                "quarantine_desc": "Tämä palvelin lähettää vain julkisia viestejä seuraaville palvelimille:",
+                "ftl_removal_desc": "Tämä palvelin poistaa nämä palvelimet \"Koko Tunnettu Verkosto\"-aikajanalta:",
+                "media_removal_desc": "Tämä palvelin postaa mediatiedostot viesteistä seuraavilta palvelimilta:",
+                "media_nsfw": "Pakota Media Arkaluontoiseksi",
+                "media_nsfw_desc": "Tämä palvelin pakottaa mediatiedostot arkaluonteisiksi seuraavilta palvelimilta:"
             },
             "federation": "Federaatio",
-            "mrf_policies": "Aktivoidut MRF-säännöt"
+            "mrf_policies": "Aktivoidut MRF-säännöt",
+            "mrf_policies_desc": "MRF-säännöt muuttavat federaation toimintaa sivulla. Seuraavat säännöt ovat kytketty päälle:"
         },
         "staff": "Henkilökunta"
     },
@@ -585,5 +714,34 @@
     },
     "selectable_list": {
         "select_all": "Valitse kaikki"
+    },
+    "password_reset": {
+        "check_email": "Tarkista sähköpostisi salasanannollausta varten.",
+        "instruction": "Syötä sähköpostiosoite tai käyttäjänimi. Lähetämme linkin salasanan nollausta varten.",
+        "password_reset_disabled": "Salasanan nollaus ei käytössä. Ota yhteyttä sivun ylläpitäjään.",
+        "password_reset_required_but_mailer_is_disabled": "Sinun täytyy vaihtaa salasana, mutta salasanan nollaus on pois käytöstä. Ota yhteyttä sivun ylläpitäjään.",
+        "forgot_password": "Unohditko salasanan?",
+        "password_reset": "Salasanan nollaus",
+        "placeholder": "Sähköpostiosoite tai käyttäjänimi",
+        "return_home": "Palaa etusivulle",
+        "not_found": "Sähköpostiosoitetta tai käyttäjänimeä ei löytynyt.",
+        "too_many_requests": "Olet käyttänyt kaikki yritykset, yritä uudelleen myöhemmin.",
+        "password_reset_required": "Sinun täytyy vaihtaa salasana kirjautuaksesi."
+    },
+    "user_reporting": {
+        "add_comment_description": "Tämä raportti lähetetään sivun moderaattoreille. Voit antaa selityksen miksi ilmiannoit tilin:",
+        "title": "Ilmiannetaan {0}",
+        "additional_comments": "Lisäkommentit",
+        "forward_description": "Tämä tili on toiselta palvelimelta. Lähetä kopio ilmiannosta sinnekin?",
+        "forward_to": "Lähetä eteenpäin: {0}",
+        "submit": "Lähetä",
+        "generic_error": "Virhe käsitellessä pyyntöä."
+    },
+    "search": {
+        "people": "Käyttäjät",
+        "hashtags": "Aihetunnisteet",
+        "people_talking": "{0} käyttäjää puhuvat",
+        "person_talking": "{0} käyttäjä puhuu",
+        "no_results": "Ei tuloksia"
     }
 }

From 1f53f0a844eed940d40005fe64bc88d7434031d7 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Wed, 13 May 2020 18:43:07 +0000
Subject: [PATCH 347/483] Translated using Weblate (Italian)

Currently translated at 28.3% (174 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 40 ++++++++++++++++++++--------------------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index 817a6465..8e35d4f9 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -33,13 +33,13 @@
         "hide_attachments_in_tl": "Nascondi gli allegati presenti nelle sequenze",
         "name": "Nome",
         "name_bio": "Nome ed introduzione",
-        "nsfw_clickthrough": "Abilita il click per visualizzare gli allegati segnati come NSFW",
+        "nsfw_clickthrough": "Fai click per visualizzare gli allegati nascosti",
         "profile_background": "Sfondo della tua pagina",
-        "profile_banner": "Banner del tuo profilo",
-        "reply_link_preview": "Abilita il link per la risposta al passaggio del mouse",
-        "set_new_avatar": "Scegli un nuovo avatar",
+        "profile_banner": "Stendardo del tuo profilo",
+        "reply_link_preview": "Visualizza le risposte al passaggio del cursore",
+        "set_new_avatar": "Scegli una nuova icona",
         "set_new_profile_background": "Scegli un nuovo sfondo per la tua pagina",
-        "set_new_profile_banner": "Scegli un nuovo banner per il tuo profilo",
+        "set_new_profile_banner": "Scegli un nuovo stendardo per il tuo profilo",
         "settings": "Impostazioni",
         "theme": "Tema",
         "user_settings": "Impostazioni Utente",
@@ -92,48 +92,48 @@
         "notification_visibility_likes": "Preferiti",
         "notification_visibility_mentions": "Menzioni",
         "notification_visibility_repeats": "Condivisioni",
-        "no_rich_text_description": "Togli l'impaginazione del testo da tutti i messaggi",
+        "no_rich_text_description": "Togli la formattazione del testo da tutti i messaggi",
         "oauth_tokens": "Token OAuth",
         "token": "Token",
         "refresh_token": "Aggiorna token",
         "valid_until": "Valido fino a",
-        "revoke_token": "Revocare",
+        "revoke_token": "Revoca",
         "panelRadius": "Pannelli",
-        "pause_on_unfocused": "Metti in pausa l'aggiornamento continuo quando la scheda non è in primo piano",
+        "pause_on_unfocused": "Interrompi l'aggiornamento continuo mentre la scheda è in secondo piano",
         "presets": "Valori predefiniti",
         "profile_tab": "Profilo",
-        "radii_help": "Imposta l'arrotondamento dei bordi (in pixel)",
-        "replies_in_timeline": "Risposte nella sequenza temporale",
+        "radii_help": "Imposta il raggio dei bordi (in pixel)",
+        "replies_in_timeline": "Risposte nella sequenza personale",
         "reply_visibility_all": "Mostra tutte le risposte",
-        "reply_visibility_following": "Mostra solo le risposte dirette a me o agli utenti che seguo",
-        "reply_visibility_self": "Mostra solo risposte dirette a me",
+        "reply_visibility_following": "Mostra solo le risposte rivolte a me o agli utenti che seguo",
+        "reply_visibility_self": "Mostra solo risposte rivolte a me",
         "saving_err": "Errore nel salvataggio delle impostazioni",
         "saving_ok": "Impostazioni salvate",
         "security_tab": "Sicurezza",
-        "stop_gifs": "Riproduci GIF al passaggio del cursore del mouse",
-        "streaming": "Abilita aggiornamento automatico dei nuovi post quando si è in alto alla pagina",
+        "stop_gifs": "Riproduci GIF al passaggio del cursore",
+        "streaming": "Mostra automaticamente i nuovi messaggi quando sei in cima alla pagina",
         "text": "Testo",
         "theme_help": "Usa codici colore esadecimali (#rrggbb) per personalizzare il tuo schema di colori.",
         "tooltipRadius": "Descrizioni/avvisi",
         "values": {
             "false": "no",
-            "true": "si"
+            "true": "sì"
         }
     },
     "timeline": {
-        "error_fetching": "Errore nel prelievo aggiornamenti",
+        "error_fetching": "Errore nell'aggiornamento",
         "load_older": "Carica messaggi più vecchi",
         "show_new": "Mostra nuovi",
         "up_to_date": "Aggiornato",
         "collapse": "Riduci",
         "conversation": "Conversazione",
-        "no_retweet_hint": "La visibilità del post è impostata solo per chi ti segue o messaggio diretto e non può essere condiviso",
+        "no_retweet_hint": "Il messaggio è diretto o solo per seguaci e non può essere condiviso",
         "repeated": "condiviso"
     },
     "user_card": {
         "follow": "Segui",
         "followees": "Chi stai seguendo",
-        "followers": "Chi ti segue",
+        "followers": "Seguaci",
         "following": "Lo stai seguendo!",
         "follows_you": "Ti segue!",
         "mute": "Silenzia",
@@ -197,10 +197,10 @@
         "token": "Codice d'invito"
     },
     "user_profile": {
-        "timeline_title": "Sequenza Temporale dell'Utente"
+        "timeline_title": "Sequenza dell'Utente"
     },
     "who_to_follow": {
-        "more": "Più",
+        "more": "Altro",
         "who_to_follow": "Chi seguire"
     },
     "about": {

From 346f3a285abe2b0f548b9eef7b393305a9b5f63b Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Wed, 13 May 2020 18:43:12 +0000
Subject: [PATCH 348/483] Translated using Weblate (Russian)

Currently translated at 61.0% (374 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
---
 src/i18n/ru.json | 34 +++++++++++++++++++++++-----------
 1 file changed, 23 insertions(+), 11 deletions(-)

diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 790f2f3c..3c52416c 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -42,7 +42,8 @@
         "public_tl": "Публичная лента",
         "timeline": "Лента",
         "twkn": "Федеративная лента",
-        "search": "Поиск"
+        "search": "Поиск",
+        "friend_requests": "Запросы на чтение"
     },
     "notifications": {
         "broken_favorite": "Неизвестный статус, ищем...",
@@ -51,7 +52,8 @@
         "load_older": "Загрузить старые уведомления",
         "notifications": "Уведомления",
         "read": "Прочесть",
-        "repeated_you": "повторил(а) ваш статус"
+        "repeated_you": "повторил(а) ваш статус",
+        "follow_request": "хочет читать вас"
     },
     "interactions": {
         "favs_repeats": "Повторы и фавориты",
@@ -59,7 +61,7 @@
         "load_older": "Загрузить старые взаимодействия"
     },
     "post_status": {
-        "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков.",
+        "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может начать читать вас чтобы видеть посты только для подписчиков.",
         "account_not_locked_warning_link": "залочен",
         "attachments_sensitive": "Вложения содержат чувствительный контент",
         "content_warning": "Тема (не обязательно)",
@@ -207,7 +209,7 @@
         "replies_in_timeline": "Ответы в ленте",
         "reply_link_preview": "Включить предварительный просмотр ответа при наведении мыши",
         "reply_visibility_all": "Показывать все ответы",
-        "reply_visibility_following": "Показывать только ответы мне и тех на кого я подписан",
+        "reply_visibility_following": "Показывать только ответы мне или тех на кого я подписан",
         "reply_visibility_self": "Показывать только ответы мне",
         "autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)",
         "saving_err": "Не удалось сохранить настройки",
@@ -343,7 +345,13 @@
                 "checkbox": "Я подтверждаю что не было ни единого разрыва",
                 "link": "ссылка"
             }
-        }
+        },
+        "notification_setting_non_followers": "Не читающие вас",
+        "allow_following_move": "Разрешить автоматически читать новый аккаунт при перемещении на другой сервер",
+        "hide_user_stats": "Не показывать статистику пользователей (например количество читателей)",
+        "notification_setting_followers": "Читающие вас",
+        "notification_setting_follows": "Читаемые вами",
+        "notification_setting_non_follows": "Не читаемые вами"
     },
     "timeline": {
         "collapse": "Свернуть",
@@ -362,12 +370,12 @@
         "follow": "Читать",
         "follow_sent": "Запрос отправлен!",
         "follow_progress": "Запрашиваем…",
-        "follow_again": "Запросить еще заново?",
+        "follow_again": "Запросить еще раз?",
         "follow_unfollow": "Перестать читать",
         "followees": "Читаемые",
         "followers": "Читатели",
-        "following": "Читаю",
-        "follows_you": "Читает вас",
+        "following": "Читаю!",
+        "follows_you": "Читает вас!",
         "mute": "Игнорировать",
         "muted": "Игнорирую",
         "per_day": "в день",
@@ -385,9 +393,9 @@
             "force_nsfw": "Отмечать посты пользователя как NSFW",
             "strip_media": "Убирать вложения из постов пользователя",
             "force_unlisted": "Не добавлять посты в публичные ленты",
-            "sandbox": "Посты доступны только для подписчиков",
-            "disable_remote_subscription": "Запретить подписываться с удаленных серверов",
-            "disable_any_subscription": "Запретить подписываться на пользователя",
+            "sandbox": "Принудить видимость постов только читателям",
+            "disable_remote_subscription": "Запретить читать с удаленных серверов",
+            "disable_any_subscription": "Запретить читать пользователя",
             "quarantine": "Не федерировать посты пользователя",
             "delete_user": "Удалить пользователя",
             "delete_user_confirmation": "Вы уверены? Это действие нельзя отменить."
@@ -460,5 +468,9 @@
         "text_limit": "Лимит символов",
         "title": "Особенности",
         "gopher": "Gopher"
+    },
+    "tool_tip": {
+        "accept_follow_request": "Принять запрос на чтение",
+        "reject_follow_request": "Отклонить запрос на чтение"
     }
 }

From cad40133d2919d012c30ba728bfd02b3491d478f Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 15:29:38 -0500
Subject: [PATCH 349/483] Alpha sort docs, add missing options

---
 docs/CONFIGURATION.md | 78 +++++++++++++++++++++++++------------------
 1 file changed, 46 insertions(+), 32 deletions(-)

diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 0a9bbd7a..d6a66d0b 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -19,32 +19,66 @@ There's currently no mechanism for user-settings synchronization across several
 
 ## Options
 
-### `theme`
-Default theme used for new users. De-facto instance-default, user can change theme.
+### `alwaysShowSubjectInput`
+`true` - will always show subject line input, `false` - only show when it's not empty (i.e. replying). To hide subject line input completely, set it to `false` and `subjectLineBehavior` to `"noop"`
 
 ### `background`
 Default image background. Be aware of using too big images as they may take longer to load. Currently image is fitted with `background-size: cover` which means "scaled and cropped", currently left-aligned. De-facto instance default, user can choose their own background, if they remove their own background, instance default will be used instead.
 
+### `collapseMessageWithSubject`
+Collapse post content when post has a subject line (content warning). Instance-default.
+
+### `disableChat`
+hides the chat (TODO: even if it's enabled on backend)
+
+### `greentext`
+Changes lines prefixed with the `>` character to have a green text color
+
+### `hideFilteredStatuses`
+Removes filtered statuses from timelines.
+
+### `hideMutedPosts`
+Removes muted statuses from timelines.
+
+### `hidePostStats`
+Hide repeats/favorites counters for posts.
+
+### `hideSitename`
+Hide instance name in header.
+
+### `hideUserStats`
+Hide followers/friends counters for users.
+
+### `loginMethod`
+`"password"` - show simple password field
+`"token"` - show button to log in with external method (will redirect to login form, more details in BE documentation)
+
 ### `logo`, `logoMask`, `logoMargin`
 Instance `logo`, could be any image, including svg. By default it assumes logo used will be monochrome-with-alpha one, this is done to be compatible with both light and dark themes, so that white logo designed with dark theme in mind won't be invisible over light theme, this is done via [CSS3 Masking](https://www.html5rocks.com/en/tutorials/masking/adobe/). Basically - it will take alpha channel of the image and fill non-transparent areas of it with solid color. If you really want colorful logo - it can be done by setting `logoMask` to `false`.
 
 `logoMargin` allows you to adjust vertical margins between logo boundary and navbar borders. The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout.
 
+### `minimalScopesMode`
+Limit scope selection to *Direct*, *User default* and *Scope of post replying to*. This also makes it impossible to reply to a DM with a non-DM post from PleromaFE.
+
+### `nsfwCensorImage`
+Use custom image for NSFW'd images
+
+### `postContentType`
+Default post formatting option (markdown/bbcode/plaintext/etc...)
+
 ### `redirectRootNoLogin`, `redirectRootLogin`
 These two settings should point to where FE should redirect visitor when they login/open up website root
 
-### `chatDisabled`
-hides the chat (TODO: even if it's enabled on backend)
+### `scopeCopy`
+Copy post scope (visibility) when replying to a post. Instance-default.
+
+### `showFeaturesPanel`
+Show panel showcasing instance features/settings to logged-out visitors
 
 ### `showInstanceSpecificPanel`
 This allows you to include arbitrary HTML content in a panel below navigation menu. PleromaFE looks for an html page `instance/panel.html`, by default it's not provided in FE, but BE bundles some [default one](https://git.pleroma.social/pleroma/pleroma/blob/develop/priv/static/instance/panel.html). De-facto instance-defaults, since user can hide instance-specific panel.
 
-### `collapseMessageWithSubject`
-Collapse post content when post has a subject line (content warning). Instance-default.
-
-### `scopeCopy`
-Copy post scope (visibility) when replying to a post. Instance-default.
-
 ### `subjectLineBehavior`
 How to handle subject line (CW) when replying to a post.
 * `"email"` - like EMail - prepend `re: ` to subject line if it doesn't already start with it.
@@ -52,33 +86,13 @@ How to handle subject line (CW) when replying to a post.
 * `"noop"` - do not copy
 Instance-default.
 
-### `postContentType`
-Default post formatting option (markdown/bbcode/plaintext/etc...)
-
-### `alwaysShowSubjectInput`
-`true` - will always show subject line input, `false` - only show when it's not empty (i.e. replying). To hide subject line input completely, set it to `false` and `subjectLineBehavior` to `"noop"`
-
-### `hidePostStats` and `hideUserStats`
-Hide counters for posts and users respectively, i.e. hiding repeats/favorites counts for posts, hiding followers/friends counts for users. This is just cosmetic and aimed to ease pressure and bias imposed by stat numbers of people and/or posts. (as an example: so that people care less about how many followers someone has since they can't see that info)
-
-### `loginMethod`
-`"password"` - show simple password field
-`"token"` - show button to log in with external method (will redirect to login form, more details in BE documentation)
+### `theme`
+Default theme used for new users. De-facto instance-default, user can change theme.
 
 ### `webPushNotifications`
 Enables [PushAPI](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) - based notifications for users. Instance-default.
 
-### `noAttachmentLinks`
-**TODO Currently doesn't seem to be doing anything code-wise**, but implication is to disable adding links for attachments, which looks nicer but breaks compatibility with old GNU/Social servers.
 
-### `nsfwCensorImage`
-Use custom image for NSFW'd images
-
-### `showFeaturesPanel`
-Show panel showcasing instance features/settings to logged-out visitors
-
-### `hideSitename`
-Hide instance name in header
 
 ## Indirect configuration
 Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it.

From 55fe6e4703fd8587744ee0ef5a8e3b4213f08432 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 15:30:54 -0500
Subject: [PATCH 350/483] Alpha sort indirect section

---
 docs/CONFIGURATION.md | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index d6a66d0b..003efc7a 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -100,12 +100,12 @@ Some features are configured depending on how backend is configured. In general
 ### Chat
 **TODO somewhat broken, see: chatDisabled** chat can be disabled by disabling it in backend
 
+### Private Mode
+If the `private` instance setting is enabled in the backend, features that are not accessible without authentication, such as the timelines and search will be disabled for unauthenticated users.
+
 ### Rich text formatting in post formatting
 Rich text formatting options are displayed depending on how many formatting options are enabled on backend, if you don't want your users to use rich text at all you can only allow "text/plain" one, frontend then will only display post text format as a label instead of dropdown (just so that users know for example if you only allow Markdown, only BBCode or only Plain text)
 
-### Who to follow
-This is a panel intended for users to find people to follow based on randomness or on post contents. Being potentially privacy unfriendly feature it needs to be enabled and configured in backend to be enabled.
-
 ### Safe DM message display
 
 Setting this will change the warning text that is displayed for direct messages.
@@ -114,5 +114,6 @@ ATTENTION: If you actually want the behavior to change. You will need to set the
 
 DO NOT activate this without checking the backend configuration first!
 
-### Private Mode
-If the `private` instance setting is enabled in the backend, features that are not accessible without authentication, such as the timelines and search will be disabled for unauthenticated users.
+### Who to follow
+This is a panel intended for users to find people to follow based on randomness or on post contents. Being potentially privacy unfriendly feature it needs to be enabled and configured in backend to be enabled.
+

From 0ebf1bebb4aac0800a57f9d4be64dda0a2a07e61 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 15:32:02 -0500
Subject: [PATCH 351/483] SafeDM is enabled by default and won't take effect if
 BE not enabled anyway.

---
 docs/CONFIGURATION.md | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 003efc7a..7100d260 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -106,14 +106,6 @@ If the `private` instance setting is enabled in the backend, features that are n
 ### Rich text formatting in post formatting
 Rich text formatting options are displayed depending on how many formatting options are enabled on backend, if you don't want your users to use rich text at all you can only allow "text/plain" one, frontend then will only display post text format as a label instead of dropdown (just so that users know for example if you only allow Markdown, only BBCode or only Plain text)
 
-### Safe DM message display
-
-Setting this will change the warning text that is displayed for direct messages.
-
-ATTENTION: If you actually want the behavior to change. You will need to set the appropriate option at the backend. See the backend documentation for information about that.
-
-DO NOT activate this without checking the backend configuration first!
-
 ### Who to follow
 This is a panel intended for users to find people to follow based on randomness or on post contents. Being potentially privacy unfriendly feature it needs to be enabled and configured in backend to be enabled.
 

From d91f5189ef6bf540e9ba34a5650be52afb746235 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 15:33:01 -0500
Subject: [PATCH 352/483] Config setting is actually called disableChat

---
 docs/CONFIGURATION.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 7100d260..39a2840d 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -98,7 +98,7 @@ Enables [PushAPI](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) - b
 Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it.
 
 ### Chat
-**TODO somewhat broken, see: chatDisabled** chat can be disabled by disabling it in backend
+**TODO somewhat broken, see: disableChat** chat can be disabled by disabling it in backend
 
 ### Private Mode
 If the `private` instance setting is enabled in the backend, features that are not accessible without authentication, such as the timelines and search will be disabled for unauthenticated users.

From e80fa3ff6d620dfe09ca373e7761b6c2f1775a12 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 15:40:13 -0500
Subject: [PATCH 353/483] Split apiConfig options from static/config.json
 options; Move safeDM to nasty section, alpha sort things

---
 src/modules/instance.js | 26 ++++++++++++++------------
 1 file changed, 14 insertions(+), 12 deletions(-)

diff --git a/src/modules/instance.js b/src/modules/instance.js
index 8be8ba63..a8029dc1 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -4,10 +4,16 @@ import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
 import { instanceDefaultProperties } from './config.js'
 
 const defaultState = {
-  // not user configurable
+  // not configurable
   name: 'Pleroma FE',
 
-  // Stuff from static/config.json and apiConfig
+  // Stuff from apiConfig
+  server: 'http://localhost:4040/',
+  textlimit: 5000,
+  themeData: undefined,
+  vapidPublicKey: undefined,
+
+  // Stuff from static/config.json
   alwaysShowSubjectInput: true,
   background: '/static/aurora_borealis.jpg',
   collapseMessageWithSubject: false,
@@ -28,29 +34,25 @@ const defaultState = {
   redirectRootLogin: '/main/friends',
   redirectRootNoLogin: '/main/all',
   registrationOpen: true,
-  safeDM: true,
   scopeCopy: true,
-  server: 'http://localhost:4040/',
   showFeaturesPanel: true,
   showInstanceSpecificPanel: false,
   subjectLineBehavior: 'email',
-  textlimit: 5000,
   theme: 'pleroma-dark',
-  themeData: undefined,
-  vapidPublicKey: undefined,
 
   // Nasty stuff
-  pleromaBackend: true,
-  emoji: [],
-  emojiFetched: false,
   customEmoji: [],
   customEmojiFetched: false,
-  restrictedNicknames: [],
+  emoji: [],
+  emojiFetched: false,
+  pleromaBackend: true,
   postFormats: [],
+  restrictedNicknames: [],
+  safeDM: true,
 
   // Feature-set, apparently, not everything here is reported...
-  mediaProxyAvailable: false,
   chatAvailable: false,
+  mediaProxyAvailable: false,
   gopherAvailable: false,
   suggestionsEnabled: false,
   suggestionsWeb: '',

From 03318b64aacbbb48d15c94ada5ade4b38752a734 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 15:48:21 -0500
Subject: [PATCH 354/483] Really alpha sort this

---
 src/modules/instance.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/instance.js b/src/modules/instance.js
index a8029dc1..278a93ab 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -52,8 +52,8 @@ const defaultState = {
 
   // Feature-set, apparently, not everything here is reported...
   chatAvailable: false,
-  mediaProxyAvailable: false,
   gopherAvailable: false,
+  mediaProxyAvailable: false,
   suggestionsEnabled: false,
   suggestionsWeb: '',
 

From 79c53b849ed8d5abac978ed675488bf53ea8fb06 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 16:46:58 -0500
Subject: [PATCH 355/483] registrationOpen is not an FE setting

---
 src/modules/instance.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/instance.js b/src/modules/instance.js
index 278a93ab..beea424b 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -8,6 +8,7 @@ const defaultState = {
   name: 'Pleroma FE',
 
   // Stuff from apiConfig
+  registrationOpen: true,
   server: 'http://localhost:4040/',
   textlimit: 5000,
   themeData: undefined,
@@ -33,7 +34,6 @@ const defaultState = {
   postContentType: 'text/plain',
   redirectRootLogin: '/main/friends',
   redirectRootNoLogin: '/main/all',
-  registrationOpen: true,
   scopeCopy: true,
   showFeaturesPanel: true,
   showInstanceSpecificPanel: false,

From 229bf79d9016d19e1482f5e02f2f03e5d8b078e2 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 13 May 2020 17:20:35 -0500
Subject: [PATCH 356/483] name setting should be with apiConfig section

---
 src/modules/instance.js | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/modules/instance.js b/src/modules/instance.js
index beea424b..94d4176b 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -4,10 +4,8 @@ import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
 import { instanceDefaultProperties } from './config.js'
 
 const defaultState = {
-  // not configurable
-  name: 'Pleroma FE',
-
   // Stuff from apiConfig
+  name: 'Pleroma FE',
   registrationOpen: true,
   server: 'http://localhost:4040/',
   textlimit: 5000,

From e50857d0ffecf7639f14c7dba150e55ef9d2fe74 Mon Sep 17 00:00:00 2001
From: Sergey Suprunenko <suprunenko.s@gmail.com>
Date: Wed, 13 May 2020 22:58:08 +0200
Subject: [PATCH 357/483] Show "it's you" label and hide follow btn for current
 user

---
 src/components/follow_card/follow_card.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue
index 76a70730..fe2d8ab8 100644
--- a/src/components/follow_card/follow_card.vue
+++ b/src/components/follow_card/follow_card.vue
@@ -2,7 +2,7 @@
   <basic-user-card :user="user">
     <div class="follow-card-content-container">
       <span
-        v-if="!noFollowsYou && relationship.followed_by"
+        v-if="isMe || !noFollowsYou && relationship.followed_by"
         class="faint"
       >
         {{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}
@@ -15,7 +15,7 @@
           <RemoteFollow :user="user" />
         </div>
       </template>
-      <template v-else>
+      <template v-else-if="!isMe">
         <FollowButton
           :relationship="relationship"
           :label-following="$t('user_card.follow_unfollow')"

From db8dd1223c124e8461806a219d77817ba495794c Mon Sep 17 00:00:00 2001
From: Sergey Suprunenko <suprunenko.s@gmail.com>
Date: Thu, 14 May 2020 09:27:58 +0200
Subject: [PATCH 358/483] Make a condition clear and unambiguous

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

diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue
index fe2d8ab8..b503783f 100644
--- a/src/components/follow_card/follow_card.vue
+++ b/src/components/follow_card/follow_card.vue
@@ -2,7 +2,7 @@
   <basic-user-card :user="user">
     <div class="follow-card-content-container">
       <span
-        v-if="isMe || !noFollowsYou && relationship.followed_by"
+        v-if="isMe || (!noFollowsYou && relationship.followed_by)"
         class="faint"
       >
         {{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}

From 9d2935e72f5244f078d3ebb95957b024dad22643 Mon Sep 17 00:00:00 2001
From: Fristi <fristi@subcon.town>
Date: Thu, 14 May 2020 07:59:08 +0000
Subject: [PATCH 359/483] Translated using Weblate (Dutch)

Currently translated at 77.6% (476 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
---
 src/i18n/nl.json | 959 +++++++++++++++++++++++++++++------------------
 1 file changed, 589 insertions(+), 370 deletions(-)

diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index 7e2f0604..e5cf8bf8 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -1,377 +1,596 @@
 {
-  "chat": {
-    "title": "Chat"
-  },
-  "features_panel": {
-    "chat": "Chat",
-    "gopher": "Gopher",
-    "media_proxy": "Media proxy",
-    "scope_options": "Zichtbaarheidsopties",
-    "text_limit": "Tekst limiet",
-    "title": "Features",
-    "who_to_follow": "Wie te volgen"
-  },
-  "finder": {
-    "error_fetching_user": "Fout tijdens ophalen gebruiker",
-    "find_user": "Gebruiker zoeken"
-  },
-  "general": {
-    "apply": "toepassen",
-    "submit": "Verzend"
-  },
-  "login": {
-    "login": "Log in",
-    "description": "Log in met OAuth",
-    "logout": "Log uit",
-    "password": "Wachtwoord",
-    "placeholder": "bv. lain",
-    "register": "Registreer",
-    "username": "Gebruikersnaam"
-  },
-  "nav": {
-    "about": "Over",
-    "back": "Terug",
-    "chat": "Locale Chat",
-    "friend_requests": "Volgverzoek",
-    "mentions": "Vermeldingen",
-    "dms": "Directe Berichten",
-    "public_tl": "Publieke Tijdlijn",
-    "timeline": "Tijdlijn",
-    "twkn": "Het Geheel Gekende Netwerk",
-    "user_search": "Zoek Gebruiker",
-    "who_to_follow": "Wie te volgen",
-    "preferences": "Voorkeuren"
-  },
-  "notifications": {
-    "broken_favorite": "Onbekende status, aan het zoeken...",
-    "favorited_you": "vond je status leuk",
-    "followed_you": "volgt jou",
-    "load_older": "Laad oudere meldingen",
-    "notifications": "Meldingen",
-    "read": "Gelezen!",
-    "repeated_you": "Herhaalde je status"
-  },
-  "post_status": {
-    "new_status": "Post nieuwe status",
-    "account_not_locked_warning": "Je account is niet {0}. Iedereen die je volgt kan enkel-volgers posts lezen.",
-    "account_not_locked_warning_link": "gesloten",
-    "attachments_sensitive": "Markeer bijlage als gevoelig",
-    "content_type": {
-      "text/plain": "Gewone tekst"
+    "chat": {
+        "title": "Chat"
     },
-    "content_warning": "Onderwerp (optioneel)",
-    "default": "Tijd voor een pauze!",
-    "direct_warning": "Deze post zal enkel zichtbaar zijn voor de personen die genoemd zijn.",
-    "posting": "Plaatsen",
-    "scope": {
-      "direct": "Direct - Post enkel naar genoemde gebruikers",
-      "private": "Enkel volgers - Post enkel naar volgers",
-      "public": "Publiek - Post op publieke tijdlijnen",
-      "unlisted": "Unlisted - Toon niet op publieke tijdlijnen"
-    }
-  },
-  "registration": {
-    "bio": "Bio",
-    "email": "Email",
-    "fullname": "Weergave naam",
-    "password_confirm": "Wachtwoord bevestiging",
-    "registration": "Registratie",
-    "token": "Uitnodigingstoken",
-    "captcha": "CAPTCHA",
-    "new_captcha": "Klik op de afbeelding voor een nieuwe captcha",
-    "validations": {
-      "username_required": "moet ingevuld zijn",
-      "fullname_required": "moet ingevuld zijn",
-      "email_required": "moet ingevuld zijn",
-      "password_required": "moet ingevuld zijn",
-      "password_confirmation_required": "moet ingevuld zijn",
-      "password_confirmation_match": "komt niet overeen met het wachtwoord"
-    }
-  },
-  "settings": {
-    "attachmentRadius": "Bijlages",
-    "attachments": "Bijlages",
-    "autoload": "Automatisch laden wanneer tot de bodem gescrold inschakelen",
-    "avatar": "Avatar",
-    "avatarAltRadius": "Avatars (Meldingen)",
-    "avatarRadius": "Avatars",
-    "background": "Achtergrond",
-    "bio": "Bio",
-    "btnRadius": "Knoppen",
-    "cBlue": "Blauw (Antwoord, volgen)",
-    "cGreen": "Groen (Herhaal)",
-    "cOrange": "Oranje (Vind ik leuk)",
-    "cRed": "Rood (Annuleer)",
-    "change_password": "Verander Wachtwoord",
-    "change_password_error": "Er was een probleem bij het aanpassen van je wachtwoord.",
-    "changed_password": "Wachtwoord succesvol aangepast!",
-    "collapse_subject": "Klap posts met onderwerp in",
-    "composing": "Samenstellen",
-    "confirm_new_password": "Bevestig nieuw wachtwoord",
-    "current_avatar": "Je huidige avatar",
-    "current_password": "Huidig wachtwoord",
-    "current_profile_banner": "Je huidige profiel banner",
-    "data_import_export_tab": "Data Import / Export",
-    "default_vis": "Standaard zichtbaarheidsscope",
-    "delete_account": "Verwijder Account",
-    "delete_account_description": "Verwijder je account en berichten permanent.",
-    "delete_account_error": "Er was een probleem bij het verwijderen van je account. Indien dit probleem blijft, gelieve de administratie van deze instantie te verwittigen.",
-    "delete_account_instructions": "Typ je wachtwoord in de input hieronder om het verwijderen van je account te bevestigen.",
-    "export_theme": "Sla preset op",
-    "filtering": "Filtering",
-    "filtering_explanation": "Alle statussen die deze woorden bevatten worden genegeerd, één filter per lijn.",
-    "follow_export": "Volgers export",
-    "follow_export_button": "Exporteer je volgers naar een csv file",
-    "follow_export_processing": "Aan het verwerken, binnen enkele ogenblikken wordt je gevraagd je bestand te downloaden",
-    "follow_import": "Volgers import",
-    "follow_import_error": "Fout bij importeren volgers",
-    "follows_imported": "Volgers geïmporteerd! Het kan even duren om ze allemaal te verwerken.",
-    "foreground": "Voorgrond",
-    "general": "Algemeen",
-    "hide_attachments_in_convo": "Verberg bijlages in conversaties",
-    "hide_attachments_in_tl": "Verberg bijlages in de tijdlijn",
-    "hide_isp": "Verberg instantie-specifiek paneel",
-    "preload_images": "Afbeeldingen voorladen",
-    "hide_post_stats": "Verberg post statistieken (bv. het aantal vind-ik-leuks)",
-    "hide_user_stats": "Verberg post statistieken (bv. het aantal volgers)",
-    "import_followers_from_a_csv_file": "Importeer volgers uit een csv file",
-    "import_theme": "Laad preset",
-    "inputRadius": "Invoer velden",
-    "checkboxRadius": "Checkboxen",
-    "instance_default": "(standaard: {value})",
-    "instance_default_simple": "(standaard)",
-    "interface": "Interface",
-    "interfaceLanguage": "Interface taal",
-    "invalid_theme_imported": "Het geselecteerde thema is geen door Pleroma ondersteund thema. Er zijn geen aanpassingen gedaan.",
-    "limited_availability": "Onbeschikbaar in je browser",
-    "links": "Links",
-    "lock_account_description": "Laat volgers enkel toe na expliciete toestemming",
-    "loop_video": "Speel videos af in een lus",
-    "loop_video_silent_only": "Speel enkel videos zonder geluid af in een lus (bv. Mastodon's \"gifs\")",
-    "name": "Naam",
-    "name_bio": "Naam & Bio",
-    "new_password": "Nieuw wachtwoord",
-    "notification_visibility": "Type meldingen die getoond worden",
-    "notification_visibility_follows": "Volgers",
-    "notification_visibility_likes": "Vind-ik-leuks",
-    "notification_visibility_mentions": "Vermeldingen",
-    "notification_visibility_repeats": "Herhalingen",
-    "no_rich_text_description": "Strip rich text formattering van alle posts",
-    "hide_network_description": "Toon niet wie mij volgt en wie ik volg.",
-    "nsfw_clickthrough": "Schakel doorklikbaar verbergen van NSFW bijlages in",
-    "oauth_tokens": "OAuth-tokens",
-    "token": "Token",
-    "refresh_token": "Token vernieuwen",
-    "valid_until": "Geldig tot",
-    "revoke_token": "Intrekken",
-    "panelRadius": "Panelen",
-    "pause_on_unfocused": "Pauzeer streamen wanneer de tab niet gefocused is",
-    "presets": "Presets",
-    "profile_background": "Profiel Achtergrond",
-    "profile_banner": "Profiel Banner",
-    "profile_tab": "Profiel",
-    "radii_help": "Stel afronding van hoeken in de interface in (in pixels)",
-    "replies_in_timeline": "Antwoorden in tijdlijn",
-    "reply_link_preview": "Schakel antwoordlink preview in bij over zweven met muisaanwijzer",
-    "reply_visibility_all": "Toon alle antwoorden",
-    "reply_visibility_following": "Toon enkel antwoorden naar mij of andere gebruikers gericht",
-    "reply_visibility_self": "Toon enkel antwoorden naar mij gericht",
-    "saving_err": "Fout tijdens opslaan van instellingen",
-    "saving_ok": "Instellingen opgeslagen",
-    "security_tab": "Veiligheid",
-    "scope_copy": "Neem scope over bij antwoorden (Directe Berichten blijven altijd Direct)",
-    "set_new_avatar": "Zet nieuwe avatar",
-    "set_new_profile_background": "Zet nieuwe profiel achtergrond",
-    "set_new_profile_banner": "Zet nieuwe profiel banner",
-    "settings": "Instellingen",
-    "subject_input_always_show": "Maak onderwerpveld altijd zichtbaar",
-    "subject_line_behavior": "Kopieer onderwerp bij antwoorden",
-    "subject_line_email": "Zoals email: \"re: onderwerp\"",
-    "subject_line_mastodon": "Zoals Mastodon: kopieer zoals het is",
-    "subject_line_noop": "Kopieer niet",
-    "stop_gifs": "Speel GIFs af bij zweven",
-    "streaming": "Schakel automatisch streamen van posts in wanneer tot boven gescrold.",
-    "text": "Tekst",
-    "theme": "Thema",
-    "theme_help": "Gebruik hex color codes (#rrggbb) om je kleurschema te wijzigen.",
-    "theme_help_v2_1": "Je kan ook de kleur en transparantie van bepaalde componenten overschrijven door de checkbox aan te vinken, gebruik de \"Wis alles\" knop om alle overschrijvingen te annuleren.",
-    "theme_help_v2_2": "Iconen onder sommige items zijn achtergrond/tekst contrast indicators, zweef er over voor gedetailleerde info. Hou er rekening mee dat bij doorzichtigheid de ergst mogelijke situatie wordt weer gegeven.",
-    "tooltipRadius": "Gereedschapstips/alarmen",
-    "user_settings": "Gebruikers Instellingen",
-    "values": {
-      "false": "nee",
-      "true": "ja"
+    "features_panel": {
+        "chat": "Chat",
+        "gopher": "Gopher",
+        "media_proxy": "Media proxy",
+        "scope_options": "Zichtbaarheidsopties",
+        "text_limit": "Tekst limiet",
+        "title": "Features",
+        "who_to_follow": "Wie te volgen"
     },
-    "notifications": "Meldingen",
-    "enable_web_push_notifications": "Schakel web push meldingen in",
-    "style": {
-      "switcher": {
-        "keep_color": "Behoud kleuren",
-        "keep_shadows": "Behoud schaduwen",
-        "keep_opacity": "Behoud transparantie",
-        "keep_roundness": "Behoud afrondingen",
-        "keep_fonts": "Behoud lettertypes",
-        "save_load_hint": "\"Behoud\" opties behouden de momenteel ingestelde opties bij het selecteren of laden van thema's, maar slaan ook de genoemde opties op bij het exporteren van een thema. Wanneer alle selectievakjes zijn uitgeschakeld, zal het exporteren van thema's alles opslaan.",
-        "reset": "Reset",
-        "clear_all": "Wis alles",
-        "clear_opacity": "Wis transparantie"
-      },
-      "common": {
-        "color": "Kleur",
-        "opacity": "Transparantie",
-        "contrast": {
-          "hint": "Contrast ratio is {ratio}, {level} {context}",
-          "level": {
-            "aa": "voldoet aan de richtlijn van niveau AA (minimum)",
-            "aaa": "voldoet aan de richtlijn van niveau AAA (aangeraden)",
-            "bad": "voldoet aan geen enkele toegankelijkheidsrichtlijn"
-          },
-          "context": {
-            "18pt": "voor grote (18pt+) tekst",
-            "text": "voor tekst"
-          }
+    "finder": {
+        "error_fetching_user": "Fout tijdens ophalen gebruiker",
+        "find_user": "Gebruiker zoeken"
+    },
+    "general": {
+        "apply": "toepassen",
+        "submit": "Verzend",
+        "more": "Meer",
+        "optional": "optioneel",
+        "show_more": "Bekijk meer",
+        "show_less": "Bekijk minder",
+        "dismiss": "Opheffen",
+        "cancel": "Annuleren",
+        "disable": "Uitschakelen",
+        "enable": "Inschakelen",
+        "confirm": "Bevestigen",
+        "verify": "Verifiëren",
+        "generic_error": "Er is een fout opgetreden"
+    },
+    "login": {
+        "login": "Log in",
+        "description": "Log in met OAuth",
+        "logout": "Log uit",
+        "password": "Wachtwoord",
+        "placeholder": "bv. lain",
+        "register": "Registreer",
+        "username": "Gebruikersnaam",
+        "hint": "Log in om deel te nemen aan de discussie",
+        "authentication_code": "Authenticatie code",
+        "enter_recovery_code": "Voer een herstelcode in",
+        "enter_two_factor_code": "Voer een twee-factor code in",
+        "recovery_code": "Herstelcode",
+        "heading": {
+            "totp": "Twee-factor authenticatie",
+            "recovery": "Twee-factor herstelling"
         }
-      },
-      "common_colors": {
-        "_tab_label": "Gemeenschappelijk",
-        "main": "Gemeenschappelijke kleuren",
-        "foreground_hint": "Zie \"Geavanceerd\" tab voor meer gedetailleerde controle",
-        "rgbo": "Iconen, accenten, badges"
-      },
-      "advanced_colors": {
-        "_tab_label": "Geavanceerd",
-        "alert": "Alarm achtergrond",
-        "alert_error": "Fout",
-        "badge": "Badge achtergrond",
-        "badge_notification": "Meldingen",
-        "panel_header": "Paneel hoofding",
-        "top_bar": "Top bar",
-        "borders": "Randen",
-        "buttons": "Knoppen",
-        "inputs": "Invoervelden",
-        "faint_text": "Vervaagde tekst"
-      },
-      "radii": {
-        "_tab_label": "Rondheid"
-      },
-      "shadows": {
-        "_tab_label": "Schaduw en belichting",
-        "component": "Component",
-        "override": "Overschrijven",
-        "shadow_id": "Schaduw #{value}",
-        "blur": "Vervagen",
-        "spread": "Spreid",
-        "inset": "Inzet",
-        "hint": "Voor schaduw kan je ook --variable gebruiken als een kleur waarde om CSS3 variabelen te gebruiken. Houd er rekening mee dat het instellen van opaciteit in dit geval niet werkt.",
-        "filter_hint": {
-          "always_drop_shadow": "Waarschuwing, deze schaduw gebruikt altijd {0} als de browser dit ondersteund.",
-          "drop_shadow_syntax": "{0} ondersteund niet de {1} parameter en {2} sleutelwoord.",
-          "avatar_inset": "Houd er rekening mee dat het combineren van zowel inzet and niet-inzet schaduwen op transparante avatars onverwachte resultaten kan opleveren.",
-          "spread_zero": "Schaduw met spreiding > 0 worden weergegeven alsof ze op nul staan",
-          "inset_classic": "Inzet schaduw zal {0} gebruiken"
-        },
-        "components": {
-          "panel": "Paneel",
-          "panelHeader": "Paneel hoofding",
-          "topBar": "Top bar",
-          "avatar": "Gebruiker avatar (in profiel weergave)",
-          "avatarStatus": "Gebruiker avatar  (in post weergave)",
-          "popup": "Popups en gereedschapstips",
-          "button": "Knop",
-          "buttonHover": "Knop (zweven)",
-          "buttonPressed": "Knop (ingedrukt)",
-          "buttonPressedHover": "Knop (ingedrukt+zweven)",
-          "input": "Invoerveld"
-        }
-      },
-      "fonts": {
-        "_tab_label": "Lettertypes",
-        "help": "Selecteer het lettertype om te gebruiken voor elementen van de UI.Voor \"aangepast\" moet je de exacte naam van het lettertype invoeren zoals die in het systeem wordt weergegeven.",
-        "components": {
-          "interface": "Interface",
-          "input": "Invoervelden",
-          "post": "Post tekst",
-          "postCode": "Monospaced tekst in een post (rich text)"
-        },
-        "family": "Naam lettertype",
-        "size": "Grootte (in px)",
-        "weight": "Gewicht (vetheid)",
-        "custom": "Aangepast"
-      },
-      "preview": {
-        "header": "Voorvertoning",
-        "content": "Inhoud",
-        "error": "Voorbeeld fout",
-        "button": "Knop",
-        "text": "Nog een boel andere {0} en {1}",
-        "mono": "inhoud",
-        "input": "Tijd voor een pauze!",
-        "faint_link": "handige gebruikershandleiding",
-        "fine_print": "Lees onze {0} om niets nuttig te leren!",
-        "header_faint": "Alles komt goed",
-        "checkbox": "Ik heb de gebruikersvoorwaarden eens van ver bekeken",
-        "link": "een link"
-      }
-    }
-  },
-  "timeline": {
-    "collapse": "Inklappen",
-    "conversation": "Conversatie",
-    "error_fetching": "Fout bij ophalen van updates",
-    "load_older": "Laad oudere Statussen",
-    "no_retweet_hint": "Post is gemarkeerd als enkel volgers of direct en kan niet worden herhaald",
-    "repeated": "herhaalde",
-    "show_new": "Toon nieuwe",
-    "up_to_date": "Up-to-date"
-  },
-  "user_card": {
-    "approve": "Goedkeuren",
-    "block": "Blokkeren",
-    "blocked": "Geblokkeerd!",
-    "deny": "Ontzeggen",
-    "favorites": "Vind-ik-leuks",
-    "follow": "Volgen",
-    "follow_sent": "Aanvraag verzonden!",
-    "follow_progress": "Aanvragen…",
-    "follow_again": "Aanvraag opnieuw zenden?",
-    "follow_unfollow": "Stop volgen",
-    "followees": "Aan het volgen",
-    "followers": "Volgers",
-    "following": "Aan het volgen!",
-    "follows_you": "Volgt jou!",
-    "its_you": "'t is jij!",
-    "mute": "Dempen",
-    "muted": "Gedempt",
-    "per_day": "per dag",
-    "remote_follow": "Volg vanop afstand",
-    "statuses": "Statussen"
-  },
-  "user_profile": {
-    "timeline_title": "Gebruikers Tijdlijn"
-  },
-  "who_to_follow": {
-    "more": "Meer",
-    "who_to_follow": "Wie te volgen"
-  },
-  "tool_tip": {
-    "media_upload": "Upload Media",
-    "repeat": "Herhaal",
-    "reply": "Antwoord",
-    "favorite": "Vind-ik-leuk",
-    "user_settings": "Gebruikers Instellingen"
-  },
-  "upload":{
-    "error": {
-    "base": "Upload gefaald.",
-    "file_too_big": "Bestand is te groot [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-    "default": "Probeer later opnieuw"
     },
-    "file_size_units": {
-      "B": "B",
-      "KiB": "KiB",
-      "MiB": "MiB",
-      "GiB": "GiB",
-      "TiB": "TiB"
+    "nav": {
+        "about": "Over",
+        "back": "Terug",
+        "chat": "Locale Chat",
+        "friend_requests": "Volgverzoek",
+        "mentions": "Vermeldingen",
+        "dms": "Directe Berichten",
+        "public_tl": "Publieke Tijdlijn",
+        "timeline": "Tijdlijn",
+        "twkn": "Het Geheel Gekende Netwerk",
+        "user_search": "Zoek Gebruiker",
+        "who_to_follow": "Wie te volgen",
+        "preferences": "Voorkeuren",
+        "administration": "Administratie",
+        "search": "Zoeken",
+        "interactions": "Interacties"
+    },
+    "notifications": {
+        "broken_favorite": "Onbekende status, aan het zoeken...",
+        "favorited_you": "vond je status leuk",
+        "followed_you": "volgt jou",
+        "load_older": "Laad oudere meldingen",
+        "notifications": "Meldingen",
+        "read": "Gelezen!",
+        "repeated_you": "Herhaalde je status",
+        "no_more_notifications": "Geen notificaties meer",
+        "migrated_to": "is gemigreerd naar",
+        "follow_request": "wil je volgen",
+        "reacted_with": "reageerde met {0}"
+    },
+    "post_status": {
+        "new_status": "Post nieuwe status",
+        "account_not_locked_warning": "Je account is niet {0}. Iedereen die je volgt kan enkel-volgers posts lezen.",
+        "account_not_locked_warning_link": "gesloten",
+        "attachments_sensitive": "Markeer bijlage als gevoelig",
+        "content_type": {
+            "text/plain": "Gewone tekst",
+            "text/html": "HTML",
+            "text/markdown": "Markdown",
+            "text/bbcode": "BBCode"
+        },
+        "content_warning": "Onderwerp (optioneel)",
+        "default": "Tijd voor een pauze!",
+        "direct_warning": "Deze post zal enkel zichtbaar zijn voor de personen die genoemd zijn.",
+        "posting": "Plaatsen",
+        "scope": {
+            "direct": "Direct - Post enkel naar genoemde gebruikers",
+            "private": "Enkel volgers - Post enkel naar volgers",
+            "public": "Publiek - Post op publieke tijdlijnen",
+            "unlisted": "Unlisted - Toon niet op publieke tijdlijnen"
+        },
+        "direct_warning_to_all": "Dit bericht zal zichtbaar zijn voor alle vermelde gebruikers.",
+        "direct_warning_to_first_only": "Dit bericht zal alleen zichtbaar zijn voor de vermelde gebruikers aan het begin van het bericht.",
+        "scope_notice": {
+            "public": "Dit bericht zal voor iedereen zichtbaar zijn",
+            "unlisted": "Dit bericht zal niet zichtbaar zijn in de Publieke Tijdlijn en Het Geheel Gekende Netwerk",
+            "private": "Dit bericht zal voor alleen je volgers zichtbaar zijn"
+        }
+    },
+    "registration": {
+        "bio": "Bio",
+        "email": "Email",
+        "fullname": "Weergave naam",
+        "password_confirm": "Wachtwoord bevestiging",
+        "registration": "Registratie",
+        "token": "Uitnodigingstoken",
+        "captcha": "CAPTCHA",
+        "new_captcha": "Klik op de afbeelding voor een nieuwe captcha",
+        "validations": {
+            "username_required": "moet ingevuld zijn",
+            "fullname_required": "moet ingevuld zijn",
+            "email_required": "moet ingevuld zijn",
+            "password_required": "moet ingevuld zijn",
+            "password_confirmation_required": "moet ingevuld zijn",
+            "password_confirmation_match": "komt niet overeen met het wachtwoord"
+        },
+        "username_placeholder": "bijv. lain",
+        "fullname_placeholder": "bijv. Lain Iwakura",
+        "bio_placeholder": "bijv.\nHallo, ik ben Lain.\nIk ben een anime meisje woonachtig in een buitenwijk in Japan. Je kent me misschien van the Wired."
+    },
+    "settings": {
+        "attachmentRadius": "Bijlages",
+        "attachments": "Bijlages",
+        "autoload": "Automatisch laden wanneer tot de bodem gescrold inschakelen",
+        "avatar": "Avatar",
+        "avatarAltRadius": "Avatars (Meldingen)",
+        "avatarRadius": "Avatars",
+        "background": "Achtergrond",
+        "bio": "Bio",
+        "btnRadius": "Knoppen",
+        "cBlue": "Blauw (Antwoord, volgen)",
+        "cGreen": "Groen (Herhaal)",
+        "cOrange": "Oranje (Vind ik leuk)",
+        "cRed": "Rood (Annuleer)",
+        "change_password": "Verander Wachtwoord",
+        "change_password_error": "Er was een probleem bij het aanpassen van je wachtwoord.",
+        "changed_password": "Wachtwoord succesvol aangepast!",
+        "collapse_subject": "Klap posts met onderwerp in",
+        "composing": "Samenstellen",
+        "confirm_new_password": "Bevestig nieuw wachtwoord",
+        "current_avatar": "Je huidige avatar",
+        "current_password": "Huidig wachtwoord",
+        "current_profile_banner": "Je huidige profiel banner",
+        "data_import_export_tab": "Data Import / Export",
+        "default_vis": "Standaard zichtbaarheidsscope",
+        "delete_account": "Verwijder Account",
+        "delete_account_description": "Verwijder je account en berichten permanent.",
+        "delete_account_error": "Er was een probleem bij het verwijderen van je account. Indien dit probleem blijft, gelieve de administratie van deze instantie te verwittigen.",
+        "delete_account_instructions": "Typ je wachtwoord in de input hieronder om het verwijderen van je account te bevestigen.",
+        "export_theme": "Sla preset op",
+        "filtering": "Filtering",
+        "filtering_explanation": "Alle statussen die deze woorden bevatten worden genegeerd, één filter per lijn.",
+        "follow_export": "Volgers exporteren",
+        "follow_export_button": "Exporteer je volgers naar een csv file",
+        "follow_export_processing": "Aan het verwerken, binnen enkele ogenblikken wordt je gevraagd je bestand te downloaden",
+        "follow_import": "Volgers importeren",
+        "follow_import_error": "Fout bij importeren volgers",
+        "follows_imported": "Volgers geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
+        "foreground": "Voorgrond",
+        "general": "Algemeen",
+        "hide_attachments_in_convo": "Verberg bijlages in conversaties",
+        "hide_attachments_in_tl": "Verberg bijlages in de tijdlijn",
+        "hide_isp": "Verberg instantie-specifiek paneel",
+        "preload_images": "Afbeeldingen voorladen",
+        "hide_post_stats": "Verberg post statistieken (bv. het aantal vind-ik-leuks)",
+        "hide_user_stats": "Verberg post statistieken (bv. het aantal volgers)",
+        "import_followers_from_a_csv_file": "Importeer volgers uit een csv file",
+        "import_theme": "Laad preset",
+        "inputRadius": "Invoer velden",
+        "checkboxRadius": "Checkboxen",
+        "instance_default": "(standaard: {value})",
+        "instance_default_simple": "(standaard)",
+        "interface": "Interface",
+        "interfaceLanguage": "Interface taal",
+        "invalid_theme_imported": "Het geselecteerde thema is geen door Pleroma ondersteund thema. Er zijn geen aanpassingen gedaan.",
+        "limited_availability": "Onbeschikbaar in je browser",
+        "links": "Links",
+        "lock_account_description": "Laat volgers enkel toe na expliciete toestemming",
+        "loop_video": "Speel videos af in een lus",
+        "loop_video_silent_only": "Speel enkel videos zonder geluid af in een lus (bv. Mastodon's \"gifs\")",
+        "name": "Naam",
+        "name_bio": "Naam & Bio",
+        "new_password": "Nieuw wachtwoord",
+        "notification_visibility": "Type meldingen die getoond worden",
+        "notification_visibility_follows": "Volgers",
+        "notification_visibility_likes": "Vind-ik-leuks",
+        "notification_visibility_mentions": "Vermeldingen",
+        "notification_visibility_repeats": "Herhalingen",
+        "no_rich_text_description": "Strip rich text formattering van alle posts",
+        "hide_network_description": "Toon niet wie mij volgt en wie ik volg.",
+        "nsfw_clickthrough": "Schakel doorklikbaar verbergen van NSFW bijlages in",
+        "oauth_tokens": "OAuth-tokens",
+        "token": "Token",
+        "refresh_token": "Token vernieuwen",
+        "valid_until": "Geldig tot",
+        "revoke_token": "Intrekken",
+        "panelRadius": "Panelen",
+        "pause_on_unfocused": "Pauzeer streamen wanneer de tab niet gefocused is",
+        "presets": "Presets",
+        "profile_background": "Profiel Achtergrond",
+        "profile_banner": "Profiel Banner",
+        "profile_tab": "Profiel",
+        "radii_help": "Stel afronding van hoeken in de interface in (in pixels)",
+        "replies_in_timeline": "Antwoorden in tijdlijn",
+        "reply_link_preview": "Schakel antwoordlink preview in bij over zweven met muisaanwijzer",
+        "reply_visibility_all": "Toon alle antwoorden",
+        "reply_visibility_following": "Toon enkel antwoorden naar mij of andere gebruikers gericht",
+        "reply_visibility_self": "Toon enkel antwoorden naar mij gericht",
+        "saving_err": "Fout tijdens opslaan van instellingen",
+        "saving_ok": "Instellingen opgeslagen",
+        "security_tab": "Veiligheid",
+        "scope_copy": "Neem scope over bij antwoorden (Directe Berichten blijven altijd Direct)",
+        "set_new_avatar": "Zet nieuwe avatar",
+        "set_new_profile_background": "Zet nieuwe profiel achtergrond",
+        "set_new_profile_banner": "Zet nieuwe profiel banner",
+        "settings": "Instellingen",
+        "subject_input_always_show": "Maak onderwerpveld altijd zichtbaar",
+        "subject_line_behavior": "Kopieer onderwerp bij antwoorden",
+        "subject_line_email": "Zoals email: \"re: onderwerp\"",
+        "subject_line_mastodon": "Zoals Mastodon: kopieer zoals het is",
+        "subject_line_noop": "Kopieer niet",
+        "stop_gifs": "Speel GIFs af bij zweven",
+        "streaming": "Schakel automatisch streamen van posts in wanneer tot boven gescrold.",
+        "text": "Tekst",
+        "theme": "Thema",
+        "theme_help": "Gebruik hex color codes (#rrggbb) om je kleurschema te wijzigen.",
+        "theme_help_v2_1": "Je kan ook de kleur en transparantie van bepaalde componenten overschrijven door de checkbox aan te vinken, gebruik de \"Wis alles\" knop om alle overschrijvingen te annuleren.",
+        "theme_help_v2_2": "Iconen onder sommige items zijn achtergrond/tekst contrast indicators, zweef er over voor gedetailleerde info. Hou er rekening mee dat bij doorzichtigheid de ergst mogelijke situatie wordt weer gegeven.",
+        "tooltipRadius": "Gereedschapstips/alarmen",
+        "user_settings": "Gebruikers Instellingen",
+        "values": {
+            "false": "nee",
+            "true": "ja"
+        },
+        "notifications": "Meldingen",
+        "enable_web_push_notifications": "Schakel web push meldingen in",
+        "style": {
+            "switcher": {
+                "keep_color": "Behoud kleuren",
+                "keep_shadows": "Behoud schaduwen",
+                "keep_opacity": "Behoud transparantie",
+                "keep_roundness": "Behoud afrondingen",
+                "keep_fonts": "Behoud lettertypes",
+                "save_load_hint": "\"Behoud\" opties behouden de momenteel ingestelde opties bij het selecteren of laden van thema's, maar slaan ook de genoemde opties op bij het exporteren van een thema. Wanneer alle selectievakjes zijn uitgeschakeld, zal het exporteren van thema's alles opslaan.",
+                "reset": "Reset",
+                "clear_all": "Wis alles",
+                "clear_opacity": "Wis transparantie",
+                "keep_as_is": "Houdt zoals het is",
+                "use_snapshot": "Oude versie",
+                "use_source": "Nieuwe versie",
+                "help": {
+                    "future_version_imported": "Het geïmporteerde bestand is gemaakt voor een nieuwere versie van FE.",
+                    "older_version_imported": "Het geïmporteerde bestand is gemaakt voor een oudere versie van FE.",
+                    "upgraded_from_v2": "PleromaFE is bijgewerkt, het thema kan iets anders uitzien dan dat je gewend bent.",
+                    "v2_imported": "Het geïmporteerde bestand is gemaakt voor een oudere FE. We proberen compatibiliteit te maximaliseren, maar het kan toch voorkomen dat er inconsistenties zijn."
+                },
+                "load_theme": "Thema laden"
+            },
+            "common": {
+                "color": "Kleur",
+                "opacity": "Transparantie",
+                "contrast": {
+                    "hint": "Contrast ratio is {ratio}, {level} {context}",
+                    "level": {
+                        "aa": "voldoet aan de richtlijn van niveau AA (minimum)",
+                        "aaa": "voldoet aan de richtlijn van niveau AAA (aangeraden)",
+                        "bad": "voldoet aan geen enkele toegankelijkheidsrichtlijn"
+                    },
+                    "context": {
+                        "18pt": "voor grote (18pt+) tekst",
+                        "text": "voor tekst"
+                    }
+                }
+            },
+            "common_colors": {
+                "_tab_label": "Gemeenschappelijk",
+                "main": "Gemeenschappelijke kleuren",
+                "foreground_hint": "Zie \"Geavanceerd\" tab voor meer gedetailleerde controle",
+                "rgbo": "Iconen, accenten, badges"
+            },
+            "advanced_colors": {
+                "_tab_label": "Geavanceerd",
+                "alert": "Alarm achtergrond",
+                "alert_error": "Fout",
+                "badge": "Badge achtergrond",
+                "badge_notification": "Meldingen",
+                "panel_header": "Paneel hoofding",
+                "top_bar": "Top bar",
+                "borders": "Randen",
+                "buttons": "Knoppen",
+                "inputs": "Invoervelden",
+                "faint_text": "Vervaagde tekst"
+            },
+            "radii": {
+                "_tab_label": "Rondheid"
+            },
+            "shadows": {
+                "_tab_label": "Schaduw en belichting",
+                "component": "Component",
+                "override": "Overschrijven",
+                "shadow_id": "Schaduw #{value}",
+                "blur": "Vervagen",
+                "spread": "Spreid",
+                "inset": "Inzet",
+                "hint": "Voor schaduw kan je ook --variable gebruiken als een kleur waarde om CSS3 variabelen te gebruiken. Houd er rekening mee dat het instellen van opaciteit in dit geval niet werkt.",
+                "filter_hint": {
+                    "always_drop_shadow": "Waarschuwing, deze schaduw gebruikt altijd {0} als de browser dit ondersteund.",
+                    "drop_shadow_syntax": "{0} ondersteund niet de {1} parameter en {2} sleutelwoord.",
+                    "avatar_inset": "Houd er rekening mee dat het combineren van zowel inzet and niet-inzet schaduwen op transparante avatars onverwachte resultaten kan opleveren.",
+                    "spread_zero": "Schaduw met spreiding > 0 worden weergegeven alsof ze op nul staan",
+                    "inset_classic": "Inzet schaduw zal {0} gebruiken"
+                },
+                "components": {
+                    "panel": "Paneel",
+                    "panelHeader": "Paneel hoofding",
+                    "topBar": "Top bar",
+                    "avatar": "Gebruiker avatar (in profiel weergave)",
+                    "avatarStatus": "Gebruiker avatar  (in post weergave)",
+                    "popup": "Popups en gereedschapstips",
+                    "button": "Knop",
+                    "buttonHover": "Knop (zweven)",
+                    "buttonPressed": "Knop (ingedrukt)",
+                    "buttonPressedHover": "Knop (ingedrukt+zweven)",
+                    "input": "Invoerveld"
+                }
+            },
+            "fonts": {
+                "_tab_label": "Lettertypes",
+                "help": "Selecteer het lettertype om te gebruiken voor elementen van de UI.Voor \"aangepast\" moet je de exacte naam van het lettertype invoeren zoals die in het systeem wordt weergegeven.",
+                "components": {
+                    "interface": "Interface",
+                    "input": "Invoervelden",
+                    "post": "Post tekst",
+                    "postCode": "Monospaced tekst in een post (rich text)"
+                },
+                "family": "Naam lettertype",
+                "size": "Grootte (in px)",
+                "weight": "Gewicht (vetheid)",
+                "custom": "Aangepast"
+            },
+            "preview": {
+                "header": "Voorvertoning",
+                "content": "Inhoud",
+                "error": "Voorbeeld fout",
+                "button": "Knop",
+                "text": "Nog een boel andere {0} en {1}",
+                "mono": "inhoud",
+                "input": "Tijd voor een pauze!",
+                "faint_link": "handige gebruikershandleiding",
+                "fine_print": "Lees onze {0} om niets nuttig te leren!",
+                "header_faint": "Alles komt goed",
+                "checkbox": "Ik heb de gebruikersvoorwaarden eens van ver bekeken",
+                "link": "een link"
+            }
+        },
+        "notification_setting_follows": "Gebruikers die je volgt",
+        "notification_setting_non_follows": "Gebruikers die je niet volgt",
+        "notification_setting_followers": "Gebruikers die je volgen",
+        "notification_setting_privacy": "Privacy",
+        "notification_setting_privacy_option": "Verberg de afzender en inhoud van push notificaties",
+        "notification_mutes": "Om niet langer notificaties te ontvangen van een specifieke gebruiker, kun je deze dempen.",
+        "app_name": "App naam",
+        "security": "Beveiliging",
+        "enter_current_password_to_confirm": "Voer je huidige wachtwoord in om je identiteit te bevestigen",
+        "mfa": {
+            "otp": "OTP",
+            "setup_otp": "OTP instellen",
+            "wait_pre_setup_otp": "OTP voorinstellen",
+            "confirm_and_enable": "Bevestig en schakel OTP in",
+            "title": "Twee-factor Authenticatie",
+            "generate_new_recovery_codes": "Genereer nieuwe herstelcodes",
+            "recovery_codes": "Herstelcodes.",
+            "waiting_a_recovery_codes": "Backup codes ontvangen...",
+            "authentication_methods": "Authenticatie methodes",
+            "scan": {
+                "title": "Scannen",
+                "desc": "Scan de QR code of voer een sleutel in met je twee-factor applicatie:",
+                "secret_code": "Sleutel"
+            },
+            "verify": {
+                "desc": "Voer de code van je twee-factor applicatie in om twee-factor authenticatie in te schakelen:"
+            },
+            "warning_of_generate_new_codes": "Wanneer je nieuwe herstelcodes genereert, zullen je oude code niet langer werken.",
+            "recovery_codes_warning": "Schrijf de codes op of sla ze op een veilige locatie op - anders kun je ze niet meer inzien. Als je toegang tot je 2FA app en herstelcodes verliest, zal je buitengesloten zijn uit je account."
+        },
+        "allow_following_move": "Auto-volgen toestaan wanneer een gevolgd account migreert",
+        "block_export": "Geblokkeerde gebruikers exporteren",
+        "block_import": "Geblokkeerde gebruikers importeren",
+        "blocks_imported": "Geblokkeerde gebruikers geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
+        "blocks_tab": "Geblokkeerde gebruikers",
+        "change_email": "Email wijzigen",
+        "change_email_error": "Er is een probleem opgetreden tijdens het wijzigen van je email.",
+        "changed_email": "Email succesvol gewijzigd!",
+        "domain_mutes": "Domeinen",
+        "avatar_size_instruction": "De aangeraden minimale afmeting voor avatar afbeeldingen is 150x150 pixels.",
+        "pad_emoji": "Vul emoji op met spaties wanneer deze met de picker ingevoegd worden",
+        "emoji_reactions_on_timeline": "Toon emoji reacties op de tijdlijn",
+        "accent": "Accent",
+        "hide_muted_posts": "Verberg berichten van gedempte gebruikers",
+        "max_thumbnails": "Maximaal aantal miniaturen per bericht",
+        "use_one_click_nsfw": "Open gevoelige bijlagen met slechts één klik",
+        "hide_filtered_statuses": "Verberg gefilterde statussen",
+        "import_blocks_from_a_csv_file": "Importeer geblokkeerde gebruikers van een csv bestand",
+        "mutes_tab": "Gedempte gebruikers",
+        "play_videos_in_modal": "Speel video's af in een popup frame",
+        "new_email": "Nieuwe Email",
+        "notification_visibility_emoji_reactions": "Reacties",
+        "no_blocks": "Geen geblokkeerde gebruikers",
+        "no_mutes": "Geen gedempte gebruikers",
+        "hide_followers_description": "Niet tonen wie mij volgt",
+        "hide_followers_count_description": "Niet mijn volgers aantal tonen",
+        "hide_follows_count_description": "Niet mijn gevolgde aantal tonen",
+        "show_admin_badge": "Admin badge tonen in mijn profiel",
+        "autohide_floating_post_button": "Nieuw Bericht knop automatisch verbergen (mobiel)",
+        "search_user_to_block": "Zoek wie je wilt blokkeren",
+        "search_user_to_mute": "Zoek wie je wilt dempen",
+        "minimal_scopes_mode": "Bericht bereik-opties minimaliseren",
+        "post_status_content_type": "Bericht status content type",
+        "user_mutes": "Gebruikers",
+        "useStreamingApi": "Berichten en notificatie in real-time ontvangen",
+        "useStreamingApiWarning": "(Afgeraden, experimenteel, kan berichten overslaan)",
+        "type_domains_to_mute": "Voer domeinen in om te dempen",
+        "upload_a_photo": "Upload een foto",
+        "fun": "Plezier",
+        "greentext": "Meme pijlen",
+        "notification_setting": "Ontvang notificaties van:",
+        "block_export_button": "Exporteer je geblokkeerde gebruikers naar een csv bestand",
+        "block_import_error": "Fout bij importeren geblokkeerde gebruikers",
+        "discoverable": "Sta toe dat dit account ontdekt kan worden in zoekresultaten en andere diensten",
+        "use_contain_fit": "Bijlage in miniaturen niet bijsnijden",
+        "notification_visibility_moves": "Gebruiker Migraties",
+        "hide_follows_description": "Niet tonen wie ik volg",
+        "show_moderator_badge": "Moderator badge tonen in mijn profiel",
+        "notification_setting_filters": "Filters",
+        "notification_setting_non_followers": "Gebruikers die je niet volgen",
+        "notification_blocks": "Door een gebruiker te blokkeren, ontvang je geen notificaties meer van de gebruiker en wordt je abonnement op de gebruiker opgeheven."
+    },
+    "timeline": {
+        "collapse": "Inklappen",
+        "conversation": "Conversatie",
+        "error_fetching": "Fout bij ophalen van updates",
+        "load_older": "Laad oudere Statussen",
+        "no_retweet_hint": "Post is gemarkeerd als enkel volgers of direct en kan niet worden herhaald",
+        "repeated": "herhaalde",
+        "show_new": "Toon nieuwe",
+        "up_to_date": "Up-to-date"
+    },
+    "user_card": {
+        "approve": "Goedkeuren",
+        "block": "Blokkeren",
+        "blocked": "Geblokkeerd!",
+        "deny": "Ontzeggen",
+        "favorites": "Vind-ik-leuks",
+        "follow": "Volgen",
+        "follow_sent": "Aanvraag verzonden!",
+        "follow_progress": "Aanvragen…",
+        "follow_again": "Aanvraag opnieuw zenden?",
+        "follow_unfollow": "Stop volgen",
+        "followees": "Aan het volgen",
+        "followers": "Volgers",
+        "following": "Aan het volgen!",
+        "follows_you": "Volgt jou!",
+        "its_you": "'t is jij!",
+        "mute": "Dempen",
+        "muted": "Gedempt",
+        "per_day": "per dag",
+        "remote_follow": "Volg vanop afstand",
+        "statuses": "Statussen"
+    },
+    "user_profile": {
+        "timeline_title": "Gebruikers Tijdlijn"
+    },
+    "who_to_follow": {
+        "more": "Meer",
+        "who_to_follow": "Wie te volgen"
+    },
+    "tool_tip": {
+        "media_upload": "Upload Media",
+        "repeat": "Herhaal",
+        "reply": "Antwoord",
+        "favorite": "Vind-ik-leuk",
+        "user_settings": "Gebruikers Instellingen"
+    },
+    "upload": {
+        "error": {
+            "base": "Upload gefaald.",
+            "file_too_big": "Bestand is te groot [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+            "default": "Probeer later opnieuw"
+        },
+        "file_size_units": {
+            "B": "B",
+            "KiB": "KiB",
+            "MiB": "MiB",
+            "GiB": "GiB",
+            "TiB": "TiB"
+        }
+    },
+    "about": {
+        "mrf": {
+            "federation": "Federatie",
+            "keyword": {
+                "reject": "Afwijzen",
+                "replace": "Vervangen",
+                "is_replaced_by": "→",
+                "keyword_policies": "Zoekwoord Beleid"
+            },
+            "mrf_policies_desc": "MRF beleidsregels beïnvloeden het federatie gedrag van de instantie. De volgende beleidsregels zijn ingeschakeld:",
+            "mrf_policies": "Ingeschakelde MRF Beleidsregels",
+            "simple": {
+                "simple_policies": "Instantie-specifieke Beleidsregels",
+                "accept": "Accepteren",
+                "accept_desc": "Deze instantie accepteert alleen berichten van de volgende instanties:",
+                "reject": "Afwijzen",
+                "reject_desc": "Deze instantie zal geen berichten accepteren van de volgende instanties:",
+                "quarantine": "Quarantaine",
+                "quarantine_desc": "Deze instantie zal alleen publieke berichten sturen naar de volgende instanties:",
+                "ftl_removal_desc": "Deze instantie verwijdert de volgende instanties van \"Het Geheel Gekende Netwerk\" tijdlijn:",
+                "media_removal_desc": "Deze instantie verwijdert media van berichten van de volgende instanties:",
+                "media_nsfw_desc": "Deze instantie stelt media in als gevoelig in berichten van de volgende instanties:"
+            }
+        },
+        "staff": "Personeel"
+    },
+    "domain_mute_card": {
+        "mute": "Dempen",
+        "mute_progress": "Dempen...",
+        "unmute": "Dempen opheffen",
+        "unmute_progress": "Dempen wordt opgeheven..."
+    },
+    "exporter": {
+        "export": "Exporteren",
+        "processing": "Verwerken, er wordt zo gevraagd om je bestand te downloaden"
+    },
+    "image_cropper": {
+        "save": "Opslaan",
+        "save_without_cropping": "Opslaan zonder bijsnijden",
+        "cancel": "Annuleren",
+        "crop_picture": "Afbeelding bijsnijden"
+    },
+    "importer": {
+        "submit": "Verzenden",
+        "success": "Succesvol geïmporteerd.",
+        "error": "Er is een fout opgetreden bij het importeren van dit bestand."
+    },
+    "media_modal": {
+        "previous": "Vorige",
+        "next": "Volgende"
+    },
+    "polls": {
+        "add_poll": "Poll Toevoegen",
+        "add_option": "Optie Toevoegen",
+        "option": "Optie",
+        "votes": "stemmen",
+        "vote": "Stem",
+        "single_choice": "Enkelkeuze",
+        "multiple_choices": "Meerkeuze",
+        "expiry": "Poll leeftijd",
+        "expires_in": "Poll eindigt in {0}",
+        "expired": "Poll is {0} geleden beëindigd",
+        "not_enough_options": "Te weinig opties in poll",
+        "type": "Poll type"
+    },
+    "emoji": {
+        "emoji": "Emoji",
+        "keep_open": "Houdt picker open",
+        "search_emoji": "Zoek voor een emoji",
+        "add_emoji": "Emoji invoegen",
+        "unicode": "Unicode emoji",
+        "load_all": "Alle {emojiAmount} emoji worden geladen",
+        "stickers": "Stickers",
+        "load_all_hint": "Eerste {saneAmount} emoji geladen, alle emoji tegelijk laden kan prestatie problemen veroorzaken."
+    },
+    "interactions": {
+        "favs_repeats": "Herhalingen en Favorieten",
+        "follows": "Nieuwe volgers",
+        "moves": "Gebruiker migreert",
+        "load_older": "Oudere interacties laden"
+    },
+    "remote_user_resolver": {
+        "searching_for": "Zoeken naar",
+        "error": "Niet gevonden."
+    },
+    "selectable_list": {
+        "select_all": "Alles selecteren"
     }
-  }
 }

From f2bcaec8dcfe7474409430d9fcf42a7ee94d6253 Mon Sep 17 00:00:00 2001
From: Fristi <fristi@subcon.town>
Date: Thu, 14 May 2020 16:30:29 +0000
Subject: [PATCH 360/483] Translated using Weblate (Dutch)

Currently translated at 77.8% (477 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
---
 src/i18n/nl.json | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index e5cf8bf8..7c87dadd 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -514,10 +514,10 @@
                 "is_replaced_by": "→",
                 "keyword_policies": "Zoekwoord Beleid"
             },
-            "mrf_policies_desc": "MRF beleidsregels beïnvloeden het federatie gedrag van de instantie. De volgende beleidsregels zijn ingeschakeld:",
-            "mrf_policies": "Ingeschakelde MRF Beleidsregels",
+            "mrf_policies_desc": "MRF regels beïnvloeden het federatie gedrag van de instantie. De volgende regels zijn ingeschakeld:",
+            "mrf_policies": "Ingeschakelde MRF Regels",
             "simple": {
-                "simple_policies": "Instantie-specifieke Beleidsregels",
+                "simple_policies": "Instantie-specifieke Regels",
                 "accept": "Accepteren",
                 "accept_desc": "Deze instantie accepteert alleen berichten van de volgende instanties:",
                 "reject": "Afwijzen",
@@ -526,7 +526,9 @@
                 "quarantine_desc": "Deze instantie zal alleen publieke berichten sturen naar de volgende instanties:",
                 "ftl_removal_desc": "Deze instantie verwijdert de volgende instanties van \"Het Geheel Gekende Netwerk\" tijdlijn:",
                 "media_removal_desc": "Deze instantie verwijdert media van berichten van de volgende instanties:",
-                "media_nsfw_desc": "Deze instantie stelt media in als gevoelig in berichten van de volgende instanties:"
+                "media_nsfw_desc": "Deze instantie stelt media in als gevoelig in berichten van de volgende instanties:",
+                "ftl_removal": "Verwijderen van \"Het Geheel Bekende Netwerk\" Tijdlijn",
+                "media_removal": "Media Verwijdering"
             }
         },
         "staff": "Personeel"

From 9c62b90212e1915bc0bb60adfc8b2da4bf37bf46 Mon Sep 17 00:00:00 2001
From: Fristi <fristi@subcon.town>
Date: Thu, 14 May 2020 16:49:22 +0000
Subject: [PATCH 361/483] Translated using Weblate (Dutch)

Currently translated at 77.8% (477 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
---
 src/i18n/nl.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index 7c87dadd..bac7e62c 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -8,7 +8,7 @@
         "media_proxy": "Media proxy",
         "scope_options": "Zichtbaarheidsopties",
         "text_limit": "Tekst limiet",
-        "title": "Features",
+        "title": "Kenmerken",
         "who_to_follow": "Wie te volgen"
     },
     "finder": {

From 7a0f27f3b06eb73dc974e3cededddbc6ac3c5902 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Wed, 13 May 2020 23:17:23 +0000
Subject: [PATCH 362/483] Translated using Weblate (Italian)

Currently translated at 35.0% (215 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 87 +++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 68 insertions(+), 19 deletions(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index 8e35d4f9..6fc2be74 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -1,13 +1,24 @@
 {
     "general": {
         "submit": "Invia",
-        "apply": "Applica"
+        "apply": "Applica",
+        "more": "Altro",
+        "generic_error": "Errore",
+        "optional": "facoltativo",
+        "show_more": "Mostra tutto",
+        "show_less": "Ripiega",
+        "dismiss": "Chiudi",
+        "cancel": "Annulla",
+        "disable": "Disabilita",
+        "enable": "Abilita",
+        "confirm": "Conferma",
+        "verify": "Verifica"
     },
     "nav": {
         "mentions": "Menzioni",
         "public_tl": "Sequenza pubblica",
         "timeline": "Sequenza personale",
-        "twkn": "Tutte le stanze accessibili",
+        "twkn": "Sequenza globale",
         "chat": "Chat della stanza",
         "friend_requests": "Vogliono seguirti"
     },
@@ -22,13 +33,13 @@
     },
     "settings": {
         "attachments": "Allegati",
-        "autoload": "Abilita caricamento automatico quando si raggiunge il fondo pagina",
+        "autoload": "Abilita caricamento automatico quando raggiungi il fondo pagina",
         "avatar": "Icona utente",
         "bio": "Introduzione",
         "current_avatar": "La tua icona attuale",
         "current_profile_banner": "Il tuo stendardo attuale",
         "filtering": "Filtri",
-        "filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, una per linea",
+        "filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, una per riga",
         "hide_attachments_in_convo": "Nascondi gli allegati presenti nelle conversazioni",
         "hide_attachments_in_tl": "Nascondi gli allegati presenti nelle sequenze",
         "name": "Nome",
@@ -44,18 +55,18 @@
         "theme": "Tema",
         "user_settings": "Impostazioni Utente",
         "attachmentRadius": "Allegati",
-        "avatarAltRadius": "Icona utente (Notifiche)",
+        "avatarAltRadius": "Icone utente (Notifiche)",
         "avatarRadius": "Icone utente",
         "background": "Sfondo",
         "btnRadius": "Pulsanti",
         "cBlue": "Blu (risposte, seguire)",
         "cGreen": "Verde (ripeti)",
-        "cOrange": "Arancio (gradire)",
+        "cOrange": "Arancione (gradire)",
         "cRed": "Rosso (annulla)",
-        "change_password": "Cambia Password",
+        "change_password": "Cambia password",
         "change_password_error": "C'è stato un problema durante il cambiamento della password.",
         "changed_password": "Password cambiata correttamente!",
-        "collapse_subject": "Collassa messaggi con Oggetto",
+        "collapse_subject": "Ripiega messaggi con Oggetto",
         "confirm_new_password": "Conferma la nuova password",
         "current_password": "La tua password attuale",
         "data_import_export_tab": "Importa o esporta dati",
@@ -66,7 +77,7 @@
         "delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione del tuo profilo.",
         "export_theme": "Salva impostazioni",
         "follow_export": "Esporta la lista di chi segui",
-        "follow_export_button": "Esporta la lista di chi segui in un file csv",
+        "follow_export_button": "Esporta la lista di chi segui in un file CSV",
         "follow_export_processing": "Sto elaborando, presto ti sarà chiesto di scaricare il tuo file",
         "follow_import": "Importa la lista di chi segui",
         "follow_import_error": "Errore nell'importazione della lista di chi segui",
@@ -75,7 +86,7 @@
         "general": "Generale",
         "hide_post_stats": "Nascondi statistiche dei messaggi (es. il numero di preferenze)",
         "hide_user_stats": "Nascondi statistiche dell'utente (es. il numero dei tuoi seguaci)",
-        "import_followers_from_a_csv_file": "Importa una lista di chi segui da un file csv",
+        "import_followers_from_a_csv_file": "Importa una lista di chi segui da un file CSV",
         "import_theme": "Carica impostazioni",
         "inputRadius": "Campi di testo",
         "instance_default": "(predefinito: {value})",
@@ -102,7 +113,7 @@
         "pause_on_unfocused": "Interrompi l'aggiornamento continuo mentre la scheda è in secondo piano",
         "presets": "Valori predefiniti",
         "profile_tab": "Profilo",
-        "radii_help": "Imposta il raggio dei bordi (in pixel)",
+        "radii_help": "Imposta il raggio degli angoli (in pixel)",
         "replies_in_timeline": "Risposte nella sequenza personale",
         "reply_visibility_all": "Mostra tutte le risposte",
         "reply_visibility_following": "Mostra solo le risposte rivolte a me o agli utenti che seguo",
@@ -134,7 +145,7 @@
         "follow": "Segui",
         "followees": "Chi stai seguendo",
         "followers": "Seguaci",
-        "following": "Lo stai seguendo!",
+        "following": "Seguìto!",
         "follows_you": "Ti segue!",
         "mute": "Silenzia",
         "muted": "Silenziato",
@@ -153,7 +164,7 @@
         "chat": "Chat",
         "gopher": "Gopher",
         "media_proxy": "Proxy multimedia",
-        "scope_options": "Opzioni raggio d'azione",
+        "scope_options": "Opzioni visibilità",
         "text_limit": "Lunghezza massima",
         "title": "Caratteristiche",
         "who_to_follow": "Chi seguire"
@@ -166,9 +177,10 @@
         "login": "Accedi",
         "logout": "Disconnettiti",
         "password": "Password",
-        "placeholder": "es. lain",
+        "placeholder": "es. Lupo Lucio",
         "register": "Registrati",
-        "username": "Nome utente"
+        "username": "Nome utente",
+        "description": "Accedi con OAuth"
     },
     "post_status": {
         "account_not_locked_warning": "Il tuo profilo non è {0}. Chiunque può seguirti e vedere i tuoi messaggi riservati ai tuoi seguaci.",
@@ -209,12 +221,49 @@
             "keyword": {
                 "reject": "Rifiuta",
                 "replace": "Sostituisci",
-                "is_replaced_by": "→"
+                "is_replaced_by": "→",
+                "keyword_policies": "Regole per parole chiave",
+                "ftl_removal": "Rimozione dalla sequenza globale"
             },
             "simple": {
                 "reject": "Rifiuta",
-                "accept": "Accetta"
-            }
-        }
+                "accept": "Accetta",
+                "simple_policies": "Regole specifiche alla stanza",
+                "accept_desc": "Questa stanza accetta messaggi solo dalle seguenti stanze:",
+                "reject_desc": "Questa stanza non accetterà messaggi dalle stanze seguenti:",
+                "quarantine": "Quarantena",
+                "quarantine_desc": "Questa stanza inoltrerà solo messaggi pubblici alle seguenti stanze:",
+                "ftl_removal": "Rimozione dalla sequenza globale",
+                "ftl_removal_desc": "Questa stanza rimuove le seguenti stanze dalla sequenza globale:",
+                "media_removal": "Rimozione multimedia",
+                "media_removal_desc": "Questa istanza rimuove gli allegati dalle seguenti stanze:",
+                "media_nsfw": "Allegati oscurati forzatamente",
+                "media_nsfw_desc": "Questa stanza oscura gli allegati dei messaggi provenienti da queste stanze:"
+            },
+            "mrf_policies": "Regole RM abilitate",
+            "mrf_policies_desc": "Le regole RM cambiano il comportamento federativo della stanza. Vigono le seguenti regole:"
+        },
+        "staff": "Equipaggio"
+    },
+    "domain_mute_card": {
+        "mute": "Zittisci",
+        "mute_progress": "Zittisco...",
+        "unmute": "Ascolta",
+        "unmute_progress": "Procedo..."
+    },
+    "exporter": {
+        "export": "Esporta",
+        "processing": "In elaborazione, il tuo file sarà scaricabile a breve"
+    },
+    "image_cropper": {
+        "crop_picture": "Ritaglia immagine",
+        "save": "Salva",
+        "save_without_cropping": "Salva senza ritagliare",
+        "cancel": "Annulla"
+    },
+    "importer": {
+        "submit": "Invia",
+        "success": "Importato.",
+        "error": "L'importazione non è andata a buon fine."
     }
 }

From 8db7cba9e16cbacf7ae180e90c9b6bacbe277345 Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Thu, 14 May 2020 11:09:26 +0000
Subject: [PATCH 363/483] Translated using Weblate (English)

Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/en/
---
 src/i18n/en.json | 1470 +++++++++++++++++++++++-----------------------
 1 file changed, 735 insertions(+), 735 deletions(-)

diff --git a/src/i18n/en.json b/src/i18n/en.json
index d5748719..4ae81bfa 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1,743 +1,743 @@
 {
-  "about": {
-    "mrf": {
-      "federation": "Federation",
-      "keyword": {
-        "keyword_policies": "Keyword Policies",
-        "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
-        "reject": "Reject",
-        "replace": "Replace",
-        "is_replaced_by": "→"
-      },
-      "mrf_policies": "Enabled MRF Policies",
-      "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance.  The following policies are enabled:",
-      "simple": {
-        "simple_policies": "Instance-specific Policies",
-        "accept": "Accept",
-        "accept_desc": "This instance only accepts messages from the following instances:",
-        "reject": "Reject",
-        "reject_desc": "This instance will not accept messages from the following instances:",
-        "quarantine": "Quarantine",
-        "quarantine_desc": "This instance will send only public posts to the following instances:",
-        "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
-        "ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
-        "media_removal": "Media Removal",
-        "media_removal_desc": "This instance removes media from posts on the following instances:",
-        "media_nsfw": "Media Force-set As Sensitive",
-        "media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
-      }
-    },
-    "staff": "Staff"
-  },
-  "chat": {
-    "title": "Chat"
-  },
-  "domain_mute_card": {
-    "mute": "Mute",
-    "mute_progress": "Muting...",
-    "unmute": "Unmute",
-    "unmute_progress": "Unmuting..."
-  },
-  "exporter": {
-    "export": "Export",
-    "processing": "Processing, you'll soon be asked to download your file"
-  },
-  "features_panel": {
-    "chat": "Chat",
-    "gopher": "Gopher",
-    "media_proxy": "Media proxy",
-    "scope_options": "Scope options",
-    "text_limit": "Text limit",
-    "title": "Features",
-    "who_to_follow": "Who to follow"
-  },
-  "finder": {
-    "error_fetching_user": "Error fetching user",
-    "find_user": "Find user"
-  },
-  "general": {
-    "apply": "Apply",
-    "submit": "Submit",
-    "more": "More",
-    "generic_error": "An error occured",
-    "optional": "optional",
-    "show_more": "Show more",
-    "show_less": "Show less",
-    "dismiss": "Dismiss",
-    "cancel": "Cancel",
-    "disable": "Disable",
-    "enable": "Enable",
-    "confirm": "Confirm",
-    "verify": "Verify"
-  },
-  "image_cropper": {
-    "crop_picture": "Crop picture",
-    "save": "Save",
-    "save_without_cropping": "Save without cropping",
-    "cancel": "Cancel"
-  },
-  "importer": {
-    "submit": "Submit",
-    "success": "Imported successfully.",
-    "error": "An error occured while importing this file."
-  },
-  "login": {
-    "login": "Log in",
-    "description": "Log in with OAuth",
-    "logout": "Log out",
-    "password": "Password",
-    "placeholder": "e.g. lain",
-    "register": "Register",
-    "username": "Username",
-    "hint": "Log in to join the discussion",
-    "authentication_code": "Authentication code",
-    "enter_recovery_code": "Enter a recovery code",
-    "enter_two_factor_code": "Enter a two-factor code",
-    "recovery_code": "Recovery code",
-    "heading" : {
-      "totp" : "Two-factor authentication",
-      "recovery" : "Two-factor recovery"
-    }
-  },
-  "media_modal": {
-    "previous": "Previous",
-    "next": "Next"
-  },
-  "nav": {
-    "about": "About",
-    "administration": "Administration",
-    "back": "Back",
-    "chat": "Local Chat",
-    "friend_requests": "Follow Requests",
-    "mentions": "Mentions",
-    "interactions": "Interactions",
-    "dms": "Direct Messages",
-    "public_tl": "Public Timeline",
-    "timeline": "Timeline",
-    "twkn": "The Whole Known Network",
-    "user_search": "User Search",
-    "search": "Search",
-    "who_to_follow": "Who to follow",
-    "preferences": "Preferences"
-  },
-  "notifications": {
-    "broken_favorite": "Unknown status, searching for it...",
-    "favorited_you": "favorited your status",
-    "followed_you": "followed you",
-    "follow_request": "wants to follow you",
-    "load_older": "Load older notifications",
-    "notifications": "Notifications",
-    "read": "Read!",
-    "repeated_you": "repeated your status",
-    "no_more_notifications": "No more notifications",
-    "migrated_to": "migrated to",
-    "reacted_with": "reacted with {0}"
-  },
-  "polls": {
-    "add_poll": "Add Poll",
-    "add_option": "Add Option",
-    "option": "Option",
-    "votes": "votes",
-    "vote": "Vote",
-    "type": "Poll type",
-    "single_choice": "Single choice",
-    "multiple_choices": "Multiple choices",
-    "expiry": "Poll age",
-    "expires_in": "Poll ends in {0}",
-    "expired": "Poll ended {0} ago",
-    "not_enough_options": "Too few unique options in poll"
-  },
-  "emoji": {
-    "stickers": "Stickers",
-    "emoji": "Emoji",
-    "keep_open": "Keep picker open",
-    "search_emoji": "Search for an emoji",
-    "add_emoji": "Insert emoji",
-    "custom": "Custom emoji",
-    "unicode": "Unicode emoji",
-    "load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
-    "load_all": "Loading all {emojiAmount} emoji"
-  },
-  "interactions": {
-    "favs_repeats": "Repeats and Favorites",
-    "follows": "New follows",
-    "moves": "User migrates",
-    "load_older": "Load older interactions"
-  },
-  "post_status": {
-    "new_status": "Post new status",
-    "account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
-    "account_not_locked_warning_link": "locked",
-    "attachments_sensitive": "Mark attachments as sensitive",
-    "content_type": {
-      "text/plain": "Plain text",
-      "text/html": "HTML",
-      "text/markdown": "Markdown",
-      "text/bbcode": "BBCode"
-    },
-    "content_warning": "Subject (optional)",
-    "default": "Just landed in L.A.",
-    "direct_warning_to_all": "This post will be visible to all the mentioned users.",
-    "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
-    "posting": "Posting",
-    "scope_notice": {
-      "public": "This post will be visible to everyone",
-      "private": "This post will be visible to your followers only",
-      "unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
-    },
-    "scope": {
-      "direct": "Direct - Post to mentioned users only",
-      "private": "Followers-only - Post to followers only",
-      "public": "Public - Post to public timelines",
-      "unlisted": "Unlisted - Do not post to public timelines"
-    }
-  },
-  "registration": {
-    "bio": "Bio",
-    "email": "Email",
-    "fullname": "Display name",
-    "password_confirm": "Password confirmation",
-    "registration": "Registration",
-    "token": "Invite token",
-    "captcha": "CAPTCHA",
-    "new_captcha": "Click the image to get a new captcha",
-    "username_placeholder": "e.g. lain",
-    "fullname_placeholder": "e.g. Lain Iwakura",
-    "bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
-    "validations": {
-      "username_required": "cannot be left blank",
-      "fullname_required": "cannot be left blank",
-      "email_required": "cannot be left blank",
-      "password_required": "cannot be left blank",
-      "password_confirmation_required": "cannot be left blank",
-      "password_confirmation_match": "should be the same as password"
-    }
-  },
-  "remote_user_resolver": {
-    "remote_user_resolver": "Remote user resolver",
-    "searching_for": "Searching for",
-    "error": "Not found."
-  },
-  "selectable_list": {
-    "select_all": "Select all"
-  },
-  "settings": {
-    "app_name": "App name",
-    "security": "Security",
-    "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
-    "mfa": {
-      "otp" : "OTP",
-      "setup_otp" : "Setup OTP",
-      "wait_pre_setup_otp" : "presetting OTP",
-      "confirm_and_enable" : "Confirm & enable OTP",
-      "title": "Two-factor Authentication",
-      "generate_new_recovery_codes" : "Generate new recovery codes",
-      "warning_of_generate_new_codes" : "When you generate new recovery codes, your old codes won’t work anymore.",
-      "recovery_codes" : "Recovery codes.",
-      "waiting_a_recovery_codes": "Receiving backup codes...",
-      "recovery_codes_warning" : "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.",
-      "authentication_methods" : "Authentication methods",
-      "scan": {
-        "title": "Scan",
-        "desc": "Using your two-factor app, scan this QR code or enter text key:",
-        "secret_code": "Key"
-      },
-      "verify": {
-        "desc": "To enable two-factor authentication, enter the code from your two-factor app:"
-      }
-    },
-    "allow_following_move": "Allow auto-follow when following account moves",
-    "attachmentRadius": "Attachments",
-    "attachments": "Attachments",
-    "autoload": "Enable automatic loading when scrolled to the bottom",
-    "avatar": "Avatar",
-    "avatarAltRadius": "Avatars (Notifications)",
-    "avatarRadius": "Avatars",
-    "background": "Background",
-    "bio": "Bio",
-    "block_export": "Block export",
-    "block_export_button": "Export your blocks to a csv file",
-    "block_import": "Block import",
-    "block_import_error": "Error importing blocks",
-    "blocks_imported": "Blocks imported! Processing them will take a while.",
-    "blocks_tab": "Blocks",
-    "btnRadius": "Buttons",
-    "cBlue": "Blue (Reply, follow)",
-    "cGreen": "Green (Retweet)",
-    "cOrange": "Orange (Favorite)",
-    "cRed": "Red (Cancel)",
-    "change_email": "Change Email",
-    "change_email_error": "There was an issue changing your email.",
-    "changed_email": "Email changed successfully!",
-    "change_password": "Change Password",
-    "change_password_error": "There was an issue changing your password.",
-    "changed_password": "Password changed successfully!",
-    "collapse_subject": "Collapse posts with subjects",
-    "composing": "Composing",
-    "confirm_new_password": "Confirm new password",
-    "current_avatar": "Your current avatar",
-    "current_password": "Current password",
-    "current_profile_banner": "Your current profile banner",
-    "data_import_export_tab": "Data Import / Export",
-    "default_vis": "Default visibility scope",
-    "delete_account": "Delete Account",
-    "delete_account_description": "Permanently delete your account and all your messages.",
-    "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
-    "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
-    "discoverable": "Allow discovery of this account in search results and other services",
-    "domain_mutes": "Domains",
-    "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
-    "pad_emoji": "Pad emoji with spaces when adding from picker",
-    "emoji_reactions_on_timeline": "Show emoji reactions on timeline",
-    "export_theme": "Save preset",
-    "filtering": "Filtering",
-    "filtering_explanation": "All statuses containing these words will be muted, one per line",
-    "follow_export": "Follow export",
-    "follow_export_button": "Export your follows to a csv file",
-    "follow_import": "Follow import",
-    "follow_import_error": "Error importing followers",
-    "follows_imported": "Follows imported! Processing them will take a while.",
-    "accent": "Accent",
-    "foreground": "Foreground",
-    "general": "General",
-    "hide_attachments_in_convo": "Hide attachments in conversations",
-    "hide_attachments_in_tl": "Hide attachments in timeline",
-    "hide_muted_posts": "Hide posts of muted users",
-    "max_thumbnails": "Maximum amount of thumbnails per post",
-    "hide_isp": "Hide instance-specific panel",
-    "preload_images": "Preload images",
-    "use_one_click_nsfw": "Open NSFW attachments with just one click",
-    "hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
-    "hide_user_stats": "Hide user statistics (e.g. the number of followers)",
-    "hide_filtered_statuses": "Hide filtered statuses",
-    "import_blocks_from_a_csv_file": "Import blocks from a csv file",
-    "import_followers_from_a_csv_file": "Import follows from a csv file",
-    "import_theme": "Load preset",
-    "inputRadius": "Input fields",
-    "checkboxRadius": "Checkboxes",
-    "instance_default": "(default: {value})",
-    "instance_default_simple": "(default)",
-    "interface": "Interface",
-    "interfaceLanguage": "Interface language",
-    "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
-    "limited_availability": "Unavailable in your browser",
-    "links": "Links",
-    "lock_account_description": "Restrict your account to approved followers only",
-    "loop_video": "Loop videos",
-    "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
-    "mutes_tab": "Mutes",
-    "play_videos_in_modal": "Play videos in a popup frame",
-    "use_contain_fit": "Don't crop the attachment in thumbnails",
-    "name": "Name",
-    "name_bio": "Name & Bio",
-    "new_email": "New Email",
-    "new_password": "New password",
-    "notification_visibility": "Types of notifications to show",
-    "notification_visibility_follows": "Follows",
-    "notification_visibility_likes": "Likes",
-    "notification_visibility_mentions": "Mentions",
-    "notification_visibility_repeats": "Repeats",
-    "notification_visibility_moves": "User Migrates",
-    "notification_visibility_emoji_reactions": "Reactions",
-    "no_rich_text_description": "Strip rich text formatting from all posts",
-    "no_blocks": "No blocks",
-    "no_mutes": "No mutes",
-    "hide_follows_description": "Don't show who I'm following",
-    "hide_followers_description": "Don't show who's following me",
-    "hide_follows_count_description": "Don't show follow count",
-    "hide_followers_count_description": "Don't show follower count",
-    "show_admin_badge": "Show Admin badge in my profile",
-    "show_moderator_badge": "Show Moderator badge in my profile",
-    "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
-    "oauth_tokens": "OAuth tokens",
-    "token": "Token",
-    "refresh_token": "Refresh Token",
-    "valid_until": "Valid Until",
-    "revoke_token": "Revoke",
-    "panelRadius": "Panels",
-    "pause_on_unfocused": "Pause streaming when tab is not focused",
-    "presets": "Presets",
-    "profile_background": "Profile Background",
-    "profile_banner": "Profile Banner",
-    "profile_tab": "Profile",
-    "radii_help": "Set up interface edge rounding (in pixels)",
-    "replies_in_timeline": "Replies in timeline",
-    "reply_link_preview": "Enable reply-link preview on mouse hover",
-    "reply_visibility_all": "Show all replies",
-    "reply_visibility_following": "Only show replies directed at me or users I'm following",
-    "reply_visibility_self": "Only show replies directed at me",
-    "autohide_floating_post_button": "Automatically hide New Post button (mobile)",
-    "saving_err": "Error saving settings",
-    "saving_ok": "Settings saved",
-    "search_user_to_block": "Search whom you want to block",
-    "search_user_to_mute": "Search whom you want to mute",
-    "security_tab": "Security",
-    "scope_copy": "Copy scope when replying (DMs are always copied)",
-    "minimal_scopes_mode": "Minimize post scope selection options",
-    "set_new_avatar": "Set new avatar",
-    "set_new_profile_background": "Set new profile background",
-    "set_new_profile_banner": "Set new profile banner",
-    "settings": "Settings",
-    "subject_input_always_show": "Always show subject field",
-    "subject_line_behavior": "Copy subject when replying",
-    "subject_line_email": "Like email: \"re: subject\"",
-    "subject_line_mastodon": "Like mastodon: copy as is",
-    "subject_line_noop": "Do not copy",
-    "post_status_content_type": "Post status content type",
-    "stop_gifs": "Play-on-hover GIFs",
-    "streaming": "Enable automatic streaming of new posts when scrolled to the top",
-    "user_mutes": "Users",
-    "useStreamingApi": "Receive posts and notifications real-time",
-    "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
-    "text": "Text",
-    "theme": "Theme",
-    "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
-    "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
-    "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
-    "tooltipRadius": "Tooltips/alerts",
-    "type_domains_to_mute": "Type in domains to mute",
-    "upload_a_photo": "Upload a photo",
-    "user_settings": "User Settings",
-    "values": {
-      "false": "no",
-      "true": "yes"
-    },
-    "fun": "Fun",
-    "greentext": "Meme arrows",
-    "notifications": "Notifications",
-    "notification_setting_filters": "Filters",
-    "notification_setting": "Receive notifications from:",
-    "notification_setting_follows": "Users you follow",
-    "notification_setting_non_follows": "Users you do not follow",
-    "notification_setting_followers": "Users who follow you",
-    "notification_setting_non_followers": "Users who do not follow you",
-    "notification_setting_privacy": "Privacy",
-    "notification_setting_privacy_option": "Hide the sender and contents of push notifications",
-    "notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
-    "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
-    "enable_web_push_notifications": "Enable web push notifications",
-    "style": {
-      "switcher": {
-        "keep_color": "Keep colors",
-        "keep_shadows": "Keep shadows",
-        "keep_opacity": "Keep opacity",
-        "keep_roundness": "Keep roundness",
-        "keep_fonts": "Keep fonts",
-        "save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
-        "reset": "Reset",
-        "clear_all": "Clear all",
-        "clear_opacity": "Clear opacity",
-        "load_theme": "Load theme",
-        "keep_as_is": "Keep as is",
-        "use_snapshot": "Old version",
-        "use_source": "New version",
-        "help": {
-          "upgraded_from_v2": "PleromaFE has been upgraded, theme could look a little bit different than you remember.",
-          "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsitencies.",
-          "future_version_imported": "File you imported was made in newer version of FE.",
-          "older_version_imported": "File you imported was made in older version of FE.",
-          "snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.",
-          "snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.",
-          "fe_upgraded": "PleromaFE's theme engine upgraded after version update.",
-          "fe_downgraded": "PleromaFE's version rolled back.",
-          "migration_snapshot_ok": "Just to be safe, theme snapshot loaded. You can try loading theme data.",
-          "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember.",
-          "snapshot_source_mismatch": "Versions conflict: most likely FE was rolled back and updated again, if you changed theme using older version of FE you most likely want to use old version, otherwise use new version."
-        }
-      },
-      "common": {
-        "color": "Color",
-        "opacity": "Opacity",
-        "contrast": {
-          "hint": "Contrast ratio is {ratio}, it {level} {context}",
-          "level": {
-            "aa": "meets Level AA guideline (minimal)",
-            "aaa": "meets Level AAA guideline (recommended)",
-            "bad": "doesn't meet any accessibility guidelines"
-          },
-          "context": {
-            "18pt": "for large (18pt+) text",
-            "text": "for text"
-          }
-        }
-      },
-      "common_colors": {
-        "_tab_label": "Common",
-        "main": "Common colors",
-        "foreground_hint": "See \"Advanced\" tab for more detailed control",
-        "rgbo": "Icons, accents, badges"
-      },
-      "advanced_colors": {
-        "_tab_label": "Advanced",
-        "alert": "Alert background",
-        "alert_error": "Error",
-        "alert_warning": "Warning",
-        "alert_neutral": "Neutral",
-        "post": "Posts/User bios",
-        "badge": "Badge background",
-        "popover": "Tooltips, menus, popovers",
-        "badge_notification": "Notification",
-        "panel_header": "Panel header",
-        "top_bar": "Top bar",
-        "borders": "Borders",
-        "buttons": "Buttons",
-        "inputs": "Input fields",
-        "faint_text": "Faded text",
-        "underlay": "Underlay",
-        "poll": "Poll graph",
-        "icons": "Icons",
-        "highlight": "Highlighted elements",
-        "pressed": "Pressed",
-        "selectedPost": "Selected post",
-        "selectedMenu": "Selected menu item",
-        "disabled": "Disabled",
-        "toggled": "Toggled",
-        "tabs": "Tabs"
-      },
-      "radii": {
-        "_tab_label": "Roundness"
-      },
-      "shadows": {
-        "_tab_label": "Shadow and lighting",
-        "component": "Component",
-        "override": "Override",
-        "shadow_id": "Shadow #{value}",
-        "blur": "Blur",
-        "spread": "Spread",
-        "inset": "Inset",
-        "hintV3": "For shadows you can also use the {0} notation to use other color slot.",
-        "filter_hint": {
-          "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
-          "drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
-          "avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
-          "spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
-          "inset_classic": "Inset shadows will be using {0}"
+    "about": {
+        "mrf": {
+            "federation": "Federation",
+            "keyword": {
+                "keyword_policies": "Keyword Policies",
+                "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+                "reject": "Reject",
+                "replace": "Replace",
+                "is_replaced_by": "→"
+            },
+            "mrf_policies": "Enabled MRF Policies",
+            "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance.  The following policies are enabled:",
+            "simple": {
+                "simple_policies": "Instance-specific Policies",
+                "accept": "Accept",
+                "accept_desc": "This instance only accepts messages from the following instances:",
+                "reject": "Reject",
+                "reject_desc": "This instance will not accept messages from the following instances:",
+                "quarantine": "Quarantine",
+                "quarantine_desc": "This instance will send only public posts to the following instances:",
+                "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+                "ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
+                "media_removal": "Media Removal",
+                "media_removal_desc": "This instance removes media from posts on the following instances:",
+                "media_nsfw": "Media Force-set As Sensitive",
+                "media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
+            }
         },
-        "components": {
-          "panel": "Panel",
-          "panelHeader": "Panel header",
-          "topBar": "Top bar",
-          "avatar": "User avatar (in profile view)",
-          "avatarStatus": "User avatar (in post display)",
-          "popup": "Popups and tooltips",
-          "button": "Button",
-          "buttonHover": "Button (hover)",
-          "buttonPressed": "Button (pressed)",
-          "buttonPressedHover": "Button (pressed+hover)",
-          "input": "Input field"
+        "staff": "Staff"
+    },
+    "chat": {
+        "title": "Chat"
+    },
+    "domain_mute_card": {
+        "mute": "Mute",
+        "mute_progress": "Muting...",
+        "unmute": "Unmute",
+        "unmute_progress": "Unmuting..."
+    },
+    "exporter": {
+        "export": "Export",
+        "processing": "Processing, you'll soon be asked to download your file"
+    },
+    "features_panel": {
+        "chat": "Chat",
+        "gopher": "Gopher",
+        "media_proxy": "Media proxy",
+        "scope_options": "Scope options",
+        "text_limit": "Text limit",
+        "title": "Features",
+        "who_to_follow": "Who to follow"
+    },
+    "finder": {
+        "error_fetching_user": "Error fetching user",
+        "find_user": "Find user"
+    },
+    "general": {
+        "apply": "Apply",
+        "submit": "Submit",
+        "more": "More",
+        "generic_error": "An error occured",
+        "optional": "optional",
+        "show_more": "Show more",
+        "show_less": "Show less",
+        "dismiss": "Dismiss",
+        "cancel": "Cancel",
+        "disable": "Disable",
+        "enable": "Enable",
+        "confirm": "Confirm",
+        "verify": "Verify"
+    },
+    "image_cropper": {
+        "crop_picture": "Crop picture",
+        "save": "Save",
+        "save_without_cropping": "Save without cropping",
+        "cancel": "Cancel"
+    },
+    "importer": {
+        "submit": "Submit",
+        "success": "Imported successfully.",
+        "error": "An error occured while importing this file."
+    },
+    "login": {
+        "login": "Log in",
+        "description": "Log in with OAuth",
+        "logout": "Log out",
+        "password": "Password",
+        "placeholder": "e.g. lain",
+        "register": "Register",
+        "username": "Username",
+        "hint": "Log in to join the discussion",
+        "authentication_code": "Authentication code",
+        "enter_recovery_code": "Enter a recovery code",
+        "enter_two_factor_code": "Enter a two-factor code",
+        "recovery_code": "Recovery code",
+        "heading": {
+            "totp": "Two-factor authentication",
+            "recovery": "Two-factor recovery"
         }
-      },
-      "fonts": {
-        "_tab_label": "Fonts",
-        "help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
-        "components": {
-          "interface": "Interface",
-          "input": "Input fields",
-          "post": "Post text",
-          "postCode": "Monospaced text in a post (rich text)"
+    },
+    "media_modal": {
+        "previous": "Previous",
+        "next": "Next"
+    },
+    "nav": {
+        "about": "About",
+        "administration": "Administration",
+        "back": "Back",
+        "chat": "Local Chat",
+        "friend_requests": "Follow Requests",
+        "mentions": "Mentions",
+        "interactions": "Interactions",
+        "dms": "Direct Messages",
+        "public_tl": "Public Timeline",
+        "timeline": "Timeline",
+        "twkn": "The Whole Known Network",
+        "user_search": "User Search",
+        "search": "Search",
+        "who_to_follow": "Who to follow",
+        "preferences": "Preferences"
+    },
+    "notifications": {
+        "broken_favorite": "Unknown status, searching for it...",
+        "favorited_you": "favorited your status",
+        "followed_you": "followed you",
+        "follow_request": "wants to follow you",
+        "load_older": "Load older notifications",
+        "notifications": "Notifications",
+        "read": "Read!",
+        "repeated_you": "repeated your status",
+        "no_more_notifications": "No more notifications",
+        "migrated_to": "migrated to",
+        "reacted_with": "reacted with {0}"
+    },
+    "polls": {
+        "add_poll": "Add Poll",
+        "add_option": "Add Option",
+        "option": "Option",
+        "votes": "votes",
+        "vote": "Vote",
+        "type": "Poll type",
+        "single_choice": "Single choice",
+        "multiple_choices": "Multiple choices",
+        "expiry": "Poll age",
+        "expires_in": "Poll ends in {0}",
+        "expired": "Poll ended {0} ago",
+        "not_enough_options": "Too few unique options in poll"
+    },
+    "emoji": {
+        "stickers": "Stickers",
+        "emoji": "Emoji",
+        "keep_open": "Keep picker open",
+        "search_emoji": "Search for an emoji",
+        "add_emoji": "Insert emoji",
+        "custom": "Custom emoji",
+        "unicode": "Unicode emoji",
+        "load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
+        "load_all": "Loading all {emojiAmount} emoji"
+    },
+    "interactions": {
+        "favs_repeats": "Repeats and Favorites",
+        "follows": "New follows",
+        "moves": "User migrates",
+        "load_older": "Load older interactions"
+    },
+    "post_status": {
+        "new_status": "Post new status",
+        "account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
+        "account_not_locked_warning_link": "locked",
+        "attachments_sensitive": "Mark attachments as sensitive",
+        "content_type": {
+            "text/plain": "Plain text",
+            "text/html": "HTML",
+            "text/markdown": "Markdown",
+            "text/bbcode": "BBCode"
         },
-        "family": "Font name",
-        "size": "Size (in px)",
-        "weight": "Weight (boldness)",
-        "custom": "Custom"
-      },
-      "preview": {
-        "header": "Preview",
-        "content": "Content",
-        "error": "Example error",
-        "button": "Button",
-        "text": "A bunch of more {0} and {1}",
-        "mono": "content",
-        "input": "Just landed in L.A.",
-        "faint_link": "helpful manual",
-        "fine_print": "Read our {0} to learn nothing useful!",
-        "header_faint": "This is fine",
-        "checkbox": "I have skimmed over terms and conditions",
-        "link": "a nice lil' link"
-      }
+        "content_warning": "Subject (optional)",
+        "default": "Just landed in L.A.",
+        "direct_warning_to_all": "This post will be visible to all the mentioned users.",
+        "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
+        "posting": "Posting",
+        "scope_notice": {
+            "public": "This post will be visible to everyone",
+            "private": "This post will be visible to your followers only",
+            "unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
+        },
+        "scope": {
+            "direct": "Direct - Post to mentioned users only",
+            "private": "Followers-only - Post to followers only",
+            "public": "Public - Post to public timelines",
+            "unlisted": "Unlisted - Do not post to public timelines"
+        }
     },
-    "version": {
-      "title": "Version",
-      "backend_version": "Backend Version",
-      "frontend_version": "Frontend Version"
-    }
-  },
-  "time": {
-    "day": "{0} day",
-    "days": "{0} days",
-    "day_short": "{0}d",
-    "days_short": "{0}d",
-    "hour": "{0} hour",
-    "hours": "{0} hours",
-    "hour_short": "{0}h",
-    "hours_short": "{0}h",
-    "in_future": "in {0}",
-    "in_past": "{0} ago",
-    "minute": "{0} minute",
-    "minutes": "{0} minutes",
-    "minute_short": "{0}min",
-    "minutes_short": "{0}min",
-    "month": "{0} month",
-    "months": "{0} months",
-    "month_short": "{0}mo",
-    "months_short": "{0}mo",
-    "now": "just now",
-    "now_short": "now",
-    "second": "{0} second",
-    "seconds": "{0} seconds",
-    "second_short": "{0}s",
-    "seconds_short": "{0}s",
-    "week": "{0} week",
-    "weeks": "{0} weeks",
-    "week_short": "{0}w",
-    "weeks_short": "{0}w",
-    "year": "{0} year",
-    "years": "{0} years",
-    "year_short": "{0}y",
-    "years_short": "{0}y"
-  },
-  "timeline": {
-    "collapse": "Collapse",
-    "conversation": "Conversation",
-    "error_fetching": "Error fetching updates",
-    "load_older": "Load older statuses",
-    "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
-    "repeated": "repeated",
-    "show_new": "Show new",
-    "up_to_date": "Up-to-date",
-    "no_more_statuses": "No more statuses",
-    "no_statuses": "No statuses"
-  },
-  "status": {
-    "favorites": "Favorites",
-    "repeats": "Repeats",
-    "delete": "Delete status",
-    "pin": "Pin on profile",
-    "unpin": "Unpin from profile",
-    "pinned": "Pinned",
-    "delete_confirm": "Do you really want to delete this status?",
-    "reply_to": "Reply to",
-    "replies_list": "Replies:",
-    "mute_conversation": "Mute conversation",
-    "unmute_conversation": "Unmute conversation",
-    "status_unavailable": "Status unavailable",
-    "copy_link": "Copy link to status"
-  },
-  "user_card": {
-    "approve": "Approve",
-    "block": "Block",
-    "blocked": "Blocked!",
-    "deny": "Deny",
-    "favorites": "Favorites",
-    "follow": "Follow",
-    "follow_sent": "Request sent!",
-    "follow_progress": "Requesting…",
-    "follow_again": "Send request again?",
-    "follow_unfollow": "Unfollow",
-    "followees": "Following",
-    "followers": "Followers",
-    "following": "Following!",
-    "follows_you": "Follows you!",
-    "hidden": "Hidden",
-    "its_you": "It's you!",
-    "media": "Media",
-    "mention": "Mention",
-    "mute": "Mute",
-    "muted": "Muted",
-    "per_day": "per day",
-    "remote_follow": "Remote follow",
-    "report": "Report",
-    "statuses": "Statuses",
-    "subscribe": "Subscribe",
-    "unsubscribe": "Unsubscribe",
-    "unblock": "Unblock",
-    "unblock_progress": "Unblocking...",
-    "block_progress": "Blocking...",
-    "unmute": "Unmute",
-    "unmute_progress": "Unmuting...",
-    "mute_progress": "Muting...",
-    "hide_repeats": "Hide repeats",
-    "show_repeats": "Show repeats",
-    "admin_menu": {
-      "moderation": "Moderation",
-      "grant_admin": "Grant Admin",
-      "revoke_admin": "Revoke Admin",
-      "grant_moderator": "Grant Moderator",
-      "revoke_moderator": "Revoke Moderator",
-      "activate_account": "Activate account",
-      "deactivate_account": "Deactivate account",
-      "delete_account": "Delete account",
-      "force_nsfw": "Mark all posts as NSFW",
-      "strip_media": "Remove media from posts",
-      "force_unlisted": "Force posts to be unlisted",
-      "sandbox": "Force posts to be followers-only",
-      "disable_remote_subscription": "Disallow following user from remote instances",
-      "disable_any_subscription": "Disallow following user at all",
-      "quarantine": "Disallow user posts from federating",
-      "delete_user": "Delete user",
-      "delete_user_confirmation": "Are you absolutely sure? This action cannot be undone."
-    }
-  },
-  "user_profile": {
-    "timeline_title": "User Timeline",
-    "profile_does_not_exist": "Sorry, this profile does not exist.",
-    "profile_loading_error": "Sorry, there was an error loading this profile."
-  },
-  "user_reporting": {
-    "title": "Reporting {0}",
-    "add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
-    "additional_comments": "Additional comments",
-    "forward_description": "The account is from another server. Send a copy of the report there as well?",
-    "forward_to": "Forward to {0}",
-    "submit": "Submit",
-    "generic_error": "An error occurred while processing your request."
-  },
-  "who_to_follow": {
-    "more": "More",
-    "who_to_follow": "Who to follow"
-  },
-  "tool_tip": {
-    "media_upload": "Upload Media",
-    "repeat": "Repeat",
-    "reply": "Reply",
-    "favorite": "Favorite",
-    "add_reaction": "Add Reaction",
-    "user_settings": "User Settings",
-    "accept_follow_request": "Accept follow request",
-    "reject_follow_request": "Reject follow request"
-  },
-  "upload":{
-    "error": {
-      "base": "Upload failed.",
-      "file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-      "default": "Try again later"
+    "registration": {
+        "bio": "Bio",
+        "email": "Email",
+        "fullname": "Display name",
+        "password_confirm": "Password confirmation",
+        "registration": "Registration",
+        "token": "Invite token",
+        "captcha": "CAPTCHA",
+        "new_captcha": "Click the image to get a new captcha",
+        "username_placeholder": "e.g. lain",
+        "fullname_placeholder": "e.g. Lain Iwakura",
+        "bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
+        "validations": {
+            "username_required": "cannot be left blank",
+            "fullname_required": "cannot be left blank",
+            "email_required": "cannot be left blank",
+            "password_required": "cannot be left blank",
+            "password_confirmation_required": "cannot be left blank",
+            "password_confirmation_match": "should be the same as password"
+        }
     },
-    "file_size_units": {
-      "B": "B",
-      "KiB": "KiB",
-      "MiB": "MiB",
-      "GiB": "GiB",
-      "TiB": "TiB"
+    "remote_user_resolver": {
+        "remote_user_resolver": "Remote user resolver",
+        "searching_for": "Searching for",
+        "error": "Not found."
+    },
+    "selectable_list": {
+        "select_all": "Select all"
+    },
+    "settings": {
+        "app_name": "App name",
+        "security": "Security",
+        "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
+        "mfa": {
+            "otp": "OTP",
+            "setup_otp": "Setup OTP",
+            "wait_pre_setup_otp": "presetting OTP",
+            "confirm_and_enable": "Confirm & enable OTP",
+            "title": "Two-factor Authentication",
+            "generate_new_recovery_codes": "Generate new recovery codes",
+            "warning_of_generate_new_codes": "When you generate new recovery codes, your old codes won’t work anymore.",
+            "recovery_codes": "Recovery codes.",
+            "waiting_a_recovery_codes": "Receiving backup codes...",
+            "recovery_codes_warning": "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.",
+            "authentication_methods": "Authentication methods",
+            "scan": {
+                "title": "Scan",
+                "desc": "Using your two-factor app, scan this QR code or enter text key:",
+                "secret_code": "Key"
+            },
+            "verify": {
+                "desc": "To enable two-factor authentication, enter the code from your two-factor app:"
+            }
+        },
+        "allow_following_move": "Allow auto-follow when following account moves",
+        "attachmentRadius": "Attachments",
+        "attachments": "Attachments",
+        "autoload": "Enable automatic loading when scrolled to the bottom",
+        "avatar": "Avatar",
+        "avatarAltRadius": "Avatars (Notifications)",
+        "avatarRadius": "Avatars",
+        "background": "Background",
+        "bio": "Bio",
+        "block_export": "Block export",
+        "block_export_button": "Export your blocks to a csv file",
+        "block_import": "Block import",
+        "block_import_error": "Error importing blocks",
+        "blocks_imported": "Blocks imported! Processing them will take a while.",
+        "blocks_tab": "Blocks",
+        "btnRadius": "Buttons",
+        "cBlue": "Blue (Reply, follow)",
+        "cGreen": "Green (Retweet)",
+        "cOrange": "Orange (Favorite)",
+        "cRed": "Red (Cancel)",
+        "change_email": "Change Email",
+        "change_email_error": "There was an issue changing your email.",
+        "changed_email": "Email changed successfully!",
+        "change_password": "Change Password",
+        "change_password_error": "There was an issue changing your password.",
+        "changed_password": "Password changed successfully!",
+        "collapse_subject": "Collapse posts with subjects",
+        "composing": "Composing",
+        "confirm_new_password": "Confirm new password",
+        "current_avatar": "Your current avatar",
+        "current_password": "Current password",
+        "current_profile_banner": "Your current profile banner",
+        "data_import_export_tab": "Data Import / Export",
+        "default_vis": "Default visibility scope",
+        "delete_account": "Delete Account",
+        "delete_account_description": "Permanently delete your account and all your messages.",
+        "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
+        "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
+        "discoverable": "Allow discovery of this account in search results and other services",
+        "domain_mutes": "Domains",
+        "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
+        "pad_emoji": "Pad emoji with spaces when adding from picker",
+        "emoji_reactions_on_timeline": "Show emoji reactions on timeline",
+        "export_theme": "Save preset",
+        "filtering": "Filtering",
+        "filtering_explanation": "All statuses containing these words will be muted, one per line",
+        "follow_export": "Follow export",
+        "follow_export_button": "Export your follows to a csv file",
+        "follow_import": "Follow import",
+        "follow_import_error": "Error importing followers",
+        "follows_imported": "Follows imported! Processing them will take a while.",
+        "accent": "Accent",
+        "foreground": "Foreground",
+        "general": "General",
+        "hide_attachments_in_convo": "Hide attachments in conversations",
+        "hide_attachments_in_tl": "Hide attachments in timeline",
+        "hide_muted_posts": "Hide posts of muted users",
+        "max_thumbnails": "Maximum amount of thumbnails per post",
+        "hide_isp": "Hide instance-specific panel",
+        "preload_images": "Preload images",
+        "use_one_click_nsfw": "Open NSFW attachments with just one click",
+        "hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
+        "hide_user_stats": "Hide user statistics (e.g. the number of followers)",
+        "hide_filtered_statuses": "Hide filtered statuses",
+        "import_blocks_from_a_csv_file": "Import blocks from a csv file",
+        "import_followers_from_a_csv_file": "Import follows from a csv file",
+        "import_theme": "Load preset",
+        "inputRadius": "Input fields",
+        "checkboxRadius": "Checkboxes",
+        "instance_default": "(default: {value})",
+        "instance_default_simple": "(default)",
+        "interface": "Interface",
+        "interfaceLanguage": "Interface language",
+        "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
+        "limited_availability": "Unavailable in your browser",
+        "links": "Links",
+        "lock_account_description": "Restrict your account to approved followers only",
+        "loop_video": "Loop videos",
+        "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
+        "mutes_tab": "Mutes",
+        "play_videos_in_modal": "Play videos in a popup frame",
+        "use_contain_fit": "Don't crop the attachment in thumbnails",
+        "name": "Name",
+        "name_bio": "Name & Bio",
+        "new_email": "New Email",
+        "new_password": "New password",
+        "notification_visibility": "Types of notifications to show",
+        "notification_visibility_follows": "Follows",
+        "notification_visibility_likes": "Likes",
+        "notification_visibility_mentions": "Mentions",
+        "notification_visibility_repeats": "Repeats",
+        "notification_visibility_moves": "User Migrates",
+        "notification_visibility_emoji_reactions": "Reactions",
+        "no_rich_text_description": "Strip rich text formatting from all posts",
+        "no_blocks": "No blocks",
+        "no_mutes": "No mutes",
+        "hide_follows_description": "Don't show who I'm following",
+        "hide_followers_description": "Don't show who's following me",
+        "hide_follows_count_description": "Don't show follow count",
+        "hide_followers_count_description": "Don't show follower count",
+        "show_admin_badge": "Show Admin badge in my profile",
+        "show_moderator_badge": "Show Moderator badge in my profile",
+        "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
+        "oauth_tokens": "OAuth tokens",
+        "token": "Token",
+        "refresh_token": "Refresh Token",
+        "valid_until": "Valid Until",
+        "revoke_token": "Revoke",
+        "panelRadius": "Panels",
+        "pause_on_unfocused": "Pause streaming when tab is not focused",
+        "presets": "Presets",
+        "profile_background": "Profile Background",
+        "profile_banner": "Profile Banner",
+        "profile_tab": "Profile",
+        "radii_help": "Set up interface edge rounding (in pixels)",
+        "replies_in_timeline": "Replies in timeline",
+        "reply_link_preview": "Enable reply-link preview on mouse hover",
+        "reply_visibility_all": "Show all replies",
+        "reply_visibility_following": "Only show replies directed at me or users I'm following",
+        "reply_visibility_self": "Only show replies directed at me",
+        "autohide_floating_post_button": "Automatically hide New Post button (mobile)",
+        "saving_err": "Error saving settings",
+        "saving_ok": "Settings saved",
+        "search_user_to_block": "Search whom you want to block",
+        "search_user_to_mute": "Search whom you want to mute",
+        "security_tab": "Security",
+        "scope_copy": "Copy scope when replying (DMs are always copied)",
+        "minimal_scopes_mode": "Minimize post scope selection options",
+        "set_new_avatar": "Set new avatar",
+        "set_new_profile_background": "Set new profile background",
+        "set_new_profile_banner": "Set new profile banner",
+        "settings": "Settings",
+        "subject_input_always_show": "Always show subject field",
+        "subject_line_behavior": "Copy subject when replying",
+        "subject_line_email": "Like email: \"re: subject\"",
+        "subject_line_mastodon": "Like mastodon: copy as is",
+        "subject_line_noop": "Do not copy",
+        "post_status_content_type": "Post status content type",
+        "stop_gifs": "Play-on-hover GIFs",
+        "streaming": "Enable automatic streaming of new posts when scrolled to the top",
+        "user_mutes": "Users",
+        "useStreamingApi": "Receive posts and notifications real-time",
+        "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
+        "text": "Text",
+        "theme": "Theme",
+        "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
+        "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
+        "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
+        "tooltipRadius": "Tooltips/alerts",
+        "type_domains_to_mute": "Type in domains to mute",
+        "upload_a_photo": "Upload a photo",
+        "user_settings": "User Settings",
+        "values": {
+            "false": "no",
+            "true": "yes"
+        },
+        "fun": "Fun",
+        "greentext": "Meme arrows",
+        "notifications": "Notifications",
+        "notification_setting_filters": "Filters",
+        "notification_setting": "Receive notifications from:",
+        "notification_setting_follows": "Users you follow",
+        "notification_setting_non_follows": "Users you do not follow",
+        "notification_setting_followers": "Users who follow you",
+        "notification_setting_non_followers": "Users who do not follow you",
+        "notification_setting_privacy": "Privacy",
+        "notification_setting_privacy_option": "Hide the sender and contents of push notifications",
+        "notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
+        "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
+        "enable_web_push_notifications": "Enable web push notifications",
+        "style": {
+            "switcher": {
+                "keep_color": "Keep colors",
+                "keep_shadows": "Keep shadows",
+                "keep_opacity": "Keep opacity",
+                "keep_roundness": "Keep roundness",
+                "keep_fonts": "Keep fonts",
+                "save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
+                "reset": "Reset",
+                "clear_all": "Clear all",
+                "clear_opacity": "Clear opacity",
+                "load_theme": "Load theme",
+                "keep_as_is": "Keep as is",
+                "use_snapshot": "Old version",
+                "use_source": "New version",
+                "help": {
+                    "upgraded_from_v2": "PleromaFE has been upgraded, theme could look a little bit different than you remember.",
+                    "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsistencies.",
+                    "future_version_imported": "File you imported was made in newer version of FE.",
+                    "older_version_imported": "File you imported was made in older version of FE.",
+                    "snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.",
+                    "snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.",
+                    "fe_upgraded": "PleromaFE's theme engine upgraded after version update.",
+                    "fe_downgraded": "PleromaFE's version rolled back.",
+                    "migration_snapshot_ok": "Just to be safe, theme snapshot loaded. You can try loading theme data.",
+                    "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember.",
+                    "snapshot_source_mismatch": "Versions conflict: most likely FE was rolled back and updated again, if you changed theme using older version of FE you most likely want to use old version, otherwise use new version."
+                }
+            },
+            "common": {
+                "color": "Color",
+                "opacity": "Opacity",
+                "contrast": {
+                    "hint": "Contrast ratio is {ratio}, it {level} {context}",
+                    "level": {
+                        "aa": "meets Level AA guideline (minimal)",
+                        "aaa": "meets Level AAA guideline (recommended)",
+                        "bad": "doesn't meet any accessibility guidelines"
+                    },
+                    "context": {
+                        "18pt": "for large (18pt+) text",
+                        "text": "for text"
+                    }
+                }
+            },
+            "common_colors": {
+                "_tab_label": "Common",
+                "main": "Common colors",
+                "foreground_hint": "See \"Advanced\" tab for more detailed control",
+                "rgbo": "Icons, accents, badges"
+            },
+            "advanced_colors": {
+                "_tab_label": "Advanced",
+                "alert": "Alert background",
+                "alert_error": "Error",
+                "alert_warning": "Warning",
+                "alert_neutral": "Neutral",
+                "post": "Posts/User bios",
+                "badge": "Badge background",
+                "popover": "Tooltips, menus, popovers",
+                "badge_notification": "Notification",
+                "panel_header": "Panel header",
+                "top_bar": "Top bar",
+                "borders": "Borders",
+                "buttons": "Buttons",
+                "inputs": "Input fields",
+                "faint_text": "Faded text",
+                "underlay": "Underlay",
+                "poll": "Poll graph",
+                "icons": "Icons",
+                "highlight": "Highlighted elements",
+                "pressed": "Pressed",
+                "selectedPost": "Selected post",
+                "selectedMenu": "Selected menu item",
+                "disabled": "Disabled",
+                "toggled": "Toggled",
+                "tabs": "Tabs"
+            },
+            "radii": {
+                "_tab_label": "Roundness"
+            },
+            "shadows": {
+                "_tab_label": "Shadow and lighting",
+                "component": "Component",
+                "override": "Override",
+                "shadow_id": "Shadow #{value}",
+                "blur": "Blur",
+                "spread": "Spread",
+                "inset": "Inset",
+                "hintV3": "For shadows you can also use the {0} notation to use other color slot.",
+                "filter_hint": {
+                    "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
+                    "drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
+                    "avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
+                    "spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
+                    "inset_classic": "Inset shadows will be using {0}"
+                },
+                "components": {
+                    "panel": "Panel",
+                    "panelHeader": "Panel header",
+                    "topBar": "Top bar",
+                    "avatar": "User avatar (in profile view)",
+                    "avatarStatus": "User avatar (in post display)",
+                    "popup": "Popups and tooltips",
+                    "button": "Button",
+                    "buttonHover": "Button (hover)",
+                    "buttonPressed": "Button (pressed)",
+                    "buttonPressedHover": "Button (pressed+hover)",
+                    "input": "Input field"
+                }
+            },
+            "fonts": {
+                "_tab_label": "Fonts",
+                "help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
+                "components": {
+                    "interface": "Interface",
+                    "input": "Input fields",
+                    "post": "Post text",
+                    "postCode": "Monospaced text in a post (rich text)"
+                },
+                "family": "Font name",
+                "size": "Size (in px)",
+                "weight": "Weight (boldness)",
+                "custom": "Custom"
+            },
+            "preview": {
+                "header": "Preview",
+                "content": "Content",
+                "error": "Example error",
+                "button": "Button",
+                "text": "A bunch of more {0} and {1}",
+                "mono": "content",
+                "input": "Just landed in L.A.",
+                "faint_link": "helpful manual",
+                "fine_print": "Read our {0} to learn nothing useful!",
+                "header_faint": "This is fine",
+                "checkbox": "I have skimmed over terms and conditions",
+                "link": "a nice lil' link"
+            }
+        },
+        "version": {
+            "title": "Version",
+            "backend_version": "Backend Version",
+            "frontend_version": "Frontend Version"
+        }
+    },
+    "time": {
+        "day": "{0} day",
+        "days": "{0} days",
+        "day_short": "{0}d",
+        "days_short": "{0}d",
+        "hour": "{0} hour",
+        "hours": "{0} hours",
+        "hour_short": "{0}h",
+        "hours_short": "{0}h",
+        "in_future": "in {0}",
+        "in_past": "{0} ago",
+        "minute": "{0} minute",
+        "minutes": "{0} minutes",
+        "minute_short": "{0}min",
+        "minutes_short": "{0}min",
+        "month": "{0} month",
+        "months": "{0} months",
+        "month_short": "{0}mo",
+        "months_short": "{0}mo",
+        "now": "just now",
+        "now_short": "now",
+        "second": "{0} second",
+        "seconds": "{0} seconds",
+        "second_short": "{0}s",
+        "seconds_short": "{0}s",
+        "week": "{0} week",
+        "weeks": "{0} weeks",
+        "week_short": "{0}w",
+        "weeks_short": "{0}w",
+        "year": "{0} year",
+        "years": "{0} years",
+        "year_short": "{0}y",
+        "years_short": "{0}y"
+    },
+    "timeline": {
+        "collapse": "Collapse",
+        "conversation": "Conversation",
+        "error_fetching": "Error fetching updates",
+        "load_older": "Load older statuses",
+        "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
+        "repeated": "repeated",
+        "show_new": "Show new",
+        "up_to_date": "Up-to-date",
+        "no_more_statuses": "No more statuses",
+        "no_statuses": "No statuses"
+    },
+    "status": {
+        "favorites": "Favorites",
+        "repeats": "Repeats",
+        "delete": "Delete status",
+        "pin": "Pin on profile",
+        "unpin": "Unpin from profile",
+        "pinned": "Pinned",
+        "delete_confirm": "Do you really want to delete this status?",
+        "reply_to": "Reply to",
+        "replies_list": "Replies:",
+        "mute_conversation": "Mute conversation",
+        "unmute_conversation": "Unmute conversation",
+        "status_unavailable": "Status unavailable",
+        "copy_link": "Copy link to status"
+    },
+    "user_card": {
+        "approve": "Approve",
+        "block": "Block",
+        "blocked": "Blocked!",
+        "deny": "Deny",
+        "favorites": "Favorites",
+        "follow": "Follow",
+        "follow_sent": "Request sent!",
+        "follow_progress": "Requesting…",
+        "follow_again": "Send request again?",
+        "follow_unfollow": "Unfollow",
+        "followees": "Following",
+        "followers": "Followers",
+        "following": "Following!",
+        "follows_you": "Follows you!",
+        "hidden": "Hidden",
+        "its_you": "It's you!",
+        "media": "Media",
+        "mention": "Mention",
+        "mute": "Mute",
+        "muted": "Muted",
+        "per_day": "per day",
+        "remote_follow": "Remote follow",
+        "report": "Report",
+        "statuses": "Statuses",
+        "subscribe": "Subscribe",
+        "unsubscribe": "Unsubscribe",
+        "unblock": "Unblock",
+        "unblock_progress": "Unblocking...",
+        "block_progress": "Blocking...",
+        "unmute": "Unmute",
+        "unmute_progress": "Unmuting...",
+        "mute_progress": "Muting...",
+        "hide_repeats": "Hide repeats",
+        "show_repeats": "Show repeats",
+        "admin_menu": {
+            "moderation": "Moderation",
+            "grant_admin": "Grant Admin",
+            "revoke_admin": "Revoke Admin",
+            "grant_moderator": "Grant Moderator",
+            "revoke_moderator": "Revoke Moderator",
+            "activate_account": "Activate account",
+            "deactivate_account": "Deactivate account",
+            "delete_account": "Delete account",
+            "force_nsfw": "Mark all posts as NSFW",
+            "strip_media": "Remove media from posts",
+            "force_unlisted": "Force posts to be unlisted",
+            "sandbox": "Force posts to be followers-only",
+            "disable_remote_subscription": "Disallow following user from remote instances",
+            "disable_any_subscription": "Disallow following user at all",
+            "quarantine": "Disallow user posts from federating",
+            "delete_user": "Delete user",
+            "delete_user_confirmation": "Are you absolutely sure? This action cannot be undone."
+        }
+    },
+    "user_profile": {
+        "timeline_title": "User Timeline",
+        "profile_does_not_exist": "Sorry, this profile does not exist.",
+        "profile_loading_error": "Sorry, there was an error loading this profile."
+    },
+    "user_reporting": {
+        "title": "Reporting {0}",
+        "add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+        "additional_comments": "Additional comments",
+        "forward_description": "The account is from another server. Send a copy of the report there as well?",
+        "forward_to": "Forward to {0}",
+        "submit": "Submit",
+        "generic_error": "An error occurred while processing your request."
+    },
+    "who_to_follow": {
+        "more": "More",
+        "who_to_follow": "Who to follow"
+    },
+    "tool_tip": {
+        "media_upload": "Upload Media",
+        "repeat": "Repeat",
+        "reply": "Reply",
+        "favorite": "Favorite",
+        "add_reaction": "Add Reaction",
+        "user_settings": "User Settings",
+        "accept_follow_request": "Accept follow request",
+        "reject_follow_request": "Reject follow request"
+    },
+    "upload": {
+        "error": {
+            "base": "Upload failed.",
+            "file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+            "default": "Try again later"
+        },
+        "file_size_units": {
+            "B": "B",
+            "KiB": "KiB",
+            "MiB": "MiB",
+            "GiB": "GiB",
+            "TiB": "TiB"
+        }
+    },
+    "search": {
+        "people": "People",
+        "hashtags": "Hashtags",
+        "person_talking": "{count} person talking",
+        "people_talking": "{count} people talking",
+        "no_results": "No results"
+    },
+    "password_reset": {
+        "forgot_password": "Forgot password?",
+        "password_reset": "Password reset",
+        "instruction": "Enter your email address or username. We will send you a link to reset your password.",
+        "placeholder": "Your email or username",
+        "check_email": "Check your email for a link to reset your password.",
+        "return_home": "Return to the home page",
+        "not_found": "We couldn't find that email or username.",
+        "too_many_requests": "You have reached the limit of attempts, try again later.",
+        "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.",
+        "password_reset_required": "You must reset your password to log in.",
+        "password_reset_required_but_mailer_is_disabled": "You must reset your password, but password reset is disabled. Please contact your instance administrator."
     }
-  },
-  "search": {
-    "people": "People",
-    "hashtags": "Hashtags",
-    "person_talking": "{count} person talking",
-    "people_talking": "{count} people talking",
-    "no_results": "No results"
-  },
-  "password_reset": {
-    "forgot_password": "Forgot password?",
-    "password_reset": "Password reset",
-    "instruction": "Enter your email address or username. We will send you a link to reset your password.",
-    "placeholder": "Your email or username",
-    "check_email": "Check your email for a link to reset your password.",
-    "return_home": "Return to the home page",
-    "not_found": "We couldn't find that email or username.",
-    "too_many_requests": "You have reached the limit of attempts, try again later.",
-    "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.",
-    "password_reset_required": "You must reset your password to log in.",
-    "password_reset_required_but_mailer_is_disabled": "You must reset your password, but password reset is disabled. Please contact your instance administrator."
-  }
 }

From 4aaa3f87d8ed375d3ec20ad6d9867357f0e6a95c Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Wed, 20 May 2020 12:16:43 -0500
Subject: [PATCH 364/483] Document the sidebarRight feature

---
 docs/CONFIGURATION.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 39a2840d..14b0428f 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -73,6 +73,9 @@ These two settings should point to where FE should redirect visitor when they lo
 ### `scopeCopy`
 Copy post scope (visibility) when replying to a post. Instance-default.
 
+### `sidebarRight`
+Change alignment of sidebar and panels to the right. Defaults to `false`.
+
 ### `showFeaturesPanel`
 Show panel showcasing instance features/settings to logged-out visitors
 

From 3cd6bc9cfb7ed29c97c3a5cdc4a35e9808a47e2e Mon Sep 17 00:00:00 2001
From: Fristi <fristi@subcon.town>
Date: Thu, 14 May 2020 19:38:31 +0000
Subject: [PATCH 365/483] Translated using Weblate (Dutch)

Currently translated at 77.9% (478 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
---
 src/i18n/nl.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index bac7e62c..9be89109 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -580,7 +580,8 @@
         "unicode": "Unicode emoji",
         "load_all": "Alle {emojiAmount} emoji worden geladen",
         "stickers": "Stickers",
-        "load_all_hint": "Eerste {saneAmount} emoji geladen, alle emoji tegelijk laden kan prestatie problemen veroorzaken."
+        "load_all_hint": "Eerste {saneAmount} emoji geladen, alle emoji tegelijk laden kan prestatie problemen veroorzaken.",
+        "custom": "Gepersonaliseerde emoji"
     },
     "interactions": {
         "favs_repeats": "Herhalingen en Favorieten",

From 7f7919bf6ffd58fc0ed312831f25dfc5f39f18ca Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Thu, 14 May 2020 18:17:30 +0000
Subject: [PATCH 366/483] Translated using Weblate (Italian)

Currently translated at 36.2% (222 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index 6fc2be74..5554620c 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -180,7 +180,16 @@
         "placeholder": "es. Lupo Lucio",
         "register": "Registrati",
         "username": "Nome utente",
-        "description": "Accedi con OAuth"
+        "description": "Accedi con OAuth",
+        "hint": "Accedi per partecipare alla discussione",
+        "authentication_code": "Codice di autenticazione",
+        "enter_recovery_code": "Inserisci un codice di recupero",
+        "enter_two_factor_code": "Inserisci un codice two-factor",
+        "recovery_code": "Codice di recupero",
+        "heading": {
+            "totp": "Autenticazione two-factor",
+            "recovery": "Recupero two-factor"
+        }
     },
     "post_status": {
         "account_not_locked_warning": "Il tuo profilo non è {0}. Chiunque può seguirti e vedere i tuoi messaggi riservati ai tuoi seguaci.",

From a62d504fabe10b8443203c84a6d7de4909dc101d Mon Sep 17 00:00:00 2001
From: Fristi <fristi@subcon.town>
Date: Thu, 14 May 2020 19:51:25 +0000
Subject: [PATCH 367/483] Translated using Weblate (Dutch)

Currently translated at 78.1% (479 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
---
 src/i18n/nl.json | 182 +++++++++++++++++++++++------------------------
 1 file changed, 91 insertions(+), 91 deletions(-)

diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index 9be89109..15216244 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -90,20 +90,20 @@
             "text/bbcode": "BBCode"
         },
         "content_warning": "Onderwerp (optioneel)",
-        "default": "Tijd voor een pauze!",
+        "default": "Zojuist geland in L.A.",
         "direct_warning": "Deze post zal enkel zichtbaar zijn voor de personen die genoemd zijn.",
         "posting": "Plaatsen",
         "scope": {
-            "direct": "Direct - Post enkel naar genoemde gebruikers",
+            "direct": "Direct - Post enkel naar vermelde gebruikers",
             "private": "Enkel volgers - Post enkel naar volgers",
             "public": "Publiek - Post op publieke tijdlijnen",
-            "unlisted": "Unlisted - Toon niet op publieke tijdlijnen"
+            "unlisted": "Niet Vermelden - Niet tonen op publieke tijdlijnen"
         },
         "direct_warning_to_all": "Dit bericht zal zichtbaar zijn voor alle vermelde gebruikers.",
         "direct_warning_to_first_only": "Dit bericht zal alleen zichtbaar zijn voor de vermelde gebruikers aan het begin van het bericht.",
         "scope_notice": {
             "public": "Dit bericht zal voor iedereen zichtbaar zijn",
-            "unlisted": "Dit bericht zal niet zichtbaar zijn in de Publieke Tijdlijn en Het Geheel Gekende Netwerk",
+            "unlisted": "Dit bericht zal niet zichtbaar zijn in de Publieke Tijdlijn en Het Geheel Bekende Netwerk",
             "private": "Dit bericht zal voor alleen je volgers zichtbaar zijn"
         }
     },
@@ -113,7 +113,7 @@
         "fullname": "Weergave naam",
         "password_confirm": "Wachtwoord bevestiging",
         "registration": "Registratie",
-        "token": "Uitnodigingstoken",
+        "token": "Uitnodigings-token",
         "captcha": "CAPTCHA",
         "new_captcha": "Klik op de afbeelding voor een nieuwe captcha",
         "validations": {
@@ -131,37 +131,37 @@
     "settings": {
         "attachmentRadius": "Bijlages",
         "attachments": "Bijlages",
-        "autoload": "Automatisch laden wanneer tot de bodem gescrold inschakelen",
+        "autoload": "Automatisch laden inschakelen wanneer tot de bodem gescrold wordt",
         "avatar": "Avatar",
         "avatarAltRadius": "Avatars (Meldingen)",
         "avatarRadius": "Avatars",
         "background": "Achtergrond",
         "bio": "Bio",
         "btnRadius": "Knoppen",
-        "cBlue": "Blauw (Antwoord, volgen)",
-        "cGreen": "Groen (Herhaal)",
-        "cOrange": "Oranje (Vind ik leuk)",
-        "cRed": "Rood (Annuleer)",
-        "change_password": "Verander Wachtwoord",
-        "change_password_error": "Er was een probleem bij het aanpassen van je wachtwoord.",
-        "changed_password": "Wachtwoord succesvol aangepast!",
-        "collapse_subject": "Klap posts met onderwerp in",
-        "composing": "Samenstellen",
-        "confirm_new_password": "Bevestig nieuw wachtwoord",
+        "cBlue": "Blauw (Beantwoorden, volgen)",
+        "cGreen": "Groen (Herhalen)",
+        "cOrange": "Oranje (Favoriet)",
+        "cRed": "Rood (Annuleren)",
+        "change_password": "Wachtwoord Wijzigen",
+        "change_password_error": "Er is een fout opgetreden bij het wijzigen van je wachtwoord.",
+        "changed_password": "Wachtwoord succesvol gewijzigd!",
+        "collapse_subject": "Klap berichten met een onderwerp in",
+        "composing": "Opstellen",
+        "confirm_new_password": "Nieuw wachtwoord bevestigen",
         "current_avatar": "Je huidige avatar",
         "current_password": "Huidig wachtwoord",
         "current_profile_banner": "Je huidige profiel banner",
         "data_import_export_tab": "Data Import / Export",
-        "default_vis": "Standaard zichtbaarheidsscope",
-        "delete_account": "Verwijder Account",
-        "delete_account_description": "Verwijder je account en berichten permanent.",
-        "delete_account_error": "Er was een probleem bij het verwijderen van je account. Indien dit probleem blijft, gelieve de administratie van deze instantie te verwittigen.",
-        "delete_account_instructions": "Typ je wachtwoord in de input hieronder om het verwijderen van je account te bevestigen.",
-        "export_theme": "Sla preset op",
+        "default_vis": "Standaard zichtbaarheidsbereik",
+        "delete_account": "Account Verwijderen",
+        "delete_account_description": "Permanent je account en al je berichten verwijderen.",
+        "delete_account_error": "Er is een fout opgetreden bij het verwijderen van je account. Indien dit probleem zich voor blijft doen, neem dan contact op met de beheerder van deze instantie.",
+        "delete_account_instructions": "Voer je wachtwoord in het onderstaande invoerveld in om het verwijderen van je account te bevestigen.",
+        "export_theme": "Preset opslaan",
         "filtering": "Filtering",
-        "filtering_explanation": "Alle statussen die deze woorden bevatten worden genegeerd, één filter per lijn.",
+        "filtering_explanation": "Alle statussen die deze woorden bevatten worden genegeerd, één filter per lijn",
         "follow_export": "Volgers exporteren",
-        "follow_export_button": "Exporteer je volgers naar een csv file",
+        "follow_export_button": "Exporteer je volgers naar een csv bestand",
         "follow_export_processing": "Aan het verwerken, binnen enkele ogenblikken wordt je gevraagd je bestand te downloaden",
         "follow_import": "Volgers importeren",
         "follow_import_error": "Fout bij importeren volgers",
@@ -171,91 +171,91 @@
         "hide_attachments_in_convo": "Verberg bijlages in conversaties",
         "hide_attachments_in_tl": "Verberg bijlages in de tijdlijn",
         "hide_isp": "Verberg instantie-specifiek paneel",
-        "preload_images": "Afbeeldingen voorladen",
-        "hide_post_stats": "Verberg post statistieken (bv. het aantal vind-ik-leuks)",
-        "hide_user_stats": "Verberg post statistieken (bv. het aantal volgers)",
-        "import_followers_from_a_csv_file": "Importeer volgers uit een csv file",
-        "import_theme": "Laad preset",
-        "inputRadius": "Invoer velden",
+        "preload_images": "Afbeeldingen vooraf laden",
+        "hide_post_stats": "Verberg bericht statistieken (bijv. het aantal favorieten)",
+        "hide_user_stats": "Verberg bericht statistieken (bijv. het aantal volgers)",
+        "import_followers_from_a_csv_file": "Importeer volgers uit een csv bestand",
+        "import_theme": "Preset laden",
+        "inputRadius": "Invoervelden",
         "checkboxRadius": "Checkboxen",
         "instance_default": "(standaard: {value})",
         "instance_default_simple": "(standaard)",
         "interface": "Interface",
         "interfaceLanguage": "Interface taal",
-        "invalid_theme_imported": "Het geselecteerde thema is geen door Pleroma ondersteund thema. Er zijn geen aanpassingen gedaan.",
-        "limited_availability": "Onbeschikbaar in je browser",
+        "invalid_theme_imported": "Het geselecteerde bestand is geen door Pleroma ondersteund thema. Er zijn geen aanpassingen gedaan.",
+        "limited_availability": "Niet beschikbaar in je browser",
         "links": "Links",
         "lock_account_description": "Laat volgers enkel toe na expliciete toestemming",
-        "loop_video": "Speel videos af in een lus",
-        "loop_video_silent_only": "Speel enkel videos zonder geluid af in een lus (bv. Mastodon's \"gifs\")",
+        "loop_video": "Herhaal video's",
+        "loop_video_silent_only": "Herhaal enkel video's zonder geluid (bijv. Mastodon's \"gifs\")",
         "name": "Naam",
         "name_bio": "Naam & Bio",
         "new_password": "Nieuw wachtwoord",
         "notification_visibility": "Type meldingen die getoond worden",
-        "notification_visibility_follows": "Volgers",
+        "notification_visibility_follows": "Volgingen",
         "notification_visibility_likes": "Vind-ik-leuks",
         "notification_visibility_mentions": "Vermeldingen",
         "notification_visibility_repeats": "Herhalingen",
-        "no_rich_text_description": "Strip rich text formattering van alle posts",
+        "no_rich_text_description": "Verwijder rich text formattering van alle berichten",
         "hide_network_description": "Toon niet wie mij volgt en wie ik volg.",
-        "nsfw_clickthrough": "Schakel doorklikbaar verbergen van NSFW bijlages in",
+        "nsfw_clickthrough": "Doorklikbaar verbergen van gevoelige bijlages inschakelen",
         "oauth_tokens": "OAuth-tokens",
         "token": "Token",
-        "refresh_token": "Token vernieuwen",
+        "refresh_token": "Token Vernieuwen",
         "valid_until": "Geldig tot",
         "revoke_token": "Intrekken",
         "panelRadius": "Panelen",
-        "pause_on_unfocused": "Pauzeer streamen wanneer de tab niet gefocused is",
+        "pause_on_unfocused": "Streamen pauzeren wanneer de tab niet in focus is",
         "presets": "Presets",
         "profile_background": "Profiel Achtergrond",
         "profile_banner": "Profiel Banner",
         "profile_tab": "Profiel",
         "radii_help": "Stel afronding van hoeken in de interface in (in pixels)",
         "replies_in_timeline": "Antwoorden in tijdlijn",
-        "reply_link_preview": "Schakel antwoordlink preview in bij over zweven met muisaanwijzer",
-        "reply_visibility_all": "Toon alle antwoorden",
-        "reply_visibility_following": "Toon enkel antwoorden naar mij of andere gebruikers gericht",
-        "reply_visibility_self": "Toon enkel antwoorden naar mij gericht",
+        "reply_link_preview": "Antwoord-link weergave inschakelen bij aanwijzen met muisaanwijzer",
+        "reply_visibility_all": "Alle antwoorden tonen",
+        "reply_visibility_following": "Enkel antwoorden tonen die aan mij of gevolgde gebruikers gericht zijn",
+        "reply_visibility_self": "Enkel antwoorden tonen die aan mij gericht zijn",
         "saving_err": "Fout tijdens opslaan van instellingen",
         "saving_ok": "Instellingen opgeslagen",
-        "security_tab": "Veiligheid",
-        "scope_copy": "Neem scope over bij antwoorden (Directe Berichten blijven altijd Direct)",
-        "set_new_avatar": "Zet nieuwe avatar",
-        "set_new_profile_background": "Zet nieuwe profiel achtergrond",
-        "set_new_profile_banner": "Zet nieuwe profiel banner",
+        "security_tab": "Beveiliging",
+        "scope_copy": "Neem bereik over bij beantwoorden (Directe Berichten blijven altijd Direct)",
+        "set_new_avatar": "Nieuwe avatar instellen",
+        "set_new_profile_background": "Nieuwe profiel achtergrond instellen",
+        "set_new_profile_banner": "Nieuwe profiel banner instellen",
         "settings": "Instellingen",
-        "subject_input_always_show": "Maak onderwerpveld altijd zichtbaar",
-        "subject_line_behavior": "Kopieer onderwerp bij antwoorden",
+        "subject_input_always_show": "Altijd onderwerpveld tonen",
+        "subject_line_behavior": "Onderwerp kopiëren bij antwoorden",
         "subject_line_email": "Zoals email: \"re: onderwerp\"",
-        "subject_line_mastodon": "Zoals Mastodon: kopieer zoals het is",
-        "subject_line_noop": "Kopieer niet",
-        "stop_gifs": "Speel GIFs af bij zweven",
-        "streaming": "Schakel automatisch streamen van posts in wanneer tot boven gescrold.",
+        "subject_line_mastodon": "Zoals mastodon: kopieer zoals het is",
+        "subject_line_noop": "Niet kopiëren",
+        "stop_gifs": "GIFs afspelen bij zweven",
+        "streaming": "Automatisch streamen van nieuwe berichten inschakelen wanneer tot boven gescrold is",
         "text": "Tekst",
         "theme": "Thema",
         "theme_help": "Gebruik hex color codes (#rrggbb) om je kleurschema te wijzigen.",
-        "theme_help_v2_1": "Je kan ook de kleur en transparantie van bepaalde componenten overschrijven door de checkbox aan te vinken, gebruik de \"Wis alles\" knop om alle overschrijvingen te annuleren.",
-        "theme_help_v2_2": "Iconen onder sommige items zijn achtergrond/tekst contrast indicators, zweef er over voor gedetailleerde info. Hou er rekening mee dat bij doorzichtigheid de ergst mogelijke situatie wordt weer gegeven.",
-        "tooltipRadius": "Gereedschapstips/alarmen",
-        "user_settings": "Gebruikers Instellingen",
+        "theme_help_v2_1": "Je kan ook de kleur en transparantie van bepaalde componenten overschrijven door de checkbox aan te vinken, gebruik de \"Alles wissen\" knop om alle overschrijvingen te annuleren.",
+        "theme_help_v2_2": "Iconen onder sommige onderdelen zijn achtergrond/tekst contrast indicatoren, zweef er over voor gedetailleerde info. Hou er rekening mee dat bij doorzichtigheid de ergst mogelijke situatie wordt weer gegeven.",
+        "tooltipRadius": "Tooltips/alarmen",
+        "user_settings": "Gebruikersinstellingen",
         "values": {
             "false": "nee",
             "true": "ja"
         },
         "notifications": "Meldingen",
-        "enable_web_push_notifications": "Schakel web push meldingen in",
+        "enable_web_push_notifications": "Web push meldingen inschakelen",
         "style": {
             "switcher": {
-                "keep_color": "Behoud kleuren",
-                "keep_shadows": "Behoud schaduwen",
-                "keep_opacity": "Behoud transparantie",
-                "keep_roundness": "Behoud afrondingen",
-                "keep_fonts": "Behoud lettertypes",
+                "keep_color": "Kleuren behouden",
+                "keep_shadows": "Schaduwen behouden",
+                "keep_opacity": "Transparantie behouden",
+                "keep_roundness": "Rondingen behouden",
+                "keep_fonts": "Lettertypes behouden",
                 "save_load_hint": "\"Behoud\" opties behouden de momenteel ingestelde opties bij het selecteren of laden van thema's, maar slaan ook de genoemde opties op bij het exporteren van een thema. Wanneer alle selectievakjes zijn uitgeschakeld, zal het exporteren van thema's alles opslaan.",
                 "reset": "Reset",
-                "clear_all": "Wis alles",
-                "clear_opacity": "Wis transparantie",
-                "keep_as_is": "Houdt zoals het is",
+                "clear_all": "Alles wissen",
+                "clear_opacity": "Transparantie wissen",
+                "keep_as_is": "Hou zoals het is",
                 "use_snapshot": "Oude versie",
                 "use_source": "Nieuwe versie",
                 "help": {
@@ -367,8 +367,8 @@
         "notification_setting_non_follows": "Gebruikers die je niet volgt",
         "notification_setting_followers": "Gebruikers die je volgen",
         "notification_setting_privacy": "Privacy",
-        "notification_setting_privacy_option": "Verberg de afzender en inhoud van push notificaties",
-        "notification_mutes": "Om niet langer notificaties te ontvangen van een specifieke gebruiker, kun je deze dempen.",
+        "notification_setting_privacy_option": "Verberg de afzender en inhoud van push meldingen",
+        "notification_mutes": "Om niet langer meldingen te ontvangen van een specifieke gebruiker, kun je deze negeren.",
         "app_name": "App naam",
         "security": "Beveiliging",
         "enter_current_password_to_confirm": "Voer je huidige wachtwoord in om je identiteit te bevestigen",
@@ -393,57 +393,57 @@
             "warning_of_generate_new_codes": "Wanneer je nieuwe herstelcodes genereert, zullen je oude code niet langer werken.",
             "recovery_codes_warning": "Schrijf de codes op of sla ze op een veilige locatie op - anders kun je ze niet meer inzien. Als je toegang tot je 2FA app en herstelcodes verliest, zal je buitengesloten zijn uit je account."
         },
-        "allow_following_move": "Auto-volgen toestaan wanneer een gevolgd account migreert",
-        "block_export": "Geblokkeerde gebruikers exporteren",
-        "block_import": "Geblokkeerde gebruikers importeren",
-        "blocks_imported": "Geblokkeerde gebruikers geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
-        "blocks_tab": "Geblokkeerde gebruikers",
+        "allow_following_move": "Automatisch volgen toestaan wanneer een gevolgd account migreert",
+        "block_export": "Blokkades exporteren",
+        "block_import": "Blokkades importeren",
+        "blocks_imported": "Blokkades geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
+        "blocks_tab": "Blokkades",
         "change_email": "Email wijzigen",
-        "change_email_error": "Er is een probleem opgetreden tijdens het wijzigen van je email.",
+        "change_email_error": "Er is een fout opgetreden tijdens het wijzigen van je email.",
         "changed_email": "Email succesvol gewijzigd!",
         "domain_mutes": "Domeinen",
         "avatar_size_instruction": "De aangeraden minimale afmeting voor avatar afbeeldingen is 150x150 pixels.",
-        "pad_emoji": "Vul emoji op met spaties wanneer deze met de picker ingevoegd worden",
+        "pad_emoji": "Vul emoji aan met spaties wanneer deze met de picker ingevoegd worden",
         "emoji_reactions_on_timeline": "Toon emoji reacties op de tijdlijn",
         "accent": "Accent",
-        "hide_muted_posts": "Verberg berichten van gedempte gebruikers",
+        "hide_muted_posts": "Verberg berichten van genegeerde gebruikers",
         "max_thumbnails": "Maximaal aantal miniaturen per bericht",
         "use_one_click_nsfw": "Open gevoelige bijlagen met slechts één klik",
-        "hide_filtered_statuses": "Verberg gefilterde statussen",
-        "import_blocks_from_a_csv_file": "Importeer geblokkeerde gebruikers van een csv bestand",
-        "mutes_tab": "Gedempte gebruikers",
+        "hide_filtered_statuses": "Gefilterde statussen verbergen",
+        "import_blocks_from_a_csv_file": "Importeer blokkades van een csv bestand",
+        "mutes_tab": "Negeringen",
         "play_videos_in_modal": "Speel video's af in een popup frame",
         "new_email": "Nieuwe Email",
         "notification_visibility_emoji_reactions": "Reacties",
-        "no_blocks": "Geen geblokkeerde gebruikers",
-        "no_mutes": "Geen gedempte gebruikers",
+        "no_blocks": "Geen blokkades",
+        "no_mutes": "Geen negeringen",
         "hide_followers_description": "Niet tonen wie mij volgt",
         "hide_followers_count_description": "Niet mijn volgers aantal tonen",
         "hide_follows_count_description": "Niet mijn gevolgde aantal tonen",
-        "show_admin_badge": "Admin badge tonen in mijn profiel",
+        "show_admin_badge": "Beheerders badge tonen in mijn profiel",
         "autohide_floating_post_button": "Nieuw Bericht knop automatisch verbergen (mobiel)",
         "search_user_to_block": "Zoek wie je wilt blokkeren",
-        "search_user_to_mute": "Zoek wie je wilt dempen",
+        "search_user_to_mute": "Zoek wie je wilt negeren",
         "minimal_scopes_mode": "Bericht bereik-opties minimaliseren",
         "post_status_content_type": "Bericht status content type",
         "user_mutes": "Gebruikers",
-        "useStreamingApi": "Berichten en notificatie in real-time ontvangen",
+        "useStreamingApi": "Berichten en meldingen in real-time ontvangen",
         "useStreamingApiWarning": "(Afgeraden, experimenteel, kan berichten overslaan)",
-        "type_domains_to_mute": "Voer domeinen in om te dempen",
+        "type_domains_to_mute": "Voer domeinen in om te negeren",
         "upload_a_photo": "Upload een foto",
         "fun": "Plezier",
         "greentext": "Meme pijlen",
-        "notification_setting": "Ontvang notificaties van:",
+        "notification_setting": "Ontvang meldingen van:",
         "block_export_button": "Exporteer je geblokkeerde gebruikers naar een csv bestand",
-        "block_import_error": "Fout bij importeren geblokkeerde gebruikers",
+        "block_import_error": "Fout bij importeren blokkades",
         "discoverable": "Sta toe dat dit account ontdekt kan worden in zoekresultaten en andere diensten",
-        "use_contain_fit": "Bijlage in miniaturen niet bijsnijden",
+        "use_contain_fit": "Snij bijlage in miniaturen niet bij",
         "notification_visibility_moves": "Gebruiker Migraties",
         "hide_follows_description": "Niet tonen wie ik volg",
-        "show_moderator_badge": "Moderator badge tonen in mijn profiel",
+        "show_moderator_badge": "Moderators badge tonen in mijn profiel",
         "notification_setting_filters": "Filters",
         "notification_setting_non_followers": "Gebruikers die je niet volgen",
-        "notification_blocks": "Door een gebruiker te blokkeren, ontvang je geen notificaties meer van de gebruiker en wordt je abonnement op de gebruiker opgeheven."
+        "notification_blocks": "Door een gebruiker te blokkeren, ontvang je geen meldingen meer van de gebruiker en wordt je abonnement op de gebruiker opgeheven."
     },
     "timeline": {
         "collapse": "Inklappen",

From 00b4b1f4f62276b94b578db1b0d25bfe23b7f779 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Thu, 14 May 2020 19:53:48 +0000
Subject: [PATCH 368/483] Translated using Weblate (Italian)

Currently translated at 40.6% (249 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 35 +++++++++++++++++++++++++++++++++--
 1 file changed, 33 insertions(+), 2 deletions(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index 5554620c..d4c4cb74 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -20,7 +20,16 @@
         "timeline": "Sequenza personale",
         "twkn": "Sequenza globale",
         "chat": "Chat della stanza",
-        "friend_requests": "Vogliono seguirti"
+        "friend_requests": "Vogliono seguirti",
+        "about": "Informazioni",
+        "administration": "Amministrazione",
+        "back": "Indietro",
+        "interactions": "Interazioni",
+        "dms": "Messaggi diretti",
+        "user_search": "Ricerca utenti",
+        "search": "Ricerca",
+        "who_to_follow": "Chi seguire",
+        "preferences": "Preferenze"
     },
     "notifications": {
         "followed_you": "ti segue",
@@ -29,7 +38,11 @@
         "broken_favorite": "Stato sconosciuto, lo sto cercando...",
         "favorited_you": "ha gradito il tuo messaggio",
         "load_older": "Carica notifiche precedenti",
-        "repeated_you": "ha condiviso il tuo messaggio"
+        "repeated_you": "ha condiviso il tuo messaggio",
+        "follow_request": "vuole seguirti",
+        "no_more_notifications": "Fine delle notifiche",
+        "migrated_to": "è migrato verso",
+        "reacted_with": "ha reagito con"
     },
     "settings": {
         "attachments": "Allegati",
@@ -274,5 +287,23 @@
         "submit": "Invia",
         "success": "Importato.",
         "error": "L'importazione non è andata a buon fine."
+    },
+    "media_modal": {
+        "previous": "Precedente",
+        "next": "Prossimo"
+    },
+    "polls": {
+        "add_poll": "Sondaggio",
+        "add_option": "Alternativa",
+        "option": "Opzione",
+        "votes": "voti",
+        "vote": "Vota",
+        "type": "Tipo di sondaggio",
+        "single_choice": "Scelta singola",
+        "multiple_choices": "Scelta multipla",
+        "expiry": "Scadenza",
+        "expires_in": "Scade fra {0}",
+        "expired": "Scaduto {0} fa",
+        "not_enough_options": "Aggiungi altre risposte"
     }
 }

From b994bde3e21e69bf5a32576779496e6f38251252 Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:20:46 +0000
Subject: [PATCH 369/483] Translated using Weblate (German)

Currently translated at 68.3% (419 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 4037b4b3..e242f364 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -84,7 +84,10 @@
         "account_not_locked_warning_link": "gesperrt",
         "attachments_sensitive": "Anhänge als heikel markieren",
         "content_type": {
-            "text/plain": "Nur Text"
+            "text/plain": "Nur Text",
+            "text/bbcode": "BBCode",
+            "text/markdown": "Markdown",
+            "text/html": "HTML"
         },
         "content_warning": "Betreff (optional)",
         "default": "Sitze gerade im Hofbräuhaus.",
@@ -95,7 +98,8 @@
             "private": "Nur Follower - Beitrag nur für Follower sichtbar",
             "public": "Öffentlich - Beitrag an öffentliche Zeitleisten",
             "unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
-        }
+        },
+        "direct_warning_to_all": "Dieser Post wird für alle erwähnten User sichtbar sein."
     },
     "registration": {
         "bio": "Bio",
@@ -511,6 +515,14 @@
         "search_emoji": "Nach einem Emoji suchen",
         "custom": "Benutzerdefinierter Emoji",
         "keep_open": "Auswahlfenster offen halten",
-        "add_emoji": "Emoji einfügen"
+        "add_emoji": "Emoji einfügen",
+        "load_all": "Lade alle {emojiAmount} Emoji",
+        "load_all_hint": "Erfolgreich erste {saneAmount} Emoji geladen, alle Emojis zu laden würde Leistungsprobleme hervorrufen.",
+        "unicode": "Unicode Emoji"
+    },
+    "interactions": {
+        "load_older": "Lade ältere Interaktionen",
+        "follows": "Neue Follows",
+        "favs_repeats": "Wiederholungen und Favoriten"
     }
 }

From 9e306efe2edb104a74a0543feb61697659330266 Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:24:51 +0000
Subject: [PATCH 370/483] Translated using Weblate (German)

Currently translated at 68.3% (419 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index e242f364..6359b608 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -523,6 +523,7 @@
     "interactions": {
         "load_older": "Lade ältere Interaktionen",
         "follows": "Neue Follows",
-        "favs_repeats": "Wiederholungen und Favoriten"
+        "favs_repeats": "Wiederholungen und Favoriten",
+        "moves": "Benutzer migriert zu"
     }
 }

From ab4790034bbd452f3e5408d1e5f2814af30c262b Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:27:02 +0000
Subject: [PATCH 371/483] Translated using Weblate (German)

Currently translated at 68.5% (420 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 6359b608..c68edb43 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -99,7 +99,7 @@
             "public": "Öffentlich - Beitrag an öffentliche Zeitleisten",
             "unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
         },
-        "direct_warning_to_all": "Dieser Post wird für alle erwähnten User sichtbar sein."
+        "direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein."
     },
     "registration": {
         "bio": "Bio",

From 9374002de09fcfb23fb8fb1ba7684d0a1d8f0254 Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:27:09 +0000
Subject: [PATCH 372/483] Translated using Weblate (German)

Currently translated at 68.5% (420 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index c68edb43..ea5dfb91 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -99,7 +99,8 @@
             "public": "Öffentlich - Beitrag an öffentliche Zeitleisten",
             "unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
         },
-        "direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein."
+        "direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein.",
+        "direct_warning_to_first_only": "Dieser Post wird für alle User, die am Anfang der Nachricht erwähnt wurden, sichtbar sein."
     },
     "registration": {
         "bio": "Bio",

From 14ac5a5fdb706174b9a4611bb1414106af79fc82 Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:27:26 +0000
Subject: [PATCH 373/483] Translated using Weblate (German)

Currently translated at 68.6% (421 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index ea5dfb91..9688695f 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -100,7 +100,7 @@
             "unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
         },
         "direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein.",
-        "direct_warning_to_first_only": "Dieser Post wird für alle User, die am Anfang der Nachricht erwähnt wurden, sichtbar sein."
+        "direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein."
     },
     "registration": {
         "bio": "Bio",

From f22d3c18cfaf587c8014e8718e8bdcbb4d14a7b0 Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:27:57 +0000
Subject: [PATCH 374/483] Translated using Weblate (German)

Currently translated at 68.6% (421 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 9688695f..1a60505e 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -100,7 +100,10 @@
             "unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
         },
         "direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein.",
-        "direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein."
+        "direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.",
+        "scope_notice": {
+            "public": "Dieser Post wird für alle sichtbar sein"
+        }
     },
     "registration": {
         "bio": "Bio",

From f91bb4dc7cfa32c06711fc1ca0f38b30712f7fb5 Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:28:07 +0000
Subject: [PATCH 375/483] Translated using Weblate (German)

Currently translated at 68.8% (422 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 1a60505e..1344ed1b 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -102,7 +102,7 @@
         "direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein.",
         "direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.",
         "scope_notice": {
-            "public": "Dieser Post wird für alle sichtbar sein"
+            "public": "Dieser Beitrag wird für alle sichtbar sein"
         }
     },
     "registration": {

From 1179a6687a87f57018eebbebda7568efbe9326bd Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:28:12 +0000
Subject: [PATCH 376/483] Translated using Weblate (German)

Currently translated at 68.8% (422 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 1344ed1b..f0f4cbaa 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -102,7 +102,8 @@
         "direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein.",
         "direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.",
         "scope_notice": {
-            "public": "Dieser Beitrag wird für alle sichtbar sein"
+            "public": "Dieser Beitrag wird für alle sichtbar sein",
+            "private": "Dieser Post wird nur für deine Follower sichtbar sein"
         }
     },
     "registration": {

From 9e43a83c487d7182da15992142ede01612e9832a Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:28:22 +0000
Subject: [PATCH 377/483] Translated using Weblate (German)

Currently translated at 69.0% (423 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index f0f4cbaa..f0b715aa 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -103,7 +103,7 @@
         "direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.",
         "scope_notice": {
             "public": "Dieser Beitrag wird für alle sichtbar sein",
-            "private": "Dieser Post wird nur für deine Follower sichtbar sein"
+            "private": "Dieser Beitrag wird nur für deine Follower sichtbar sein"
         }
     },
     "registration": {

From e7fd793438b87674d6d27ed5e6446b08d3f2aef8 Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:28:34 +0000
Subject: [PATCH 378/483] Translated using Weblate (German)

Currently translated at 69.0% (423 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index f0b715aa..71f4eb49 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -103,7 +103,8 @@
         "direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.",
         "scope_notice": {
             "public": "Dieser Beitrag wird für alle sichtbar sein",
-            "private": "Dieser Beitrag wird nur für deine Follower sichtbar sein"
+            "private": "Dieser Beitrag wird nur für deine Follower sichtbar sein",
+            "unlisted": "Dieser Post wird weder in der öffentlichen Zeitleiste noch im gesamten bekannten Netzwerk sichtbar sein"
         }
     },
     "registration": {

From f141fa2a6609cdea0ea22b0b83fe2563de0d1006 Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:28:45 +0000
Subject: [PATCH 379/483] Translated using Weblate (German)

Currently translated at 70.3% (431 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 71f4eb49..eaf7bef1 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -104,7 +104,7 @@
         "scope_notice": {
             "public": "Dieser Beitrag wird für alle sichtbar sein",
             "private": "Dieser Beitrag wird nur für deine Follower sichtbar sein",
-            "unlisted": "Dieser Post wird weder in der öffentlichen Zeitleiste noch im gesamten bekannten Netzwerk sichtbar sein"
+            "unlisted": "Dieser Beitrag wird weder in der öffentlichen Zeitleiste noch im gesamten bekannten Netzwerk sichtbar sein"
         }
     },
     "registration": {
@@ -531,5 +531,8 @@
         "follows": "Neue Follows",
         "favs_repeats": "Wiederholungen und Favoriten",
         "moves": "Benutzer migriert zu"
+    },
+    "selectable_list": {
+        "select_all": "Alle auswählen"
     }
 }

From 7bd0f1a5f10db69bebb98ea991e051bce62183b6 Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:28:54 +0000
Subject: [PATCH 380/483] Translated using Weblate (German)

Currently translated at 70.3% (431 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index eaf7bef1..ee09f1f3 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -123,7 +123,10 @@
             "password_required": "darf nicht leer sein",
             "password_confirmation_required": "darf nicht leer sein",
             "password_confirmation_match": "sollte mit dem Passwort identisch sein."
-        }
+        },
+        "bio_placeholder": "z.B.\nHallo, ich bin Lain.\nIch bin ein Anime Mödchen aus dem vorstädtischen Japan. Du kennst mich vielleicht vom Wired.",
+        "fullname_placeholder": "z.B. Lain Iwakura",
+        "username_placeholder": "z.B. lain"
     },
     "settings": {
         "attachmentRadius": "Anhänge",
@@ -365,7 +368,13 @@
                 "checkbox": "Ich habe die Allgemeinen Geschäftsbedingungen überflogen",
                 "link": "ein netter kleiner Link"
             }
-        }
+        },
+        "app_name": "Anwendungsname",
+        "mfa": {
+            "otp": "OTP"
+        },
+        "enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen",
+        "security": "Sicherheit"
     },
     "timeline": {
         "collapse": "Einklappen",

From c1834b31569aa67f4dfa0a2680b2f8e76c484285 Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:31:31 +0000
Subject: [PATCH 381/483] Translated using Weblate (German)

Currently translated at 71.2% (437 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index ee09f1f3..e14492d0 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -371,7 +371,12 @@
         },
         "app_name": "Anwendungsname",
         "mfa": {
-            "otp": "OTP"
+            "otp": "OTP",
+            "recovery_codes_warning": "Schreibe dir die Codes auf oder speichere sie an einem sicheren Ort - ansonsten wirst du sie nicht wieder finden. Wenn du den Zugriff zu deiner 2FA App und die Wiederherstellungs-Codes verlierst, wirst du aus deinem Account ausgeschlossen sein.",
+            "recovery_codes": "Wiederherstellungs-Codes.",
+            "warning_of_generate_new_codes": "Wenn du neue Wiederherstellungs-Codes generierst, werden die alten Codes nicht mehr funktionieren.",
+            "generate_new_recovery_codes": "Generiere neue Wiederherstellungs-Codes",
+            "title": "Zwei-Faktor Authentifizierung"
         },
         "enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen",
         "security": "Sicherheit"

From 495b3d819cae106462e1d7af16efea2758db8f26 Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:33:16 +0000
Subject: [PATCH 382/483] Translated using Weblate (German)

Currently translated at 71.2% (437 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index e14492d0..a34c604b 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -376,7 +376,8 @@
             "recovery_codes": "Wiederherstellungs-Codes.",
             "warning_of_generate_new_codes": "Wenn du neue Wiederherstellungs-Codes generierst, werden die alten Codes nicht mehr funktionieren.",
             "generate_new_recovery_codes": "Generiere neue Wiederherstellungs-Codes",
-            "title": "Zwei-Faktor Authentifizierung"
+            "title": "Zwei-Faktor Authentifizierung",
+            "waiting_a_recovery_codes": "Erhalte Wiederherstellungscodes..."
         },
         "enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen",
         "security": "Sicherheit"

From 9202eeef4b10c935b511a53abbd596b89f581f7b Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:34:19 +0000
Subject: [PATCH 383/483] Translated using Weblate (German)

Currently translated at 71.4% (438 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index a34c604b..884e31d1 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -372,7 +372,7 @@
         "app_name": "Anwendungsname",
         "mfa": {
             "otp": "OTP",
-            "recovery_codes_warning": "Schreibe dir die Codes auf oder speichere sie an einem sicheren Ort - ansonsten wirst du sie nicht wieder finden. Wenn du den Zugriff zu deiner 2FA App und die Wiederherstellungs-Codes verlierst, wirst du aus deinem Account ausgeschlossen sein.",
+            "recovery_codes_warning": "Schreibe dir die Codes auf oder speichere sie an einem sicheren Ort - ansonsten wirst du sie nicht wiederfinden. Wenn du den Zugriff zu deiner 2FA App und die Wiederherstellungs-Codes verlierst, wirst du aus deinem Account ausgeschlossen sein.",
             "recovery_codes": "Wiederherstellungs-Codes.",
             "warning_of_generate_new_codes": "Wenn du neue Wiederherstellungs-Codes generierst, werden die alten Codes nicht mehr funktionieren.",
             "generate_new_recovery_codes": "Generiere neue Wiederherstellungs-Codes",

From c8cf939cddbb173be67544025b53e24b7abd8376 Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:34:35 +0000
Subject: [PATCH 384/483] Translated using Weblate (German)

Currently translated at 71.4% (438 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 884e31d1..b50bd72a 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -377,7 +377,8 @@
             "warning_of_generate_new_codes": "Wenn du neue Wiederherstellungs-Codes generierst, werden die alten Codes nicht mehr funktionieren.",
             "generate_new_recovery_codes": "Generiere neue Wiederherstellungs-Codes",
             "title": "Zwei-Faktor Authentifizierung",
-            "waiting_a_recovery_codes": "Erhalte Wiederherstellungscodes..."
+            "waiting_a_recovery_codes": "Erhalte Wiederherstellungscodes...",
+            "authentication_methods": "Authentifizierungs-Methoden"
         },
         "enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen",
         "security": "Sicherheit"

From 53b2ae0f73a40fb7dbe2522edbd78e6b2d7349e9 Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:34:42 +0000
Subject: [PATCH 385/483] Translated using Weblate (German)

Currently translated at 73.0% (448 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index b50bd72a..979566d6 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -378,10 +378,14 @@
             "generate_new_recovery_codes": "Generiere neue Wiederherstellungs-Codes",
             "title": "Zwei-Faktor Authentifizierung",
             "waiting_a_recovery_codes": "Erhalte Wiederherstellungscodes...",
-            "authentication_methods": "Authentifizierungs-Methoden"
+            "authentication_methods": "Authentifizierungsmethoden",
+            "scan": {
+                "title": "Scan"
+            }
         },
         "enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen",
-        "security": "Sicherheit"
+        "security": "Sicherheit",
+        "allow_following_move": "Erlaube automatisches Folgen, sobald ein gefolgter Nutzer umzieht"
     },
     "timeline": {
         "collapse": "Einklappen",

From 3e260661d0cdbaaaeda8c3fa33756c4e2e9ccc0d Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:37:01 +0000
Subject: [PATCH 386/483] Translated using Weblate (German)

Currently translated at 73.0% (448 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 979566d6..ce7c13c5 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -380,12 +380,22 @@
             "waiting_a_recovery_codes": "Erhalte Wiederherstellungscodes...",
             "authentication_methods": "Authentifizierungsmethoden",
             "scan": {
-                "title": "Scan"
+                "title": "Scan",
+                "secret_code": "Schlüssel",
+                "desc": "Wenn du deine 2FA App verwendest, scanne diesen QR Code oder gebe den Schlüssel ein:"
+            },
+            "verify": {
+                "desc": "Um 2FA zu aktivieren, gib den Code von deiner 2FA-App ein:"
             }
         },
         "enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen",
         "security": "Sicherheit",
-        "allow_following_move": "Erlaube automatisches Folgen, sobald ein gefolgter Nutzer umzieht"
+        "allow_following_move": "Erlaube automatisches Folgen, sobald ein gefolgter Nutzer umzieht",
+        "blocks_imported": "Blocks importiert! Die Verarbeitung wird eine kurze Weile brauchen.",
+        "block_import_error": "Fehler beim Importieren der Blocks",
+        "block_import": "Block Import",
+        "block_export_button": "Exportiere deine Blocks in eine csv Datei",
+        "block_export": "Block Export"
     },
     "timeline": {
         "collapse": "Einklappen",

From 9ede7db8dfe773af93dbfcf82a889f6de63b92ae Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:40:50 +0000
Subject: [PATCH 387/483] Translated using Weblate (German)

Currently translated at 74.0% (454 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index ce7c13c5..644682db 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -138,7 +138,7 @@
         "background": "Hintergrund",
         "bio": "Bio",
         "btnRadius": "Buttons",
-        "cBlue": "Blau (Antworten, Folgt dir)",
+        "cBlue": "Blau (Antworten, folgt dir)",
         "cGreen": "Grün (Retweet)",
         "cOrange": "Orange (Favorisieren)",
         "cRed": "Rot (Abbrechen)",
@@ -154,10 +154,10 @@
         "data_import_export_tab": "Datenimport/-export",
         "default_vis": "Standard-Sichtbarkeitsumfang",
         "delete_account": "Account löschen",
-        "delete_account_description": "Lösche deinen Account und alle deine Nachrichten unwiderruflich.",
+        "delete_account_description": "Lösche deine Daten und deaktiviere deinen Account unwiderruflich.",
         "delete_account_error": "Es ist ein Fehler beim Löschen deines Accounts aufgetreten. Tritt dies weiterhin auf, wende dich an den Administrator der Instanz.",
         "delete_account_instructions": "Tippe dein Passwort unten in das Feld ein, um die Löschung deines Accounts zu bestätigen.",
-        "discoverable": "Erlaubnis für automatisches Suchen nach diesem Account",
+        "discoverable": "Erlaube, dass dieser Account in Suchergebnissen auftaucht",
         "avatar_size_instruction": "Die empfohlene minimale Größe für Avatare ist 150x150 Pixel.",
         "pad_emoji": "Emojis mit Leerzeichen umrahmen",
         "export_theme": "Farbschema speichern",
@@ -391,7 +391,7 @@
         "enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen",
         "security": "Sicherheit",
         "allow_following_move": "Erlaube automatisches Folgen, sobald ein gefolgter Nutzer umzieht",
-        "blocks_imported": "Blocks importiert! Die Verarbeitung wird eine kurze Weile brauchen.",
+        "blocks_imported": "Blocks importiert! Die Verarbeitung wird einen Moment brauchen.",
         "block_import_error": "Fehler beim Importieren der Blocks",
         "block_import": "Block Import",
         "block_export_button": "Exportiere deine Blocks in eine csv Datei",

From 91da315af9409c45bd6bea069ba9b32f8200decd Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:42:50 +0000
Subject: [PATCH 388/483] Translated using Weblate (German)

Currently translated at 74.0% (454 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 644682db..71e1fd0a 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -395,7 +395,12 @@
         "block_import_error": "Fehler beim Importieren der Blocks",
         "block_import": "Block Import",
         "block_export_button": "Exportiere deine Blocks in eine csv Datei",
-        "block_export": "Block Export"
+        "block_export": "Block Export",
+        "emoji_reactions_on_timeline": "Zeige Emoji Reaktionen auf der Zeitleiste",
+        "domain_mutes": "Domains",
+        "changed_email": "Email Adresse erfolgreich geändert!",
+        "change_email_error": "Es trat ein Problem auf beim Versuch, deine Email Adresse zu ändern.",
+        "change_email": "Ändere Email"
     },
     "timeline": {
         "collapse": "Einklappen",

From 1478ff7fc768d5677ee6f43d6903df818160f0b2 Mon Sep 17 00:00:00 2001
From: Toromino <mail@dennisbuchholz.net>
Date: Fri, 15 May 2020 09:48:15 +0000
Subject: [PATCH 389/483] Translated using Weblate (German)

Currently translated at 75.2% (461 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 26 ++++++++++++++++----------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 71e1fd0a..5490f710 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -122,7 +122,7 @@
             "email_required": "darf nicht leer sein",
             "password_required": "darf nicht leer sein",
             "password_confirmation_required": "darf nicht leer sein",
-            "password_confirmation_match": "sollte mit dem Passwort identisch sein."
+            "password_confirmation_match": "sollte mit dem Passwort identisch sein"
         },
         "bio_placeholder": "z.B.\nHallo, ich bin Lain.\nIch bin ein Anime Mödchen aus dem vorstädtischen Japan. Du kennst mich vielleicht vom Wired.",
         "fullname_placeholder": "z.B. Lain Iwakura",
@@ -162,13 +162,13 @@
         "pad_emoji": "Emojis mit Leerzeichen umrahmen",
         "export_theme": "Farbschema speichern",
         "filtering": "Filtern",
-        "filtering_explanation": "Alle Beiträge die diese Wörter enthalten werden ausgeblendet. Ein Wort pro Zeile.",
+        "filtering_explanation": "Alle Beiträge, welche diese Wörter enthalten, werden ausgeblendet. Ein Wort pro Zeile.",
         "follow_export": "Follower exportieren",
         "follow_export_button": "Exportiere deine Follows in eine csv-Datei",
         "follow_export_processing": "In Bearbeitung. Die Liste steht gleich zum herunterladen bereit.",
-        "follow_import": "Followers importieren",
-        "follow_import_error": "Fehler beim importieren der Follower",
-        "follows_imported": "Followers importiert! Die Bearbeitung kann eine Zeit lang dauern.",
+        "follow_import": "Follower importieren",
+        "follow_import_error": "Fehler beim Importieren der Follower",
+        "follows_imported": "Follower importiert! Die Bearbeitung kann einen Moment dauern.",
         "foreground": "Vordergrund",
         "general": "Allgemein",
         "hide_attachments_in_convo": "Anhänge in Unterhaltungen ausblenden",
@@ -181,7 +181,7 @@
         "hide_post_stats": "Beitragsstatistiken verbergen (z.B. die Anzahl der Favoriten)",
         "hide_user_stats": "Benutzerstatistiken verbergen (z.B. die Anzahl der Follower)",
         "hide_filtered_statuses": "Gefilterte Beiträge verbergen",
-        "import_followers_from_a_csv_file": "Importiere Follower, denen du folgen möchtest, aus einer CSV-Datei",
+        "import_followers_from_a_csv_file": "Importiere Follower aus einer CSV-Datei",
         "import_theme": "Farbschema laden",
         "inputRadius": "Eingabefelder",
         "checkboxRadius": "Auswahlfelder",
@@ -195,7 +195,7 @@
         "lock_account_description": "Sperre deinen Account, um neue Follower zu genehmigen oder abzulehnen",
         "loop_video": "Videos wiederholen",
         "loop_video_silent_only": "Nur Videos ohne Ton wiederholen (z.B. Mastodons \"gifs\")",
-        "mutes_tab": "Mutes",
+        "mutes_tab": "Stummschaltungen",
         "play_videos_in_modal": "Videos in größerem Medienfenster abspielen",
         "use_contain_fit": "Vorschaubilder nicht zuschneiden",
         "name": "Name",
@@ -396,7 +396,7 @@
         "block_import": "Block Import",
         "block_export_button": "Exportiere deine Blocks in eine csv Datei",
         "block_export": "Block Export",
-        "emoji_reactions_on_timeline": "Zeige Emoji Reaktionen auf der Zeitleiste",
+        "emoji_reactions_on_timeline": "Zeige Emoji-Reaktionen auf der Zeitleiste",
         "domain_mutes": "Domains",
         "changed_email": "Email Adresse erfolgreich geändert!",
         "change_email_error": "Es trat ein Problem auf beim Versuch, deine Email Adresse zu ändern.",
@@ -423,7 +423,7 @@
         "follow_again": "Anfrage erneut senden?",
         "follow_unfollow": "Folgen beenden",
         "followees": "Folgt",
-        "followers": "Followers",
+        "followers": "Folgende",
         "following": "Folgst du!",
         "follows_you": "Folgt dir!",
         "its_you": "Das bist du!",
@@ -431,7 +431,10 @@
         "muted": "Stummgeschaltet",
         "per_day": "pro Tag",
         "remote_follow": "Folgen",
-        "statuses": "Beiträge"
+        "statuses": "Beiträge",
+        "admin_menu": {
+            "sandbox": "Erzwinge Beiträge nur für Follower sichtbar zu sein"
+        }
     },
     "user_profile": {
         "timeline_title": "Beiträge"
@@ -569,5 +572,8 @@
     },
     "selectable_list": {
         "select_all": "Alle auswählen"
+    },
+    "remote_user_resolver": {
+        "searching_for": "Suche nach"
     }
 }

From 7f9eb23759cffc8462c369e4051e3735b617aa8f Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Fri, 15 May 2020 09:54:03 +0000
Subject: [PATCH 390/483] Translated using Weblate (German)

Currently translated at 75.2% (461 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
---
 src/i18n/de.json | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/i18n/de.json b/src/i18n/de.json
index 5490f710..d3eedcb6 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -400,7 +400,11 @@
         "domain_mutes": "Domains",
         "changed_email": "Email Adresse erfolgreich geändert!",
         "change_email_error": "Es trat ein Problem auf beim Versuch, deine Email Adresse zu ändern.",
-        "change_email": "Ändere Email"
+        "change_email": "Ändere Email",
+        "notification_setting_non_followers": "Nutzer, die dir nicht folgen",
+        "notification_setting_followers": "Nutzer, die dir folgen",
+        "import_blocks_from_a_csv_file": "Importiere Blocks von einer CSV Datei",
+        "accent": "Akzent"
     },
     "timeline": {
         "collapse": "Einklappen",
@@ -571,9 +575,10 @@
         "moves": "Benutzer migriert zu"
     },
     "selectable_list": {
-        "select_all": "Alle auswählen"
+        "select_all": "Wähle alle"
     },
     "remote_user_resolver": {
-        "searching_for": "Suche nach"
+        "searching_for": "Suche nach",
+        "error": "Nicht gefunden."
     }
 }

From b4f8d91905c37ee95b09ca81e7dbde1491902def Mon Sep 17 00:00:00 2001
From: Fristi <fristi@subcon.town>
Date: Fri, 15 May 2020 08:00:54 +0000
Subject: [PATCH 391/483] Translated using Weblate (Dutch)

Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
---
 src/i18n/nl.json | 284 +++++++++++++++++++++++++++++++++++------------
 1 file changed, 216 insertions(+), 68 deletions(-)

diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index 15216244..472d11ec 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -16,7 +16,7 @@
         "find_user": "Gebruiker zoeken"
     },
     "general": {
-        "apply": "toepassen",
+        "apply": "Toepassen",
         "submit": "Verzend",
         "more": "Meer",
         "optional": "optioneel",
@@ -33,10 +33,10 @@
     "login": {
         "login": "Log in",
         "description": "Log in met OAuth",
-        "logout": "Log uit",
+        "logout": "Uitloggen",
         "password": "Wachtwoord",
-        "placeholder": "bv. lain",
-        "register": "Registreer",
+        "placeholder": "bijv. lain",
+        "register": "Registreren",
         "username": "Gebruikersnaam",
         "hint": "Log in om deel te nemen aan de discussie",
         "authentication_code": "Authenticatie code",
@@ -51,14 +51,14 @@
     "nav": {
         "about": "Over",
         "back": "Terug",
-        "chat": "Locale Chat",
-        "friend_requests": "Volgverzoek",
+        "chat": "Lokale Chat",
+        "friend_requests": "Volgverzoeken",
         "mentions": "Vermeldingen",
         "dms": "Directe Berichten",
         "public_tl": "Publieke Tijdlijn",
         "timeline": "Tijdlijn",
-        "twkn": "Het Geheel Gekende Netwerk",
-        "user_search": "Zoek Gebruiker",
+        "twkn": "Het Geheel Bekende Netwerk",
+        "user_search": "Gebruiker Zoeken",
         "who_to_follow": "Wie te volgen",
         "preferences": "Voorkeuren",
         "administration": "Administratie",
@@ -73,18 +73,18 @@
         "notifications": "Meldingen",
         "read": "Gelezen!",
         "repeated_you": "Herhaalde je status",
-        "no_more_notifications": "Geen notificaties meer",
+        "no_more_notifications": "Geen meldingen meer",
         "migrated_to": "is gemigreerd naar",
         "follow_request": "wil je volgen",
         "reacted_with": "reageerde met {0}"
     },
     "post_status": {
-        "new_status": "Post nieuwe status",
-        "account_not_locked_warning": "Je account is niet {0}. Iedereen die je volgt kan enkel-volgers posts lezen.",
+        "new_status": "Nieuwe status plaatsen",
+        "account_not_locked_warning": "Je account is niet {0}. Iedereen kan je volgen om je alleen-volgers berichten te lezen.",
         "account_not_locked_warning_link": "gesloten",
-        "attachments_sensitive": "Markeer bijlage als gevoelig",
+        "attachments_sensitive": "Markeer bijlagen als gevoelig",
         "content_type": {
-            "text/plain": "Gewone tekst",
+            "text/plain": "Platte tekst",
             "text/html": "HTML",
             "text/markdown": "Markdown",
             "text/bbcode": "BBCode"
@@ -154,7 +154,7 @@
         "data_import_export_tab": "Data Import / Export",
         "default_vis": "Standaard zichtbaarheidsbereik",
         "delete_account": "Account Verwijderen",
-        "delete_account_description": "Permanent je account en al je berichten verwijderen.",
+        "delete_account_description": "Permanent je gegevens verwijderen en account deactiveren.",
         "delete_account_error": "Er is een fout opgetreden bij het verwijderen van je account. Indien dit probleem zich voor blijft doen, neem dan contact op met de beheerder van deze instantie.",
         "delete_account_instructions": "Voer je wachtwoord in het onderstaande invoerveld in om het verwijderen van je account te bevestigen.",
         "export_theme": "Preset opslaan",
@@ -262,7 +262,14 @@
                     "future_version_imported": "Het geïmporteerde bestand is gemaakt voor een nieuwere versie van FE.",
                     "older_version_imported": "Het geïmporteerde bestand is gemaakt voor een oudere versie van FE.",
                     "upgraded_from_v2": "PleromaFE is bijgewerkt, het thema kan iets anders uitzien dan dat je gewend bent.",
-                    "v2_imported": "Het geïmporteerde bestand is gemaakt voor een oudere FE. We proberen compatibiliteit te maximaliseren, maar het kan toch voorkomen dat er inconsistenties zijn."
+                    "v2_imported": "Het geïmporteerde bestand is gemaakt voor een oudere FE. We proberen compatibiliteit te maximaliseren, maar het kan toch voorkomen dat er inconsistenties zijn.",
+                    "snapshot_source_mismatch": "Versie conflict: waarschijnlijk was FE terug gerold en opnieuw bijgewerkt, indien je het thema aangepast hebt met de oudere versie van FE wil je waarschijnlijk de oude versie gebruiken, gebruik anders de nieuwe versie.",
+                    "migration_napshot_gone": "Voor een onduidelijke reden mist de momentopname, dus sommige dingen kunnen anders uitzien dan je gewend bent.",
+                    "migration_snapshot_ok": "Voor de zekerheid is een momentopname van het thema geladen. Je kunt proberen om de thema gegevens te laden.",
+                    "fe_downgraded": "PleromaFE's versie is terug gerold.",
+                    "fe_upgraded": "De thema-engine van PleromaFE is bijgewerkt na de versie update.",
+                    "snapshot_missing": "Het bestand bevat geen thema momentopname, dus het thema kan anders uitzien dan je oorspronkelijk bedacht had.",
+                    "snapshot_present": "Thema momentopname is geladen, alle waarden zijn overschreven. Je kunt in plaats daarvan ook de daadwerkelijke data van het thema laden."
                 },
                 "load_theme": "Thema laden"
             },
@@ -270,7 +277,7 @@
                 "color": "Kleur",
                 "opacity": "Transparantie",
                 "contrast": {
-                    "hint": "Contrast ratio is {ratio}, {level} {context}",
+                    "hint": "Contrast verhouding is {ratio}, {level} {context}",
                     "level": {
                         "aa": "voldoet aan de richtlijn van niveau AA (minimum)",
                         "aaa": "voldoet aan de richtlijn van niveau AAA (aangeraden)",
@@ -283,8 +290,8 @@
                 }
             },
             "common_colors": {
-                "_tab_label": "Gemeenschappelijk",
-                "main": "Gemeenschappelijke kleuren",
+                "_tab_label": "Algemeen",
+                "main": "Algemene kleuren",
                 "foreground_hint": "Zie \"Geavanceerd\" tab voor meer gedetailleerde controle",
                 "rgbo": "Iconen, accenten, badges"
             },
@@ -294,58 +301,73 @@
                 "alert_error": "Fout",
                 "badge": "Badge achtergrond",
                 "badge_notification": "Meldingen",
-                "panel_header": "Paneel hoofding",
-                "top_bar": "Top bar",
+                "panel_header": "Paneel koptekst",
+                "top_bar": "Top balk",
                 "borders": "Randen",
                 "buttons": "Knoppen",
                 "inputs": "Invoervelden",
-                "faint_text": "Vervaagde tekst"
+                "faint_text": "Vervaagde tekst",
+                "tabs": "Tabbladen",
+                "toggled": "Geschakeld",
+                "disabled": "Uitgeschakeld",
+                "selectedMenu": "Geselecteerd menu item",
+                "selectedPost": "Geselecteerd bericht",
+                "pressed": "Ingedrukt",
+                "highlight": "Gemarkeerde elementen",
+                "icons": "Iconen",
+                "poll": "Poll grafiek",
+                "underlay": "Onderlaag",
+                "popover": "Tooltips, menu's, popovers",
+                "post": "Berichten / Gebruiker bios",
+                "alert_neutral": "Neutraal",
+                "alert_warning": "Waarschuwing"
             },
             "radii": {
                 "_tab_label": "Rondheid"
             },
             "shadows": {
                 "_tab_label": "Schaduw en belichting",
-                "component": "Component",
+                "component": "Onderdeel",
                 "override": "Overschrijven",
                 "shadow_id": "Schaduw #{value}",
                 "blur": "Vervagen",
-                "spread": "Spreid",
+                "spread": "Spreiding",
                 "inset": "Inzet",
                 "hint": "Voor schaduw kan je ook --variable gebruiken als een kleur waarde om CSS3 variabelen te gebruiken. Houd er rekening mee dat het instellen van opaciteit in dit geval niet werkt.",
                 "filter_hint": {
                     "always_drop_shadow": "Waarschuwing, deze schaduw gebruikt altijd {0} als de browser dit ondersteund.",
                     "drop_shadow_syntax": "{0} ondersteund niet de {1} parameter en {2} sleutelwoord.",
-                    "avatar_inset": "Houd er rekening mee dat het combineren van zowel inzet and niet-inzet schaduwen op transparante avatars onverwachte resultaten kan opleveren.",
+                    "avatar_inset": "Houdt er rekening mee dat het combineren van zowel inzet and niet-inzet schaduwen op transparante avatars onverwachte resultaten kan opleveren.",
                     "spread_zero": "Schaduw met spreiding > 0 worden weergegeven alsof ze op nul staan",
                     "inset_classic": "Inzet schaduw zal {0} gebruiken"
                 },
                 "components": {
                     "panel": "Paneel",
-                    "panelHeader": "Paneel hoofding",
-                    "topBar": "Top bar",
-                    "avatar": "Gebruiker avatar (in profiel weergave)",
-                    "avatarStatus": "Gebruiker avatar  (in post weergave)",
-                    "popup": "Popups en gereedschapstips",
+                    "panelHeader": "Paneel koptekst",
+                    "topBar": "Top balk",
+                    "avatar": "Gebruikers avatar (in profiel weergave)",
+                    "avatarStatus": "Gebruikers avatar (in bericht weergave)",
+                    "popup": "Popups en tooltips",
                     "button": "Knop",
                     "buttonHover": "Knop (zweven)",
                     "buttonPressed": "Knop (ingedrukt)",
                     "buttonPressedHover": "Knop (ingedrukt+zweven)",
                     "input": "Invoerveld"
-                }
+                },
+                "hintV3": "Voor schaduwen kun je ook de {0} notatie gebruiken om de andere kleur invoer te gebruiken."
             },
             "fonts": {
                 "_tab_label": "Lettertypes",
-                "help": "Selecteer het lettertype om te gebruiken voor elementen van de UI.Voor \"aangepast\" moet je de exacte naam van het lettertype invoeren zoals die in het systeem wordt weergegeven.",
+                "help": "Selecteer het lettertype om te gebruiken voor elementen van de UI. Voor \"aangepast\" dien je de exacte naam van het lettertype in te voeren zoals die in het systeem wordt weergegeven.",
                 "components": {
                     "interface": "Interface",
                     "input": "Invoervelden",
-                    "post": "Post tekst",
-                    "postCode": "Monospaced tekst in een post (rich text)"
+                    "post": "Bericht tekst",
+                    "postCode": "Monospaced tekst in een bericht (rich text)"
                 },
-                "family": "Naam lettertype",
+                "family": "Lettertype naam",
                 "size": "Grootte (in px)",
-                "weight": "Gewicht (vetheid)",
+                "weight": "Gewicht (dikgedruktheid)",
                 "custom": "Aangepast"
             },
             "preview": {
@@ -355,12 +377,12 @@
                 "button": "Knop",
                 "text": "Nog een boel andere {0} en {1}",
                 "mono": "inhoud",
-                "input": "Tijd voor een pauze!",
+                "input": "Zojuist geland in L.A.",
                 "faint_link": "handige gebruikershandleiding",
                 "fine_print": "Lees onze {0} om niets nuttig te leren!",
                 "header_faint": "Alles komt goed",
-                "checkbox": "Ik heb de gebruikersvoorwaarden eens van ver bekeken",
-                "link": "een link"
+                "checkbox": "Ik heb de gebruikersvoorwaarden gelezen",
+                "link": "een leuke kleine link"
             }
         },
         "notification_setting_follows": "Gebruikers die je volgt",
@@ -443,24 +465,31 @@
         "show_moderator_badge": "Moderators badge tonen in mijn profiel",
         "notification_setting_filters": "Filters",
         "notification_setting_non_followers": "Gebruikers die je niet volgen",
-        "notification_blocks": "Door een gebruiker te blokkeren, ontvang je geen meldingen meer van de gebruiker en wordt je abonnement op de gebruiker opgeheven."
+        "notification_blocks": "Door een gebruiker te blokkeren, ontvang je geen meldingen meer van de gebruiker en wordt je abonnement op de gebruiker opgeheven.",
+        "version": {
+            "frontend_version": "Frontend Versie",
+            "backend_version": "Backend Versie",
+            "title": "Versie"
+        }
     },
     "timeline": {
         "collapse": "Inklappen",
         "conversation": "Conversatie",
         "error_fetching": "Fout bij ophalen van updates",
-        "load_older": "Laad oudere Statussen",
-        "no_retweet_hint": "Post is gemarkeerd als enkel volgers of direct en kan niet worden herhaald",
+        "load_older": "Oudere statussen laden",
+        "no_retweet_hint": "Bericht is gemarkeerd als enkel volgers of direct en kan niet worden herhaald",
         "repeated": "herhaalde",
-        "show_new": "Toon nieuwe",
-        "up_to_date": "Up-to-date"
+        "show_new": "Nieuwe tonen",
+        "up_to_date": "Up-to-date",
+        "no_statuses": "Geen statussen",
+        "no_more_statuses": "Geen statussen meer"
     },
     "user_card": {
         "approve": "Goedkeuren",
         "block": "Blokkeren",
         "blocked": "Geblokkeerd!",
-        "deny": "Ontzeggen",
-        "favorites": "Vind-ik-leuks",
+        "deny": "Weigeren",
+        "favorites": "Favorieten",
         "follow": "Volgen",
         "follow_sent": "Aanvraag verzonden!",
         "follow_progress": "Aanvragen…",
@@ -471,31 +500,69 @@
         "following": "Aan het volgen!",
         "follows_you": "Volgt jou!",
         "its_you": "'t is jij!",
-        "mute": "Dempen",
-        "muted": "Gedempt",
+        "mute": "Negeren",
+        "muted": "Genegeerd",
         "per_day": "per dag",
         "remote_follow": "Volg vanop afstand",
-        "statuses": "Statussen"
+        "statuses": "Statussen",
+        "admin_menu": {
+            "delete_user_confirmation": "Weet je het heel zeker? Deze uitvoering kan niet ongedaan worden gemaakt.",
+            "delete_user": "Gebruiker verwijderen",
+            "quarantine": "Federeren van gebruikers berichten verbieden",
+            "disable_any_subscription": "Volgen van gebruiker in zijn geheel verbieden",
+            "disable_remote_subscription": "Volgen van gebruiker vanaf andere instanties verbieden",
+            "sandbox": "Berichten forceren om alleen voor volgers zichtbaar te zijn",
+            "force_unlisted": "Berichten forceren om niet publiekelijk getoond te worden",
+            "strip_media": "Media van berichten verwijderen",
+            "force_nsfw": "Alle berichten als gevoelig markeren",
+            "delete_account": "Account verwijderen",
+            "deactivate_account": "Account deactiveren",
+            "activate_account": "Account activeren",
+            "revoke_moderator": "Moderatorsrechten intrekken",
+            "grant_moderator": "Moderatorsrechten toekennen",
+            "revoke_admin": "Beheerdersrechten intrekken",
+            "grant_admin": "Beheerdersrechten toekennen",
+            "moderation": "Moderatie"
+        },
+        "show_repeats": "Herhalingen tonen",
+        "hide_repeats": "Herhalingen verbergen",
+        "mute_progress": "Negeren...",
+        "unmute_progress": "Negering opheffen...",
+        "unmute": "Negering opheffen",
+        "block_progress": "Blokkeren...",
+        "unblock_progress": "Blokkade opheffen...",
+        "unblock": "Blokkade opheffen",
+        "unsubscribe": "Abonnement opzeggen",
+        "subscribe": "Abonneren",
+        "report": "Aangeven",
+        "mention": "Vermelding",
+        "media": "Media",
+        "hidden": "Verborgen"
     },
     "user_profile": {
-        "timeline_title": "Gebruikers Tijdlijn"
+        "timeline_title": "Gebruikers Tijdlijn",
+        "profile_loading_error": "Sorry, er is een fout opgetreden bij het laden van dit profiel.",
+        "profile_does_not_exist": "Sorry, dit profiel bestaat niet."
     },
     "who_to_follow": {
         "more": "Meer",
         "who_to_follow": "Wie te volgen"
     },
     "tool_tip": {
-        "media_upload": "Upload Media",
-        "repeat": "Herhaal",
-        "reply": "Antwoord",
-        "favorite": "Vind-ik-leuk",
-        "user_settings": "Gebruikers Instellingen"
+        "media_upload": "Media Uploaden",
+        "repeat": "Herhalen",
+        "reply": "Beantwoorden",
+        "favorite": "Favoriet maken",
+        "user_settings": "Gebruikers Instellingen",
+        "reject_follow_request": "Volg-verzoek afwijzen",
+        "accept_follow_request": "Volg-aanvraag accepteren",
+        "add_reaction": "Reactie toevoegen"
     },
     "upload": {
         "error": {
-            "base": "Upload gefaald.",
+            "base": "Upload mislukt.",
             "file_too_big": "Bestand is te groot [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-            "default": "Probeer later opnieuw"
+            "default": "Probeer het later opnieuw"
         },
         "file_size_units": {
             "B": "B",
@@ -512,7 +579,8 @@
                 "reject": "Afwijzen",
                 "replace": "Vervangen",
                 "is_replaced_by": "→",
-                "keyword_policies": "Zoekwoord Beleid"
+                "keyword_policies": "Zoekwoord Beleid",
+                "ftl_removal": "Verwijdering van \"Het Geheel Bekende Netwerk\" Tijdlijn"
             },
             "mrf_policies_desc": "MRF regels beïnvloeden het federatie gedrag van de instantie. De volgende regels zijn ingeschakeld:",
             "mrf_policies": "Ingeschakelde MRF Regels",
@@ -524,20 +592,21 @@
                 "reject_desc": "Deze instantie zal geen berichten accepteren van de volgende instanties:",
                 "quarantine": "Quarantaine",
                 "quarantine_desc": "Deze instantie zal alleen publieke berichten sturen naar de volgende instanties:",
-                "ftl_removal_desc": "Deze instantie verwijdert de volgende instanties van \"Het Geheel Gekende Netwerk\" tijdlijn:",
+                "ftl_removal_desc": "Deze instantie verwijdert de volgende instanties van \"Het Geheel Bekende Netwerk\" tijdlijn:",
                 "media_removal_desc": "Deze instantie verwijdert media van berichten van de volgende instanties:",
                 "media_nsfw_desc": "Deze instantie stelt media in als gevoelig in berichten van de volgende instanties:",
                 "ftl_removal": "Verwijderen van \"Het Geheel Bekende Netwerk\" Tijdlijn",
-                "media_removal": "Media Verwijdering"
+                "media_removal": "Media Verwijdering",
+                "media_nsfw": "Forceer Media als Gevoelig"
             }
         },
         "staff": "Personeel"
     },
     "domain_mute_card": {
-        "mute": "Dempen",
-        "mute_progress": "Dempen...",
-        "unmute": "Dempen opheffen",
-        "unmute_progress": "Dempen wordt opgeheven..."
+        "mute": "Negeren",
+        "mute_progress": "Negeren...",
+        "unmute": "Negering opheffen",
+        "unmute_progress": "Negering wordt opgeheven..."
     },
     "exporter": {
         "export": "Exporteren",
@@ -564,7 +633,7 @@
         "option": "Optie",
         "votes": "stemmen",
         "vote": "Stem",
-        "single_choice": "Enkelkeuze",
+        "single_choice": "Enkele keuze",
         "multiple_choices": "Meerkeuze",
         "expiry": "Poll leeftijd",
         "expires_in": "Poll eindigt in {0}",
@@ -574,26 +643,105 @@
     },
     "emoji": {
         "emoji": "Emoji",
-        "keep_open": "Houdt picker open",
+        "keep_open": "Picker openhouden",
         "search_emoji": "Zoek voor een emoji",
         "add_emoji": "Emoji invoegen",
         "unicode": "Unicode emoji",
         "load_all": "Alle {emojiAmount} emoji worden geladen",
         "stickers": "Stickers",
-        "load_all_hint": "Eerste {saneAmount} emoji geladen, alle emoji tegelijk laden kan prestatie problemen veroorzaken.",
+        "load_all_hint": "Eerste {saneAmount} emoji geladen, alle emoji tegelijk laden kan problemen veroorzaken met prestaties.",
         "custom": "Gepersonaliseerde emoji"
     },
     "interactions": {
         "favs_repeats": "Herhalingen en Favorieten",
-        "follows": "Nieuwe volgers",
+        "follows": "Nieuwe volgingen",
         "moves": "Gebruiker migreert",
         "load_older": "Oudere interacties laden"
     },
     "remote_user_resolver": {
         "searching_for": "Zoeken naar",
-        "error": "Niet gevonden."
+        "error": "Niet gevonden.",
+        "remote_user_resolver": "Externe gebruikers zoeker"
     },
     "selectable_list": {
         "select_all": "Alles selecteren"
+    },
+    "password_reset": {
+        "password_reset_required_but_mailer_is_disabled": "Je dient je wachtwoord opnieuw in te stellen, maar wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
+        "password_reset_required": "Je dient je wachtwoord opnieuw in te stellen om in te kunnen loggen.",
+        "password_reset_disabled": "Wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
+        "too_many_requests": "Je hebt het maximaal aantal pogingen bereikt, probeer het later opnieuw.",
+        "not_found": "We kunnen die email of gebruikersnaam niet vinden.",
+        "return_home": "Terugkeren naar de home pagina",
+        "check_email": "Controleer je email inbox voor een link om je wachtwoord opnieuw in te stellen.",
+        "placeholder": "Je email of gebruikersnaam",
+        "instruction": "Voer je email adres of gebruikersnaam in. We sturen je een link om je wachtwoord opnieuw in te stellen.",
+        "password_reset": "Wachtwoord opnieuw instellen",
+        "forgot_password": "Wachtwoord vergeten?"
+    },
+    "search": {
+        "no_results": "Geen resultaten",
+        "people_talking": "{count} personen aan het praten",
+        "person_talking": "{count} persoon aan het praten",
+        "hashtags": "Hashtags",
+        "people": "Personen"
+    },
+    "user_reporting": {
+        "generic_error": "Er is een fout opgetreden tijdens het verwerken van je verzoek.",
+        "submit": "Verzenden",
+        "forward_to": "Doorsturen naar {0}",
+        "forward_description": "Dit account hoort bij een andere server. Wil je een kopie van het rapport ook daarheen sturen?",
+        "additional_comments": "Aanvullende opmerkingen",
+        "add_comment_description": "Het rapport zal naar de moderators van de instantie worden verstuurd. Je kunt hieronder uitleg bijvoegen waarom je dit account wilt aangeven:",
+        "title": "{0} aangeven"
+    },
+    "status": {
+        "copy_link": "Link naar status kopiëren",
+        "status_unavailable": "Status niet beschikbaar",
+        "unmute_conversation": "Conversatie niet meer negeren",
+        "mute_conversation": "Conversatie negeren",
+        "replies_list": "Antwoorden:",
+        "reply_to": "Antwoorden aan",
+        "delete_confirm": "Wil je echt deze status verwijderen?",
+        "pin": "Aan profiel vastmaken",
+        "pinned": "Vastgezet",
+        "unpin": "Van profiel losmaken",
+        "delete": "Status verwijderen",
+        "repeats": "Herhalingen",
+        "favorites": "Favorieten"
+    },
+    "time": {
+        "years_short": "{0}j",
+        "year_short": "{0}j",
+        "years": "{0} jaren",
+        "year": "{0} jaar",
+        "weeks_short": "{0}w",
+        "week_short": "{0}w",
+        "weeks": "{0} weken",
+        "week": "{0} week",
+        "seconds_short": "{0}s",
+        "second_short": "{0}s",
+        "seconds": "{0} seconden",
+        "second": "{0} seconde",
+        "now_short": "nu",
+        "now": "zojuist",
+        "months_short": "{0}ma",
+        "month_short": "{0}ma",
+        "months": "{0} maanden",
+        "month": "{0} maand",
+        "minutes_short": "{0}min",
+        "minute_short": "{0}min",
+        "minutes": "{0} minuten",
+        "minute": "{0} minuut",
+        "in_past": "{0} geleden",
+        "in_future": "over {0}",
+        "hours_short": "{0}u",
+        "hour_short": "{0}u",
+        "hours": "{0} uren",
+        "hour": "{0} uur",
+        "days_short": "{0}d",
+        "day_short": "{0}d",
+        "days": "{0} dagen",
+        "day": "{0} dag"
     }
 }

From 0a2aeb5d1a7ce0bf13cc09e57a2a5a877216ace2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C4=99drzej=20Tomaszewski?= <jederow@hotmail.com>
Date: Sat, 16 May 2020 13:49:12 +0000
Subject: [PATCH 392/483] Translated using Weblate (Polish)

Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/pl/
---
 src/i18n/pl.json | 64 ++++++++++++++++++++++++------------------------
 1 file changed, 32 insertions(+), 32 deletions(-)

diff --git a/src/i18n/pl.json b/src/i18n/pl.json
index 89f2fdc2..101e5d39 100644
--- a/src/i18n/pl.json
+++ b/src/i18n/pl.json
@@ -27,7 +27,7 @@
                 "media_nsfw_desc": "Ta instancja wymusza, by multimedia z wymienionych instancji były ustawione jako wrażliwe:"
             }
         },
-        "staff": "Obsługa"
+        "staff": "Administracja"
     },
     "chat": {
         "title": "Czat"
@@ -40,7 +40,7 @@
     },
     "exporter": {
         "export": "Eksportuj",
-        "processing": "Przetwarzam, za chwilę zostaniesz zapytany o ściągnięcie pliku"
+        "processing": "Przetwarzam, za chwilę zostaniesz zapytany(-na) o ściągnięcie pliku"
     },
     "features_panel": {
         "chat": "Czat",
@@ -131,7 +131,7 @@
         "no_more_notifications": "Nie masz więcej powiadomień",
         "migrated_to": "wyemigrował do",
         "reacted_with": "zareagował z {0}",
-        "follow_request": "chce cię obserwować"
+        "follow_request": "chce ciebie obserwować"
     },
     "polls": {
         "add_poll": "Dodaj ankietę",
@@ -143,7 +143,7 @@
         "single_choice": "jednokrotnego wyboru",
         "multiple_choices": "wielokrotnego wyboru",
         "expiry": "Czas trwania ankiety",
-        "expires_in": "Ankieta kończy się za{0}",
+        "expires_in": "Ankieta kończy się za {0}",
         "expired": "Ankieta skończyła się {0} temu",
         "not_enough_options": "Zbyt mało unikalnych opcji w ankiecie"
     },
@@ -232,10 +232,10 @@
             "confirm_and_enable": "Potwierdź i włącz OTP",
             "title": "Weryfikacja dwuetapowa",
             "generate_new_recovery_codes": "Wygeneruj nowe kody zapasowe",
-            "warning_of_generate_new_codes": "Po tym gdy generujesz nowe kody zapasowe, stare przestaną działać.",
+            "warning_of_generate_new_codes": "Po tym gdy wygenerujesz nowe kody zapasowe, stare przestaną działać.",
             "recovery_codes": "Kody zapasowe.",
             "waiting_a_recovery_codes": "Otrzymuję kody zapasowe...",
-            "recovery_codes_warning": "Spisz kody na kartce papieru, albo zapisz je w bezpiecznym miejscu - inaczej nie zobaczysz ich już nigdy. Jeśli stracisz dostęp do twojej aplikacji 2FA i kodów zapasowych, nie będziesz miał dostępu do swojego konta.",
+            "recovery_codes_warning": "Spisz kody na kartce papieru, albo zapisz je w bezpiecznym miejscu - inaczej nie zobaczysz ich już nigdy. Jeśli stracisz dostęp do twojej aplikacji 2FA i kodów zapasowych, nie będziesz miał(-a) dostępu do swojego konta.",
             "authentication_methods": "Metody weryfikacji",
             "scan": {
                 "title": "Skanuj",
@@ -281,7 +281,7 @@
         "data_import_export_tab": "Import/eksport danych",
         "default_vis": "Domyślny zakres widoczności",
         "delete_account": "Usuń konto",
-        "delete_account_description": "Trwale usuń konto i wszystkie posty.",
+        "delete_account_description": "Trwale usuń dane i zdezaktywuj konto.",
         "delete_account_error": "Wystąpił problem z usuwaniem twojego konta. Jeżeli problem powtarza się, poinformuj administratora swojej instancji.",
         "delete_account_instructions": "Wprowadź swoje hasło w poniższe pole aby potwierdzić usunięcie konta.",
         "discoverable": "Zezwól na odkrywanie tego konta w wynikach wyszukiwania i innych usługach",
@@ -315,14 +315,14 @@
         "import_theme": "Załaduj motyw",
         "inputRadius": "Pola tekstowe",
         "checkboxRadius": "Pola wyboru",
-        "instance_default": "(domyślny: {value})",
-        "instance_default_simple": "(domyślny)",
+        "instance_default": "(domyślnie: {value})",
+        "instance_default_simple": "(domyślne)",
         "interface": "Interfejs",
         "interfaceLanguage": "Język interfejsu",
         "invalid_theme_imported": "Wybrany plik nie jest obsługiwanym motywem Pleromy. Nie dokonano zmian w twoim motywie.",
         "limited_availability": "Niedostępne w twojej przeglądarce",
         "links": "Łącza",
-        "lock_account_description": "Ogranicz swoje konto dla zatwierdzonych obserwowanych",
+        "lock_account_description": "Spraw, by konto mogli wyświetlać tylko zatwierdzeni obserwujący",
         "loop_video": "Zapętlaj filmy",
         "loop_video_silent_only": "Zapętlaj tylko filmy bez dźwięku (np. mastodonowe „gify”)",
         "mutes_tab": "Wyciszenia",
@@ -420,7 +420,7 @@
                 "keep_opacity": "Zachowaj widoczność",
                 "keep_roundness": "Zachowaj zaokrąglenie",
                 "keep_fonts": "Zachowaj czcionki",
-                "save_load_hint": "Opcje „zachowaj” pozwalają na pozostanie przy obecnych opcjach po wybraniu lub załadowaniu motywu, jak i przechowywanie ich podczas eksportowania motywu. Jeżeli wszystkie są odznaczone, eksportowanie motywu spowoduje zapisanie wszystkiego.",
+                "save_load_hint": "Opcje „zachowaj” pozwalają na pozostanie przy obecnych opcjach po wybraniu lub załadowaniu motywu, jak i przechowywanie ich podczas eksportowania motywu. Jeżeli wszystkie opcje są odznaczone, eksportowanie motywu spowoduje zapisanie wszystkiego.",
                 "reset": "Wyzeruj",
                 "clear_all": "Wyczyść wszystko",
                 "clear_opacity": "Wyczyść widoczność",
@@ -429,17 +429,17 @@
                 "use_snapshot": "Stara wersja",
                 "use_source": "Nowa wersja",
                 "help": {
-                    "upgraded_from_v2": "PleromaFE zostało zaaktualizowane, motyw może wyglądać nieco inaczej niż sobie zapamiętałeś.",
-                    "v2_imported": "Plik który zaimportowałeś został stworzony dla starszego FE. Próbujemy zwiększyć kompatybiliność, lecz wciąż mogą występować rozbieżności.",
-                    "future_version_imported": "Plik który zaimportowałeś został stworzony w nowszej wersji FE.",
-                    "older_version_imported": "Plik który zaimportowałeś został stworzony w starszej wersji FE.",
+                    "upgraded_from_v2": "PleromaFE zostało zaaktualizowane, motyw może wyglądać nieco inaczej niż zapamiętałeś(-aś).",
+                    "v2_imported": "Plik który zaimportowałeś(-aś) został stworzony dla starszego FE. Próbujemy zwiększyć kompatybilność, lecz wciąż mogą występować rozbieżności.",
+                    "future_version_imported": "Plik który zaimportowałeś(-aś) został stworzony w nowszej wersji FE.",
+                    "older_version_imported": "Plik który zaimportowałeś(-aś) został stworzony w starszej wersji FE.",
                     "snapshot_present": "Migawka motywu jest załadowana, więc wszystkie wartości zostały nadpisane. Zamiast tego możesz załadować właściwe dane motywu.",
                     "snapshot_missing": "Nie znaleziono migawki motywu w pliku, więc motyw może wyglądać inaczej niż pierwotnie zaplanowano.",
                     "fe_upgraded": "Silnik motywów PleromaFE został zaaktualizowany.",
                     "fe_downgraded": "Wersja PleromaFE została cofnięta.",
                     "migration_snapshot_ok": "Żeby być bezpiecznym, migawka motywu została załadowana. Możesz spróbować załadować dane motywu.",
-                    "migration_napshot_gone": "Z jakiegoś powodu migawka zniknęła, niektóre rzeczy mogą wyglądać inaczej niż sobie zapamiętałeś.",
-                    "snapshot_source_mismatch": "Konflikt wersji: najprawdopodobniej FE zostało cofnięte do poprzedniej wersji i zaaktualizowane ponownie, jeśli zmieniłeś motyw używając starszej wersji FE, najprawdopodobniej chcesz używać starszej wersji, w przeciwnym razie użyj nowej wersji."
+                    "migration_napshot_gone": "Z jakiegoś powodu migawka zniknęła, niektóre rzeczy mogą wyglądać inaczej niż zapamiętałeś(-aś).",
+                    "snapshot_source_mismatch": "Konflikt wersji: najprawdopodobniej FE zostało cofnięte do poprzedniej wersji i zaktualizowane ponownie, jeśli zmieniłeś(-aś) motyw używając starszej wersji FE, najprawdopodobniej chcesz używać starszej wersji, w przeciwnym razie użyj nowej wersji."
                 }
             },
             "common": {
@@ -506,7 +506,7 @@
                 "filter_hint": {
                     "always_drop_shadow": "Ostrzeżenie, ten cień zawsze używa {0} jeżeli to obsługiwane przez przeglądarkę.",
                     "drop_shadow_syntax": "{0} nie obsługuje parametru {1} i słowa kluczowego {2}.",
-                    "avatar_inset": "Pamiętaj że użycie jednocześnie cieni inset i nie inset na awatarach może daćnieoczekiwane wyniki z przezroczystymi awatarami.",
+                    "avatar_inset": "Pamiętaj że użycie jednocześnie cieni inset i nie inset na awatarach może dać nieoczekiwane wyniki z przezroczystymi awatarami.",
                     "spread_zero": "Cienie o ujemnej szerokości będą widoczne tak, jakby wynosiła ona zero",
                     "inset_classic": "Cienie inset będą używały {0}"
                 },
@@ -549,7 +549,7 @@
                 "faint_link": "pomocny podręcznik",
                 "fine_print": "Przeczytaj nasz {0}, aby nie nauczyć się niczego przydatnego!",
                 "header_faint": "W porządku",
-                "checkbox": "Przeleciałem przez zasady użytkowania",
+                "checkbox": "Przeleciałem(-am) przez zasady użytkowania",
                 "link": "i fajny mały odnośnik"
             }
         },
@@ -565,8 +565,8 @@
     "time": {
         "day": "{0} dzień",
         "days": "{0} dni",
-        "day_short": "{0}d",
-        "days_short": "{0}d",
+        "day_short": "{0} d",
+        "days_short": "{0} d",
         "hour": "{0} godzina",
         "hours": "{0} godzin",
         "hour_short": "{0} godz.",
@@ -575,8 +575,8 @@
         "in_past": "{0} temu",
         "minute": "{0} minuta",
         "minutes": "{0} minut",
-        "minute_short": "{0}min",
-        "minutes_short": "{0}min",
+        "minute_short": "{0} min",
+        "minutes_short": "{0} min",
         "month": "{0} miesiąc",
         "months": "{0} miesięcy",
         "month_short": "{0} mies.",
@@ -585,8 +585,8 @@
         "now_short": "teraz",
         "second": "{0} sekunda",
         "seconds": "{0} sekund",
-        "second_short": "{0}s",
-        "seconds_short": "{0}s",
+        "second_short": "{0} s",
+        "seconds_short": "{0} s",
         "week": "{0} tydzień",
         "weeks": "{0} tygodni",
         "week_short": "{0} tydz.",
@@ -646,7 +646,7 @@
         "muted": "Wyciszony(-a)",
         "per_day": "dziennie",
         "remote_follow": "Zdalna obserwacja",
-        "report": "Raportuj",
+        "report": "Zgłoś",
         "statuses": "Statusy",
         "subscribe": "Subskrybuj",
         "unsubscribe": "Odsubskrybuj",
@@ -675,7 +675,7 @@
             "disable_any_subscription": "Zakaż całkowicie obserwowania użytkownika",
             "quarantine": "Zakaż federowania postów od tego użytkownika",
             "delete_user": "Usuń użytkownika",
-            "delete_user_confirmation": "Czy jesteś absolutnie pewny? Ta operacja nie może być cofnięta."
+            "delete_user_confirmation": "Czy jesteś absolutnie pewny(-a)? Ta operacja nie może być cofnięta."
         }
     },
     "user_profile": {
@@ -685,10 +685,10 @@
     },
     "user_reporting": {
         "title": "Raportowanie {0}",
-        "add_comment_description": "Raport zostanie wysłany do moderatorów instancji. Możesz dodać powód dlaczego raportujesz to konto poniżej:",
+        "add_comment_description": "Zgłoszenie zostanie wysłane do moderatorów instancji. Możesz dodać powód dlaczego zgłaszasz owe konto poniżej:",
         "additional_comments": "Dodatkowe komentarze",
-        "forward_description": "To konto jest z innego serwera. Wysłać również tam kopię raportu?",
-        "forward_to": "Przekaż do{0}",
+        "forward_description": "To konto jest z innego serwera. Wysłać również tam kopię zgłoszenia?",
+        "forward_to": "Przekaż do {0}",
         "submit": "Wyślij",
         "generic_error": "Wystąpił błąd podczas przetwarzania twojej prośby."
     },
@@ -728,14 +728,14 @@
         "no_results": "Brak wyników"
     },
     "password_reset": {
-        "forgot_password": "Zapomniałeś hasła?",
+        "forgot_password": "Zapomniałeś(-aś) hasła?",
         "password_reset": "Reset hasła",
         "instruction": "Wprowadź swój adres email lub nazwę użytkownika. Wyślemy ci link z którym możesz zresetować hasło.",
         "placeholder": "Twój email lub nazwa użytkownika",
         "check_email": "Sprawdź pocztę, aby uzyskać link do zresetowania hasła.",
         "return_home": "Wróć do strony głównej",
         "not_found": "Nie mogliśmy znaleźć tego emaila lub nazwy użytkownika.",
-        "too_many_requests": "Przekroczyłeś limit prób, spróbuj ponownie później.",
+        "too_many_requests": "Przekroczyłeś(-aś) limit prób, spróbuj ponownie później.",
         "password_reset_disabled": "Resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji.",
         "password_reset_required": "Musisz zresetować hasło, by się zalogować.",
         "password_reset_required_but_mailer_is_disabled": "Musisz zresetować hasło, ale resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji."

From 866fe78bbaa4285f2ee96ea500c33a0f5d9ecfdd Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Sat, 16 May 2020 11:22:34 +0000
Subject: [PATCH 393/483] Translated using Weblate (Russian)

Currently translated at 61.1% (375 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
---
 src/i18n/ru.json | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 3c52416c..357d97a8 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -16,7 +16,9 @@
         "verify": "Проверить",
         "more": "Больше",
         "generic_error": "Произошла ошибка",
-        "optional": "не обязательно"
+        "optional": "не обязательно",
+        "show_less": "Показать меньше",
+        "show_more": "Показать больше"
     },
     "login": {
         "login": "Войти",

From e300f08e09b2f1cb444d2127fb2d06d904f39d15 Mon Sep 17 00:00:00 2001
From: Master Sparker <starmancer@protonmail.com>
Date: Fri, 15 May 2020 13:46:01 +0000
Subject: [PATCH 394/483] Translated using Weblate (Chinese (Simplified))

Currently translated at 92.0% (564 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/zh_Hans/
---
 src/i18n/zh.json | 1295 ++++++++++++++++++++++++----------------------
 1 file changed, 685 insertions(+), 610 deletions(-)

diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index 8d9462ab..095db754 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -1,623 +1,698 @@
 {
-  "chat": {
-    "title": "聊天"
-  },
-  "exporter": {
-    "export": "导出",
-    "processing": "正在处理,稍后会提示您下载文件"
-  },
-  "features_panel": {
-    "chat": "聊天",
-    "gopher": "Gopher",
-    "media_proxy": "媒体代理",
-    "scope_options": "可见范围设置",
-    "text_limit": "文本长度限制",
-    "title": "功能",
-    "who_to_follow": "推荐关注"
-  },
-  "finder": {
-    "error_fetching_user": "获取用户时发生错误",
-    "find_user": "寻找用户"
-  },
-  "general": {
-    "apply": "应用",
-    "submit": "提交",
-    "more": "更多",
-    "generic_error": "发生一个错误",
-    "optional": "可选项",
-    "show_more": "显示更多",
-    "show_less": "显示更少",
-    "cancel": "取消",
-    "disable": "禁用",
-    "enable": "启用",
-    "confirm": "确认",
-    "verify": "验证"
-  },
-  "image_cropper": {
-    "crop_picture": "裁剪图片",
-    "save": "保存",
-    "save_without_cropping": "保存未经裁剪的图片",
-    "cancel": "取消"
-  },
-  "importer": {
-    "submit": "提交",
-    "success": "导入成功。",
-    "error": "导入此文件时出现一个错误。"
-  },
-  "login": {
-    "login": "登录",
-    "description": "用 OAuth 登录",
-    "logout": "登出",
-    "password": "密码",
-    "placeholder": "例如:lain",
-    "register": "注册",
-    "username": "用户名",
-    "hint": "登录后加入讨论",
-    "authentication_code": "验证码",
-    "enter_recovery_code": "输入一个恢复码",
-    "enter_two_factor_code": "输入一个双重因素验证码",
-    "recovery_code": "恢复码",
-    "heading" : {
-      "totp" : "双重因素验证",
-      "recovery" : "双重因素恢复"
-    }
-  },
-  "media_modal": {
-    "previous": "往前",
-    "next": "往后"
-  },
-  "nav": {
-    "about": "关于",
-    "back": "Back",
-    "chat": "本地聊天",
-    "friend_requests": "关注请求",
-    "mentions": "提及",
-    "interactions": "互动",
-    "dms": "私信",
-    "public_tl": "公共时间线",
-    "timeline": "时间线",
-    "twkn": "所有已知网络",
-    "user_search": "用户搜索",
-    "search": "搜索",
-    "who_to_follow": "推荐关注",
-    "preferences": "偏好设置"
-  },
-  "notifications": {
-    "broken_favorite": "未知的状态,正在搜索中...",
-    "favorited_you": "收藏了你的状态",
-    "followed_you": "关注了你",
-    "load_older": "加载更早的通知",
-    "notifications": "通知",
-    "read": "阅读!",
-    "repeated_you": "转发了你的状态",
-    "no_more_notifications": "没有更多的通知"
-  },
-  "polls": {
-    "add_poll": "增加问卷调查",
-    "add_option": "增加选项",
-    "option": "选项",
-    "votes": "投票",
-    "vote": "投票",
-    "type": "问卷类型",
-    "single_choice": "单选项",
-    "multiple_choices": "多选项",
-    "expiry": "问卷的时间",
-    "expires_in": "投票于 {0} 内结束",
-    "expired": "投票 {0} 前已结束",
-    "not_enough_options": "投票的选项太少"
-  },
-  "stickers": {
-    "add_sticker": "添加贴纸"
-  },
-  "interactions": {
-    "favs_repeats": "转发和收藏",
-    "follows": "新的关注者",
-    "load_older": "加载更早的互动"
-  },
-  "post_status": {
-    "new_status": "发布新状态",
-    "account_not_locked_warning": "你的帐号没有 {0}。任何人都可以关注你并浏览你的上锁内容。",
-    "account_not_locked_warning_link": "上锁",
-    "attachments_sensitive": "标记附件为敏感内容",
-    "content_type": {
-      "text/plain": "纯文本",
-      "text/html": "HTML",
-      "text/markdown": "Markdown",
-      "text/bbcode": "BBCode"
+    "chat": {
+        "title": "聊天"
     },
-    "content_warning": "主题(可选)",
-    "default": "刚刚抵达上海",
-    "direct_warning_to_all": "本条内容只有被提及的用户能够看到。",
-    "direct_warning_to_first_only": "本条内容只有被在消息开始处提及的用户能够看到。",
-    "posting": "发送",
-    "scope_notice": {
-      "public": "本条内容可以被所有人看到",
-      "private": "关注你的人才能看到本条内容",
-      "unlisted": "本条内容既不在公共时间线,也不会在所有已知网络上可见"
+    "exporter": {
+        "export": "导出",
+        "processing": "正在处理,稍后会提示您下载文件"
     },
-    "scope": {
-      "direct": "私信 - 只发送给被提及的用户",
-      "private": "仅关注者 - 只有关注了你的人能看到",
-      "public": "公共 - 发送到公共时间轴",
-      "unlisted": "不公开 - 不会发送到公共时间轴"
-    }
-  },
-  "registration": {
-    "bio": "简介",
-    "email": "电子邮箱",
-    "fullname": "全名",
-    "password_confirm": "确认密码",
-    "registration": "注册",
-    "token": "邀请码",
-    "captcha": "CAPTCHA",
-    "new_captcha": "点击图片获取新的验证码",
-    "username_placeholder": "例如: lain",
-    "fullname_placeholder": "例如: Lain Iwakura",
-    "bio_placeholder": "例如:\n你好, 我是 Lain.\n我是一个住在上海的宅男。你可能在某处见过我。",
-    "validations": {
-      "username_required": "不能留空",
-      "fullname_required": "不能留空",
-      "email_required": "不能留空",
-      "password_required": "不能留空",
-      "password_confirmation_required": "不能留空",
-      "password_confirmation_match": "密码不一致"
-    }
-  },
-  "selectable_list": {
-    "select_all": "选择全部"
-  },
-  "settings": {
-    "app_name": "App 名称",
-    "security": "安全",
-    "enter_current_password_to_confirm": "输入你当前密码来确认你的身份",
-    "mfa": {
-      "otp" : "OTP",
-      "setup_otp" : "设置 OTP",
-      "wait_pre_setup_otp" : "预设 OTP",
-      "confirm_and_enable" : "确认并启用 OTP",
-      "title": "双因素验证",
-      "generate_new_recovery_codes" : "生成新的恢复码",
-      "warning_of_generate_new_codes" : "当你生成新的恢复码时,你的就恢复码就失效了。",
-      "recovery_codes" : "恢复码。",
-      "waiting_a_recovery_codes": "接受备份码。。。",
-      "recovery_codes_warning" : "抄写这些号码,或者保存在安全的地方。这些号码不会再次显示。如果你无法访问你的 2FA app,也丢失了你的恢复码,你的账号就再也无法登录了。",
-      "authentication_methods" : "身份验证方法",
-      "scan": {
-        "title": "扫一下",
-        "desc": "使用你的双因素验证 app,扫描这个二维码,或者输入这些文字密钥:",
-        "secret_code": "密钥"
-      },
-      "verify": {
-        "desc": "要启用双因素验证,请把你的双因素验证 app 里的数字输入:"
-      }
+    "features_panel": {
+        "chat": "聊天",
+        "gopher": "Gopher",
+        "media_proxy": "媒体代理",
+        "scope_options": "可见范围设置",
+        "text_limit": "文本长度限制",
+        "title": "功能",
+        "who_to_follow": "推荐关注"
     },
-    "attachmentRadius": "附件",
-    "attachments": "附件",
-    "autoload": "启用滚动到底部时的自动加载",
-    "avatar": "头像",
-    "avatarAltRadius": "头像(通知)",
-    "avatarRadius": "头像",
-    "background": "背景",
-    "bio": "简介",
-    "block_export": "拉黑名单导出",
-    "block_export_button": "导出你的拉黑名单到一个 csv 文件",
-    "block_import": "拉黑名单导入",
-    "block_import_error": "导入拉黑名单出错",
-    "blocks_imported": "拉黑名单导入成功!需要一点时间来处理。",
-    "blocks_tab": "块",
-    "btnRadius": "按钮",
-    "cBlue": "蓝色(回复,关注)",
-    "cGreen": "绿色(转发)",
-    "cOrange": "橙色(收藏)",
-    "cRed": "红色(取消)",
-    "change_password": "修改密码",
-    "change_password_error": "修改密码的时候出了点问题。",
-    "changed_password": "成功修改了密码!",
-    "collapse_subject": "折叠带主题的内容",
-    "composing": "正在书写",
-    "confirm_new_password": "确认新密码",
-    "current_avatar": "当前头像",
-    "current_password": "当前密码",
-    "current_profile_banner": "您当前的横幅图片",
-    "data_import_export_tab": "数据导入/导出",
-    "default_vis": "默认可见范围",
-    "delete_account": "删除账户",
-    "delete_account_description": "永久删除你的帐号和所有消息。",
-    "delete_account_error": "删除账户时发生错误,如果一直删除不了,请联系实例管理员。",
-    "delete_account_instructions": "在下面输入你的密码来确认删除账户",
-    "avatar_size_instruction": "推荐的头像图片最小的尺寸是 150x150 像素。",
-    "export_theme": "导出预置主题",
-    "filtering": "过滤器",
-    "filtering_explanation": "所有包含以下词汇的内容都会被隐藏,一行一个",
-    "follow_export": "导出关注",
-    "follow_export_button": "将关注导出成 csv 文件",
-    "follow_import": "导入关注",
-    "follow_import_error": "导入关注时错误",
-    "follows_imported": "关注已导入!尚需要一些时间来处理。",
-    "foreground": "前景",
-    "general": "通用",
-    "hide_attachments_in_convo": "在对话中隐藏附件",
-    "hide_attachments_in_tl": "在时间线上隐藏附件",
-    "hide_muted_posts": "不显示被隐藏的用户的帖子",
-    "max_thumbnails": "最多再每个帖子所能显示的缩略图数量",
-    "hide_isp": "隐藏指定实例的面板H",
-    "preload_images": "预载图片",
-    "use_one_click_nsfw": "点击一次以打开工作场所不适宜的附件",
-    "hide_post_stats": "隐藏推文相关的统计数据(例如:收藏的次数)",
-    "hide_user_stats": "隐藏用户的统计数据(例如:关注者的数量)",
-    "hide_filtered_statuses": "隐藏过滤的状态",
-    "import_blocks_from_a_csv_file": "从 csv 文件中导入拉黑名单",
-    "import_followers_from_a_csv_file": "从 csv 文件中导入关注",
-    "import_theme": "导入预置主题",
-    "inputRadius": "输入框",
-    "checkboxRadius": "复选框",
-    "instance_default": "(默认:{value})",
-    "instance_default_simple": "(默认)",
-    "interface": "界面",
-    "interfaceLanguage": "界面语言",
-    "invalid_theme_imported": "您所选择的主题文件不被 Pleroma 支持,因此主题未被修改。",
-    "limited_availability": "在您的浏览器中无法使用",
-    "links": "链接",
-    "lock_account_description": "你需要手动审核关注请求",
-    "loop_video": "循环视频",
-    "loop_video_silent_only": "只循环没有声音的视频(例如:Mastodon 里的“GIF”)",
-    "mutes_tab": "隐藏",
-    "play_videos_in_modal": "在弹出框内播放视频",
-    "use_contain_fit": "生成缩略图时不要裁剪附件。",
-    "name": "名字",
-    "name_bio": "名字及简介",
-    "new_password": "新密码",
-    "notification_visibility": "要显示的通知类型",
-    "notification_visibility_follows": "关注",
-    "notification_visibility_likes": "点赞",
-    "notification_visibility_mentions": "提及",
-    "notification_visibility_repeats": "转发",
-    "no_rich_text_description": "不显示富文本格式",
-    "no_blocks": "没有拉黑的",
-    "no_mutes": "没有隐藏",
-    "hide_follows_description": "不要显示我所关注的人",
-    "hide_followers_description": "不要显示关注我的人",
-    "show_admin_badge": "显示管理徽章",
-    "show_moderator_badge": "显示版主徽章",
-    "nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开",
-    "oauth_tokens": "OAuth令牌",
-    "token": "令牌",
-    "refresh_token": "刷新令牌",
-    "valid_until": "有效期至",
-    "revoke_token": "撤消",
-    "panelRadius": "面板",
-    "pause_on_unfocused": "在离开页面时暂停时间线推送",
-    "presets": "预置",
-    "profile_background": "个人资料背景图",
-    "profile_banner": "横幅图片",
-    "profile_tab": "个人资料",
-    "radii_help": "设置界面边缘的圆角 (单位:像素)",
-    "replies_in_timeline": "时间线中的回复",
-    "reply_link_preview": "启用鼠标悬停时预览回复链接",
-    "reply_visibility_all": "显示所有回复",
-    "reply_visibility_following": "只显示发送给我的回复/发送给我关注的用户的回复",
-    "reply_visibility_self": "只显示发送给我的回复",
-    "autohide_floating_post_button": "自动隐藏新帖子的按钮(移动设备)",
-    "saving_err": "保存设置时发生错误",
-    "saving_ok": "设置已保存",
-    "search_user_to_block": "搜索你想屏蔽的用户",
-    "search_user_to_mute": "搜索你想要隐藏的用户",
-    "security_tab": "安全",
-    "scope_copy": "回复时的复制范围(私信是总是复制的)",
-    "minimal_scopes_mode": "最小发文范围",
-    "set_new_avatar": "设置新头像",
-    "set_new_profile_background": "设置新的个人资料背景",
-    "set_new_profile_banner": "设置新的横幅图片",
-    "settings": "设置",
-    "subject_input_always_show": "总是显示主题框",
-    "subject_line_behavior": "回复时复制主题",
-    "subject_line_email": "比如电邮: \"re: 主题\"",
-    "subject_line_mastodon": "比如 mastodon: copy as is",
-    "subject_line_noop": "不要复制",
-    "post_status_content_type": "发文状态内容类型",
-    "stop_gifs": "鼠标悬停时播放GIF",
-    "streaming": "开启滚动到顶部时的自动推送",
-    "text": "文本",
-    "theme": "主题",
-    "theme_help": "使用十六进制代码(#rrggbb)来设置主题颜色。",
-    "theme_help_v2_1": "你也可以通过切换复选框来覆盖某些组件的颜色和透明。使用“清除所有”来清楚所有覆盖设置。",
-    "theme_help_v2_2": "某些条目下的图标是背景或文本对比指示器,鼠标悬停可以获取详细信息。请记住,使用透明度来显示最差的情况。",
-    "tooltipRadius": "提醒",
-    "upload_a_photo": "上传照片",
-    "user_settings": "用户设置",
-    "values": {
-      "false": "否",
-      "true": "是"
+    "finder": {
+        "error_fetching_user": "获取用户时发生错误",
+        "find_user": "寻找用户"
     },
-    "notifications": "通知",
-    "notification_setting": "通知来源:",
-    "notification_setting_follows": "你所关注的用户",
-    "notification_setting_non_follows": "你没有关注的用户",
-    "notification_setting_followers": "关注你的用户",
-    "notification_setting_non_followers": "没有关注你的用户",
-    "notification_mutes": "要停止收到某个指定的用户的通知,请使用隐藏功能。",
-    "notification_blocks": "拉黑一个用户会停掉所有他的通知,等同于取消关注。",
-    "enable_web_push_notifications": "启用 web 推送通知",
-    "style": {
-      "switcher": {
-        "keep_color": "保留颜色",
-        "keep_shadows": "保留阴影",
-        "keep_opacity": "保留透明度",
-        "keep_roundness": "保留圆角",
-        "keep_fonts": "保留字体",
-        "save_load_hint": "\"保留\" 选项在选择或加载主题时保留当前设置的选项,在导出主题时还会存储上述选项。当所有复选框未设置时,导出主题将保存所有内容。",
-        "reset": "重置",
-        "clear_all": "清除全部",
-        "clear_opacity": "清除透明度"
-      },
-      "common": {
-        "color": "颜色",
-        "opacity": "透明度",
-        "contrast": {
-          "hint": "对比度是 {ratio}, 它 {level} {context}",
-          "level": {
-            "aa": "符合 AA 等级准则(最低)",
-            "aaa": "符合 AAA 等级准则(推荐)",
-            "bad": "不符合任何辅助功能指南"
-          },
-          "context": {
-            "18pt": "大字文本 (18pt+)",
-            "text": "文本"
-          }
+    "general": {
+        "apply": "应用",
+        "submit": "提交",
+        "more": "更多",
+        "generic_error": "发生一个错误",
+        "optional": "可选项",
+        "show_more": "展开",
+        "show_less": "收起",
+        "cancel": "取消",
+        "disable": "禁用",
+        "enable": "启用",
+        "confirm": "确认",
+        "verify": "验证",
+        "dismiss": "忽略"
+    },
+    "image_cropper": {
+        "crop_picture": "裁剪图片",
+        "save": "保存",
+        "save_without_cropping": "保存未经裁剪的图片",
+        "cancel": "取消"
+    },
+    "importer": {
+        "submit": "提交",
+        "success": "导入成功。",
+        "error": "导入此文件时出现一个错误。"
+    },
+    "login": {
+        "login": "登录",
+        "description": "用 OAuth 登录",
+        "logout": "登出",
+        "password": "密码",
+        "placeholder": "例如:lain",
+        "register": "注册",
+        "username": "用户名",
+        "hint": "登录后加入讨论",
+        "authentication_code": "验证码",
+        "enter_recovery_code": "输入一个恢复码",
+        "enter_two_factor_code": "输入一个双重因素验证码",
+        "recovery_code": "恢复码",
+        "heading": {
+            "totp": "双重因素验证",
+            "recovery": "双重因素恢复"
         }
-      },
-      "common_colors": {
-        "_tab_label": "常规",
-        "main": "常用颜色",
-        "foreground_hint": "点击”高级“ 标签进行细致的控制",
-        "rgbo": "图标,口音,徽章"
-      },
-      "advanced_colors": {
-        "_tab_label": "高级",
-        "alert": "提醒或警告背景色",
-        "alert_error": "错误",
-        "badge": "徽章背景",
-        "badge_notification": "通知",
-        "panel_header": "面板标题",
-        "top_bar": "顶栏",
-        "borders": "边框",
-        "buttons": "按钮",
-        "inputs": "输入框",
-        "faint_text": "灰度文字"
-      },
-      "radii": {
-        "_tab_label": "圆角"
-      },
-      "shadows": {
-        "_tab_label": "阴影和照明",
-        "component": "组件",
-        "override": "覆盖",
-        "shadow_id": "阴影 #{value}",
-        "blur": "模糊",
-        "spread": "扩散",
-        "inset": "插入内部",
-        "hint": "对于阴影你还可以使用 --variable 作为颜色值来使用 CSS3 变量。请注意,这种情况下,透明设置将不起作用。",
-        "filter_hint": {
-          "always_drop_shadow": "警告,此阴影设置会总是使用 {0} ,如果浏览器支持的话。",
-          "drop_shadow_syntax": "{0} 不支持参数 {1} 和关键词 {2} 。",
-          "avatar_inset": "请注意组合两个内部和非内部的阴影到头像上,在透明头像上可能会有意料之外的效果。",
-          "spread_zero": "阴影的扩散 > 0 会同设置成零一样",
-          "inset_classic": "插入内部的阴影会使用 {0}"
+    },
+    "media_modal": {
+        "previous": "往前",
+        "next": "往后"
+    },
+    "nav": {
+        "about": "关于",
+        "back": "后退",
+        "chat": "本站聊天",
+        "friend_requests": "关注请求",
+        "mentions": "提及",
+        "interactions": "互动",
+        "dms": "私信",
+        "public_tl": "公共时间线",
+        "timeline": "时间线",
+        "twkn": "所有已知网络",
+        "user_search": "用户搜索",
+        "search": "搜索",
+        "who_to_follow": "推荐关注",
+        "preferences": "偏好设置",
+        "administration": "管理员"
+    },
+    "notifications": {
+        "broken_favorite": "未知的状态,正在搜索中...",
+        "favorited_you": "收藏了你的状态",
+        "followed_you": "关注了你",
+        "load_older": "加载更早的通知",
+        "notifications": "通知",
+        "read": "阅读!",
+        "repeated_you": "转发了你的状态",
+        "no_more_notifications": "没有更多的通知",
+        "reacted_with": "和 {0} 互动过",
+        "migrated_to": "迁移到",
+        "follow_request": "想要关注你"
+    },
+    "polls": {
+        "add_poll": "增加问卷调查",
+        "add_option": "增加选项",
+        "option": "选项",
+        "votes": "投票",
+        "vote": "投票",
+        "type": "问卷类型",
+        "single_choice": "单选项",
+        "multiple_choices": "多选项",
+        "expiry": "问卷的时间",
+        "expires_in": "投票于 {0} 内结束",
+        "expired": "投票 {0} 前已结束",
+        "not_enough_options": "投票的选项太少"
+    },
+    "stickers": {
+        "add_sticker": "添加贴纸"
+    },
+    "interactions": {
+        "favs_repeats": "转发和收藏",
+        "follows": "新的关注者",
+        "load_older": "加载更早的互动",
+        "moves": "用户迁移"
+    },
+    "post_status": {
+        "new_status": "发布新状态",
+        "account_not_locked_warning": "你的帐号没有 {0}。任何人都可以关注你并浏览你的上锁内容。",
+        "account_not_locked_warning_link": "上锁",
+        "attachments_sensitive": "标记附件为敏感内容",
+        "content_type": {
+            "text/plain": "纯文本",
+            "text/html": "HTML",
+            "text/markdown": "Markdown",
+            "text/bbcode": "BBCode"
         },
-        "components": {
-          "panel": "面板",
-          "panelHeader": "面板标题",
-          "topBar": "顶栏",
-          "avatar": "用户头像(在个人资料栏)",
-          "avatarStatus": "用户头像(在帖子显示栏)",
-          "popup": "弹窗和工具提示",
-          "button": "按钮",
-          "buttonHover": "按钮(悬停)",
-          "buttonPressed": "按钮(按下)",
-          "buttonPressedHover": "按钮(按下和悬停)",
-          "input": "输入框"
+        "content_warning": "主题(可选)",
+        "default": "刚刚抵达上海",
+        "direct_warning_to_all": "本条内容只有被提及的用户能够看到。",
+        "direct_warning_to_first_only": "本条内容只有被在消息开始处提及的用户能够看到。",
+        "posting": "发送",
+        "scope_notice": {
+            "public": "本条内容可以被所有人看到",
+            "private": "关注你的人才能看到本条内容",
+            "unlisted": "本条内容既不在公共时间线,也不会在所有已知网络上可见"
+        },
+        "scope": {
+            "direct": "私信 - 只发送给被提及的用户",
+            "private": "仅关注者 - 只有关注了你的人能看到",
+            "public": "公共 - 发送到公共时间轴",
+            "unlisted": "不公开 - 不会发送到公共时间轴"
         }
-      },
-      "fonts": {
-        "_tab_label": "字体",
-        "help": "给用户界面的元素选择字体。选择 “自选”的你必须输入确切的字体名称。",
-        "components": {
-          "interface": "界面",
-          "input": "输入框",
-          "post": "发帖文字",
-          "postCode": "帖子中使用等间距文字(富文本)"
+    },
+    "registration": {
+        "bio": "简介",
+        "email": "电子邮箱",
+        "fullname": "全名",
+        "password_confirm": "确认密码",
+        "registration": "注册",
+        "token": "邀请码",
+        "captcha": "CAPTCHA",
+        "new_captcha": "点击图片获取新的验证码",
+        "username_placeholder": "例如:lain",
+        "fullname_placeholder": "例如:岩仓玲音",
+        "bio_placeholder": "例如:\n你好,我是玲音。\n我是一个住在日本郊区的动画少女。你可能在 Wired 见过我。",
+        "validations": {
+            "username_required": "不能留空",
+            "fullname_required": "不能留空",
+            "email_required": "不能留空",
+            "password_required": "不能留空",
+            "password_confirmation_required": "不能留空",
+            "password_confirmation_match": "密码不一致"
+        }
+    },
+    "selectable_list": {
+        "select_all": "选择全部"
+    },
+    "settings": {
+        "app_name": "App 名称",
+        "security": "安全",
+        "enter_current_password_to_confirm": "输入你当前密码来确认你的身份",
+        "mfa": {
+            "otp": "OTP",
+            "setup_otp": "设置 OTP",
+            "wait_pre_setup_otp": "预设 OTP",
+            "confirm_and_enable": "确认并启用 OTP",
+            "title": "双因素验证",
+            "generate_new_recovery_codes": "生成新的恢复码",
+            "warning_of_generate_new_codes": "当你生成新的恢复码时,你的旧恢复码就失效了。",
+            "recovery_codes": "恢复码。",
+            "waiting_a_recovery_codes": "正在接收备份码……",
+            "recovery_codes_warning": "抄写这些号码,或者保存在安全的地方。这些号码不会再次显示。如果你无法访问你的 2FA app,也丢失了你的恢复码,你的账号就再也无法登录了。",
+            "authentication_methods": "身份验证方法",
+            "scan": {
+                "title": "扫一下",
+                "desc": "使用你的双因素验证 app,扫描这个二维码,或者输入这些文字密钥:",
+                "secret_code": "密钥"
+            },
+            "verify": {
+                "desc": "要启用双因素验证,请把你的双因素验证 app 里的数字输入:"
+            }
         },
-        "family": "字体名称",
-        "size": "大小 (in px)",
-        "weight": "字重 (粗体))",
-        "custom": "自选"
-      },
-      "preview": {
-        "header": "预览",
-        "content": "内容",
-        "error": "例子错误",
-        "button": "按钮",
-        "text": "有堆 {0} 和 {1}",
-        "mono": "内容",
-        "input": "刚刚抵达上海",
-        "faint_link": "帮助菜单",
-        "fine_print": "阅读我们的 {0} 学不到什么东东!",
-        "header_faint": "这很正常",
-        "checkbox": "我已经浏览了 TOC",
-        "link": "一个很棒的摇滚链接"
-      }
+        "attachmentRadius": "附件",
+        "attachments": "附件",
+        "autoload": "启用滚动到底部时的自动加载",
+        "avatar": "头像",
+        "avatarAltRadius": "头像(通知)",
+        "avatarRadius": "头像",
+        "background": "背景",
+        "bio": "简介",
+        "block_export": "拉黑名单导出",
+        "block_export_button": "导出你的拉黑名单到一个 csv 文件",
+        "block_import": "拉黑名单导入",
+        "block_import_error": "导入拉黑名单出错",
+        "blocks_imported": "拉黑名单导入成功!需要一点时间来处理。",
+        "blocks_tab": "块",
+        "btnRadius": "按钮",
+        "cBlue": "蓝色(回复,关注)",
+        "cGreen": "绿色(转发)",
+        "cOrange": "橙色(收藏)",
+        "cRed": "红色(取消)",
+        "change_password": "修改密码",
+        "change_password_error": "修改密码的时候出了点问题。",
+        "changed_password": "成功修改了密码!",
+        "collapse_subject": "折叠带主题的内容",
+        "composing": "正在书写",
+        "confirm_new_password": "确认新密码",
+        "current_avatar": "当前头像",
+        "current_password": "当前密码",
+        "current_profile_banner": "您当前的横幅图片",
+        "data_import_export_tab": "数据导入/导出",
+        "default_vis": "默认可见范围",
+        "delete_account": "删除账户",
+        "delete_account_description": "永久删除你的帐号和所有数据。",
+        "delete_account_error": "删除账户时发生错误,如果一直删除不了,请联系实例管理员。",
+        "delete_account_instructions": "在下面输入你的密码来确认删除账户",
+        "avatar_size_instruction": "推荐的头像图片最小的尺寸是 150x150 像素。",
+        "export_theme": "导出预置主题",
+        "filtering": "过滤器",
+        "filtering_explanation": "所有包含以下词汇的内容都会被隐藏,一行一个",
+        "follow_export": "导出关注",
+        "follow_export_button": "将关注导出成 csv 文件",
+        "follow_import": "导入关注",
+        "follow_import_error": "导入关注时错误",
+        "follows_imported": "关注已导入!尚需要一些时间来处理。",
+        "foreground": "前景",
+        "general": "通用",
+        "hide_attachments_in_convo": "在对话中隐藏附件",
+        "hide_attachments_in_tl": "在时间线上隐藏附件",
+        "hide_muted_posts": "不显示被隐藏的用户的帖子",
+        "max_thumbnails": "最多再每个帖子所能显示的缩略图数量",
+        "hide_isp": "隐藏指定实例的面板H",
+        "preload_images": "预载图片",
+        "use_one_click_nsfw": "点击一次以打开工作场所不适宜的附件",
+        "hide_post_stats": "隐藏推文相关的统计数据(例如:收藏的次数)",
+        "hide_user_stats": "隐藏用户的统计数据(例如:关注者的数量)",
+        "hide_filtered_statuses": "隐藏过滤的状态",
+        "import_blocks_from_a_csv_file": "从 csv 文件中导入拉黑名单",
+        "import_followers_from_a_csv_file": "从 csv 文件中导入关注",
+        "import_theme": "导入预置主题",
+        "inputRadius": "输入框",
+        "checkboxRadius": "复选框",
+        "instance_default": "(默认:{value})",
+        "instance_default_simple": "(默认)",
+        "interface": "界面",
+        "interfaceLanguage": "界面语言",
+        "invalid_theme_imported": "您所选择的主题文件不被 Pleroma 支持,因此主题未被修改。",
+        "limited_availability": "在您的浏览器中无法使用",
+        "links": "链接",
+        "lock_account_description": "你需要手动审核关注请求",
+        "loop_video": "循环视频",
+        "loop_video_silent_only": "只循环没有声音的视频(例如:Mastodon 里的“GIF”)",
+        "mutes_tab": "隐藏",
+        "play_videos_in_modal": "在弹出框内播放视频",
+        "use_contain_fit": "生成缩略图时不要裁剪附件",
+        "name": "名字",
+        "name_bio": "名字及简介",
+        "new_password": "新密码",
+        "notification_visibility": "要显示的通知类型",
+        "notification_visibility_follows": "关注",
+        "notification_visibility_likes": "点赞",
+        "notification_visibility_mentions": "提及",
+        "notification_visibility_repeats": "转发",
+        "no_rich_text_description": "不显示富文本格式",
+        "no_blocks": "没有拉黑的",
+        "no_mutes": "没有隐藏",
+        "hide_follows_description": "不要显示我所关注的人",
+        "hide_followers_description": "不要显示关注我的人",
+        "show_admin_badge": "显示管理徽章",
+        "show_moderator_badge": "显示版主徽章",
+        "nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开",
+        "oauth_tokens": "OAuth令牌",
+        "token": "令牌",
+        "refresh_token": "刷新令牌",
+        "valid_until": "有效期至",
+        "revoke_token": "撤消",
+        "panelRadius": "面板",
+        "pause_on_unfocused": "在离开页面时暂停时间线推送",
+        "presets": "预置",
+        "profile_background": "个人资料背景图",
+        "profile_banner": "横幅图片",
+        "profile_tab": "个人资料",
+        "radii_help": "设置界面边缘的圆角 (单位:像素)",
+        "replies_in_timeline": "时间线中的回复",
+        "reply_link_preview": "启用鼠标悬停时预览回复链接",
+        "reply_visibility_all": "显示所有回复",
+        "reply_visibility_following": "只显示发送给我的回复/发送给我关注的用户的回复",
+        "reply_visibility_self": "只显示发送给我的回复",
+        "autohide_floating_post_button": "自动隐藏新帖子的按钮(移动设备)",
+        "saving_err": "保存设置时发生错误",
+        "saving_ok": "设置已保存",
+        "search_user_to_block": "搜索你想屏蔽的用户",
+        "search_user_to_mute": "搜索你想要隐藏的用户",
+        "security_tab": "安全",
+        "scope_copy": "回复时的复制范围(私信是总是复制的)",
+        "minimal_scopes_mode": "最小发文范围",
+        "set_new_avatar": "设置新头像",
+        "set_new_profile_background": "设置新的个人资料背景",
+        "set_new_profile_banner": "设置新的横幅图片",
+        "settings": "设置",
+        "subject_input_always_show": "总是显示主题框",
+        "subject_line_behavior": "回复时复制主题",
+        "subject_line_email": "比如电邮: \"re: 主题\"",
+        "subject_line_mastodon": "比如 mastodon: copy as is",
+        "subject_line_noop": "不要复制",
+        "post_status_content_type": "发文状态内容类型",
+        "stop_gifs": "鼠标悬停时播放GIF",
+        "streaming": "开启滚动到顶部时的自动推送",
+        "text": "文本",
+        "theme": "主题",
+        "theme_help": "使用十六进制代码(#rrggbb)来设置主题颜色。",
+        "theme_help_v2_1": "你也可以通过切换复选框来覆盖某些组件的颜色和透明。使用“清除所有”来清楚所有覆盖设置。",
+        "theme_help_v2_2": "某些条目下的图标是背景或文本对比指示器,鼠标悬停可以获取详细信息。请记住,使用透明度来显示最差的情况。",
+        "tooltipRadius": "提醒",
+        "upload_a_photo": "上传照片",
+        "user_settings": "用户设置",
+        "values": {
+            "false": "否",
+            "true": "是"
+        },
+        "notifications": "通知",
+        "notification_setting": "通知来源:",
+        "notification_setting_follows": "你所关注的用户",
+        "notification_setting_non_follows": "你没有关注的用户",
+        "notification_setting_followers": "关注你的用户",
+        "notification_setting_non_followers": "没有关注你的用户",
+        "notification_mutes": "要停止收到某个指定的用户的通知,请使用隐藏功能。",
+        "notification_blocks": "拉黑一个用户会停掉所有他的通知,等同于取消关注。",
+        "enable_web_push_notifications": "启用 web 推送通知",
+        "style": {
+            "switcher": {
+                "keep_color": "保留颜色",
+                "keep_shadows": "保留阴影",
+                "keep_opacity": "保留透明度",
+                "keep_roundness": "保留圆角",
+                "keep_fonts": "保留字体",
+                "save_load_hint": "\"保留\" 选项在选择或加载主题时保留当前设置的选项,在导出主题时还会存储上述选项。当所有复选框未设置时,导出主题将保存所有内容。",
+                "reset": "重置",
+                "clear_all": "清除全部",
+                "clear_opacity": "清除透明度",
+                "load_theme": "加载主题",
+                "help": {
+                    "upgraded_from_v2": "PleromaFE 已升级,主题会和你记忆中的不太一样。"
+                },
+                "use_source": "新版本",
+                "use_snapshot": "老版本",
+                "keep_as_is": "保持原状"
+            },
+            "common": {
+                "color": "颜色",
+                "opacity": "透明度",
+                "contrast": {
+                    "hint": "对比度是 {ratio}, 它 {level} {context}",
+                    "level": {
+                        "aa": "符合 AA 等级准则(最低)",
+                        "aaa": "符合 AAA 等级准则(推荐)",
+                        "bad": "不符合任何辅助功能指南"
+                    },
+                    "context": {
+                        "18pt": "大字文本 (18pt+)",
+                        "text": "文本"
+                    }
+                }
+            },
+            "common_colors": {
+                "_tab_label": "常规",
+                "main": "常用颜色",
+                "foreground_hint": "点击”高级“ 标签进行细致的控制",
+                "rgbo": "图标,口音,徽章"
+            },
+            "advanced_colors": {
+                "_tab_label": "高级",
+                "alert": "提醒或警告背景色",
+                "alert_error": "错误",
+                "badge": "徽章背景",
+                "badge_notification": "通知",
+                "panel_header": "面板标题",
+                "top_bar": "顶栏",
+                "borders": "边框",
+                "buttons": "按钮",
+                "inputs": "输入框",
+                "faint_text": "灰度文字"
+            },
+            "radii": {
+                "_tab_label": "圆角"
+            },
+            "shadows": {
+                "_tab_label": "阴影和照明",
+                "component": "组件",
+                "override": "覆盖",
+                "shadow_id": "阴影 #{value}",
+                "blur": "模糊",
+                "spread": "扩散",
+                "inset": "插入内部",
+                "hint": "对于阴影你还可以使用 --variable 作为颜色值来使用 CSS3 变量。请注意,这种情况下,透明设置将不起作用。",
+                "filter_hint": {
+                    "always_drop_shadow": "警告,此阴影设置会总是使用 {0} ,如果浏览器支持的话。",
+                    "drop_shadow_syntax": "{0} 不支持参数 {1} 和关键词 {2} 。",
+                    "avatar_inset": "请注意组合两个内部和非内部的阴影到头像上,在透明头像上可能会有意料之外的效果。",
+                    "spread_zero": "阴影的扩散 > 0 会同设置成零一样",
+                    "inset_classic": "插入内部的阴影会使用 {0}"
+                },
+                "components": {
+                    "panel": "面板",
+                    "panelHeader": "面板标题",
+                    "topBar": "顶栏",
+                    "avatar": "用户头像(在个人资料栏)",
+                    "avatarStatus": "用户头像(在帖子显示栏)",
+                    "popup": "弹窗和工具提示",
+                    "button": "按钮",
+                    "buttonHover": "按钮(悬停)",
+                    "buttonPressed": "按钮(按下)",
+                    "buttonPressedHover": "按钮(按下和悬停)",
+                    "input": "输入框"
+                }
+            },
+            "fonts": {
+                "_tab_label": "字体",
+                "help": "给用户界面的元素选择字体。选择 “自选”的你必须输入确切的字体名称。",
+                "components": {
+                    "interface": "界面",
+                    "input": "输入框",
+                    "post": "发帖文字",
+                    "postCode": "帖子中使用等间距文字(富文本)"
+                },
+                "family": "字体名称",
+                "size": "大小 (in px)",
+                "weight": "字重 (粗体))",
+                "custom": "自选"
+            },
+            "preview": {
+                "header": "预览",
+                "content": "内容",
+                "error": "例子错误",
+                "button": "按钮",
+                "text": "有堆 {0} 和 {1}",
+                "mono": "内容",
+                "input": "刚刚抵达上海",
+                "faint_link": "帮助菜单",
+                "fine_print": "阅读我们的 {0} ,然而什么也学不到!",
+                "header_faint": "这很正常",
+                "checkbox": "我已经浏览了 TOC",
+                "link": "一个很棒的摇滚链接"
+            }
+        },
+        "version": {
+            "title": "版本",
+            "backend_version": "后端版本",
+            "frontend_version": "前端版本"
+        },
+        "notification_setting_filters": "过滤器",
+        "domain_mutes": "域名",
+        "changed_email": "邮箱修改成功!",
+        "change_email_error": "修改你的电子邮箱时发生错误",
+        "change_email": "修改电子邮箱",
+        "allow_following_move": "正在关注的账号迁移时自动重新关注",
+        "notification_setting_privacy_option": "在通知推送中隐藏发送者和内容",
+        "notification_setting_privacy": "隐私",
+        "hide_follows_count_description": "不显示关注数",
+        "notification_visibility_emoji_reactions": "互动",
+        "notification_visibility_moves": "用户迁移",
+        "new_email": "新邮箱",
+        "emoji_reactions_on_timeline": "在时间线上显示表情符号互动"
     },
-    "version": {
-      "title": "版本",
-      "backend_version": "后端版本",
-      "frontend_version": "前端版本"
-    }
-  },
-  "time": {
-    "day": "{0} 天",
-    "days": "{0} 天",
-    "day_short": "{0}d",
-    "days_short": "{0}d",
-    "hour": "{0} 小时",
-    "hours": "{0} 小时",
-    "hour_short": "{0}h",
-    "hours_short": "{0}h",
-    "in_future": "还有 {0}",
-    "in_past": "{0} 之前",
-    "minute": "{0} 分钟",
-    "minutes": "{0} 分钟",
-    "minute_short": "{0}min",
-    "minutes_short": "{0}min",
-    "month": "{0} 月",
-    "months": "{0} 月",
-    "month_short": "{0}mo",
-    "months_short": "{0}mo",
-    "now": "刚刚",
-    "now_short": "刚刚",
-    "second": "{0} 秒",
-    "seconds": "{0} 秒",
-    "second_short": "{0}s",
-    "seconds_short": "{0}s",
-    "week": "{0} 周",
-    "weeks": "{0} 周",
-    "week_short": "{0}w",
-    "weeks_short": "{0}w",
-    "year": "{0} 年",
-    "years": "{0} 年",
-    "year_short": "{0}y",
-    "years_short": "{0}y"
-  },
-  "timeline": {
-    "collapse": "折叠",
-    "conversation": "对话",
-    "error_fetching": "获取更新时发生错误",
-    "load_older": "加载更早的状态",
-    "no_retweet_hint": "这条内容仅关注者可见,或者是私信,因此不能转发。",
-    "repeated": "已转发",
-    "show_new": "显示新内容",
-    "up_to_date": "已是最新",
-    "no_more_statuses": "没有更多的状态",
-    "no_statuses": "没有状态更新"
-  },
-  "status": {
-    "favorites": "收藏",
-    "repeats": "转发",
-    "delete": "删除状态",
-    "pin": "在个人资料置顶",
-    "unpin": "取消在个人资料置顶",
-    "pinned": "置顶",
-    "delete_confirm": "你真的想要删除这条状态吗?",
-    "reply_to": "回复",
-    "replies_list": "回复:",
-    "mute_conversation": "隐藏对话",
-    "unmute_conversation": "对话取消隐藏"
-  },
-  "user_card": {
-    "approve": "允许",
-    "block": "屏蔽",
-    "blocked": "已屏蔽!",
-    "deny": "拒绝",
-    "favorites": "收藏",
-    "follow": "关注",
-    "follow_sent": "请求已发送!",
-    "follow_progress": "请求中",
-    "follow_again": "再次发送请求?",
-    "follow_unfollow": "取消关注",
-    "followees": "正在关注",
-    "followers": "关注者",
-    "following": "正在关注!",
-    "follows_you": "关注了你!",
-    "its_you": "就是你!!",
-    "media": "媒体",
-    "mute": "隐藏",
-    "muted": "已隐藏",
-    "per_day": "每天",
-    "remote_follow": "跨站关注",
-    "report": "报告", 
-    "statuses": "状态",
-    "subscribe": "订阅",
-    "unsubscribe": "退订",
-    "unblock": "取消拉黑",
-    "unblock_progress": "取消拉黑中...",
-    "block_progress": "拉黑中...",
-    "unmute": "取消隐藏",
-    "unmute_progress": "取消隐藏中...",
-    "mute_progress": "隐藏中...",
-    "admin_menu": {
-      "moderation": "权限",
-      "grant_admin": "赋予管理权限",
-      "revoke_admin": "撤销管理权限",
-      "grant_moderator": "赋予版主权限",
-      "revoke_moderator": "撤销版主权限",
-      "activate_account": "激活账号",
-      "deactivate_account": "关闭账号",
-      "delete_account": "删除账号",
-      "force_nsfw": "标记所有的帖子都是 - 工作场合不适",
-      "strip_media": "从帖子里删除媒体文件",
-      "force_unlisted": "强制帖子为不公开",
-      "sandbox": "强制帖子为只有关注者可看",
-      "disable_remote_subscription": "禁止从远程实例关注用户",
-      "disable_any_subscription": "完全禁止关注用户",
-      "quarantine": "从联合实例中禁止用户帖子",
-      "delete_user": "删除用户",
-      "delete_user_confirmation": "你确认吗?此操作无法撤销。"
-    }
-  },
-  "user_profile": {
-    "timeline_title": "用户时间线",
-    "profile_does_not_exist": "抱歉,此个人资料不存在。",
-    "profile_loading_error": "抱歉,载入个人资料时出错。"
-  },
-  "user_reporting": {
-    "title": "报告 {0}",
-    "add_comment_description": "此报告会发送给你的实例管理员。你可以在下面提供更多详细信息解释报告的缘由:",
-    "additional_comments": "其它信息",
-    "forward_description": "这个账号是从另外一个服务器。同时发送一个副本到那里?",
-    "forward_to": "转发 {0}",
-    "submit": "提交",
-    "generic_error": "当处理你的请求时,发生了一个错误。"
-  },
-  "who_to_follow": {
-    "more": "更多",
-    "who_to_follow": "推荐关注"
-  },
-  "tool_tip": {
-    "media_upload": "上传多媒体",
-    "repeat": "转发",
-    "reply": "回复",
-    "favorite": "收藏",
-    "user_settings": "用户设置"
-  },
-  "upload":{
-    "error": {
-      "base": "上传不成功。",
-      "file_too_big": "文件太大了 [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-      "default": "迟些再试"
+    "time": {
+        "day": "{0} 天",
+        "days": "{0} 天",
+        "day_short": "{0}d",
+        "days_short": "{0}d",
+        "hour": "{0} 小时",
+        "hours": "{0} 小时",
+        "hour_short": "{0}h",
+        "hours_short": "{0}h",
+        "in_future": "还有 {0}",
+        "in_past": "{0} 之前",
+        "minute": "{0} 分钟",
+        "minutes": "{0} 分钟",
+        "minute_short": "{0}min",
+        "minutes_short": "{0}min",
+        "month": "{0} 月",
+        "months": "{0} 月",
+        "month_short": "{0}mo",
+        "months_short": "{0}mo",
+        "now": "刚刚",
+        "now_short": "刚刚",
+        "second": "{0} 秒",
+        "seconds": "{0} 秒",
+        "second_short": "{0}s",
+        "seconds_short": "{0}s",
+        "week": "{0} 周",
+        "weeks": "{0} 周",
+        "week_short": "{0}w",
+        "weeks_short": "{0}w",
+        "year": "{0} 年",
+        "years": "{0} 年",
+        "year_short": "{0}y",
+        "years_short": "{0}y"
     },
-    "file_size_units": {
-      "B": "B",
-      "KiB": "KiB",
-      "MiB": "MiB",
-      "GiB": "GiB",
-      "TiB": "TiB"
+    "timeline": {
+        "collapse": "折叠",
+        "conversation": "对话",
+        "error_fetching": "获取更新时发生错误",
+        "load_older": "加载更早的状态",
+        "no_retweet_hint": "这条内容仅关注者可见,或者是私信,因此不能转发。",
+        "repeated": "已转发",
+        "show_new": "显示新内容",
+        "up_to_date": "已是最新",
+        "no_more_statuses": "没有更多的状态",
+        "no_statuses": "没有状态更新"
+    },
+    "status": {
+        "favorites": "收藏",
+        "repeats": "转发",
+        "delete": "删除状态",
+        "pin": "在个人资料置顶",
+        "unpin": "取消在个人资料置顶",
+        "pinned": "置顶",
+        "delete_confirm": "你真的想要删除这条状态吗?",
+        "reply_to": "回复",
+        "replies_list": "回复:",
+        "mute_conversation": "隐藏对话",
+        "unmute_conversation": "对话取消隐藏"
+    },
+    "user_card": {
+        "approve": "允许",
+        "block": "屏蔽",
+        "blocked": "已屏蔽!",
+        "deny": "拒绝",
+        "favorites": "收藏",
+        "follow": "关注",
+        "follow_sent": "请求已发送!",
+        "follow_progress": "请求中",
+        "follow_again": "再次发送请求?",
+        "follow_unfollow": "取消关注",
+        "followees": "正在关注",
+        "followers": "关注者",
+        "following": "正在关注!",
+        "follows_you": "关注了你!",
+        "its_you": "就是你!!",
+        "media": "媒体",
+        "mute": "隐藏",
+        "muted": "已隐藏",
+        "per_day": "每天",
+        "remote_follow": "跨站关注",
+        "report": "报告",
+        "statuses": "状态",
+        "subscribe": "订阅",
+        "unsubscribe": "退订",
+        "unblock": "取消拉黑",
+        "unblock_progress": "取消拉黑中...",
+        "block_progress": "拉黑中...",
+        "unmute": "取消隐藏",
+        "unmute_progress": "取消隐藏中...",
+        "mute_progress": "隐藏中...",
+        "admin_menu": {
+            "moderation": "权限",
+            "grant_admin": "赋予管理权限",
+            "revoke_admin": "撤销管理权限",
+            "grant_moderator": "赋予版主权限",
+            "revoke_moderator": "撤销版主权限",
+            "activate_account": "激活账号",
+            "deactivate_account": "关闭账号",
+            "delete_account": "删除账号",
+            "force_nsfw": "标记所有的帖子都是 - 工作场合不适",
+            "strip_media": "从帖子里删除媒体文件",
+            "force_unlisted": "强制帖子为不公开",
+            "sandbox": "强制帖子为只有关注者可看",
+            "disable_remote_subscription": "禁止从远程实例关注用户",
+            "disable_any_subscription": "完全禁止关注用户",
+            "quarantine": "从联合实例中禁止用户帖子",
+            "delete_user": "删除用户",
+            "delete_user_confirmation": "你确认吗?此操作无法撤销。"
+        },
+        "hidden": "已隐藏",
+        "show_repeats": "显示转发",
+        "hide_repeats": "隐藏转发"
+    },
+    "user_profile": {
+        "timeline_title": "用户时间线",
+        "profile_does_not_exist": "抱歉,此个人资料不存在。",
+        "profile_loading_error": "抱歉,载入个人资料时出错。"
+    },
+    "user_reporting": {
+        "title": "报告 {0}",
+        "add_comment_description": "此报告会发送给你的实例管理员。你可以在下面提供更多详细信息解释报告的缘由:",
+        "additional_comments": "其它信息",
+        "forward_description": "这个账号是从另外一个服务器。同时发送一个副本到那里?",
+        "forward_to": "转发 {0}",
+        "submit": "提交",
+        "generic_error": "当处理你的请求时,发生了一个错误。"
+    },
+    "who_to_follow": {
+        "more": "更多",
+        "who_to_follow": "推荐关注"
+    },
+    "tool_tip": {
+        "media_upload": "上传多媒体",
+        "repeat": "转发",
+        "reply": "回复",
+        "favorite": "收藏",
+        "user_settings": "用户设置",
+        "reject_follow_request": "拒绝关注请求",
+        "add_reaction": "添加互动"
+    },
+    "upload": {
+        "error": {
+            "base": "上传不成功。",
+            "file_too_big": "文件太大了 [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+            "default": "迟些再试"
+        },
+        "file_size_units": {
+            "B": "B",
+            "KiB": "KiB",
+            "MiB": "MiB",
+            "GiB": "GiB",
+            "TiB": "TiB"
+        }
+    },
+    "search": {
+        "people": "人",
+        "hashtags": "Hashtags",
+        "person_talking": "{count} 人正在讨论",
+        "people_talking": "{count} 人正在讨论",
+        "no_results": "没有搜索结果"
+    },
+    "password_reset": {
+        "forgot_password": "忘记密码了?",
+        "password_reset": "重置密码",
+        "instruction": "输入你的电邮地址或者用户名,我们将发送一个链接到你的邮箱,用于重置密码。",
+        "placeholder": "你的电邮地址或者用户名",
+        "check_email": "检查你的邮箱,会有一个链接用于重置密码。",
+        "return_home": "回到首页",
+        "not_found": "我们无法找到匹配的邮箱地址或者用户名。",
+        "too_many_requests": "你触发了尝试的限制,请稍后再试。",
+        "password_reset_disabled": "密码重置已经被禁用。请联系你的实例管理员。"
+    },
+    "remote_user_resolver": {
+        "error": "未找到。",
+        "searching_for": "搜索",
+        "remote_user_resolver": "远程用户解析器"
+    },
+    "emoji": {
+        "keep_open": "选择器保持打开",
+        "stickers": "贴图",
+        "unicode": "Unicode 表情符号",
+        "custom": "自定义表情符号",
+        "add_emoji": "插入表情符号",
+        "search_emoji": "搜索表情符号",
+        "emoji": "表情符号"
+    },
+    "about": {
+        "mrf": {
+            "simple": {
+                "quarantine_desc": "本实例只会把公开状态发送非下列实例:",
+                "quarantine": "隔离",
+                "reject_desc": "本实例不会接收来自下列实例的消息:",
+                "reject": "拒绝",
+                "accept_desc": "本实例只接收来自下列实例的消息:",
+                "simple_policies": "站规",
+                "accept": "接受",
+                "media_removal": "移除媒体"
+            },
+            "mrf_policies_desc": "MRF 策略会影响本实例的互通行为。以下策略已启用:",
+            "mrf_policies": "已启动 MRF 策略",
+            "keyword": {
+                "ftl_removal": "从“全部已知网络”时间线上移除",
+                "keyword_policies": "关键词策略",
+                "is_replaced_by": "→",
+                "replace": "替换",
+                "reject": "拒绝"
+            },
+            "federation": "联邦"
+        }
+    },
+    "domain_mute_card": {
+        "unmute_progress": "正在取消隐藏……",
+        "unmute": "取消隐藏",
+        "mute_progress": "隐藏中……",
+        "mute": "隐藏"
     }
-  },
-  "search": {
-    "people": "人",
-    "hashtags": "Hashtags",
-    "person_talking": "{count} 人谈论",
-    "people_talking": "{count} 人谈论",
-    "no_results": "没有搜索结果"
-  },
-  "password_reset": {
-    "forgot_password": "忘记密码了?",
-    "password_reset": "重置密码",
-    "instruction": "输入你的电邮地址或者用户名,我们将发送一个链接到你的邮箱,用于重置密码。",
-    "placeholder": "你的电邮地址或者用户名",
-    "check_email": "检查你的邮箱,会有一个链接用于重置密码。",
-    "return_home": "回到首页",
-    "not_found": "我们无法找到匹配的邮箱地址或者用户名。",
-    "too_many_requests": "你触发了尝试的限制,请稍后再试。",
-    "password_reset_disabled": "密码重置已经被禁用。请联系你的实例管理员。"
-  }
 }

From 306ed130872a861dc6e7cfe2524f3e50bd262809 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Mon, 18 May 2020 15:42:11 +0000
Subject: [PATCH 395/483] Translated using Weblate (Italian)

Currently translated at 42.2% (259 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index d4c4cb74..e565049d 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -85,7 +85,7 @@
         "data_import_export_tab": "Importa o esporta dati",
         "default_vis": "Visibilità predefinita dei messaggi",
         "delete_account": "Elimina profilo",
-        "delete_account_description": "Elimina definitivamente il tuo profilo e tutti i tuoi messaggi.",
+        "delete_account_description": "Elimina definitivamente i tuoi dati e disattiva il tuo profilo.",
         "delete_account_error": "C'è stato un problema durante l'eliminazione del tuo profilo. Se il problema persiste contatta l'amministratore della tua stanza.",
         "delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione del tuo profilo.",
         "export_theme": "Salva impostazioni",
@@ -305,5 +305,19 @@
         "expires_in": "Scade fra {0}",
         "expired": "Scaduto {0} fa",
         "not_enough_options": "Aggiungi altre risposte"
+    },
+    "interactions": {
+        "favs_repeats": "Condivisi e preferiti"
+    },
+    "emoji": {
+        "load_all": "Carico tutti i {emojiAmount} emoji",
+        "load_all_hint": "Primi {saneAmount} emoji caricati, caricarli tutti potrebbe causare rallentamenti.",
+        "unicode": "Emoji Unicode",
+        "custom": "Emoji personale",
+        "add_emoji": "Inserisci Emoji",
+        "search_emoji": "Cerca un emoji",
+        "keep_open": "Tieni aperto il menù",
+        "emoji": "Emoji",
+        "stickers": "Adesivi"
     }
 }

From ab74cd497205d5964db38d56f6f70fb727c60d78 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 24 May 2020 02:06:55 +0300
Subject: [PATCH 396/483] Multiple fixes for CSS, added proper auth checking

---
 src/boot/routes.js                            |   4 -
 src/components/modal/modal.vue                |  17 +-
 .../settings_modal/settings_modal.scss        |  26 +-
 .../settings_modal/settings_modal.vue         |  61 +++-
 .../tabs/mutes_and_blocks_tab.vue             | 327 +++++++++---------
 .../tabs/theme_tab/theme_tab.scss             |   1 +
 src/components/tab_switcher/tab_switcher.js   |  12 +-
 src/components/tab_switcher/tab_switcher.scss |   8 +-
 8 files changed, 263 insertions(+), 193 deletions(-)

diff --git a/src/boot/routes.js b/src/boot/routes.js
index 7400a682..d98a3b50 100644
--- a/src/boot/routes.js
+++ b/src/boot/routes.js
@@ -7,10 +7,8 @@ import Interactions from 'components/interactions/interactions.vue'
 import DMs from 'components/dm_timeline/dm_timeline.vue'
 import UserProfile from 'components/user_profile/user_profile.vue'
 import Search from 'components/search/search.vue'
-import Settings from 'components/settings/settings.vue'
 import Registration from 'components/registration/registration.vue'
 import PasswordReset from 'components/password_reset/password_reset.vue'
-import UserSettings from 'components/user_settings/user_settings.vue'
 import FollowRequests from 'components/follow_requests/follow_requests.vue'
 import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
 import Notifications from 'components/notifications/notifications.vue'
@@ -56,12 +54,10 @@ export default (store) => {
     { name: 'external-user-profile', path: '/users/:id', component: UserProfile },
     { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
     { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
-    { name: 'settings', path: '/settings', component: Settings },
     { name: 'registration', path: '/registration', component: Registration },
     { name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
     { name: 'registration-token', path: '/registration/:token', component: Registration },
     { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
-    { name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute },
     { name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
     { name: 'login', path: '/login', component: AuthForm },
     { name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) },
diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue
index e5ecc0c0..2b58913f 100644
--- a/src/components/modal/modal.vue
+++ b/src/components/modal/modal.vue
@@ -1,9 +1,9 @@
 <template>
   <div
     v-show="isOpen"
-    v-body-scroll-lock="isOpen"
+    v-body-scroll-lock="isOpen && !noBackground"
     class="modal-view"
-    :class="{ 'modal-background': !noBackground }"
+    :class="classes"
     @click.self="$emit('backdropClicked')"
   >
     <slot />
@@ -21,6 +21,14 @@ export default {
       type: Boolean,
       default: false
     }
+  },
+  computed: {
+    classes () {
+      return {
+        'modal-background': !this.noBackground,
+        'open': this.isOpen
+      }
+    }
   }
 }
 </script>
@@ -40,6 +48,7 @@ export default {
   pointer-events: none;
   animation-duration: 0.2s;
   animation-name: modal-background-fadein;
+  opacity: 0;
 
   > * {
     pointer-events: initial;
@@ -50,8 +59,8 @@ export default {
     background-color: rgba(0, 0, 0, 0.5);
   }
 
-  body:not(.scroll-locked) & {
-    opacity: 0;
+  &.open {
+    opacity: 1;
   }
 }
 
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index 3efbe205..b82590a7 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -1,12 +1,21 @@
 @import 'src/_variables.scss';
 .settings-modal {
+  overflow: hidden;
 
   .settings_tab-switcher {
     height: 100%;
   }
   &.peek {
     .settings-modal-panel {
-      transform: translateY(calc(100% - 50px));
+      /* Explanation:
+       * Modal is positioned vertically centered.
+       * 100vh - 100% = Distance between modal's top+bottom boundaries and screen
+       * (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen
+       * + 100% - we move modal completely off-screen, it's top boundary touches
+       *   bottom of the screen
+       * - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible
+       */
+      transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));
     }
   }
 
@@ -25,17 +34,22 @@
   .panel-body {
     height: 100%;
     overflow-y: hidden;
-  }
-  .setting-item {
-    border-bottom: 2px solid var(--fg, $fallback--fg);
-    margin: 1em 1em 1.4em;
-    padding-bottom: 1.4em;
 
     .btn {
       min-height: 28px;
       min-width: 10em;
       padding: 0 2em;
     }
+  }
+
+  .full-height {
+    height: 100%;
+  }
+
+  .setting-item {
+    border-bottom: 2px solid var(--fg, $fallback--fg);
+    margin: 1em 1em 1.4em;
+    padding-bottom: 1.4em;
 
     > div {
       margin-bottom: .5em;
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index 53481bdd..741c15c4 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -1,6 +1,5 @@
 <template>
 <Modal
-  v-if="isLoggedIn && !resettingForm"
   :is-open="modalActivated"
   class="settings-modal"
   :class="{ peek: modalPeeked }"
@@ -25,15 +24,57 @@
         :scrollableTabs="true"
         ref="tabSwitcher"
         >
-        <div :label="$t('settings.general')"><GeneralTab /></div>
-        <div :label="$t('settings.profile_tab')"><ProfileTab /></div>
-        <div :label="$t('settings.security_tab')"><SecurityTab /></div>
-        <div :label="$t('settings.filtering')"><FilteringTab /></div>
-        <div :label="$t('settings.theme')"><ThemeTab /></div>
-        <div :label="$t('settings.notifications')"><NotificationsTab /></div>
-        <div :label="$t('settings.data_import_export_tab')"><DataImportExportTab /></div>
-        <div :label="$t('settings.mutes_and_blocks')"><MutesAndBlocksTab /></div>
-        <div :label="$t('settings.version.title')"><VersionTab /></div>
+        <div
+          :label="$t('settings.general')"
+          >
+          <GeneralTab />
+        </div>
+        <div v-if="isLoggedIn"
+             :label="$t('settings.profile_tab')"
+             >
+          <ProfileTab />
+        </div>
+        <div
+          v-if="isLoggedIn"
+          :label="$t('settings.security_tab')"
+          >
+          <SecurityTab />
+        </div>
+        <div
+          :label="$t('settings.filtering')"
+          >
+          <FilteringTab />
+        </div>
+        <div
+          :label="$t('settings.theme')"
+          >
+          <ThemeTab />
+        </div>
+        <div
+          v-if="isLoggedIn"
+          :label="$t('settings.notifications')"
+          >
+          <NotificationsTab />
+        </div>
+        <div
+          v-if="isLoggedIn"
+          :label="$t('settings.data_import_export_tab')"
+          >
+          <DataImportExportTab />
+        </div>
+        <div
+          v-if="isLoggedIn"
+          :label="$t('settings.mutes_and_blocks')"
+          :fullHeight="true"
+          class="full-height"
+          >
+          <MutesAndBlocksTab />
+        </div>
+        <div
+          :label="$t('settings.version.title')"
+          >
+          <VersionTab />
+        </div>
       </tab-switcher>
     </div>
   </div>
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
index 7fce7b78..04f9c6dd 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
@@ -1,173 +1,176 @@
 <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="i => i"
+<tab-switcher
+  :scrollableTabs="true"
+  class="mutes-and-blocks-tab"
+  >
+  <div :label="$t('settings.blocks_tab')">
+    <div class="usersearch-wrapper">
+      <Autosuggest
+        :filter="filterUnblockedUsers"
+        :query="queryUserIds"
+        :placeholder="$t('settings.search_user_to_block')"
         >
-        <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>
+        <BlockCard
+          slot-scope="row"
+          :user-id="row.item"
+          />
+      </Autosuggest>
     </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="i => i"
+    <BlockList
+      :refresh="true"
+      :get-key="i => i"
+      >
+      <template
+        slot="header"
+        slot-scope="{selected}"
+        >
+        <div class="bulk-actions">
+          <ProgressButton
+            v-if="selected.length > 0"
+            class="btn btn-default bulk-action-button"
+            :click="() => blockUsers(selected)"
             >
-            <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>
+            {{ $t('user_card.block') }}
+            <template slot="progress">
+              {{ $t('user_card.block_progress') }}
             </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="i => i"
+          </ProgressButton>
+          <ProgressButton
+            v-if="selected.length > 0"
+            class="btn btn-default"
+            :click="() => unblockUsers(selected)"
             >
-            <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>
+            {{ $t('user_card.unblock') }}
+            <template slot="progress">
+              {{ $t('user_card.unblock_progress') }}
             </template>
-            <template
-              slot="item"
-              slot-scope="{item}"
-              >
-              <DomainMuteCard :domain="item" />
-            </template>
-            <template slot="empty">
-              {{ $t('settings.no_mutes') }}
-            </template>
-          </DomainMuteList>
+          </ProgressButton>
         </div>
-      </tab-switcher>
-    </div>
-  </tab-switcher>
+      </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="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="i => i"
+          >
+          <template
+            slot="header"
+            slot-scope="{selected}"
+            >
+            <div class="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="domain-mute-form">
+          <input
+            v-model="newDomainToMute"
+            :placeholder="$t('settings.type_domains_to_mute')"
+            type="text"
+            @keyup.enter="muteDomain"
+            >
+          <ProgressButton
+            class="btn btn-default domain-mute-button"
+            :click="muteDomain"
+            >
+            {{ $t('domain_mute_card.mute') }}
+            <template slot="progress">
+              {{ $t('domain_mute_card.mute_progress') }}
+            </template>
+          </ProgressButton>
+        </div>
+        <DomainMuteList
+          :refresh="true"
+          :get-key="i => i"
+          >
+          <template
+            slot="header"
+            slot-scope="{selected}"
+            >
+            <div class="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_tab.js"></script>
-<!-- <style lang="scss" src="./profile.scss"></style> -->
+<style lang="scss" src="./mutes_and_blocks_tab.scss"></style>
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
index 75b3017d..e0b1a2df 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
@@ -1,5 +1,6 @@
 @import 'src/_variables.scss';
 .theme-tab {
+  padding-bottom: 2em;
   .theme-warning {
     display: flex;
     align-items: baseline;
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index 97791de3..a54b474f 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -69,7 +69,6 @@ export default Vue.component('tab-switcher', {
         if (!slot.tag) return
         const classesTab = ['tab']
         const classesWrapper = ['tab-wrapper']
-
         if (this.activeIndex === index) {
           classesTab.push('active')
           classesWrapper.push('active')
@@ -101,12 +100,17 @@ export default Vue.component('tab-switcher', {
     const contents = this.$slots.default.map((slot, index) => {
       if (!slot.tag) return
       const active = this.activeIndex === index
+      const classes = [ active ? 'active' : 'hidden' ]
+      if (slot.data.attrs.fullHeight) {
+        classes.push('full-height')
+      }
+
       if (this.renderOnlyFocused) {
         return active
-          ? <div class="active">{slot}</div>
-          : <div class="hidden"></div>
+          ? <div class={classes.join(' ')}>{slot}</div>
+          : <div class={classes.join(' ')}></div>
       }
-      return <div class={active ? 'active' : 'hidden' }>{slot}</div>
+      return <div class={classes.join(' ')}>{slot}</div>
     })
 
     return (
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index a7b790a3..c9050781 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -59,7 +59,6 @@
       flex: 1 0 auto;
       overflow-y: auto;
       overflow-x: hidden;
-      padding-top: 5px;
       flex-direction: column;
       &::after {
         content: '';
@@ -78,6 +77,9 @@
           border-right-color: $fallback--border;
           border-right-color: var(--border, $fallback--border);
         }
+        &:last-child .tab {
+          margin-bottom: 0;
+        }
       }
       .tab {
         box-sizing: content-box;
@@ -87,8 +89,8 @@
         min-width: 1px;
         border-top-right-radius: 0;
         border-bottom-right-radius: 0;
-        // padding-right: 200px;
-        // margin-right: 6px - 200px;
+        padding-right: calc(1em + 200px);
+        margin-right: 6px - 200px;
         margin-left: 6px;
       }
 

From a872c53472a46973a18808254f9d812b074bb0ee Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 24 May 2020 02:29:09 +0300
Subject: [PATCH 397/483] misc fixes

---
 .../tabs/mutes_and_blocks_tab.scss            | 29 ++++++++++++++++
 src/components/tab_switcher/tab_switcher.scss | 33 ++++++++++---------
 2 files changed, 47 insertions(+), 15 deletions(-)
 create mode 100644 src/components/settings_modal/tabs/mutes_and_blocks_tab.scss

diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss
new file mode 100644
index 00000000..ceb64efb
--- /dev/null
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.scss
@@ -0,0 +1,29 @@
+.mutes-and-blocks-tab {
+    height: 100%;
+
+    .usersearch-wrapper {
+        padding: 1em;
+    }
+
+    .bulk-actions {
+        text-align: right;
+        padding: 0 1em;
+        min-height: 28px;
+    }
+
+    .bulk-action-button {
+        width: 10em
+    }
+
+    .domain-mute-form {
+        padding: 1em;
+        display: flex;
+        flex-direction: column
+    }
+
+    .domain-mute-button {
+        align-self: flex-end;
+        margin-top: 1em;
+        width: 10em
+    }
+}
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index c9050781..f994380f 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -61,14 +61,22 @@
       overflow-x: hidden;
       flex-direction: column;
       &::after {
-        content: '';
         flex: 1 1 auto;
+      }
+      &::before {
+        flex: 0 0 auto;
+        height: 0.5em;
+      }
+      &::after, &::before {
+        content: '';
         border-right: 1px solid;
         border-right-color: $fallback--border;
         border-right-color: var(--border, $fallback--border);
       }
       .tab-wrapper {
         min-width: 10em;
+        display: flex;
+        flex-direction: column;
         &:not(.active)::after {
           top: 0;
           right: 0;
@@ -77,14 +85,21 @@
           border-right-color: $fallback--border;
           border-right-color: var(--border, $fallback--border);
         }
+        &::before {
+          flex: 0 0 6px;
+          content: '';
+          border-right: 1px solid;
+          border-right-color: $fallback--border;
+          border-right-color: var(--border, $fallback--border);
+        }
         &:last-child .tab {
           margin-bottom: 0;
         }
       }
+
       .tab {
+        flex: 1;
         box-sizing: content-box;
-        width: 100%;
-        margin-bottom: 5px;
         min-width: 10em;
         min-width: 1px;
         border-top-right-radius: 0;
@@ -105,18 +120,6 @@
           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;
-      }
     }
   }
 

From 1e606d2f268e796a3efd2a995713c70a000daf62 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 03:14:41 +0300
Subject: [PATCH 398/483] split modal's content into another component, add
 lazy loading

---
 .../settings_modal/settings_modal.js          | 33 +-------
 .../settings_modal/settings_modal.scss        | 71 +----------------
 .../settings_modal/settings_modal.vue         | 77 +-----------------
 .../settings_modal/settings_modal_content.js  | 42 ++++++++++
 .../settings_modal_content.scss               | 73 +++++++++++++++++
 .../settings_modal/settings_modal_content.vue | 79 +++++++++++++++++++
 6 files changed, 200 insertions(+), 175 deletions(-)
 create mode 100644 src/components/settings_modal/settings_modal_content.js
 create mode 100644 src/components/settings_modal/settings_modal_content.scss
 create mode 100644 src/components/settings_modal/settings_modal_content.vue

diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index d60babf6..8f55af71 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -1,40 +1,11 @@
 import Modal from 'src/components/modal/modal.vue'
-import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
-
-import DataImportExportTab from './tabs/data_import_export_tab.vue'
-import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue'
-import NotificationsTab from './tabs/notifications_tab.vue'
-import FilteringTab from './tabs/filtering_tab.vue'
-import SecurityTab from './tabs/security_tab/security_tab.vue'
-import ProfileTab from './tabs/profile_tab.vue'
-import GeneralTab from './tabs/general_tab.vue'
-import VersionTab from './tabs/version_tab.vue'
-import ThemeTab from './tabs/theme_tab/theme_tab.vue'
 
 const SettingsModal = {
   components: {
     Modal,
-    TabSwitcher,
-
-    DataImportExportTab,
-    MutesAndBlocksTab,
-    NotificationsTab,
-    FilteringTab,
-    SecurityTab,
-    ProfileTab,
-    GeneralTab,
-    VersionTab,
-    ThemeTab
-  },
-  data () {
-    return {
-      resettingForm: false
-    }
+    SettingsModalContent: () => import('./settings_modal_content.vue')
   },
   computed: {
-    isLoggedIn () {
-      return !!this.$store.state.users.currentUser
-    },
     modalActivated () {
       return this.$store.state.interface.settingsModalState !== 'hidden'
     },
@@ -42,8 +13,6 @@ const SettingsModal = {
       return this.$store.state.interface.settingsModalState === 'minimized'
     }
   },
-  watch: {
-  },
   methods: {
     closeModal () {
       this.$store.dispatch('closeSettingsModal')
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index b82590a7..ece96364 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -2,11 +2,8 @@
 .settings-modal {
   overflow: hidden;
 
-  .settings_tab-switcher {
-    height: 100%;
-  }
   &.peek {
-    .settings-modal-panel {
+    .modal-panel {
       /* Explanation:
        * Modal is positioned vertically centered.
        * 100vh - 100% = Distance between modal's top+bottom boundaries and screen
@@ -18,70 +15,4 @@
       transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));
     }
   }
-
-  .settings-modal-panel {
-    transition: transform;
-    transition-timing-function: ease-in-out;
-    transition-duration: 300ms;
-    width: 1000px;
-    max-width: 90vw;
-    height: 90vh;
-    @media all and (max-width: 800px) {
-      max-width: 100vw;
-      height: 100vh;
-    }
-  }
-  .panel-body {
-    height: 100%;
-    overflow-y: hidden;
-
-    .btn {
-      min-height: 28px;
-      min-width: 10em;
-      padding: 0 2em;
-    }
-  }
-
-  .full-height {
-    height: 100%;
-  }
-
-  .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;
-    }
-
-    .number-input {
-      max-width: 6em;
-    }
-  }
 }
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index 741c15c4..b9c0689e 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -5,79 +5,10 @@
   :class="{ peek: modalPeeked }"
   :no-background="modalPeeked"
   >
-  <div class="settings-modal-panel panel">
-    <div class="panel-heading">
-      <span class="title">
-        {{ $t('settings.settings') }}
-      </span>
-      <button class="btn" @click="peekModal">
-        {{ $t('general.peek') }}
-      </button>
-      <button class="btn" @click="closeModal">
-        {{ $t('general.close') }}
-      </button>
-    </div>
-    <div class="panel-body">
-      <tab-switcher
-        class="settings_tab-switcher"
-        :sideTabBar="true"
-        :scrollableTabs="true"
-        ref="tabSwitcher"
-        >
-        <div
-          :label="$t('settings.general')"
-          >
-          <GeneralTab />
-        </div>
-        <div v-if="isLoggedIn"
-             :label="$t('settings.profile_tab')"
-             >
-          <ProfileTab />
-        </div>
-        <div
-          v-if="isLoggedIn"
-          :label="$t('settings.security_tab')"
-          >
-          <SecurityTab />
-        </div>
-        <div
-          :label="$t('settings.filtering')"
-          >
-          <FilteringTab />
-        </div>
-        <div
-          :label="$t('settings.theme')"
-          >
-          <ThemeTab />
-        </div>
-        <div
-          v-if="isLoggedIn"
-          :label="$t('settings.notifications')"
-          >
-          <NotificationsTab />
-        </div>
-        <div
-          v-if="isLoggedIn"
-          :label="$t('settings.data_import_export_tab')"
-          >
-          <DataImportExportTab />
-        </div>
-        <div
-          v-if="isLoggedIn"
-          :label="$t('settings.mutes_and_blocks')"
-          :fullHeight="true"
-          class="full-height"
-          >
-          <MutesAndBlocksTab />
-        </div>
-        <div
-          :label="$t('settings.version.title')"
-          >
-          <VersionTab />
-        </div>
-      </tab-switcher>
-    </div>
-  </div>
+  <SettingsModalContent
+    v-if="modalActivated"
+    class="modal-panel"
+    />
 </Modal>
 </template>
 
diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_content.js
new file mode 100644
index 00000000..bd8df672
--- /dev/null
+++ b/src/components/settings_modal/settings_modal_content.js
@@ -0,0 +1,42 @@
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
+
+import DataImportExportTab from './tabs/data_import_export_tab.vue'
+import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue'
+import NotificationsTab from './tabs/notifications_tab.vue'
+import FilteringTab from './tabs/filtering_tab.vue'
+import SecurityTab from './tabs/security_tab/security_tab.vue'
+import ProfileTab from './tabs/profile_tab.vue'
+import GeneralTab from './tabs/general_tab.vue'
+import VersionTab from './tabs/version_tab.vue'
+import ThemeTab from './tabs/theme_tab/theme_tab.vue'
+
+const SettingsModalContent = {
+  components: {
+    TabSwitcher,
+
+    DataImportExportTab,
+    MutesAndBlocksTab,
+    NotificationsTab,
+    FilteringTab,
+    SecurityTab,
+    ProfileTab,
+    GeneralTab,
+    VersionTab,
+    ThemeTab
+  },
+  computed: {
+    isLoggedIn () {
+      return !!this.$store.state.users.currentUser
+    }
+  },
+  methods: {
+    closeModal () {
+      this.$store.dispatch('closeSettingsModal')
+    },
+    peekModal () {
+      this.$store.dispatch('togglePeekSettingsModal')
+    }
+  }
+}
+
+export default SettingsModalContent
diff --git a/src/components/settings_modal/settings_modal_content.scss b/src/components/settings_modal/settings_modal_content.scss
new file mode 100644
index 00000000..92e167a2
--- /dev/null
+++ b/src/components/settings_modal/settings_modal_content.scss
@@ -0,0 +1,73 @@
+@import 'src/_variables.scss';
+
+.settings-modal-panel {
+  overflow: hidden;
+  transition: transform;
+  transition-timing-function: ease-in-out;
+  transition-duration: 300ms;
+  width: 1000px;
+  max-width: 90vw;
+  height: 90vh;
+
+  @media all and (max-width: 800px) {
+    max-width: 100vw;
+    height: 100vh;
+  }
+
+  .settings_tab-switcher {
+    height: 100%;
+  }
+  .panel-body {
+    height: 100%;
+    overflow-y: hidden;
+
+    .btn {
+      min-height: 28px;
+      min-width: 10em;
+      padding: 0 2em;
+    }
+  }
+
+  .full-height {
+    height: 100%;
+  }
+
+  .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;
+    }
+
+    .number-input {
+      max-width: 6em;
+    }
+  }
+}
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
new file mode 100644
index 00000000..1778c23b
--- /dev/null
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -0,0 +1,79 @@
+<template>
+  <div class="settings-modal-panel panel">
+    <div class="panel-heading">
+      <span class="title">
+        {{ $t('settings.settings') }}
+      </span>
+      <button class="btn" @click="peekModal">
+        {{ $t('general.peek') }}
+      </button>
+      <button class="btn" @click="closeModal">
+        {{ $t('general.close') }}
+      </button>
+    </div>
+    <div class="panel-body">
+      <tab-switcher
+        class="settings_tab-switcher"
+        :sideTabBar="true"
+        :scrollableTabs="true"
+        ref="tabSwitcher"
+        >
+        <div
+          :label="$t('settings.general')"
+          >
+          <GeneralTab />
+        </div>
+        <div v-if="isLoggedIn"
+             :label="$t('settings.profile_tab')"
+             >
+          <ProfileTab />
+        </div>
+        <div
+          v-if="isLoggedIn"
+          :label="$t('settings.security_tab')"
+          >
+          <SecurityTab />
+        </div>
+        <div
+          :label="$t('settings.filtering')"
+          >
+          <FilteringTab />
+        </div>
+        <div
+          :label="$t('settings.theme')"
+          >
+          <ThemeTab />
+        </div>
+        <div
+          v-if="isLoggedIn"
+          :label="$t('settings.notifications')"
+          >
+          <NotificationsTab />
+        </div>
+        <div
+          v-if="isLoggedIn"
+          :label="$t('settings.data_import_export_tab')"
+          >
+          <DataImportExportTab />
+        </div>
+        <div
+          v-if="isLoggedIn"
+          :label="$t('settings.mutes_and_blocks')"
+          :fullHeight="true"
+          class="full-height"
+          >
+          <MutesAndBlocksTab />
+        </div>
+        <div
+          :label="$t('settings.version.title')"
+          >
+          <VersionTab />
+        </div>
+      </tab-switcher>
+    </div>
+  </div>
+</template>
+
+<script src="./settings_modal_content.js"></script>
+
+<style src="./settings_modal_content.scss" lang="scss"></style>

From bcea2e4d12a2f8a460def340debdff3d0dec7770 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 03:30:14 +0300
Subject: [PATCH 399/483] cleanup

---
 src/components/settings_modal/settings_modal.js | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index 8f55af71..192ba5d3 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -13,14 +13,6 @@ const SettingsModal = {
       return this.$store.state.interface.settingsModalState === 'minimized'
     }
   },
-  methods: {
-    closeModal () {
-      this.$store.dispatch('closeSettingsModal')
-    },
-    peekModal () {
-      this.$store.dispatch('togglePeekSettingsModal')
-    }
-  }
 }
 
 export default SettingsModal

From e7ba4255bbfdc199c513e83007fc6956166b6dc0 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 03:43:55 +0300
Subject: [PATCH 400/483] eslint --fix + small fix

---
 .../settings_modal/settings_modal.js          |   2 +-
 .../settings_modal/settings_modal.vue         |  18 +-
 .../settings_modal/settings_modal_content.vue |  41 +-
 .../tabs/data_import_export_tab.vue           |  64 +--
 .../settings_modal/tabs/filtering_tab.vue     | 152 +++----
 .../settings_modal/tabs/general_tab.js        |   3 +-
 .../settings_modal/tabs/general_tab.vue       | 306 +++++++-------
 .../tabs/mutes_and_blocks_tab.vue             | 300 +++++++-------
 .../settings_modal/tabs/notifications_tab.vue |  94 ++---
 .../settings_modal/tabs/profile_tab.js        |   2 +-
 .../settings_modal/tabs/profile_tab.vue       | 388 +++++++++---------
 .../tabs/security_tab/security_tab.vue        | 260 ++++++------
 .../tabs/theme_tab/theme_tab.vue              | 168 ++++----
 .../settings_modal/tabs/version_tab.vue       |  50 +--
 14 files changed, 927 insertions(+), 921 deletions(-)

diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index 192ba5d3..d38c5751 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -12,7 +12,7 @@ const SettingsModal = {
     modalPeeked () {
       return this.$store.state.interface.settingsModalState === 'minimized'
     }
-  },
+  }
 }
 
 export default SettingsModal
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index b9c0689e..b6ca5c6b 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -1,15 +1,15 @@
 <template>
-<Modal
-  :is-open="modalActivated"
-  class="settings-modal"
-  :class="{ peek: modalPeeked }"
-  :no-background="modalPeeked"
+  <Modal
+    :is-open="modalActivated"
+    class="settings-modal"
+    :class="{ peek: modalPeeked }"
+    :no-background="modalPeeked"
   >
-  <SettingsModalContent
-    v-if="modalActivated"
-    class="modal-panel"
+    <SettingsModalContent
+      v-if="modalActivated"
+      class="modal-panel"
     />
-</Modal>
+  </Modal>
 </template>
 
 <script src="./settings_modal.js"></script>
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
index 1778c23b..013bf34f 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -4,56 +4,63 @@
       <span class="title">
         {{ $t('settings.settings') }}
       </span>
-      <button class="btn" @click="peekModal">
+      <button
+        class="btn"
+        @click="peekModal"
+      >
         {{ $t('general.peek') }}
       </button>
-      <button class="btn" @click="closeModal">
+      <button
+        class="btn"
+        @click="closeModal"
+      >
         {{ $t('general.close') }}
       </button>
     </div>
     <div class="panel-body">
       <tab-switcher
-        class="settings_tab-switcher"
-        :sideTabBar="true"
-        :scrollableTabs="true"
         ref="tabSwitcher"
-        >
+        class="settings_tab-switcher"
+        :side-tab-bar="true"
+        :scrollable-tabs="true"
+      >
         <div
           :label="$t('settings.general')"
-          >
+        >
           <GeneralTab />
         </div>
-        <div v-if="isLoggedIn"
-             :label="$t('settings.profile_tab')"
-             >
+        <div
+          v-if="isLoggedIn"
+          :label="$t('settings.profile_tab')"
+        >
           <ProfileTab />
         </div>
         <div
           v-if="isLoggedIn"
           :label="$t('settings.security_tab')"
-          >
+        >
           <SecurityTab />
         </div>
         <div
           :label="$t('settings.filtering')"
-          >
+        >
           <FilteringTab />
         </div>
         <div
           :label="$t('settings.theme')"
-          >
+        >
           <ThemeTab />
         </div>
         <div
           v-if="isLoggedIn"
           :label="$t('settings.notifications')"
-          >
+        >
           <NotificationsTab />
         </div>
         <div
           v-if="isLoggedIn"
           :label="$t('settings.data_import_export_tab')"
-          >
+        >
           <DataImportExportTab />
         </div>
         <div
@@ -61,12 +68,12 @@
           :label="$t('settings.mutes_and_blocks')"
           :fullHeight="true"
           class="full-height"
-          >
+        >
           <MutesAndBlocksTab />
         </div>
         <div
           :label="$t('settings.version.title')"
-          >
+        >
           <VersionTab />
         </div>
       </tab-switcher>
diff --git a/src/components/settings_modal/tabs/data_import_export_tab.vue b/src/components/settings_modal/tabs/data_import_export_tab.vue
index 3ddc8b03..b5d0f5ed 100644
--- a/src/components/settings_modal/tabs/data_import_export_tab.vue
+++ b/src/components/settings_modal/tabs/data_import_export_tab.vue
@@ -1,42 +1,42 @@
 <template>
-<div
-  :label="$t('settings.data_import_export_tab')"
+  <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 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.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_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 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>
 </template>
 
 <script src="./data_import_export_tab.js"></script>
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
index 647ec7b4..eea41514 100644
--- a/src/components/settings_modal/tabs/filtering_tab.vue
+++ b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -1,86 +1,86 @@
 <template>
-<div :label="$t('settings.filtering')">
-  <div class="setting-item">
-    <div class="select-multiple">
-      <span class="label">{{ $t('settings.notification_visibility') }}</span>
-      <ul class="option-list">
-        <li>
-          <Checkbox v-model="notificationVisibility.likes">
-            {{ $t('settings.notification_visibility_likes') }}
-          </Checkbox>
-        </li>
-        <li>
-          <Checkbox v-model="notificationVisibility.repeats">
-            {{ $t('settings.notification_visibility_repeats') }}
-          </Checkbox>
-        </li>
-        <li>
-          <Checkbox v-model="notificationVisibility.follows">
-            {{ $t('settings.notification_visibility_follows') }}
-          </Checkbox>
-        </li>
-        <li>
-          <Checkbox v-model="notificationVisibility.mentions">
-            {{ $t('settings.notification_visibility_mentions') }}
-          </Checkbox>
-        </li>
-        <li>
-          <Checkbox v-model="notificationVisibility.moves">
-            {{ $t('settings.notification_visibility_moves') }}
-          </Checkbox>
-        </li>
-        <li>
-          <Checkbox v-model="notificationVisibility.emojiReactions">
-            {{ $t('settings.notification_visibility_emoji_reactions') }}
-          </Checkbox>
-        </li>
-      </ul>
-    </div>
-    <div>
-      {{ $t('settings.replies_in_timeline') }}
-      <label
-        for="replyVisibility"
-        class="select"
+  <div :label="$t('settings.filtering')">
+    <div class="setting-item">
+      <div class="select-multiple">
+        <span class="label">{{ $t('settings.notification_visibility') }}</span>
+        <ul class="option-list">
+          <li>
+            <Checkbox v-model="notificationVisibility.likes">
+              {{ $t('settings.notification_visibility_likes') }}
+            </Checkbox>
+          </li>
+          <li>
+            <Checkbox v-model="notificationVisibility.repeats">
+              {{ $t('settings.notification_visibility_repeats') }}
+            </Checkbox>
+          </li>
+          <li>
+            <Checkbox v-model="notificationVisibility.follows">
+              {{ $t('settings.notification_visibility_follows') }}
+            </Checkbox>
+          </li>
+          <li>
+            <Checkbox v-model="notificationVisibility.mentions">
+              {{ $t('settings.notification_visibility_mentions') }}
+            </Checkbox>
+          </li>
+          <li>
+            <Checkbox v-model="notificationVisibility.moves">
+              {{ $t('settings.notification_visibility_moves') }}
+            </Checkbox>
+          </li>
+          <li>
+            <Checkbox v-model="notificationVisibility.emojiReactions">
+              {{ $t('settings.notification_visibility_emoji_reactions') }}
+            </Checkbox>
+          </li>
+        </ul>
+      </div>
+      <div>
+        {{ $t('settings.replies_in_timeline') }}
+        <label
+          for="replyVisibility"
+          class="select"
         >
-        <select
-          id="replyVisibility"
-          v-model="replyVisibility"
+          <select
+            id="replyVisibility"
+            v-model="replyVisibility"
           >
-          <option
-            value="all"
-            selected
+            <option
+              value="all"
+              selected
             >{{ $t('settings.reply_visibility_all') }}</option>
-          <option value="following">{{ $t('settings.reply_visibility_following') }}</option>
-          <option value="self">{{ $t('settings.reply_visibility_self') }}</option>
-        </select>
-        <i class="icon-down-open" />
-      </label>
+            <option value="following">{{ $t('settings.reply_visibility_following') }}</option>
+            <option value="self">{{ $t('settings.reply_visibility_self') }}</option>
+          </select>
+          <i class="icon-down-open" />
+        </label>
+      </div>
+      <div>
+        <Checkbox v-model="hidePostStats">
+          {{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }}
+        </Checkbox>
+      </div>
+      <div>
+        <Checkbox v-model="hideUserStats">
+          {{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }}
+        </Checkbox>
+      </div>
     </div>
-    <div>
-      <Checkbox v-model="hidePostStats">
-        {{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }}
-      </Checkbox>
-    </div>
-    <div>
-      <Checkbox v-model="hideUserStats">
-        {{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }}
-      </Checkbox>
-    </div>
-  </div>
-  <div class="setting-item">
-    <div>
-      <p>{{ $t('settings.filtering_explanation') }}</p>
-      <textarea
-        id="muteWords"
-        v-model="muteWordsString"
+    <div class="setting-item">
+      <div>
+        <p>{{ $t('settings.filtering_explanation') }}</p>
+        <textarea
+          id="muteWords"
+          v-model="muteWordsString"
         />
-    </div>
-    <div>
-      <Checkbox v-model="hideFilteredStatuses">
-        {{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }}
-      </Checkbox>
+      </div>
+      <div>
+        <Checkbox v-model="hideFilteredStatuses">
+          {{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }}
+        </Checkbox>
+      </div>
     </div>
   </div>
-</div>
 </template>
 <script src="./filtering_tab.js"></script>
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index 82bf6862..9df1da79 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -5,7 +5,6 @@ import SharedComputedObject from './helpers/shared_computed_object.js'
 
 const GeneralTab = {
   data () {
-    const instance = this.$store.state.instance
     return {
       loopSilentAvailable:
       // Firefox
@@ -13,7 +12,7 @@ const GeneralTab = {
       // Chrome-likes
       Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
       // Future spec, still not supported in Nightly 63 as of 08/2018
-      Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks'),
+      Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks')
     }
   },
   components: {
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 0d2da07a..f89c0480 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -1,160 +1,160 @@
 <template>
-<div :label="$t('settings.general')">
-  <div class="setting-item">
-    <h2>{{ $t('settings.interface') }}</h2>
-    <ul class="setting-list">
-      <li>
-        <interface-language-switcher />
-      </li>
-      <li v-if="instanceSpecificPanelPresent">
-        <Checkbox v-model="hideISP">
-          {{ $t('settings.hide_isp') }}
-        </Checkbox>
-      </li>
-    </ul>
-  </div>
-  <div class="setting-item">
-    <h2>{{ $t('nav.timeline') }}</h2>
-    <ul class="setting-list">
-      <li>
-        <Checkbox v-model="hideMutedPosts">
-          {{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }}
-        </Checkbox>
-      </li>
-      <li>
-        <Checkbox v-model="collapseMessageWithSubject">
-          {{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }}
-        </Checkbox>
-      </li>
-      <li>
-        <Checkbox v-model="streaming">
-          {{ $t('settings.streaming') }}
-        </Checkbox>
-        <ul
-          class="setting-list suboptions"
-          :class="[{disabled: !streaming}]"
+  <div :label="$t('settings.general')">
+    <div class="setting-item">
+      <h2>{{ $t('settings.interface') }}</h2>
+      <ul class="setting-list">
+        <li>
+          <interface-language-switcher />
+        </li>
+        <li v-if="instanceSpecificPanelPresent">
+          <Checkbox v-model="hideISP">
+            {{ $t('settings.hide_isp') }}
+          </Checkbox>
+        </li>
+      </ul>
+    </div>
+    <div class="setting-item">
+      <h2>{{ $t('nav.timeline') }}</h2>
+      <ul class="setting-list">
+        <li>
+          <Checkbox v-model="hideMutedPosts">
+            {{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="collapseMessageWithSubject">
+            {{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="streaming">
+            {{ $t('settings.streaming') }}
+          </Checkbox>
+          <ul
+            class="setting-list suboptions"
+            :class="[{disabled: !streaming}]"
           >
-          <li>
-            <Checkbox
-              v-model="pauseOnUnfocused"
-              :disabled="!streaming"
+            <li>
+              <Checkbox
+                v-model="pauseOnUnfocused"
+                :disabled="!streaming"
               >
-              {{ $t('settings.pause_on_unfocused') }}
-            </Checkbox>
-          </li>
-        </ul>
-      </li>
-      <li>
-        <Checkbox v-model="useStreamingApi">
-          {{ $t('settings.useStreamingApi') }}
-          <br>
-          <small>
-            {{ $t('settings.useStreamingApiWarning') }}
-          </small>
-        </Checkbox>
-      </li>
-      <li>
-        <Checkbox v-model="autoLoad">
-          {{ $t('settings.autoload') }}
-        </Checkbox>
-      </li>
-      <li>
-        <Checkbox v-model="hoverPreview">
-          {{ $t('settings.reply_link_preview') }}
-        </Checkbox>
-      </li>
-      <li>
-        <Checkbox v-model="emojiReactionsOnTimeline">
-          {{ $t('settings.emoji_reactions_on_timeline') }}
-        </Checkbox>
-      </li>
-    </ul>
-  </div>
+                {{ $t('settings.pause_on_unfocused') }}
+              </Checkbox>
+            </li>
+          </ul>
+        </li>
+        <li>
+          <Checkbox v-model="useStreamingApi">
+            {{ $t('settings.useStreamingApi') }}
+            <br>
+            <small>
+              {{ $t('settings.useStreamingApiWarning') }}
+            </small>
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="autoLoad">
+            {{ $t('settings.autoload') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="hoverPreview">
+            {{ $t('settings.reply_link_preview') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="emojiReactionsOnTimeline">
+            {{ $t('settings.emoji_reactions_on_timeline') }}
+          </Checkbox>
+        </li>
+      </ul>
+    </div>
 
-  <div class="setting-item">
-    <h2>{{ $t('settings.composing') }}</h2>
-    <ul class="setting-list">
-      <li>
-        <Checkbox v-model="scopeCopy">
-          {{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }}
-        </Checkbox>
-      </li>
-      <li>
-        <Checkbox v-model="alwaysShowSubjectInput">
-          {{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }}
-        </Checkbox>
-      </li>
-      <li>
-        <div>
-          {{ $t('settings.subject_line_behavior') }}
-          <label
-            for="subjectLineBehavior"
-            class="select"
+    <div class="setting-item">
+      <h2>{{ $t('settings.composing') }}</h2>
+      <ul class="setting-list">
+        <li>
+          <Checkbox v-model="scopeCopy">
+            {{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="alwaysShowSubjectInput">
+            {{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }}
+          </Checkbox>
+        </li>
+        <li>
+          <div>
+            {{ $t('settings.subject_line_behavior') }}
+            <label
+              for="subjectLineBehavior"
+              class="select"
             >
-            <select
-              id="subjectLineBehavior"
-              v-model="subjectLineBehavior"
+              <select
+                id="subjectLineBehavior"
+                v-model="subjectLineBehavior"
               >
-              <option value="email">
-                {{ $t('settings.subject_line_email') }}
-                {{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }}
-              </option>
-              <option value="masto">
-                {{ $t('settings.subject_line_mastodon') }}
-                {{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
-              </option>
-              <option value="noop">
-                {{ $t('settings.subject_line_noop') }}
-                {{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
-              </option>
-            </select>
-            <i class="icon-down-open" />
-          </label>
-        </div>
-      </li>
-      <li v-if="postFormats.length > 0">
-        <div>
-          {{ $t('settings.post_status_content_type') }}
-          <label
-            for="postContentType"
-            class="select"
+                <option value="email">
+                  {{ $t('settings.subject_line_email') }}
+                  {{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }}
+                </option>
+                <option value="masto">
+                  {{ $t('settings.subject_line_mastodon') }}
+                  {{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
+                </option>
+                <option value="noop">
+                  {{ $t('settings.subject_line_noop') }}
+                  {{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
+                </option>
+              </select>
+              <i class="icon-down-open" />
+            </label>
+          </div>
+        </li>
+        <li v-if="postFormats.length > 0">
+          <div>
+            {{ $t('settings.post_status_content_type') }}
+            <label
+              for="postContentType"
+              class="select"
             >
-            <select
-              id="postContentType"
-              v-model="postContentType"
+              <select
+                id="postContentType"
+                v-model="postContentType"
               >
-              <option
-                v-for="postFormat in postFormats"
-                :key="postFormat"
-                :value="postFormat"
+                <option
+                  v-for="postFormat in postFormats"
+                  :key="postFormat"
+                  :value="postFormat"
                 >
-                {{ $t(`post_status.content_type["${postFormat}"]`) }}
-                {{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
-              </option>
-            </select>
-            <i class="icon-down-open" />
-          </label>
-        </div>
-      </li>
-      <li>
-        <Checkbox v-model="minimalScopesMode">
-          {{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }}
-        </Checkbox>
-      </li>
-      <li>
-        <Checkbox v-model="autohideFloatingPostButton">
-          {{ $t('settings.autohide_floating_post_button') }}
-        </Checkbox>
-      </li>
-      <li>
-        <Checkbox v-model="padEmoji">
-          {{ $t('settings.pad_emoji') }}
-        </Checkbox>
-      </li>
-    </ul>
-  </div>
+                  {{ $t(`post_status.content_type["${postFormat}"]`) }}
+                  {{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
+                </option>
+              </select>
+              <i class="icon-down-open" />
+            </label>
+          </div>
+        </li>
+        <li>
+          <Checkbox v-model="minimalScopesMode">
+            {{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="autohideFloatingPostButton">
+            {{ $t('settings.autohide_floating_post_button') }}
+          </Checkbox>
+        </li>
+        <li>
+          <Checkbox v-model="padEmoji">
+            {{ $t('settings.pad_emoji') }}
+          </Checkbox>
+        </li>
+      </ul>
+    </div>
 
-  <div class="setting-item">
+    <div class="setting-item">
       <h2>{{ $t('settings.attachments') }}</h2>
       <ul class="setting-list">
         <li>
@@ -178,7 +178,7 @@
             type="number"
             min="0"
             step="1"
-            >
+          >
         </li>
         <li>
           <Checkbox v-model="hideNsfw">
@@ -190,7 +190,7 @@
             <Checkbox
               v-model="preloadImage"
               :disabled="!hideNsfw"
-              >
+            >
               {{ $t('settings.preload_images') }}
             </Checkbox>
           </li>
@@ -198,7 +198,7 @@
             <Checkbox
               v-model="useOneClickNsfw"
               :disabled="!hideNsfw"
-              >
+            >
               {{ $t('settings.use_one_click_nsfw') }}
             </Checkbox>
           </li>
@@ -215,18 +215,18 @@
           <ul
             class="setting-list suboptions"
             :class="[{disabled: !streaming}]"
-            >
+          >
             <li>
               <Checkbox
                 v-model="loopVideoSilentOnly"
                 :disabled="!loopVideo || !loopSilentAvailable"
-                >
+              >
                 {{ $t('settings.loop_video_silent_only') }}
               </Checkbox>
               <div
                 v-if="!loopSilentAvailable"
                 class="unavailable"
-                >
+              >
                 <i class="icon-globe" />! {{ $t('settings.limited_availability') }}
               </div>
             </li>
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
index 04f9c6dd..6884b7be 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
@@ -1,175 +1,175 @@
 <template>
-<tab-switcher
-  :scrollableTabs="true"
-  class="mutes-and-blocks-tab"
+  <tab-switcher
+    :scrollable-tabs="true"
+    class="mutes-and-blocks-tab"
   >
-  <div :label="$t('settings.blocks_tab')">
-    <div class="usersearch-wrapper">
-      <Autosuggest
-        :filter="filterUnblockedUsers"
-        :query="queryUserIds"
-        :placeholder="$t('settings.search_user_to_block')"
+    <div :label="$t('settings.blocks_tab')">
+      <div class="usersearch-wrapper">
+        <Autosuggest
+          :filter="filterUnblockedUsers"
+          :query="queryUserIds"
+          :placeholder="$t('settings.search_user_to_block')"
         >
-        <BlockCard
-          slot-scope="row"
-          :user-id="row.item"
+          <BlockCard
+            slot-scope="row"
+            :user-id="row.item"
           />
-      </Autosuggest>
-    </div>
-    <BlockList
-      :refresh="true"
-      :get-key="i => i"
+        </Autosuggest>
+      </div>
+      <BlockList
+        :refresh="true"
+        :get-key="i => i"
       >
-      <template
-        slot="header"
-        slot-scope="{selected}"
+        <template
+          slot="header"
+          slot-scope="{selected}"
         >
-        <div class="bulk-actions">
-          <ProgressButton
-            v-if="selected.length > 0"
-            class="btn btn-default bulk-action-button"
-            :click="() => blockUsers(selected)"
+          <div class="bulk-actions">
+            <ProgressButton
+              v-if="selected.length > 0"
+              class="btn btn-default bulk-action-button"
+              :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.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}"
+              {{ $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>
+          <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="usersearch-wrapper">
-          <Autosuggest
-            :filter="filterUnMutedUsers"
-            :query="queryUserIds"
-            :placeholder="$t('settings.search_user_to_mute')"
+    <div :label="$t('settings.mutes_tab')">
+      <tab-switcher>
+        <div label="Users">
+          <div class="usersearch-wrapper">
+            <Autosuggest
+              :filter="filterUnMutedUsers"
+              :query="queryUserIds"
+              :placeholder="$t('settings.search_user_to_mute')"
             >
-            <MuteCard
-              slot-scope="row"
-              :user-id="row.item"
+              <MuteCard
+                slot-scope="row"
+                :user-id="row.item"
               />
-          </Autosuggest>
-        </div>
-        <MuteList
-          :refresh="true"
-          :get-key="i => i"
+            </Autosuggest>
+          </div>
+          <MuteList
+            :refresh="true"
+            :get-key="i => i"
           >
-          <template
-            slot="header"
-            slot-scope="{selected}"
+            <template
+              slot="header"
+              slot-scope="{selected}"
             >
-            <div class="bulk-actions">
-              <ProgressButton
-                v-if="selected.length > 0"
-                class="btn btn-default"
-                :click="() => muteUsers(selected)"
+              <div class="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.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="domain-mute-form">
-          <input
-            v-model="newDomainToMute"
-            :placeholder="$t('settings.type_domains_to_mute')"
-            type="text"
-            @keyup.enter="muteDomain"
-            >
-          <ProgressButton
-            class="btn btn-default domain-mute-button"
-            :click="muteDomain"
-            >
-            {{ $t('domain_mute_card.mute') }}
-            <template slot="progress">
-              {{ $t('domain_mute_card.mute_progress') }}
+                  {{ $t('user_card.unmute') }}
+                  <template slot="progress">
+                    {{ $t('user_card.unmute_progress') }}
+                  </template>
+                </ProgressButton>
+              </div>
             </template>
-          </ProgressButton>
+            <template
+              slot="item"
+              slot-scope="{item}"
+            >
+              <MuteCard :user-id="item" />
+            </template>
+            <template slot="empty">
+              {{ $t('settings.no_mutes') }}
+            </template>
+          </MuteList>
         </div>
-        <DomainMuteList
-          :refresh="true"
-          :get-key="i => i"
+
+        <div :label="$t('settings.domain_mutes')">
+          <div class="domain-mute-form">
+            <input
+              v-model="newDomainToMute"
+              :placeholder="$t('settings.type_domains_to_mute')"
+              type="text"
+              @keyup.enter="muteDomain"
+            >
+            <ProgressButton
+              class="btn btn-default domain-mute-button"
+              :click="muteDomain"
+            >
+              {{ $t('domain_mute_card.mute') }}
+              <template slot="progress">
+                {{ $t('domain_mute_card.mute_progress') }}
+              </template>
+            </ProgressButton>
+          </div>
+          <DomainMuteList
+            :refresh="true"
+            :get-key="i => i"
           >
-          <template
-            slot="header"
-            slot-scope="{selected}"
+            <template
+              slot="header"
+              slot-scope="{selected}"
             >
-            <div class="bulk-actions">
-              <ProgressButton
-                v-if="selected.length > 0"
-                class="btn btn-default"
-                :click="() => unmuteDomains(selected)"
+              <div class="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}"
+                  {{ $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>
+              <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_tab.js"></script>
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue
index 19181e24..b7a3cb37 100644
--- a/src/components/settings_modal/tabs/notifications_tab.vue
+++ b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -1,53 +1,53 @@
 <template>
-<div :label="$t('settings.notifications')">
-  <div class="setting-item">
-    <h2>{{ $t('settings.notification_setting_filters') }}</h2>
-    <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 :label="$t('settings.notifications')">
+    <div class="setting-item">
+      <h2>{{ $t('settings.notification_setting_filters') }}</h2>
+      <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>
+    </div>
+
+    <div class="setting-item">
+      <h2>{{ $t('settings.notification_setting_privacy') }}</h2>
+      <p>
+        <Checkbox v-model="notificationSettings.privacy_option">
+          {{ $t('settings.notification_setting_privacy_option') }}
+        </Checkbox>
+      </p>
+    </div>
+    <div class="setting-item">
+      <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 class="setting-item">
-    <h2>{{ $t('settings.notification_setting_privacy') }}</h2>
-    <p>
-      <Checkbox v-model="notificationSettings.privacy_option">
-        {{ $t('settings.notification_setting_privacy_option') }}
-      </Checkbox>
-    </p>
-  </div>
-  <div class="setting-item">
-    <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_tab.js"></script>
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index 0b4951d3..8658b097 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -32,7 +32,7 @@ const ProfileTab = {
       background: null,
       backgroundPreview: null,
       bannerUploadError: null,
-      backgroundUploadError: null,
+      backgroundUploadError: null
     }
   },
   components: {
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index 9dd89b99..fff4f970 100644
--- a/src/components/settings_modal/tabs/profile_tab.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -1,212 +1,212 @@
 <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"
+  <div class="profile-tab">
+    <div class="setting-item">
+      <h2>{{ $t('settings.name_bio') }}</h2>
+      <p>{{ $t('settings.name') }}</p>
+      <EmojiInput
         v-model="newName"
-        classname="name-changer"
-        >
-    </EmojiInput>
-    <p>{{ $t('settings.bio') }}</p>
-    <EmojiInput
-      v-model="newBio"
-      enable-emoji-picker
-      :suggest="emojiUserSuggestor"
+        enable-emoji-picker
+        :suggest="emojiSuggestor"
       >
-      <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"
+        <input
+          id="username"
+          v-model="newName"
+          classname="name-changer"
         >
-        <scope-selector
-          :show-all="true"
-          :user-default="newDefaultScope"
-          :initial-scope="newDefaultScope"
-          :on-scope-change="changeVis"
+      </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>
-    <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"
+    <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)"
         >
-        {{ $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 }}
+      </div>
       <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"
+        v-if="backgroundUploading"
+        class=" icon-spin4 animate-spin uploading"
       />
-    <button
-      v-else-if="backgroundPreview"
-      class="btn btn-default"
-      @click="submitBg"
+      <button
+        v-else-if="backgroundPreview"
+        class="btn btn-default"
+        @click="submitBg"
       >
-      {{ $t('general.submit') }}
-    </button>
-    <div
-      v-if="backgroundUploadError"
-      class="alert error"
+        {{ $t('general.submit') }}
+      </button>
+      <div
+        v-if="backgroundUploadError"
+        class="alert error"
       >
-      Error: {{ backgroundUploadError }}
-      <i
-        class="button-icon icon-cancel"
-        @click="clearUploadError('background')"
+        Error: {{ backgroundUploadError }}
+        <i
+          class="button-icon icon-cancel"
+          @click="clearUploadError('background')"
         />
+      </div>
     </div>
   </div>
-</div>
 </template>
 
 <script src="./profile_tab.js"></script>
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue
index 45bacec1..3d32d73d 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.vue
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue
@@ -1,142 +1,142 @@
 <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 :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"
+      </div>
+      <div>
+        <p>{{ $t('settings.current_password') }}</p>
+        <input
+          v-model="changeEmailPassword"
+          type="password"
+          autocomplete="current-password"
         >
+      </div>
       <button
         class="btn btn-default"
-        @click="deleteAccount"
+        @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"
         >
-        {{ $t('settings.delete_account') }}
+      </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>
-    <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_tab.js"></script>
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
index 6f6cf1d6..33098498 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
@@ -1,90 +1,90 @@
 <template>
-<div class="theme-tab">
-  <div class="presets-container">
-    <div class="save-load">
-      <div
-        v-if="themeWarning"
-        class="theme-warning"
+  <div class="theme-tab">
+    <div class="presets-container">
+      <div class="save-load">
+        <div
+          v-if="themeWarning"
+          class="theme-warning"
         >
-        <div class="alert warning">
-          {{ themeWarningHelp }}
-        </div>
-        <div class="buttons">
-          <template v-if="themeWarning.type === 'snapshot_source_mismatch'">
-            <button
-              class="btn"
-              @click="forceLoad"
-              >
-              {{ $t('settings.style.switcher.use_source') }}
-            </button>
-            <button
-              class="btn"
-              @click="forceSnapshot"
-              >
-              {{ $t('settings.style.switcher.use_snapshot') }}
-            </button>
-          </template>
-          <template v-else-if="themeWarning.noActionsPossible">
-            <button
-              class="btn"
-              @click="dismissWarning"
-              >
-              {{ $t('general.dismiss') }}
-            </button>
-          </template>
-          <template v-else>
-            <button
-              class="btn"
-              @click="forceLoad"
-              >
-              {{ $t('settings.style.switcher.load_theme') }}
-            </button>
-            <button
-              class="btn"
-              @click="dismissWarning"
-              >
-              {{ $t('settings.style.switcher.keep_as_is') }}
-            </button>
-          </template>
-        </div>
-      </div>
-      <ExportImport
-        :export-object="exportedTheme"
-        :export-label="$t(&quot;settings.export_theme&quot;)"
-        :import-label="$t(&quot;settings.import_theme&quot;)"
-        :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
-        :on-import="onImport"
-        :validator="importValidator"
-        >
-        <template slot="before">
-          <div class="presets">
-            {{ $t('settings.presets') }}
-            <label
-              for="preset-switcher"
-              class="select"
-              >
-              <select
-                id="preset-switcher"
-                v-model="selected"
-                class="preset-switcher"
-                >
-                <option
-                  v-for="style in availableStyles"
-                  :key="style.name"
-                  :value="style"
-                  :style="{
-                          backgroundColor: style[1] || (style.theme || style.source).colors.bg,
-                          color: style[3] || (style.theme || style.source).colors.text
-                          }"
-                  >
-                  {{ style[0] || style.name }}
-                </option>
-              </select>
-              <i class="icon-down-open" />
-            </label>
+          <div class="alert warning">
+            {{ themeWarningHelp }}
           </div>
-        </template>
-      </ExportImport>
+          <div class="buttons">
+            <template v-if="themeWarning.type === 'snapshot_source_mismatch'">
+              <button
+                class="btn"
+                @click="forceLoad"
+              >
+                {{ $t('settings.style.switcher.use_source') }}
+              </button>
+              <button
+                class="btn"
+                @click="forceSnapshot"
+              >
+                {{ $t('settings.style.switcher.use_snapshot') }}
+              </button>
+            </template>
+            <template v-else-if="themeWarning.noActionsPossible">
+              <button
+                class="btn"
+                @click="dismissWarning"
+              >
+                {{ $t('general.dismiss') }}
+              </button>
+            </template>
+            <template v-else>
+              <button
+                class="btn"
+                @click="forceLoad"
+              >
+                {{ $t('settings.style.switcher.load_theme') }}
+              </button>
+              <button
+                class="btn"
+                @click="dismissWarning"
+              >
+                {{ $t('settings.style.switcher.keep_as_is') }}
+              </button>
+            </template>
+          </div>
+        </div>
+        <ExportImport
+          :export-object="exportedTheme"
+          :export-label="$t(&quot;settings.export_theme&quot;)"
+          :import-label="$t(&quot;settings.import_theme&quot;)"
+          :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
+          :on-import="onImport"
+          :validator="importValidator"
+        >
+          <template slot="before">
+            <div class="presets">
+              {{ $t('settings.presets') }}
+              <label
+                for="preset-switcher"
+                class="select"
+              >
+                <select
+                  id="preset-switcher"
+                  v-model="selected"
+                  class="preset-switcher"
+                >
+                  <option
+                    v-for="style in availableStyles"
+                    :key="style.name"
+                    :value="style"
+                    :style="{
+                      backgroundColor: style[1] || (style.theme || style.source).colors.bg,
+                      color: style[3] || (style.theme || style.source).colors.text
+                    }"
+                  >
+                    {{ style[0] || style.name }}
+                  </option>
+                </select>
+                <i class="icon-down-open" />
+              </label>
+            </div>
+          </template>
+        </ExportImport>
       </div>
       <div class="save-load-options">
         <span class="keep-option">
diff --git a/src/components/settings_modal/tabs/version_tab.vue b/src/components/settings_modal/tabs/version_tab.vue
index acc43569..d35ff25e 100644
--- a/src/components/settings_modal/tabs/version_tab.vue
+++ b/src/components/settings_modal/tabs/version_tab.vue
@@ -1,31 +1,31 @@
 <template>
-<div :label="$t('settings.version.title')">
-  <div class="setting-item">
-    <ul class="setting-list">
-      <li>
-        <p>{{ $t('settings.version.backend_version') }}</p>
-        <ul class="option-list">
-          <li>
-            <a
-              :href="backendVersionLink"
-              target="_blank"
+  <div :label="$t('settings.version.title')">
+    <div class="setting-item">
+      <ul class="setting-list">
+        <li>
+          <p>{{ $t('settings.version.backend_version') }}</p>
+          <ul class="option-list">
+            <li>
+              <a
+                :href="backendVersionLink"
+                target="_blank"
               >{{ backendVersion }}</a>
-          </li>
-        </ul>
-      </li>
-      <li>
-        <p>{{ $t('settings.version.frontend_version') }}</p>
-        <ul class="option-list">
-          <li>
-            <a
-              :href="frontendVersionLink"
-              target="_blank"
+            </li>
+          </ul>
+        </li>
+        <li>
+          <p>{{ $t('settings.version.frontend_version') }}</p>
+          <ul class="option-list">
+            <li>
+              <a
+                :href="frontendVersionLink"
+                target="_blank"
               >{{ frontendVersion }}</a>
-          </li>
-        </ul>
-      </li>
-    </ul>
+            </li>
+          </ul>
+        </li>
+      </ul>
+    </div>
   </div>
-</div>
 </template>
 <script src="./version_tab.js">

From 5235e7ea1e133d699f7e3dc39473d44e789f15c9 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Mon, 25 May 2020 08:48:44 +0300
Subject: [PATCH 401/483] Removed `with_muted` param usage for user favorites
 timeline endpoint (it only supports pagination params).

---
 src/services/api/api.service.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 7f82d2fa..9c7530a2 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -538,9 +538,11 @@ const fetchTimeline = ({
   if (timeline === 'public' || timeline === 'publicAndExternal') {
     params.push(['only_media', false])
   }
+  if (timeline !== 'favorites') {
+    params.push(['with_muted', withMuted])
+  }
 
   params.push(['limit', 20])
-  params.push(['with_muted', withMuted])
 
   const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
   url += `?${queryString}`

From 6a4ad1fe624b2e2da68707ffffcc6a4cbe7e5e03 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 14:04:36 +0300
Subject: [PATCH 402/483] added "settings saved" notice back

---
 .../settings_modal/settings_modal_content.js  |  3 +++
 .../settings_modal/settings_modal_content.vue | 19 +++++++++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_content.js
index bd8df672..b842ec7d 100644
--- a/src/components/settings_modal/settings_modal_content.js
+++ b/src/components/settings_modal/settings_modal_content.js
@@ -25,6 +25,9 @@ const SettingsModalContent = {
     ThemeTab
   },
   computed: {
+    currentSaveStateNotice () {
+      return this.$store.state.interface.settings.currentSaveStateNotice
+    },
     isLoggedIn () {
       return !!this.$store.state.users.currentUser
     }
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
index 013bf34f..737c3637 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -4,6 +4,25 @@
       <span class="title">
         {{ $t('settings.settings') }}
       </span>
+      <transition name="fade">
+        <template v-if="currentSaveStateNotice">
+          <div
+            v-if="currentSaveStateNotice.error"
+            class="alert error"
+            @click.prevent
+            >
+            {{ $t('settings.saving_err') }}
+          </div>
+
+          <div
+            v-if="!currentSaveStateNotice.error"
+            class="alert transparent"
+            @click.prevent
+            >
+            {{ $t('settings.saving_ok') }}
+          </div>
+        </template>
+      </transition>
       <button
         class="btn"
         @click="peekModal"

From 79c03984bcf63387863f6979849c39ff5f404186 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 14:16:03 +0300
Subject: [PATCH 403/483] scroll to top when switching tabs

---
 src/components/tab_switcher/tab_switcher.js | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index a54b474f..bd1f51cb 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -60,6 +60,9 @@ export default Vue.component('tab-switcher', {
           this.onSwitch.call(null, this.$slots.default[index].key)
         }
         this.active = index
+        if (this.scrollableTabs) {
+          this.$refs.contents.scrollTop = 0
+        }
       }
     }
   },
@@ -118,7 +121,7 @@ export default Vue.component('tab-switcher', {
         <div class="tabs">
           {tabs}
         </div>
-        <div class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}>
+        <div ref="contents" class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}>
           {contents}
         </div>
       </div>

From a6ca923a7695477f57367bf3dd95bdf41a4b64a4 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 16:10:14 +0300
Subject: [PATCH 404/483] icons update

---
 .../settings_modal/settings_modal_content.vue        |  9 +++++++++
 src/components/tab_switcher/tab_switcher.js          |  7 +++++--
 src/components/tab_switcher/tab_switcher.scss        |  5 +++++
 static/fontello.json                                 | 12 ++++++++++++
 4 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
index 737c3637..847de1da 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -45,40 +45,47 @@
       >
         <div
           :label="$t('settings.general')"
+          icon="wrench"
         >
           <GeneralTab />
         </div>
         <div
           v-if="isLoggedIn"
           :label="$t('settings.profile_tab')"
+          icon="user"
         >
           <ProfileTab />
         </div>
         <div
           v-if="isLoggedIn"
           :label="$t('settings.security_tab')"
+          icon="lock"
         >
           <SecurityTab />
         </div>
         <div
           :label="$t('settings.filtering')"
+          icon="filter"
         >
           <FilteringTab />
         </div>
         <div
           :label="$t('settings.theme')"
+          icon="brush"
         >
           <ThemeTab />
         </div>
         <div
           v-if="isLoggedIn"
           :label="$t('settings.notifications')"
+          icon="chat"
         >
           <NotificationsTab />
         </div>
         <div
           v-if="isLoggedIn"
           :label="$t('settings.data_import_export_tab')"
+          icon="download"
         >
           <DataImportExportTab />
         </div>
@@ -87,11 +94,13 @@
           :label="$t('settings.mutes_and_blocks')"
           :fullHeight="true"
           class="full-height"
+          icon="eye-off"
         >
           <MutesAndBlocksTab />
         </div>
         <div
           :label="$t('settings.version.title')"
+          icon="info-circled"
         >
           <VersionTab />
         </div>
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index bd1f51cb..2d04e15d 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -94,8 +94,11 @@ export default Vue.component('tab-switcher', {
             <button
               disabled={slot.data.attrs.disabled}
               onClick={this.activateTab(index)}
-              class={classesTab.join(' ')}>
-              {slot.data.attrs.label}</button>
+              class={classesTab.join(' ')}
+            >
+              {!slot.data.attrs.icon ? '' : (<i class={'tab-icon icon-' + slot.data.attrs.icon}/>)}
+              {slot.data.attrs.label}
+            </button>
           </div>
         )
       })
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index f994380f..129a68b7 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -3,6 +3,11 @@
 .tab-switcher {
   display: flex;
 
+  .tab-icon {
+    font-size: 2em;
+    display: block;
+  }
+
   &.top-tabs {
     flex-direction: column;
     > .tabs {
diff --git a/static/fontello.json b/static/fontello.json
index 7f0e7cdd..ac3f0a18 100755
--- a/static/fontello.json
+++ b/static/fontello.json
@@ -363,6 +363,18 @@
       "css": "ok",
       "code": 59431,
       "src": "fontawesome"
+    },
+    {
+      "uid": "4109c474ff99cad28fd5a2c38af2ec6f",
+      "css": "filter",
+      "code": 61616,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "9a76bc135eac17d2c8b8ad4a5774fc87",
+      "css": "download",
+      "code": 59429,
+      "src": "fontawesome"
     }
   ]
 }
\ No newline at end of file

From 7951192cd98263a2e5c9e1010f1299c651f82cef Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 16:11:05 +0300
Subject: [PATCH 405/483] Improve settings-modal async loading, update vue to
 2.6.11 to be able to use Vue.observable, to implmement resettable async
 component

---
 package.json                                  |  4 +-
 src/components/big_spinner/big_spinner.vue    | 13 ++++++
 src/components/error_window/error_window.vue  | 41 +++++++++++++++++++
 .../settings_modal/settings_modal.js          | 13 +++++-
 src/i18n/en.json                              |  1 +
 src/services/resettable_async_component.js    | 32 +++++++++++++++
 yarn.lock                                     | 15 ++++---
 7 files changed, 110 insertions(+), 9 deletions(-)
 create mode 100644 src/components/big_spinner/big_spinner.vue
 create mode 100644 src/components/error_window/error_window.vue
 create mode 100644 src/services/resettable_async_component.js

diff --git a/package.json b/package.json
index 542086b4..4d68cc6e 100644
--- a/package.json
+++ b/package.json
@@ -29,11 +29,11 @@
     "portal-vue": "^2.1.4",
     "sanitize-html": "^1.13.0",
     "v-click-outside": "^2.1.1",
-    "vue": "^2.5.13",
+    "vue": "^2.6.11",
     "vue-chat-scroll": "^1.2.1",
     "vue-i18n": "^7.3.2",
     "vue-router": "^3.0.1",
-    "vue-template-compiler": "^2.3.4",
+    "vue-template-compiler": "^2.6.11",
     "vuelidate": "^0.7.4",
     "vuex": "^3.0.1",
     "whatwg-fetch": "^2.0.3"
diff --git a/src/components/big_spinner/big_spinner.vue b/src/components/big_spinner/big_spinner.vue
new file mode 100644
index 00000000..cda28de5
--- /dev/null
+++ b/src/components/big_spinner/big_spinner.vue
@@ -0,0 +1,13 @@
+<template>
+  <div class="big-spinner">
+    <i class="icon-spin4 animate-spin" />
+  </div>
+</template>
+
+<style lang="scss">
+.big-spinner {
+  font-size: 15em;
+  line-height: 0;
+  opacity: .6;
+}
+</style>
diff --git a/src/components/error_window/error_window.vue b/src/components/error_window/error_window.vue
new file mode 100644
index 00000000..ddb4ba00
--- /dev/null
+++ b/src/components/error_window/error_window.vue
@@ -0,0 +1,41 @@
+<template>
+  <div class="error-window panel">
+    <div class="panel-heading">
+      <span class="title">
+        {{ $t('general.generic_error') }}
+      </span>
+    </div>
+    <div class="panel-body">
+      <p>
+        {{ $t('general.error_retry') }}
+      </p>
+      <button
+        class="btn"
+        @click="closeAllModals"
+      >
+        {{ $t('general.close') }}
+      </button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  methods: {
+    closeAllModals () {
+      // TODO make a global hook to close all modals?
+      this.$store.dispatch('closeSettingsModal')
+      this.$emit('resetAsyncComponent')
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.error-window {
+  .btn {
+    margin: .5em;
+    padding: .5em 2em;
+  }
+}
+</style>
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index d38c5751..60d14649 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -1,9 +1,20 @@
 import Modal from 'src/components/modal/modal.vue'
+import BigSpinner from 'src/components/big_spinner/big_spinner.vue'
+import ErrorWindow from 'src/components/error_window/error_window.vue'
+import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
 
 const SettingsModal = {
   components: {
     Modal,
-    SettingsModalContent: () => import('./settings_modal_content.vue')
+    SettingsModalContent: getResettableAsyncComponent(
+      () => import('./settings_modal_content.vue'),
+      {
+        loading: BigSpinner,
+        error: ErrorWindow,
+        delay: 0,
+        timeout: 3000
+      }
+    )
   },
   computed: {
     modalActivated () {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ad9c22bd..e3dc75d7 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -60,6 +60,7 @@
     "submit": "Submit",
     "more": "More",
     "generic_error": "An error occured",
+    "error_retry": "Please try again",
     "optional": "optional",
     "show_more": "Show more",
     "show_less": "Show less",
diff --git a/src/services/resettable_async_component.js b/src/services/resettable_async_component.js
new file mode 100644
index 00000000..517bbd88
--- /dev/null
+++ b/src/services/resettable_async_component.js
@@ -0,0 +1,32 @@
+import Vue from 'vue'
+
+/* By default async components don't have any way to recover, if component is
+ * failed, it is failed forever. This helper tries to remedy that by recreating
+ * async component when retry is requested (by user). You need to emit the
+ * `resetAsyncComponent` event from child to reset the component. Generally,
+ * this should be done from error component but could be done from loading or
+ * actual target component itself if needs to be.
+ */
+function getResettableAsyncComponent (asyncComponent, options) {
+  const asyncComponentFactory = () => () => ({
+    component: asyncComponent(),
+    ...options
+  })
+
+  const observe = Vue.observable({ c: asyncComponentFactory() })
+
+  return {
+    functional: true,
+    render (createElement, { data, children }) {
+      //  emit event resetAsyncComponent to reloading
+      data.on = {}
+      data.on.resetAsyncComponent = () => {
+        observe.c = asyncComponentFactory()
+        // parent.$forceUpdate()
+      }
+      return createElement(observe.c, data, children)
+    }
+  }
+}
+
+export default getResettableAsyncComponent
diff --git a/yarn.lock b/yarn.lock
index 0defefcb..61afa7ca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2327,6 +2327,7 @@ dateformat@^1.0.6:
 de-indent@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
+  integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
 
 debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
   version "2.6.9"
@@ -7903,9 +7904,10 @@ vue-style-loader@^4.0.0, vue-style-loader@^4.0.1:
     hash-sum "^1.0.2"
     loader-utils "^1.0.2"
 
-vue-template-compiler@^2.3.4:
-  version "2.5.21"
-  resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a"
+vue-template-compiler@^2.6.11:
+  version "2.6.11"
+  resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080"
+  integrity sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==
   dependencies:
     de-indent "^1.0.2"
     he "^1.1.0"
@@ -7914,9 +7916,10 @@ vue-template-es2015-compiler@^1.6.0:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
 
-vue@^2.5.13:
-  version "2.5.21"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
+vue@^2.6.11:
+  version "2.6.11"
+  resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
+  integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==
 
 vuelidate@^0.7.4:
   version "0.7.4"

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: [PATCH 406/483] 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(-)

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'}">

From bb418bf155dad1c6dc39e88bf4d20f0644d1236d Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 16:23:14 +0300
Subject: [PATCH 407/483] fix tests. user-profile didn't have tab-switcher
 compnent imported!!

---
 src/components/user_profile/user_profile.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 9558a0bd..95760bf8 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -3,6 +3,7 @@ import UserCard from '../user_card/user_card.vue'
 import FollowCard from '../follow_card/follow_card.vue'
 import Timeline from '../timeline/timeline.vue'
 import Conversation from '../conversation/conversation.vue'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
 import List from '../list/list.vue'
 import withLoadMore from '../../hocs/with_load_more/with_load_more'
 
@@ -146,6 +147,7 @@ const UserProfile = {
     FollowerList,
     FriendList,
     FollowCard,
+    TabSwitcher,
     Conversation
   }
 }

From 097216c49eaabf02b7a64308bf2c41bcf58b4c53 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 16:32:32 +0300
Subject: [PATCH 408/483] remove timeout, was meant for testing

---
 src/components/settings_modal/settings_modal.js | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index 60d14649..84d673a8 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -11,8 +11,7 @@ const SettingsModal = {
       {
         loading: BigSpinner,
         error: ErrorWindow,
-        delay: 0,
-        timeout: 3000
+        delay: 0
       }
     )
   },

From 5d1b539da20950f05bf062df1a0904437c204e97 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 16:48:23 +0300
Subject: [PATCH 409/483] lint

---
 src/components/settings_modal/settings_modal_content.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
index 847de1da..865a2adf 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -10,7 +10,7 @@
             v-if="currentSaveStateNotice.error"
             class="alert error"
             @click.prevent
-            >
+          >
             {{ $t('settings.saving_err') }}
           </div>
 
@@ -18,7 +18,7 @@
             v-if="!currentSaveStateNotice.error"
             class="alert transparent"
             @click.prevent
-            >
+          >
             {{ $t('settings.saving_ok') }}
           </div>
         </template>

From 534e1fef88862023cd85e29b265319da300b7995 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 16:56:32 +0300
Subject: [PATCH 410/483] lint

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

diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index e0d118a1..d1f044f6 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -65,7 +65,7 @@ const SideDrawer = {
     },
     openSettingsModal () {
       this.$store.dispatch('openSettingsModal')
-    },
+    }
   }
 }
 

From 500511b41534e051803db8731a6e6315447d5854 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 17:05:30 +0300
Subject: [PATCH 411/483] fix sidebar not closing in mobile

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

diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index aa39bc6f..f253742d 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -124,7 +124,7 @@
         <li @click="toggleDrawer">
           <a
             href="#"
-            @click.stop="openSettingsModal"
+            @click="openSettingsModal"
           >
             <i class="button-icon icon-cog" /> {{ $t("settings.settings") }}
           </a>

From 0eea5c6b80c1fcd9f99636599e484bac8845e075 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 17:11:30 +0300
Subject: [PATCH 412/483] fix icon changing color

---
 src/components/tab_switcher/tab_switcher.scss | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index 129a68b7..479fb686 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -147,8 +147,10 @@
     white-space: nowrap;
 
     padding: 6px 1em;
-    color: $fallback--text;
-    color: var(--tabText, $fallback--text);
+    &, &:active .tab-icon {
+      color: $fallback--text;
+      color: var(--tabText, $fallback--text);
+    }
     background-color: $fallback--fg;
     background-color: var(--tab, $fallback--fg);
 

From c0fe39af7a817be47f3a80019127ddb74876222e Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 17:15:02 +0300
Subject: [PATCH 413/483] increase gap between buttons in titlebars

---
 src/App.scss | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/App.scss b/src/App.scss
index 120eea53..f2972eda 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -566,7 +566,7 @@ main-router {
     min-height: 0;
     box-sizing: border-box;
     margin: 0;
-    margin-left: .25em;
+    margin-left: .5em;
     min-width: 1px;
     align-self: stretch;
   }

From ccdbba23485d90d437b0a3a19594faa25ef8833a Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 17:18:57 +0300
Subject: [PATCH 414/483] Change flex for tab switcher lines to always have
 some small space reserved

---
 src/components/tab_switcher/tab_switcher.scss | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index 479fb686..fff6cc21 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -65,14 +65,8 @@
       overflow-y: auto;
       overflow-x: hidden;
       flex-direction: column;
-      &::after {
-        flex: 1 1 auto;
-      }
-      &::before {
-        flex: 0 0 auto;
-        height: 0.5em;
-      }
       &::after, &::before {
+        flex: 1 0 .5em;
         content: '';
         border-right: 1px solid;
         border-right-color: $fallback--border;

From 101f657d36275cf575ccbc45b556eb628bc8ad43 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 17:21:05 +0300
Subject: [PATCH 415/483] improve big spinner visibility

---
 src/components/big_spinner/big_spinner.vue | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/components/big_spinner/big_spinner.vue b/src/components/big_spinner/big_spinner.vue
index cda28de5..64f16e4d 100644
--- a/src/components/big_spinner/big_spinner.vue
+++ b/src/components/big_spinner/big_spinner.vue
@@ -9,5 +9,8 @@
   font-size: 15em;
   line-height: 0;
   opacity: .6;
+  > i {
+    color: white;
+  }
 }
 </style>

From dc8f78e84dc2f125407cecd66cfaa814e131713a Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 17:22:57 +0300
Subject: [PATCH 416/483] fix for a previous fix

---
 src/components/tab_switcher/tab_switcher.scss | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index fff6cc21..d60e065b 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -72,6 +72,9 @@
         border-right-color: $fallback--border;
         border-right-color: var(--border, $fallback--border);
       }
+      &::before {
+        flex-grow: 0;
+      }
       .tab-wrapper {
         min-width: 10em;
         display: flex;

From a14635f4f2338301d707b6acc0d11a4f9de20068 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 19:57:32 +0300
Subject: [PATCH 417/483] oops

---
 .../mobile_post_status_button/mobile_post_status_button.js      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 ff2d4eaa..0ad12bb1 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('openSettingsModal')
+      this.$store.dispatch('openPostStatusModal')
     },
     handleOSK () {
       // This is a big hack: we're guessing from changed window sizes if the

From b5c1d074f83d08473a19a3885f6ff5eeb95274e5 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 25 May 2020 23:38:31 +0300
Subject: [PATCH 418/483] =?UTF-8?q?fix=20repr=C3=B6=C3=B6ted=20posts=20not?=
 =?UTF-8?q?=20being=20muted=20properly.=20fix=20muted=20posts=20making=20d?=
 =?UTF-8?q?esktop=20notifications?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/status/status.js             | 57 ++++++++++++++++-----
 src/components/status/status.vue            | 18 ++++++-
 src/i18n/en.json                            |  4 +-
 src/modules/statuses.js                     | 10 +++-
 src/services/status_parser/status_parser.js | 11 ++++
 5 files changed, 83 insertions(+), 17 deletions(-)

diff --git a/src/components/status/status.js b/src/components/status/status.js
index 9cd9d61c..95278968 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -12,7 +12,8 @@ import StatusPopover from '../status_popover/status_popover.vue'
 import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
-import { filter, unescape, uniqBy } from 'lodash'
+import { muteWordHits } from '../../services/status_parser/status_parser.js'
+import { unescape, uniqBy } from 'lodash'
 import { mapGetters, mapState } from 'vuex'
 
 const Status = {
@@ -44,6 +45,12 @@ const Status = {
     muteWords () {
       return this.mergedConfig.muteWords
     },
+    showReasonMutedThread () {
+      return (
+        this.status.thread_muted ||
+          (this.status.reblog && this.status.reblog.thread_muted)
+      ) && !this.inConversation
+    },
     repeaterClass () {
       const user = this.statusoid.user
       return highlightClass(user)
@@ -93,20 +100,44 @@ const Status = {
       return !!this.currentUser
     },
     muteWordHits () {
-      const statusText = this.status.text.toLowerCase()
-      const statusSummary = this.status.summary.toLowerCase()
-      const hits = filter(this.muteWords, (muteWord) => {
-        return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase())
-      })
-
-      return hits
+      return muteWordHits(this.status, this.muteWords)
     },
     muted () {
-      const relationship = this.$store.getters.relationship(this.status.user.id)
-      return !this.unmuted && (
-        (!(this.inProfile && this.status.user.id === this.profileUserId) && relationship.muting) ||
-        (!this.inConversation && this.status.thread_muted) ||
-        this.muteWordHits.length > 0)
+      const { status } = this
+      const { reblog } = status
+      const relationship = this.$store.getters.relationship(status.user.id)
+      const relationshipReblog = reblog && this.$store.getters.relationship(reblog.user.id)
+      const reasonsToMute = (
+        // Post is muted according to BE
+        status.muted ||
+        // Reprööt of a muted post according to BE
+        (reblog && reblog.muted) ||
+        // Muted user
+        relationship.muting ||
+        // Muted user of a reprööt
+        (relationshipReblog && relationshipReblog.muting) ||
+        // Thread is muted
+        status.thread_muted ||
+        // Wordfiltered
+        this.muteWordHits.length > 0
+      )
+      const excusesNotToMute = (
+        // Currently showing status
+        this.unmuted ||
+        (
+          this.inProfile && (
+            // Don't mute user's posts on user timeline (except reblogs)
+            (!reblog && status.user.id === this.profileUserId) ||
+            // Same as above but also allow self-reblogs
+            (reblog && reblog.user.id === this.profileUserId)
+          )
+        ) ||
+        // Don't mute statuses in muted conversation when said conversation is opened
+        (this.inConversation && status.thread_muted)
+        // No excuses if post has muted words
+      ) && !this.muteWordHits.length > 0
+
+      return !excusesNotToMute && reasonsToMute
     },
     hideFilteredStatuses () {
       return this.mergedConfig.hideFilteredStatuses
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index e4c7545b..1c287aa7 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -18,10 +18,26 @@
     <template v-if="muted && !isPreview">
       <div class="media status container muted">
         <small>
+          <i
+            v-if="muted && retweet"
+            class="button-icon icon-retweet"
+          />
           <router-link :to="userProfileLink">
             {{ status.user.screen_name }}
           </router-link>
         </small>
+        <small
+          v-if="showReasonMutedThread && muteWordHits.length === 0"
+          class="mutedThread"
+        >
+          {{ $t('status.thread_muted') }}
+        </small>
+        <small
+          v-if="showReasonMutedThread && muteWordHits.length > 0"
+          class="mutedThread"
+        >
+          {{ $t('status.thread_muted_and_words') }}
+        </small>
         <small class="muteWords">{{ muteWordHits.join(', ') }}</small>
         <a
           href="#"
@@ -642,7 +658,7 @@ $status-margin: 0.75em;
     margin-left: auto;
   }
 
-  .muteWords {
+  .mutedThread, .muteWords {
     margin-left: 10px;
   }
 }
diff --git a/src/i18n/en.json b/src/i18n/en.json
index f5d1ce7c..61b818a6 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -621,7 +621,9 @@
     "mute_conversation": "Mute conversation",
     "unmute_conversation": "Unmute conversation",
     "status_unavailable": "Status unavailable",
-    "copy_link": "Copy link to status"
+    "copy_link": "Copy link to status",
+    "thread_muted": "Conversation muted",
+    "thread_muted_and_words": "Conversation muted, contains filtered words:"
   },
   "user_card": {
     "approve": "Approve",
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index cd8c1dba..f73fde25 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -15,7 +15,7 @@ import {
 import { set } from 'vue'
 import { isStatusNotification } from '../services/notification_utils/notification_utils.js'
 import apiService from '../services/api/api.service.js'
-// import parse from '../services/status_parser/status_parser.js'
+import { muteWordHits } from '../services/status_parser/status_parser.js'
 
 const emptyTl = (userId = 0) => ({
   statuses: [],
@@ -381,7 +381,13 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
           notifObj.image = status.attachments[0].url
         }
 
-        if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) {
+        if (
+          !notification.seen &&
+            !state.notifications.desktopNotificationSilence &&
+            visibleNotificationTypes.includes(notification.type) &&
+            !status.muted &&
+            muteWordHits(status, rootGetters.mergedConfig.muteWords).length === 0
+        ) {
           let desktopNotification = new window.Notification(title, notifObj)
           // Chrome is known for not closing notifications automatically
           // according to MDN, anyway.
diff --git a/src/services/status_parser/status_parser.js b/src/services/status_parser/status_parser.js
index 900cd56e..3d517e3c 100644
--- a/src/services/status_parser/status_parser.js
+++ b/src/services/status_parser/status_parser.js
@@ -1,3 +1,4 @@
+import { filter } from 'lodash'
 import sanitize from 'sanitize-html'
 
 export const removeAttachmentLinks = (html) => {
@@ -12,4 +13,14 @@ export const parse = (html) => {
   return removeAttachmentLinks(html)
 }
 
+export const muteWordHits = (status, muteWords) => {
+  const statusText = status.text.toLowerCase()
+  const statusSummary = status.summary.toLowerCase()
+  const hits = filter(muteWords, (muteWord) => {
+    return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase())
+  })
+
+  return hits
+}
+
 export default parse

From 83e5ee549472591b782c36d2321aceec0547cf18 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 26 May 2020 00:22:15 +0300
Subject: [PATCH 419/483] fix non-mention notifs

---
 src/modules/statuses.js | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index f73fde25..c809cf1c 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -381,13 +381,18 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
           notifObj.image = status.attachments[0].url
         }
 
-        if (
-          !notification.seen &&
-            !state.notifications.desktopNotificationSilence &&
-            visibleNotificationTypes.includes(notification.type) &&
-            !status.muted &&
-            muteWordHits(status, rootGetters.mergedConfig.muteWords).length === 0
-        ) {
+        const reasonsToMuteNotif = (
+          notification.seen ||
+            state.notifications.desktopNotificationSilence ||
+            !visibleNotificationTypes.includes(notification.type) ||
+            (
+              status && (
+                status.muted ||
+                  muteWordHits(status, rootGetters.mergedConfig.muteWords).length === 0
+              )
+            )
+        )
+        if (!reasonsToMuteNotif) {
           let desktopNotification = new window.Notification(title, notifObj)
           // Chrome is known for not closing notifications automatically
           // according to MDN, anyway.

From 9d09e4090fe37b5cbc775e4e9ae8097610ffd952 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 26 May 2020 01:01:25 +0300
Subject: [PATCH 420/483] multiple fixes

---
 src/components/notification/notification.js  | 6 ++++--
 src/components/notification/notification.vue | 6 ++----
 src/components/status/status.js              | 4 +---
 src/modules/statuses.js                      | 2 +-
 4 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 1cf4c9bc..cacdce87 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -1,3 +1,4 @@
+import StatusContent from '../status_content/status_content.vue'
 import Status from '../status/status.vue'
 import UserAvatar from '../user_avatar/user_avatar.vue'
 import UserCard from '../user_card/user_card.vue'
@@ -16,10 +17,11 @@ const Notification = {
   },
   props: [ 'notification' ],
   components: {
-    Status,
+    StatusContent,
     UserAvatar,
     UserCard,
-    Timeago
+    Timeago,
+    Status,
   },
   methods: {
     toggleUserExpanded () {
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 0e46a2a7..044ac871 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -157,11 +157,9 @@
           </router-link>
         </div>
         <template v-else>
-          <status
+          <status-content
             class="faint"
-            :compact="true"
-            :statusoid="notification.action"
-            :no-heading="true"
+            :status="notification.action"
           />
         </template>
       </div>
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 95278968..73382521 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -122,8 +122,6 @@ const Status = {
         this.muteWordHits.length > 0
       )
       const excusesNotToMute = (
-        // Currently showing status
-        this.unmuted ||
         (
           this.inProfile && (
             // Don't mute user's posts on user timeline (except reblogs)
@@ -137,7 +135,7 @@ const Status = {
         // No excuses if post has muted words
       ) && !this.muteWordHits.length > 0
 
-      return !excusesNotToMute && reasonsToMute
+      return !this.unmuted && !excusesNotToMute && reasonsToMute
     },
     hideFilteredStatuses () {
       return this.mergedConfig.hideFilteredStatuses
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index c809cf1c..9a2e0df1 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -386,7 +386,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
             state.notifications.desktopNotificationSilence ||
             !visibleNotificationTypes.includes(notification.type) ||
             (
-              status && (
+              notification.type === 'mention' && status && (
                 status.muted ||
                   muteWordHits(status, rootGetters.mergedConfig.muteWords).length === 0
               )

From 1f205b87ac906f67fa8e72530d18977bca085296 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 26 May 2020 01:06:34 +0300
Subject: [PATCH 421/483] lint

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

diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index cacdce87..5aa40e98 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -21,7 +21,7 @@ const Notification = {
     UserAvatar,
     UserCard,
     Timeago,
-    Status,
+    Status
   },
   methods: {
     toggleUserExpanded () {

From 5187b37aca4d6ca177c254f999e6acb637db5532 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 26 May 2020 22:50:37 +0300
Subject: [PATCH 422/483] moved multiChoiceProperties where it fits better

---
 .../tabs/helpers/shared_computed_object.js            | 11 +++++------
 src/modules/config.js                                 | 10 ++++++++++
 2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/src/components/settings_modal/tabs/helpers/shared_computed_object.js b/src/components/settings_modal/tabs/helpers/shared_computed_object.js
index 61643e3b..b6a18e9c 100644
--- a/src/components/settings_modal/tabs/helpers/shared_computed_object.js
+++ b/src/components/settings_modal/tabs/helpers/shared_computed_object.js
@@ -1,10 +1,9 @@
 import { filter, trim } from 'lodash'
-import { instanceDefaultProperties, defaultState as configDefaultState } from 'src/modules/config.js'
-
-const multiChoiceProperties = [
-  'postContentType',
-  'subjectLineBehavior'
-]
+import {
+  instanceDefaultProperties,
+  multiChoiceProperties,
+  defaultState as configDefaultState
+} from 'src/modules/config.js'
 
 const SharedComputedObject = () => ({
   user () {
diff --git a/src/modules/config.js b/src/modules/config.js
index 8f4638f5..b6b1b241 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -3,6 +3,16 @@ import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
 
 const browserLocale = (window.navigator.language || 'en').split('-')[0]
 
+/* TODO this is a bit messy.
+ * We need to declare settings with their types and also deal with
+ * instance-default settings in some way, hopefully try to avoid copy-pasta
+ * in general.
+ */
+export const multiChoiceProperties = [
+  'postContentType',
+  'subjectLineBehavior'
+]
+
 export const defaultState = {
   colors: {},
   theme: undefined,

From a8e013bd6517edb3a81eb5001e6ab948cb87bedb Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 26 May 2020 23:58:55 +0300
Subject: [PATCH 423/483] Move modal frame parts away from modal-content into
 modal, improve error handling

---
 .../async_component_error.vue}                |  26 +--
 src/components/big_spinner/big_spinner.vue    |  16 --
 .../panel_loading/panel_loading.vue           |  29 +++
 .../settings_modal/settings_modal.js          |  16 +-
 .../settings_modal/settings_modal.scss        |  28 ++-
 .../settings_modal/settings_modal.vue         |  45 ++++-
 .../settings_modal/settings_modal_content.js  |   8 -
 .../settings_modal_content.scss               |  30 +---
 .../settings_modal/settings_modal_content.vue | 168 +++++++-----------
 src/i18n/en.json                              |   2 +
 10 files changed, 190 insertions(+), 178 deletions(-)
 rename src/components/{error_window/error_window.vue => async_component_error/async_component_error.vue} (51%)
 delete mode 100644 src/components/big_spinner/big_spinner.vue
 create mode 100644 src/components/panel_loading/panel_loading.vue

diff --git a/src/components/error_window/error_window.vue b/src/components/async_component_error/async_component_error.vue
similarity index 51%
rename from src/components/error_window/error_window.vue
rename to src/components/async_component_error/async_component_error.vue
index ddb4ba00..66b3fb53 100644
--- a/src/components/error_window/error_window.vue
+++ b/src/components/async_component_error/async_component_error.vue
@@ -1,19 +1,17 @@
 <template>
-  <div class="error-window panel">
-    <div class="panel-heading">
-      <span class="title">
+  <div class="async-component-error">
+    <div>
+      <h4>
         {{ $t('general.generic_error') }}
-      </span>
-    </div>
-    <div class="panel-body">
+      </h4>
       <p>
         {{ $t('general.error_retry') }}
       </p>
       <button
         class="btn"
-        @click="closeAllModals"
-      >
-        {{ $t('general.close') }}
+        @click="retry"
+        >
+        {{ $t('general.retry') }}
       </button>
     </div>
   </div>
@@ -22,9 +20,7 @@
 <script>
 export default {
   methods: {
-    closeAllModals () {
-      // TODO make a global hook to close all modals?
-      this.$store.dispatch('closeSettingsModal')
+    retry () {
       this.$emit('resetAsyncComponent')
     }
   }
@@ -32,7 +28,11 @@ export default {
 </script>
 
 <style lang="scss">
-.error-window {
+.async-component-error {
+  display: flex;
+  height: 100%;
+  align-items: center;
+  justify-content: center;
   .btn {
     margin: .5em;
     padding: .5em 2em;
diff --git a/src/components/big_spinner/big_spinner.vue b/src/components/big_spinner/big_spinner.vue
deleted file mode 100644
index 64f16e4d..00000000
--- a/src/components/big_spinner/big_spinner.vue
+++ /dev/null
@@ -1,16 +0,0 @@
-<template>
-  <div class="big-spinner">
-    <i class="icon-spin4 animate-spin" />
-  </div>
-</template>
-
-<style lang="scss">
-.big-spinner {
-  font-size: 15em;
-  line-height: 0;
-  opacity: .6;
-  > i {
-    color: white;
-  }
-}
-</style>
diff --git a/src/components/panel_loading/panel_loading.vue b/src/components/panel_loading/panel_loading.vue
new file mode 100644
index 00000000..4efebb3c
--- /dev/null
+++ b/src/components/panel_loading/panel_loading.vue
@@ -0,0 +1,29 @@
+<template>
+  <div class="panel-loading">
+    <span class="loading-text">
+      <i class="icon-spin4 animate-spin" />
+      {{ $t('general.loading') }}
+    </span>
+  </div>
+</template>
+
+<style lang="scss">
+@import 'src/_variables.scss';
+
+.panel-loading {
+  display: flex;
+  height: 100%;
+  align-items: center;
+  justify-content: center;
+  font-size: 2em;
+  color: $fallback--text;
+  color: var(--text, $fallback--text);
+  .loading-text i {
+    font-size: 3em;
+    line-height: 0;
+    vertical-align: middle;
+    color: $fallback--text;
+    color: var(--text, $fallback--text);
+  }
+}
+</style>
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index 84d673a8..32ef38d6 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -1,6 +1,6 @@
 import Modal from 'src/components/modal/modal.vue'
-import BigSpinner from 'src/components/big_spinner/big_spinner.vue'
-import ErrorWindow from 'src/components/error_window/error_window.vue'
+import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
+import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue'
 import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
 
 const SettingsModal = {
@@ -9,12 +9,20 @@ const SettingsModal = {
     SettingsModalContent: getResettableAsyncComponent(
       () => import('./settings_modal_content.vue'),
       {
-        loading: BigSpinner,
-        error: ErrorWindow,
+        loading: PanelLoading,
+        error: AsyncComponentError,
         delay: 0
       }
     )
   },
+  methods: {
+    closeModal () {
+      this.$store.dispatch('closeSettingsModal')
+    },
+    peekModal () {
+      this.$store.dispatch('togglePeekSettingsModal')
+    }
+  },
   computed: {
     modalActivated () {
       return this.$store.state.interface.settingsModalState !== 'hidden'
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index ece96364..833ff89a 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -3,7 +3,7 @@
   overflow: hidden;
 
   &.peek {
-    .modal-panel {
+    .settings-modal-panel {
       /* Explanation:
        * Modal is positioned vertically centered.
        * 100vh - 100% = Distance between modal's top+bottom boundaries and screen
@@ -15,4 +15,30 @@
       transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));
     }
   }
+
+  .settings-modal-panel {
+    overflow: hidden;
+    transition: transform;
+    transition-timing-function: ease-in-out;
+    transition-duration: 300ms;
+    width: 1000px;
+    max-width: 90vw;
+    height: 90vh;
+
+    @media all and (max-width: 800px) {
+      max-width: 100vw;
+      height: 100vh;
+    }
+
+    .panel-body {
+      height: 100%;
+      overflow-y: hidden;
+
+      .btn {
+        min-height: 28px;
+        min-width: 10em;
+        padding: 0 2em;
+      }
+    }
+  }
 }
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index b6ca5c6b..ded02f4a 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -5,10 +5,47 @@
     :class="{ peek: modalPeeked }"
     :no-background="modalPeeked"
   >
-    <SettingsModalContent
-      v-if="modalActivated"
-      class="modal-panel"
-    />
+    <div class="settings-modal-panel panel">
+      <div class="panel-heading">
+        <span class="title">
+          {{ $t('settings.settings') }}
+        </span>
+        <transition name="fade">
+          <template v-if="currentSaveStateNotice">
+            <div
+              v-if="currentSaveStateNotice.error"
+              class="alert error"
+              @click.prevent
+            >
+              {{ $t('settings.saving_err') }}
+            </div>
+
+            <div
+              v-if="!currentSaveStateNotice.error"
+              class="alert transparent"
+              @click.prevent
+            >
+              {{ $t('settings.saving_ok') }}
+            </div>
+          </template>
+        </transition>
+        <button
+          class="btn"
+          @click="peekModal"
+        >
+          {{ $t('general.peek') }}
+        </button>
+        <button
+          class="btn"
+          @click="closeModal"
+        >
+          {{ $t('general.close') }}
+        </button>
+      </div>
+      <div class="panel-body">
+        <SettingsModalContent v-if="modalActivated" />
+      </div>
+    </div>
   </Modal>
 </template>
 
diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_content.js
index b842ec7d..b27fbd28 100644
--- a/src/components/settings_modal/settings_modal_content.js
+++ b/src/components/settings_modal/settings_modal_content.js
@@ -31,14 +31,6 @@ const SettingsModalContent = {
     isLoggedIn () {
       return !!this.$store.state.users.currentUser
     }
-  },
-  methods: {
-    closeModal () {
-      this.$store.dispatch('closeSettingsModal')
-    },
-    peekModal () {
-      this.$store.dispatch('togglePeekSettingsModal')
-    }
   }
 }
 
diff --git a/src/components/settings_modal/settings_modal_content.scss b/src/components/settings_modal/settings_modal_content.scss
index 92e167a2..f80306c6 100644
--- a/src/components/settings_modal/settings_modal_content.scss
+++ b/src/components/settings_modal/settings_modal_content.scss
@@ -1,32 +1,6 @@
 @import 'src/_variables.scss';
-
-.settings-modal-panel {
-  overflow: hidden;
-  transition: transform;
-  transition-timing-function: ease-in-out;
-  transition-duration: 300ms;
-  width: 1000px;
-  max-width: 90vw;
-  height: 90vh;
-
-  @media all and (max-width: 800px) {
-    max-width: 100vw;
-    height: 100vh;
-  }
-
-  .settings_tab-switcher {
-    height: 100%;
-  }
-  .panel-body {
-    height: 100%;
-    overflow-y: hidden;
-
-    .btn {
-      min-height: 28px;
-      min-width: 10em;
-      padding: 0 2em;
-    }
-  }
+.settings_tab-switcher {
+  height: 100%;
 
   .full-height {
     height: 100%;
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
index 865a2adf..3e06148f 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -1,112 +1,72 @@
 <template>
-  <div class="settings-modal-panel panel">
-    <div class="panel-heading">
-      <span class="title">
-        {{ $t('settings.settings') }}
-      </span>
-      <transition name="fade">
-        <template v-if="currentSaveStateNotice">
-          <div
-            v-if="currentSaveStateNotice.error"
-            class="alert error"
-            @click.prevent
-          >
-            {{ $t('settings.saving_err') }}
-          </div>
-
-          <div
-            v-if="!currentSaveStateNotice.error"
-            class="alert transparent"
-            @click.prevent
-          >
-            {{ $t('settings.saving_ok') }}
-          </div>
-        </template>
-      </transition>
-      <button
-        class="btn"
-        @click="peekModal"
+  <tab-switcher
+    ref="tabSwitcher"
+    class="settings_tab-switcher"
+    :side-tab-bar="true"
+    :scrollable-tabs="true"
+    >
+    <div
+      :label="$t('settings.general')"
+      icon="wrench"
       >
-        {{ $t('general.peek') }}
-      </button>
-      <button
-        class="btn"
-        @click="closeModal"
-      >
-        {{ $t('general.close') }}
-      </button>
+      <GeneralTab />
     </div>
-    <div class="panel-body">
-      <tab-switcher
-        ref="tabSwitcher"
-        class="settings_tab-switcher"
-        :side-tab-bar="true"
-        :scrollable-tabs="true"
+    <div
+      v-if="isLoggedIn"
+      :label="$t('settings.profile_tab')"
+      icon="user"
       >
-        <div
-          :label="$t('settings.general')"
-          icon="wrench"
-        >
-          <GeneralTab />
-        </div>
-        <div
-          v-if="isLoggedIn"
-          :label="$t('settings.profile_tab')"
-          icon="user"
-        >
-          <ProfileTab />
-        </div>
-        <div
-          v-if="isLoggedIn"
-          :label="$t('settings.security_tab')"
-          icon="lock"
-        >
-          <SecurityTab />
-        </div>
-        <div
-          :label="$t('settings.filtering')"
-          icon="filter"
-        >
-          <FilteringTab />
-        </div>
-        <div
-          :label="$t('settings.theme')"
-          icon="brush"
-        >
-          <ThemeTab />
-        </div>
-        <div
-          v-if="isLoggedIn"
-          :label="$t('settings.notifications')"
-          icon="chat"
-        >
-          <NotificationsTab />
-        </div>
-        <div
-          v-if="isLoggedIn"
-          :label="$t('settings.data_import_export_tab')"
-          icon="download"
-        >
-          <DataImportExportTab />
-        </div>
-        <div
-          v-if="isLoggedIn"
-          :label="$t('settings.mutes_and_blocks')"
-          :fullHeight="true"
-          class="full-height"
-          icon="eye-off"
-        >
-          <MutesAndBlocksTab />
-        </div>
-        <div
-          :label="$t('settings.version.title')"
-          icon="info-circled"
-        >
-          <VersionTab />
-        </div>
-      </tab-switcher>
+      <ProfileTab />
     </div>
-  </div>
+    <div
+      v-if="isLoggedIn"
+      :label="$t('settings.security_tab')"
+      icon="lock"
+      >
+      <SecurityTab />
+    </div>
+    <div
+      :label="$t('settings.filtering')"
+      icon="filter"
+      >
+      <FilteringTab />
+    </div>
+    <div
+      :label="$t('settings.theme')"
+      icon="brush"
+      >
+      <ThemeTab />
+    </div>
+    <div
+      v-if="isLoggedIn"
+      :label="$t('settings.notifications')"
+      icon="chat"
+      >
+      <NotificationsTab />
+    </div>
+    <div
+      v-if="isLoggedIn"
+      :label="$t('settings.data_import_export_tab')"
+      icon="download"
+      >
+      <DataImportExportTab />
+    </div>
+    <div
+      v-if="isLoggedIn"
+      :label="$t('settings.mutes_and_blocks')"
+      :fullHeight="true"
+      class="full-height"
+      icon="eye-off"
+      >
+      <MutesAndBlocksTab />
+    </div>
+    <div
+      :label="$t('settings.version.title')"
+      icon="info-circled"
+      >
+      <VersionTab />
+    </div>
+  </tab-switcher>
 </template>
 
 <script src="./settings_modal_content.js"></script>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index e3dc75d7..062af2c7 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -59,8 +59,10 @@
     "apply": "Apply",
     "submit": "Submit",
     "more": "More",
+    "loading": "Loading…",
     "generic_error": "An error occured",
     "error_retry": "Please try again",
+    "retry": "Try again",
     "optional": "optional",
     "show_more": "Show more",
     "show_less": "Show less",

From 3938ccb8e77afa33ebae2a00b74145399322a060 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 27 May 2020 00:02:36 +0300
Subject: [PATCH 424/483] lint

---
 .../async_component_error.vue                 |  2 +-
 .../settings_modal/settings_modal_content.vue | 20 +++++++++----------
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/components/async_component_error/async_component_error.vue b/src/components/async_component_error/async_component_error.vue
index 66b3fb53..b68b98f9 100644
--- a/src/components/async_component_error/async_component_error.vue
+++ b/src/components/async_component_error/async_component_error.vue
@@ -10,7 +10,7 @@
       <button
         class="btn"
         @click="retry"
-        >
+      >
         {{ $t('general.retry') }}
       </button>
     </div>
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
index 3e06148f..8b83e48c 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -4,51 +4,51 @@
     class="settings_tab-switcher"
     :side-tab-bar="true"
     :scrollable-tabs="true"
-    >
+  >
     <div
       :label="$t('settings.general')"
       icon="wrench"
-      >
+    >
       <GeneralTab />
     </div>
     <div
       v-if="isLoggedIn"
       :label="$t('settings.profile_tab')"
       icon="user"
-      >
+    >
       <ProfileTab />
     </div>
     <div
       v-if="isLoggedIn"
       :label="$t('settings.security_tab')"
       icon="lock"
-      >
+    >
       <SecurityTab />
     </div>
     <div
       :label="$t('settings.filtering')"
       icon="filter"
-      >
+    >
       <FilteringTab />
     </div>
     <div
       :label="$t('settings.theme')"
       icon="brush"
-      >
+    >
       <ThemeTab />
     </div>
     <div
       v-if="isLoggedIn"
       :label="$t('settings.notifications')"
       icon="chat"
-      >
+    >
       <NotificationsTab />
     </div>
     <div
       v-if="isLoggedIn"
       :label="$t('settings.data_import_export_tab')"
       icon="download"
-      >
+    >
       <DataImportExportTab />
     </div>
     <div
@@ -57,13 +57,13 @@
       :fullHeight="true"
       class="full-height"
       icon="eye-off"
-      >
+    >
       <MutesAndBlocksTab />
     </div>
     <div
       :label="$t('settings.version.title')"
       icon="info-circled"
-      >
+    >
       <VersionTab />
     </div>
   </tab-switcher>

From 5ffcddd3b9b4b6600e4e51066b9410d7e852df11 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 27 May 2020 03:32:57 +0300
Subject: [PATCH 425/483] fixes. sorry for bad commit message i'm tired

---
 .../settings_modal/settings_modal.js          |  3 +
 .../settings_modal/settings_modal_content.js  |  3 -
 .../settings_modal/settings_modal_content.vue |  2 +-
 .../tabs/theme_tab/theme_tab.scss             | 19 +++--
 .../tabs/theme_tab/theme_tab.vue              | 26 ++++---
 src/components/tab_switcher/tab_switcher.js   | 19 ++++-
 src/components/tab_switcher/tab_switcher.scss | 74 +++++++++++++------
 7 files changed, 96 insertions(+), 50 deletions(-)

diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index 32ef38d6..caa7c48c 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -24,6 +24,9 @@ const SettingsModal = {
     }
   },
   computed: {
+    currentSaveStateNotice () {
+      return this.$store.state.interface.settings.currentSaveStateNotice
+    },
     modalActivated () {
       return this.$store.state.interface.settingsModalState !== 'hidden'
     },
diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_content.js
index b27fbd28..48101a90 100644
--- a/src/components/settings_modal/settings_modal_content.js
+++ b/src/components/settings_modal/settings_modal_content.js
@@ -25,9 +25,6 @@ const SettingsModalContent = {
     ThemeTab
   },
   computed: {
-    currentSaveStateNotice () {
-      return this.$store.state.interface.settings.currentSaveStateNotice
-    },
     isLoggedIn () {
       return !!this.$store.state.users.currentUser
     }
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
index 8b83e48c..283b4ef8 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -40,7 +40,7 @@
     <div
       v-if="isLoggedIn"
       :label="$t('settings.notifications')"
-      icon="chat"
+      icon="bell-ringing-o"
     >
       <NotificationsTab />
     </div>
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
index e0b1a2df..926eceff 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
@@ -95,20 +95,25 @@
     align-items: baseline;
     width: 100%;
     min-height: 30px;
-
-    .btn {
-      min-width: 1px;
-      flex: 0 auto;
-      padding: 0 1em;
-    }
+    margin-bottom: 1em;
 
     p {
       flex: 1;
       margin: 0;
       margin-right: .5em;
     }
+  }
 
-    margin-bottom: 1em;
+  .tab-header-buttons {
+    display: flex;
+    flex-direction: column;
+
+    .btn {
+      min-width: 1px;
+      flex: 0 auto;
+      padding: 0 1em;
+      margin-bottom: .5em;
+    }
   }
 
   .shadow-selector {
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
index 33098498..fcfad23b 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
@@ -126,18 +126,20 @@
         >
           <div class="tab-header">
             <p>{{ $t('settings.theme_help') }}</p>
-            <button
-              class="btn"
-              @click="clearOpacity"
-            >
-              {{ $t('settings.style.switcher.clear_opacity') }}
-            </button>
-            <button
-              class="btn"
-              @click="clearV1"
-            >
-              {{ $t('settings.style.switcher.clear_all') }}
-            </button>
+            <div class="tab-header-buttons">
+              <button
+                class="btn"
+                @click="clearOpacity"
+              >
+                {{ $t('settings.style.switcher.clear_opacity') }}
+              </button>
+              <button
+                class="btn"
+                @click="clearV1"
+              >
+                {{ $t('settings.style.switcher.clear_all') }}
+              </button>
+            </div>
           </div>
           <p>{{ $t('settings.theme_help_v2_1') }}</p>
           <h4>{{ $t('settings.style.common_colors.main') }}</h4>
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index 2d04e15d..616f1a19 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -95,9 +95,12 @@ export default Vue.component('tab-switcher', {
               disabled={slot.data.attrs.disabled}
               onClick={this.activateTab(index)}
               class={classesTab.join(' ')}
+              type="button"
             >
               {!slot.data.attrs.icon ? '' : (<i class={'tab-icon icon-' + slot.data.attrs.icon}/>)}
-              {slot.data.attrs.label}
+              <span class="text">
+                {slot.data.attrs.label}
+              </span>
             </button>
           </div>
         )
@@ -110,13 +113,23 @@ export default Vue.component('tab-switcher', {
       if (slot.data.attrs.fullHeight) {
         classes.push('full-height')
       }
+      const newSlot = (
+        <div class={classes}>
+          {
+            this.sideTabBar
+              ? <h1 class="mobile-label">{slot.data.attrs.label}</h1>
+              : ''
+          }
+          {slot}
+        </div>
+      )
 
       if (this.renderOnlyFocused) {
         return active
-          ? <div class={classes.join(' ')}>{slot}</div>
+          ? <div class={classes.join(' ')}>{newSlot}</div>
           : <div class={classes.join(' ')}></div>
       }
-      return <div class={classes.join(' ')}>{slot}</div>
+      return <div class={classes.join(' ')}>{newSlot}</div>
     })
 
     return (
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index d60e065b..db58f4cd 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -10,12 +10,14 @@
 
   &.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;
@@ -51,34 +53,47 @@
 
   &.side-tabs {
     flex-direction: row;
+
     @media all and (max-width: 800px) {
       overflow-x: auto;
     }
+
     > .contents {
-      flex: 0 1 80%;
-      @media all and (max-width: 800px) {
-        min-width: 96vw;
-      }
+      flex: 1 1 auto;
     }
+
     > .tabs {
-      flex: 1 0 auto;
+      flex: 0 0 auto;
       overflow-y: auto;
       overflow-x: hidden;
       flex-direction: column;
+
       &::after, &::before {
-        flex: 1 0 .5em;
+        flex-shrink: 0;
+        flex-basis: .5em;
         content: '';
         border-right: 1px solid;
         border-right-color: $fallback--border;
         border-right-color: var(--border, $fallback--border);
       }
+
+      &::after {
+        flex-grow: 1;
+      }
+
       &::before {
         flex-grow: 0;
       }
+
       .tab-wrapper {
         min-width: 10em;
         display: flex;
         flex-direction: column;
+
+        @media all and (max-width: 800px) {
+          min-width: 1em;
+        }
+
         &:not(.active)::after {
           top: 0;
           right: 0;
@@ -87,6 +102,7 @@
           border-right-color: $fallback--border;
           border-right-color: var(--border, $fallback--border);
         }
+
         &::before {
           flex: 0 0 6px;
           content: '';
@@ -94,6 +110,7 @@
           border-right-color: $fallback--border;
           border-right-color: var(--border, $fallback--border);
         }
+
         &:last-child .tab {
           margin-bottom: 0;
         }
@@ -106,26 +123,23 @@
         min-width: 1px;
         border-top-right-radius: 0;
         border-bottom-right-radius: 0;
+        padding-left: 1em;
         padding-right: calc(1em + 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);
+        margin-right: calc(1em - 200px);
+        margin-left: 1em;
+        @media all and (max-width: 800px) {
+          padding-left: .25em;
+          padding-right: calc(.25em + 200px);
+          margin-right: calc(.25em - 200px);
+          margin-left: .25em;
+          .text {
+            display: none
+          }
         }
       }
     }
   }
 
-
   .contents {
     flex: 1 0 auto;
     min-height: 0px;
@@ -142,14 +156,14 @@
   .tab {
     position: relative;
     white-space: nowrap;
-
     padding: 6px 1em;
+    background-color: $fallback--fg;
+    background-color: var(--tab, $fallback--fg);
+
     &, &:active .tab-icon {
       color: $fallback--text;
       color: var(--tabText, $fallback--text);
     }
-    background-color: $fallback--fg;
-    background-color: var(--tab, $fallback--fg);
 
     &:not(.active) {
       z-index: 4;
@@ -173,7 +187,6 @@
     }
   }
 
-
   .tabs {
     display: flex;
     position: relative;
@@ -198,4 +211,17 @@
       }
     }
   }
+
+  .mobile-label {
+    padding-left: .3em;
+    padding-bottom: .25em;
+    margin-top: .5em;
+    margin-left: .2em;
+    margin-bottom: .25em;
+    border-bottom: 1px solid var(--border, $fallback--border);
+
+    @media all and (min-width: 800px) {
+      display: none;
+    }
+  }
 }

From 6ecd1dd15bc07b7eff86b47ebecd8012eae00e7a Mon Sep 17 00:00:00 2001
From: Francis Dinh <normandy@firemail.cc>
Date: Thu, 28 May 2020 05:05:27 -0400
Subject: [PATCH 426/483] Remove mention of GNU Social

Pleroma-FE has not supported GNU Social for a while now. Plus the
wiki page for setting it up has been deleted.
---
 README.md | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 889f0837..da54eb8f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # pleroma_fe
 
-> A single column frontend for both Pleroma and GS servers.
+> A single column frontend designed for Pleroma.
 
 ![screenshot](https://i.imgur.com/DJVqSJ0.png)
 
@@ -11,7 +11,6 @@ To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://git
 # FOR ADMINS
 
 You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box.
-For the GNU social backend, check out https://git.pleroma.social/pleroma/pleroma-fe/wikis/dual-boot-with-qvitter to see how to run Pleroma-FE and Qvitter at the same time.
 
 ## Build Setup
 

From 3111fc5dbc566181fe80b8807e809cf5942a09bd Mon Sep 17 00:00:00 2001
From: Francis Dinh <normandy@firemail.cc>
Date: Thu, 28 May 2020 05:08:17 -0400
Subject: [PATCH 427/483] Use consistent naming for Pleroma-FE in README

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index da54eb8f..b66383ad 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# pleroma_fe
+# Pleroma-FE 
 
 > A single column frontend designed for Pleroma.
 

From 5f41ee88649ea8cf452680c8081f7d7417a522d3 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 28 May 2020 12:28:32 -0500
Subject: [PATCH 428/483] The sidebarRight option wasn't being read

---
 src/boot/after_store.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index abdba305..0db03547 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -110,6 +110,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
   copyInstanceOption('alwaysShowSubjectInput')
   copyInstanceOption('showFeaturesPanel')
   copyInstanceOption('hideSitename')
+  copyInstanceOption('sidebarRight')
 
   return store.dispatch('setTheme', config['theme'])
 }

From 9a20a9093287b1835d635398ea900e5dc2ca3484 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 28 May 2020 21:26:33 +0300
Subject: [PATCH 429/483] fixed the remaining issues

---
 .../settings_modal/settings_modal_content.scss    |  4 ----
 .../settings_modal/settings_modal_content.vue     |  1 -
 src/components/tab_switcher/tab_switcher.js       | 15 ++++++---------
 src/components/tab_switcher/tab_switcher.scss     | 11 ++++++++++-
 4 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/src/components/settings_modal/settings_modal_content.scss b/src/components/settings_modal/settings_modal_content.scss
index f80306c6..a3fef1cf 100644
--- a/src/components/settings_modal/settings_modal_content.scss
+++ b/src/components/settings_modal/settings_modal_content.scss
@@ -2,10 +2,6 @@
 .settings_tab-switcher {
   height: 100%;
 
-  .full-height {
-    height: 100%;
-  }
-
   .setting-item {
     border-bottom: 2px solid var(--fg, $fallback--fg);
     margin: 1em 1em 1.4em;
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
index 283b4ef8..2156844f 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -55,7 +55,6 @@
       v-if="isLoggedIn"
       :label="$t('settings.mutes_and_blocks')"
       :fullHeight="true"
-      class="full-height"
       icon="eye-off"
     >
       <MutesAndBlocksTab />
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index 616f1a19..7891cb78 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -113,23 +113,20 @@ export default Vue.component('tab-switcher', {
       if (slot.data.attrs.fullHeight) {
         classes.push('full-height')
       }
-      const newSlot = (
+      const renderSlot = (!this.renderOnlyFocused || active)
+        ? slot
+        : ''
+
+      return (
         <div class={classes}>
           {
             this.sideTabBar
               ? <h1 class="mobile-label">{slot.data.attrs.label}</h1>
               : ''
           }
-          {slot}
+          {renderSlot}
         </div>
       )
-
-      if (this.renderOnlyFocused) {
-        return active
-          ? <div class={classes.join(' ')}>{newSlot}</div>
-          : <div class={classes.join(' ')}></div>
-      }
-      return <div class={classes.join(' ')}>{newSlot}</div>
     })
 
     return (
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index db58f4cd..2a364731 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -125,8 +125,9 @@
         border-bottom-right-radius: 0;
         padding-left: 1em;
         padding-right: calc(1em + 200px);
-        margin-right: calc(1em - 200px);
+        margin-right: -200px;
         margin-left: 1em;
+
         @media all and (max-width: 800px) {
           padding-left: .25em;
           padding-right: calc(.25em + 200px);
@@ -147,6 +148,14 @@
     .hidden {
       display: none;
     }
+    .full-height {
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      > *:not(.mobile-label) {
+        flex: 1;
+      }
+    }
 
     &.scrollable-tabs {
       overflow-y: auto;

From 94436c1f858dc0fcd8a876df202241fb96f029cc Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 29 May 2020 13:39:30 +0300
Subject: [PATCH 430/483] fixed tab not hiding

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

diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index 2a364731..d2ef4857 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -148,7 +148,7 @@
     .hidden {
       display: none;
     }
-    .full-height {
+    .full-height:not(.hidden) {
       height: 100%;
       display: flex;
       flex-direction: column;

From 4fae2f8ea878bedfce89402f19adffaedec65baf Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 2 Jun 2020 01:10:52 +0300
Subject: [PATCH 431/483] track if settings modal has been opened once

---
 src/components/settings_modal/settings_modal.js  | 3 +++
 src/components/settings_modal/settings_modal.vue | 2 +-
 src/modules/interface.js                         | 4 ++++
 3 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index caa7c48c..f0d49c91 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -30,6 +30,9 @@ const SettingsModal = {
     modalActivated () {
       return this.$store.state.interface.settingsModalState !== 'hidden'
     },
+    modalOpenedOnce () {
+      return this.$store.state.interface.settingsModalLoaded
+    },
     modalPeeked () {
       return this.$store.state.interface.settingsModalState === 'minimized'
     }
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index ded02f4a..6bc64ed0 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -43,7 +43,7 @@
         </button>
       </div>
       <div class="panel-body">
-        <SettingsModalContent v-if="modalActivated" />
+        <SettingsModalContent v-if="modalOpenedOnce" />
       </div>
     </div>
   </Modal>
diff --git a/src/modules/interface.js b/src/modules/interface.js
index e55b7290..eeebd65e 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -2,6 +2,7 @@ import { set, delete as del } from 'vue'
 
 const defaultState = {
   settingsModalState: 'hidden',
+  settingsModalLoaded: false,
   settings: {
     currentSaveStateNotice: null,
     noticeClearTimeout: null,
@@ -54,6 +55,9 @@ const interfaceMod = {
     },
     openSettingsModal (state) {
       state.settingsModalState = 'visible'
+      if (!state.settingsModalLoaded) {
+        state.settingsModalLoaded = true
+      }
     }
   },
   actions: {

From de3a376bebb25fefb1fe513c3491ee6c4e48af0f Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 2 Jun 2020 01:16:10 +0300
Subject: [PATCH 432/483] fixed case in class name

---
 src/components/status/status.vue | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 1c287aa7..3137cb9d 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -28,17 +28,17 @@
         </small>
         <small
           v-if="showReasonMutedThread && muteWordHits.length === 0"
-          class="mutedThread"
+          class="muted-thread"
         >
           {{ $t('status.thread_muted') }}
         </small>
         <small
           v-if="showReasonMutedThread && muteWordHits.length > 0"
-          class="mutedThread"
+          class="muted-Thread"
         >
           {{ $t('status.thread_muted_and_words') }}
         </small>
-        <small class="muteWords">{{ muteWordHits.join(', ') }}</small>
+        <small class="mute-words">{{ muteWordHits.join(', ') }}</small>
         <a
           href="#"
           class="unmute"
@@ -658,7 +658,7 @@ $status-margin: 0.75em;
     margin-left: auto;
   }
 
-  .mutedThread, .muteWords {
+  .muted-thread, .mute-words {
     margin-left: 10px;
   }
 }

From f197a2aa39eb5cb77887b64b182be78a3eaefdd1 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 4 Jun 2020 15:12:03 +0200
Subject: [PATCH 433/483] EntityNormalizer: Add colons to emoji alt text.

This makes it possible to copy them and still have them work.
---
 src/services/entity_normalizer/entity_normalizer.service.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 6dac7c15..c7ed65a4 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -210,7 +210,7 @@ export const addEmojis = (string, emojis) => {
     const regexSafeShortCode = emoji.shortcode.replace(matchOperatorsRegex, '\\$&')
     return acc.replace(
       new RegExp(`:${regexSafeShortCode}:`, 'g'),
-      `<img src='${emoji.url}' alt='${emoji.shortcode}' title='${emoji.shortcode}' class='emoji' />`
+      `<img src='${emoji.url}' alt=':${emoji.shortcode}:' title=':${emoji.shortcode}:' class='emoji' />`
     )
   }, string)
 }

From c2dfe1f6cc364175d5aa17d587f82bdb6b8b189c Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 4 Jun 2020 15:25:00 +0200
Subject: [PATCH 434/483] EntityNormalizerSpec: Test new behavior.

---
 .../services/entity_normalizer/entity_normalizer.spec.js      | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
index cfb380ba..178e75c6 100644
--- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
+++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -338,9 +338,9 @@ describe('API Entities normalizer', () => {
 
   describe('MastoAPI emoji adder', () => {
     const emojis = makeMockEmojiMasto()
-    const imageHtml = '<img src="https://example.com/image.png" alt="image" title="image" class="emoji" />'
+    const imageHtml = '<img src="https://example.com/image.png" alt=":image:" title=":image:" class="emoji" />'
       .replace(/"/g, '\'')
-    const thinkHtml = '<img src="https://example.com/think.png" alt="thinking" title="thinking" class="emoji" />'
+    const thinkHtml = '<img src="https://example.com/think.png" alt=":thinking:" title=":thinking:" class="emoji" />'
       .replace(/"/g, '\'')
 
     it('correctly replaces shortcodes in supplied string', () => {

From 05167f202fbb55d1cee1a1c36b630c18ab297419 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 4 Jun 2020 15:31:52 +0200
Subject: [PATCH 435/483] EntityNormalizerSpec: More fixes.

---
 .../services/entity_normalizer/entity_normalizer.spec.js      | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
index 178e75c6..166fce2b 100644
--- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
+++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -366,8 +366,8 @@ describe('API Entities normalizer', () => {
         shortcode: '[a-z] {|}*'
       }])
       const result = addEmojis('This post has :c++: emoji and :[a-z] {|}*: emoji', emojis)
-      expect(result).to.include('title=\'c++\'')
-      expect(result).to.include('title=\'[a-z] {|}*\'')
+      expect(result).to.include('title=\':c++:\'')
+      expect(result).to.include('title=\':[a-z] {|}*:\'')
     })
   })
 })

From c0497b6f1fae9e61156d9bd4fa111f7bd1b56e24 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 4 Jun 2020 15:34:29 +0200
Subject: [PATCH 436/483] Docs: Change wrong documentation.

---
 docs/USER_GUIDE.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md
index 076bfb1c..f417f33d 100644
--- a/docs/USER_GUIDE.md
+++ b/docs/USER_GUIDE.md
@@ -33,7 +33,7 @@ will become
 Note that you can only use emoji defined on your instance, you cannot "copy" someone else's emoji, and will have to ask your administrator to copy emoji from other instance to yours.  
 Lastly, there's two convenience options for emoji: an emoji picker (smiley face to the right of "submit" button) and autocomplete suggestions - when you start typing :shortcode: it will automatically try to suggest you emoj and complete the shortcode for you if you select one. **Note** that if emoji doesn't show up in suggestions nor in emoji picker it means there's no such emoji on your instance, if shortcode doesn't match any defined emoji it will appear as text.
 * **Attachments** are fairly simple - you can attach any file to a post as long as the file is within maximum size limits. If you're uploading explicit material you can mark all of your attachments as sensitive (or add `#nsfw` tag) - it will hide the images and videos behind a warning so that it won't be displayed instantly.
-* **Subject line** also known as **CW** (Content Warning) could be used as a header to the post and/or to warn others about contents of the post having something that might upset somebody or something among those lines. Several applications allow to hide post content leaving only subject line visible. As a side-effect using subject line will also mark your images as sensitive (see above).
+* **Subject line** also known as **CW** (Content Warning) could be used as a header to the post and/or to warn others about contents of the post having something that might upset somebody or something among those lines. Several applications allow to hide post content leaving only subject line visible. Using a subject line will not mark your images as sensitive, you will have to do that explicitly (see above).
 * **Visiblity scope** controls who will be able to see your posts. There are four scopes available:
 
 1. `Public`: This is the default, and some fediverse software like GNU Social only supports this. This means that your post is accessible by anyone and will be shown in the public timelines.

From d872d55832cf3af43f698edd28b99f0c3c515f5c Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 4 Jun 2020 15:50:44 +0200
Subject: [PATCH 437/483] StatusContent: Try to get hashtag from dataset first.

---
 src/components/status_content/status_content.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
index ccc01b6f..c0a71e8f 100644
--- a/src/components/status_content/status_content.js
+++ b/src/components/status_content/status_content.js
@@ -176,8 +176,8 @@ const StatusContent = {
           }
         }
         if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) {
-          // Extract tag name from link url
-          const tag = extractTagFromUrl(target.href)
+          // Extract tag name from dataset or link url
+          const tag = target.dataset.tag || extractTagFromUrl(target.href)
           if (tag) {
             const link = this.generateTagLink(tag)
             this.$router.push(link)

From cb99dc2b270d2554f7e2fc85707786a74563e0ca Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 4 Jun 2020 16:30:28 +0200
Subject: [PATCH 438/483] MediaModal: Close on browser navigation events.

---
 src/components/media_modal/media_modal.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index abb18c7d..24764e80 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -84,10 +84,12 @@ const MediaModal = {
     }
   },
   mounted () {
+    window.addEventListener('popstate', this.hide)
     document.addEventListener('keyup', this.handleKeyupEvent)
     document.addEventListener('keydown', this.handleKeydownEvent)
   },
   destroyed () {
+    window.removeEventListener('popstate', this.hide)
     document.removeEventListener('keyup', this.handleKeyupEvent)
     document.removeEventListener('keydown', this.handleKeydownEvent)
   }

From 282e10e3bec1a76da2d4c2a89152b441a2273fb7 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Fri, 5 Jun 2020 13:42:39 +0200
Subject: [PATCH 439/483] Settings: Keep a local version of the
 mutedWordsString

Without this it was impossible to use newlines
---
 src/components/settings/settings.js | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index 31a9e9be..5d01a05f 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -30,7 +30,8 @@ const settings = {
         Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks'),
 
       backendVersion: instance.backendVersion,
-      frontendVersion: instance.frontendVersion
+      frontendVersion: instance.frontendVersion,
+      muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n') 
     }
   },
   components: {
@@ -86,8 +87,11 @@ const settings = {
       .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
     // Special cases (need to transform values or perform actions first)
     muteWordsString: {
-      get () { return this.$store.getters.mergedConfig.muteWords.join('\n') },
+      get () { 
+        return this.muteWordsStringLocal
+      },
       set (value) {
+        this.muteWordsStringLocal = value
         this.$store.dispatch('setOption', {
           name: 'muteWords',
           value: filter(value.split('\n'), (word) => trim(word).length > 0)

From c4c5568b98802d05b303a38be61e68386e3f7cd0 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Fri, 5 Jun 2020 13:51:47 +0200
Subject: [PATCH 440/483] Linting fixes.

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

diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index 5d01a05f..527b9a8d 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -31,7 +31,7 @@ const settings = {
 
       backendVersion: instance.backendVersion,
       frontendVersion: instance.frontendVersion,
-      muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n') 
+      muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n')
     }
   },
   components: {
@@ -87,7 +87,7 @@ const settings = {
       .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
     // Special cases (need to transform values or perform actions first)
     muteWordsString: {
-      get () { 
+      get () {
         return this.muteWordsStringLocal
       },
       set (value) {

From ba4c189c53fd6450de7247e55203f18ae65c48e8 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Fri, 5 Jun 2020 17:26:13 +0200
Subject: [PATCH 441/483] ReactButton: Change the combined emoji (heart) to a
 simple one.

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

diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index abc3bf07..f0931446 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -24,7 +24,7 @@ const ReactButton = {
   },
   computed: {
     commonEmojis () {
-      return ['❤️', '😠', '👀', '😂', '🔥']
+      return ['👍', '😠', '👀', '😂', '🔥']
     },
     emojis () {
       if (this.filterWord !== '') {

From 2aabd8ed1a75be0d4079d23d0bf6702883ff4959 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Fri, 5 Jun 2020 17:55:19 +0200
Subject: [PATCH 442/483] StillImage: Make it work properly in both firefox and
 chrome.

---
 src/components/still-image/still-image.vue | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
index 4137bd59..08af26f6 100644
--- a/src/components/still-image/still-image.vue
+++ b/src/components/still-image/still-image.vue
@@ -23,12 +23,21 @@
 
 <style lang="scss">
 @import '../../_variables.scss';
+.contain-fit {
+  .still-image {
+    img {
+      height: 100%;
+    }
+  }
+}
+
 .still-image {
   position: relative;
   line-height: 0;
   overflow: hidden;
   width: 100%;
   height: 100%;
+  display: flex;
 
   &:hover canvas {
     display: none;
@@ -36,8 +45,8 @@
 
   img {
     width: 100%;
-    height: 100%;
     object-fit: contain;
+    align-self: center;
   }
 
   &.animated {

From 0a3069aec0c6b664381d202bca5a8460a1d6d9fc Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Fri, 5 Jun 2020 16:13:41 +0000
Subject: [PATCH 443/483] Update CHANGELOG.md

---
 CHANGELOG.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a44fb163..9501f981 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,9 +13,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ### Changed
 - Registration page no longer requires email if the server is configured not to require it
+- Change heart to thumbs up in reaction picker
+- Close the media modal on navigation events
+- Add colons to the emoji alt text, to make them copyable
 
 ### Fixed
 - Status ellipsis menu closes properly when selecting certain options
+- Cropped images look correct in Chrome
+- Newlines in the muted words settings work again
+- Clicking on non-latin hashtags won't open a new window
 
 ## [2.0.3] - 2020-05-02
 ### Fixed

From e0e1fe5e20aaf63937674f51db23ef1e89885770 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Sun, 24 May 2020 19:36:21 +0000
Subject: [PATCH 444/483] Translated using Weblate (Italian)

Currently translated at 51.8% (318 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 83 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 78 insertions(+), 5 deletions(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index e565049d..82c8e7c8 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -142,7 +142,45 @@
         "values": {
             "false": "no",
             "true": "sì"
-        }
+        },
+        "avatar_size_instruction": "La taglia minima per l'icona personale è 150x150 pixel.",
+        "domain_mutes": "Domini",
+        "discoverable": "Permetti la scoperta di questo profilo da servizi di ricerca ed altro",
+        "composing": "Composizione",
+        "changed_email": "Email cambiata con successo!",
+        "change_email_error": "C'è stato un problema nel cambiare la tua email.",
+        "change_email": "Cambia email",
+        "blocks_tab": "Bloccati",
+        "blocks_imported": "Blocchi importati! Saranno elaborati a breve.",
+        "block_import_error": "Errore nell'importazione",
+        "block_import": "Importa blocchi",
+        "block_export_button": "Esporta i tuoi blocchi in un file CSV",
+        "block_export": "Esporta blocchi",
+        "allow_following_move": "Consenti",
+        "mfa": {
+            "verify": {
+                "desc": "Per abilitare l'autenticazione bifattoriale, inserisci il codice fornito dalla tua applicazione:"
+            },
+            "scan": {
+                "secret_code": "Codice",
+                "desc": "Con la tua applicazione bifattoriale, acquisisci questo QR o inserisci il codice manualmente:",
+                "title": "Acquisisci"
+            },
+            "authentication_methods": "Metodi di accesso",
+            "recovery_codes_warning": "Appuntati i codici o salvali in un posto sicuro, altrimenti rischi di non rivederli mai più. Se perderai l'accesso sia alla tua applicazione bifattoriale che ai codici di recupero non potrai più accedere al tuo profilo.",
+            "waiting_a_recovery_codes": "Ricevo codici di recupero...",
+            "recovery_codes": "Codici di recupero.",
+            "warning_of_generate_new_codes": "Alla generazione di nuovi codici di recupero, quelli vecchi saranno disattivati.",
+            "generate_new_recovery_codes": "Genera nuovi codici di recupero",
+            "title": "Accesso bifattoriale",
+            "confirm_and_enable": "Conferma ed abilita OTP",
+            "wait_pre_setup_otp": "preimposto OTP",
+            "setup_otp": "Imposta OTP",
+            "otp": "OTP"
+        },
+        "enter_current_password_to_confirm": "Inserisci la tua password per identificarti",
+        "security": "Sicurezza",
+        "app_name": "Nome applicazione"
     },
     "timeline": {
         "error_fetching": "Errore nell'aggiornamento",
@@ -209,7 +247,10 @@
         "account_not_locked_warning_link": "protetto",
         "attachments_sensitive": "Nascondi gli allegati",
         "content_type": {
-            "text/plain": "Testo normale"
+            "text/plain": "Testo normale",
+            "text/bbcode": "BBCode",
+            "text/markdown": "Markdown",
+            "text/html": "HTML"
         },
         "content_warning": "Oggetto (facoltativo)",
         "default": "Sono appena atterrato a Fiumicino.",
@@ -220,7 +261,15 @@
             "private": "Solo per seguaci - Visibile solo dai tuoi seguaci",
             "public": "Pubblico - Visibile sulla sequenza pubblica",
             "unlisted": "Non elencato - Non visibile sulla sequenza pubblica"
-        }
+        },
+        "scope_notice": {
+            "unlisted": "Questo messaggio non sarà visibile sulla sequenza locale né su quella pubblica",
+            "private": "Questo messaggio sarà visibile solo ai tuoi seguaci",
+            "public": "Questo messaggio sarà visibile a tutti"
+        },
+        "direct_warning_to_first_only": "Questo messaggio sarà visibile solo agli utenti menzionati all'inizio.",
+        "direct_warning_to_all": "Questo messaggio sarà visibile a tutti i menzionati.",
+        "new_status": "Nuovo messaggio"
     },
     "registration": {
         "bio": "Introduzione",
@@ -228,7 +277,20 @@
         "fullname": "Nome visualizzato",
         "password_confirm": "Conferma password",
         "registration": "Registrazione",
-        "token": "Codice d'invito"
+        "token": "Codice d'invito",
+        "validations": {
+            "password_confirmation_match": "dovrebbe essere uguale alla password",
+            "password_confirmation_required": "non può essere vuoto",
+            "password_required": "non può essere vuoto",
+            "email_required": "non può essere vuoto",
+            "fullname_required": "non può essere vuoto",
+            "username_required": "non può essere vuoto"
+        },
+        "bio_placeholder": "es.\nCiao, sono Lupo Lucio.\nSono un lupo fantastico che vive nel Fantabosco. Forse mi hai visto alla Melevisione.",
+        "fullname_placeholder": "es. Lupo Lucio",
+        "username_placeholder": "es. mister_wolf",
+        "new_captcha": "Clicca l'immagine per avere un altro captcha",
+        "captcha": "CAPTCHA"
     },
     "user_profile": {
         "timeline_title": "Sequenza dell'Utente"
@@ -307,7 +369,10 @@
         "not_enough_options": "Aggiungi altre risposte"
     },
     "interactions": {
-        "favs_repeats": "Condivisi e preferiti"
+        "favs_repeats": "Condivisi e preferiti",
+        "load_older": "Carica vecchie interazioni",
+        "moves": "Utenti migrati",
+        "follows": "Nuovi seguìti"
     },
     "emoji": {
         "load_all": "Carico tutti i {emojiAmount} emoji",
@@ -319,5 +384,13 @@
         "keep_open": "Tieni aperto il menù",
         "emoji": "Emoji",
         "stickers": "Adesivi"
+    },
+    "selectable_list": {
+        "select_all": "Seleziona tutto"
+    },
+    "remote_user_resolver": {
+        "error": "Non trovato.",
+        "searching_for": "Cerco",
+        "remote_user_resolver": "Cerca utenti remoti"
     }
 }

From 18997c2d078ebb7991f247bf87208fca80f8d739 Mon Sep 17 00:00:00 2001
From: Chuculate <l6d5up+9u2qx1kec2m64@sharklasers.com>
Date: Tue, 26 May 2020 15:32:22 +0000
Subject: [PATCH 445/483] Translated using Weblate (Spanish)

Currently translated at 84.3% (517 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/es/
---
 src/i18n/es.json | 1252 +++++++++++++++++++++++-----------------------
 1 file changed, 626 insertions(+), 626 deletions(-)

diff --git a/src/i18n/es.json b/src/i18n/es.json
index 163eb707..08de345c 100644
--- a/src/i18n/es.json
+++ b/src/i18n/es.json
@@ -1,638 +1,638 @@
 {
-  "chat": {
-    "title": "Chat"
-  },
-  "exporter": {
-    "export": "Exportar",
-    "processing": "Procesando. Pronto se te pedirá que descargues tu archivo"
-  },
-  "features_panel": {
-    "chat": "Chat",
-    "gopher": "Gopher",
-    "media_proxy": "Proxy de medios",
-    "scope_options": "Opciones del alcance de la visibilidad",
-    "text_limit": "Límite de caracteres",
-    "title": "Características",
-    "who_to_follow": "A quién seguir"
-  },
-  "finder": {
-    "error_fetching_user": "Error al buscar usuario",
-    "find_user": "Encontrar usuario"
-  },
-  "general": {
-    "apply": "Aplicar",
-    "submit": "Enviar",
-    "more": "Más",
-    "generic_error": "Ha ocurrido un error",
-    "optional": "opcional",
-    "show_more": "Mostrar más",
-    "show_less": "Mostrar menos",
-    "cancel": "Cancelar",
-    "disable": "Inhabilitar",
-    "enable": "Habilitar",
-    "confirm": "Confirmar",
-    "verify": "Verificar"
-  },
-  "image_cropper": {
-    "crop_picture": "Recortar la foto",
-    "save": "Guardar",
-    "save_without_cropping": "Guardar sin recortar",
-    "cancel": "Cancelar"
-  },
-  "importer": {
-    "submit": "Enviar",
-    "success": "Importado con éxito",
-    "error": "Se ha producido un error al importar el archivo."
-  },
-  "login": {
-    "login": "Identificarse",
-    "description": "Identificarse con OAuth",
-    "logout": "Cerrar sesión",
-    "password": "Contraseña",
-    "placeholder": "p.ej. lain",
-    "register": "Registrarse",
-    "username": "Usuario",
-    "hint": "Inicia sesión para unirte a la discusión",
-    "authentication_code": "Código de autenticación",
-    "enter_recovery_code": "Inserta el código de recuperación",
-    "enter_two_factor_code": "Inserta el código de dos factores",
-    "recovery_code": "Código de recuperación",
-    "heading" : {
-      "totp" : "Autenticación de dos factores",
-      "recovery" : "Recuperación de dos factores"
-    }
-  },
-   "media_modal": {
-    "previous": "Anterior",
-    "next": "Siguiente"
-  },
-  "nav": {
-    "about": "Acerca de",
-    "administration": "Administración",
-    "back": "Volver",
-    "chat": "Chat Local",
-    "friend_requests": "Solicitudes de seguimiento",
-    "mentions": "Menciones",
-    "interactions": "Interacciones",
-    "dms": "Mensajes Directos",
-    "public_tl": "Línea Temporal Pública",
-    "timeline": "Línea Temporal",
-    "twkn": "Toda La Red Conocida",
-    "user_search": "Búsqueda de Usuarios",
-    "search": "Buscar",
-    "who_to_follow": "A quién seguir",
-    "preferences": "Preferencias"
-  },
-  "notifications": {
-    "broken_favorite": "Estado desconocido, buscándolo...",
-    "favorited_you": "le gusta tu estado",
-    "followed_you": "empezó a seguirte",
-    "load_older": "Cargar notificaciones antiguas",
-    "notifications": "Notificaciones",
-    "read": "¡Leído!",
-    "repeated_you": "repitió tu estado",
-    "no_more_notifications": "No hay más notificaciones"
-  },
-  "polls": {
-    "add_poll": "Añadir encuesta",
-    "add_option": "Añadir opción",
-    "option": "Opción",
-    "votes": "votos",
-    "vote": "Votar",
-    "type": "Tipo de encuesta",
-    "single_choice": "Elección única",
-    "multiple_choices": "Elección múltiple",
-    "expiry": "Tiempo de vida de la encuesta",
-    "expires_in": "La encuensta termina en {0}",
-    "expired": "La encuesta terminó hace {0}",
-    "not_enough_options": "Muy pocas opciones únicas en la encuesta"
-  },
-  "emoji": {
-    "stickers": "Pegatinas",
-    "emoji": "Emoji",
-    "keep_open": "Mantener el selector abierto",
-    "search_emoji": "Buscar un emoji",
-    "add_emoji": "Insertar un emoji",
-    "custom": "Emojis personalizados",
-    "unicode": "Emojis unicode"
-  },
-  "stickers": {
-    "add_sticker": "Añadir Pegatina"
-  },
-  "interactions": {
-    "favs_repeats": "Favoritos y Repetidos",
-    "follows": "Nuevos seguidores",
-    "load_older": "Cargar interacciones más antiguas"
-  },
-  "post_status": {
-    "new_status": "Publicar un nuevo estado",
-    "account_not_locked_warning": "Tu cuenta no está {0}. Cualquiera puede seguirte y leer las entradas para Solo-Seguidores.",
-    "account_not_locked_warning_link": "bloqueada",
-    "attachments_sensitive": "Contenido sensible",
-    "content_type": {
-      "text/plain": "Texto Plano",
-      "text/html": "HTML",
-      "text/markdown": "Markdown",
-      "text/bbcode": "BBCode"
+    "chat": {
+        "title": "Chat"
     },
-    "content_warning": "Tema (opcional)",
-    "default": "Acabo de aterrizar en L.A.",
-    "direct_warning_to_all": "Esta publicación será visible para todos los usarios mencionados.",
-    "direct_warning_to_first_only": "Esta publicación solo será visible para los usuarios mencionados al comienzo del mensaje.",
-    "posting": "Publicando",
-    "scope_notice": {
-      "public": "Esta publicación será visible para todo el mundo",
-      "private": "Esta publicación solo será visible para tus seguidores.",
-      "unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida"
+    "exporter": {
+        "export": "Exportar",
+        "processing": "Procesando. Pronto se te pedirá que descargues tu archivo"
     },
-    "scope": {
-      "direct": "Directo - Solo para los usuarios mencionados.",
-      "private": "Solo-seguidores - Solo tus seguidores leerán la publicación",
-      "public": "Público - Entradas visibles en las Líneas Temporales Públicas",
-      "unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas"
-    }
-  },
-  "registration": {
-    "bio": "Biografía",
-    "email": "Correo electrónico",
-    "fullname": "Nombre a mostrar",
-    "password_confirm": "Confirmar contraseña",
-    "registration": "Registro",
-    "token": "Token de invitación",
-    "captcha": "CAPTCHA",
-    "new_captcha": "Haz click en la imagen para obtener un nuevo captcha",
-    "username_placeholder": "p.ej. lain",
-    "fullname_placeholder": "p.ej. Lain Iwakura",
-    "bio_placeholder": "e.g.\nHola, soy un ejemplo.\nAquí puedes poner algo representativo tuyo... o no.",
-    "validations": {
-      "username_required": "no puede estar vacío",
-      "fullname_required": "no puede estar vacío",
-      "email_required": "no puede estar vacío",
-      "password_required": "no puede estar vacío",
-      "password_confirmation_required": "no puede estar vacío",
-      "password_confirmation_match": "la contraseña no coincide"
-    }
-  },
-   "selectable_list": {
-    "select_all": "Seleccionar todo"
-  },
-  "settings": {
-    "app_name": "Nombre de la aplicación",
-    "security": "Seguridad",
-    "enter_current_password_to_confirm": "Introduce la contraseña actual para confirmar tu identidad",
-    "mfa": {
-      "otp" : "OTP",
-      "setup_otp" : "Configurar OTP",
-      "wait_pre_setup_otp" : "preconfiguración OTP",
-      "confirm_and_enable" : "Confirmar y habilitar OTP",
-      "title": "Autentificación de dos factores",
-      "generate_new_recovery_codes" : "Generar códigos de recuperación nuevos",
-      "warning_of_generate_new_codes" : "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.",
-      "recovery_codes" : "Códigos de recuperación.",
-      "waiting_a_recovery_codes": "Recibiendo códigos de respaldo",
-      "recovery_codes_warning" : "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.",
-      "authentication_methods" : "Métodos de autentificación",
-      "scan": {
-        "title": "Escanear",
-        "desc": "Usando su aplicación de dos factores, escanee este código QR o ingrese la clave de texto:",
-        "secret_code": "Clave"
-      },
-      "verify": {
-        "desc": "Para habilitar la autenticación de dos factores, ingrese el código de su aplicación 2FA:"
-      }
+    "features_panel": {
+        "chat": "Chat",
+        "gopher": "Gopher",
+        "media_proxy": "Proxy de medios",
+        "scope_options": "Opciones del alcance de la visibilidad",
+        "text_limit": "Límite de caracteres",
+        "title": "Características",
+        "who_to_follow": "A quién seguir"
     },
-    "attachmentRadius": "Adjuntos",
-    "attachments": "Adjuntos",
-    "autoload": "Habilitar carga automática al llegar al final de la página",
-    "avatar": "Avatar",
-    "avatarAltRadius": "Avatares (Notificaciones)",
-    "avatarRadius": "Avatares",
-    "background": "Fondo",
-    "bio": "Biografía",
-    "block_export": "Exportar usuarios bloqueados",
-    "block_export_button": "Exporta la lista de tus usarios bloqueados a un archivo csv",
-    "block_import": "Importar usuarios bloqueados",
-    "block_import_error": "Error importando la lista de usuarios bloqueados",
-    "blocks_imported": "¡Lista de usuarios bloqueados importada! El procesado puede tardar un poco.",
-    "blocks_tab": "Bloqueados",
-    "btnRadius": "Botones",
-    "cBlue": "Azul (Responder, seguir)",
-    "cGreen": "Verde (Retweet)",
-    "cOrange": "Naranja (Favorito)",
-    "cRed": "Rojo (Cancelar)",
-    "change_password": "Cambiar contraseña",
-    "change_password_error": "Hubo un problema cambiando la contraseña.",
-    "changed_password": "Contraseña cambiada correctamente!",
-    "collapse_subject": "Colapsar entradas con tema",
-    "composing": "Redactando",
-    "confirm_new_password": "Confirmar la nueva contraseña",
-    "current_avatar": "Tu avatar actual",
-    "current_password": "Contraseña actual",
-    "current_profile_banner": "Tu cabecera actual",
-    "data_import_export_tab": "Importar / Exportar Datos",
-    "default_vis": "Alcance de visibilidad por defecto",
-    "delete_account": "Eliminar la cuenta",
-    "discoverable": "Permitir la aparición de esta cuenta en los resultados de búsqueda y otros servicios",
-    "delete_account_description": "Eliminar para siempre la cuenta y todos los mensajes.",
-    "pad_emoji": "Rellenar con espacios al agregar emojis desde el selector",
-    "delete_account_error": "Hubo un error al eliminar tu cuenta. Si el fallo persiste, ponte en contacto con el administrador de tu instancia.",
-    "delete_account_instructions": "Escribe tu contraseña para confirmar la eliminación de tu cuenta.",
-    "avatar_size_instruction": "El tamaño mínimo recomendado para el avatar es de 150X150 píxeles.",
-    "export_theme": "Exportar tema",
-    "filtering": "Filtrado",
-    "filtering_explanation": "Todos los estados que contengan estas palabras serán silenciados, una por línea",
-    "follow_export": "Exportar personas que tú sigues",
-    "follow_export_button": "Exporta tus seguidores a un fichero csv",
-    "follow_import": "Importar personas que tú sigues",
-    "follow_import_error": "Error al importar el fichero",
-    "follows_imported": "¡Importado! Procesarlos llevará tiempo.",
-    "foreground": "Primer plano",
-    "general": "General",
-    "hide_attachments_in_convo": "Ocultar adjuntos en las conversaciones",
-    "hide_attachments_in_tl": "Ocultar adjuntos en la línea temporal",
-    "hide_muted_posts": "Ocultar las publicaciones de los usuarios silenciados",
-    "max_thumbnails": "Cantidad máxima de miniaturas por publicación",
-    "hide_isp": "Ocultar el panel específico de la instancia",
-    "preload_images": "Precargar las imágenes",
-    "use_one_click_nsfw": "Abrir los adjuntos NSFW con un solo click.",
-    "hide_post_stats": "Ocultar las estadísticas de las entradas (p.ej. el número de favoritos)",
-    "hide_user_stats": "Ocultar las estadísticas del usuario (p.ej. el número de seguidores)",
-    "hide_filtered_statuses": "Ocultar estados filtrados",
-    "import_blocks_from_a_csv_file": "Importar lista de usuarios bloqueados dese un archivo csv",
-    "import_followers_from_a_csv_file": "Importar personas que tú sigues a partir de un archivo csv",
-    "import_theme": "Importar tema",
-    "inputRadius": "Campos de entrada",
-    "checkboxRadius": "Casillas de verificación",
-    "instance_default": "(por defecto: {value})",
-    "instance_default_simple": "(por defecto)",
-    "interface": "Interfaz",
-    "interfaceLanguage": "Idioma",
-    "invalid_theme_imported": "El archivo importado no es un tema válido de Pleroma. No se han realizado cambios.",
-    "limited_availability": "No disponible en tu navegador",
-    "links": "Enlaces",
-    "lock_account_description": "Restringir el acceso a tu cuenta solo a seguidores admitidos",
-    "loop_video": "Vídeos en bucle",
-    "loop_video_silent_only": "Bucle solo en vídeos sin sonido (p.ej. \"gifs\" de Mastodon)",
-    "mutes_tab": "Silenciados",
-    "play_videos_in_modal": "Reproducir los vídeos en un marco emergente",
-    "use_contain_fit": "No recortar los adjuntos en miniaturas",
-    "name": "Nombre",
-    "name_bio": "Nombre y Biografía",
-    "new_password": "Nueva contraseña",
-    "notification_visibility": "Tipos de notificaciones a mostrar",
-    "notification_visibility_follows": "Nuevos seguidores",
-    "notification_visibility_likes": "Me gustan (Likes)",
-    "notification_visibility_mentions": "Menciones",
-    "notification_visibility_repeats": "Repeticiones (Repeats)",
-    "no_rich_text_description": "Eliminar el formato de texto enriquecido de todas las entradas",
-    "no_blocks": "No hay usuarios bloqueados",
-    "no_mutes": "No hay usuarios sinlenciados",
-    "hide_follows_description": "No mostrar a quién sigo",
-    "hide_followers_description": "No mostrar quién me sigue",
-    "hide_follows_count_description": "No mostrar el número de cuentas que sigo",
-    "hide_followers_count_description": "No mostrar el número de cuentas que me siguen",
-    "show_admin_badge": "Mostrar la insignia de Administrador en mi perfil",
-    "show_moderator_badge": "Mostrar la insignia de Moderador en mi perfil",
-    "nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW",
-    "oauth_tokens": "Tokens de OAuth",
-    "token": "Token",
-    "refresh_token": "Actualizar el token",
-    "valid_until": "Válido hasta",
-    "revoke_token": "Revocar",
-    "panelRadius": "Paneles",
-    "pause_on_unfocused": "Parar la transmisión cuando no estés en foco.",
-    "presets": "Por defecto",
-    "profile_background": "Fondo del Perfil",
-    "profile_banner": "Cabecera del Perfil",
-    "profile_tab": "Perfil",
-    "radii_help": "Estable el redondeo de las esquinas de la interfaz (en píxeles)",
-    "replies_in_timeline": "Réplicas en la línea temporal",
-    "reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encima",
-    "reply_visibility_all": "Mostrar todas las réplicas",
-    "reply_visibility_following": "Solo mostrar réplicas para mí o usuarios a los que sigo",
-    "reply_visibility_self": "Solo mostrar réplicas para mí",
-    "autohide_floating_post_button": "Ocultar automáticamente el botón 'Nueva Publicación' (para móviles)",
-    "saving_err": "Error al guardar los ajustes",
-    "saving_ok": "Ajustes guardados",
-    "search_user_to_block": "Buscar usuarios a bloquear",
-    "search_user_to_mute": "Buscar usuarios a silenciar",
-    "security_tab": "Seguridad",
-    "scope_copy": "Copiar la visibilidad de la publicación cuando contestamos (En los mensajes directos (MDs) siempre se copia)",
-    "minimal_scopes_mode": "Minimizar las opciones de publicación",
-    "set_new_avatar": "Cambiar avatar",
-    "set_new_profile_background": "Cambiar el fondo del perfil",
-    "set_new_profile_banner": "Cambiar la cabecera del perfil",
-    "settings": "Ajustes",
-    "subject_input_always_show": "Mostrar siempre el campo del tema",
-    "subject_line_behavior": "Copiar el tema en las respuestas",
-    "subject_line_email": "Como email: \"re: tema\"",
-    "subject_line_mastodon": "Como mastodon: copiar como es",
-    "subject_line_noop": "No copiar",
-    "post_status_content_type": "Formato de publicación",
-    "stop_gifs": "Iniciar GIFs al pasar el ratón",
-    "streaming": "Habilitar la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior",
-    "text": "Texto",
-    "theme": "Tema",
-    "theme_help": "Use códigos de color hexadecimales (#rrggbb) para personalizar su tema de colores.",
-    "theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación. Use el botón \"Borrar todo\" para deshacer los cambios.",
-    "theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón por encima para obtener información más detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
-    "tooltipRadius": "Información/alertas",
-     "upload_a_photo": "Subir una foto",
-    "user_settings": "Ajustes del Usuario",
-    "values": {
-      "false": "no",
-      "true": "sí"
+    "finder": {
+        "error_fetching_user": "Error al buscar usuario",
+        "find_user": "Encontrar usuario"
     },
-    "notifications": "Notificaciones",
-    "notification_setting": "Recibir notificaciones de:",
-    "notification_setting_follows": "Usuarios que sigues",
-    "notification_setting_non_follows": "Usuarios que no sigues",
-    "notification_setting_followers": "Usuarios que te siguen",
-    "notification_setting_non_followers": "Usuarios que no te siguen",
-    "notification_mutes": "Para dejar de recibir notificaciones de un usuario específico, siléncialo.",
-    "notification_blocks": "El bloqueo de un usuario detiene todas las notificaciones y también las cancela.",
-    "enable_web_push_notifications": "Habilitar las notificiaciones en el navegador",
-    "style": {
-      "switcher": {
-        "keep_color": "Mantener colores",
-        "keep_shadows": "Mantener sombras",
-        "keep_opacity": "Mantener opacidad",
-        "keep_roundness": "Mantener redondeces",
-        "keep_fonts": "Mantener fuentes",
-        "save_load_hint": "Las opciones \"Mantener\" conservan las opciones configuradas actualmente al seleccionar o cargar temas, también almacena dichas opciones al exportar un tema. Cuando se desactiven todas las casillas de verificación, el tema de exportación lo guardará todo.",
-        "reset": "Reiniciar",
-        "clear_all": "Limpiar todo",
-        "clear_opacity": "Limpiar opacidad"
-      },
-      "common": {
-        "color": "Color",
-        "opacity": "Opacidad",
-        "contrast": {
-          "hint": "El ratio de contraste es {ratio}. {level} {context}",
-          "level": {
-            "aa": "Cumple con la pauta de nivel AA (mínimo)",
-            "aaa": "Cumple con la pauta de nivel AAA (recomendado)",
-            "bad": "No cumple con las pautas de accesibilidad"
-          },
-          "context": {
-            "18pt": "para textos grandes (+18pt)",
-            "text": "para textos"
-          }
+    "general": {
+        "apply": "Aplicar",
+        "submit": "Enviar",
+        "more": "Más",
+        "generic_error": "Ha ocurrido un error",
+        "optional": "opcional",
+        "show_more": "Mostrar más",
+        "show_less": "Mostrar menos",
+        "cancel": "Cancelar",
+        "disable": "Inhabilitar",
+        "enable": "Habilitar",
+        "confirm": "Confirmar",
+        "verify": "Verificar"
+    },
+    "image_cropper": {
+        "crop_picture": "Recortar la foto",
+        "save": "Guardar",
+        "save_without_cropping": "Guardar sin recortar",
+        "cancel": "Cancelar"
+    },
+    "importer": {
+        "submit": "Enviar",
+        "success": "Importado con éxito",
+        "error": "Se ha producido un error al importar el archivo."
+    },
+    "login": {
+        "login": "Identificarse",
+        "description": "Identificarse con OAuth",
+        "logout": "Cerrar sesión",
+        "password": "Contraseña",
+        "placeholder": "p.ej. lain",
+        "register": "Registrarse",
+        "username": "Usuario",
+        "hint": "Inicia sesión para unirte a la discusión",
+        "authentication_code": "Código de autenticación",
+        "enter_recovery_code": "Inserta el código de recuperación",
+        "enter_two_factor_code": "Inserta el código de dos factores",
+        "recovery_code": "Código de recuperación",
+        "heading": {
+            "totp": "Autenticación de dos factores",
+            "recovery": "Recuperación de dos factores"
         }
-      },
-      "common_colors": {
-        "_tab_label": "Común",
-        "main": "Colores comunes",
-        "foreground_hint": "Vea la pestaña \"Avanzado\" para un control más detallado",
-        "rgbo": "Iconos, acentos, insignias"
-      },
-      "advanced_colors": {
-        "_tab_label": "Avanzado",
-        "alert": "Fondo de Alertas",
-        "alert_error": "Error",
-        "badge": "Fondo de Insignias",
-        "badge_notification": "Notificaciones",
-        "panel_header": "Cabecera del panel",
-        "top_bar": "Barra superior",
-        "borders": "Bordes",
-        "buttons": "Botones",
-        "inputs": "Campos de entrada",
-        "faint_text": "Texto desvanecido"
-      },
-      "radii": {
-        "_tab_label": "Redondez"
-      },
-      "shadows": {
-        "_tab_label": "Sombra e iluminación",
-        "component": "Componente",
-        "override": "Sobreescribir",
-        "shadow_id": "Sombra #{value}",
-        "blur": "Difuminar",
-        "spread": "Cantidad",
-        "inset": "Sombra interior",
-        "hint": "Para las sombras, también puede usar --variable como un valor de color para usar las variables CSS3. Tenga en cuenta que establecer la opacidad no funcionará en este caso.",
-        "filter_hint": {
-          "always_drop_shadow": "Advertencia, esta sombra siempre usa {0} cuando el navegador lo soporta.",
-          "drop_shadow_syntax": "{0} no soporta el parámetro {1} y la palabra clave {2}.",
-          "avatar_inset": "Tenga en cuenta que la combinación de sombras interiores como no-interiores en los avatares, puede dar resultados inesperados con los avatares transparentes.",
-          "spread_zero": "Sombras con una cantidad > 0 aparecerá como si estuviera puesto a cero",
-          "inset_classic": "Las sombras interiores estarán usando {0}"
+    },
+    "media_modal": {
+        "previous": "Anterior",
+        "next": "Siguiente"
+    },
+    "nav": {
+        "about": "Acerca de",
+        "administration": "Administración",
+        "back": "Volver",
+        "chat": "Chat Local",
+        "friend_requests": "Solicitudes de seguimiento",
+        "mentions": "Menciones",
+        "interactions": "Interacciones",
+        "dms": "Mensajes Directos",
+        "public_tl": "Línea Temporal Pública",
+        "timeline": "Línea Temporal",
+        "twkn": "Toda La Red Conocida",
+        "user_search": "Búsqueda de Usuarios",
+        "search": "Buscar",
+        "who_to_follow": "A quién seguir",
+        "preferences": "Preferencias"
+    },
+    "notifications": {
+        "broken_favorite": "Estado desconocido, buscándolo...",
+        "favorited_you": "le gusta tu estado",
+        "followed_you": "empezó a seguirte",
+        "load_older": "Cargar notificaciones antiguas",
+        "notifications": "Notificaciones",
+        "read": "¡Leído!",
+        "repeated_you": "repitió tu estado",
+        "no_more_notifications": "No hay más notificaciones"
+    },
+    "polls": {
+        "add_poll": "Añadir encuesta",
+        "add_option": "Añadir opción",
+        "option": "Opción",
+        "votes": "votos",
+        "vote": "Votar",
+        "type": "Tipo de encuesta",
+        "single_choice": "Elección única",
+        "multiple_choices": "Elección múltiple",
+        "expiry": "Tiempo de vida de la encuesta",
+        "expires_in": "La encuesta termina en {0}",
+        "expired": "La encuesta terminó hace {0}",
+        "not_enough_options": "Muy pocas opciones únicas en la encuesta"
+    },
+    "emoji": {
+        "stickers": "Pegatinas",
+        "emoji": "Emoji",
+        "keep_open": "Mantener el selector abierto",
+        "search_emoji": "Buscar un emoji",
+        "add_emoji": "Insertar un emoji",
+        "custom": "Emojis personalizados",
+        "unicode": "Emojis unicode"
+    },
+    "stickers": {
+        "add_sticker": "Añadir Pegatina"
+    },
+    "interactions": {
+        "favs_repeats": "Favoritos y Repetidos",
+        "follows": "Nuevos seguidores",
+        "load_older": "Cargar interacciones más antiguas"
+    },
+    "post_status": {
+        "new_status": "Publicar un nuevo estado",
+        "account_not_locked_warning": "Tu cuenta no está {0}. Cualquiera puede seguirte y leer las entradas para Solo-Seguidores.",
+        "account_not_locked_warning_link": "bloqueada",
+        "attachments_sensitive": "Contenido sensible",
+        "content_type": {
+            "text/plain": "Texto Plano",
+            "text/html": "HTML",
+            "text/markdown": "Markdown",
+            "text/bbcode": "BBCode"
         },
-        "components": {
-          "panel": "Panel",
-          "panelHeader": "Cabecera del panel",
-          "topBar": "Barra superior",
-          "avatar": "Avatar del usuario (en la vista del perfil)",
-          "avatarStatus": "Avatar del usuario (en la vista de la entrada)",
-          "popup": "Ventanas y textos emergentes (popups & tooltips)",
-          "button": "Botones",
-          "buttonHover": "Botón (encima)",
-          "buttonPressed": "Botón (presionado)",
-          "buttonPressedHover": "Botón (presionado+encima)",
-          "input": "Campo de entrada"
+        "content_warning": "Tema (opcional)",
+        "default": "Acabo de aterrizar en L.A.",
+        "direct_warning_to_all": "Esta publicación será visible para todos los usuarios mencionados.",
+        "direct_warning_to_first_only": "Esta publicación solo será visible para los usuarios mencionados al comienzo del mensaje.",
+        "posting": "Publicando",
+        "scope_notice": {
+            "public": "Esta publicación será visible para todo el mundo",
+            "private": "Esta publicación solo será visible para tus seguidores.",
+            "unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida"
+        },
+        "scope": {
+            "direct": "Directo - Solo para los usuarios mencionados",
+            "private": "Solo-seguidores - Solo tus seguidores leerán la publicación",
+            "public": "Público - Entradas visibles en las Líneas Temporales Públicas",
+            "unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas"
         }
-      },
-      "fonts": {
-        "_tab_label": "Fuentes",
-        "help": "Seleccione la fuente a utilizar para los elementos de la interfaz de usuario. Para \"personalizar\", debe ingresar el nombre exacto de la fuente tal como aparece en el sistema.",
-        "components": {
-          "interface": "Interfaz",
-          "input": "Campos de entrada",
-          "post": "Texto de publicaciones",
-          "postCode": "Texto monoespaciado en publicación (texto enriquecido)"
+    },
+    "registration": {
+        "bio": "Biografía",
+        "email": "Correo electrónico",
+        "fullname": "Nombre a mostrar",
+        "password_confirm": "Confirmar contraseña",
+        "registration": "Registro",
+        "token": "Token de invitación",
+        "captcha": "CAPTCHA",
+        "new_captcha": "Haz click en la imagen para obtener un nuevo captcha",
+        "username_placeholder": "p.ej. lain",
+        "fullname_placeholder": "p.ej. Lain Iwakura",
+        "bio_placeholder": "e.g.\nHola, soy un ejemplo.\nAquí puedes poner algo representativo tuyo... o no.",
+        "validations": {
+            "username_required": "no puede estar vacío",
+            "fullname_required": "no puede estar vacío",
+            "email_required": "no puede estar vacío",
+            "password_required": "no puede estar vacío",
+            "password_confirmation_required": "no puede estar vacío",
+            "password_confirmation_match": "la contraseña no coincide"
+        }
+    },
+    "selectable_list": {
+        "select_all": "Seleccionar todo"
+    },
+    "settings": {
+        "app_name": "Nombre de la aplicación",
+        "security": "Seguridad",
+        "enter_current_password_to_confirm": "Introduce la contraseña actual para confirmar tu identidad",
+        "mfa": {
+            "otp": "OTP",
+            "setup_otp": "Configurar OTP",
+            "wait_pre_setup_otp": "preconfiguración OTP",
+            "confirm_and_enable": "Confirmar y habilitar OTP",
+            "title": "Autentificación de dos factores",
+            "generate_new_recovery_codes": "Generar códigos de recuperación nuevos",
+            "warning_of_generate_new_codes": "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.",
+            "recovery_codes": "Códigos de recuperación.",
+            "waiting_a_recovery_codes": "Recibiendo códigos de respaldo",
+            "recovery_codes_warning": "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.",
+            "authentication_methods": "Métodos de autentificación",
+            "scan": {
+                "title": "Escanear",
+                "desc": "Usando su aplicación de dos factores, escanee este código QR o ingrese la clave de texto:",
+                "secret_code": "Clave"
+            },
+            "verify": {
+                "desc": "Para habilitar la autenticación de dos factores, ingrese el código de su aplicación 2FA:"
+            }
         },
-        "family": "Nombre de la fuente",
-        "size": "Tamaño (en px)",
-        "weight": "Peso (negrita)",
-        "custom": "Personalizado"
-      },
-      "preview": {
-        "header": "Vista previa",
-        "content": "Contenido",
-        "error": "Ejemplo de error",
-        "button": "Botón",
-        "text": "Un montón de {0} y {1}",
-        "mono": "contenido",
-        "input": "Acaba de aterrizar en L.A.",
-        "faint_link": "manual útil",
-        "fine_print": "¡Lea nuestro {0} para aprender nada útil!",
-        "header_faint": "Esto está bien",
-        "checkbox": "He revisado los términos y condiciones",
-        "link": "un bonito enlace"
-      }
+        "attachmentRadius": "Adjuntos",
+        "attachments": "Adjuntos",
+        "autoload": "Habilitar carga automática al llegar al final de la página",
+        "avatar": "Avatar",
+        "avatarAltRadius": "Avatares (Notificaciones)",
+        "avatarRadius": "Avatares",
+        "background": "Fondo",
+        "bio": "Biografía",
+        "block_export": "Exportar usuarios bloqueados",
+        "block_export_button": "Exporta la lista de tus usuarios bloqueados a un archivo csv",
+        "block_import": "Importar usuarios bloqueados",
+        "block_import_error": "Error importando la lista de usuarios bloqueados",
+        "blocks_imported": "¡Lista de usuarios bloqueados importada! El procesado puede tardar un poco.",
+        "blocks_tab": "Bloqueados",
+        "btnRadius": "Botones",
+        "cBlue": "Azul (Responder, seguir)",
+        "cGreen": "Verde (Retweet)",
+        "cOrange": "Naranja (Favorito)",
+        "cRed": "Rojo (Cancelar)",
+        "change_password": "Cambiar contraseña",
+        "change_password_error": "Hubo un problema cambiando la contraseña.",
+        "changed_password": "¡Contraseña cambiada correctamente!",
+        "collapse_subject": "Colapsar entradas con tema",
+        "composing": "Redactando",
+        "confirm_new_password": "Confirmar la nueva contraseña",
+        "current_avatar": "Tu avatar actual",
+        "current_password": "Contraseña actual",
+        "current_profile_banner": "Tu cabecera actual",
+        "data_import_export_tab": "Importar / Exportar Datos",
+        "default_vis": "Alcance de visibilidad por defecto",
+        "delete_account": "Eliminar la cuenta",
+        "discoverable": "Permitir la aparición de esta cuenta en los resultados de búsqueda y otros servicios",
+        "delete_account_description": "Eliminar para siempre la cuenta y todos los mensajes.",
+        "pad_emoji": "Rellenar con espacios al agregar emojis desde el selector",
+        "delete_account_error": "Hubo un error al eliminar tu cuenta. Si el fallo persiste, ponte en contacto con el administrador de tu instancia.",
+        "delete_account_instructions": "Escribe tu contraseña para confirmar la eliminación de tu cuenta.",
+        "avatar_size_instruction": "El tamaño mínimo recomendado para el avatar es de 150X150 píxeles.",
+        "export_theme": "Exportar tema",
+        "filtering": "Filtrado",
+        "filtering_explanation": "Todos los estados que contengan estas palabras serán silenciados, una por línea",
+        "follow_export": "Exportar personas que tú sigues",
+        "follow_export_button": "Exporta tus seguidores a un fichero csv",
+        "follow_import": "Importar personas que tú sigues",
+        "follow_import_error": "Error al importar el fichero",
+        "follows_imported": "¡Importado! Procesarlos llevará tiempo.",
+        "foreground": "Primer plano",
+        "general": "General",
+        "hide_attachments_in_convo": "Ocultar adjuntos en las conversaciones",
+        "hide_attachments_in_tl": "Ocultar adjuntos en la línea temporal",
+        "hide_muted_posts": "Ocultar las publicaciones de los usuarios silenciados",
+        "max_thumbnails": "Cantidad máxima de miniaturas por publicación",
+        "hide_isp": "Ocultar el panel específico de la instancia",
+        "preload_images": "Precargar las imágenes",
+        "use_one_click_nsfw": "Abrir los adjuntos NSFW con un solo click.",
+        "hide_post_stats": "Ocultar las estadísticas de las entradas (p.ej. el número de favoritos)",
+        "hide_user_stats": "Ocultar las estadísticas del usuario (p.ej. el número de seguidores)",
+        "hide_filtered_statuses": "Ocultar estados filtrados",
+        "import_blocks_from_a_csv_file": "Importar lista de usuarios bloqueados dese un archivo csv",
+        "import_followers_from_a_csv_file": "Importar personas que tú sigues a partir de un archivo csv",
+        "import_theme": "Importar tema",
+        "inputRadius": "Campos de entrada",
+        "checkboxRadius": "Casillas de verificación",
+        "instance_default": "(por defecto: {value})",
+        "instance_default_simple": "(por defecto)",
+        "interface": "Interfaz",
+        "interfaceLanguage": "Idioma",
+        "invalid_theme_imported": "El archivo importado no es un tema válido de Pleroma. No se han realizado cambios.",
+        "limited_availability": "No disponible en tu navegador",
+        "links": "Enlaces",
+        "lock_account_description": "Restringir el acceso a tu cuenta solo a seguidores admitidos",
+        "loop_video": "Vídeos en bucle",
+        "loop_video_silent_only": "Bucle solo en vídeos sin sonido (p.ej. \"gifs\" de Mastodon)",
+        "mutes_tab": "Silenciados",
+        "play_videos_in_modal": "Reproducir los vídeos en un marco emergente",
+        "use_contain_fit": "No recortar los adjuntos en miniaturas",
+        "name": "Nombre",
+        "name_bio": "Nombre y Biografía",
+        "new_password": "Nueva contraseña",
+        "notification_visibility": "Tipos de notificaciones a mostrar",
+        "notification_visibility_follows": "Nuevos seguidores",
+        "notification_visibility_likes": "Me gustan (Likes)",
+        "notification_visibility_mentions": "Menciones",
+        "notification_visibility_repeats": "Repeticiones (Repeats)",
+        "no_rich_text_description": "Eliminar el formato de texto enriquecido de todas las entradas",
+        "no_blocks": "No hay usuarios bloqueados",
+        "no_mutes": "No hay usuarios silenciados",
+        "hide_follows_description": "No mostrar a quién sigo",
+        "hide_followers_description": "No mostrar quién me sigue",
+        "hide_follows_count_description": "No mostrar el número de cuentas que sigo",
+        "hide_followers_count_description": "No mostrar el número de cuentas que me siguen",
+        "show_admin_badge": "Mostrar la insignia de Administrador en mi perfil",
+        "show_moderator_badge": "Mostrar la insignia de Moderador en mi perfil",
+        "nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW",
+        "oauth_tokens": "Tokens de OAuth",
+        "token": "Token",
+        "refresh_token": "Actualizar el token",
+        "valid_until": "Válido hasta",
+        "revoke_token": "Revocar",
+        "panelRadius": "Paneles",
+        "pause_on_unfocused": "Parar la transmisión cuando no estés en foco.",
+        "presets": "Por defecto",
+        "profile_background": "Fondo del Perfil",
+        "profile_banner": "Cabecera del Perfil",
+        "profile_tab": "Perfil",
+        "radii_help": "Establezca el redondeo de las esquinas de la interfaz (en píxeles)",
+        "replies_in_timeline": "Réplicas en la línea temporal",
+        "reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encima",
+        "reply_visibility_all": "Mostrar todas las réplicas",
+        "reply_visibility_following": "Solo mostrar réplicas para mí o usuarios a los que sigo",
+        "reply_visibility_self": "Solo mostrar réplicas para mí",
+        "autohide_floating_post_button": "Ocultar automáticamente el botón 'Nueva Publicación' (para móviles)",
+        "saving_err": "Error al guardar los ajustes",
+        "saving_ok": "Ajustes guardados",
+        "search_user_to_block": "Buscar usuarios a bloquear",
+        "search_user_to_mute": "Buscar usuarios a silenciar",
+        "security_tab": "Seguridad",
+        "scope_copy": "Copiar la visibilidad de la publicación cuando contestamos (En los mensajes directos (MDs) siempre se copia)",
+        "minimal_scopes_mode": "Minimizar las opciones de publicación",
+        "set_new_avatar": "Cambiar avatar",
+        "set_new_profile_background": "Cambiar el fondo del perfil",
+        "set_new_profile_banner": "Cambiar la cabecera del perfil",
+        "settings": "Ajustes",
+        "subject_input_always_show": "Mostrar siempre el campo del tema",
+        "subject_line_behavior": "Copiar el tema en las respuestas",
+        "subject_line_email": "Como email: \"re: tema\"",
+        "subject_line_mastodon": "Como mastodon: copiar como es",
+        "subject_line_noop": "No copiar",
+        "post_status_content_type": "Formato de publicación",
+        "stop_gifs": "Iniciar GIFs al pasar el ratón",
+        "streaming": "Habilitar la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior",
+        "text": "Texto",
+        "theme": "Tema",
+        "theme_help": "Use códigos de color hexadecimales (#rrggbb) para personalizar su tema de colores.",
+        "theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación. Use el botón \"Borrar todo\" para deshacer los cambios.",
+        "theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón por encima para obtener información más detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
+        "tooltipRadius": "Información/alertas",
+        "upload_a_photo": "Subir una foto",
+        "user_settings": "Ajustes del Usuario",
+        "values": {
+            "false": "no",
+            "true": "sí"
+        },
+        "notifications": "Notificaciones",
+        "notification_setting": "Recibir notificaciones de:",
+        "notification_setting_follows": "Usuarios que sigues",
+        "notification_setting_non_follows": "Usuarios que no sigues",
+        "notification_setting_followers": "Usuarios que te siguen",
+        "notification_setting_non_followers": "Usuarios que no te siguen",
+        "notification_mutes": "Para dejar de recibir notificaciones de un usuario específico, siléncialo.",
+        "notification_blocks": "El bloqueo de un usuario detiene todas las notificaciones y también las cancela.",
+        "enable_web_push_notifications": "Habilitar las notificiaciones en el navegador",
+        "style": {
+            "switcher": {
+                "keep_color": "Mantener colores",
+                "keep_shadows": "Mantener sombras",
+                "keep_opacity": "Mantener opacidad",
+                "keep_roundness": "Mantener redondeces",
+                "keep_fonts": "Mantener fuentes",
+                "save_load_hint": "Las opciones \"Mantener\" conservan las opciones configuradas actualmente al seleccionar o cargar temas, también almacena dichas opciones al exportar un tema. Cuando se desactiven todas las casillas de verificación, el tema de exportación lo guardará todo.",
+                "reset": "Reiniciar",
+                "clear_all": "Limpiar todo",
+                "clear_opacity": "Limpiar opacidad"
+            },
+            "common": {
+                "color": "Color",
+                "opacity": "Opacidad",
+                "contrast": {
+                    "hint": "El ratio de contraste es {ratio}. {level} {context}",
+                    "level": {
+                        "aa": "Cumple con la pauta de nivel AA (mínimo)",
+                        "aaa": "Cumple con la pauta de nivel AAA (recomendado)",
+                        "bad": "No cumple con las pautas de accesibilidad"
+                    },
+                    "context": {
+                        "18pt": "para textos grandes (+18pt)",
+                        "text": "para textos"
+                    }
+                }
+            },
+            "common_colors": {
+                "_tab_label": "Común",
+                "main": "Colores comunes",
+                "foreground_hint": "Vea la pestaña \"Avanzado\" para un control más detallado",
+                "rgbo": "Iconos, acentos, insignias"
+            },
+            "advanced_colors": {
+                "_tab_label": "Avanzado",
+                "alert": "Fondo de Alertas",
+                "alert_error": "Error",
+                "badge": "Fondo de Insignias",
+                "badge_notification": "Notificaciones",
+                "panel_header": "Cabecera del panel",
+                "top_bar": "Barra superior",
+                "borders": "Bordes",
+                "buttons": "Botones",
+                "inputs": "Campos de entrada",
+                "faint_text": "Texto desvanecido"
+            },
+            "radii": {
+                "_tab_label": "Redondez"
+            },
+            "shadows": {
+                "_tab_label": "Sombra e iluminación",
+                "component": "Componente",
+                "override": "Sobreescribir",
+                "shadow_id": "Sombra #{value}",
+                "blur": "Difuminar",
+                "spread": "Cantidad",
+                "inset": "Sombra interior",
+                "hint": "Para las sombras, también puede usar --variable como un valor de color para usar las variables CSS3. Tenga en cuenta que establecer la opacidad no funcionará en este caso.",
+                "filter_hint": {
+                    "always_drop_shadow": "Advertencia, esta sombra siempre usa {0} cuando el navegador lo soporta.",
+                    "drop_shadow_syntax": "{0} no soporta el parámetro {1} y la palabra clave {2}.",
+                    "avatar_inset": "Tenga en cuenta que la combinación de sombras interiores como no-interiores en los avatares, puede dar resultados inesperados con los avatares transparentes.",
+                    "spread_zero": "Sombras con una cantidad > 0 aparecerá como si estuviera puesto a cero",
+                    "inset_classic": "Las sombras interiores estarán usando {0}"
+                },
+                "components": {
+                    "panel": "Panel",
+                    "panelHeader": "Cabecera del panel",
+                    "topBar": "Barra superior",
+                    "avatar": "Avatar del usuario (en la vista del perfil)",
+                    "avatarStatus": "Avatar del usuario (en la vista de la entrada)",
+                    "popup": "Ventanas y textos emergentes (popups & tooltips)",
+                    "button": "Botones",
+                    "buttonHover": "Botón (encima)",
+                    "buttonPressed": "Botón (presionado)",
+                    "buttonPressedHover": "Botón (presionado+encima)",
+                    "input": "Campo de entrada"
+                }
+            },
+            "fonts": {
+                "_tab_label": "Fuentes",
+                "help": "Seleccione la fuente a utilizar para los elementos de la interfaz de usuario. Para \"personalizar\", debe ingresar el nombre exacto de la fuente tal como aparece en el sistema.",
+                "components": {
+                    "interface": "Interfaz",
+                    "input": "Campos de entrada",
+                    "post": "Texto de publicaciones",
+                    "postCode": "Texto monoespaciado en publicación (texto enriquecido)"
+                },
+                "family": "Nombre de la fuente",
+                "size": "Tamaño (en px)",
+                "weight": "Peso (negrita)",
+                "custom": "Personalizado"
+            },
+            "preview": {
+                "header": "Vista previa",
+                "content": "Contenido",
+                "error": "Ejemplo de error",
+                "button": "Botón",
+                "text": "Un montón de {0} y {1}",
+                "mono": "contenido",
+                "input": "Acaba de aterrizar en L.A.",
+                "faint_link": "manual útil",
+                "fine_print": "¡Lea nuestro {0} para aprender nada útil!",
+                "header_faint": "Esto está bien",
+                "checkbox": "He revisado los términos y condiciones",
+                "link": "un bonito enlace"
+            }
+        },
+        "version": {
+            "title": "Versión",
+            "backend_version": "Versión del Backend",
+            "frontend_version": "Versión del Frontend"
+        }
     },
-    "version": {
-      "title": "Versión",
-      "backend_version": "Versión del Backend",
-      "frontend_version": "Versión del Frontend"
-    }
-  },
-  "time": {
-    "day": "{0} día",
-    "days": "{0} días",
-    "day_short": "{0}d",
-    "days_short": "{0}d",
-    "hour": "{0} hora",
-    "hours": "{0} horas",
-    "hour_short": "{0}h",
-    "hours_short": "{0}h",
-    "in_future": "en {0}",
-    "in_past": "hace {0}",
-    "minute": "{0} minuto",
-    "minutes": "{0} minutos",
-    "minute_short": "{0}min",
-    "minutes_short": "{0}min",
-    "month": "{0} mes",
-    "months": "{0} meses",
-    "month_short": "{0}m",
-    "months_short": "{0}m",
-    "now": "justo ahora",
-    "now_short": "ahora",
-    "second": "{0} segundo",
-    "seconds": "{0} segundos",
-    "second_short": "{0}s",
-    "seconds_short": "{0}s",
-    "week": "{0} semana",
-    "weeks": "{0} semanas",
-    "week_short": "{0}sem",
-    "weeks_short": "{0}sem",
-    "year": "{0} año",
-    "years": "{0} años",
-    "year_short": "{0}a",
-    "years_short": "{0}a"
-  },
-  "timeline": {
-    "collapse": "Colapsar",
-    "conversation": "Conversación",
-    "error_fetching": "Error al cargar las actualizaciones",
-    "load_older": "Cargar actualizaciones anteriores",
-    "no_retweet_hint": "La publicación está marcada como solo para seguidores o directa y no se puede repetir",
-    "repeated": "repetida",
-    "show_new": "Mostrar lo nuevo",
-    "up_to_date": "Actualizado",
-    "no_more_statuses": "No hay más estados",
-    "no_statuses": "Sin estados"
-  },
-  "status": {
-    "favorites": "Favoritos",
-    "repeats": "Repetidos",
-    "delete": "Eliminar publicación",
-    "pin": "Fijar en tu perfil",
-    "unpin": "Desclavar de tu perfil",
-    "pinned": "Fijado",
-    "delete_confirm": "¿Realmente quieres borrar la publicación?",
-    "reply_to": "Respondiendo a",
-    "replies_list": "Respuestas:",
-    "mute_conversation": "Silenciar la conversación",
-    "unmute_conversation": "Mostrar la conversación"
-  },
-  "user_card": {
-    "approve": "Aprobar",
-    "block": "Bloquear",
-    "blocked": "¡Bloqueado!",
-    "deny": "Denegar",
-    "favorites": "Favoritos",
-    "follow": "Seguir",
-    "follow_sent": "¡Solicitud enviada!",
-    "follow_progress": "Solicitando…",
-    "follow_again": "¿Enviar solicitud de nuevo?",
-    "follow_unfollow": "Dejar de seguir",
-    "followees": "Siguiendo",
-    "followers": "Seguidores",
-    "following": "¡Siguiendo!",
-    "follows_you": "¡Te sigue!",
-    "its_you": "¡Eres tú!",
-    "media": "Media",
-    "mention": "Mencionar",
-    "mute": "Silenciar",
-    "muted": "Silenciado",
-    "per_day": "por día",
-    "remote_follow": "Seguir",
-    "report": "Reportar",
-    "statuses": "Estados",
-    "subscribe": "Suscribirse",
-    "unsubscribe": "Desuscribirse",
-    "unblock": "Desbloquear",
-    "unblock_progress": "Desbloqueando...",
-    "block_progress": "Bloqueando...",
-    "unmute": "Quitar silencio",
-    "unmute_progress": "Quitando silencio...",
-    "mute_progress": "Silenciando...",
-    "admin_menu": {
-      "moderation": "Moderación",
-      "grant_admin": "Conceder permisos de Administrador",
-      "revoke_admin": "Revocar permisos de Administrador",
-      "grant_moderator": "Conceder permisos de Moderador",
-      "revoke_moderator": "Revocar permisos de Moderador",
-      "activate_account": "Activar cuenta",
-      "deactivate_account": "Desactivar cuenta",
-      "delete_account": "Eliminar cuenta",
-      "force_nsfw": "Marcar todas las publicaciones como NSFW (no es seguro/apropiado para el trabajo)",
-      "strip_media": "Eliminar archivos multimedia de las publicaciones",
-      "force_unlisted": "Forzar que se publique en el modo -Sin Listar-",
-      "sandbox": "Forzar que se publique solo para tus seguidores",
-      "disable_remote_subscription": "No permitir que usuarios de instancias remotas te siga.",
-      "disable_any_subscription": "No permitir que ningún usuario te siga",
-      "quarantine": "No permitir publicaciones de usuarios de instancias remotas",
-      "delete_user": "Eliminar usuario",
-      "delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer."
-    }
-  },
-  "user_profile": {
-    "timeline_title": "Linea Temporal del Usuario",
-    "profile_does_not_exist": "Lo sentimos, este perfil no existe.",
-    "profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil."
-  },
-   "user_reporting": {
-    "title": "Reportando a {0}",
-    "add_comment_description": "El informe será enviado a los moderadores de su instancia. Puedes proporcionar una explicación de por qué estás reportando esta cuenta a continuación:",
-    "additional_comments": "Comentarios adicionales",
-    "forward_description": "La cuenta es de otro servidor. ¿Enviar una copia del informe allí también?",
-    "forward_to": "Reenviar a {0}",
-    "submit": "Enviar",
-    "generic_error": "Se produjo un error al procesar la solicitud."
-  },
-  "who_to_follow": {
-    "more": "Más",
-    "who_to_follow": "A quién seguir"
-  },
-  "tool_tip": {
-    "media_upload": "Subir Medios",
-    "repeat": "Repetir",
-    "reply": "Contestar",
-    "favorite": "Favorito",
-    "user_settings": "Ajustes de usuario"
-  },
-  "upload":{
-    "error": {
-      "base": "Subida fallida.",
-      "file_too_big": "Archivo demasiado grande [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-      "default": "Inténtalo más tarde"
+    "time": {
+        "day": "{0} día",
+        "days": "{0} días",
+        "day_short": "{0}d",
+        "days_short": "{0}d",
+        "hour": "{0} hora",
+        "hours": "{0} horas",
+        "hour_short": "{0}h",
+        "hours_short": "{0}h",
+        "in_future": "en {0}",
+        "in_past": "hace {0}",
+        "minute": "{0} minuto",
+        "minutes": "{0} minutos",
+        "minute_short": "{0}min",
+        "minutes_short": "{0}min",
+        "month": "{0} mes",
+        "months": "{0} meses",
+        "month_short": "{0}m",
+        "months_short": "{0}m",
+        "now": "justo ahora",
+        "now_short": "ahora",
+        "second": "{0} segundo",
+        "seconds": "{0} segundos",
+        "second_short": "{0}s",
+        "seconds_short": "{0}s",
+        "week": "{0} semana",
+        "weeks": "{0} semanas",
+        "week_short": "{0}sem",
+        "weeks_short": "{0}sem",
+        "year": "{0} año",
+        "years": "{0} años",
+        "year_short": "{0}a",
+        "years_short": "{0}a"
     },
-    "file_size_units": {
-      "B": "B",
-      "KiB": "KiB",
-      "MiB": "MiB",
-      "GiB": "GiB",
-      "TiB": "TiB"
+    "timeline": {
+        "collapse": "Colapsar",
+        "conversation": "Conversación",
+        "error_fetching": "Error al cargar las actualizaciones",
+        "load_older": "Cargar actualizaciones anteriores",
+        "no_retweet_hint": "La publicación está marcada como solo para seguidores o directa y no se puede repetir",
+        "repeated": "repetida",
+        "show_new": "Mostrar lo nuevo",
+        "up_to_date": "Actualizado",
+        "no_more_statuses": "No hay más estados",
+        "no_statuses": "Sin estados"
+    },
+    "status": {
+        "favorites": "Favoritos",
+        "repeats": "Repetidos",
+        "delete": "Eliminar publicación",
+        "pin": "Fijar en tu perfil",
+        "unpin": "Desclavar de tu perfil",
+        "pinned": "Fijado",
+        "delete_confirm": "¿Realmente quieres borrar la publicación?",
+        "reply_to": "Respondiendo a",
+        "replies_list": "Respuestas:",
+        "mute_conversation": "Silenciar la conversación",
+        "unmute_conversation": "Mostrar la conversación"
+    },
+    "user_card": {
+        "approve": "Aprobar",
+        "block": "Bloquear",
+        "blocked": "¡Bloqueado!",
+        "deny": "Denegar",
+        "favorites": "Favoritos",
+        "follow": "Seguir",
+        "follow_sent": "¡Solicitud enviada!",
+        "follow_progress": "Solicitando…",
+        "follow_again": "¿Enviar solicitud de nuevo?",
+        "follow_unfollow": "Dejar de seguir",
+        "followees": "Siguiendo",
+        "followers": "Seguidores",
+        "following": "¡Siguiendo!",
+        "follows_you": "¡Te sigue!",
+        "its_you": "¡Eres tú!",
+        "media": "Media",
+        "mention": "Mencionar",
+        "mute": "Silenciar",
+        "muted": "Silenciado",
+        "per_day": "por día",
+        "remote_follow": "Seguir",
+        "report": "Reportar",
+        "statuses": "Estados",
+        "subscribe": "Suscribirse",
+        "unsubscribe": "Desuscribirse",
+        "unblock": "Desbloquear",
+        "unblock_progress": "Desbloqueando...",
+        "block_progress": "Bloqueando...",
+        "unmute": "Quitar silencio",
+        "unmute_progress": "Quitando silencio...",
+        "mute_progress": "Silenciando...",
+        "admin_menu": {
+            "moderation": "Moderación",
+            "grant_admin": "Conceder permisos de Administrador",
+            "revoke_admin": "Revocar permisos de Administrador",
+            "grant_moderator": "Conceder permisos de Moderador",
+            "revoke_moderator": "Revocar permisos de Moderador",
+            "activate_account": "Activar cuenta",
+            "deactivate_account": "Desactivar cuenta",
+            "delete_account": "Eliminar cuenta",
+            "force_nsfw": "Marcar todas las publicaciones como NSFW (no es seguro/apropiado para el trabajo)",
+            "strip_media": "Eliminar archivos multimedia de las publicaciones",
+            "force_unlisted": "Forzar que se publique en el modo -Sin Listar-",
+            "sandbox": "Forzar que se publique solo para tus seguidores",
+            "disable_remote_subscription": "No permitir que usuarios de instancias remotas te siga.",
+            "disable_any_subscription": "No permitir que ningún usuario te siga",
+            "quarantine": "No permitir publicaciones de usuarios de instancias remotas",
+            "delete_user": "Eliminar usuario",
+            "delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer."
+        }
+    },
+    "user_profile": {
+        "timeline_title": "Linea Temporal del Usuario",
+        "profile_does_not_exist": "Lo sentimos, este perfil no existe.",
+        "profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil."
+    },
+    "user_reporting": {
+        "title": "Reportando a {0}",
+        "add_comment_description": "El informe será enviado a los moderadores de su instancia. Puedes proporcionar una explicación de por qué estás reportando esta cuenta a continuación:",
+        "additional_comments": "Comentarios adicionales",
+        "forward_description": "La cuenta es de otro servidor. ¿Enviar una copia del informe allí también?",
+        "forward_to": "Reenviar a {0}",
+        "submit": "Enviar",
+        "generic_error": "Se produjo un error al procesar la solicitud."
+    },
+    "who_to_follow": {
+        "more": "Más",
+        "who_to_follow": "A quién seguir"
+    },
+    "tool_tip": {
+        "media_upload": "Subir Medios",
+        "repeat": "Repetir",
+        "reply": "Contestar",
+        "favorite": "Favorito",
+        "user_settings": "Ajustes de usuario"
+    },
+    "upload": {
+        "error": {
+            "base": "Subida fallida.",
+            "file_too_big": "Archivo demasiado grande [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+            "default": "Inténtalo más tarde"
+        },
+        "file_size_units": {
+            "B": "B",
+            "KiB": "KiB",
+            "MiB": "MiB",
+            "GiB": "GiB",
+            "TiB": "TiB"
+        }
+    },
+    "search": {
+        "people": "Personas",
+        "hashtags": "Etiquetas",
+        "person_talking": "{count} personas hablando",
+        "people_talking": "{count} gente hablando",
+        "no_results": "Sin resultados"
+    },
+    "password_reset": {
+        "forgot_password": "¿Contraseña olvidada?",
+        "password_reset": "Restablecer la contraseña",
+        "instruction": "Ingrese su dirección de correo electrónico o nombre de usuario. Le enviaremos un enlace para restablecer su contraseña.",
+        "placeholder": "Su correo electrónico o nombre de usuario",
+        "check_email": "Revise su correo electrónico para obtener un enlace para restablecer su contraseña.",
+        "return_home": "Volver a la página de inicio",
+        "not_found": "No pudimos encontrar ese correo electrónico o nombre de usuario.",
+        "too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.",
+        "password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia."
     }
-  },
-  "search": {
-    "people": "Personas",
-    "hashtags": "Etiquetas",
-    "person_talking": "{count} personas hablando",
-    "people_talking": "{count} gente hablando",
-    "no_results": "Sin resultados"
-  },
-  "password_reset": {
-    "forgot_password": "¿Contraseña olvidada?",
-    "password_reset": "Restablecer la contraseña",
-    "instruction": "Ingrese su dirección de correo electrónico o nombre de usuario. Le enviaremos un enlace para restablecer su contraseña.",
-    "placeholder": "Su correo electrónico o nombre de usuario",
-    "check_email": "Revise su correo electrónico para obtener un enlace para restablecer su contraseña.",
-    "return_home": "Volver a la página de inicio",
-    "not_found": "No pudimos encontrar ese correo electrónico o nombre de usuario.",
-    "too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.",
-    "password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia."
-  }
-}
\ No newline at end of file
+}

From 5048f203a38984e7ff358bdf649302b954a1c842 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Tue, 26 May 2020 15:53:28 +0000
Subject: [PATCH 446/483] Translated using Weblate (Italian)

Currently translated at 64.1% (393 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 89 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 85 insertions(+), 4 deletions(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index 82c8e7c8..e1b8022c 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -42,7 +42,7 @@
         "follow_request": "vuole seguirti",
         "no_more_notifications": "Fine delle notifiche",
         "migrated_to": "è migrato verso",
-        "reacted_with": "ha reagito con"
+        "reacted_with": "ha reagito con {0}"
     },
     "settings": {
         "attachments": "Allegati",
@@ -57,7 +57,7 @@
         "hide_attachments_in_tl": "Nascondi gli allegati presenti nelle sequenze",
         "name": "Nome",
         "name_bio": "Nome ed introduzione",
-        "nsfw_clickthrough": "Fai click per visualizzare gli allegati nascosti",
+        "nsfw_clickthrough": "Fai click per visualizzare gli allegati offuscati",
         "profile_background": "Sfondo della tua pagina",
         "profile_banner": "Stendardo del tuo profilo",
         "reply_link_preview": "Visualizza le risposte al passaggio del cursore",
@@ -138,7 +138,7 @@
         "streaming": "Mostra automaticamente i nuovi messaggi quando sei in cima alla pagina",
         "text": "Testo",
         "theme_help": "Usa codici colore esadecimali (#rrggbb) per personalizzare il tuo schema di colori.",
-        "tooltipRadius": "Descrizioni/avvisi",
+        "tooltipRadius": "Suggerimenti/avvisi",
         "values": {
             "false": "no",
             "true": "sì"
@@ -180,7 +180,88 @@
         },
         "enter_current_password_to_confirm": "Inserisci la tua password per identificarti",
         "security": "Sicurezza",
-        "app_name": "Nome applicazione"
+        "app_name": "Nome applicazione",
+        "style": {
+            "switcher": {
+                "help": {
+                    "older_version_imported": "Il tema importato è stato creato per una versione precedente dell'interfaccia.",
+                    "future_version_imported": "Il tema importato è stato creato per una versione più recente dell'interfaccia.",
+                    "v2_imported": "Il tema importato è stato creato per una vecchia interfaccia. Non tutto potrebbe essere come prima.",
+                    "upgraded_from_v2": "L'interfaccia è stata aggiornata, il tema potrebbe essere diverso da come lo intendevi."
+                },
+                "use_source": "Nuova versione",
+                "use_snapshot": "Versione precedente",
+                "keep_as_is": "Mantieni tal quale",
+                "load_theme": "Carica tema",
+                "clear_opacity": "Rimuovi opacità",
+                "clear_all": "Azzera tutto",
+                "reset": "Reimposta",
+                "save_load_hint": "Le opzioni \"mantieni\" conservano le impostazioni correnti quando selezioni o carichi un tema, e le salvano quando ne esporti uno. Quando nessuna casella è selezionata, tutte le impostazioni correnti saranno salvate nel tema.",
+                "keep_fonts": "Mantieni font",
+                "keep_roundness": "Mantieni vertici",
+                "keep_opacity": "Mantieni opacità",
+                "keep_shadows": "Mantieni ombre",
+                "keep_color": "Mantieni colori"
+            }
+        },
+        "enable_web_push_notifications": "Abilita notifiche web push",
+        "fun": "Divertimento",
+        "notification_mutes": "Per non ricevere notifiche da uno specifico utente, zittiscilo.",
+        "notification_setting_privacy_option": "Nascondi mittente e contenuti delle notifiche push",
+        "notification_setting_privacy": "Privacy",
+        "notification_setting_followers": "Utenti che ti seguono",
+        "notification_setting_non_followers": "Utenti che non ti seguono",
+        "notification_setting_non_follows": "Utenti che non segui",
+        "notification_setting_follows": "Utenti che segui",
+        "notification_setting": "Ricevi notifiche da:",
+        "notification_setting_filters": "Filtri",
+        "notifications": "Notifiche",
+        "greentext": "Frecce da meme",
+        "upload_a_photo": "Carica un'immagine",
+        "type_domains_to_mute": "Inserisci domini da zittire",
+        "theme_help_v2_2": "Le icone dietro alcuni elementi sono indicatori del contrasto fra testo e sfondo, passaci sopra col puntatore per ulteriori informazioni. Se si usano delle trasparenze, questi indicatori mostrano il peggior caso possibile.",
+        "theme_help_v2_1": "Puoi anche forzare colore ed opacità di alcuni elementi selezionando la casella. Usa il pulsante \"Azzera\" per azzerare tutte le forzature.",
+        "useStreamingApiWarning": "(Sconsigliato, sperimentale, può saltare messaggi)",
+        "useStreamingApi": "Ricevi messaggi e notifiche in tempo reale",
+        "user_mutes": "Utenti",
+        "post_status_content_type": "Tipo di contenuto dei messaggi",
+        "subject_line_noop": "Non copiare",
+        "subject_line_mastodon": "Come in Mastodon: copia tal quale",
+        "subject_line_email": "Come nelle email: \"re: oggetto\"",
+        "subject_line_behavior": "Copia oggetto quando rispondi",
+        "subject_input_always_show": "Mostra sempre il campo Oggetto",
+        "minimal_scopes_mode": "Riduci opzioni di visibilità",
+        "scope_copy": "Risposte ereditano la visibilità (messaggi privati lo fanno sempre)",
+        "search_user_to_mute": "Cerca utente da zittire",
+        "search_user_to_block": "Cerca utente da bloccare",
+        "autohide_floating_post_button": "Nascondi automaticamente il pulsante di composizione (mobile)",
+        "show_moderator_badge": "Mostra l'insegna di moderatore sulla mia pagina",
+        "show_admin_badge": "Mostra l'insegna di amministratore sulla mia pagina",
+        "hide_followers_count_description": "Non mostrare quanti seguaci ho",
+        "hide_follows_count_description": "Non mostrare quanti utenti seguo",
+        "hide_followers_description": "Non mostrare i miei seguaci",
+        "hide_follows_description": "Non mostrare chi seguo",
+        "no_mutes": "Nessun utente zittito",
+        "no_blocks": "Nessun utente bloccato",
+        "notification_visibility_emoji_reactions": "Reazioni",
+        "notification_visibility_moves": "Migrazioni utenti",
+        "new_email": "Nuova email",
+        "use_contain_fit": "Non ritagliare le anteprime degli allegati",
+        "play_videos_in_modal": "Riproduci video in un riquadro a sbalzo",
+        "mutes_tab": "Zittiti",
+        "interface": "Interfaccia",
+        "instance_default_simple": "(predefinito)",
+        "checkboxRadius": "Caselle di selezione",
+        "import_blocks_from_a_csv_file": "Importa blocchi da un file CSV",
+        "hide_filtered_statuses": "Nascondi messaggi filtrati",
+        "use_one_click_nsfw": "Apri media offuscati con un solo click",
+        "preload_images": "Precarica immagini",
+        "hide_isp": "Nascondi pannello della stanza",
+        "max_thumbnails": "Numero massimo di anteprime per messaggio",
+        "hide_muted_posts": "Nascondi messaggi degli utenti zittiti",
+        "accent": "Accento",
+        "emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze",
+        "pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore"
     },
     "timeline": {
         "error_fetching": "Errore nell'aggiornamento",

From e1475d7f5729d906444ff0432a1f177f5a4cc5f4 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Thu, 28 May 2020 21:21:34 +0000
Subject: [PATCH 447/483] Translated using Weblate (English)

Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/en/
---
 src/i18n/en.json | 1470 +++++++++++++++++++++++-----------------------
 1 file changed, 735 insertions(+), 735 deletions(-)

diff --git a/src/i18n/en.json b/src/i18n/en.json
index f5d1ce7c..9a4bd154 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1,743 +1,743 @@
 {
-  "about": {
-    "mrf": {
-      "federation": "Federation",
-      "keyword": {
-        "keyword_policies": "Keyword Policies",
-        "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
-        "reject": "Reject",
-        "replace": "Replace",
-        "is_replaced_by": "→"
-      },
-      "mrf_policies": "Enabled MRF Policies",
-      "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance.  The following policies are enabled:",
-      "simple": {
-        "simple_policies": "Instance-specific Policies",
-        "accept": "Accept",
-        "accept_desc": "This instance only accepts messages from the following instances:",
-        "reject": "Reject",
-        "reject_desc": "This instance will not accept messages from the following instances:",
-        "quarantine": "Quarantine",
-        "quarantine_desc": "This instance will send only public posts to the following instances:",
-        "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
-        "ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
-        "media_removal": "Media Removal",
-        "media_removal_desc": "This instance removes media from posts on the following instances:",
-        "media_nsfw": "Media Force-set As Sensitive",
-        "media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
-      }
-    },
-    "staff": "Staff"
-  },
-  "chat": {
-    "title": "Chat"
-  },
-  "domain_mute_card": {
-    "mute": "Mute",
-    "mute_progress": "Muting...",
-    "unmute": "Unmute",
-    "unmute_progress": "Unmuting..."
-  },
-  "exporter": {
-    "export": "Export",
-    "processing": "Processing, you'll soon be asked to download your file"
-  },
-  "features_panel": {
-    "chat": "Chat",
-    "gopher": "Gopher",
-    "media_proxy": "Media proxy",
-    "scope_options": "Scope options",
-    "text_limit": "Text limit",
-    "title": "Features",
-    "who_to_follow": "Who to follow"
-  },
-  "finder": {
-    "error_fetching_user": "Error fetching user",
-    "find_user": "Find user"
-  },
-  "general": {
-    "apply": "Apply",
-    "submit": "Submit",
-    "more": "More",
-    "generic_error": "An error occured",
-    "optional": "optional",
-    "show_more": "Show more",
-    "show_less": "Show less",
-    "dismiss": "Dismiss",
-    "cancel": "Cancel",
-    "disable": "Disable",
-    "enable": "Enable",
-    "confirm": "Confirm",
-    "verify": "Verify"
-  },
-  "image_cropper": {
-    "crop_picture": "Crop picture",
-    "save": "Save",
-    "save_without_cropping": "Save without cropping",
-    "cancel": "Cancel"
-  },
-  "importer": {
-    "submit": "Submit",
-    "success": "Imported successfully.",
-    "error": "An error occured while importing this file."
-  },
-  "login": {
-    "login": "Log in",
-    "description": "Log in with OAuth",
-    "logout": "Log out",
-    "password": "Password",
-    "placeholder": "e.g. lain",
-    "register": "Register",
-    "username": "Username",
-    "hint": "Log in to join the discussion",
-    "authentication_code": "Authentication code",
-    "enter_recovery_code": "Enter a recovery code",
-    "enter_two_factor_code": "Enter a two-factor code",
-    "recovery_code": "Recovery code",
-    "heading" : {
-      "totp" : "Two-factor authentication",
-      "recovery" : "Two-factor recovery"
-    }
-  },
-  "media_modal": {
-    "previous": "Previous",
-    "next": "Next"
-  },
-  "nav": {
-    "about": "About",
-    "administration": "Administration",
-    "back": "Back",
-    "chat": "Local Chat",
-    "friend_requests": "Follow Requests",
-    "mentions": "Mentions",
-    "interactions": "Interactions",
-    "dms": "Direct Messages",
-    "public_tl": "Public Timeline",
-    "timeline": "Timeline",
-    "twkn": "The Whole Known Network",
-    "user_search": "User Search",
-    "search": "Search",
-    "who_to_follow": "Who to follow",
-    "preferences": "Preferences"
-  },
-  "notifications": {
-    "broken_favorite": "Unknown status, searching for it...",
-    "favorited_you": "favorited your status",
-    "followed_you": "followed you",
-    "follow_request": "wants to follow you",
-    "load_older": "Load older notifications",
-    "notifications": "Notifications",
-    "read": "Read!",
-    "repeated_you": "repeated your status",
-    "no_more_notifications": "No more notifications",
-    "migrated_to": "migrated to",
-    "reacted_with": "reacted with {0}"
-  },
-  "polls": {
-    "add_poll": "Add Poll",
-    "add_option": "Add Option",
-    "option": "Option",
-    "votes": "votes",
-    "vote": "Vote",
-    "type": "Poll type",
-    "single_choice": "Single choice",
-    "multiple_choices": "Multiple choices",
-    "expiry": "Poll age",
-    "expires_in": "Poll ends in {0}",
-    "expired": "Poll ended {0} ago",
-    "not_enough_options": "Too few unique options in poll"
-  },
-  "emoji": {
-    "stickers": "Stickers",
-    "emoji": "Emoji",
-    "keep_open": "Keep picker open",
-    "search_emoji": "Search for an emoji",
-    "add_emoji": "Insert emoji",
-    "custom": "Custom emoji",
-    "unicode": "Unicode emoji",
-    "load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
-    "load_all": "Loading all {emojiAmount} emoji"
-  },
-  "interactions": {
-    "favs_repeats": "Repeats and Favorites",
-    "follows": "New follows",
-    "moves": "User migrates",
-    "load_older": "Load older interactions"
-  },
-  "post_status": {
-    "new_status": "Post new status",
-    "account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
-    "account_not_locked_warning_link": "locked",
-    "attachments_sensitive": "Mark attachments as sensitive",
-    "content_type": {
-      "text/plain": "Plain text",
-      "text/html": "HTML",
-      "text/markdown": "Markdown",
-      "text/bbcode": "BBCode"
-    },
-    "content_warning": "Subject (optional)",
-    "default": "Just landed in L.A.",
-    "direct_warning_to_all": "This post will be visible to all the mentioned users.",
-    "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
-    "posting": "Posting",
-    "scope_notice": {
-      "public": "This post will be visible to everyone",
-      "private": "This post will be visible to your followers only",
-      "unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
-    },
-    "scope": {
-      "direct": "Direct - Post to mentioned users only",
-      "private": "Followers-only - Post to followers only",
-      "public": "Public - Post to public timelines",
-      "unlisted": "Unlisted - Do not post to public timelines"
-    }
-  },
-  "registration": {
-    "bio": "Bio",
-    "email": "Email",
-    "fullname": "Display name",
-    "password_confirm": "Password confirmation",
-    "registration": "Registration",
-    "token": "Invite token",
-    "captcha": "CAPTCHA",
-    "new_captcha": "Click the image to get a new captcha",
-    "username_placeholder": "e.g. lain",
-    "fullname_placeholder": "e.g. Lain Iwakura",
-    "bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
-    "validations": {
-      "username_required": "cannot be left blank",
-      "fullname_required": "cannot be left blank",
-      "email_required": "cannot be left blank",
-      "password_required": "cannot be left blank",
-      "password_confirmation_required": "cannot be left blank",
-      "password_confirmation_match": "should be the same as password"
-    }
-  },
-  "remote_user_resolver": {
-    "remote_user_resolver": "Remote user resolver",
-    "searching_for": "Searching for",
-    "error": "Not found."
-  },
-  "selectable_list": {
-    "select_all": "Select all"
-  },
-  "settings": {
-    "app_name": "App name",
-    "security": "Security",
-    "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
-    "mfa": {
-      "otp" : "OTP",
-      "setup_otp" : "Setup OTP",
-      "wait_pre_setup_otp" : "presetting OTP",
-      "confirm_and_enable" : "Confirm & enable OTP",
-      "title": "Two-factor Authentication",
-      "generate_new_recovery_codes" : "Generate new recovery codes",
-      "warning_of_generate_new_codes" : "When you generate new recovery codes, your old codes won’t work anymore.",
-      "recovery_codes" : "Recovery codes.",
-      "waiting_a_recovery_codes": "Receiving backup codes...",
-      "recovery_codes_warning" : "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.",
-      "authentication_methods" : "Authentication methods",
-      "scan": {
-        "title": "Scan",
-        "desc": "Using your two-factor app, scan this QR code or enter text key:",
-        "secret_code": "Key"
-      },
-      "verify": {
-        "desc": "To enable two-factor authentication, enter the code from your two-factor app:"
-      }
-    },
-    "allow_following_move": "Allow auto-follow when following account moves",
-    "attachmentRadius": "Attachments",
-    "attachments": "Attachments",
-    "autoload": "Enable automatic loading when scrolled to the bottom",
-    "avatar": "Avatar",
-    "avatarAltRadius": "Avatars (Notifications)",
-    "avatarRadius": "Avatars",
-    "background": "Background",
-    "bio": "Bio",
-    "block_export": "Block export",
-    "block_export_button": "Export your blocks to a csv file",
-    "block_import": "Block import",
-    "block_import_error": "Error importing blocks",
-    "blocks_imported": "Blocks imported! Processing them will take a while.",
-    "blocks_tab": "Blocks",
-    "btnRadius": "Buttons",
-    "cBlue": "Blue (Reply, follow)",
-    "cGreen": "Green (Retweet)",
-    "cOrange": "Orange (Favorite)",
-    "cRed": "Red (Cancel)",
-    "change_email": "Change Email",
-    "change_email_error": "There was an issue changing your email.",
-    "changed_email": "Email changed successfully!",
-    "change_password": "Change Password",
-    "change_password_error": "There was an issue changing your password.",
-    "changed_password": "Password changed successfully!",
-    "collapse_subject": "Collapse posts with subjects",
-    "composing": "Composing",
-    "confirm_new_password": "Confirm new password",
-    "current_avatar": "Your current avatar",
-    "current_password": "Current password",
-    "current_profile_banner": "Your current profile banner",
-    "data_import_export_tab": "Data Import / Export",
-    "default_vis": "Default visibility scope",
-    "delete_account": "Delete Account",
-    "delete_account_description": "Permanently delete your data and deactivate your account.",
-    "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
-    "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
-    "discoverable": "Allow discovery of this account in search results and other services",
-    "domain_mutes": "Domains",
-    "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
-    "pad_emoji": "Pad emoji with spaces when adding from picker",
-    "emoji_reactions_on_timeline": "Show emoji reactions on timeline",
-    "export_theme": "Save preset",
-    "filtering": "Filtering",
-    "filtering_explanation": "All statuses containing these words will be muted, one per line",
-    "follow_export": "Follow export",
-    "follow_export_button": "Export your follows to a csv file",
-    "follow_import": "Follow import",
-    "follow_import_error": "Error importing followers",
-    "follows_imported": "Follows imported! Processing them will take a while.",
-    "accent": "Accent",
-    "foreground": "Foreground",
-    "general": "General",
-    "hide_attachments_in_convo": "Hide attachments in conversations",
-    "hide_attachments_in_tl": "Hide attachments in timeline",
-    "hide_muted_posts": "Hide posts of muted users",
-    "max_thumbnails": "Maximum amount of thumbnails per post",
-    "hide_isp": "Hide instance-specific panel",
-    "preload_images": "Preload images",
-    "use_one_click_nsfw": "Open NSFW attachments with just one click",
-    "hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
-    "hide_user_stats": "Hide user statistics (e.g. the number of followers)",
-    "hide_filtered_statuses": "Hide filtered statuses",
-    "import_blocks_from_a_csv_file": "Import blocks from a csv file",
-    "import_followers_from_a_csv_file": "Import follows from a csv file",
-    "import_theme": "Load preset",
-    "inputRadius": "Input fields",
-    "checkboxRadius": "Checkboxes",
-    "instance_default": "(default: {value})",
-    "instance_default_simple": "(default)",
-    "interface": "Interface",
-    "interfaceLanguage": "Interface language",
-    "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
-    "limited_availability": "Unavailable in your browser",
-    "links": "Links",
-    "lock_account_description": "Restrict your account to approved followers only",
-    "loop_video": "Loop videos",
-    "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
-    "mutes_tab": "Mutes",
-    "play_videos_in_modal": "Play videos in a popup frame",
-    "use_contain_fit": "Don't crop the attachment in thumbnails",
-    "name": "Name",
-    "name_bio": "Name & Bio",
-    "new_email": "New Email",
-    "new_password": "New password",
-    "notification_visibility": "Types of notifications to show",
-    "notification_visibility_follows": "Follows",
-    "notification_visibility_likes": "Likes",
-    "notification_visibility_mentions": "Mentions",
-    "notification_visibility_repeats": "Repeats",
-    "notification_visibility_moves": "User Migrates",
-    "notification_visibility_emoji_reactions": "Reactions",
-    "no_rich_text_description": "Strip rich text formatting from all posts",
-    "no_blocks": "No blocks",
-    "no_mutes": "No mutes",
-    "hide_follows_description": "Don't show who I'm following",
-    "hide_followers_description": "Don't show who's following me",
-    "hide_follows_count_description": "Don't show follow count",
-    "hide_followers_count_description": "Don't show follower count",
-    "show_admin_badge": "Show Admin badge in my profile",
-    "show_moderator_badge": "Show Moderator badge in my profile",
-    "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
-    "oauth_tokens": "OAuth tokens",
-    "token": "Token",
-    "refresh_token": "Refresh Token",
-    "valid_until": "Valid Until",
-    "revoke_token": "Revoke",
-    "panelRadius": "Panels",
-    "pause_on_unfocused": "Pause streaming when tab is not focused",
-    "presets": "Presets",
-    "profile_background": "Profile Background",
-    "profile_banner": "Profile Banner",
-    "profile_tab": "Profile",
-    "radii_help": "Set up interface edge rounding (in pixels)",
-    "replies_in_timeline": "Replies in timeline",
-    "reply_link_preview": "Enable reply-link preview on mouse hover",
-    "reply_visibility_all": "Show all replies",
-    "reply_visibility_following": "Only show replies directed at me or users I'm following",
-    "reply_visibility_self": "Only show replies directed at me",
-    "autohide_floating_post_button": "Automatically hide New Post button (mobile)",
-    "saving_err": "Error saving settings",
-    "saving_ok": "Settings saved",
-    "search_user_to_block": "Search whom you want to block",
-    "search_user_to_mute": "Search whom you want to mute",
-    "security_tab": "Security",
-    "scope_copy": "Copy scope when replying (DMs are always copied)",
-    "minimal_scopes_mode": "Minimize post scope selection options",
-    "set_new_avatar": "Set new avatar",
-    "set_new_profile_background": "Set new profile background",
-    "set_new_profile_banner": "Set new profile banner",
-    "settings": "Settings",
-    "subject_input_always_show": "Always show subject field",
-    "subject_line_behavior": "Copy subject when replying",
-    "subject_line_email": "Like email: \"re: subject\"",
-    "subject_line_mastodon": "Like mastodon: copy as is",
-    "subject_line_noop": "Do not copy",
-    "post_status_content_type": "Post status content type",
-    "stop_gifs": "Play-on-hover GIFs",
-    "streaming": "Enable automatic streaming of new posts when scrolled to the top",
-    "user_mutes": "Users",
-    "useStreamingApi": "Receive posts and notifications real-time",
-    "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
-    "text": "Text",
-    "theme": "Theme",
-    "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
-    "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
-    "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
-    "tooltipRadius": "Tooltips/alerts",
-    "type_domains_to_mute": "Type in domains to mute",
-    "upload_a_photo": "Upload a photo",
-    "user_settings": "User Settings",
-    "values": {
-      "false": "no",
-      "true": "yes"
-    },
-    "fun": "Fun",
-    "greentext": "Meme arrows",
-    "notifications": "Notifications",
-    "notification_setting_filters": "Filters",
-    "notification_setting": "Receive notifications from:",
-    "notification_setting_follows": "Users you follow",
-    "notification_setting_non_follows": "Users you do not follow",
-    "notification_setting_followers": "Users who follow you",
-    "notification_setting_non_followers": "Users who do not follow you",
-    "notification_setting_privacy": "Privacy",
-    "notification_setting_privacy_option": "Hide the sender and contents of push notifications",
-    "notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
-    "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
-    "enable_web_push_notifications": "Enable web push notifications",
-    "style": {
-      "switcher": {
-        "keep_color": "Keep colors",
-        "keep_shadows": "Keep shadows",
-        "keep_opacity": "Keep opacity",
-        "keep_roundness": "Keep roundness",
-        "keep_fonts": "Keep fonts",
-        "save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
-        "reset": "Reset",
-        "clear_all": "Clear all",
-        "clear_opacity": "Clear opacity",
-        "load_theme": "Load theme",
-        "keep_as_is": "Keep as is",
-        "use_snapshot": "Old version",
-        "use_source": "New version",
-        "help": {
-          "upgraded_from_v2": "PleromaFE has been upgraded, theme could look a little bit different than you remember.",
-          "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsistencies.",
-          "future_version_imported": "File you imported was made in newer version of FE.",
-          "older_version_imported": "File you imported was made in older version of FE.",
-          "snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.",
-          "snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.",
-          "fe_upgraded": "PleromaFE's theme engine upgraded after version update.",
-          "fe_downgraded": "PleromaFE's version rolled back.",
-          "migration_snapshot_ok": "Just to be safe, theme snapshot loaded. You can try loading theme data.",
-          "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember.",
-          "snapshot_source_mismatch": "Versions conflict: most likely FE was rolled back and updated again, if you changed theme using older version of FE you most likely want to use old version, otherwise use new version."
-        }
-      },
-      "common": {
-        "color": "Color",
-        "opacity": "Opacity",
-        "contrast": {
-          "hint": "Contrast ratio is {ratio}, it {level} {context}",
-          "level": {
-            "aa": "meets Level AA guideline (minimal)",
-            "aaa": "meets Level AAA guideline (recommended)",
-            "bad": "doesn't meet any accessibility guidelines"
-          },
-          "context": {
-            "18pt": "for large (18pt+) text",
-            "text": "for text"
-          }
-        }
-      },
-      "common_colors": {
-        "_tab_label": "Common",
-        "main": "Common colors",
-        "foreground_hint": "See \"Advanced\" tab for more detailed control",
-        "rgbo": "Icons, accents, badges"
-      },
-      "advanced_colors": {
-        "_tab_label": "Advanced",
-        "alert": "Alert background",
-        "alert_error": "Error",
-        "alert_warning": "Warning",
-        "alert_neutral": "Neutral",
-        "post": "Posts/User bios",
-        "badge": "Badge background",
-        "popover": "Tooltips, menus, popovers",
-        "badge_notification": "Notification",
-        "panel_header": "Panel header",
-        "top_bar": "Top bar",
-        "borders": "Borders",
-        "buttons": "Buttons",
-        "inputs": "Input fields",
-        "faint_text": "Faded text",
-        "underlay": "Underlay",
-        "poll": "Poll graph",
-        "icons": "Icons",
-        "highlight": "Highlighted elements",
-        "pressed": "Pressed",
-        "selectedPost": "Selected post",
-        "selectedMenu": "Selected menu item",
-        "disabled": "Disabled",
-        "toggled": "Toggled",
-        "tabs": "Tabs"
-      },
-      "radii": {
-        "_tab_label": "Roundness"
-      },
-      "shadows": {
-        "_tab_label": "Shadow and lighting",
-        "component": "Component",
-        "override": "Override",
-        "shadow_id": "Shadow #{value}",
-        "blur": "Blur",
-        "spread": "Spread",
-        "inset": "Inset",
-        "hintV3": "For shadows you can also use the {0} notation to use other color slot.",
-        "filter_hint": {
-          "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
-          "drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
-          "avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
-          "spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
-          "inset_classic": "Inset shadows will be using {0}"
+    "about": {
+        "mrf": {
+            "federation": "Federation",
+            "keyword": {
+                "keyword_policies": "Keyword Policies",
+                "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+                "reject": "Reject",
+                "replace": "Replace",
+                "is_replaced_by": "→"
+            },
+            "mrf_policies": "Enabled MRF Policies",
+            "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance.  The following policies are enabled:",
+            "simple": {
+                "simple_policies": "Instance-specific Policies",
+                "accept": "Accept",
+                "accept_desc": "This instance only accepts messages from the following instances:",
+                "reject": "Reject",
+                "reject_desc": "This instance will not accept messages from the following instances:",
+                "quarantine": "Quarantine",
+                "quarantine_desc": "This instance will send only public posts to the following instances:",
+                "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+                "ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
+                "media_removal": "Media Removal",
+                "media_removal_desc": "This instance removes media from posts on the following instances:",
+                "media_nsfw": "Media Force-set As Sensitive",
+                "media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
+            }
         },
-        "components": {
-          "panel": "Panel",
-          "panelHeader": "Panel header",
-          "topBar": "Top bar",
-          "avatar": "User avatar (in profile view)",
-          "avatarStatus": "User avatar (in post display)",
-          "popup": "Popups and tooltips",
-          "button": "Button",
-          "buttonHover": "Button (hover)",
-          "buttonPressed": "Button (pressed)",
-          "buttonPressedHover": "Button (pressed+hover)",
-          "input": "Input field"
+        "staff": "Staff"
+    },
+    "chat": {
+        "title": "Chat"
+    },
+    "domain_mute_card": {
+        "mute": "Mute",
+        "mute_progress": "Muting…",
+        "unmute": "Unmute",
+        "unmute_progress": "Unmuting…"
+    },
+    "exporter": {
+        "export": "Export",
+        "processing": "Processing, you'll soon be asked to download your file"
+    },
+    "features_panel": {
+        "chat": "Chat",
+        "gopher": "Gopher",
+        "media_proxy": "Media proxy",
+        "scope_options": "Scope options",
+        "text_limit": "Text limit",
+        "title": "Features",
+        "who_to_follow": "Who to follow"
+    },
+    "finder": {
+        "error_fetching_user": "Error fetching user",
+        "find_user": "Find user"
+    },
+    "general": {
+        "apply": "Apply",
+        "submit": "Submit",
+        "more": "More",
+        "generic_error": "An error occured",
+        "optional": "optional",
+        "show_more": "Show more",
+        "show_less": "Show less",
+        "dismiss": "Dismiss",
+        "cancel": "Cancel",
+        "disable": "Disable",
+        "enable": "Enable",
+        "confirm": "Confirm",
+        "verify": "Verify"
+    },
+    "image_cropper": {
+        "crop_picture": "Crop picture",
+        "save": "Save",
+        "save_without_cropping": "Save without cropping",
+        "cancel": "Cancel"
+    },
+    "importer": {
+        "submit": "Submit",
+        "success": "Imported successfully.",
+        "error": "An error occured while importing this file."
+    },
+    "login": {
+        "login": "Log in",
+        "description": "Log in with OAuth",
+        "logout": "Log out",
+        "password": "Password",
+        "placeholder": "e.g. lain",
+        "register": "Register",
+        "username": "Username",
+        "hint": "Log in to join the discussion",
+        "authentication_code": "Authentication code",
+        "enter_recovery_code": "Enter a recovery code",
+        "enter_two_factor_code": "Enter a two-factor code",
+        "recovery_code": "Recovery code",
+        "heading": {
+            "totp": "Two-factor authentication",
+            "recovery": "Two-factor recovery"
         }
-      },
-      "fonts": {
-        "_tab_label": "Fonts",
-        "help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
-        "components": {
-          "interface": "Interface",
-          "input": "Input fields",
-          "post": "Post text",
-          "postCode": "Monospaced text in a post (rich text)"
+    },
+    "media_modal": {
+        "previous": "Previous",
+        "next": "Next"
+    },
+    "nav": {
+        "about": "About",
+        "administration": "Administration",
+        "back": "Back",
+        "chat": "Local Chat",
+        "friend_requests": "Follow Requests",
+        "mentions": "Mentions",
+        "interactions": "Interactions",
+        "dms": "Direct Messages",
+        "public_tl": "Public Timeline",
+        "timeline": "Timeline",
+        "twkn": "The Whole Known Network",
+        "user_search": "User Search",
+        "search": "Search",
+        "who_to_follow": "Who to follow",
+        "preferences": "Preferences"
+    },
+    "notifications": {
+        "broken_favorite": "Unknown status, searching for it…",
+        "favorited_you": "favorited your status",
+        "followed_you": "followed you",
+        "follow_request": "wants to follow you",
+        "load_older": "Load older notifications",
+        "notifications": "Notifications",
+        "read": "Read!",
+        "repeated_you": "repeated your status",
+        "no_more_notifications": "No more notifications",
+        "migrated_to": "migrated to",
+        "reacted_with": "reacted with {0}"
+    },
+    "polls": {
+        "add_poll": "Add Poll",
+        "add_option": "Add Option",
+        "option": "Option",
+        "votes": "votes",
+        "vote": "Vote",
+        "type": "Poll type",
+        "single_choice": "Single choice",
+        "multiple_choices": "Multiple choices",
+        "expiry": "Poll age",
+        "expires_in": "Poll ends in {0}",
+        "expired": "Poll ended {0} ago",
+        "not_enough_options": "Too few unique options in poll"
+    },
+    "emoji": {
+        "stickers": "Stickers",
+        "emoji": "Emoji",
+        "keep_open": "Keep picker open",
+        "search_emoji": "Search for an emoji",
+        "add_emoji": "Insert emoji",
+        "custom": "Custom emoji",
+        "unicode": "Unicode emoji",
+        "load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
+        "load_all": "Loading all {emojiAmount} emoji"
+    },
+    "interactions": {
+        "favs_repeats": "Repeats and Favorites",
+        "follows": "New follows",
+        "moves": "User migrates",
+        "load_older": "Load older interactions"
+    },
+    "post_status": {
+        "new_status": "Post new status",
+        "account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
+        "account_not_locked_warning_link": "locked",
+        "attachments_sensitive": "Mark attachments as sensitive",
+        "content_type": {
+            "text/plain": "Plain text",
+            "text/html": "HTML",
+            "text/markdown": "Markdown",
+            "text/bbcode": "BBCode"
         },
-        "family": "Font name",
-        "size": "Size (in px)",
-        "weight": "Weight (boldness)",
-        "custom": "Custom"
-      },
-      "preview": {
-        "header": "Preview",
-        "content": "Content",
-        "error": "Example error",
-        "button": "Button",
-        "text": "A bunch of more {0} and {1}",
-        "mono": "content",
-        "input": "Just landed in L.A.",
-        "faint_link": "helpful manual",
-        "fine_print": "Read our {0} to learn nothing useful!",
-        "header_faint": "This is fine",
-        "checkbox": "I have skimmed over terms and conditions",
-        "link": "a nice lil' link"
-      }
+        "content_warning": "Subject (optional)",
+        "default": "Just landed in L.A.",
+        "direct_warning_to_all": "This post will be visible to all the mentioned users.",
+        "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
+        "posting": "Posting",
+        "scope_notice": {
+            "public": "This post will be visible to everyone",
+            "private": "This post will be visible to your followers only",
+            "unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
+        },
+        "scope": {
+            "direct": "Direct - Post to mentioned users only",
+            "private": "Followers-only - Post to followers only",
+            "public": "Public - Post to public timelines",
+            "unlisted": "Unlisted - Do not post to public timelines"
+        }
     },
-    "version": {
-      "title": "Version",
-      "backend_version": "Backend Version",
-      "frontend_version": "Frontend Version"
-    }
-  },
-  "time": {
-    "day": "{0} day",
-    "days": "{0} days",
-    "day_short": "{0}d",
-    "days_short": "{0}d",
-    "hour": "{0} hour",
-    "hours": "{0} hours",
-    "hour_short": "{0}h",
-    "hours_short": "{0}h",
-    "in_future": "in {0}",
-    "in_past": "{0} ago",
-    "minute": "{0} minute",
-    "minutes": "{0} minutes",
-    "minute_short": "{0}min",
-    "minutes_short": "{0}min",
-    "month": "{0} month",
-    "months": "{0} months",
-    "month_short": "{0}mo",
-    "months_short": "{0}mo",
-    "now": "just now",
-    "now_short": "now",
-    "second": "{0} second",
-    "seconds": "{0} seconds",
-    "second_short": "{0}s",
-    "seconds_short": "{0}s",
-    "week": "{0} week",
-    "weeks": "{0} weeks",
-    "week_short": "{0}w",
-    "weeks_short": "{0}w",
-    "year": "{0} year",
-    "years": "{0} years",
-    "year_short": "{0}y",
-    "years_short": "{0}y"
-  },
-  "timeline": {
-    "collapse": "Collapse",
-    "conversation": "Conversation",
-    "error_fetching": "Error fetching updates",
-    "load_older": "Load older statuses",
-    "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
-    "repeated": "repeated",
-    "show_new": "Show new",
-    "up_to_date": "Up-to-date",
-    "no_more_statuses": "No more statuses",
-    "no_statuses": "No statuses"
-  },
-  "status": {
-    "favorites": "Favorites",
-    "repeats": "Repeats",
-    "delete": "Delete status",
-    "pin": "Pin on profile",
-    "unpin": "Unpin from profile",
-    "pinned": "Pinned",
-    "delete_confirm": "Do you really want to delete this status?",
-    "reply_to": "Reply to",
-    "replies_list": "Replies:",
-    "mute_conversation": "Mute conversation",
-    "unmute_conversation": "Unmute conversation",
-    "status_unavailable": "Status unavailable",
-    "copy_link": "Copy link to status"
-  },
-  "user_card": {
-    "approve": "Approve",
-    "block": "Block",
-    "blocked": "Blocked!",
-    "deny": "Deny",
-    "favorites": "Favorites",
-    "follow": "Follow",
-    "follow_sent": "Request sent!",
-    "follow_progress": "Requesting…",
-    "follow_again": "Send request again?",
-    "follow_unfollow": "Unfollow",
-    "followees": "Following",
-    "followers": "Followers",
-    "following": "Following!",
-    "follows_you": "Follows you!",
-    "hidden": "Hidden",
-    "its_you": "It's you!",
-    "media": "Media",
-    "mention": "Mention",
-    "mute": "Mute",
-    "muted": "Muted",
-    "per_day": "per day",
-    "remote_follow": "Remote follow",
-    "report": "Report",
-    "statuses": "Statuses",
-    "subscribe": "Subscribe",
-    "unsubscribe": "Unsubscribe",
-    "unblock": "Unblock",
-    "unblock_progress": "Unblocking...",
-    "block_progress": "Blocking...",
-    "unmute": "Unmute",
-    "unmute_progress": "Unmuting...",
-    "mute_progress": "Muting...",
-    "hide_repeats": "Hide repeats",
-    "show_repeats": "Show repeats",
-    "admin_menu": {
-      "moderation": "Moderation",
-      "grant_admin": "Grant Admin",
-      "revoke_admin": "Revoke Admin",
-      "grant_moderator": "Grant Moderator",
-      "revoke_moderator": "Revoke Moderator",
-      "activate_account": "Activate account",
-      "deactivate_account": "Deactivate account",
-      "delete_account": "Delete account",
-      "force_nsfw": "Mark all posts as NSFW",
-      "strip_media": "Remove media from posts",
-      "force_unlisted": "Force posts to be unlisted",
-      "sandbox": "Force posts to be followers-only",
-      "disable_remote_subscription": "Disallow following user from remote instances",
-      "disable_any_subscription": "Disallow following user at all",
-      "quarantine": "Disallow user posts from federating",
-      "delete_user": "Delete user",
-      "delete_user_confirmation": "Are you absolutely sure? This action cannot be undone."
-    }
-  },
-  "user_profile": {
-    "timeline_title": "User Timeline",
-    "profile_does_not_exist": "Sorry, this profile does not exist.",
-    "profile_loading_error": "Sorry, there was an error loading this profile."
-  },
-  "user_reporting": {
-    "title": "Reporting {0}",
-    "add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
-    "additional_comments": "Additional comments",
-    "forward_description": "The account is from another server. Send a copy of the report there as well?",
-    "forward_to": "Forward to {0}",
-    "submit": "Submit",
-    "generic_error": "An error occurred while processing your request."
-  },
-  "who_to_follow": {
-    "more": "More",
-    "who_to_follow": "Who to follow"
-  },
-  "tool_tip": {
-    "media_upload": "Upload Media",
-    "repeat": "Repeat",
-    "reply": "Reply",
-    "favorite": "Favorite",
-    "add_reaction": "Add Reaction",
-    "user_settings": "User Settings",
-    "accept_follow_request": "Accept follow request",
-    "reject_follow_request": "Reject follow request"
-  },
-  "upload":{
-    "error": {
-      "base": "Upload failed.",
-      "file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-      "default": "Try again later"
+    "registration": {
+        "bio": "Bio",
+        "email": "Email",
+        "fullname": "Display name",
+        "password_confirm": "Password confirmation",
+        "registration": "Registration",
+        "token": "Invite token",
+        "captcha": "CAPTCHA",
+        "new_captcha": "Click the image to get a new captcha",
+        "username_placeholder": "e.g. lain",
+        "fullname_placeholder": "e.g. Lain Iwakura",
+        "bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
+        "validations": {
+            "username_required": "cannot be left blank",
+            "fullname_required": "cannot be left blank",
+            "email_required": "cannot be left blank",
+            "password_required": "cannot be left blank",
+            "password_confirmation_required": "cannot be left blank",
+            "password_confirmation_match": "should be the same as password"
+        }
     },
-    "file_size_units": {
-      "B": "B",
-      "KiB": "KiB",
-      "MiB": "MiB",
-      "GiB": "GiB",
-      "TiB": "TiB"
+    "remote_user_resolver": {
+        "remote_user_resolver": "Remote user resolver",
+        "searching_for": "Searching for",
+        "error": "Not found."
+    },
+    "selectable_list": {
+        "select_all": "Select all"
+    },
+    "settings": {
+        "app_name": "App name",
+        "security": "Security",
+        "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
+        "mfa": {
+            "otp": "OTP",
+            "setup_otp": "Setup OTP",
+            "wait_pre_setup_otp": "presetting OTP",
+            "confirm_and_enable": "Confirm & enable OTP",
+            "title": "Two-factor Authentication",
+            "generate_new_recovery_codes": "Generate new recovery codes",
+            "warning_of_generate_new_codes": "When you generate new recovery codes, your old codes won’t work anymore.",
+            "recovery_codes": "Recovery codes.",
+            "waiting_a_recovery_codes": "Receiving backup codes…",
+            "recovery_codes_warning": "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.",
+            "authentication_methods": "Authentication methods",
+            "scan": {
+                "title": "Scan",
+                "desc": "Using your two-factor app, scan this QR code or enter text key:",
+                "secret_code": "Key"
+            },
+            "verify": {
+                "desc": "To enable two-factor authentication, enter the code from your two-factor app:"
+            }
+        },
+        "allow_following_move": "Allow auto-follow when following account moves",
+        "attachmentRadius": "Attachments",
+        "attachments": "Attachments",
+        "autoload": "Enable automatic loading when scrolled to the bottom",
+        "avatar": "Avatar",
+        "avatarAltRadius": "Avatars (Notifications)",
+        "avatarRadius": "Avatars",
+        "background": "Background",
+        "bio": "Bio",
+        "block_export": "Block export",
+        "block_export_button": "Export your blocks to a csv file",
+        "block_import": "Block import",
+        "block_import_error": "Error importing blocks",
+        "blocks_imported": "Blocks imported! Processing them will take a while.",
+        "blocks_tab": "Blocks",
+        "btnRadius": "Buttons",
+        "cBlue": "Blue (Reply, follow)",
+        "cGreen": "Green (Retweet)",
+        "cOrange": "Orange (Favorite)",
+        "cRed": "Red (Cancel)",
+        "change_email": "Change Email",
+        "change_email_error": "There was an issue changing your email.",
+        "changed_email": "Email changed successfully!",
+        "change_password": "Change Password",
+        "change_password_error": "There was an issue changing your password.",
+        "changed_password": "Password changed successfully!",
+        "collapse_subject": "Collapse posts with subjects",
+        "composing": "Composing",
+        "confirm_new_password": "Confirm new password",
+        "current_avatar": "Your current avatar",
+        "current_password": "Current password",
+        "current_profile_banner": "Your current profile banner",
+        "data_import_export_tab": "Data Import / Export",
+        "default_vis": "Default visibility scope",
+        "delete_account": "Delete Account",
+        "delete_account_description": "Permanently delete your data and deactivate your account.",
+        "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
+        "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
+        "discoverable": "Allow discovery of this account in search results and other services",
+        "domain_mutes": "Domains",
+        "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
+        "pad_emoji": "Pad emoji with spaces when adding from picker",
+        "emoji_reactions_on_timeline": "Show emoji reactions on timeline",
+        "export_theme": "Save preset",
+        "filtering": "Filtering",
+        "filtering_explanation": "All statuses containing these words will be muted, one per line",
+        "follow_export": "Follow export",
+        "follow_export_button": "Export your follows to a csv file",
+        "follow_import": "Follow import",
+        "follow_import_error": "Error importing followers",
+        "follows_imported": "Follows imported! Processing them will take a while.",
+        "accent": "Accent",
+        "foreground": "Foreground",
+        "general": "General",
+        "hide_attachments_in_convo": "Hide attachments in conversations",
+        "hide_attachments_in_tl": "Hide attachments in timeline",
+        "hide_muted_posts": "Hide posts of muted users",
+        "max_thumbnails": "Maximum amount of thumbnails per post",
+        "hide_isp": "Hide instance-specific panel",
+        "preload_images": "Preload images",
+        "use_one_click_nsfw": "Open NSFW attachments with just one click",
+        "hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
+        "hide_user_stats": "Hide user statistics (e.g. the number of followers)",
+        "hide_filtered_statuses": "Hide filtered statuses",
+        "import_blocks_from_a_csv_file": "Import blocks from a csv file",
+        "import_followers_from_a_csv_file": "Import follows from a csv file",
+        "import_theme": "Load preset",
+        "inputRadius": "Input fields",
+        "checkboxRadius": "Checkboxes",
+        "instance_default": "(default: {value})",
+        "instance_default_simple": "(default)",
+        "interface": "Interface",
+        "interfaceLanguage": "Interface language",
+        "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
+        "limited_availability": "Unavailable in your browser",
+        "links": "Links",
+        "lock_account_description": "Restrict your account to approved followers only",
+        "loop_video": "Loop videos",
+        "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
+        "mutes_tab": "Mutes",
+        "play_videos_in_modal": "Play videos in a popup frame",
+        "use_contain_fit": "Don't crop the attachment in thumbnails",
+        "name": "Name",
+        "name_bio": "Name & Bio",
+        "new_email": "New Email",
+        "new_password": "New password",
+        "notification_visibility": "Types of notifications to show",
+        "notification_visibility_follows": "Follows",
+        "notification_visibility_likes": "Likes",
+        "notification_visibility_mentions": "Mentions",
+        "notification_visibility_repeats": "Repeats",
+        "notification_visibility_moves": "User Migrates",
+        "notification_visibility_emoji_reactions": "Reactions",
+        "no_rich_text_description": "Strip rich text formatting from all posts",
+        "no_blocks": "No blocks",
+        "no_mutes": "No mutes",
+        "hide_follows_description": "Don't show who I'm following",
+        "hide_followers_description": "Don't show who's following me",
+        "hide_follows_count_description": "Don't show follow count",
+        "hide_followers_count_description": "Don't show follower count",
+        "show_admin_badge": "Show Admin badge in my profile",
+        "show_moderator_badge": "Show Moderator badge in my profile",
+        "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
+        "oauth_tokens": "OAuth tokens",
+        "token": "Token",
+        "refresh_token": "Refresh Token",
+        "valid_until": "Valid Until",
+        "revoke_token": "Revoke",
+        "panelRadius": "Panels",
+        "pause_on_unfocused": "Pause streaming when tab is not focused",
+        "presets": "Presets",
+        "profile_background": "Profile Background",
+        "profile_banner": "Profile Banner",
+        "profile_tab": "Profile",
+        "radii_help": "Set up interface edge rounding (in pixels)",
+        "replies_in_timeline": "Replies in timeline",
+        "reply_link_preview": "Enable reply-link preview on mouse hover",
+        "reply_visibility_all": "Show all replies",
+        "reply_visibility_following": "Only show replies directed at me or users I'm following",
+        "reply_visibility_self": "Only show replies directed at me",
+        "autohide_floating_post_button": "Automatically hide New Post button (mobile)",
+        "saving_err": "Error saving settings",
+        "saving_ok": "Settings saved",
+        "search_user_to_block": "Search whom you want to block",
+        "search_user_to_mute": "Search whom you want to mute",
+        "security_tab": "Security",
+        "scope_copy": "Copy scope when replying (DMs are always copied)",
+        "minimal_scopes_mode": "Minimize post scope selection options",
+        "set_new_avatar": "Set new avatar",
+        "set_new_profile_background": "Set new profile background",
+        "set_new_profile_banner": "Set new profile banner",
+        "settings": "Settings",
+        "subject_input_always_show": "Always show subject field",
+        "subject_line_behavior": "Copy subject when replying",
+        "subject_line_email": "Like email: \"re: subject\"",
+        "subject_line_mastodon": "Like mastodon: copy as is",
+        "subject_line_noop": "Do not copy",
+        "post_status_content_type": "Post status content type",
+        "stop_gifs": "Play-on-hover GIFs",
+        "streaming": "Enable automatic streaming of new posts when scrolled to the top",
+        "user_mutes": "Users",
+        "useStreamingApi": "Receive posts and notifications real-time",
+        "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
+        "text": "Text",
+        "theme": "Theme",
+        "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
+        "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
+        "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
+        "tooltipRadius": "Tooltips/alerts",
+        "type_domains_to_mute": "Type in domains to mute",
+        "upload_a_photo": "Upload a photo",
+        "user_settings": "User Settings",
+        "values": {
+            "false": "no",
+            "true": "yes"
+        },
+        "fun": "Fun",
+        "greentext": "Meme arrows",
+        "notifications": "Notifications",
+        "notification_setting_filters": "Filters",
+        "notification_setting": "Receive notifications from:",
+        "notification_setting_follows": "Users you follow",
+        "notification_setting_non_follows": "Users you do not follow",
+        "notification_setting_followers": "Users who follow you",
+        "notification_setting_non_followers": "Users who do not follow you",
+        "notification_setting_privacy": "Privacy",
+        "notification_setting_privacy_option": "Hide the sender and contents of push notifications",
+        "notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
+        "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
+        "enable_web_push_notifications": "Enable web push notifications",
+        "style": {
+            "switcher": {
+                "keep_color": "Keep colors",
+                "keep_shadows": "Keep shadows",
+                "keep_opacity": "Keep opacity",
+                "keep_roundness": "Keep roundness",
+                "keep_fonts": "Keep fonts",
+                "save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
+                "reset": "Reset",
+                "clear_all": "Clear all",
+                "clear_opacity": "Clear opacity",
+                "load_theme": "Load theme",
+                "keep_as_is": "Keep as is",
+                "use_snapshot": "Old version",
+                "use_source": "New version",
+                "help": {
+                    "upgraded_from_v2": "PleromaFE has been upgraded, theme could look a little bit different than you remember.",
+                    "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsistencies.",
+                    "future_version_imported": "File you imported was made in newer version of FE.",
+                    "older_version_imported": "File you imported was made in older version of FE.",
+                    "snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.",
+                    "snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.",
+                    "fe_upgraded": "PleromaFE's theme engine upgraded after version update.",
+                    "fe_downgraded": "PleromaFE's version rolled back.",
+                    "migration_snapshot_ok": "Just to be safe, theme snapshot loaded. You can try loading theme data.",
+                    "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember.",
+                    "snapshot_source_mismatch": "Versions conflict: most likely FE was rolled back and updated again, if you changed theme using older version of FE you most likely want to use old version, otherwise use new version."
+                }
+            },
+            "common": {
+                "color": "Color",
+                "opacity": "Opacity",
+                "contrast": {
+                    "hint": "Contrast ratio is {ratio}, it {level} {context}",
+                    "level": {
+                        "aa": "meets Level AA guideline (minimal)",
+                        "aaa": "meets Level AAA guideline (recommended)",
+                        "bad": "doesn't meet any accessibility guidelines"
+                    },
+                    "context": {
+                        "18pt": "for large (18pt+) text",
+                        "text": "for text"
+                    }
+                }
+            },
+            "common_colors": {
+                "_tab_label": "Common",
+                "main": "Common colors",
+                "foreground_hint": "See \"Advanced\" tab for more detailed control",
+                "rgbo": "Icons, accents, badges"
+            },
+            "advanced_colors": {
+                "_tab_label": "Advanced",
+                "alert": "Alert background",
+                "alert_error": "Error",
+                "alert_warning": "Warning",
+                "alert_neutral": "Neutral",
+                "post": "Posts/User bios",
+                "badge": "Badge background",
+                "popover": "Tooltips, menus, popovers",
+                "badge_notification": "Notification",
+                "panel_header": "Panel header",
+                "top_bar": "Top bar",
+                "borders": "Borders",
+                "buttons": "Buttons",
+                "inputs": "Input fields",
+                "faint_text": "Faded text",
+                "underlay": "Underlay",
+                "poll": "Poll graph",
+                "icons": "Icons",
+                "highlight": "Highlighted elements",
+                "pressed": "Pressed",
+                "selectedPost": "Selected post",
+                "selectedMenu": "Selected menu item",
+                "disabled": "Disabled",
+                "toggled": "Toggled",
+                "tabs": "Tabs"
+            },
+            "radii": {
+                "_tab_label": "Roundness"
+            },
+            "shadows": {
+                "_tab_label": "Shadow and lighting",
+                "component": "Component",
+                "override": "Override",
+                "shadow_id": "Shadow #{value}",
+                "blur": "Blur",
+                "spread": "Spread",
+                "inset": "Inset",
+                "hintV3": "For shadows you can also use the {0} notation to use other color slot.",
+                "filter_hint": {
+                    "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
+                    "drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
+                    "avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
+                    "spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
+                    "inset_classic": "Inset shadows will be using {0}"
+                },
+                "components": {
+                    "panel": "Panel",
+                    "panelHeader": "Panel header",
+                    "topBar": "Top bar",
+                    "avatar": "User avatar (in profile view)",
+                    "avatarStatus": "User avatar (in post display)",
+                    "popup": "Popups and tooltips",
+                    "button": "Button",
+                    "buttonHover": "Button (hover)",
+                    "buttonPressed": "Button (pressed)",
+                    "buttonPressedHover": "Button (pressed+hover)",
+                    "input": "Input field"
+                }
+            },
+            "fonts": {
+                "_tab_label": "Fonts",
+                "help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
+                "components": {
+                    "interface": "Interface",
+                    "input": "Input fields",
+                    "post": "Post text",
+                    "postCode": "Monospaced text in a post (rich text)"
+                },
+                "family": "Font name",
+                "size": "Size (in px)",
+                "weight": "Weight (boldness)",
+                "custom": "Custom"
+            },
+            "preview": {
+                "header": "Preview",
+                "content": "Content",
+                "error": "Example error",
+                "button": "Button",
+                "text": "A bunch of more {0} and {1}",
+                "mono": "content",
+                "input": "Just landed in L.A.",
+                "faint_link": "helpful manual",
+                "fine_print": "Read our {0} to learn nothing useful!",
+                "header_faint": "This is fine",
+                "checkbox": "I have skimmed over terms and conditions",
+                "link": "a nice lil' link"
+            }
+        },
+        "version": {
+            "title": "Version",
+            "backend_version": "Backend Version",
+            "frontend_version": "Frontend Version"
+        }
+    },
+    "time": {
+        "day": "{0} day",
+        "days": "{0} days",
+        "day_short": "{0}d",
+        "days_short": "{0}d",
+        "hour": "{0} hour",
+        "hours": "{0} hours",
+        "hour_short": "{0}h",
+        "hours_short": "{0}h",
+        "in_future": "in {0}",
+        "in_past": "{0} ago",
+        "minute": "{0} minute",
+        "minutes": "{0} minutes",
+        "minute_short": "{0}min",
+        "minutes_short": "{0}min",
+        "month": "{0} month",
+        "months": "{0} months",
+        "month_short": "{0}mo",
+        "months_short": "{0}mo",
+        "now": "just now",
+        "now_short": "now",
+        "second": "{0} second",
+        "seconds": "{0} seconds",
+        "second_short": "{0}s",
+        "seconds_short": "{0}s",
+        "week": "{0} week",
+        "weeks": "{0} weeks",
+        "week_short": "{0}w",
+        "weeks_short": "{0}w",
+        "year": "{0} year",
+        "years": "{0} years",
+        "year_short": "{0}y",
+        "years_short": "{0}y"
+    },
+    "timeline": {
+        "collapse": "Collapse",
+        "conversation": "Conversation",
+        "error_fetching": "Error fetching updates",
+        "load_older": "Load older statuses",
+        "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
+        "repeated": "repeated",
+        "show_new": "Show new",
+        "up_to_date": "Up-to-date",
+        "no_more_statuses": "No more statuses",
+        "no_statuses": "No statuses"
+    },
+    "status": {
+        "favorites": "Favorites",
+        "repeats": "Repeats",
+        "delete": "Delete status",
+        "pin": "Pin on profile",
+        "unpin": "Unpin from profile",
+        "pinned": "Pinned",
+        "delete_confirm": "Do you really want to delete this status?",
+        "reply_to": "Reply to",
+        "replies_list": "Replies:",
+        "mute_conversation": "Mute conversation",
+        "unmute_conversation": "Unmute conversation",
+        "status_unavailable": "Status unavailable",
+        "copy_link": "Copy link to status"
+    },
+    "user_card": {
+        "approve": "Approve",
+        "block": "Block",
+        "blocked": "Blocked!",
+        "deny": "Deny",
+        "favorites": "Favorites",
+        "follow": "Follow",
+        "follow_sent": "Request sent!",
+        "follow_progress": "Requesting…",
+        "follow_again": "Send request again?",
+        "follow_unfollow": "Unfollow",
+        "followees": "Following",
+        "followers": "Followers",
+        "following": "Following!",
+        "follows_you": "Follows you!",
+        "hidden": "Hidden",
+        "its_you": "It's you!",
+        "media": "Media",
+        "mention": "Mention",
+        "mute": "Mute",
+        "muted": "Muted",
+        "per_day": "per day",
+        "remote_follow": "Remote follow",
+        "report": "Report",
+        "statuses": "Statuses",
+        "subscribe": "Subscribe",
+        "unsubscribe": "Unsubscribe",
+        "unblock": "Unblock",
+        "unblock_progress": "Unblocking…",
+        "block_progress": "Blocking…",
+        "unmute": "Unmute",
+        "unmute_progress": "Unmuting…",
+        "mute_progress": "Muting…",
+        "hide_repeats": "Hide repeats",
+        "show_repeats": "Show repeats",
+        "admin_menu": {
+            "moderation": "Moderation",
+            "grant_admin": "Grant Admin",
+            "revoke_admin": "Revoke Admin",
+            "grant_moderator": "Grant Moderator",
+            "revoke_moderator": "Revoke Moderator",
+            "activate_account": "Activate account",
+            "deactivate_account": "Deactivate account",
+            "delete_account": "Delete account",
+            "force_nsfw": "Mark all posts as NSFW",
+            "strip_media": "Remove media from posts",
+            "force_unlisted": "Force posts to be unlisted",
+            "sandbox": "Force posts to be followers-only",
+            "disable_remote_subscription": "Disallow following user from remote instances",
+            "disable_any_subscription": "Disallow following user at all",
+            "quarantine": "Disallow user posts from federating",
+            "delete_user": "Delete user",
+            "delete_user_confirmation": "Are you absolutely sure? This action cannot be undone."
+        }
+    },
+    "user_profile": {
+        "timeline_title": "User Timeline",
+        "profile_does_not_exist": "Sorry, this profile does not exist.",
+        "profile_loading_error": "Sorry, there was an error loading this profile."
+    },
+    "user_reporting": {
+        "title": "Reporting {0}",
+        "add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+        "additional_comments": "Additional comments",
+        "forward_description": "The account is from another server. Send a copy of the report there as well?",
+        "forward_to": "Forward to {0}",
+        "submit": "Submit",
+        "generic_error": "An error occurred while processing your request."
+    },
+    "who_to_follow": {
+        "more": "More",
+        "who_to_follow": "Who to follow"
+    },
+    "tool_tip": {
+        "media_upload": "Upload Media",
+        "repeat": "Repeat",
+        "reply": "Reply",
+        "favorite": "Favorite",
+        "add_reaction": "Add Reaction",
+        "user_settings": "User Settings",
+        "accept_follow_request": "Accept follow request",
+        "reject_follow_request": "Reject follow request"
+    },
+    "upload": {
+        "error": {
+            "base": "Upload failed.",
+            "file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+            "default": "Try again later"
+        },
+        "file_size_units": {
+            "B": "B",
+            "KiB": "KiB",
+            "MiB": "MiB",
+            "GiB": "GiB",
+            "TiB": "TiB"
+        }
+    },
+    "search": {
+        "people": "People",
+        "hashtags": "Hashtags",
+        "person_talking": "{count} person talking",
+        "people_talking": "{count} people talking",
+        "no_results": "No results"
+    },
+    "password_reset": {
+        "forgot_password": "Forgot password?",
+        "password_reset": "Password reset",
+        "instruction": "Enter your email address or username. We will send you a link to reset your password.",
+        "placeholder": "Your email or username",
+        "check_email": "Check your email for a link to reset your password.",
+        "return_home": "Return to the home page",
+        "not_found": "We couldn't find that email or username.",
+        "too_many_requests": "You have reached the limit of attempts, try again later.",
+        "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.",
+        "password_reset_required": "You must reset your password to log in.",
+        "password_reset_required_but_mailer_is_disabled": "You must reset your password, but password reset is disabled. Please contact your instance administrator."
     }
-  },
-  "search": {
-    "people": "People",
-    "hashtags": "Hashtags",
-    "person_talking": "{count} person talking",
-    "people_talking": "{count} people talking",
-    "no_results": "No results"
-  },
-  "password_reset": {
-    "forgot_password": "Forgot password?",
-    "password_reset": "Password reset",
-    "instruction": "Enter your email address or username. We will send you a link to reset your password.",
-    "placeholder": "Your email or username",
-    "check_email": "Check your email for a link to reset your password.",
-    "return_home": "Return to the home page",
-    "not_found": "We couldn't find that email or username.",
-    "too_many_requests": "You have reached the limit of attempts, try again later.",
-    "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.",
-    "password_reset_required": "You must reset your password to log in.",
-    "password_reset_required_but_mailer_is_disabled": "You must reset your password, but password reset is disabled. Please contact your instance administrator."
-  }
 }

From a0d935649b7c579b37452bf811d050e92be909e5 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Thu, 28 May 2020 20:12:47 +0000
Subject: [PATCH 448/483] Translated using Weblate (Italian)

Currently translated at 65.0% (399 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index e1b8022c..6a87b6ad 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -35,7 +35,7 @@
         "followed_you": "ti segue",
         "notifications": "Notifiche",
         "read": "Letto!",
-        "broken_favorite": "Stato sconosciuto, lo sto cercando...",
+        "broken_favorite": "Stato sconosciuto, lo sto cercando…",
         "favorited_you": "ha gradito il tuo messaggio",
         "load_older": "Carica notifiche precedenti",
         "repeated_you": "ha condiviso il tuo messaggio",
@@ -168,7 +168,7 @@
             },
             "authentication_methods": "Metodi di accesso",
             "recovery_codes_warning": "Appuntati i codici o salvali in un posto sicuro, altrimenti rischi di non rivederli mai più. Se perderai l'accesso sia alla tua applicazione bifattoriale che ai codici di recupero non potrai più accedere al tuo profilo.",
-            "waiting_a_recovery_codes": "Ricevo codici di recupero...",
+            "waiting_a_recovery_codes": "Ricevo codici di recupero…",
             "recovery_codes": "Codici di recupero.",
             "warning_of_generate_new_codes": "Alla generazione di nuovi codici di recupero, quelli vecchi saranno disattivati.",
             "generate_new_recovery_codes": "Genera nuovi codici di recupero",
@@ -187,7 +187,12 @@
                     "older_version_imported": "Il tema importato è stato creato per una versione precedente dell'interfaccia.",
                     "future_version_imported": "Il tema importato è stato creato per una versione più recente dell'interfaccia.",
                     "v2_imported": "Il tema importato è stato creato per una vecchia interfaccia. Non tutto potrebbe essere come prima.",
-                    "upgraded_from_v2": "L'interfaccia è stata aggiornata, il tema potrebbe essere diverso da come lo intendevi."
+                    "upgraded_from_v2": "L'interfaccia è stata aggiornata, il tema potrebbe essere diverso da come lo intendevi.",
+                    "migration_snapshot_ok": "Ho caricato l'anteprima del tema. Puoi provare a caricarne i contenuti.",
+                    "fe_downgraded": "L'interfaccia è stata portata ad una versione precedente.",
+                    "fe_upgraded": "Lo schema dei temi è stato aggiornato insieme all'interfaccia.",
+                    "snapshot_missing": "Il tema non è provvisto di anteprima, quindi potrebbe essere diverso da come appare.",
+                    "snapshot_present": "Tutti i valori sono sostituiti dall'anteprima del tema. Puoi invece caricare i suoi contenuti."
                 },
                 "use_source": "Nuova versione",
                 "use_snapshot": "Versione precedente",
@@ -261,7 +266,8 @@
         "hide_muted_posts": "Nascondi messaggi degli utenti zittiti",
         "accent": "Accento",
         "emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze",
-        "pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore"
+        "pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore",
+        "notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più."
     },
     "timeline": {
         "error_fetching": "Errore nell'aggiornamento",
@@ -412,9 +418,9 @@
     },
     "domain_mute_card": {
         "mute": "Zittisci",
-        "mute_progress": "Zittisco...",
+        "mute_progress": "Zittisco…",
         "unmute": "Ascolta",
-        "unmute_progress": "Procedo..."
+        "unmute_progress": "Procedo…"
     },
     "exporter": {
         "export": "Esporta",

From 654105d5c1231cfd9f9a3851ee1cb7f3102ccb3a Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Mon, 1 Jun 2020 16:23:29 +0000
Subject: [PATCH 449/483] Translated using Weblate (Dutch)

Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
---
 src/i18n/nl.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index 472d11ec..768babb5 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -604,9 +604,9 @@
     },
     "domain_mute_card": {
         "mute": "Negeren",
-        "mute_progress": "Negeren...",
+        "mute_progress": "Negeren…",
         "unmute": "Negering opheffen",
-        "unmute_progress": "Negering wordt opgeheven..."
+        "unmute_progress": "Negering wordt opgeheven…"
     },
     "exporter": {
         "export": "Exporteren",

From 790825257f01a1dabefaea382a9ab96913584b9d Mon Sep 17 00:00:00 2001
From: Fristi <fristi@subcon.town>
Date: Mon, 1 Jun 2020 16:23:44 +0000
Subject: [PATCH 450/483] Translated using Weblate (Dutch)

Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
---
 src/i18n/nl.json | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index 768babb5..243e9ab9 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -66,7 +66,7 @@
         "interactions": "Interacties"
     },
     "notifications": {
-        "broken_favorite": "Onbekende status, aan het zoeken...",
+        "broken_favorite": "Onbekende status, aan het zoeken…",
         "favorited_you": "vond je status leuk",
         "followed_you": "volgt jou",
         "load_older": "Laad oudere meldingen",
@@ -402,7 +402,7 @@
             "title": "Twee-factor Authenticatie",
             "generate_new_recovery_codes": "Genereer nieuwe herstelcodes",
             "recovery_codes": "Herstelcodes.",
-            "waiting_a_recovery_codes": "Backup codes ontvangen...",
+            "waiting_a_recovery_codes": "Backup codes ontvangen…",
             "authentication_methods": "Authenticatie methodes",
             "scan": {
                 "title": "Scannen",
@@ -526,11 +526,11 @@
         },
         "show_repeats": "Herhalingen tonen",
         "hide_repeats": "Herhalingen verbergen",
-        "mute_progress": "Negeren...",
-        "unmute_progress": "Negering opheffen...",
+        "mute_progress": "Negeren…",
+        "unmute_progress": "Negering opheffen…",
         "unmute": "Negering opheffen",
-        "block_progress": "Blokkeren...",
-        "unblock_progress": "Blokkade opheffen...",
+        "block_progress": "Blokkeren…",
+        "unblock_progress": "Blokkade opheffen…",
         "unblock": "Blokkade opheffen",
         "unsubscribe": "Abonnement opzeggen",
         "subscribe": "Abonneren",

From f5c4d7d989f54071ed298a8eab67b1e72411ae94 Mon Sep 17 00:00:00 2001
From: Evert Prants <evert@lunasqu.ee>
Date: Wed, 3 Jun 2020 06:57:53 +0000
Subject: [PATCH 451/483] Translated using Weblate (Estonian)

Currently translated at 54.3% (333 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/et/
---
 src/i18n/et.json | 487 +++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 406 insertions(+), 81 deletions(-)

diff --git a/src/i18n/et.json b/src/i18n/et.json
index 5262b2a4..ead7ae67 100644
--- a/src/i18n/et.json
+++ b/src/i18n/et.json
@@ -1,83 +1,408 @@
 {
-  "finder": {
-    "error_fetching_user": "Viga kasutaja leidmisel",
-    "find_user": "Otsi kasutajaid"
-  },
-  "general": {
-    "submit": "Postita"
-  },
-  "login": {
-    "login": "Logi sisse",
-    "logout": "Logi välja",
-    "password": "Parool",
-    "placeholder": "nt lain",
-    "register": "Registreeru",
-    "username": "Kasutajanimi"
-  },
-  "nav": {
-    "mentions": "Mainimised",
-    "public_tl": "Avalik Ajajoon",
-    "timeline": "Ajajoon",
-    "twkn": "Kogu Teadaolev Võrgustik"
-  },
-  "notifications": {
-    "followed_you": "alustas sinu jälgimist",
-    "notifications": "Teavitused",
-    "read": "Loe!"
-  },
-  "post_status": {
-    "default": "Just sõitsin elektrirongiga Tallinnast Pääskülla.",
-    "posting": "Postitan"
-  },
-  "registration": {
-    "bio": "Bio",
-    "email": "E-post",
-    "fullname": "Kuvatav nimi",
-    "password_confirm": "Parooli kinnitamine",
-    "registration": "Registreerimine"
-  },
-  "settings": {
-    "attachments": "Manused",
-    "autoload": "Luba ajajoone automaatne uuendamine kui ajajoon on põhja keritud",
-    "avatar": "Profiilipilt",
-    "bio": "Bio",
-    "current_avatar": "Sinu praegune profiilipilt",
-    "current_profile_banner": "Praegune profiilibänner",
-    "filtering": "Sisu filtreerimine",
-    "filtering_explanation": "Kõiki staatuseid, mis sisaldavad neid sõnu, ei kuvata. Üks sõna reale.",
-    "hide_attachments_in_convo": "Peida manused vastlustes",
-    "hide_attachments_in_tl": "Peida manused ajajoonel",
-    "name": "Nimi",
-    "name_bio": "Nimi ja Bio",
-    "nsfw_clickthrough": "Peida tööks-mittesobivad(NSFW) manuste hiireklõpsu taha",
-    "profile_background": "Profiilitaust",
-    "profile_banner": "Profiilibänner",
-    "reply_link_preview": "Luba algpostituse kuvamine vastustes",
-    "set_new_avatar": "Vali uus profiilipilt",
-    "set_new_profile_background": "Vali uus profiilitaust",
-    "set_new_profile_banner": "Vali uus profiilibänner",
-    "settings": "Sätted",
-    "theme": "Teema",
-    "user_settings": "Kasutaja sätted"
-  },
-  "timeline": {
-    "conversation": "Vestlus",
-    "error_fetching": "Viga uuenduste laadimisel",
-    "load_older": "Kuva vanemaid staatuseid",
-    "show_new": "Näita uusi",
-    "up_to_date": "Uuendatud"
-  },
-  "user_card": {
-    "block": "Blokeeri",
-    "blocked": "Blokeeritud!",
-    "follow": "Jälgi",
-    "followees": "Jälgitavaid",
-    "followers": "Jälgijaid",
-    "following": "Jälgin!",
-    "follows_you": "Jälgib sind!",
-    "mute": "Vaigista",
-    "muted": "Vaigistatud",
-    "per_day": "päevas",
-    "statuses": "Staatuseid"
-  }
+    "finder": {
+        "error_fetching_user": "Viga kasutaja leidmisel",
+        "find_user": "Otsi kasutajaid"
+    },
+    "general": {
+        "submit": "Postita",
+        "verify": "Kinnita",
+        "confirm": "Kinnita",
+        "enable": "Luba",
+        "disable": "Keela",
+        "cancel": "Tühista",
+        "dismiss": "Olgu",
+        "show_less": "Kuva vähem",
+        "show_more": "Kuva rohkem",
+        "optional": "valikuline",
+        "generic_error": "Esines viga",
+        "more": "Rohkem",
+        "apply": "Rakenda"
+    },
+    "login": {
+        "login": "Logi sisse",
+        "logout": "Logi välja",
+        "password": "Parool",
+        "placeholder": "nt lain",
+        "register": "Registreeru",
+        "username": "Kasutajanimi",
+        "heading": {
+            "recovery": "Kaheastmelise autentimise taaste",
+            "totp": "Kaheastmeline autentimine"
+        },
+        "recovery_code": "Taastekood",
+        "enter_two_factor_code": "Sisesta kaheastmelise autentimise kood",
+        "enter_recovery_code": "Sisesta taastekood",
+        "authentication_code": "Autentimiskood",
+        "hint": "Logi sisse, et liituda vestlusega",
+        "description": "Logi sisse OAuthiga"
+    },
+    "nav": {
+        "mentions": "Mainimised",
+        "public_tl": "Avalik Ajajoon",
+        "timeline": "Ajajoon",
+        "twkn": "Kogu Teadaolev Võrgustik",
+        "preferences": "Eelistused",
+        "who_to_follow": "Keda jälgida",
+        "search": "Otsing",
+        "user_search": "Kasutajaotsing",
+        "dms": "Privaatsõnumid",
+        "interactions": "Interaktsioonid",
+        "friend_requests": "Jägimistaotlused",
+        "chat": "Kohalik vestlus",
+        "back": "Tagasi",
+        "administration": "Administreerimine",
+        "about": "Meist"
+    },
+    "notifications": {
+        "followed_you": "alustas sinu jälgimist",
+        "notifications": "Teavitused",
+        "read": "Loe!",
+        "reacted_with": "reageeris {0}",
+        "migrated_to": "migreerus",
+        "no_more_notifications": "Rohkem teavitusi ei ole",
+        "repeated_you": "taaspostitas su staatuse",
+        "load_older": "Laadi vanemad teavitused",
+        "follow_request": "soovib Teid jälgida",
+        "favorited_you": "lisas su staatuse lemmikuks",
+        "broken_favorite": "Tundmatu staatus, otsin…"
+    },
+    "post_status": {
+        "default": "Just sõitsin elektrirongiga Tallinnast Pääskülla.",
+        "posting": "Postitan",
+        "scope": {
+            "unlisted": "Peidetud - Ära postita avalikele ajajoontele",
+            "public": "Avalil - Postita avalikele ajajoontele",
+            "private": "Jälgijatele - Postita ainult jälgijatele",
+            "direct": "Privaatne - Postita ainult mainitud kasutajatele"
+        },
+        "scope_notice": {
+            "unlisted": "See postitus ei ole nähtav avalikul ega kogu võrgu ajajoonel",
+            "private": "See postitus on nähtav ainult Teie jälgijatele",
+            "public": "See postitus on nähtav kõigile"
+        },
+        "direct_warning_to_first_only": "See postitus on nähtav ainult kirja alguses mainitud kasutajatele.",
+        "direct_warning_to_all": "See postitus on nähtav kõikidele mainitud kasutajatele.",
+        "content_warning": "Pealkiri (valikuline)",
+        "content_type": {
+            "text/bbcode": "BBCode",
+            "text/markdown": "Markdown",
+            "text/html": "HTML",
+            "text/plain": "Lihttekst"
+        },
+        "attachments_sensitive": "Märgi manused sensitiivseks",
+        "account_not_locked_warning_link": "lukus",
+        "account_not_locked_warning": "Teie konto ei ole {0}. Kõik võivad Teid jälgida, et näha Teie ainult-jälgijatele postitusi.",
+        "new_status": "Postita uus staatus"
+    },
+    "registration": {
+        "bio": "Bio",
+        "email": "E-post",
+        "fullname": "Kuvatav nimi",
+        "password_confirm": "Parooli kinnitamine",
+        "registration": "Registreerimine",
+        "validations": {
+            "password_confirmation_match": "peaks olema sama kui salasõna",
+            "password_confirmation_required": "ei saa jätta tühjaks",
+            "password_required": "ei saa jätta tühjaks",
+            "email_required": "ei saa jätta tühjaks",
+            "fullname_required": "ei saa jätta tühjaks",
+            "username_required": "ei saa jätta tühjaks"
+        },
+        "fullname_placeholder": "Näiteks Lain Iwakura",
+        "username_placeholder": "Näiteks lain",
+        "new_captcha": "Vajuta pildile, et saada uus captcha",
+        "captcha": "CAPTCHA",
+        "token": "Kutse võti"
+    },
+    "settings": {
+        "attachments": "Manused",
+        "autoload": "Luba ajajoone automaatne uuendamine kui ajajoon on põhja keritud",
+        "avatar": "Profiilipilt",
+        "bio": "Bio",
+        "current_avatar": "Sinu praegune profiilipilt",
+        "current_profile_banner": "Praegune profiilibänner",
+        "filtering": "Sisu filtreerimine",
+        "filtering_explanation": "Kõiki staatuseid, mis sisaldavad neid sõnu, ei kuvata. Üks sõna reale",
+        "hide_attachments_in_convo": "Peida manused vastlustes",
+        "hide_attachments_in_tl": "Peida manused ajajoonel",
+        "name": "Nimi",
+        "name_bio": "Nimi ja Bio",
+        "nsfw_clickthrough": "Peida tööks-mittesobivad(NSFW) manuste hiireklõpsu taha",
+        "profile_background": "Profiilitaust",
+        "profile_banner": "Profiilibänner",
+        "reply_link_preview": "Luba algpostituse kuvamine vastustes",
+        "set_new_avatar": "Vali uus profiilipilt",
+        "set_new_profile_background": "Vali uus profiilitaust",
+        "set_new_profile_banner": "Vali uus profiilibänner",
+        "settings": "Sätted",
+        "theme": "Teema",
+        "user_settings": "Kasutaja sätted",
+        "subject_line_noop": "Ära kopeeri",
+        "subject_line_mastodon": "Nagu mastodon: kopeeri nagu on",
+        "subject_line_email": "Nagu e-post: \"vs: pealkiri\"",
+        "subject_line_behavior": "Kopeeri pealkiri vastamisel",
+        "subject_input_always_show": "Alati kuva pealkirja välja",
+        "minimal_scopes_mode": "Peida postituse nähtavussätted",
+        "scope_copy": "Kopeeri nähtavussätted vastamisel (Privaatsed on alati kopeeritud)",
+        "security_tab": "Turvalisus",
+        "search_user_to_mute": "Otsi, keda soovid vaigistada",
+        "search_user_to_block": "Otsi, keda soovid blokeerida",
+        "saving_ok": "Sätted salvestatud",
+        "saving_err": "Sätete salvestamine ebaõnnestus",
+        "autohide_floating_post_button": "Automaatselt peida uue postituse nupp (mobiilil)",
+        "reply_visibility_self": "Näita ainult vastuseid, mis on suunatud mulle",
+        "reply_visibility_following": "Näita ainult vastuseid, mis on suunatud mulle või kasutajatele, keda jälgin",
+        "reply_visibility_all": "Näita kõiki vastuseid",
+        "replies_in_timeline": "Vastused ajajoonel",
+        "radii_help": "Liidese ümardamine (pikslites)",
+        "profile_tab": "Profiil",
+        "presets": "Salvestatud sätted",
+        "pause_on_unfocused": "Peata reaalajas voog kui leht pole fookuses",
+        "panelRadius": "Paneelid",
+        "revoke_token": "Keela",
+        "valid_until": "Kehtiv kuni",
+        "refresh_token": "Värskendustoken",
+        "token": "Token",
+        "oauth_tokens": "OAuth tokenid",
+        "show_moderator_badge": "Näita Moderaator silti mu profiilil",
+        "show_admin_badge": "Näita Admin silti mu profiilil",
+        "hide_followers_count_description": "Ära näita minu jälgijate arvu",
+        "hide_follows_count_description": "Ära näita minu jälgimiste arvu",
+        "hide_followers_description": "Ära näita minu jälgijaid",
+        "hide_follows_description": "Ära näita minu jälgimisi",
+        "no_mutes": "Vaigistusi pole",
+        "no_blocks": "Blokeeringuid pole",
+        "no_rich_text_description": "Muuda kõik postitused lihttekstiks",
+        "notification_visibility_emoji_reactions": "Reaktsioonid",
+        "notification_visibility_moves": "Kasutaja kolimised",
+        "notification_visibility_repeats": "Taaspostitused",
+        "notification_visibility_mentions": "Mainimised",
+        "notification_visibility_likes": "Lemmikud",
+        "notification_visibility_follows": "Jälgimised",
+        "notification_visibility": "Milliseid teateid kuvatakse",
+        "new_password": "Uus salasõna",
+        "new_email": "Uus e-post",
+        "use_contain_fit": "Näita eelvaadetes täis suuruses pilte",
+        "play_videos_in_modal": "Näita videoid eraldi raamis",
+        "mutes_tab": "Vaigistused",
+        "loop_video_silent_only": "Loop videod, millel pole heli (nt. Mastodoni \"gifid\")",
+        "loop_video": "Loop videod",
+        "lock_account_description": "Piira oma konto ainult lubatud jälgijatele",
+        "links": "Lingid",
+        "limited_availability": "Pole Teie veebilehitsejas saadaval",
+        "invalid_theme_imported": "Valitud fail ei ole Pleroma kujundus. Kujundusele muudatusi ei tehtud.",
+        "interfaceLanguage": "Liidese keel",
+        "interface": "Liides",
+        "instance_default_simple": "(vaikimisi)",
+        "instance_default": "(vaikimisi: {value})",
+        "checkboxRadius": "Märkeruudud",
+        "inputRadius": "Sisestuskastid",
+        "import_theme": "Lae sätted",
+        "import_followers_from_a_csv_file": "Impordi jälgimised csv failist",
+        "import_blocks_from_a_csv_file": "Impordi blokeeringud csv failist",
+        "hide_filtered_statuses": "Peida filtreeritud staatused",
+        "hide_user_stats": "Peida kasutaja statistika (nt. jälgijate arv)",
+        "hide_post_stats": "Peida postituse statistika (nt. lemmikute arv)",
+        "use_one_click_nsfw": "Ava NSFW manused ühe klikiga",
+        "preload_images": "Piltide eellaadimine",
+        "hide_isp": "Peida instantsipõhine paneel",
+        "max_thumbnails": "Maksimaalne lubatud eelvaadete arv postituste kohta",
+        "hide_muted_posts": "Peida vaigistatud kasutajate postitused",
+        "general": "Üldine",
+        "foreground": "Esiplaan",
+        "accent": "Rõhk",
+        "follows_imported": "Jälgimised imporditud! Nende töötlemine võtab natuke aega.",
+        "follow_import_error": "Jälgimiste importimisel tekkis viga",
+        "follow_import": "Impordi jälgimised",
+        "follow_export_button": "Ekspordi oma jälgimised csv failiks",
+        "follow_export": "Ekspordi jälgimised",
+        "export_theme": "Salvesta sätted",
+        "emoji_reactions_on_timeline": "Näita reaktsioone ajajoonel",
+        "pad_emoji": "Lisa emotikonidele tühikud ette ja järgi neid menüüst valides",
+        "avatar_size_instruction": "Profiilipildi soovitatud minimaalne suurus on 150x150 pikslit.",
+        "domain_mutes": "Domeenid",
+        "discoverable": "Luba selle konto ilmumine otsingutulemustes ning muudes teenustes",
+        "delete_account_instructions": "Konto kustutamise kinnitamiseks sisestage oma salasõna.",
+        "delete_account_error": "Teie konto kustutamisel tekkis viga. Kui see jätkub, palun võtke kontakti administraatoriga.",
+        "delete_account_description": "Jäädavalt kustuta oma andmed ja konto.",
+        "delete_account": "Kustuta konto",
+        "default_vis": "Vaikimisi nähtavus",
+        "data_import_export_tab": "Andmete import / eksport",
+        "current_password": "Praegune salasõna",
+        "confirm_new_password": "Kinnita uus salasõna",
+        "composing": "Koostamine",
+        "collapse_subject": "Peida postituste pealkirjad",
+        "changed_password": "Salasõna edukalt muudetud!",
+        "change_password_error": "Esines viga salasõna muutmisel.",
+        "change_password": "Muuda salasõna",
+        "changed_email": "E-post edukalt muudetud!",
+        "change_email_error": "Esines viga e-posti muutmisel.",
+        "change_email": "Muuda e-posti",
+        "cRed": "Punane (Tühista)",
+        "cOrange": "Oranž (Lisa lemmikuks)",
+        "cGreen": "Roheline (Taaspostita)",
+        "cBlue": "Sinine (Vasta, jälgi)",
+        "btnRadius": "Nupud",
+        "blocks_tab": "Blokeeringud",
+        "blocks_imported": "Blokeeringud imporditud! Nende töötlemine võtab natuke aega.",
+        "block_import_error": "Blokeeringute importimisel esines viga",
+        "block_import": "Blokeeringute import",
+        "block_export_button": "Ekspordi oma blokeeringud csv failiks",
+        "block_export": "Blokeeringute eksport",
+        "background": "Taust",
+        "avatarRadius": "Profiilipildid",
+        "avatarAltRadius": "Profiilipildid (Teavitused)",
+        "attachmentRadius": "Manused",
+        "allow_following_move": "Luba automaatjälgimine kui jälgitav konto kolib",
+        "mfa": {
+            "verify": {
+                "desc": "Et lubada kaheastmelist autentimist, sisestage kood oma äpist:"
+            },
+            "scan": {
+                "desc": "Kasutades oma kaheastmelise autentimise äppi, skännige see QR kood või sisestage tekstiline võti:",
+                "secret_code": "Võti",
+                "title": "Skänni"
+            },
+            "authentication_methods": "Autentimismeetodid",
+            "recovery_codes_warning": "Kirjutage need koodid üles ning hoidke need kindlas kohas. Kui Te kaotate ligipääsu oma kaheastmelise autentimise äppile ning nendele koodidele, ei ole Teil võimalik oma kontosse sisse logida.",
+            "waiting_a_recovery_codes": "Laen taastekoode…",
+            "recovery_codes": "Taastekoodid.",
+            "warning_of_generate_new_codes": "Kui Te loote uued taastekoodid, Teie vanad koodid ei tööta enam.",
+            "generate_new_recovery_codes": "Loo uued taastekoodid",
+            "title": "Kaheastmeline autentimine",
+            "confirm_and_enable": "Kinnita & luba OTP",
+            "wait_pre_setup_otp": "sean üles OTP",
+            "setup_otp": "Sea üles OTP",
+            "otp": "OTP"
+        },
+        "enter_current_password_to_confirm": "Sisetage isiku tõestamiseks oma salasõna",
+        "security": "Turvalisus",
+        "app_name": "Rakenduse nimi"
+    },
+    "timeline": {
+        "conversation": "Vestlus",
+        "error_fetching": "Viga uuenduste laadimisel",
+        "load_older": "Kuva vanemaid staatuseid",
+        "show_new": "Näita uusi",
+        "up_to_date": "Uuendatud"
+    },
+    "user_card": {
+        "block": "Blokeeri",
+        "blocked": "Blokeeritud!",
+        "follow": "Jälgi",
+        "followees": "Jälgitavaid",
+        "followers": "Jälgijaid",
+        "following": "Jälgin!",
+        "follows_you": "Jälgib sind!",
+        "mute": "Vaigista",
+        "muted": "Vaigistatud",
+        "per_day": "päevas",
+        "statuses": "Staatuseid"
+    },
+    "about": {
+        "mrf": {
+            "mrf_policies_desc": "MRF poliitikad mõjutavad selle instansi föderatsiooni käitumist.    Järgmised poliitikad on lubatud:",
+            "simple": {
+                "media_nsfw_desc": "See instants määrab nendest instantsidest postituste meedia sensitiivseks:",
+                "media_nsfw": "Meedia määratakse sensitiivseks",
+                "media_removal_desc": "See instants eemaldab meedia postitustelt nendest instantsidest:",
+                "media_removal": "Meedia eemaldamine",
+                "ftl_removal_desc": "See instants eemaldab postitused nendelt instantsidest \"Kogu teatud võrgu\" ajajoonelt:",
+                "ftl_removal": "\"Kogu teatud võrgu\" ajajoonelt eemaldamine",
+                "quarantine_desc": "See instants saadab ainult avalikke postitusi järgmistele instantsidele:",
+                "quarantine": "Karantiini",
+                "reject_desc": "See instants ei luba sõnumeid nendest instantsidest:",
+                "reject": "Keela",
+                "accept_desc": "See instants lubab sõnumeid ainult nendest instantsidest:",
+                "accept": "Luba",
+                "simple_policies": "Instansi-omased poliitikad"
+            },
+            "mrf_policies": "Lubatud MRF poliitikad",
+            "keyword": {
+                "is_replaced_by": "→",
+                "replace": "Vaheta",
+                "reject": "Lükka tagasi",
+                "ftl_removal": "\"Kogu teatud võrgu\" ajajoonelt eemaldamine",
+                "keyword_policies": "Võtmesõna poliitikad"
+            },
+            "federation": "Föderatsioon"
+        },
+        "staff": "Personal"
+    },
+    "selectable_list": {
+        "select_all": "Vali kõik"
+    },
+    "remote_user_resolver": {
+        "error": "Ei leitud.",
+        "searching_for": "Otsin",
+        "remote_user_resolver": "Kaugkasutaja leidja"
+    },
+    "interactions": {
+        "load_older": "Laadi vanemad interaktsioonid",
+        "moves": "Kasutaja kolimised",
+        "follows": "Uued jälgimised",
+        "favs_repeats": "Taaspostitused ja lemmikud"
+    },
+    "emoji": {
+        "load_all": "Laen kõik {emojiAmount} emotikoni",
+        "load_all_hint": "Laadisin esimesed {saneAmount} emotikoni, kõike laadides võib esineda probleeme jõudlusega.",
+        "unicode": "Unicode emotikonid",
+        "custom": "Kohandatud emotikonid",
+        "add_emoji": "Lisa emotikon",
+        "search_emoji": "Otsi emotikone",
+        "keep_open": "Hoia valija lahti",
+        "emoji": "Emotikonid",
+        "stickers": "Kleepsud"
+    },
+    "polls": {
+        "not_enough_options": "Liiga vähe unikaalseid valikuid hääletuses",
+        "expired": "Hääletus lõppes {0} tagasi",
+        "expires_in": "Hääletus lõppeb {0}",
+        "expiry": "Hääletuse vanus",
+        "multiple_choices": "Mitu vastust",
+        "single_choice": "Üks vastus",
+        "type": "Hääletuse tüüp",
+        "vote": "Hääleta",
+        "votes": "häält",
+        "option": "Valik",
+        "add_option": "Lisa valik",
+        "add_poll": "Lisa küsitlus"
+    },
+    "media_modal": {
+        "next": "Järgmine",
+        "previous": "Eelmine"
+    },
+    "importer": {
+        "error": "Faili importimisel tekkis viga.",
+        "success": "Import õnnestus.",
+        "submit": "Esita"
+    },
+    "image_cropper": {
+        "cancel": "Tühista",
+        "save_without_cropping": "Salvesta muudatusteta",
+        "save": "Salvesta",
+        "crop_picture": "Modifitseeri pilti"
+    },
+    "features_panel": {
+        "who_to_follow": "Keda jälgida",
+        "title": "Featuurid",
+        "text_limit": "Tekstilimiit",
+        "scope_options": "Ulatuse valikud",
+        "media_proxy": "Meedia proksi",
+        "gopher": "Gopher",
+        "chat": "Vestlus"
+    },
+    "exporter": {
+        "processing": "Töötlemine, Teilt küsitakse varsti faili allalaadimist",
+        "export": "Ekspordi"
+    },
+    "domain_mute_card": {
+        "unmute_progress": "Eemaldan vaigistuse…",
+        "unmute": "Ära vaigista",
+        "mute_progress": "Vaigistan…",
+        "mute": "Vaigista"
+    },
+    "chat": {
+        "title": "Vestlus"
+    }
 }

From 49d0a056448114c4bc9daceec699abe970707382 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
 <contact+translate.pleroma.social@hacktivis.me>
Date: Wed, 3 Jun 2020 14:29:03 +0000
Subject: [PATCH 452/483] Translated using Weblate (French)

Currently translated at 99.8% (612 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fr/
---
 src/i18n/fr.json | 28 +++++++++++++++++++++-------
 1 file changed, 21 insertions(+), 7 deletions(-)

diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 61877a3a..2bd8e91d 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -167,7 +167,7 @@
             "generate_new_recovery_codes": "Générer de nouveaux codes de récupération",
             "warning_of_generate_new_codes": "Quand vous générez de nouveauc codes de récupération, vos anciens codes ne fonctionnerons plus.",
             "recovery_codes": "Codes de récupération.",
-            "waiting_a_recovery_codes": "Récéption des codes de récupération…",
+            "waiting_a_recovery_codes": "Réception des codes de récupération…",
             "recovery_codes_warning": "Écrivez les codes ou sauvez les quelquepart sécurisé - sinon vous ne les verrez plus jamais. Si vous perdez l'accès à votre application de double authentification et codes de récupération vous serez vérouillé en dehors de votre compte.",
             "authentication_methods": "Methodes d'authentification",
             "scan": {
@@ -210,7 +210,7 @@
         "data_import_export_tab": "Import / Export des Données",
         "default_vis": "Visibilité par défaut",
         "delete_account": "Supprimer le compte",
-        "delete_account_description": "Supprimer définitivement votre compte et tous vos statuts.",
+        "delete_account_description": "Supprimer définitivement vos données et désactiver votre compte.",
         "delete_account_error": "Il y a eu un problème lors de la tentative de suppression de votre compte. Si le problème persiste, contactez l'administrateur⋅ice de cette instance.",
         "delete_account_instructions": "Indiquez votre mot de passe ci-dessous pour confirmer la suppression de votre compte.",
         "avatar_size_instruction": "La taille minimale recommandée pour l'image de l'avatar est de 150x150 pixels.",
@@ -343,7 +343,13 @@
                     "upgraded_from_v2": "PleromaFE à été mis à jour, le thème peut être un peu différent que dans vos souvenirs.",
                     "v2_imported": "Le fichier que vous avez importé vient d'un version antérieure. Nous essayons de maximizer la compatibilité mais il peu y avoir quelques incohérences.",
                     "future_version_imported": "Le fichier importé viens d'une version postérieure de PleromaFE.",
-                    "older_version_imported": "Le fichier importé viens d'une version antérieure de PleromaFE."
+                    "older_version_imported": "Le fichier importé viens d'une version antérieure de PleromaFE.",
+                    "snapshot_source_mismatch": "Conflict de version : Probablement due à un retour arrière puis remise à jour de la version de PleromaFE, si vous avez charger le thème en utilisant une version antérieure vous voulez probablement utiliser la version antérieure, autrement utiliser la version postérieure.",
+                    "migration_napshot_gone": "Pour une raison inconnue l'instantané est manquant, des parties peuvent rendre différentes que dans vos souvenirs.",
+                    "migration_snapshot_ok": "Pour être sûr un instantanée du thème à été chargé. Vos pouvez essayer de charger ses données.",
+                    "fe_downgraded": "Retour en arrière de la version de PleromaFE.",
+                    "fe_upgraded": "Le moteur de thème PleromaFE à été mis à jour après un changement de version.",
+                    "snapshot_missing": "Aucun instantané du thème à été trouvé dans le fichier, il peut y avoir un rendu différent à la vision originelle."
                 },
                 "keep_as_is": "Garder tel-quel",
                 "use_source": "Nouvelle version"
@@ -392,7 +398,10 @@
                 "selectedPost": "Message sélectionné",
                 "selectedMenu": "Objet sélectionné du menu",
                 "disabled": "Désactivé",
-                "tabs": "Onglets"
+                "tabs": "Onglets",
+                "toggled": "(Dés)activé",
+                "highlight": "Éléments mis en valeur",
+                "popover": "Infobulles, menus"
             },
             "radii": {
                 "_tab_label": "Rondeur"
@@ -482,7 +491,9 @@
         "useStreamingApi": "Recevoir les messages et notifications en temps réel",
         "notification_setting_filters": "Filtres",
         "notification_setting_privacy_option": "Masquer l'expéditeur et le contenu des notifications push",
-        "notification_setting_privacy": "Intimité"
+        "notification_setting_privacy": "Intimité",
+        "hide_followers_count_description": "Masquer le nombre d'abonnés",
+        "accent": "Accent"
     },
     "timeline": {
         "collapse": "Fermer",
@@ -666,10 +677,13 @@
         "unicode": "émoji unicode",
         "load_all": "Charger tout les {emojiAmount} émojis",
         "load_all_hint": "{saneAmount} émojis chargé, charger tout les émojis peuvent causer des problèmes de performances.",
-        "stickers": "Stickers"
+        "stickers": "Stickers",
+        "keep_open": "Garder le sélecteur ouvert"
     },
     "remote_user_resolver": {
-        "error": "Non trouvé."
+        "error": "Non trouvé.",
+        "searching_for": "Rechercher",
+        "remote_user_resolver": "Résolution de compte distant"
     },
     "time": {
         "minutes_short": "{0}min",

From 1c4be95384cc25de8c0e404599dd953ca54e2e61 Mon Sep 17 00:00:00 2001
From: Anonymous <noreply@weblate.org>
Date: Wed, 3 Jun 2020 14:30:26 +0000
Subject: [PATCH 453/483] Translated using Weblate (French)

Currently translated at 99.8% (612 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fr/
---
 src/i18n/fr.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 2bd8e91d..719bde76 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -31,7 +31,8 @@
         "disable": "Désactiver",
         "enable": "Activer",
         "confirm": "Confirmer",
-        "verify": "Vérifier"
+        "verify": "Vérifier",
+        "dismiss": "Rejeter"
     },
     "image_cropper": {
         "crop_picture": "Rogner l'image",

From 419fdd58a55889646607ba388aa7413faf9eea8b Mon Sep 17 00:00:00 2001
From: Evert Prants <evert@lunasqu.ee>
Date: Fri, 5 Jun 2020 08:05:11 +0000
Subject: [PATCH 454/483] Translated using Weblate (Estonian)

Currently translated at 61.5% (377 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/et/
---
 src/i18n/et.json | 65 +++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 59 insertions(+), 6 deletions(-)

diff --git a/src/i18n/et.json b/src/i18n/et.json
index ead7ae67..d7bc3a9d 100644
--- a/src/i18n/et.json
+++ b/src/i18n/et.json
@@ -55,13 +55,13 @@
     },
     "notifications": {
         "followed_you": "alustas sinu jälgimist",
-        "notifications": "Teavitused",
+        "notifications": "Teated",
         "read": "Loe!",
         "reacted_with": "reageeris {0}",
-        "migrated_to": "migreerus",
-        "no_more_notifications": "Rohkem teavitusi ei ole",
+        "migrated_to": "kolis",
+        "no_more_notifications": "Rohkem teateid ei ole",
         "repeated_you": "taaspostitas su staatuse",
-        "load_older": "Laadi vanemad teavitused",
+        "load_older": "Laadi vanemad teated",
         "follow_request": "soovib Teid jälgida",
         "favorited_you": "lisas su staatuse lemmikuks",
         "broken_favorite": "Tundmatu staatus, otsin…"
@@ -251,7 +251,7 @@
         "block_export": "Blokeeringute eksport",
         "background": "Taust",
         "avatarRadius": "Profiilipildid",
-        "avatarAltRadius": "Profiilipildid (Teavitused)",
+        "avatarAltRadius": "Profiilipildid (Teated)",
         "attachmentRadius": "Manused",
         "allow_following_move": "Luba automaatjälgimine kui jälgitav konto kolib",
         "mfa": {
@@ -277,7 +277,60 @@
         },
         "enter_current_password_to_confirm": "Sisetage isiku tõestamiseks oma salasõna",
         "security": "Turvalisus",
-        "app_name": "Rakenduse nimi"
+        "app_name": "Rakenduse nimi",
+        "style": {
+            "switcher": {
+                "help": {
+                    "snapshot_present": "Kujunduse eelvaade on laetud, nii et kõik väärtused on üle kirjutatud. Te saate laadida ka kujunduse päris sisu.",
+                    "older_version_imported": "Teie imporditud fail oli loodud vanemas versioonis.",
+                    "future_version_imported": "Teie imporditud fail oli loodud uuemas versioonis.",
+                    "v2_imported": "Teie imporditud fail oli vanema versiooni jaoks. Me üritame hoida ühilduvust, kuid ikkagi võib esineda erinevusi.",
+                    "upgraded_from_v2": "PleromaFE-d uuendati, teie kujundus võib välja näha natuke erinev, kui mäletate."
+                },
+                "use_source": "Uus versioon",
+                "use_snapshot": "Vana versioon",
+                "keep_as_is": "Jäta nii, nagu on",
+                "load_theme": "Lae kujundus",
+                "clear_opacity": "Tühista läbipaistvus",
+                "clear_all": "Tühista kõik",
+                "reset": "Taasta algne",
+                "keep_fonts": "Jäta fondid",
+                "keep_roundness": "Jäta ümarus",
+                "keep_opacity": "Jäta läbipaistvus",
+                "keep_shadows": "Jäta varjud",
+                "keep_color": "Jäta värvid"
+            }
+        },
+        "enable_web_push_notifications": "Luba veebipõhised push-teated",
+        "notification_blocks": "Kasutaja blokeerimisel ei tule neilt enam teateid ning nendele teilt ka mitte.",
+        "notification_setting_privacy_option": "Peida saatja ning sisu push-teadetelt",
+        "notification_setting": "Saa teateid nendelt:",
+        "notifications": "Teated",
+        "notification_mutes": "Kui soovid mõnelt kasutajalt mitte teateid saada, kasuta vaigistust.",
+        "notification_setting_privacy": "Privaatsus",
+        "notification_setting_non_followers": "Kasutajatelt, kes sind ei jälgi",
+        "notification_setting_followers": "Kasutajatelt, kes jälgivad sind",
+        "notification_setting_non_follows": "Kasutajatelt, keda sa ei jälgi",
+        "notification_setting_follows": "Kasutajatelt, keda jälgid",
+        "notification_setting_filters": "Filtrid",
+        "greentext": "Meemi nooled",
+        "fun": "Naljad",
+        "values": {
+            "true": "jah",
+            "false": "ei"
+        },
+        "upload_a_photo": "Lae üles foto",
+        "type_domains_to_mute": "Trüki siia domeene, mida vaigistada",
+        "tooltipRadius": "Vihjed/hoiatused",
+        "theme_help_v2_1": "Te saate ka mõndade komponentide värvust ning läbipaistvust üle kirjutada vajutades ruudule. Kasuta \"Tühista kõik\" nuppu, et need tühistada.",
+        "theme_help": "Kasuta hex värvikoode (#rrggbb) oma kujunduse isikupärastamiseks.",
+        "text": "Tekst",
+        "useStreamingApiWarning": "(Pole soovituslik, eksperimentaalne, on teada, et jätab postitusi vahele)",
+        "useStreamingApi": "Saa postitusi ning teateid reaalajas",
+        "user_mutes": "Kasutajad",
+        "streaming": "Luba uute postituste automaatvoog kui oled lehekülje alguses",
+        "stop_gifs": "Mängi GIFid hiirega ületades",
+        "post_status_content_type": "Postituse sisutüüp"
     },
     "timeline": {
         "conversation": "Vestlus",

From 8b43893c47f4025c0fca615ad41b7cdbfc5eddbf Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Fri, 5 Jun 2020 11:19:03 +0000
Subject: [PATCH 455/483] Translated using Weblate (Italian)

Currently translated at 65.7% (403 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index 6a87b6ad..cddcb489 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -192,7 +192,9 @@
                     "fe_downgraded": "L'interfaccia è stata portata ad una versione precedente.",
                     "fe_upgraded": "Lo schema dei temi è stato aggiornato insieme all'interfaccia.",
                     "snapshot_missing": "Il tema non è provvisto di anteprima, quindi potrebbe essere diverso da come appare.",
-                    "snapshot_present": "Tutti i valori sono sostituiti dall'anteprima del tema. Puoi invece caricare i suoi contenuti."
+                    "snapshot_present": "Tutti i valori sono sostituiti dall'anteprima del tema. Puoi invece caricare i suoi contenuti.",
+                    "snapshot_source_mismatch": "Conflitto di versione: probabilmente l'interfaccia è stata portata ad una versione precedente e poi aggiornata di nuovo. Se hai modificato il tema con una versione precedente dell'interfaccia, usa la vecchia versione del tema, altrimenti puoi usare la nuova.",
+                    "migration_napshot_gone": "Anteprima del tema non trovata, non tutto potrebbe essere come ricordi."
                 },
                 "use_source": "Nuova versione",
                 "use_snapshot": "Versione precedente",
@@ -207,6 +209,10 @@
                 "keep_opacity": "Mantieni opacità",
                 "keep_shadows": "Mantieni ombre",
                 "keep_color": "Mantieni colori"
+            },
+            "common": {
+                "opacity": "Opacità",
+                "color": "Colore"
             }
         },
         "enable_web_push_notifications": "Abilita notifiche web push",

From ebf2ce84fdd7e175f5715452400ec3bdf789310b Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sat, 6 Jun 2020 23:08:52 +0300
Subject: [PATCH 456/483] alignment fixes

---
 .../notifications/notifications.scss          |  6 +-
 src/components/status/status.vue              | 66 ++++++++++++++-----
 src/i18n/en.json                              |  4 +-
 3 files changed, 54 insertions(+), 22 deletions(-)

diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 9efcfcf8..b675af5a 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -36,6 +36,8 @@
   border-bottom: 1px solid;
   border-color: $fallback--border;
   border-color: var(--border, $fallback--border);
+  word-wrap: break-word;
+  word-break: break-word;
 
   &:hover .animated.avatar {
     canvas {
@@ -46,10 +48,6 @@
     }
   }
 
-  .muted {
-    padding: .25em .6em;
-  }
-
   .non-mention {
     display: flex;
     flex: 1;
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 3137cb9d..336f912a 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -17,7 +17,7 @@
     </div>
     <template v-if="muted && !isPreview">
       <div class="media status container muted">
-        <small>
+        <small class="username">
           <i
             v-if="muted && retweet"
             class="button-icon icon-retweet"
@@ -27,18 +27,23 @@
           </router-link>
         </small>
         <small
-          v-if="showReasonMutedThread && muteWordHits.length === 0"
-          class="muted-thread"
+          v-if="showReasonMutedThread"
+          class="mute-thread"
         >
           {{ $t('status.thread_muted') }}
         </small>
         <small
           v-if="showReasonMutedThread && muteWordHits.length > 0"
-          class="muted-Thread"
+          class="mute-thread"
         >
           {{ $t('status.thread_muted_and_words') }}
         </small>
-        <small class="mute-words">{{ muteWordHits.join(', ') }}</small>
+        <small
+          class="mute-words"
+          :title="muteWordHits.join(', ')"
+        >
+          {{ muteWordHits.join(', ') }}
+        </small>
         <a
           href="#"
           class="unmute"
@@ -653,19 +658,48 @@ $status-margin: 0.75em;
 }
 
 .muted {
-  padding: 0.25em 0.5em;
-  button {
+  padding: .25em .6em;
+  height: 1.2em;
+  line-height: 1.2em;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  display: flex;
+  flex-wrap: nowrap;
+
+  .username, .mute-thread, .mute-words {
+    word-wrap: normal;
+    word-break: normal;
+    white-space: nowrap;
+  }
+
+  .username, .mute-words {
+    text-overflow: ellipsis;
+    overflow: hidden;
+  }
+
+  .username {
+    flex: 0 1 auto;
+    margin-right: .2em;
+  }
+
+  .mute-thread {
+    flex: 0 0 auto;
+  }
+
+  .mute-words {
+    flex: 1 0 5em;
+    margin-left: .2em;
+    &::before {
+      content: ' '
+    }
+  }
+
+  .unmute {
+    flex: 0 0 auto;
+    margin-left: auto;
+    display: block;
     margin-left: auto;
   }
-
-  .muted-thread, .mute-words {
-    margin-left: 10px;
-  }
-}
-
-a.unmute {
-  display: block;
-  margin-left: auto;
 }
 
 .reply-body {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 61b818a6..5bcf074b 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -622,8 +622,8 @@
     "unmute_conversation": "Unmute conversation",
     "status_unavailable": "Status unavailable",
     "copy_link": "Copy link to status",
-    "thread_muted": "Conversation muted",
-    "thread_muted_and_words": "Conversation muted, contains filtered words:"
+    "thread_muted": "Thread muted",
+    "thread_muted_and_words": ", has words:"
   },
   "user_card": {
     "approve": "Approve",

From 7d695fc8f285357393a669d776a1fa1313b0b16d Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sat, 6 Jun 2020 23:22:58 +0300
Subject: [PATCH 457/483] changelog

---
 CHANGELOG.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a44fb163..721a1b76 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Changed
 - Removed the use of with_move parameters when fetching notifications
 
+### Fixed
+- Multiple issues with muted statuses/notifications
+
 ## [Unreleased patch]
 ### Add
 - Added private notifications option for push notifications

From 03952832b4363c651266d14bf14a20a0215e6c3c Mon Sep 17 00:00:00 2001
From: Karol Kosek <krkk@krkk.ct8.pl>
Date: Sat, 6 Jun 2020 22:43:56 +0200
Subject: [PATCH 458/483] Fix the cropped button shadow in 2FA settings

---
 src/components/user_settings/mfa.vue | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/components/user_settings/mfa.vue b/src/components/user_settings/mfa.vue
index 14ea10a1..2c1c786c 100644
--- a/src/components/user_settings/mfa.vue
+++ b/src/components/user_settings/mfa.vue
@@ -144,7 +144,6 @@
 }
 .mfa-settings {
   .mfa-heading, .method-item {
-    overflow: hidden;
     display: flex;
     flex-wrap: wrap;
     justify-content: space-between;

From 68482fd3a6870514c7e8bd0fe433f24711e97ce9 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 7 Jun 2020 00:15:10 +0300
Subject: [PATCH 459/483] move helpers out of tabs directory

---
 .../settings_modal/{tabs => }/helpers/shared_computed_object.js | 0
 src/components/settings_modal/tabs/filtering_tab.js             | 2 +-
 src/components/settings_modal/tabs/general_tab.js               | 2 +-
 3 files changed, 2 insertions(+), 2 deletions(-)
 rename src/components/settings_modal/{tabs => }/helpers/shared_computed_object.js (100%)

diff --git a/src/components/settings_modal/tabs/helpers/shared_computed_object.js b/src/components/settings_modal/helpers/shared_computed_object.js
similarity index 100%
rename from src/components/settings_modal/tabs/helpers/shared_computed_object.js
rename to src/components/settings_modal/helpers/shared_computed_object.js
diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js
index dd7ecaf7..224a7f47 100644
--- a/src/components/settings_modal/tabs/filtering_tab.js
+++ b/src/components/settings_modal/tabs/filtering_tab.js
@@ -1,7 +1,7 @@
 import { filter, trim } from 'lodash'
 import Checkbox from 'src/components/checkbox/checkbox.vue'
 
-import SharedComputedObject from './helpers/shared_computed_object.js'
+import SharedComputedObject from '../helpers/shared_computed_object.js'
 
 const FilteringTab = {
   data () {
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index 9df1da79..0eb37e44 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -1,7 +1,7 @@
 import Checkbox from 'src/components/checkbox/checkbox.vue'
 import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
 
-import SharedComputedObject from './helpers/shared_computed_object.js'
+import SharedComputedObject from '../helpers/shared_computed_object.js'
 
 const GeneralTab = {
   data () {

From 1e554ee2876c675cacc0e4f0bff59b320159432f Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 7 Jun 2020 18:19:34 +0300
Subject: [PATCH 460/483] indent 2

---
 src/i18n/ar.json |  406 ++++++-------
 src/i18n/de.json | 1156 +++++++++++++++++------------------
 src/i18n/en.json | 1478 ++++++++++++++++++++++-----------------------
 src/i18n/es.json | 1270 +++++++++++++++++++--------------------
 src/i18n/et.json |  906 ++++++++++++++--------------
 src/i18n/fi.json | 1482 ++++++++++++++++++++++-----------------------
 src/i18n/fr.json | 1480 ++++++++++++++++++++++-----------------------
 src/i18n/it.json |  964 +++++++++++++++---------------
 src/i18n/nl.json | 1488 +++++++++++++++++++++++-----------------------
 src/i18n/pl.json | 1478 ++++++++++++++++++++++-----------------------
 src/i18n/ru.json |  950 ++++++++++++++---------------
 src/i18n/te.json |  700 +++++++++++-----------
 src/i18n/zh.json | 1390 +++++++++++++++++++++----------------------
 13 files changed, 7574 insertions(+), 7574 deletions(-)

diff --git a/src/i18n/ar.json b/src/i18n/ar.json
index 72e3010f..8bba2b97 100644
--- a/src/i18n/ar.json
+++ b/src/i18n/ar.json
@@ -1,206 +1,206 @@
 {
-    "chat": {
-        "title": "الدردشة"
+  "chat": {
+    "title": "الدردشة"
+  },
+  "features_panel": {
+    "chat": "الدردشة",
+    "gopher": "غوفر",
+    "media_proxy": "بروكسي الوسائط",
+    "scope_options": "",
+    "text_limit": "الحد الأقصى للنص",
+    "title": "الميّزات",
+    "who_to_follow": "للمتابعة"
+  },
+  "finder": {
+    "error_fetching_user": "خطأ أثناء جلب صفحة المستخدم",
+    "find_user": "البحث عن مستخدِم"
+  },
+  "general": {
+    "apply": "تطبيق",
+    "submit": "إرسال"
+  },
+  "login": {
+    "login": "تسجيل الدخول",
+    "logout": "الخروج",
+    "password": "الكلمة السرية",
+    "placeholder": "مثال lain",
+    "register": "انشاء حساب",
+    "username": "إسم المستخدم"
+  },
+  "nav": {
+    "chat": "الدردشة المحلية",
+    "friend_requests": "طلبات المتابَعة",
+    "mentions": "الإشارات",
+    "public_tl": "الخيط الزمني العام",
+    "timeline": "الخيط الزمني",
+    "twkn": "كافة الشبكة المعروفة"
+  },
+  "notifications": {
+    "broken_favorite": "منشور مجهول، جارٍ البحث عنه…",
+    "favorited_you": "أعجِب بمنشورك",
+    "followed_you": "يُتابعك",
+    "load_older": "تحميل الإشعارات الأقدم",
+    "notifications": "الإخطارات",
+    "read": "مقروء!",
+    "repeated_you": "شارَك منشورك"
+  },
+  "post_status": {
+    "account_not_locked_warning": "",
+    "account_not_locked_warning_link": "مقفل",
+    "attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس",
+    "content_type": {
+      "text/plain": "نص صافٍ"
     },
-    "features_panel": {
-        "chat": "الدردشة",
-        "gopher": "غوفر",
-        "media_proxy": "بروكسي الوسائط",
-        "scope_options": "",
-        "text_limit": "الحد الأقصى للنص",
-        "title": "الميّزات",
-        "who_to_follow": "للمتابعة"
-    },
-    "finder": {
-        "error_fetching_user": "خطأ أثناء جلب صفحة المستخدم",
-        "find_user": "البحث عن مستخدِم"
-    },
-    "general": {
-        "apply": "تطبيق",
-        "submit": "إرسال"
-    },
-    "login": {
-        "login": "تسجيل الدخول",
-        "logout": "الخروج",
-        "password": "الكلمة السرية",
-        "placeholder": "مثال lain",
-        "register": "انشاء حساب",
-        "username": "إسم المستخدم"
-    },
-    "nav": {
-        "chat": "الدردشة المحلية",
-        "friend_requests": "طلبات المتابَعة",
-        "mentions": "الإشارات",
-        "public_tl": "الخيط الزمني العام",
-        "timeline": "الخيط الزمني",
-        "twkn": "كافة الشبكة المعروفة"
-    },
-    "notifications": {
-        "broken_favorite": "منشور مجهول، جارٍ البحث عنه…",
-        "favorited_you": "أعجِب بمنشورك",
-        "followed_you": "يُتابعك",
-        "load_older": "تحميل الإشعارات الأقدم",
-        "notifications": "الإخطارات",
-        "read": "مقروء!",
-        "repeated_you": "شارَك منشورك"
-    },
-    "post_status": {
-        "account_not_locked_warning": "",
-        "account_not_locked_warning_link": "مقفل",
-        "attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس",
-        "content_type": {
-            "text/plain": "نص صافٍ"
-        },
-        "content_warning": "الموضوع (اختياري)",
-        "default": "وصلت للتوّ إلى لوس أنجلس.",
-        "direct_warning": "",
-        "posting": "النشر",
-        "scope": {
-            "direct": "",
-            "private": "",
-            "public": "علني - يُنشر على الخيوط الزمنية العمومية",
-            "unlisted": "غير مُدرَج - لا يُنشَر على الخيوط الزمنية العمومية"
-        }
-    },
-    "registration": {
-        "bio": "السيرة الذاتية",
-        "email": "عنوان البريد الإلكتروني",
-        "fullname": "الإسم المعروض",
-        "password_confirm": "تأكيد الكلمة السرية",
-        "registration": "التسجيل",
-        "token": "رمز الدعوة"
-    },
-    "settings": {
-        "attachmentRadius": "المُرفَقات",
-        "attachments": "المُرفَقات",
-        "autoload": "",
-        "avatar": "الصورة الرمزية",
-        "avatarAltRadius": "الصور الرمزية (الإشعارات)",
-        "avatarRadius": "الصور الرمزية",
-        "background": "الخلفية",
-        "bio": "السيرة الذاتية",
-        "btnRadius": "الأزرار",
-        "cBlue": "أزرق (الرد، المتابَعة)",
-        "cGreen": "أخضر (إعادة النشر)",
-        "cOrange": "برتقالي (مفضلة)",
-        "cRed": "أحمر (إلغاء)",
-        "change_password": "تغيير كلمة السر",
-        "change_password_error": "وقع هناك خلل أثناء تعديل كلمتك السرية.",
-        "changed_password": "تم تغيير كلمة المرور بنجاح!",
-        "collapse_subject": "",
-        "confirm_new_password": "تأكيد كلمة السر الجديدة",
-        "current_avatar": "صورتك الرمزية الحالية",
-        "current_password": "كلمة السر الحالية",
-        "current_profile_banner": "الرأسية الحالية لصفحتك الشخصية",
-        "data_import_export_tab": "تصدير واستيراد البيانات",
-        "default_vis": "أسلوب العرض الافتراضي",
-        "delete_account": "حذف الحساب",
-        "delete_account_description": "حذف حسابك و كافة منشوراتك نهائيًا.",
-        "delete_account_error": "",
-        "delete_account_instructions": "يُرجى إدخال كلمتك السرية أدناه لتأكيد عملية حذف الحساب.",
-        "export_theme": "حفظ النموذج",
-        "filtering": "التصفية",
-        "filtering_explanation": "سيتم إخفاء كافة المنشورات التي تحتوي على هذه الكلمات، كلمة واحدة في كل سطر",
-        "follow_export": "تصدير الاشتراكات",
-        "follow_export_button": "تصدير الاشتراكات كملف csv",
-        "follow_export_processing": "التصدير جارٍ، سوف يُطلَب منك تنزيل ملفك بعد حين",
-        "follow_import": "استيراد الاشتراكات",
-        "follow_import_error": "خطأ أثناء استيراد المتابِعين",
-        "follows_imported": "",
-        "foreground": "الأمامية",
-        "general": "الإعدادات العامة",
-        "hide_attachments_in_convo": "إخفاء المرفقات على المحادثات",
-        "hide_attachments_in_tl": "إخفاء المرفقات على الخيط الزمني",
-        "hide_post_stats": "",
-        "hide_user_stats": "",
-        "import_followers_from_a_csv_file": "",
-        "import_theme": "تحميل نموذج",
-        "inputRadius": "",
-        "instance_default": "",
-        "interfaceLanguage": "لغة الواجهة",
-        "invalid_theme_imported": "",
-        "limited_availability": "غير متوفر على متصفحك",
-        "links": "الروابط",
-        "lock_account_description": "",
-        "loop_video": "",
-        "loop_video_silent_only": "",
-        "name": "الاسم",
-        "name_bio": "الاسم والسيرة الذاتية",
-        "new_password": "كلمة السر الجديدة",
-        "no_rich_text_description": "",
-        "notification_visibility": "نوع الإشعارات التي تريد عرضها",
-        "notification_visibility_follows": "يتابع",
-        "notification_visibility_likes": "الإعجابات",
-        "notification_visibility_mentions": "الإشارات",
-        "notification_visibility_repeats": "",
-        "nsfw_clickthrough": "",
-        "oauth_tokens": "رموز OAuth",
-        "token": "رمز",
-        "refresh_token": "رمز التحديث",
-        "valid_until": "صالح حتى",
-        "revoke_token": "سحب",
-        "panelRadius": "",
-        "pause_on_unfocused": "",
-        "presets": "النماذج",
-        "profile_background": "خلفية الصفحة الشخصية",
-        "profile_banner": "رأسية الصفحة الشخصية",
-        "profile_tab": "الملف الشخصي",
-        "radii_help": "",
-        "replies_in_timeline": "الردود على الخيط الزمني",
-        "reply_link_preview": "",
-        "reply_visibility_all": "عرض كافة الردود",
-        "reply_visibility_following": "",
-        "reply_visibility_self": "",
-        "saving_err": "خطأ أثناء حفظ الإعدادات",
-        "saving_ok": "تم حفظ الإعدادات",
-        "security_tab": "الأمان",
-        "set_new_avatar": "اختيار صورة رمزية جديدة",
-        "set_new_profile_background": "اختيار خلفية جديدة للملف الشخصي",
-        "set_new_profile_banner": "اختيار رأسية جديدة للصفحة الشخصية",
-        "settings": "الإعدادات",
-        "stop_gifs": "",
-        "streaming": "",
-        "text": "النص",
-        "theme": "المظهر",
-        "theme_help": "",
-        "tooltipRadius": "",
-        "user_settings": "إعدادات المستخدم",
-        "values": {
-            "false": "لا",
-            "true": "نعم"
-        }
-    },
-    "timeline": {
-        "collapse": "",
-        "conversation": "محادثة",
-        "error_fetching": "خطأ أثناء جلب التحديثات",
-        "load_older": "تحميل المنشورات القديمة",
-        "no_retweet_hint": "",
-        "repeated": "",
-        "show_new": "عرض الجديد",
-        "up_to_date": "تم تحديثه"
-    },
-    "user_card": {
-        "approve": "قبول",
-        "block": "حظر",
-        "blocked": "تم حظره!",
-        "deny": "رفض",
-        "follow": "اتبع",
-        "followees": "",
-        "followers": "مُتابِعون",
-        "following": "",
-        "follows_you": "يتابعك!",
-        "mute": "كتم",
-        "muted": "تم كتمه",
-        "per_day": "في اليوم",
-        "remote_follow": "مُتابَعة عن بُعد",
-        "statuses": "المنشورات"
-    },
-    "user_profile": {
-        "timeline_title": "الخيط الزمني للمستخدم"
-    },
-    "who_to_follow": {
-        "more": "المزيد",
-        "who_to_follow": "للمتابعة"
+    "content_warning": "الموضوع (اختياري)",
+    "default": "وصلت للتوّ إلى لوس أنجلس.",
+    "direct_warning": "",
+    "posting": "النشر",
+    "scope": {
+      "direct": "",
+      "private": "",
+      "public": "علني - يُنشر على الخيوط الزمنية العمومية",
+      "unlisted": "غير مُدرَج - لا يُنشَر على الخيوط الزمنية العمومية"
     }
-}
\ No newline at end of file
+  },
+  "registration": {
+    "bio": "السيرة الذاتية",
+    "email": "عنوان البريد الإلكتروني",
+    "fullname": "الإسم المعروض",
+    "password_confirm": "تأكيد الكلمة السرية",
+    "registration": "التسجيل",
+    "token": "رمز الدعوة"
+  },
+  "settings": {
+    "attachmentRadius": "المُرفَقات",
+    "attachments": "المُرفَقات",
+    "autoload": "",
+    "avatar": "الصورة الرمزية",
+    "avatarAltRadius": "الصور الرمزية (الإشعارات)",
+    "avatarRadius": "الصور الرمزية",
+    "background": "الخلفية",
+    "bio": "السيرة الذاتية",
+    "btnRadius": "الأزرار",
+    "cBlue": "أزرق (الرد، المتابَعة)",
+    "cGreen": "أخضر (إعادة النشر)",
+    "cOrange": "برتقالي (مفضلة)",
+    "cRed": "أحمر (إلغاء)",
+    "change_password": "تغيير كلمة السر",
+    "change_password_error": "وقع هناك خلل أثناء تعديل كلمتك السرية.",
+    "changed_password": "تم تغيير كلمة المرور بنجاح!",
+    "collapse_subject": "",
+    "confirm_new_password": "تأكيد كلمة السر الجديدة",
+    "current_avatar": "صورتك الرمزية الحالية",
+    "current_password": "كلمة السر الحالية",
+    "current_profile_banner": "الرأسية الحالية لصفحتك الشخصية",
+    "data_import_export_tab": "تصدير واستيراد البيانات",
+    "default_vis": "أسلوب العرض الافتراضي",
+    "delete_account": "حذف الحساب",
+    "delete_account_description": "حذف حسابك و كافة منشوراتك نهائيًا.",
+    "delete_account_error": "",
+    "delete_account_instructions": "يُرجى إدخال كلمتك السرية أدناه لتأكيد عملية حذف الحساب.",
+    "export_theme": "حفظ النموذج",
+    "filtering": "التصفية",
+    "filtering_explanation": "سيتم إخفاء كافة المنشورات التي تحتوي على هذه الكلمات، كلمة واحدة في كل سطر",
+    "follow_export": "تصدير الاشتراكات",
+    "follow_export_button": "تصدير الاشتراكات كملف csv",
+    "follow_export_processing": "التصدير جارٍ، سوف يُطلَب منك تنزيل ملفك بعد حين",
+    "follow_import": "استيراد الاشتراكات",
+    "follow_import_error": "خطأ أثناء استيراد المتابِعين",
+    "follows_imported": "",
+    "foreground": "الأمامية",
+    "general": "الإعدادات العامة",
+    "hide_attachments_in_convo": "إخفاء المرفقات على المحادثات",
+    "hide_attachments_in_tl": "إخفاء المرفقات على الخيط الزمني",
+    "hide_post_stats": "",
+    "hide_user_stats": "",
+    "import_followers_from_a_csv_file": "",
+    "import_theme": "تحميل نموذج",
+    "inputRadius": "",
+    "instance_default": "",
+    "interfaceLanguage": "لغة الواجهة",
+    "invalid_theme_imported": "",
+    "limited_availability": "غير متوفر على متصفحك",
+    "links": "الروابط",
+    "lock_account_description": "",
+    "loop_video": "",
+    "loop_video_silent_only": "",
+    "name": "الاسم",
+    "name_bio": "الاسم والسيرة الذاتية",
+    "new_password": "كلمة السر الجديدة",
+    "no_rich_text_description": "",
+    "notification_visibility": "نوع الإشعارات التي تريد عرضها",
+    "notification_visibility_follows": "يتابع",
+    "notification_visibility_likes": "الإعجابات",
+    "notification_visibility_mentions": "الإشارات",
+    "notification_visibility_repeats": "",
+    "nsfw_clickthrough": "",
+    "oauth_tokens": "رموز OAuth",
+    "token": "رمز",
+    "refresh_token": "رمز التحديث",
+    "valid_until": "صالح حتى",
+    "revoke_token": "سحب",
+    "panelRadius": "",
+    "pause_on_unfocused": "",
+    "presets": "النماذج",
+    "profile_background": "خلفية الصفحة الشخصية",
+    "profile_banner": "رأسية الصفحة الشخصية",
+    "profile_tab": "الملف الشخصي",
+    "radii_help": "",
+    "replies_in_timeline": "الردود على الخيط الزمني",
+    "reply_link_preview": "",
+    "reply_visibility_all": "عرض كافة الردود",
+    "reply_visibility_following": "",
+    "reply_visibility_self": "",
+    "saving_err": "خطأ أثناء حفظ الإعدادات",
+    "saving_ok": "تم حفظ الإعدادات",
+    "security_tab": "الأمان",
+    "set_new_avatar": "اختيار صورة رمزية جديدة",
+    "set_new_profile_background": "اختيار خلفية جديدة للملف الشخصي",
+    "set_new_profile_banner": "اختيار رأسية جديدة للصفحة الشخصية",
+    "settings": "الإعدادات",
+    "stop_gifs": "",
+    "streaming": "",
+    "text": "النص",
+    "theme": "المظهر",
+    "theme_help": "",
+    "tooltipRadius": "",
+    "user_settings": "إعدادات المستخدم",
+    "values": {
+      "false": "لا",
+      "true": "نعم"
+    }
+  },
+  "timeline": {
+    "collapse": "",
+    "conversation": "محادثة",
+    "error_fetching": "خطأ أثناء جلب التحديثات",
+    "load_older": "تحميل المنشورات القديمة",
+    "no_retweet_hint": "",
+    "repeated": "",
+    "show_new": "عرض الجديد",
+    "up_to_date": "تم تحديثه"
+  },
+  "user_card": {
+    "approve": "قبول",
+    "block": "حظر",
+    "blocked": "تم حظره!",
+    "deny": "رفض",
+    "follow": "اتبع",
+    "followees": "",
+    "followers": "مُتابِعون",
+    "following": "",
+    "follows_you": "يتابعك!",
+    "mute": "كتم",
+    "muted": "تم كتمه",
+    "per_day": "في اليوم",
+    "remote_follow": "مُتابَعة عن بُعد",
+    "statuses": "المنشورات"
+  },
+  "user_profile": {
+    "timeline_title": "الخيط الزمني للمستخدم"
+  },
+  "who_to_follow": {
+    "more": "المزيد",
+    "who_to_follow": "للمتابعة"
+  }
+}
diff --git a/src/i18n/de.json b/src/i18n/de.json
index d3eedcb6..a44e58cb 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -1,584 +1,584 @@
 {
-    "chat": {
-        "title": "Chat"
+  "chat": {
+    "title": "Chat"
+  },
+  "features_panel": {
+    "chat": "Chat",
+    "gopher": "Gopher",
+    "media_proxy": "Medienproxy",
+    "scope_options": "Reichweitenoptionen",
+    "text_limit": "Zeichenlimit",
+    "title": "Funktionen",
+    "who_to_follow": "Wem folgen?"
+  },
+  "finder": {
+    "error_fetching_user": "Fehler beim Suchen des Benutzers",
+    "find_user": "Finde Benutzer"
+  },
+  "general": {
+    "apply": "Anwenden",
+    "submit": "Absenden",
+    "more": "Mehr",
+    "generic_error": "Ein Fehler ist aufgetreten",
+    "optional": "Optional",
+    "show_more": "Zeige mehr",
+    "show_less": "Zeige weniger",
+    "dismiss": "Ablehnen",
+    "cancel": "Abbrechen",
+    "disable": "Deaktivieren",
+    "enable": "Aktivieren",
+    "confirm": "Bestätigen",
+    "verify": "Verifizieren"
+  },
+  "login": {
+    "login": "Anmelden",
+    "description": "Mit OAuth anmelden",
+    "logout": "Abmelden",
+    "password": "Passwort",
+    "placeholder": "z.B. lain",
+    "register": "Registrieren",
+    "username": "Benutzername",
+    "authentication_code": "Authentifizierungscode",
+    "enter_recovery_code": "Gebe einen Wiederherstellungscode ein",
+    "recovery_code": "Wiederherstellungscode",
+    "heading": {
+      "totp": "Zwei-Faktor Authentifizierung",
+      "recovery": "Zwei-Faktor Wiederherstellung"
     },
-    "features_panel": {
-        "chat": "Chat",
-        "gopher": "Gopher",
-        "media_proxy": "Medienproxy",
-        "scope_options": "Reichweitenoptionen",
-        "text_limit": "Zeichenlimit",
-        "title": "Funktionen",
-        "who_to_follow": "Wem folgen?"
+    "hint": "Anmelden um an der Diskussion teilzunehmen",
+    "enter_two_factor_code": "Gebe einen Zwei-Faktor-Code ein"
+  },
+  "nav": {
+    "about": "Über",
+    "back": "Zurück",
+    "chat": "Lokaler Chat",
+    "friend_requests": "Followanfragen",
+    "mentions": "Erwähnungen",
+    "interactions": "Interaktionen",
+    "dms": "Direktnachrichten",
+    "public_tl": "Öffentliche Zeitleiste",
+    "timeline": "Zeitleiste",
+    "twkn": "Das gesamte bekannte Netzwerk",
+    "user_search": "Benutzersuche",
+    "search": "Suche",
+    "preferences": "Voreinstellungen",
+    "administration": "Administration",
+    "who_to_follow": "Wem folgen"
+  },
+  "notifications": {
+    "broken_favorite": "Unbekannte Nachricht, suche danach...",
+    "favorited_you": "favorisierte deine Nachricht",
+    "followed_you": "folgt dir",
+    "load_older": "Ältere Benachrichtigungen laden",
+    "notifications": "Benachrichtigungen",
+    "read": "Gelesen!",
+    "repeated_you": "wiederholte deine Nachricht",
+    "follow_request": "möchte dir folgen",
+    "migrated_to": "migrierte zu",
+    "reacted_with": "reagierte mit {0}",
+    "no_more_notifications": "Keine Benachrichtigungen mehr"
+  },
+  "post_status": {
+    "new_status": "Neuen Status veröffentlichen",
+    "account_not_locked_warning": "Dein Profil ist nicht {0}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.",
+    "account_not_locked_warning_link": "gesperrt",
+    "attachments_sensitive": "Anhänge als heikel markieren",
+    "content_type": {
+      "text/plain": "Nur Text",
+      "text/bbcode": "BBCode",
+      "text/markdown": "Markdown",
+      "text/html": "HTML"
     },
-    "finder": {
-        "error_fetching_user": "Fehler beim Suchen des Benutzers",
-        "find_user": "Finde Benutzer"
+    "content_warning": "Betreff (optional)",
+    "default": "Sitze gerade im Hofbräuhaus.",
+    "direct_warning": "Dieser Beitrag wird nur für die erwähnten Nutzer sichtbar sein.",
+    "posting": "Veröffentlichen",
+    "scope": {
+      "direct": "Direkt - Beitrag nur an erwähnte Profile",
+      "private": "Nur Follower - Beitrag nur für Follower sichtbar",
+      "public": "Öffentlich - Beitrag an öffentliche Zeitleisten",
+      "unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
     },
-    "general": {
-        "apply": "Anwenden",
-        "submit": "Absenden",
-        "more": "Mehr",
-        "generic_error": "Ein Fehler ist aufgetreten",
-        "optional": "Optional",
-        "show_more": "Zeige mehr",
-        "show_less": "Zeige weniger",
-        "dismiss": "Ablehnen",
-        "cancel": "Abbrechen",
-        "disable": "Deaktivieren",
-        "enable": "Aktivieren",
-        "confirm": "Bestätigen",
-        "verify": "Verifizieren"
-    },
-    "login": {
-        "login": "Anmelden",
-        "description": "Mit OAuth anmelden",
-        "logout": "Abmelden",
-        "password": "Passwort",
-        "placeholder": "z.B. lain",
-        "register": "Registrieren",
-        "username": "Benutzername",
-        "authentication_code": "Authentifizierungscode",
-        "enter_recovery_code": "Gebe einen Wiederherstellungscode ein",
-        "recovery_code": "Wiederherstellungscode",
-        "heading": {
-            "totp": "Zwei-Faktor Authentifizierung",
-            "recovery": "Zwei-Faktor Wiederherstellung"
-        },
-        "hint": "Anmelden um an der Diskussion teilzunehmen",
-        "enter_two_factor_code": "Gebe einen Zwei-Faktor-Code ein"
-    },
-    "nav": {
-        "about": "Über",
-        "back": "Zurück",
-        "chat": "Lokaler Chat",
-        "friend_requests": "Followanfragen",
-        "mentions": "Erwähnungen",
-        "interactions": "Interaktionen",
-        "dms": "Direktnachrichten",
-        "public_tl": "Öffentliche Zeitleiste",
-        "timeline": "Zeitleiste",
-        "twkn": "Das gesamte bekannte Netzwerk",
-        "user_search": "Benutzersuche",
-        "search": "Suche",
-        "preferences": "Voreinstellungen",
-        "administration": "Administration",
-        "who_to_follow": "Wem folgen"
-    },
-    "notifications": {
-        "broken_favorite": "Unbekannte Nachricht, suche danach...",
-        "favorited_you": "favorisierte deine Nachricht",
-        "followed_you": "folgt dir",
-        "load_older": "Ältere Benachrichtigungen laden",
-        "notifications": "Benachrichtigungen",
-        "read": "Gelesen!",
-        "repeated_you": "wiederholte deine Nachricht",
-        "follow_request": "möchte dir folgen",
-        "migrated_to": "migrierte zu",
-        "reacted_with": "reagierte mit {0}",
-        "no_more_notifications": "Keine Benachrichtigungen mehr"
-    },
-    "post_status": {
-        "new_status": "Neuen Status veröffentlichen",
-        "account_not_locked_warning": "Dein Profil ist nicht {0}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.",
-        "account_not_locked_warning_link": "gesperrt",
-        "attachments_sensitive": "Anhänge als heikel markieren",
-        "content_type": {
-            "text/plain": "Nur Text",
-            "text/bbcode": "BBCode",
-            "text/markdown": "Markdown",
-            "text/html": "HTML"
-        },
-        "content_warning": "Betreff (optional)",
-        "default": "Sitze gerade im Hofbräuhaus.",
-        "direct_warning": "Dieser Beitrag wird nur für die erwähnten Nutzer sichtbar sein.",
-        "posting": "Veröffentlichen",
-        "scope": {
-            "direct": "Direkt - Beitrag nur an erwähnte Profile",
-            "private": "Nur Follower - Beitrag nur für Follower sichtbar",
-            "public": "Öffentlich - Beitrag an öffentliche Zeitleisten",
-            "unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
-        },
-        "direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein.",
-        "direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.",
-        "scope_notice": {
-            "public": "Dieser Beitrag wird für alle sichtbar sein",
-            "private": "Dieser Beitrag wird nur für deine Follower sichtbar sein",
-            "unlisted": "Dieser Beitrag wird weder in der öffentlichen Zeitleiste noch im gesamten bekannten Netzwerk sichtbar sein"
-        }
-    },
-    "registration": {
-        "bio": "Bio",
-        "email": "Email",
-        "fullname": "Angezeigter Name",
-        "password_confirm": "Passwort bestätigen",
-        "registration": "Registrierung",
-        "token": "Einladungsschlüssel",
-        "captcha": "CAPTCHA",
-        "new_captcha": "Zum Erstellen eines neuen Captcha auf das Bild klicken.",
-        "validations": {
-            "username_required": "darf nicht leer sein",
-            "fullname_required": "darf nicht leer sein",
-            "email_required": "darf nicht leer sein",
-            "password_required": "darf nicht leer sein",
-            "password_confirmation_required": "darf nicht leer sein",
-            "password_confirmation_match": "sollte mit dem Passwort identisch sein"
-        },
-        "bio_placeholder": "z.B.\nHallo, ich bin Lain.\nIch bin ein Anime Mödchen aus dem vorstädtischen Japan. Du kennst mich vielleicht vom Wired.",
-        "fullname_placeholder": "z.B. Lain Iwakura",
-        "username_placeholder": "z.B. lain"
-    },
-    "settings": {
-        "attachmentRadius": "Anhänge",
-        "attachments": "Anhänge",
-        "autoload": "Aktiviere automatisches Laden von älteren Beiträgen beim scrollen",
-        "avatar": "Avatar",
-        "avatarAltRadius": "Avatare (Benachrichtigungen)",
-        "avatarRadius": "Avatare",
-        "background": "Hintergrund",
-        "bio": "Bio",
-        "btnRadius": "Buttons",
-        "cBlue": "Blau (Antworten, folgt dir)",
-        "cGreen": "Grün (Retweet)",
-        "cOrange": "Orange (Favorisieren)",
-        "cRed": "Rot (Abbrechen)",
-        "change_password": "Passwort ändern",
-        "change_password_error": "Es gab ein Problem bei der Änderung des Passworts.",
-        "changed_password": "Passwort erfolgreich geändert!",
-        "collapse_subject": "Beiträge mit Betreff einklappen",
-        "composing": "Verfassen",
-        "confirm_new_password": "Neues Passwort bestätigen",
-        "current_avatar": "Dein derzeitiger Avatar",
-        "current_password": "Aktuelles Passwort",
-        "current_profile_banner": "Der derzeitige Banner deines Profils",
-        "data_import_export_tab": "Datenimport/-export",
-        "default_vis": "Standard-Sichtbarkeitsumfang",
-        "delete_account": "Account löschen",
-        "delete_account_description": "Lösche deine Daten und deaktiviere deinen Account unwiderruflich.",
-        "delete_account_error": "Es ist ein Fehler beim Löschen deines Accounts aufgetreten. Tritt dies weiterhin auf, wende dich an den Administrator der Instanz.",
-        "delete_account_instructions": "Tippe dein Passwort unten in das Feld ein, um die Löschung deines Accounts zu bestätigen.",
-        "discoverable": "Erlaube, dass dieser Account in Suchergebnissen auftaucht",
-        "avatar_size_instruction": "Die empfohlene minimale Größe für Avatare ist 150x150 Pixel.",
-        "pad_emoji": "Emojis mit Leerzeichen umrahmen",
-        "export_theme": "Farbschema speichern",
-        "filtering": "Filtern",
-        "filtering_explanation": "Alle Beiträge, welche diese Wörter enthalten, werden ausgeblendet. Ein Wort pro Zeile.",
-        "follow_export": "Follower exportieren",
-        "follow_export_button": "Exportiere deine Follows in eine csv-Datei",
-        "follow_export_processing": "In Bearbeitung. Die Liste steht gleich zum herunterladen bereit.",
-        "follow_import": "Follower importieren",
-        "follow_import_error": "Fehler beim Importieren der Follower",
-        "follows_imported": "Follower importiert! Die Bearbeitung kann einen Moment dauern.",
-        "foreground": "Vordergrund",
-        "general": "Allgemein",
-        "hide_attachments_in_convo": "Anhänge in Unterhaltungen ausblenden",
-        "hide_attachments_in_tl": "Anhänge in der Zeitleiste ausblenden",
-        "hide_muted_posts": "Verberge Beiträge stummgeschalteter Nutzer",
-        "max_thumbnails": "Maximale Anzahl von Vorschaubildern pro Beitrag",
-        "hide_isp": "Instanz-spezifisches Panel ausblenden",
-        "preload_images": "Bilder vorausladen",
-        "use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen",
-        "hide_post_stats": "Beitragsstatistiken verbergen (z.B. die Anzahl der Favoriten)",
-        "hide_user_stats": "Benutzerstatistiken verbergen (z.B. die Anzahl der Follower)",
-        "hide_filtered_statuses": "Gefilterte Beiträge verbergen",
-        "import_followers_from_a_csv_file": "Importiere Follower aus einer CSV-Datei",
-        "import_theme": "Farbschema laden",
-        "inputRadius": "Eingabefelder",
-        "checkboxRadius": "Auswahlfelder",
-        "instance_default": "(Standard: {value})",
-        "instance_default_simple": "(Standard)",
-        "interface": "Oberfläche",
-        "interfaceLanguage": "Sprache der Oberfläche",
-        "invalid_theme_imported": "Die ausgewählte Datei ist kein unterstütztes Pleroma-Theme. Keine Änderungen wurden vorgenommen.",
-        "limited_availability": "In deinem Browser nicht verfügbar",
-        "links": "Links",
-        "lock_account_description": "Sperre deinen Account, um neue Follower zu genehmigen oder abzulehnen",
-        "loop_video": "Videos wiederholen",
-        "loop_video_silent_only": "Nur Videos ohne Ton wiederholen (z.B. Mastodons \"gifs\")",
-        "mutes_tab": "Stummschaltungen",
-        "play_videos_in_modal": "Videos in größerem Medienfenster abspielen",
-        "use_contain_fit": "Vorschaubilder nicht zuschneiden",
-        "name": "Name",
-        "name_bio": "Name & Bio",
-        "new_password": "Neues Passwort",
-        "notification_visibility": "Benachrichtigungstypen, die angezeigt werden sollen",
-        "notification_visibility_follows": "Follows",
-        "notification_visibility_likes": "Favoriten",
-        "notification_visibility_mentions": "Erwähnungen",
-        "notification_visibility_repeats": "Wiederholungen",
-        "no_rich_text_description": "Rich-Text Formatierungen von allen Beiträgen entfernen",
-        "hide_follows_description": "Zeige nicht, wem ich folge",
-        "hide_followers_description": "Zeige nicht, wer mir folgt",
-        "hide_follows_count_description": "Verberge die Anzahl deiner Gefolgten",
-        "hide_followers_count_description": "Verberge die Anzahl deiner Folgenden",
-        "nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind",
-        "oauth_tokens": "OAuth-Token",
-        "token": "Zeichen",
-        "refresh_token": "Token aktualisieren",
-        "valid_until": "Gültig bis",
-        "revoke_token": "Widerrufen",
-        "panelRadius": "Panel",
-        "pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist",
-        "presets": "Voreinstellungen",
-        "profile_background": "Profilhintergrund",
-        "profile_banner": "Profilbanner",
-        "profile_tab": "Profil",
-        "radii_help": "Kantenrundung (in Pixel) der Oberfläche anpassen",
-        "replies_in_timeline": "Antworten in der Zeitleiste",
-        "reply_link_preview": "Antwortlink-Vorschau beim Überfahren mit der Maus aktivieren",
-        "reply_visibility_all": "Alle Antworten zeigen",
-        "reply_visibility_following": "Zeige nur Antworten an mich oder an Benutzer, denen ich folge",
-        "reply_visibility_self": "Nur Antworten an mich anzeigen",
-        "autohide_floating_post_button": "Automatisches Verbergen des Knopfs für neue Beiträge (mobil)",
-        "saving_err": "Fehler beim Speichern der Einstellungen",
-        "saving_ok": "Einstellungen gespeichert",
-        "security_tab": "Sicherheit",
-        "scope_copy": "Reichweite beim Antworten übernehmen (Direktnachrichten werden immer kopiert)",
-        "minimal_scopes_mode": "Minimiere Reichweitenoptionen",
-        "set_new_avatar": "Setze einen neuen Avatar",
-        "set_new_profile_background": "Setze einen neuen Hintergrund für dein Profil",
-        "set_new_profile_banner": "Setze einen neuen Banner für dein Profil",
-        "settings": "Einstellungen",
-        "subject_input_always_show": "Betreff-Feld immer anzeigen",
-        "subject_line_behavior": "Betreff beim Antworten kopieren",
-        "subject_line_email": "Wie Email: \"re: Betreff\"",
-        "subject_line_mastodon": "Wie Mastodon: unverändert kopieren",
-        "subject_line_noop": "Nicht kopieren",
-        "post_status_content_type": "Beitragsart",
-        "stop_gifs": "Animationen nur beim Darüberfahren abspielen",
-        "streaming": "Aktiviere automatisches Laden (Streaming) von neuen Beiträgen",
-        "text": "Text",
-        "theme": "Farbschema",
-        "theme_help": "Benutze HTML-Farbcodes (#rrggbb) um dein Farbschema anzupassen",
-        "theme_help_v2_1": "Du kannst auch die Farben und die Deckkraft bestimmter Komponenten überschreiben, indem du das Kontrollkästchen umschaltest. Verwende die Schaltfläche \"Alle löschen\", um alle Überschreibungen zurückzusetzen.",
-        "theme_help_v2_2": "Unter einigen Einträgen befinden sich Symbole für Hintergrund-/Textkontrastindikatoren, für detaillierte Informationen fahre mit der Maus darüber. Bitte beachte, dass bei der Verwendung von Transparenz Kontrastindikatoren den schlechtest möglichen Fall darstellen.",
-        "tooltipRadius": "Tooltips/Warnungen",
-        "user_settings": "Benutzereinstellungen",
-        "values": {
-            "false": "nein",
-            "true": "Ja"
-        },
-        "notifications": "Benachrichtigungen",
-        "enable_web_push_notifications": "Web-Pushbenachrichtigungen aktivieren",
-        "style": {
-            "switcher": {
-                "keep_color": "Farben beibehalten",
-                "keep_shadows": "Schatten beibehalten",
-                "keep_opacity": "Deckkraft beibehalten",
-                "keep_roundness": "Abrundungen beibehalten",
-                "keep_fonts": "Schriften beibehalten",
-                "save_load_hint": "Die \"Beibehalten\"-Optionen behalten die aktuell eingestellten Optionen beim Auswählen oder Laden von Designs bei, sie speichern diese Optionen auch beim Exportieren eines Designs. Wenn alle Kontrollkästchen deaktiviert sind, wird beim Exportieren des Designs alles gespeichert.",
-                "reset": "Zurücksetzen",
-                "clear_all": "Alles leeren",
-                "clear_opacity": "Deckkraft leeren"
-            },
-            "common": {
-                "color": "Farbe",
-                "opacity": "Deckkraft",
-                "contrast": {
-                    "hint": "Das Kontrastverhältnis ist {ratio}, es {level} {context}",
-                    "level": {
-                        "aa": "entspricht Level AA Richtlinie (minimum)",
-                        "aaa": "entspricht Level AAA Richtlinie (empfohlen)",
-                        "bad": "entspricht keiner Richtlinien zur Barrierefreiheit"
-                    },
-                    "context": {
-                        "18pt": "für großen (18pt+) Text",
-                        "text": "für Text"
-                    }
-                }
-            },
-            "common_colors": {
-                "_tab_label": "Allgemein",
-                "main": "Allgemeine Farben",
-                "foreground_hint": "Siehe Reiter \"Erweitert\" für eine detailliertere Einstellungen",
-                "rgbo": "Symbole, Betonungen, Kennzeichnungen"
-            },
-            "advanced_colors": {
-                "_tab_label": "Erweitert",
-                "alert": "Warnhinweis-Hintergrund",
-                "alert_error": "Fehler",
-                "badge": "Kennzeichnungs-Hintergrund",
-                "badge_notification": "Benachrichtigung",
-                "panel_header": "Panel-Kopf",
-                "top_bar": "Obere Leiste",
-                "borders": "Rahmen",
-                "buttons": "Schaltflächen",
-                "inputs": "Eingabefelder",
-                "faint_text": "Verblasster Text"
-            },
-            "radii": {
-                "_tab_label": "Abrundungen"
-            },
-            "shadows": {
-                "_tab_label": "Schatten und Beleuchtung",
-                "component": "Komponente",
-                "override": "Überschreiben",
-                "shadow_id": "Schatten #{value}",
-                "blur": "Unschärfe",
-                "spread": "Streuung",
-                "inset": "Einsatz",
-                "hint": "Für Schatten kannst du auch --variable als Farbwert verwenden, um CSS3-Variablen zu verwenden. Bitte beachte, dass die Einstellung der Deckkraft in diesem Fall nicht funktioniert.",
-                "filter_hint": {
-                    "always_drop_shadow": "Achtung, dieser Schatten verwendet immer {0}, wenn der Browser dies unterstützt.",
-                    "drop_shadow_syntax": "{0} unterstützt Parameter {1} und Schlüsselwort {2} nicht.",
-                    "avatar_inset": "Bitte beachte, dass die Kombination von eingesetzten und nicht eingesetzten Schatten auf Avataren zu unerwarteten Ergebnissen bei transparenten Avataren führen kann.",
-                    "spread_zero": "Schatten mit einer Streuung > 0 erscheinen so, als ob sie auf Null gesetzt wären.",
-                    "inset_classic": "Eingesetzte Schatten werden mit {0} verwendet"
-                },
-                "components": {
-                    "panel": "Panel",
-                    "panelHeader": "Panel-Kopf",
-                    "topBar": "Obere Leiste",
-                    "avatar": "Benutzer-Avatar (in der Profilansicht)",
-                    "avatarStatus": "Benutzer-Avatar (in der Beitragsanzeige)",
-                    "popup": "Dialogfenster und Hinweistexte",
-                    "button": "Schaltfläche",
-                    "buttonHover": "Schaltfläche (hover)",
-                    "buttonPressed": "Schaltfläche (gedrückt)",
-                    "buttonPressedHover": "Schaltfläche (gedrückt+hover)",
-                    "input": "Input field"
-                }
-            },
-            "fonts": {
-                "_tab_label": "Schriften",
-                "help": "Wähl die Schriftart, die für Elemente der Benutzeroberfläche verwendet werden soll. Für \" Benutzerdefiniert\" musst du den genauen Schriftnamen eingeben, wie er im System angezeigt wird.",
-                "components": {
-                    "interface": "Oberfläche",
-                    "input": "Eingabefelder",
-                    "post": "Beitragstext",
-                    "postCode": "Dicktengleicher Text in einem Beitrag (Rich-Text)"
-                },
-                "family": "Schriftname",
-                "size": "Größe (in px)",
-                "weight": "Gewicht (Dicke)",
-                "custom": "Benutzerdefiniert"
-            },
-            "preview": {
-                "header": "Vorschau",
-                "content": "Inhalt",
-                "error": "Beispielfehler",
-                "button": "Schaltfläche",
-                "text": "Ein Haufen mehr von {0} und {1}",
-                "mono": "Inhalt",
-                "input": "Sitze gerade im Hofbräuhaus.",
-                "faint_link": "Hilfreiche Anleitung",
-                "fine_print": "Lies unser {0}, um nichts Nützliches zu lernen!",
-                "header_faint": "Das ist in Ordnung",
-                "checkbox": "Ich habe die Allgemeinen Geschäftsbedingungen überflogen",
-                "link": "ein netter kleiner Link"
-            }
-        },
-        "app_name": "Anwendungsname",
-        "mfa": {
-            "otp": "OTP",
-            "recovery_codes_warning": "Schreibe dir die Codes auf oder speichere sie an einem sicheren Ort - ansonsten wirst du sie nicht wiederfinden. Wenn du den Zugriff zu deiner 2FA App und die Wiederherstellungs-Codes verlierst, wirst du aus deinem Account ausgeschlossen sein.",
-            "recovery_codes": "Wiederherstellungs-Codes.",
-            "warning_of_generate_new_codes": "Wenn du neue Wiederherstellungs-Codes generierst, werden die alten Codes nicht mehr funktionieren.",
-            "generate_new_recovery_codes": "Generiere neue Wiederherstellungs-Codes",
-            "title": "Zwei-Faktor Authentifizierung",
-            "waiting_a_recovery_codes": "Erhalte Wiederherstellungscodes...",
-            "authentication_methods": "Authentifizierungsmethoden",
-            "scan": {
-                "title": "Scan",
-                "secret_code": "Schlüssel",
-                "desc": "Wenn du deine 2FA App verwendest, scanne diesen QR Code oder gebe den Schlüssel ein:"
-            },
-            "verify": {
-                "desc": "Um 2FA zu aktivieren, gib den Code von deiner 2FA-App ein:"
-            }
-        },
-        "enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen",
-        "security": "Sicherheit",
-        "allow_following_move": "Erlaube automatisches Folgen, sobald ein gefolgter Nutzer umzieht",
-        "blocks_imported": "Blocks importiert! Die Verarbeitung wird einen Moment brauchen.",
-        "block_import_error": "Fehler beim Importieren der Blocks",
-        "block_import": "Block Import",
-        "block_export_button": "Exportiere deine Blocks in eine csv Datei",
-        "block_export": "Block Export",
-        "emoji_reactions_on_timeline": "Zeige Emoji-Reaktionen auf der Zeitleiste",
-        "domain_mutes": "Domains",
-        "changed_email": "Email Adresse erfolgreich geändert!",
-        "change_email_error": "Es trat ein Problem auf beim Versuch, deine Email Adresse zu ändern.",
-        "change_email": "Ändere Email",
-        "notification_setting_non_followers": "Nutzer, die dir nicht folgen",
-        "notification_setting_followers": "Nutzer, die dir folgen",
-        "import_blocks_from_a_csv_file": "Importiere Blocks von einer CSV Datei",
-        "accent": "Akzent"
-    },
-    "timeline": {
-        "collapse": "Einklappen",
-        "conversation": "Unterhaltung",
-        "error_fetching": "Fehler beim Laden",
-        "load_older": "Lade ältere Beiträge",
-        "no_retweet_hint": "Der Beitrag ist als nur-für-Follower oder als Direktnachricht markiert und kann nicht wiederholt werden.",
-        "repeated": "wiederholte",
-        "show_new": "Zeige Neuere",
-        "up_to_date": "Aktuell"
-    },
-    "user_card": {
-        "approve": "Genehmigen",
-        "block": "Blockieren",
-        "blocked": "Blockiert!",
-        "deny": "Ablehnen",
-        "follow": "Folgen",
-        "follow_sent": "Anfrage gesendet!",
-        "follow_progress": "Anfragen…",
-        "follow_again": "Anfrage erneut senden?",
-        "follow_unfollow": "Folgen beenden",
-        "followees": "Folgt",
-        "followers": "Folgende",
-        "following": "Folgst du!",
-        "follows_you": "Folgt dir!",
-        "its_you": "Das bist du!",
-        "mute": "Stummschalten",
-        "muted": "Stummgeschaltet",
-        "per_day": "pro Tag",
-        "remote_follow": "Folgen",
-        "statuses": "Beiträge",
-        "admin_menu": {
-            "sandbox": "Erzwinge Beiträge nur für Follower sichtbar zu sein"
-        }
-    },
-    "user_profile": {
-        "timeline_title": "Beiträge"
-    },
-    "who_to_follow": {
-        "more": "Mehr",
-        "who_to_follow": "Wem soll ich folgen"
-    },
-    "tool_tip": {
-        "media_upload": "Medien hochladen",
-        "repeat": "Wiederholen",
-        "reply": "Antworten",
-        "favorite": "Favorisieren",
-        "user_settings": "Benutzereinstellungen"
-    },
-    "upload": {
-        "error": {
-            "base": "Hochladen fehlgeschlagen.",
-            "file_too_big": "Datei ist zu groß [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-            "default": "Bitte versuche es später erneut"
-        },
-        "file_size_units": {
-            "B": "B",
-            "KiB": "KiB",
-            "MiB": "MiB",
-            "GiB": "GiB",
-            "TiB": "TiB"
-        }
-    },
-    "search": {
-        "people": "Leute",
-        "hashtags": "Hashtags",
-        "person_talking": "{count} Person spricht darüber",
-        "people_talking": "{count} Leute sprechen darüber",
-        "no_results": "Keine Ergebnisse"
-    },
-    "password_reset": {
-        "forgot_password": "Passwort vergessen?",
-        "password_reset": "Password zurücksetzen",
-        "instruction": "Wenn du hier deinen Benutznamen oder die zugehörige E-Mail-Adresse eingibst, kann dir der Server einen Link zum Passwortzurücksetzen zuschicken.",
-        "placeholder": "Dein Benutzername oder die zugehörige E-Mail-Adresse",
-        "check_email": "Im E-Mail-Posteingang des angebenen Kontos müsste sich jetzt (oder zumindest in Kürze) die E-Mail mit dem Link zum Passwortzurücksetzen befinden.",
-        "return_home": "Zurück zur Heimseite",
-        "not_found": "Benutzername/E-Mail-Adresse nicht gefunden. Vertippt?",
-        "too_many_requests": "Kurze Pause. Zu viele Versuche. Bitte, später nochmal probieren.",
-        "password_reset_disabled": "Passwortzurücksetzen deaktiviert. Bitte Administrator kontaktieren.",
-        "password_reset_required": "Passwortzurücksetzen erforderlich",
-        "password_reset_required_but_mailer_is_disabled": "Passwortzurücksetzen wäre erforderlich, ist aber deaktiviert. Bitte Administrator kontaktieren."
-    },
-    "about": {
-        "mrf": {
-            "federation": "Föderation",
-            "mrf_policies": "Aktivierte MRF Richtlinien",
-            "simple": {
-                "simple_policies": "Instanzspezifische Richtlinien",
-                "accept": "Akzeptieren",
-                "reject": "Ablehnen",
-                "reject_desc": "Diese Instanz akzeptiert keine Nachrichten der folgenden Instanzen:",
-                "quarantine": "Quarantäne",
-                "ftl_removal": "Von der Zeitleiste \"Das gesamte bekannte Netzwerk\" entfernen",
-                "media_removal": "Medienentfernung",
-                "media_removal_desc": "Diese Instanz entfernt Medien von den Beiträgen der folgenden Instanzen:",
-                "media_nsfw": "Erzwingen Medien als heikel zu makieren",
-                "media_nsfw_desc": "Diese Instanz makiert die Medien in Beiträgen der folgenden Instanzen als heikel:",
-                "accept_desc": "Diese Instanz akzeptiert nur Nachrichten von den folgenden Instanzen:",
-                "quarantine_desc": "Diese Instanz sendet nur öffentliche Beiträge zu den folgenden Instanzen:",
-                "ftl_removal_desc": "Dieser Instanz entfernt folgende Instanzen von der \"Das gesamte bekannte Netzwerk\" Zeitleiste:"
-            },
-            "keyword": {
-                "keyword_policies": "Keyword Richtlinien",
-                "reject": "Ablehnen",
-                "replace": "Ersetzen",
-                "is_replaced_by": "→",
-                "ftl_removal": "Von der Zeitleiste \"Das gesamte bekannte Netzwerk\" entfernen"
-            },
-            "mrf_policies_desc": "MRF Richtlinien manipulieren das Föderationsverhalten dieser Instanz. Die folgenden Richtlinien sind aktiv:"
-        },
-        "staff": "Mitarbeiter"
-    },
-    "domain_mute_card": {
-        "mute": "Stummschalten",
-        "mute_progress": "Wird stummgeschaltet..",
-        "unmute": "Stummschaltung aufheben",
-        "unmute_progress": "Stummschaltung wird aufgehoben.."
-    },
-    "exporter": {
-        "export": "Exportieren",
-        "processing": "Verarbeitung läuft, bald wird Du dazu aufgefordert, deine Datei herunterzuladen"
-    },
-    "image_cropper": {
-        "crop_picture": "Bild zuschneiden",
-        "save": "Speichern",
-        "cancel": "Abbrechen",
-        "save_without_cropping": "Ohne Zuschneiden speichern"
-    },
-    "importer": {
-        "submit": "Absenden",
-        "success": "Erfolgreich importiert.",
-        "error": "Ein Fehler ist beim Verabeiten der Datei aufgetreten."
-    },
-    "media_modal": {
-        "previous": "Zurück",
-        "next": "Weiter"
-    },
-    "polls": {
-        "add_poll": "Umfrage hinzufügen",
-        "add_option": "Option hinzufügen",
-        "option": "Option",
-        "votes": "Stimmen",
-        "vote": "Abstimmen",
-        "type": "Umfragetyp",
-        "multiple_choices": "Mehrere Auswahlmöglichkeiten",
-        "single_choice": "Eine Auswahlmöglichkeit",
-        "expiry": "Alter der Umfrage",
-        "expired": "Die Umfrage endete vor {0}",
-        "not_enough_options": "Zu wenig einzigartige Auswahlmöglichkeiten in der Umfrage",
-        "expires_in": "Die Umfrage endet in {0}"
-    },
-    "emoji": {
-        "stickers": "Sticker",
-        "emoji": "Emoji",
-        "search_emoji": "Nach einem Emoji suchen",
-        "custom": "Benutzerdefinierter Emoji",
-        "keep_open": "Auswahlfenster offen halten",
-        "add_emoji": "Emoji einfügen",
-        "load_all": "Lade alle {emojiAmount} Emoji",
-        "load_all_hint": "Erfolgreich erste {saneAmount} Emoji geladen, alle Emojis zu laden würde Leistungsprobleme hervorrufen.",
-        "unicode": "Unicode Emoji"
-    },
-    "interactions": {
-        "load_older": "Lade ältere Interaktionen",
-        "follows": "Neue Follows",
-        "favs_repeats": "Wiederholungen und Favoriten",
-        "moves": "Benutzer migriert zu"
-    },
-    "selectable_list": {
-        "select_all": "Wähle alle"
-    },
-    "remote_user_resolver": {
-        "searching_for": "Suche nach",
-        "error": "Nicht gefunden."
+    "direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein.",
+    "direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.",
+    "scope_notice": {
+      "public": "Dieser Beitrag wird für alle sichtbar sein",
+      "private": "Dieser Beitrag wird nur für deine Follower sichtbar sein",
+      "unlisted": "Dieser Beitrag wird weder in der öffentlichen Zeitleiste noch im gesamten bekannten Netzwerk sichtbar sein"
     }
+  },
+  "registration": {
+    "bio": "Bio",
+    "email": "Email",
+    "fullname": "Angezeigter Name",
+    "password_confirm": "Passwort bestätigen",
+    "registration": "Registrierung",
+    "token": "Einladungsschlüssel",
+    "captcha": "CAPTCHA",
+    "new_captcha": "Zum Erstellen eines neuen Captcha auf das Bild klicken.",
+    "validations": {
+      "username_required": "darf nicht leer sein",
+      "fullname_required": "darf nicht leer sein",
+      "email_required": "darf nicht leer sein",
+      "password_required": "darf nicht leer sein",
+      "password_confirmation_required": "darf nicht leer sein",
+      "password_confirmation_match": "sollte mit dem Passwort identisch sein"
+    },
+    "bio_placeholder": "z.B.\nHallo, ich bin Lain.\nIch bin ein Anime Mödchen aus dem vorstädtischen Japan. Du kennst mich vielleicht vom Wired.",
+    "fullname_placeholder": "z.B. Lain Iwakura",
+    "username_placeholder": "z.B. lain"
+  },
+  "settings": {
+    "attachmentRadius": "Anhänge",
+    "attachments": "Anhänge",
+    "autoload": "Aktiviere automatisches Laden von älteren Beiträgen beim scrollen",
+    "avatar": "Avatar",
+    "avatarAltRadius": "Avatare (Benachrichtigungen)",
+    "avatarRadius": "Avatare",
+    "background": "Hintergrund",
+    "bio": "Bio",
+    "btnRadius": "Buttons",
+    "cBlue": "Blau (Antworten, folgt dir)",
+    "cGreen": "Grün (Retweet)",
+    "cOrange": "Orange (Favorisieren)",
+    "cRed": "Rot (Abbrechen)",
+    "change_password": "Passwort ändern",
+    "change_password_error": "Es gab ein Problem bei der Änderung des Passworts.",
+    "changed_password": "Passwort erfolgreich geändert!",
+    "collapse_subject": "Beiträge mit Betreff einklappen",
+    "composing": "Verfassen",
+    "confirm_new_password": "Neues Passwort bestätigen",
+    "current_avatar": "Dein derzeitiger Avatar",
+    "current_password": "Aktuelles Passwort",
+    "current_profile_banner": "Der derzeitige Banner deines Profils",
+    "data_import_export_tab": "Datenimport/-export",
+    "default_vis": "Standard-Sichtbarkeitsumfang",
+    "delete_account": "Account löschen",
+    "delete_account_description": "Lösche deine Daten und deaktiviere deinen Account unwiderruflich.",
+    "delete_account_error": "Es ist ein Fehler beim Löschen deines Accounts aufgetreten. Tritt dies weiterhin auf, wende dich an den Administrator der Instanz.",
+    "delete_account_instructions": "Tippe dein Passwort unten in das Feld ein, um die Löschung deines Accounts zu bestätigen.",
+    "discoverable": "Erlaube, dass dieser Account in Suchergebnissen auftaucht",
+    "avatar_size_instruction": "Die empfohlene minimale Größe für Avatare ist 150x150 Pixel.",
+    "pad_emoji": "Emojis mit Leerzeichen umrahmen",
+    "export_theme": "Farbschema speichern",
+    "filtering": "Filtern",
+    "filtering_explanation": "Alle Beiträge, welche diese Wörter enthalten, werden ausgeblendet. Ein Wort pro Zeile.",
+    "follow_export": "Follower exportieren",
+    "follow_export_button": "Exportiere deine Follows in eine csv-Datei",
+    "follow_export_processing": "In Bearbeitung. Die Liste steht gleich zum herunterladen bereit.",
+    "follow_import": "Follower importieren",
+    "follow_import_error": "Fehler beim Importieren der Follower",
+    "follows_imported": "Follower importiert! Die Bearbeitung kann einen Moment dauern.",
+    "foreground": "Vordergrund",
+    "general": "Allgemein",
+    "hide_attachments_in_convo": "Anhänge in Unterhaltungen ausblenden",
+    "hide_attachments_in_tl": "Anhänge in der Zeitleiste ausblenden",
+    "hide_muted_posts": "Verberge Beiträge stummgeschalteter Nutzer",
+    "max_thumbnails": "Maximale Anzahl von Vorschaubildern pro Beitrag",
+    "hide_isp": "Instanz-spezifisches Panel ausblenden",
+    "preload_images": "Bilder vorausladen",
+    "use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen",
+    "hide_post_stats": "Beitragsstatistiken verbergen (z.B. die Anzahl der Favoriten)",
+    "hide_user_stats": "Benutzerstatistiken verbergen (z.B. die Anzahl der Follower)",
+    "hide_filtered_statuses": "Gefilterte Beiträge verbergen",
+    "import_followers_from_a_csv_file": "Importiere Follower aus einer CSV-Datei",
+    "import_theme": "Farbschema laden",
+    "inputRadius": "Eingabefelder",
+    "checkboxRadius": "Auswahlfelder",
+    "instance_default": "(Standard: {value})",
+    "instance_default_simple": "(Standard)",
+    "interface": "Oberfläche",
+    "interfaceLanguage": "Sprache der Oberfläche",
+    "invalid_theme_imported": "Die ausgewählte Datei ist kein unterstütztes Pleroma-Theme. Keine Änderungen wurden vorgenommen.",
+    "limited_availability": "In deinem Browser nicht verfügbar",
+    "links": "Links",
+    "lock_account_description": "Sperre deinen Account, um neue Follower zu genehmigen oder abzulehnen",
+    "loop_video": "Videos wiederholen",
+    "loop_video_silent_only": "Nur Videos ohne Ton wiederholen (z.B. Mastodons \"gifs\")",
+    "mutes_tab": "Stummschaltungen",
+    "play_videos_in_modal": "Videos in größerem Medienfenster abspielen",
+    "use_contain_fit": "Vorschaubilder nicht zuschneiden",
+    "name": "Name",
+    "name_bio": "Name & Bio",
+    "new_password": "Neues Passwort",
+    "notification_visibility": "Benachrichtigungstypen, die angezeigt werden sollen",
+    "notification_visibility_follows": "Follows",
+    "notification_visibility_likes": "Favoriten",
+    "notification_visibility_mentions": "Erwähnungen",
+    "notification_visibility_repeats": "Wiederholungen",
+    "no_rich_text_description": "Rich-Text Formatierungen von allen Beiträgen entfernen",
+    "hide_follows_description": "Zeige nicht, wem ich folge",
+    "hide_followers_description": "Zeige nicht, wer mir folgt",
+    "hide_follows_count_description": "Verberge die Anzahl deiner Gefolgten",
+    "hide_followers_count_description": "Verberge die Anzahl deiner Folgenden",
+    "nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind",
+    "oauth_tokens": "OAuth-Token",
+    "token": "Zeichen",
+    "refresh_token": "Token aktualisieren",
+    "valid_until": "Gültig bis",
+    "revoke_token": "Widerrufen",
+    "panelRadius": "Panel",
+    "pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist",
+    "presets": "Voreinstellungen",
+    "profile_background": "Profilhintergrund",
+    "profile_banner": "Profilbanner",
+    "profile_tab": "Profil",
+    "radii_help": "Kantenrundung (in Pixel) der Oberfläche anpassen",
+    "replies_in_timeline": "Antworten in der Zeitleiste",
+    "reply_link_preview": "Antwortlink-Vorschau beim Überfahren mit der Maus aktivieren",
+    "reply_visibility_all": "Alle Antworten zeigen",
+    "reply_visibility_following": "Zeige nur Antworten an mich oder an Benutzer, denen ich folge",
+    "reply_visibility_self": "Nur Antworten an mich anzeigen",
+    "autohide_floating_post_button": "Automatisches Verbergen des Knopfs für neue Beiträge (mobil)",
+    "saving_err": "Fehler beim Speichern der Einstellungen",
+    "saving_ok": "Einstellungen gespeichert",
+    "security_tab": "Sicherheit",
+    "scope_copy": "Reichweite beim Antworten übernehmen (Direktnachrichten werden immer kopiert)",
+    "minimal_scopes_mode": "Minimiere Reichweitenoptionen",
+    "set_new_avatar": "Setze einen neuen Avatar",
+    "set_new_profile_background": "Setze einen neuen Hintergrund für dein Profil",
+    "set_new_profile_banner": "Setze einen neuen Banner für dein Profil",
+    "settings": "Einstellungen",
+    "subject_input_always_show": "Betreff-Feld immer anzeigen",
+    "subject_line_behavior": "Betreff beim Antworten kopieren",
+    "subject_line_email": "Wie Email: \"re: Betreff\"",
+    "subject_line_mastodon": "Wie Mastodon: unverändert kopieren",
+    "subject_line_noop": "Nicht kopieren",
+    "post_status_content_type": "Beitragsart",
+    "stop_gifs": "Animationen nur beim Darüberfahren abspielen",
+    "streaming": "Aktiviere automatisches Laden (Streaming) von neuen Beiträgen",
+    "text": "Text",
+    "theme": "Farbschema",
+    "theme_help": "Benutze HTML-Farbcodes (#rrggbb) um dein Farbschema anzupassen",
+    "theme_help_v2_1": "Du kannst auch die Farben und die Deckkraft bestimmter Komponenten überschreiben, indem du das Kontrollkästchen umschaltest. Verwende die Schaltfläche \"Alle löschen\", um alle Überschreibungen zurückzusetzen.",
+    "theme_help_v2_2": "Unter einigen Einträgen befinden sich Symbole für Hintergrund-/Textkontrastindikatoren, für detaillierte Informationen fahre mit der Maus darüber. Bitte beachte, dass bei der Verwendung von Transparenz Kontrastindikatoren den schlechtest möglichen Fall darstellen.",
+    "tooltipRadius": "Tooltips/Warnungen",
+    "user_settings": "Benutzereinstellungen",
+    "values": {
+      "false": "nein",
+      "true": "Ja"
+    },
+    "notifications": "Benachrichtigungen",
+    "enable_web_push_notifications": "Web-Pushbenachrichtigungen aktivieren",
+    "style": {
+      "switcher": {
+        "keep_color": "Farben beibehalten",
+        "keep_shadows": "Schatten beibehalten",
+        "keep_opacity": "Deckkraft beibehalten",
+        "keep_roundness": "Abrundungen beibehalten",
+        "keep_fonts": "Schriften beibehalten",
+        "save_load_hint": "Die \"Beibehalten\"-Optionen behalten die aktuell eingestellten Optionen beim Auswählen oder Laden von Designs bei, sie speichern diese Optionen auch beim Exportieren eines Designs. Wenn alle Kontrollkästchen deaktiviert sind, wird beim Exportieren des Designs alles gespeichert.",
+        "reset": "Zurücksetzen",
+        "clear_all": "Alles leeren",
+        "clear_opacity": "Deckkraft leeren"
+      },
+      "common": {
+        "color": "Farbe",
+        "opacity": "Deckkraft",
+        "contrast": {
+          "hint": "Das Kontrastverhältnis ist {ratio}, es {level} {context}",
+          "level": {
+            "aa": "entspricht Level AA Richtlinie (minimum)",
+            "aaa": "entspricht Level AAA Richtlinie (empfohlen)",
+            "bad": "entspricht keiner Richtlinien zur Barrierefreiheit"
+          },
+          "context": {
+            "18pt": "für großen (18pt+) Text",
+            "text": "für Text"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "Allgemein",
+        "main": "Allgemeine Farben",
+        "foreground_hint": "Siehe Reiter \"Erweitert\" für eine detailliertere Einstellungen",
+        "rgbo": "Symbole, Betonungen, Kennzeichnungen"
+      },
+      "advanced_colors": {
+        "_tab_label": "Erweitert",
+        "alert": "Warnhinweis-Hintergrund",
+        "alert_error": "Fehler",
+        "badge": "Kennzeichnungs-Hintergrund",
+        "badge_notification": "Benachrichtigung",
+        "panel_header": "Panel-Kopf",
+        "top_bar": "Obere Leiste",
+        "borders": "Rahmen",
+        "buttons": "Schaltflächen",
+        "inputs": "Eingabefelder",
+        "faint_text": "Verblasster Text"
+      },
+      "radii": {
+        "_tab_label": "Abrundungen"
+      },
+      "shadows": {
+        "_tab_label": "Schatten und Beleuchtung",
+        "component": "Komponente",
+        "override": "Überschreiben",
+        "shadow_id": "Schatten #{value}",
+        "blur": "Unschärfe",
+        "spread": "Streuung",
+        "inset": "Einsatz",
+        "hint": "Für Schatten kannst du auch --variable als Farbwert verwenden, um CSS3-Variablen zu verwenden. Bitte beachte, dass die Einstellung der Deckkraft in diesem Fall nicht funktioniert.",
+        "filter_hint": {
+          "always_drop_shadow": "Achtung, dieser Schatten verwendet immer {0}, wenn der Browser dies unterstützt.",
+          "drop_shadow_syntax": "{0} unterstützt Parameter {1} und Schlüsselwort {2} nicht.",
+          "avatar_inset": "Bitte beachte, dass die Kombination von eingesetzten und nicht eingesetzten Schatten auf Avataren zu unerwarteten Ergebnissen bei transparenten Avataren führen kann.",
+          "spread_zero": "Schatten mit einer Streuung > 0 erscheinen so, als ob sie auf Null gesetzt wären.",
+          "inset_classic": "Eingesetzte Schatten werden mit {0} verwendet"
+        },
+        "components": {
+          "panel": "Panel",
+          "panelHeader": "Panel-Kopf",
+          "topBar": "Obere Leiste",
+          "avatar": "Benutzer-Avatar (in der Profilansicht)",
+          "avatarStatus": "Benutzer-Avatar (in der Beitragsanzeige)",
+          "popup": "Dialogfenster und Hinweistexte",
+          "button": "Schaltfläche",
+          "buttonHover": "Schaltfläche (hover)",
+          "buttonPressed": "Schaltfläche (gedrückt)",
+          "buttonPressedHover": "Schaltfläche (gedrückt+hover)",
+          "input": "Input field"
+        }
+      },
+      "fonts": {
+        "_tab_label": "Schriften",
+        "help": "Wähl die Schriftart, die für Elemente der Benutzeroberfläche verwendet werden soll. Für \" Benutzerdefiniert\" musst du den genauen Schriftnamen eingeben, wie er im System angezeigt wird.",
+        "components": {
+          "interface": "Oberfläche",
+          "input": "Eingabefelder",
+          "post": "Beitragstext",
+          "postCode": "Dicktengleicher Text in einem Beitrag (Rich-Text)"
+        },
+        "family": "Schriftname",
+        "size": "Größe (in px)",
+        "weight": "Gewicht (Dicke)",
+        "custom": "Benutzerdefiniert"
+      },
+      "preview": {
+        "header": "Vorschau",
+        "content": "Inhalt",
+        "error": "Beispielfehler",
+        "button": "Schaltfläche",
+        "text": "Ein Haufen mehr von {0} und {1}",
+        "mono": "Inhalt",
+        "input": "Sitze gerade im Hofbräuhaus.",
+        "faint_link": "Hilfreiche Anleitung",
+        "fine_print": "Lies unser {0}, um nichts Nützliches zu lernen!",
+        "header_faint": "Das ist in Ordnung",
+        "checkbox": "Ich habe die Allgemeinen Geschäftsbedingungen überflogen",
+        "link": "ein netter kleiner Link"
+      }
+    },
+    "app_name": "Anwendungsname",
+    "mfa": {
+      "otp": "OTP",
+      "recovery_codes_warning": "Schreibe dir die Codes auf oder speichere sie an einem sicheren Ort - ansonsten wirst du sie nicht wiederfinden. Wenn du den Zugriff zu deiner 2FA App und die Wiederherstellungs-Codes verlierst, wirst du aus deinem Account ausgeschlossen sein.",
+      "recovery_codes": "Wiederherstellungs-Codes.",
+      "warning_of_generate_new_codes": "Wenn du neue Wiederherstellungs-Codes generierst, werden die alten Codes nicht mehr funktionieren.",
+      "generate_new_recovery_codes": "Generiere neue Wiederherstellungs-Codes",
+      "title": "Zwei-Faktor Authentifizierung",
+      "waiting_a_recovery_codes": "Erhalte Wiederherstellungscodes...",
+      "authentication_methods": "Authentifizierungsmethoden",
+      "scan": {
+        "title": "Scan",
+        "secret_code": "Schlüssel",
+        "desc": "Wenn du deine 2FA App verwendest, scanne diesen QR Code oder gebe den Schlüssel ein:"
+      },
+      "verify": {
+        "desc": "Um 2FA zu aktivieren, gib den Code von deiner 2FA-App ein:"
+      }
+    },
+    "enter_current_password_to_confirm": "Gib dein aktuelles Passwort ein, um deine Identität zu bestätigen",
+    "security": "Sicherheit",
+    "allow_following_move": "Erlaube automatisches Folgen, sobald ein gefolgter Nutzer umzieht",
+    "blocks_imported": "Blocks importiert! Die Verarbeitung wird einen Moment brauchen.",
+    "block_import_error": "Fehler beim Importieren der Blocks",
+    "block_import": "Block Import",
+    "block_export_button": "Exportiere deine Blocks in eine csv Datei",
+    "block_export": "Block Export",
+    "emoji_reactions_on_timeline": "Zeige Emoji-Reaktionen auf der Zeitleiste",
+    "domain_mutes": "Domains",
+    "changed_email": "Email Adresse erfolgreich geändert!",
+    "change_email_error": "Es trat ein Problem auf beim Versuch, deine Email Adresse zu ändern.",
+    "change_email": "Ändere Email",
+    "notification_setting_non_followers": "Nutzer, die dir nicht folgen",
+    "notification_setting_followers": "Nutzer, die dir folgen",
+    "import_blocks_from_a_csv_file": "Importiere Blocks von einer CSV Datei",
+    "accent": "Akzent"
+  },
+  "timeline": {
+    "collapse": "Einklappen",
+    "conversation": "Unterhaltung",
+    "error_fetching": "Fehler beim Laden",
+    "load_older": "Lade ältere Beiträge",
+    "no_retweet_hint": "Der Beitrag ist als nur-für-Follower oder als Direktnachricht markiert und kann nicht wiederholt werden.",
+    "repeated": "wiederholte",
+    "show_new": "Zeige Neuere",
+    "up_to_date": "Aktuell"
+  },
+  "user_card": {
+    "approve": "Genehmigen",
+    "block": "Blockieren",
+    "blocked": "Blockiert!",
+    "deny": "Ablehnen",
+    "follow": "Folgen",
+    "follow_sent": "Anfrage gesendet!",
+    "follow_progress": "Anfragen…",
+    "follow_again": "Anfrage erneut senden?",
+    "follow_unfollow": "Folgen beenden",
+    "followees": "Folgt",
+    "followers": "Folgende",
+    "following": "Folgst du!",
+    "follows_you": "Folgt dir!",
+    "its_you": "Das bist du!",
+    "mute": "Stummschalten",
+    "muted": "Stummgeschaltet",
+    "per_day": "pro Tag",
+    "remote_follow": "Folgen",
+    "statuses": "Beiträge",
+    "admin_menu": {
+      "sandbox": "Erzwinge Beiträge nur für Follower sichtbar zu sein"
+    }
+  },
+  "user_profile": {
+    "timeline_title": "Beiträge"
+  },
+  "who_to_follow": {
+    "more": "Mehr",
+    "who_to_follow": "Wem soll ich folgen"
+  },
+  "tool_tip": {
+    "media_upload": "Medien hochladen",
+    "repeat": "Wiederholen",
+    "reply": "Antworten",
+    "favorite": "Favorisieren",
+    "user_settings": "Benutzereinstellungen"
+  },
+  "upload": {
+    "error": {
+      "base": "Hochladen fehlgeschlagen.",
+      "file_too_big": "Datei ist zu groß [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+      "default": "Bitte versuche es später erneut"
+    },
+    "file_size_units": {
+      "B": "B",
+      "KiB": "KiB",
+      "MiB": "MiB",
+      "GiB": "GiB",
+      "TiB": "TiB"
+    }
+  },
+  "search": {
+    "people": "Leute",
+    "hashtags": "Hashtags",
+    "person_talking": "{count} Person spricht darüber",
+    "people_talking": "{count} Leute sprechen darüber",
+    "no_results": "Keine Ergebnisse"
+  },
+  "password_reset": {
+    "forgot_password": "Passwort vergessen?",
+    "password_reset": "Password zurücksetzen",
+    "instruction": "Wenn du hier deinen Benutznamen oder die zugehörige E-Mail-Adresse eingibst, kann dir der Server einen Link zum Passwortzurücksetzen zuschicken.",
+    "placeholder": "Dein Benutzername oder die zugehörige E-Mail-Adresse",
+    "check_email": "Im E-Mail-Posteingang des angebenen Kontos müsste sich jetzt (oder zumindest in Kürze) die E-Mail mit dem Link zum Passwortzurücksetzen befinden.",
+    "return_home": "Zurück zur Heimseite",
+    "not_found": "Benutzername/E-Mail-Adresse nicht gefunden. Vertippt?",
+    "too_many_requests": "Kurze Pause. Zu viele Versuche. Bitte, später nochmal probieren.",
+    "password_reset_disabled": "Passwortzurücksetzen deaktiviert. Bitte Administrator kontaktieren.",
+    "password_reset_required": "Passwortzurücksetzen erforderlich",
+    "password_reset_required_but_mailer_is_disabled": "Passwortzurücksetzen wäre erforderlich, ist aber deaktiviert. Bitte Administrator kontaktieren."
+  },
+  "about": {
+    "mrf": {
+      "federation": "Föderation",
+      "mrf_policies": "Aktivierte MRF Richtlinien",
+      "simple": {
+        "simple_policies": "Instanzspezifische Richtlinien",
+        "accept": "Akzeptieren",
+        "reject": "Ablehnen",
+        "reject_desc": "Diese Instanz akzeptiert keine Nachrichten der folgenden Instanzen:",
+        "quarantine": "Quarantäne",
+        "ftl_removal": "Von der Zeitleiste \"Das gesamte bekannte Netzwerk\" entfernen",
+        "media_removal": "Medienentfernung",
+        "media_removal_desc": "Diese Instanz entfernt Medien von den Beiträgen der folgenden Instanzen:",
+        "media_nsfw": "Erzwingen Medien als heikel zu makieren",
+        "media_nsfw_desc": "Diese Instanz makiert die Medien in Beiträgen der folgenden Instanzen als heikel:",
+        "accept_desc": "Diese Instanz akzeptiert nur Nachrichten von den folgenden Instanzen:",
+        "quarantine_desc": "Diese Instanz sendet nur öffentliche Beiträge zu den folgenden Instanzen:",
+        "ftl_removal_desc": "Dieser Instanz entfernt folgende Instanzen von der \"Das gesamte bekannte Netzwerk\" Zeitleiste:"
+      },
+      "keyword": {
+        "keyword_policies": "Keyword Richtlinien",
+        "reject": "Ablehnen",
+        "replace": "Ersetzen",
+        "is_replaced_by": "→",
+        "ftl_removal": "Von der Zeitleiste \"Das gesamte bekannte Netzwerk\" entfernen"
+      },
+      "mrf_policies_desc": "MRF Richtlinien manipulieren das Föderationsverhalten dieser Instanz. Die folgenden Richtlinien sind aktiv:"
+    },
+    "staff": "Mitarbeiter"
+  },
+  "domain_mute_card": {
+    "mute": "Stummschalten",
+    "mute_progress": "Wird stummgeschaltet..",
+    "unmute": "Stummschaltung aufheben",
+    "unmute_progress": "Stummschaltung wird aufgehoben.."
+  },
+  "exporter": {
+    "export": "Exportieren",
+    "processing": "Verarbeitung läuft, bald wird Du dazu aufgefordert, deine Datei herunterzuladen"
+  },
+  "image_cropper": {
+    "crop_picture": "Bild zuschneiden",
+    "save": "Speichern",
+    "cancel": "Abbrechen",
+    "save_without_cropping": "Ohne Zuschneiden speichern"
+  },
+  "importer": {
+    "submit": "Absenden",
+    "success": "Erfolgreich importiert.",
+    "error": "Ein Fehler ist beim Verabeiten der Datei aufgetreten."
+  },
+  "media_modal": {
+    "previous": "Zurück",
+    "next": "Weiter"
+  },
+  "polls": {
+    "add_poll": "Umfrage hinzufügen",
+    "add_option": "Option hinzufügen",
+    "option": "Option",
+    "votes": "Stimmen",
+    "vote": "Abstimmen",
+    "type": "Umfragetyp",
+    "multiple_choices": "Mehrere Auswahlmöglichkeiten",
+    "single_choice": "Eine Auswahlmöglichkeit",
+    "expiry": "Alter der Umfrage",
+    "expired": "Die Umfrage endete vor {0}",
+    "not_enough_options": "Zu wenig einzigartige Auswahlmöglichkeiten in der Umfrage",
+    "expires_in": "Die Umfrage endet in {0}"
+  },
+  "emoji": {
+    "stickers": "Sticker",
+    "emoji": "Emoji",
+    "search_emoji": "Nach einem Emoji suchen",
+    "custom": "Benutzerdefinierter Emoji",
+    "keep_open": "Auswahlfenster offen halten",
+    "add_emoji": "Emoji einfügen",
+    "load_all": "Lade alle {emojiAmount} Emoji",
+    "load_all_hint": "Erfolgreich erste {saneAmount} Emoji geladen, alle Emojis zu laden würde Leistungsprobleme hervorrufen.",
+    "unicode": "Unicode Emoji"
+  },
+  "interactions": {
+    "load_older": "Lade ältere Interaktionen",
+    "follows": "Neue Follows",
+    "favs_repeats": "Wiederholungen und Favoriten",
+    "moves": "Benutzer migriert zu"
+  },
+  "selectable_list": {
+    "select_all": "Wähle alle"
+  },
+  "remote_user_resolver": {
+    "searching_for": "Suche nach",
+    "error": "Nicht gefunden."
+  }
 }
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 9a4bd154..364e3b70 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1,743 +1,743 @@
 {
-    "about": {
-        "mrf": {
-            "federation": "Federation",
-            "keyword": {
-                "keyword_policies": "Keyword Policies",
-                "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
-                "reject": "Reject",
-                "replace": "Replace",
-                "is_replaced_by": "→"
-            },
-            "mrf_policies": "Enabled MRF Policies",
-            "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance.  The following policies are enabled:",
-            "simple": {
-                "simple_policies": "Instance-specific Policies",
-                "accept": "Accept",
-                "accept_desc": "This instance only accepts messages from the following instances:",
-                "reject": "Reject",
-                "reject_desc": "This instance will not accept messages from the following instances:",
-                "quarantine": "Quarantine",
-                "quarantine_desc": "This instance will send only public posts to the following instances:",
-                "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
-                "ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
-                "media_removal": "Media Removal",
-                "media_removal_desc": "This instance removes media from posts on the following instances:",
-                "media_nsfw": "Media Force-set As Sensitive",
-                "media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
-            }
-        },
-        "staff": "Staff"
+  "about": {
+    "mrf": {
+      "federation": "Federation",
+      "keyword": {
+        "keyword_policies": "Keyword Policies",
+        "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+        "reject": "Reject",
+        "replace": "Replace",
+        "is_replaced_by": "→"
+      },
+      "mrf_policies": "Enabled MRF Policies",
+      "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance.  The following policies are enabled:",
+      "simple": {
+        "simple_policies": "Instance-specific Policies",
+        "accept": "Accept",
+        "accept_desc": "This instance only accepts messages from the following instances:",
+        "reject": "Reject",
+        "reject_desc": "This instance will not accept messages from the following instances:",
+        "quarantine": "Quarantine",
+        "quarantine_desc": "This instance will send only public posts to the following instances:",
+        "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+        "ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
+        "media_removal": "Media Removal",
+        "media_removal_desc": "This instance removes media from posts on the following instances:",
+        "media_nsfw": "Media Force-set As Sensitive",
+        "media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
+      }
     },
-    "chat": {
-        "title": "Chat"
-    },
-    "domain_mute_card": {
-        "mute": "Mute",
-        "mute_progress": "Muting…",
-        "unmute": "Unmute",
-        "unmute_progress": "Unmuting…"
-    },
-    "exporter": {
-        "export": "Export",
-        "processing": "Processing, you'll soon be asked to download your file"
-    },
-    "features_panel": {
-        "chat": "Chat",
-        "gopher": "Gopher",
-        "media_proxy": "Media proxy",
-        "scope_options": "Scope options",
-        "text_limit": "Text limit",
-        "title": "Features",
-        "who_to_follow": "Who to follow"
-    },
-    "finder": {
-        "error_fetching_user": "Error fetching user",
-        "find_user": "Find user"
-    },
-    "general": {
-        "apply": "Apply",
-        "submit": "Submit",
-        "more": "More",
-        "generic_error": "An error occured",
-        "optional": "optional",
-        "show_more": "Show more",
-        "show_less": "Show less",
-        "dismiss": "Dismiss",
-        "cancel": "Cancel",
-        "disable": "Disable",
-        "enable": "Enable",
-        "confirm": "Confirm",
-        "verify": "Verify"
-    },
-    "image_cropper": {
-        "crop_picture": "Crop picture",
-        "save": "Save",
-        "save_without_cropping": "Save without cropping",
-        "cancel": "Cancel"
-    },
-    "importer": {
-        "submit": "Submit",
-        "success": "Imported successfully.",
-        "error": "An error occured while importing this file."
-    },
-    "login": {
-        "login": "Log in",
-        "description": "Log in with OAuth",
-        "logout": "Log out",
-        "password": "Password",
-        "placeholder": "e.g. lain",
-        "register": "Register",
-        "username": "Username",
-        "hint": "Log in to join the discussion",
-        "authentication_code": "Authentication code",
-        "enter_recovery_code": "Enter a recovery code",
-        "enter_two_factor_code": "Enter a two-factor code",
-        "recovery_code": "Recovery code",
-        "heading": {
-            "totp": "Two-factor authentication",
-            "recovery": "Two-factor recovery"
-        }
-    },
-    "media_modal": {
-        "previous": "Previous",
-        "next": "Next"
-    },
-    "nav": {
-        "about": "About",
-        "administration": "Administration",
-        "back": "Back",
-        "chat": "Local Chat",
-        "friend_requests": "Follow Requests",
-        "mentions": "Mentions",
-        "interactions": "Interactions",
-        "dms": "Direct Messages",
-        "public_tl": "Public Timeline",
-        "timeline": "Timeline",
-        "twkn": "The Whole Known Network",
-        "user_search": "User Search",
-        "search": "Search",
-        "who_to_follow": "Who to follow",
-        "preferences": "Preferences"
-    },
-    "notifications": {
-        "broken_favorite": "Unknown status, searching for it…",
-        "favorited_you": "favorited your status",
-        "followed_you": "followed you",
-        "follow_request": "wants to follow you",
-        "load_older": "Load older notifications",
-        "notifications": "Notifications",
-        "read": "Read!",
-        "repeated_you": "repeated your status",
-        "no_more_notifications": "No more notifications",
-        "migrated_to": "migrated to",
-        "reacted_with": "reacted with {0}"
-    },
-    "polls": {
-        "add_poll": "Add Poll",
-        "add_option": "Add Option",
-        "option": "Option",
-        "votes": "votes",
-        "vote": "Vote",
-        "type": "Poll type",
-        "single_choice": "Single choice",
-        "multiple_choices": "Multiple choices",
-        "expiry": "Poll age",
-        "expires_in": "Poll ends in {0}",
-        "expired": "Poll ended {0} ago",
-        "not_enough_options": "Too few unique options in poll"
-    },
-    "emoji": {
-        "stickers": "Stickers",
-        "emoji": "Emoji",
-        "keep_open": "Keep picker open",
-        "search_emoji": "Search for an emoji",
-        "add_emoji": "Insert emoji",
-        "custom": "Custom emoji",
-        "unicode": "Unicode emoji",
-        "load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
-        "load_all": "Loading all {emojiAmount} emoji"
-    },
-    "interactions": {
-        "favs_repeats": "Repeats and Favorites",
-        "follows": "New follows",
-        "moves": "User migrates",
-        "load_older": "Load older interactions"
-    },
-    "post_status": {
-        "new_status": "Post new status",
-        "account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
-        "account_not_locked_warning_link": "locked",
-        "attachments_sensitive": "Mark attachments as sensitive",
-        "content_type": {
-            "text/plain": "Plain text",
-            "text/html": "HTML",
-            "text/markdown": "Markdown",
-            "text/bbcode": "BBCode"
-        },
-        "content_warning": "Subject (optional)",
-        "default": "Just landed in L.A.",
-        "direct_warning_to_all": "This post will be visible to all the mentioned users.",
-        "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
-        "posting": "Posting",
-        "scope_notice": {
-            "public": "This post will be visible to everyone",
-            "private": "This post will be visible to your followers only",
-            "unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
-        },
-        "scope": {
-            "direct": "Direct - Post to mentioned users only",
-            "private": "Followers-only - Post to followers only",
-            "public": "Public - Post to public timelines",
-            "unlisted": "Unlisted - Do not post to public timelines"
-        }
-    },
-    "registration": {
-        "bio": "Bio",
-        "email": "Email",
-        "fullname": "Display name",
-        "password_confirm": "Password confirmation",
-        "registration": "Registration",
-        "token": "Invite token",
-        "captcha": "CAPTCHA",
-        "new_captcha": "Click the image to get a new captcha",
-        "username_placeholder": "e.g. lain",
-        "fullname_placeholder": "e.g. Lain Iwakura",
-        "bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
-        "validations": {
-            "username_required": "cannot be left blank",
-            "fullname_required": "cannot be left blank",
-            "email_required": "cannot be left blank",
-            "password_required": "cannot be left blank",
-            "password_confirmation_required": "cannot be left blank",
-            "password_confirmation_match": "should be the same as password"
-        }
-    },
-    "remote_user_resolver": {
-        "remote_user_resolver": "Remote user resolver",
-        "searching_for": "Searching for",
-        "error": "Not found."
-    },
-    "selectable_list": {
-        "select_all": "Select all"
-    },
-    "settings": {
-        "app_name": "App name",
-        "security": "Security",
-        "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
-        "mfa": {
-            "otp": "OTP",
-            "setup_otp": "Setup OTP",
-            "wait_pre_setup_otp": "presetting OTP",
-            "confirm_and_enable": "Confirm & enable OTP",
-            "title": "Two-factor Authentication",
-            "generate_new_recovery_codes": "Generate new recovery codes",
-            "warning_of_generate_new_codes": "When you generate new recovery codes, your old codes won’t work anymore.",
-            "recovery_codes": "Recovery codes.",
-            "waiting_a_recovery_codes": "Receiving backup codes…",
-            "recovery_codes_warning": "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.",
-            "authentication_methods": "Authentication methods",
-            "scan": {
-                "title": "Scan",
-                "desc": "Using your two-factor app, scan this QR code or enter text key:",
-                "secret_code": "Key"
-            },
-            "verify": {
-                "desc": "To enable two-factor authentication, enter the code from your two-factor app:"
-            }
-        },
-        "allow_following_move": "Allow auto-follow when following account moves",
-        "attachmentRadius": "Attachments",
-        "attachments": "Attachments",
-        "autoload": "Enable automatic loading when scrolled to the bottom",
-        "avatar": "Avatar",
-        "avatarAltRadius": "Avatars (Notifications)",
-        "avatarRadius": "Avatars",
-        "background": "Background",
-        "bio": "Bio",
-        "block_export": "Block export",
-        "block_export_button": "Export your blocks to a csv file",
-        "block_import": "Block import",
-        "block_import_error": "Error importing blocks",
-        "blocks_imported": "Blocks imported! Processing them will take a while.",
-        "blocks_tab": "Blocks",
-        "btnRadius": "Buttons",
-        "cBlue": "Blue (Reply, follow)",
-        "cGreen": "Green (Retweet)",
-        "cOrange": "Orange (Favorite)",
-        "cRed": "Red (Cancel)",
-        "change_email": "Change Email",
-        "change_email_error": "There was an issue changing your email.",
-        "changed_email": "Email changed successfully!",
-        "change_password": "Change Password",
-        "change_password_error": "There was an issue changing your password.",
-        "changed_password": "Password changed successfully!",
-        "collapse_subject": "Collapse posts with subjects",
-        "composing": "Composing",
-        "confirm_new_password": "Confirm new password",
-        "current_avatar": "Your current avatar",
-        "current_password": "Current password",
-        "current_profile_banner": "Your current profile banner",
-        "data_import_export_tab": "Data Import / Export",
-        "default_vis": "Default visibility scope",
-        "delete_account": "Delete Account",
-        "delete_account_description": "Permanently delete your data and deactivate your account.",
-        "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
-        "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
-        "discoverable": "Allow discovery of this account in search results and other services",
-        "domain_mutes": "Domains",
-        "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
-        "pad_emoji": "Pad emoji with spaces when adding from picker",
-        "emoji_reactions_on_timeline": "Show emoji reactions on timeline",
-        "export_theme": "Save preset",
-        "filtering": "Filtering",
-        "filtering_explanation": "All statuses containing these words will be muted, one per line",
-        "follow_export": "Follow export",
-        "follow_export_button": "Export your follows to a csv file",
-        "follow_import": "Follow import",
-        "follow_import_error": "Error importing followers",
-        "follows_imported": "Follows imported! Processing them will take a while.",
-        "accent": "Accent",
-        "foreground": "Foreground",
-        "general": "General",
-        "hide_attachments_in_convo": "Hide attachments in conversations",
-        "hide_attachments_in_tl": "Hide attachments in timeline",
-        "hide_muted_posts": "Hide posts of muted users",
-        "max_thumbnails": "Maximum amount of thumbnails per post",
-        "hide_isp": "Hide instance-specific panel",
-        "preload_images": "Preload images",
-        "use_one_click_nsfw": "Open NSFW attachments with just one click",
-        "hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
-        "hide_user_stats": "Hide user statistics (e.g. the number of followers)",
-        "hide_filtered_statuses": "Hide filtered statuses",
-        "import_blocks_from_a_csv_file": "Import blocks from a csv file",
-        "import_followers_from_a_csv_file": "Import follows from a csv file",
-        "import_theme": "Load preset",
-        "inputRadius": "Input fields",
-        "checkboxRadius": "Checkboxes",
-        "instance_default": "(default: {value})",
-        "instance_default_simple": "(default)",
-        "interface": "Interface",
-        "interfaceLanguage": "Interface language",
-        "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
-        "limited_availability": "Unavailable in your browser",
-        "links": "Links",
-        "lock_account_description": "Restrict your account to approved followers only",
-        "loop_video": "Loop videos",
-        "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
-        "mutes_tab": "Mutes",
-        "play_videos_in_modal": "Play videos in a popup frame",
-        "use_contain_fit": "Don't crop the attachment in thumbnails",
-        "name": "Name",
-        "name_bio": "Name & Bio",
-        "new_email": "New Email",
-        "new_password": "New password",
-        "notification_visibility": "Types of notifications to show",
-        "notification_visibility_follows": "Follows",
-        "notification_visibility_likes": "Likes",
-        "notification_visibility_mentions": "Mentions",
-        "notification_visibility_repeats": "Repeats",
-        "notification_visibility_moves": "User Migrates",
-        "notification_visibility_emoji_reactions": "Reactions",
-        "no_rich_text_description": "Strip rich text formatting from all posts",
-        "no_blocks": "No blocks",
-        "no_mutes": "No mutes",
-        "hide_follows_description": "Don't show who I'm following",
-        "hide_followers_description": "Don't show who's following me",
-        "hide_follows_count_description": "Don't show follow count",
-        "hide_followers_count_description": "Don't show follower count",
-        "show_admin_badge": "Show Admin badge in my profile",
-        "show_moderator_badge": "Show Moderator badge in my profile",
-        "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
-        "oauth_tokens": "OAuth tokens",
-        "token": "Token",
-        "refresh_token": "Refresh Token",
-        "valid_until": "Valid Until",
-        "revoke_token": "Revoke",
-        "panelRadius": "Panels",
-        "pause_on_unfocused": "Pause streaming when tab is not focused",
-        "presets": "Presets",
-        "profile_background": "Profile Background",
-        "profile_banner": "Profile Banner",
-        "profile_tab": "Profile",
-        "radii_help": "Set up interface edge rounding (in pixels)",
-        "replies_in_timeline": "Replies in timeline",
-        "reply_link_preview": "Enable reply-link preview on mouse hover",
-        "reply_visibility_all": "Show all replies",
-        "reply_visibility_following": "Only show replies directed at me or users I'm following",
-        "reply_visibility_self": "Only show replies directed at me",
-        "autohide_floating_post_button": "Automatically hide New Post button (mobile)",
-        "saving_err": "Error saving settings",
-        "saving_ok": "Settings saved",
-        "search_user_to_block": "Search whom you want to block",
-        "search_user_to_mute": "Search whom you want to mute",
-        "security_tab": "Security",
-        "scope_copy": "Copy scope when replying (DMs are always copied)",
-        "minimal_scopes_mode": "Minimize post scope selection options",
-        "set_new_avatar": "Set new avatar",
-        "set_new_profile_background": "Set new profile background",
-        "set_new_profile_banner": "Set new profile banner",
-        "settings": "Settings",
-        "subject_input_always_show": "Always show subject field",
-        "subject_line_behavior": "Copy subject when replying",
-        "subject_line_email": "Like email: \"re: subject\"",
-        "subject_line_mastodon": "Like mastodon: copy as is",
-        "subject_line_noop": "Do not copy",
-        "post_status_content_type": "Post status content type",
-        "stop_gifs": "Play-on-hover GIFs",
-        "streaming": "Enable automatic streaming of new posts when scrolled to the top",
-        "user_mutes": "Users",
-        "useStreamingApi": "Receive posts and notifications real-time",
-        "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
-        "text": "Text",
-        "theme": "Theme",
-        "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
-        "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
-        "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
-        "tooltipRadius": "Tooltips/alerts",
-        "type_domains_to_mute": "Type in domains to mute",
-        "upload_a_photo": "Upload a photo",
-        "user_settings": "User Settings",
-        "values": {
-            "false": "no",
-            "true": "yes"
-        },
-        "fun": "Fun",
-        "greentext": "Meme arrows",
-        "notifications": "Notifications",
-        "notification_setting_filters": "Filters",
-        "notification_setting": "Receive notifications from:",
-        "notification_setting_follows": "Users you follow",
-        "notification_setting_non_follows": "Users you do not follow",
-        "notification_setting_followers": "Users who follow you",
-        "notification_setting_non_followers": "Users who do not follow you",
-        "notification_setting_privacy": "Privacy",
-        "notification_setting_privacy_option": "Hide the sender and contents of push notifications",
-        "notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
-        "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
-        "enable_web_push_notifications": "Enable web push notifications",
-        "style": {
-            "switcher": {
-                "keep_color": "Keep colors",
-                "keep_shadows": "Keep shadows",
-                "keep_opacity": "Keep opacity",
-                "keep_roundness": "Keep roundness",
-                "keep_fonts": "Keep fonts",
-                "save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
-                "reset": "Reset",
-                "clear_all": "Clear all",
-                "clear_opacity": "Clear opacity",
-                "load_theme": "Load theme",
-                "keep_as_is": "Keep as is",
-                "use_snapshot": "Old version",
-                "use_source": "New version",
-                "help": {
-                    "upgraded_from_v2": "PleromaFE has been upgraded, theme could look a little bit different than you remember.",
-                    "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsistencies.",
-                    "future_version_imported": "File you imported was made in newer version of FE.",
-                    "older_version_imported": "File you imported was made in older version of FE.",
-                    "snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.",
-                    "snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.",
-                    "fe_upgraded": "PleromaFE's theme engine upgraded after version update.",
-                    "fe_downgraded": "PleromaFE's version rolled back.",
-                    "migration_snapshot_ok": "Just to be safe, theme snapshot loaded. You can try loading theme data.",
-                    "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember.",
-                    "snapshot_source_mismatch": "Versions conflict: most likely FE was rolled back and updated again, if you changed theme using older version of FE you most likely want to use old version, otherwise use new version."
-                }
-            },
-            "common": {
-                "color": "Color",
-                "opacity": "Opacity",
-                "contrast": {
-                    "hint": "Contrast ratio is {ratio}, it {level} {context}",
-                    "level": {
-                        "aa": "meets Level AA guideline (minimal)",
-                        "aaa": "meets Level AAA guideline (recommended)",
-                        "bad": "doesn't meet any accessibility guidelines"
-                    },
-                    "context": {
-                        "18pt": "for large (18pt+) text",
-                        "text": "for text"
-                    }
-                }
-            },
-            "common_colors": {
-                "_tab_label": "Common",
-                "main": "Common colors",
-                "foreground_hint": "See \"Advanced\" tab for more detailed control",
-                "rgbo": "Icons, accents, badges"
-            },
-            "advanced_colors": {
-                "_tab_label": "Advanced",
-                "alert": "Alert background",
-                "alert_error": "Error",
-                "alert_warning": "Warning",
-                "alert_neutral": "Neutral",
-                "post": "Posts/User bios",
-                "badge": "Badge background",
-                "popover": "Tooltips, menus, popovers",
-                "badge_notification": "Notification",
-                "panel_header": "Panel header",
-                "top_bar": "Top bar",
-                "borders": "Borders",
-                "buttons": "Buttons",
-                "inputs": "Input fields",
-                "faint_text": "Faded text",
-                "underlay": "Underlay",
-                "poll": "Poll graph",
-                "icons": "Icons",
-                "highlight": "Highlighted elements",
-                "pressed": "Pressed",
-                "selectedPost": "Selected post",
-                "selectedMenu": "Selected menu item",
-                "disabled": "Disabled",
-                "toggled": "Toggled",
-                "tabs": "Tabs"
-            },
-            "radii": {
-                "_tab_label": "Roundness"
-            },
-            "shadows": {
-                "_tab_label": "Shadow and lighting",
-                "component": "Component",
-                "override": "Override",
-                "shadow_id": "Shadow #{value}",
-                "blur": "Blur",
-                "spread": "Spread",
-                "inset": "Inset",
-                "hintV3": "For shadows you can also use the {0} notation to use other color slot.",
-                "filter_hint": {
-                    "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
-                    "drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
-                    "avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
-                    "spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
-                    "inset_classic": "Inset shadows will be using {0}"
-                },
-                "components": {
-                    "panel": "Panel",
-                    "panelHeader": "Panel header",
-                    "topBar": "Top bar",
-                    "avatar": "User avatar (in profile view)",
-                    "avatarStatus": "User avatar (in post display)",
-                    "popup": "Popups and tooltips",
-                    "button": "Button",
-                    "buttonHover": "Button (hover)",
-                    "buttonPressed": "Button (pressed)",
-                    "buttonPressedHover": "Button (pressed+hover)",
-                    "input": "Input field"
-                }
-            },
-            "fonts": {
-                "_tab_label": "Fonts",
-                "help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
-                "components": {
-                    "interface": "Interface",
-                    "input": "Input fields",
-                    "post": "Post text",
-                    "postCode": "Monospaced text in a post (rich text)"
-                },
-                "family": "Font name",
-                "size": "Size (in px)",
-                "weight": "Weight (boldness)",
-                "custom": "Custom"
-            },
-            "preview": {
-                "header": "Preview",
-                "content": "Content",
-                "error": "Example error",
-                "button": "Button",
-                "text": "A bunch of more {0} and {1}",
-                "mono": "content",
-                "input": "Just landed in L.A.",
-                "faint_link": "helpful manual",
-                "fine_print": "Read our {0} to learn nothing useful!",
-                "header_faint": "This is fine",
-                "checkbox": "I have skimmed over terms and conditions",
-                "link": "a nice lil' link"
-            }
-        },
-        "version": {
-            "title": "Version",
-            "backend_version": "Backend Version",
-            "frontend_version": "Frontend Version"
-        }
-    },
-    "time": {
-        "day": "{0} day",
-        "days": "{0} days",
-        "day_short": "{0}d",
-        "days_short": "{0}d",
-        "hour": "{0} hour",
-        "hours": "{0} hours",
-        "hour_short": "{0}h",
-        "hours_short": "{0}h",
-        "in_future": "in {0}",
-        "in_past": "{0} ago",
-        "minute": "{0} minute",
-        "minutes": "{0} minutes",
-        "minute_short": "{0}min",
-        "minutes_short": "{0}min",
-        "month": "{0} month",
-        "months": "{0} months",
-        "month_short": "{0}mo",
-        "months_short": "{0}mo",
-        "now": "just now",
-        "now_short": "now",
-        "second": "{0} second",
-        "seconds": "{0} seconds",
-        "second_short": "{0}s",
-        "seconds_short": "{0}s",
-        "week": "{0} week",
-        "weeks": "{0} weeks",
-        "week_short": "{0}w",
-        "weeks_short": "{0}w",
-        "year": "{0} year",
-        "years": "{0} years",
-        "year_short": "{0}y",
-        "years_short": "{0}y"
-    },
-    "timeline": {
-        "collapse": "Collapse",
-        "conversation": "Conversation",
-        "error_fetching": "Error fetching updates",
-        "load_older": "Load older statuses",
-        "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
-        "repeated": "repeated",
-        "show_new": "Show new",
-        "up_to_date": "Up-to-date",
-        "no_more_statuses": "No more statuses",
-        "no_statuses": "No statuses"
-    },
-    "status": {
-        "favorites": "Favorites",
-        "repeats": "Repeats",
-        "delete": "Delete status",
-        "pin": "Pin on profile",
-        "unpin": "Unpin from profile",
-        "pinned": "Pinned",
-        "delete_confirm": "Do you really want to delete this status?",
-        "reply_to": "Reply to",
-        "replies_list": "Replies:",
-        "mute_conversation": "Mute conversation",
-        "unmute_conversation": "Unmute conversation",
-        "status_unavailable": "Status unavailable",
-        "copy_link": "Copy link to status"
-    },
-    "user_card": {
-        "approve": "Approve",
-        "block": "Block",
-        "blocked": "Blocked!",
-        "deny": "Deny",
-        "favorites": "Favorites",
-        "follow": "Follow",
-        "follow_sent": "Request sent!",
-        "follow_progress": "Requesting…",
-        "follow_again": "Send request again?",
-        "follow_unfollow": "Unfollow",
-        "followees": "Following",
-        "followers": "Followers",
-        "following": "Following!",
-        "follows_you": "Follows you!",
-        "hidden": "Hidden",
-        "its_you": "It's you!",
-        "media": "Media",
-        "mention": "Mention",
-        "mute": "Mute",
-        "muted": "Muted",
-        "per_day": "per day",
-        "remote_follow": "Remote follow",
-        "report": "Report",
-        "statuses": "Statuses",
-        "subscribe": "Subscribe",
-        "unsubscribe": "Unsubscribe",
-        "unblock": "Unblock",
-        "unblock_progress": "Unblocking…",
-        "block_progress": "Blocking…",
-        "unmute": "Unmute",
-        "unmute_progress": "Unmuting…",
-        "mute_progress": "Muting…",
-        "hide_repeats": "Hide repeats",
-        "show_repeats": "Show repeats",
-        "admin_menu": {
-            "moderation": "Moderation",
-            "grant_admin": "Grant Admin",
-            "revoke_admin": "Revoke Admin",
-            "grant_moderator": "Grant Moderator",
-            "revoke_moderator": "Revoke Moderator",
-            "activate_account": "Activate account",
-            "deactivate_account": "Deactivate account",
-            "delete_account": "Delete account",
-            "force_nsfw": "Mark all posts as NSFW",
-            "strip_media": "Remove media from posts",
-            "force_unlisted": "Force posts to be unlisted",
-            "sandbox": "Force posts to be followers-only",
-            "disable_remote_subscription": "Disallow following user from remote instances",
-            "disable_any_subscription": "Disallow following user at all",
-            "quarantine": "Disallow user posts from federating",
-            "delete_user": "Delete user",
-            "delete_user_confirmation": "Are you absolutely sure? This action cannot be undone."
-        }
-    },
-    "user_profile": {
-        "timeline_title": "User Timeline",
-        "profile_does_not_exist": "Sorry, this profile does not exist.",
-        "profile_loading_error": "Sorry, there was an error loading this profile."
-    },
-    "user_reporting": {
-        "title": "Reporting {0}",
-        "add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
-        "additional_comments": "Additional comments",
-        "forward_description": "The account is from another server. Send a copy of the report there as well?",
-        "forward_to": "Forward to {0}",
-        "submit": "Submit",
-        "generic_error": "An error occurred while processing your request."
-    },
-    "who_to_follow": {
-        "more": "More",
-        "who_to_follow": "Who to follow"
-    },
-    "tool_tip": {
-        "media_upload": "Upload Media",
-        "repeat": "Repeat",
-        "reply": "Reply",
-        "favorite": "Favorite",
-        "add_reaction": "Add Reaction",
-        "user_settings": "User Settings",
-        "accept_follow_request": "Accept follow request",
-        "reject_follow_request": "Reject follow request"
-    },
-    "upload": {
-        "error": {
-            "base": "Upload failed.",
-            "file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-            "default": "Try again later"
-        },
-        "file_size_units": {
-            "B": "B",
-            "KiB": "KiB",
-            "MiB": "MiB",
-            "GiB": "GiB",
-            "TiB": "TiB"
-        }
-    },
-    "search": {
-        "people": "People",
-        "hashtags": "Hashtags",
-        "person_talking": "{count} person talking",
-        "people_talking": "{count} people talking",
-        "no_results": "No results"
-    },
-    "password_reset": {
-        "forgot_password": "Forgot password?",
-        "password_reset": "Password reset",
-        "instruction": "Enter your email address or username. We will send you a link to reset your password.",
-        "placeholder": "Your email or username",
-        "check_email": "Check your email for a link to reset your password.",
-        "return_home": "Return to the home page",
-        "not_found": "We couldn't find that email or username.",
-        "too_many_requests": "You have reached the limit of attempts, try again later.",
-        "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.",
-        "password_reset_required": "You must reset your password to log in.",
-        "password_reset_required_but_mailer_is_disabled": "You must reset your password, but password reset is disabled. Please contact your instance administrator."
+    "staff": "Staff"
+  },
+  "chat": {
+    "title": "Chat"
+  },
+  "domain_mute_card": {
+    "mute": "Mute",
+    "mute_progress": "Muting…",
+    "unmute": "Unmute",
+    "unmute_progress": "Unmuting…"
+  },
+  "exporter": {
+    "export": "Export",
+    "processing": "Processing, you'll soon be asked to download your file"
+  },
+  "features_panel": {
+    "chat": "Chat",
+    "gopher": "Gopher",
+    "media_proxy": "Media proxy",
+    "scope_options": "Scope options",
+    "text_limit": "Text limit",
+    "title": "Features",
+    "who_to_follow": "Who to follow"
+  },
+  "finder": {
+    "error_fetching_user": "Error fetching user",
+    "find_user": "Find user"
+  },
+  "general": {
+    "apply": "Apply",
+    "submit": "Submit",
+    "more": "More",
+    "generic_error": "An error occured",
+    "optional": "optional",
+    "show_more": "Show more",
+    "show_less": "Show less",
+    "dismiss": "Dismiss",
+    "cancel": "Cancel",
+    "disable": "Disable",
+    "enable": "Enable",
+    "confirm": "Confirm",
+    "verify": "Verify"
+  },
+  "image_cropper": {
+    "crop_picture": "Crop picture",
+    "save": "Save",
+    "save_without_cropping": "Save without cropping",
+    "cancel": "Cancel"
+  },
+  "importer": {
+    "submit": "Submit",
+    "success": "Imported successfully.",
+    "error": "An error occured while importing this file."
+  },
+  "login": {
+    "login": "Log in",
+    "description": "Log in with OAuth",
+    "logout": "Log out",
+    "password": "Password",
+    "placeholder": "e.g. lain",
+    "register": "Register",
+    "username": "Username",
+    "hint": "Log in to join the discussion",
+    "authentication_code": "Authentication code",
+    "enter_recovery_code": "Enter a recovery code",
+    "enter_two_factor_code": "Enter a two-factor code",
+    "recovery_code": "Recovery code",
+    "heading": {
+      "totp": "Two-factor authentication",
+      "recovery": "Two-factor recovery"
     }
+  },
+  "media_modal": {
+    "previous": "Previous",
+    "next": "Next"
+  },
+  "nav": {
+    "about": "About",
+    "administration": "Administration",
+    "back": "Back",
+    "chat": "Local Chat",
+    "friend_requests": "Follow Requests",
+    "mentions": "Mentions",
+    "interactions": "Interactions",
+    "dms": "Direct Messages",
+    "public_tl": "Public Timeline",
+    "timeline": "Timeline",
+    "twkn": "The Whole Known Network",
+    "user_search": "User Search",
+    "search": "Search",
+    "who_to_follow": "Who to follow",
+    "preferences": "Preferences"
+  },
+  "notifications": {
+    "broken_favorite": "Unknown status, searching for it…",
+    "favorited_you": "favorited your status",
+    "followed_you": "followed you",
+    "follow_request": "wants to follow you",
+    "load_older": "Load older notifications",
+    "notifications": "Notifications",
+    "read": "Read!",
+    "repeated_you": "repeated your status",
+    "no_more_notifications": "No more notifications",
+    "migrated_to": "migrated to",
+    "reacted_with": "reacted with {0}"
+  },
+  "polls": {
+    "add_poll": "Add Poll",
+    "add_option": "Add Option",
+    "option": "Option",
+    "votes": "votes",
+    "vote": "Vote",
+    "type": "Poll type",
+    "single_choice": "Single choice",
+    "multiple_choices": "Multiple choices",
+    "expiry": "Poll age",
+    "expires_in": "Poll ends in {0}",
+    "expired": "Poll ended {0} ago",
+    "not_enough_options": "Too few unique options in poll"
+  },
+  "emoji": {
+    "stickers": "Stickers",
+    "emoji": "Emoji",
+    "keep_open": "Keep picker open",
+    "search_emoji": "Search for an emoji",
+    "add_emoji": "Insert emoji",
+    "custom": "Custom emoji",
+    "unicode": "Unicode emoji",
+    "load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
+    "load_all": "Loading all {emojiAmount} emoji"
+  },
+  "interactions": {
+    "favs_repeats": "Repeats and Favorites",
+    "follows": "New follows",
+    "moves": "User migrates",
+    "load_older": "Load older interactions"
+  },
+  "post_status": {
+    "new_status": "Post new status",
+    "account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
+    "account_not_locked_warning_link": "locked",
+    "attachments_sensitive": "Mark attachments as sensitive",
+    "content_type": {
+      "text/plain": "Plain text",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
+    },
+    "content_warning": "Subject (optional)",
+    "default": "Just landed in L.A.",
+    "direct_warning_to_all": "This post will be visible to all the mentioned users.",
+    "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
+    "posting": "Posting",
+    "scope_notice": {
+      "public": "This post will be visible to everyone",
+      "private": "This post will be visible to your followers only",
+      "unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
+    },
+    "scope": {
+      "direct": "Direct - Post to mentioned users only",
+      "private": "Followers-only - Post to followers only",
+      "public": "Public - Post to public timelines",
+      "unlisted": "Unlisted - Do not post to public timelines"
+    }
+  },
+  "registration": {
+    "bio": "Bio",
+    "email": "Email",
+    "fullname": "Display name",
+    "password_confirm": "Password confirmation",
+    "registration": "Registration",
+    "token": "Invite token",
+    "captcha": "CAPTCHA",
+    "new_captcha": "Click the image to get a new captcha",
+    "username_placeholder": "e.g. lain",
+    "fullname_placeholder": "e.g. Lain Iwakura",
+    "bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
+    "validations": {
+      "username_required": "cannot be left blank",
+      "fullname_required": "cannot be left blank",
+      "email_required": "cannot be left blank",
+      "password_required": "cannot be left blank",
+      "password_confirmation_required": "cannot be left blank",
+      "password_confirmation_match": "should be the same as password"
+    }
+  },
+  "remote_user_resolver": {
+    "remote_user_resolver": "Remote user resolver",
+    "searching_for": "Searching for",
+    "error": "Not found."
+  },
+  "selectable_list": {
+    "select_all": "Select all"
+  },
+  "settings": {
+    "app_name": "App name",
+    "security": "Security",
+    "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
+    "mfa": {
+      "otp": "OTP",
+      "setup_otp": "Setup OTP",
+      "wait_pre_setup_otp": "presetting OTP",
+      "confirm_and_enable": "Confirm & enable OTP",
+      "title": "Two-factor Authentication",
+      "generate_new_recovery_codes": "Generate new recovery codes",
+      "warning_of_generate_new_codes": "When you generate new recovery codes, your old codes won’t work anymore.",
+      "recovery_codes": "Recovery codes.",
+      "waiting_a_recovery_codes": "Receiving backup codes…",
+      "recovery_codes_warning": "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.",
+      "authentication_methods": "Authentication methods",
+      "scan": {
+        "title": "Scan",
+        "desc": "Using your two-factor app, scan this QR code or enter text key:",
+        "secret_code": "Key"
+      },
+      "verify": {
+        "desc": "To enable two-factor authentication, enter the code from your two-factor app:"
+      }
+    },
+    "allow_following_move": "Allow auto-follow when following account moves",
+    "attachmentRadius": "Attachments",
+    "attachments": "Attachments",
+    "autoload": "Enable automatic loading when scrolled to the bottom",
+    "avatar": "Avatar",
+    "avatarAltRadius": "Avatars (Notifications)",
+    "avatarRadius": "Avatars",
+    "background": "Background",
+    "bio": "Bio",
+    "block_export": "Block export",
+    "block_export_button": "Export your blocks to a csv file",
+    "block_import": "Block import",
+    "block_import_error": "Error importing blocks",
+    "blocks_imported": "Blocks imported! Processing them will take a while.",
+    "blocks_tab": "Blocks",
+    "btnRadius": "Buttons",
+    "cBlue": "Blue (Reply, follow)",
+    "cGreen": "Green (Retweet)",
+    "cOrange": "Orange (Favorite)",
+    "cRed": "Red (Cancel)",
+    "change_email": "Change Email",
+    "change_email_error": "There was an issue changing your email.",
+    "changed_email": "Email changed successfully!",
+    "change_password": "Change Password",
+    "change_password_error": "There was an issue changing your password.",
+    "changed_password": "Password changed successfully!",
+    "collapse_subject": "Collapse posts with subjects",
+    "composing": "Composing",
+    "confirm_new_password": "Confirm new password",
+    "current_avatar": "Your current avatar",
+    "current_password": "Current password",
+    "current_profile_banner": "Your current profile banner",
+    "data_import_export_tab": "Data Import / Export",
+    "default_vis": "Default visibility scope",
+    "delete_account": "Delete Account",
+    "delete_account_description": "Permanently delete your data and deactivate your account.",
+    "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
+    "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
+    "discoverable": "Allow discovery of this account in search results and other services",
+    "domain_mutes": "Domains",
+    "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
+    "pad_emoji": "Pad emoji with spaces when adding from picker",
+    "emoji_reactions_on_timeline": "Show emoji reactions on timeline",
+    "export_theme": "Save preset",
+    "filtering": "Filtering",
+    "filtering_explanation": "All statuses containing these words will be muted, one per line",
+    "follow_export": "Follow export",
+    "follow_export_button": "Export your follows to a csv file",
+    "follow_import": "Follow import",
+    "follow_import_error": "Error importing followers",
+    "follows_imported": "Follows imported! Processing them will take a while.",
+    "accent": "Accent",
+    "foreground": "Foreground",
+    "general": "General",
+    "hide_attachments_in_convo": "Hide attachments in conversations",
+    "hide_attachments_in_tl": "Hide attachments in timeline",
+    "hide_muted_posts": "Hide posts of muted users",
+    "max_thumbnails": "Maximum amount of thumbnails per post",
+    "hide_isp": "Hide instance-specific panel",
+    "preload_images": "Preload images",
+    "use_one_click_nsfw": "Open NSFW attachments with just one click",
+    "hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
+    "hide_user_stats": "Hide user statistics (e.g. the number of followers)",
+    "hide_filtered_statuses": "Hide filtered statuses",
+    "import_blocks_from_a_csv_file": "Import blocks from a csv file",
+    "import_followers_from_a_csv_file": "Import follows from a csv file",
+    "import_theme": "Load preset",
+    "inputRadius": "Input fields",
+    "checkboxRadius": "Checkboxes",
+    "instance_default": "(default: {value})",
+    "instance_default_simple": "(default)",
+    "interface": "Interface",
+    "interfaceLanguage": "Interface language",
+    "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
+    "limited_availability": "Unavailable in your browser",
+    "links": "Links",
+    "lock_account_description": "Restrict your account to approved followers only",
+    "loop_video": "Loop videos",
+    "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
+    "mutes_tab": "Mutes",
+    "play_videos_in_modal": "Play videos in a popup frame",
+    "use_contain_fit": "Don't crop the attachment in thumbnails",
+    "name": "Name",
+    "name_bio": "Name & Bio",
+    "new_email": "New Email",
+    "new_password": "New password",
+    "notification_visibility": "Types of notifications to show",
+    "notification_visibility_follows": "Follows",
+    "notification_visibility_likes": "Likes",
+    "notification_visibility_mentions": "Mentions",
+    "notification_visibility_repeats": "Repeats",
+    "notification_visibility_moves": "User Migrates",
+    "notification_visibility_emoji_reactions": "Reactions",
+    "no_rich_text_description": "Strip rich text formatting from all posts",
+    "no_blocks": "No blocks",
+    "no_mutes": "No mutes",
+    "hide_follows_description": "Don't show who I'm following",
+    "hide_followers_description": "Don't show who's following me",
+    "hide_follows_count_description": "Don't show follow count",
+    "hide_followers_count_description": "Don't show follower count",
+    "show_admin_badge": "Show Admin badge in my profile",
+    "show_moderator_badge": "Show Moderator badge in my profile",
+    "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
+    "oauth_tokens": "OAuth tokens",
+    "token": "Token",
+    "refresh_token": "Refresh Token",
+    "valid_until": "Valid Until",
+    "revoke_token": "Revoke",
+    "panelRadius": "Panels",
+    "pause_on_unfocused": "Pause streaming when tab is not focused",
+    "presets": "Presets",
+    "profile_background": "Profile Background",
+    "profile_banner": "Profile Banner",
+    "profile_tab": "Profile",
+    "radii_help": "Set up interface edge rounding (in pixels)",
+    "replies_in_timeline": "Replies in timeline",
+    "reply_link_preview": "Enable reply-link preview on mouse hover",
+    "reply_visibility_all": "Show all replies",
+    "reply_visibility_following": "Only show replies directed at me or users I'm following",
+    "reply_visibility_self": "Only show replies directed at me",
+    "autohide_floating_post_button": "Automatically hide New Post button (mobile)",
+    "saving_err": "Error saving settings",
+    "saving_ok": "Settings saved",
+    "search_user_to_block": "Search whom you want to block",
+    "search_user_to_mute": "Search whom you want to mute",
+    "security_tab": "Security",
+    "scope_copy": "Copy scope when replying (DMs are always copied)",
+    "minimal_scopes_mode": "Minimize post scope selection options",
+    "set_new_avatar": "Set new avatar",
+    "set_new_profile_background": "Set new profile background",
+    "set_new_profile_banner": "Set new profile banner",
+    "settings": "Settings",
+    "subject_input_always_show": "Always show subject field",
+    "subject_line_behavior": "Copy subject when replying",
+    "subject_line_email": "Like email: \"re: subject\"",
+    "subject_line_mastodon": "Like mastodon: copy as is",
+    "subject_line_noop": "Do not copy",
+    "post_status_content_type": "Post status content type",
+    "stop_gifs": "Play-on-hover GIFs",
+    "streaming": "Enable automatic streaming of new posts when scrolled to the top",
+    "user_mutes": "Users",
+    "useStreamingApi": "Receive posts and notifications real-time",
+    "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
+    "text": "Text",
+    "theme": "Theme",
+    "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
+    "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
+    "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
+    "tooltipRadius": "Tooltips/alerts",
+    "type_domains_to_mute": "Type in domains to mute",
+    "upload_a_photo": "Upload a photo",
+    "user_settings": "User Settings",
+    "values": {
+      "false": "no",
+      "true": "yes"
+    },
+    "fun": "Fun",
+    "greentext": "Meme arrows",
+    "notifications": "Notifications",
+    "notification_setting_filters": "Filters",
+    "notification_setting": "Receive notifications from:",
+    "notification_setting_follows": "Users you follow",
+    "notification_setting_non_follows": "Users you do not follow",
+    "notification_setting_followers": "Users who follow you",
+    "notification_setting_non_followers": "Users who do not follow you",
+    "notification_setting_privacy": "Privacy",
+    "notification_setting_privacy_option": "Hide the sender and contents of push notifications",
+    "notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
+    "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
+    "enable_web_push_notifications": "Enable web push notifications",
+    "style": {
+      "switcher": {
+        "keep_color": "Keep colors",
+        "keep_shadows": "Keep shadows",
+        "keep_opacity": "Keep opacity",
+        "keep_roundness": "Keep roundness",
+        "keep_fonts": "Keep fonts",
+        "save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
+        "reset": "Reset",
+        "clear_all": "Clear all",
+        "clear_opacity": "Clear opacity",
+        "load_theme": "Load theme",
+        "keep_as_is": "Keep as is",
+        "use_snapshot": "Old version",
+        "use_source": "New version",
+        "help": {
+          "upgraded_from_v2": "PleromaFE has been upgraded, theme could look a little bit different than you remember.",
+          "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsistencies.",
+          "future_version_imported": "File you imported was made in newer version of FE.",
+          "older_version_imported": "File you imported was made in older version of FE.",
+          "snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.",
+          "snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.",
+          "fe_upgraded": "PleromaFE's theme engine upgraded after version update.",
+          "fe_downgraded": "PleromaFE's version rolled back.",
+          "migration_snapshot_ok": "Just to be safe, theme snapshot loaded. You can try loading theme data.",
+          "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember.",
+          "snapshot_source_mismatch": "Versions conflict: most likely FE was rolled back and updated again, if you changed theme using older version of FE you most likely want to use old version, otherwise use new version."
+        }
+      },
+      "common": {
+        "color": "Color",
+        "opacity": "Opacity",
+        "contrast": {
+          "hint": "Contrast ratio is {ratio}, it {level} {context}",
+          "level": {
+            "aa": "meets Level AA guideline (minimal)",
+            "aaa": "meets Level AAA guideline (recommended)",
+            "bad": "doesn't meet any accessibility guidelines"
+          },
+          "context": {
+            "18pt": "for large (18pt+) text",
+            "text": "for text"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "Common",
+        "main": "Common colors",
+        "foreground_hint": "See \"Advanced\" tab for more detailed control",
+        "rgbo": "Icons, accents, badges"
+      },
+      "advanced_colors": {
+        "_tab_label": "Advanced",
+        "alert": "Alert background",
+        "alert_error": "Error",
+        "alert_warning": "Warning",
+        "alert_neutral": "Neutral",
+        "post": "Posts/User bios",
+        "badge": "Badge background",
+        "popover": "Tooltips, menus, popovers",
+        "badge_notification": "Notification",
+        "panel_header": "Panel header",
+        "top_bar": "Top bar",
+        "borders": "Borders",
+        "buttons": "Buttons",
+        "inputs": "Input fields",
+        "faint_text": "Faded text",
+        "underlay": "Underlay",
+        "poll": "Poll graph",
+        "icons": "Icons",
+        "highlight": "Highlighted elements",
+        "pressed": "Pressed",
+        "selectedPost": "Selected post",
+        "selectedMenu": "Selected menu item",
+        "disabled": "Disabled",
+        "toggled": "Toggled",
+        "tabs": "Tabs"
+      },
+      "radii": {
+        "_tab_label": "Roundness"
+      },
+      "shadows": {
+        "_tab_label": "Shadow and lighting",
+        "component": "Component",
+        "override": "Override",
+        "shadow_id": "Shadow #{value}",
+        "blur": "Blur",
+        "spread": "Spread",
+        "inset": "Inset",
+        "hintV3": "For shadows you can also use the {0} notation to use other color slot.",
+        "filter_hint": {
+          "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
+          "drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
+          "avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
+          "spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
+          "inset_classic": "Inset shadows will be using {0}"
+        },
+        "components": {
+          "panel": "Panel",
+          "panelHeader": "Panel header",
+          "topBar": "Top bar",
+          "avatar": "User avatar (in profile view)",
+          "avatarStatus": "User avatar (in post display)",
+          "popup": "Popups and tooltips",
+          "button": "Button",
+          "buttonHover": "Button (hover)",
+          "buttonPressed": "Button (pressed)",
+          "buttonPressedHover": "Button (pressed+hover)",
+          "input": "Input field"
+        }
+      },
+      "fonts": {
+        "_tab_label": "Fonts",
+        "help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
+        "components": {
+          "interface": "Interface",
+          "input": "Input fields",
+          "post": "Post text",
+          "postCode": "Monospaced text in a post (rich text)"
+        },
+        "family": "Font name",
+        "size": "Size (in px)",
+        "weight": "Weight (boldness)",
+        "custom": "Custom"
+      },
+      "preview": {
+        "header": "Preview",
+        "content": "Content",
+        "error": "Example error",
+        "button": "Button",
+        "text": "A bunch of more {0} and {1}",
+        "mono": "content",
+        "input": "Just landed in L.A.",
+        "faint_link": "helpful manual",
+        "fine_print": "Read our {0} to learn nothing useful!",
+        "header_faint": "This is fine",
+        "checkbox": "I have skimmed over terms and conditions",
+        "link": "a nice lil' link"
+      }
+    },
+    "version": {
+      "title": "Version",
+      "backend_version": "Backend Version",
+      "frontend_version": "Frontend Version"
+    }
+  },
+  "time": {
+    "day": "{0} day",
+    "days": "{0} days",
+    "day_short": "{0}d",
+    "days_short": "{0}d",
+    "hour": "{0} hour",
+    "hours": "{0} hours",
+    "hour_short": "{0}h",
+    "hours_short": "{0}h",
+    "in_future": "in {0}",
+    "in_past": "{0} ago",
+    "minute": "{0} minute",
+    "minutes": "{0} minutes",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} month",
+    "months": "{0} months",
+    "month_short": "{0}mo",
+    "months_short": "{0}mo",
+    "now": "just now",
+    "now_short": "now",
+    "second": "{0} second",
+    "seconds": "{0} seconds",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} week",
+    "weeks": "{0} weeks",
+    "week_short": "{0}w",
+    "weeks_short": "{0}w",
+    "year": "{0} year",
+    "years": "{0} years",
+    "year_short": "{0}y",
+    "years_short": "{0}y"
+  },
+  "timeline": {
+    "collapse": "Collapse",
+    "conversation": "Conversation",
+    "error_fetching": "Error fetching updates",
+    "load_older": "Load older statuses",
+    "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
+    "repeated": "repeated",
+    "show_new": "Show new",
+    "up_to_date": "Up-to-date",
+    "no_more_statuses": "No more statuses",
+    "no_statuses": "No statuses"
+  },
+  "status": {
+    "favorites": "Favorites",
+    "repeats": "Repeats",
+    "delete": "Delete status",
+    "pin": "Pin on profile",
+    "unpin": "Unpin from profile",
+    "pinned": "Pinned",
+    "delete_confirm": "Do you really want to delete this status?",
+    "reply_to": "Reply to",
+    "replies_list": "Replies:",
+    "mute_conversation": "Mute conversation",
+    "unmute_conversation": "Unmute conversation",
+    "status_unavailable": "Status unavailable",
+    "copy_link": "Copy link to status"
+  },
+  "user_card": {
+    "approve": "Approve",
+    "block": "Block",
+    "blocked": "Blocked!",
+    "deny": "Deny",
+    "favorites": "Favorites",
+    "follow": "Follow",
+    "follow_sent": "Request sent!",
+    "follow_progress": "Requesting…",
+    "follow_again": "Send request again?",
+    "follow_unfollow": "Unfollow",
+    "followees": "Following",
+    "followers": "Followers",
+    "following": "Following!",
+    "follows_you": "Follows you!",
+    "hidden": "Hidden",
+    "its_you": "It's you!",
+    "media": "Media",
+    "mention": "Mention",
+    "mute": "Mute",
+    "muted": "Muted",
+    "per_day": "per day",
+    "remote_follow": "Remote follow",
+    "report": "Report",
+    "statuses": "Statuses",
+    "subscribe": "Subscribe",
+    "unsubscribe": "Unsubscribe",
+    "unblock": "Unblock",
+    "unblock_progress": "Unblocking…",
+    "block_progress": "Blocking…",
+    "unmute": "Unmute",
+    "unmute_progress": "Unmuting…",
+    "mute_progress": "Muting…",
+    "hide_repeats": "Hide repeats",
+    "show_repeats": "Show repeats",
+    "admin_menu": {
+      "moderation": "Moderation",
+      "grant_admin": "Grant Admin",
+      "revoke_admin": "Revoke Admin",
+      "grant_moderator": "Grant Moderator",
+      "revoke_moderator": "Revoke Moderator",
+      "activate_account": "Activate account",
+      "deactivate_account": "Deactivate account",
+      "delete_account": "Delete account",
+      "force_nsfw": "Mark all posts as NSFW",
+      "strip_media": "Remove media from posts",
+      "force_unlisted": "Force posts to be unlisted",
+      "sandbox": "Force posts to be followers-only",
+      "disable_remote_subscription": "Disallow following user from remote instances",
+      "disable_any_subscription": "Disallow following user at all",
+      "quarantine": "Disallow user posts from federating",
+      "delete_user": "Delete user",
+      "delete_user_confirmation": "Are you absolutely sure? This action cannot be undone."
+    }
+  },
+  "user_profile": {
+    "timeline_title": "User Timeline",
+    "profile_does_not_exist": "Sorry, this profile does not exist.",
+    "profile_loading_error": "Sorry, there was an error loading this profile."
+  },
+  "user_reporting": {
+    "title": "Reporting {0}",
+    "add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+    "additional_comments": "Additional comments",
+    "forward_description": "The account is from another server. Send a copy of the report there as well?",
+    "forward_to": "Forward to {0}",
+    "submit": "Submit",
+    "generic_error": "An error occurred while processing your request."
+  },
+  "who_to_follow": {
+    "more": "More",
+    "who_to_follow": "Who to follow"
+  },
+  "tool_tip": {
+    "media_upload": "Upload Media",
+    "repeat": "Repeat",
+    "reply": "Reply",
+    "favorite": "Favorite",
+    "add_reaction": "Add Reaction",
+    "user_settings": "User Settings",
+    "accept_follow_request": "Accept follow request",
+    "reject_follow_request": "Reject follow request"
+  },
+  "upload": {
+    "error": {
+      "base": "Upload failed.",
+      "file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+      "default": "Try again later"
+    },
+    "file_size_units": {
+      "B": "B",
+      "KiB": "KiB",
+      "MiB": "MiB",
+      "GiB": "GiB",
+      "TiB": "TiB"
+    }
+  },
+  "search": {
+    "people": "People",
+    "hashtags": "Hashtags",
+    "person_talking": "{count} person talking",
+    "people_talking": "{count} people talking",
+    "no_results": "No results"
+  },
+  "password_reset": {
+    "forgot_password": "Forgot password?",
+    "password_reset": "Password reset",
+    "instruction": "Enter your email address or username. We will send you a link to reset your password.",
+    "placeholder": "Your email or username",
+    "check_email": "Check your email for a link to reset your password.",
+    "return_home": "Return to the home page",
+    "not_found": "We couldn't find that email or username.",
+    "too_many_requests": "You have reached the limit of attempts, try again later.",
+    "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.",
+    "password_reset_required": "You must reset your password to log in.",
+    "password_reset_required_but_mailer_is_disabled": "You must reset your password, but password reset is disabled. Please contact your instance administrator."
+  }
 }
diff --git a/src/i18n/es.json b/src/i18n/es.json
index 08de345c..931d4c64 100644
--- a/src/i18n/es.json
+++ b/src/i18n/es.json
@@ -1,638 +1,638 @@
 {
-    "chat": {
-        "title": "Chat"
-    },
-    "exporter": {
-        "export": "Exportar",
-        "processing": "Procesando. Pronto se te pedirá que descargues tu archivo"
-    },
-    "features_panel": {
-        "chat": "Chat",
-        "gopher": "Gopher",
-        "media_proxy": "Proxy de medios",
-        "scope_options": "Opciones del alcance de la visibilidad",
-        "text_limit": "Límite de caracteres",
-        "title": "Características",
-        "who_to_follow": "A quién seguir"
-    },
-    "finder": {
-        "error_fetching_user": "Error al buscar usuario",
-        "find_user": "Encontrar usuario"
-    },
-    "general": {
-        "apply": "Aplicar",
-        "submit": "Enviar",
-        "more": "Más",
-        "generic_error": "Ha ocurrido un error",
-        "optional": "opcional",
-        "show_more": "Mostrar más",
-        "show_less": "Mostrar menos",
-        "cancel": "Cancelar",
-        "disable": "Inhabilitar",
-        "enable": "Habilitar",
-        "confirm": "Confirmar",
-        "verify": "Verificar"
-    },
-    "image_cropper": {
-        "crop_picture": "Recortar la foto",
-        "save": "Guardar",
-        "save_without_cropping": "Guardar sin recortar",
-        "cancel": "Cancelar"
-    },
-    "importer": {
-        "submit": "Enviar",
-        "success": "Importado con éxito",
-        "error": "Se ha producido un error al importar el archivo."
-    },
-    "login": {
-        "login": "Identificarse",
-        "description": "Identificarse con OAuth",
-        "logout": "Cerrar sesión",
-        "password": "Contraseña",
-        "placeholder": "p.ej. lain",
-        "register": "Registrarse",
-        "username": "Usuario",
-        "hint": "Inicia sesión para unirte a la discusión",
-        "authentication_code": "Código de autenticación",
-        "enter_recovery_code": "Inserta el código de recuperación",
-        "enter_two_factor_code": "Inserta el código de dos factores",
-        "recovery_code": "Código de recuperación",
-        "heading": {
-            "totp": "Autenticación de dos factores",
-            "recovery": "Recuperación de dos factores"
-        }
-    },
-    "media_modal": {
-        "previous": "Anterior",
-        "next": "Siguiente"
-    },
-    "nav": {
-        "about": "Acerca de",
-        "administration": "Administración",
-        "back": "Volver",
-        "chat": "Chat Local",
-        "friend_requests": "Solicitudes de seguimiento",
-        "mentions": "Menciones",
-        "interactions": "Interacciones",
-        "dms": "Mensajes Directos",
-        "public_tl": "Línea Temporal Pública",
-        "timeline": "Línea Temporal",
-        "twkn": "Toda La Red Conocida",
-        "user_search": "Búsqueda de Usuarios",
-        "search": "Buscar",
-        "who_to_follow": "A quién seguir",
-        "preferences": "Preferencias"
-    },
-    "notifications": {
-        "broken_favorite": "Estado desconocido, buscándolo...",
-        "favorited_you": "le gusta tu estado",
-        "followed_you": "empezó a seguirte",
-        "load_older": "Cargar notificaciones antiguas",
-        "notifications": "Notificaciones",
-        "read": "¡Leído!",
-        "repeated_you": "repitió tu estado",
-        "no_more_notifications": "No hay más notificaciones"
-    },
-    "polls": {
-        "add_poll": "Añadir encuesta",
-        "add_option": "Añadir opción",
-        "option": "Opción",
-        "votes": "votos",
-        "vote": "Votar",
-        "type": "Tipo de encuesta",
-        "single_choice": "Elección única",
-        "multiple_choices": "Elección múltiple",
-        "expiry": "Tiempo de vida de la encuesta",
-        "expires_in": "La encuesta termina en {0}",
-        "expired": "La encuesta terminó hace {0}",
-        "not_enough_options": "Muy pocas opciones únicas en la encuesta"
-    },
-    "emoji": {
-        "stickers": "Pegatinas",
-        "emoji": "Emoji",
-        "keep_open": "Mantener el selector abierto",
-        "search_emoji": "Buscar un emoji",
-        "add_emoji": "Insertar un emoji",
-        "custom": "Emojis personalizados",
-        "unicode": "Emojis unicode"
-    },
-    "stickers": {
-        "add_sticker": "Añadir Pegatina"
-    },
-    "interactions": {
-        "favs_repeats": "Favoritos y Repetidos",
-        "follows": "Nuevos seguidores",
-        "load_older": "Cargar interacciones más antiguas"
-    },
-    "post_status": {
-        "new_status": "Publicar un nuevo estado",
-        "account_not_locked_warning": "Tu cuenta no está {0}. Cualquiera puede seguirte y leer las entradas para Solo-Seguidores.",
-        "account_not_locked_warning_link": "bloqueada",
-        "attachments_sensitive": "Contenido sensible",
-        "content_type": {
-            "text/plain": "Texto Plano",
-            "text/html": "HTML",
-            "text/markdown": "Markdown",
-            "text/bbcode": "BBCode"
-        },
-        "content_warning": "Tema (opcional)",
-        "default": "Acabo de aterrizar en L.A.",
-        "direct_warning_to_all": "Esta publicación será visible para todos los usuarios mencionados.",
-        "direct_warning_to_first_only": "Esta publicación solo será visible para los usuarios mencionados al comienzo del mensaje.",
-        "posting": "Publicando",
-        "scope_notice": {
-            "public": "Esta publicación será visible para todo el mundo",
-            "private": "Esta publicación solo será visible para tus seguidores.",
-            "unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida"
-        },
-        "scope": {
-            "direct": "Directo - Solo para los usuarios mencionados",
-            "private": "Solo-seguidores - Solo tus seguidores leerán la publicación",
-            "public": "Público - Entradas visibles en las Líneas Temporales Públicas",
-            "unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas"
-        }
-    },
-    "registration": {
-        "bio": "Biografía",
-        "email": "Correo electrónico",
-        "fullname": "Nombre a mostrar",
-        "password_confirm": "Confirmar contraseña",
-        "registration": "Registro",
-        "token": "Token de invitación",
-        "captcha": "CAPTCHA",
-        "new_captcha": "Haz click en la imagen para obtener un nuevo captcha",
-        "username_placeholder": "p.ej. lain",
-        "fullname_placeholder": "p.ej. Lain Iwakura",
-        "bio_placeholder": "e.g.\nHola, soy un ejemplo.\nAquí puedes poner algo representativo tuyo... o no.",
-        "validations": {
-            "username_required": "no puede estar vacío",
-            "fullname_required": "no puede estar vacío",
-            "email_required": "no puede estar vacío",
-            "password_required": "no puede estar vacío",
-            "password_confirmation_required": "no puede estar vacío",
-            "password_confirmation_match": "la contraseña no coincide"
-        }
-    },
-    "selectable_list": {
-        "select_all": "Seleccionar todo"
-    },
-    "settings": {
-        "app_name": "Nombre de la aplicación",
-        "security": "Seguridad",
-        "enter_current_password_to_confirm": "Introduce la contraseña actual para confirmar tu identidad",
-        "mfa": {
-            "otp": "OTP",
-            "setup_otp": "Configurar OTP",
-            "wait_pre_setup_otp": "preconfiguración OTP",
-            "confirm_and_enable": "Confirmar y habilitar OTP",
-            "title": "Autentificación de dos factores",
-            "generate_new_recovery_codes": "Generar códigos de recuperación nuevos",
-            "warning_of_generate_new_codes": "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.",
-            "recovery_codes": "Códigos de recuperación.",
-            "waiting_a_recovery_codes": "Recibiendo códigos de respaldo",
-            "recovery_codes_warning": "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.",
-            "authentication_methods": "Métodos de autentificación",
-            "scan": {
-                "title": "Escanear",
-                "desc": "Usando su aplicación de dos factores, escanee este código QR o ingrese la clave de texto:",
-                "secret_code": "Clave"
-            },
-            "verify": {
-                "desc": "Para habilitar la autenticación de dos factores, ingrese el código de su aplicación 2FA:"
-            }
-        },
-        "attachmentRadius": "Adjuntos",
-        "attachments": "Adjuntos",
-        "autoload": "Habilitar carga automática al llegar al final de la página",
-        "avatar": "Avatar",
-        "avatarAltRadius": "Avatares (Notificaciones)",
-        "avatarRadius": "Avatares",
-        "background": "Fondo",
-        "bio": "Biografía",
-        "block_export": "Exportar usuarios bloqueados",
-        "block_export_button": "Exporta la lista de tus usuarios bloqueados a un archivo csv",
-        "block_import": "Importar usuarios bloqueados",
-        "block_import_error": "Error importando la lista de usuarios bloqueados",
-        "blocks_imported": "¡Lista de usuarios bloqueados importada! El procesado puede tardar un poco.",
-        "blocks_tab": "Bloqueados",
-        "btnRadius": "Botones",
-        "cBlue": "Azul (Responder, seguir)",
-        "cGreen": "Verde (Retweet)",
-        "cOrange": "Naranja (Favorito)",
-        "cRed": "Rojo (Cancelar)",
-        "change_password": "Cambiar contraseña",
-        "change_password_error": "Hubo un problema cambiando la contraseña.",
-        "changed_password": "¡Contraseña cambiada correctamente!",
-        "collapse_subject": "Colapsar entradas con tema",
-        "composing": "Redactando",
-        "confirm_new_password": "Confirmar la nueva contraseña",
-        "current_avatar": "Tu avatar actual",
-        "current_password": "Contraseña actual",
-        "current_profile_banner": "Tu cabecera actual",
-        "data_import_export_tab": "Importar / Exportar Datos",
-        "default_vis": "Alcance de visibilidad por defecto",
-        "delete_account": "Eliminar la cuenta",
-        "discoverable": "Permitir la aparición de esta cuenta en los resultados de búsqueda y otros servicios",
-        "delete_account_description": "Eliminar para siempre la cuenta y todos los mensajes.",
-        "pad_emoji": "Rellenar con espacios al agregar emojis desde el selector",
-        "delete_account_error": "Hubo un error al eliminar tu cuenta. Si el fallo persiste, ponte en contacto con el administrador de tu instancia.",
-        "delete_account_instructions": "Escribe tu contraseña para confirmar la eliminación de tu cuenta.",
-        "avatar_size_instruction": "El tamaño mínimo recomendado para el avatar es de 150X150 píxeles.",
-        "export_theme": "Exportar tema",
-        "filtering": "Filtrado",
-        "filtering_explanation": "Todos los estados que contengan estas palabras serán silenciados, una por línea",
-        "follow_export": "Exportar personas que tú sigues",
-        "follow_export_button": "Exporta tus seguidores a un fichero csv",
-        "follow_import": "Importar personas que tú sigues",
-        "follow_import_error": "Error al importar el fichero",
-        "follows_imported": "¡Importado! Procesarlos llevará tiempo.",
-        "foreground": "Primer plano",
-        "general": "General",
-        "hide_attachments_in_convo": "Ocultar adjuntos en las conversaciones",
-        "hide_attachments_in_tl": "Ocultar adjuntos en la línea temporal",
-        "hide_muted_posts": "Ocultar las publicaciones de los usuarios silenciados",
-        "max_thumbnails": "Cantidad máxima de miniaturas por publicación",
-        "hide_isp": "Ocultar el panel específico de la instancia",
-        "preload_images": "Precargar las imágenes",
-        "use_one_click_nsfw": "Abrir los adjuntos NSFW con un solo click.",
-        "hide_post_stats": "Ocultar las estadísticas de las entradas (p.ej. el número de favoritos)",
-        "hide_user_stats": "Ocultar las estadísticas del usuario (p.ej. el número de seguidores)",
-        "hide_filtered_statuses": "Ocultar estados filtrados",
-        "import_blocks_from_a_csv_file": "Importar lista de usuarios bloqueados dese un archivo csv",
-        "import_followers_from_a_csv_file": "Importar personas que tú sigues a partir de un archivo csv",
-        "import_theme": "Importar tema",
-        "inputRadius": "Campos de entrada",
-        "checkboxRadius": "Casillas de verificación",
-        "instance_default": "(por defecto: {value})",
-        "instance_default_simple": "(por defecto)",
-        "interface": "Interfaz",
-        "interfaceLanguage": "Idioma",
-        "invalid_theme_imported": "El archivo importado no es un tema válido de Pleroma. No se han realizado cambios.",
-        "limited_availability": "No disponible en tu navegador",
-        "links": "Enlaces",
-        "lock_account_description": "Restringir el acceso a tu cuenta solo a seguidores admitidos",
-        "loop_video": "Vídeos en bucle",
-        "loop_video_silent_only": "Bucle solo en vídeos sin sonido (p.ej. \"gifs\" de Mastodon)",
-        "mutes_tab": "Silenciados",
-        "play_videos_in_modal": "Reproducir los vídeos en un marco emergente",
-        "use_contain_fit": "No recortar los adjuntos en miniaturas",
-        "name": "Nombre",
-        "name_bio": "Nombre y Biografía",
-        "new_password": "Nueva contraseña",
-        "notification_visibility": "Tipos de notificaciones a mostrar",
-        "notification_visibility_follows": "Nuevos seguidores",
-        "notification_visibility_likes": "Me gustan (Likes)",
-        "notification_visibility_mentions": "Menciones",
-        "notification_visibility_repeats": "Repeticiones (Repeats)",
-        "no_rich_text_description": "Eliminar el formato de texto enriquecido de todas las entradas",
-        "no_blocks": "No hay usuarios bloqueados",
-        "no_mutes": "No hay usuarios silenciados",
-        "hide_follows_description": "No mostrar a quién sigo",
-        "hide_followers_description": "No mostrar quién me sigue",
-        "hide_follows_count_description": "No mostrar el número de cuentas que sigo",
-        "hide_followers_count_description": "No mostrar el número de cuentas que me siguen",
-        "show_admin_badge": "Mostrar la insignia de Administrador en mi perfil",
-        "show_moderator_badge": "Mostrar la insignia de Moderador en mi perfil",
-        "nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW",
-        "oauth_tokens": "Tokens de OAuth",
-        "token": "Token",
-        "refresh_token": "Actualizar el token",
-        "valid_until": "Válido hasta",
-        "revoke_token": "Revocar",
-        "panelRadius": "Paneles",
-        "pause_on_unfocused": "Parar la transmisión cuando no estés en foco.",
-        "presets": "Por defecto",
-        "profile_background": "Fondo del Perfil",
-        "profile_banner": "Cabecera del Perfil",
-        "profile_tab": "Perfil",
-        "radii_help": "Establezca el redondeo de las esquinas de la interfaz (en píxeles)",
-        "replies_in_timeline": "Réplicas en la línea temporal",
-        "reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encima",
-        "reply_visibility_all": "Mostrar todas las réplicas",
-        "reply_visibility_following": "Solo mostrar réplicas para mí o usuarios a los que sigo",
-        "reply_visibility_self": "Solo mostrar réplicas para mí",
-        "autohide_floating_post_button": "Ocultar automáticamente el botón 'Nueva Publicación' (para móviles)",
-        "saving_err": "Error al guardar los ajustes",
-        "saving_ok": "Ajustes guardados",
-        "search_user_to_block": "Buscar usuarios a bloquear",
-        "search_user_to_mute": "Buscar usuarios a silenciar",
-        "security_tab": "Seguridad",
-        "scope_copy": "Copiar la visibilidad de la publicación cuando contestamos (En los mensajes directos (MDs) siempre se copia)",
-        "minimal_scopes_mode": "Minimizar las opciones de publicación",
-        "set_new_avatar": "Cambiar avatar",
-        "set_new_profile_background": "Cambiar el fondo del perfil",
-        "set_new_profile_banner": "Cambiar la cabecera del perfil",
-        "settings": "Ajustes",
-        "subject_input_always_show": "Mostrar siempre el campo del tema",
-        "subject_line_behavior": "Copiar el tema en las respuestas",
-        "subject_line_email": "Como email: \"re: tema\"",
-        "subject_line_mastodon": "Como mastodon: copiar como es",
-        "subject_line_noop": "No copiar",
-        "post_status_content_type": "Formato de publicación",
-        "stop_gifs": "Iniciar GIFs al pasar el ratón",
-        "streaming": "Habilitar la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior",
-        "text": "Texto",
-        "theme": "Tema",
-        "theme_help": "Use códigos de color hexadecimales (#rrggbb) para personalizar su tema de colores.",
-        "theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación. Use el botón \"Borrar todo\" para deshacer los cambios.",
-        "theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón por encima para obtener información más detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
-        "tooltipRadius": "Información/alertas",
-        "upload_a_photo": "Subir una foto",
-        "user_settings": "Ajustes del Usuario",
-        "values": {
-            "false": "no",
-            "true": "sí"
-        },
-        "notifications": "Notificaciones",
-        "notification_setting": "Recibir notificaciones de:",
-        "notification_setting_follows": "Usuarios que sigues",
-        "notification_setting_non_follows": "Usuarios que no sigues",
-        "notification_setting_followers": "Usuarios que te siguen",
-        "notification_setting_non_followers": "Usuarios que no te siguen",
-        "notification_mutes": "Para dejar de recibir notificaciones de un usuario específico, siléncialo.",
-        "notification_blocks": "El bloqueo de un usuario detiene todas las notificaciones y también las cancela.",
-        "enable_web_push_notifications": "Habilitar las notificiaciones en el navegador",
-        "style": {
-            "switcher": {
-                "keep_color": "Mantener colores",
-                "keep_shadows": "Mantener sombras",
-                "keep_opacity": "Mantener opacidad",
-                "keep_roundness": "Mantener redondeces",
-                "keep_fonts": "Mantener fuentes",
-                "save_load_hint": "Las opciones \"Mantener\" conservan las opciones configuradas actualmente al seleccionar o cargar temas, también almacena dichas opciones al exportar un tema. Cuando se desactiven todas las casillas de verificación, el tema de exportación lo guardará todo.",
-                "reset": "Reiniciar",
-                "clear_all": "Limpiar todo",
-                "clear_opacity": "Limpiar opacidad"
-            },
-            "common": {
-                "color": "Color",
-                "opacity": "Opacidad",
-                "contrast": {
-                    "hint": "El ratio de contraste es {ratio}. {level} {context}",
-                    "level": {
-                        "aa": "Cumple con la pauta de nivel AA (mínimo)",
-                        "aaa": "Cumple con la pauta de nivel AAA (recomendado)",
-                        "bad": "No cumple con las pautas de accesibilidad"
-                    },
-                    "context": {
-                        "18pt": "para textos grandes (+18pt)",
-                        "text": "para textos"
-                    }
-                }
-            },
-            "common_colors": {
-                "_tab_label": "Común",
-                "main": "Colores comunes",
-                "foreground_hint": "Vea la pestaña \"Avanzado\" para un control más detallado",
-                "rgbo": "Iconos, acentos, insignias"
-            },
-            "advanced_colors": {
-                "_tab_label": "Avanzado",
-                "alert": "Fondo de Alertas",
-                "alert_error": "Error",
-                "badge": "Fondo de Insignias",
-                "badge_notification": "Notificaciones",
-                "panel_header": "Cabecera del panel",
-                "top_bar": "Barra superior",
-                "borders": "Bordes",
-                "buttons": "Botones",
-                "inputs": "Campos de entrada",
-                "faint_text": "Texto desvanecido"
-            },
-            "radii": {
-                "_tab_label": "Redondez"
-            },
-            "shadows": {
-                "_tab_label": "Sombra e iluminación",
-                "component": "Componente",
-                "override": "Sobreescribir",
-                "shadow_id": "Sombra #{value}",
-                "blur": "Difuminar",
-                "spread": "Cantidad",
-                "inset": "Sombra interior",
-                "hint": "Para las sombras, también puede usar --variable como un valor de color para usar las variables CSS3. Tenga en cuenta que establecer la opacidad no funcionará en este caso.",
-                "filter_hint": {
-                    "always_drop_shadow": "Advertencia, esta sombra siempre usa {0} cuando el navegador lo soporta.",
-                    "drop_shadow_syntax": "{0} no soporta el parámetro {1} y la palabra clave {2}.",
-                    "avatar_inset": "Tenga en cuenta que la combinación de sombras interiores como no-interiores en los avatares, puede dar resultados inesperados con los avatares transparentes.",
-                    "spread_zero": "Sombras con una cantidad > 0 aparecerá como si estuviera puesto a cero",
-                    "inset_classic": "Las sombras interiores estarán usando {0}"
-                },
-                "components": {
-                    "panel": "Panel",
-                    "panelHeader": "Cabecera del panel",
-                    "topBar": "Barra superior",
-                    "avatar": "Avatar del usuario (en la vista del perfil)",
-                    "avatarStatus": "Avatar del usuario (en la vista de la entrada)",
-                    "popup": "Ventanas y textos emergentes (popups & tooltips)",
-                    "button": "Botones",
-                    "buttonHover": "Botón (encima)",
-                    "buttonPressed": "Botón (presionado)",
-                    "buttonPressedHover": "Botón (presionado+encima)",
-                    "input": "Campo de entrada"
-                }
-            },
-            "fonts": {
-                "_tab_label": "Fuentes",
-                "help": "Seleccione la fuente a utilizar para los elementos de la interfaz de usuario. Para \"personalizar\", debe ingresar el nombre exacto de la fuente tal como aparece en el sistema.",
-                "components": {
-                    "interface": "Interfaz",
-                    "input": "Campos de entrada",
-                    "post": "Texto de publicaciones",
-                    "postCode": "Texto monoespaciado en publicación (texto enriquecido)"
-                },
-                "family": "Nombre de la fuente",
-                "size": "Tamaño (en px)",
-                "weight": "Peso (negrita)",
-                "custom": "Personalizado"
-            },
-            "preview": {
-                "header": "Vista previa",
-                "content": "Contenido",
-                "error": "Ejemplo de error",
-                "button": "Botón",
-                "text": "Un montón de {0} y {1}",
-                "mono": "contenido",
-                "input": "Acaba de aterrizar en L.A.",
-                "faint_link": "manual útil",
-                "fine_print": "¡Lea nuestro {0} para aprender nada útil!",
-                "header_faint": "Esto está bien",
-                "checkbox": "He revisado los términos y condiciones",
-                "link": "un bonito enlace"
-            }
-        },
-        "version": {
-            "title": "Versión",
-            "backend_version": "Versión del Backend",
-            "frontend_version": "Versión del Frontend"
-        }
-    },
-    "time": {
-        "day": "{0} día",
-        "days": "{0} días",
-        "day_short": "{0}d",
-        "days_short": "{0}d",
-        "hour": "{0} hora",
-        "hours": "{0} horas",
-        "hour_short": "{0}h",
-        "hours_short": "{0}h",
-        "in_future": "en {0}",
-        "in_past": "hace {0}",
-        "minute": "{0} minuto",
-        "minutes": "{0} minutos",
-        "minute_short": "{0}min",
-        "minutes_short": "{0}min",
-        "month": "{0} mes",
-        "months": "{0} meses",
-        "month_short": "{0}m",
-        "months_short": "{0}m",
-        "now": "justo ahora",
-        "now_short": "ahora",
-        "second": "{0} segundo",
-        "seconds": "{0} segundos",
-        "second_short": "{0}s",
-        "seconds_short": "{0}s",
-        "week": "{0} semana",
-        "weeks": "{0} semanas",
-        "week_short": "{0}sem",
-        "weeks_short": "{0}sem",
-        "year": "{0} año",
-        "years": "{0} años",
-        "year_short": "{0}a",
-        "years_short": "{0}a"
-    },
-    "timeline": {
-        "collapse": "Colapsar",
-        "conversation": "Conversación",
-        "error_fetching": "Error al cargar las actualizaciones",
-        "load_older": "Cargar actualizaciones anteriores",
-        "no_retweet_hint": "La publicación está marcada como solo para seguidores o directa y no se puede repetir",
-        "repeated": "repetida",
-        "show_new": "Mostrar lo nuevo",
-        "up_to_date": "Actualizado",
-        "no_more_statuses": "No hay más estados",
-        "no_statuses": "Sin estados"
-    },
-    "status": {
-        "favorites": "Favoritos",
-        "repeats": "Repetidos",
-        "delete": "Eliminar publicación",
-        "pin": "Fijar en tu perfil",
-        "unpin": "Desclavar de tu perfil",
-        "pinned": "Fijado",
-        "delete_confirm": "¿Realmente quieres borrar la publicación?",
-        "reply_to": "Respondiendo a",
-        "replies_list": "Respuestas:",
-        "mute_conversation": "Silenciar la conversación",
-        "unmute_conversation": "Mostrar la conversación"
-    },
-    "user_card": {
-        "approve": "Aprobar",
-        "block": "Bloquear",
-        "blocked": "¡Bloqueado!",
-        "deny": "Denegar",
-        "favorites": "Favoritos",
-        "follow": "Seguir",
-        "follow_sent": "¡Solicitud enviada!",
-        "follow_progress": "Solicitando…",
-        "follow_again": "¿Enviar solicitud de nuevo?",
-        "follow_unfollow": "Dejar de seguir",
-        "followees": "Siguiendo",
-        "followers": "Seguidores",
-        "following": "¡Siguiendo!",
-        "follows_you": "¡Te sigue!",
-        "its_you": "¡Eres tú!",
-        "media": "Media",
-        "mention": "Mencionar",
-        "mute": "Silenciar",
-        "muted": "Silenciado",
-        "per_day": "por día",
-        "remote_follow": "Seguir",
-        "report": "Reportar",
-        "statuses": "Estados",
-        "subscribe": "Suscribirse",
-        "unsubscribe": "Desuscribirse",
-        "unblock": "Desbloquear",
-        "unblock_progress": "Desbloqueando...",
-        "block_progress": "Bloqueando...",
-        "unmute": "Quitar silencio",
-        "unmute_progress": "Quitando silencio...",
-        "mute_progress": "Silenciando...",
-        "admin_menu": {
-            "moderation": "Moderación",
-            "grant_admin": "Conceder permisos de Administrador",
-            "revoke_admin": "Revocar permisos de Administrador",
-            "grant_moderator": "Conceder permisos de Moderador",
-            "revoke_moderator": "Revocar permisos de Moderador",
-            "activate_account": "Activar cuenta",
-            "deactivate_account": "Desactivar cuenta",
-            "delete_account": "Eliminar cuenta",
-            "force_nsfw": "Marcar todas las publicaciones como NSFW (no es seguro/apropiado para el trabajo)",
-            "strip_media": "Eliminar archivos multimedia de las publicaciones",
-            "force_unlisted": "Forzar que se publique en el modo -Sin Listar-",
-            "sandbox": "Forzar que se publique solo para tus seguidores",
-            "disable_remote_subscription": "No permitir que usuarios de instancias remotas te siga.",
-            "disable_any_subscription": "No permitir que ningún usuario te siga",
-            "quarantine": "No permitir publicaciones de usuarios de instancias remotas",
-            "delete_user": "Eliminar usuario",
-            "delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer."
-        }
-    },
-    "user_profile": {
-        "timeline_title": "Linea Temporal del Usuario",
-        "profile_does_not_exist": "Lo sentimos, este perfil no existe.",
-        "profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil."
-    },
-    "user_reporting": {
-        "title": "Reportando a {0}",
-        "add_comment_description": "El informe será enviado a los moderadores de su instancia. Puedes proporcionar una explicación de por qué estás reportando esta cuenta a continuación:",
-        "additional_comments": "Comentarios adicionales",
-        "forward_description": "La cuenta es de otro servidor. ¿Enviar una copia del informe allí también?",
-        "forward_to": "Reenviar a {0}",
-        "submit": "Enviar",
-        "generic_error": "Se produjo un error al procesar la solicitud."
-    },
-    "who_to_follow": {
-        "more": "Más",
-        "who_to_follow": "A quién seguir"
-    },
-    "tool_tip": {
-        "media_upload": "Subir Medios",
-        "repeat": "Repetir",
-        "reply": "Contestar",
-        "favorite": "Favorito",
-        "user_settings": "Ajustes de usuario"
-    },
-    "upload": {
-        "error": {
-            "base": "Subida fallida.",
-            "file_too_big": "Archivo demasiado grande [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-            "default": "Inténtalo más tarde"
-        },
-        "file_size_units": {
-            "B": "B",
-            "KiB": "KiB",
-            "MiB": "MiB",
-            "GiB": "GiB",
-            "TiB": "TiB"
-        }
-    },
-    "search": {
-        "people": "Personas",
-        "hashtags": "Etiquetas",
-        "person_talking": "{count} personas hablando",
-        "people_talking": "{count} gente hablando",
-        "no_results": "Sin resultados"
-    },
-    "password_reset": {
-        "forgot_password": "¿Contraseña olvidada?",
-        "password_reset": "Restablecer la contraseña",
-        "instruction": "Ingrese su dirección de correo electrónico o nombre de usuario. Le enviaremos un enlace para restablecer su contraseña.",
-        "placeholder": "Su correo electrónico o nombre de usuario",
-        "check_email": "Revise su correo electrónico para obtener un enlace para restablecer su contraseña.",
-        "return_home": "Volver a la página de inicio",
-        "not_found": "No pudimos encontrar ese correo electrónico o nombre de usuario.",
-        "too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.",
-        "password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia."
+  "chat": {
+    "title": "Chat"
+  },
+  "exporter": {
+    "export": "Exportar",
+    "processing": "Procesando. Pronto se te pedirá que descargues tu archivo"
+  },
+  "features_panel": {
+    "chat": "Chat",
+    "gopher": "Gopher",
+    "media_proxy": "Proxy de medios",
+    "scope_options": "Opciones del alcance de la visibilidad",
+    "text_limit": "Límite de caracteres",
+    "title": "Características",
+    "who_to_follow": "A quién seguir"
+  },
+  "finder": {
+    "error_fetching_user": "Error al buscar usuario",
+    "find_user": "Encontrar usuario"
+  },
+  "general": {
+    "apply": "Aplicar",
+    "submit": "Enviar",
+    "more": "Más",
+    "generic_error": "Ha ocurrido un error",
+    "optional": "opcional",
+    "show_more": "Mostrar más",
+    "show_less": "Mostrar menos",
+    "cancel": "Cancelar",
+    "disable": "Inhabilitar",
+    "enable": "Habilitar",
+    "confirm": "Confirmar",
+    "verify": "Verificar"
+  },
+  "image_cropper": {
+    "crop_picture": "Recortar la foto",
+    "save": "Guardar",
+    "save_without_cropping": "Guardar sin recortar",
+    "cancel": "Cancelar"
+  },
+  "importer": {
+    "submit": "Enviar",
+    "success": "Importado con éxito",
+    "error": "Se ha producido un error al importar el archivo."
+  },
+  "login": {
+    "login": "Identificarse",
+    "description": "Identificarse con OAuth",
+    "logout": "Cerrar sesión",
+    "password": "Contraseña",
+    "placeholder": "p.ej. lain",
+    "register": "Registrarse",
+    "username": "Usuario",
+    "hint": "Inicia sesión para unirte a la discusión",
+    "authentication_code": "Código de autenticación",
+    "enter_recovery_code": "Inserta el código de recuperación",
+    "enter_two_factor_code": "Inserta el código de dos factores",
+    "recovery_code": "Código de recuperación",
+    "heading": {
+      "totp": "Autenticación de dos factores",
+      "recovery": "Recuperación de dos factores"
     }
+  },
+  "media_modal": {
+    "previous": "Anterior",
+    "next": "Siguiente"
+  },
+  "nav": {
+    "about": "Acerca de",
+    "administration": "Administración",
+    "back": "Volver",
+    "chat": "Chat Local",
+    "friend_requests": "Solicitudes de seguimiento",
+    "mentions": "Menciones",
+    "interactions": "Interacciones",
+    "dms": "Mensajes Directos",
+    "public_tl": "Línea Temporal Pública",
+    "timeline": "Línea Temporal",
+    "twkn": "Toda La Red Conocida",
+    "user_search": "Búsqueda de Usuarios",
+    "search": "Buscar",
+    "who_to_follow": "A quién seguir",
+    "preferences": "Preferencias"
+  },
+  "notifications": {
+    "broken_favorite": "Estado desconocido, buscándolo...",
+    "favorited_you": "le gusta tu estado",
+    "followed_you": "empezó a seguirte",
+    "load_older": "Cargar notificaciones antiguas",
+    "notifications": "Notificaciones",
+    "read": "¡Leído!",
+    "repeated_you": "repitió tu estado",
+    "no_more_notifications": "No hay más notificaciones"
+  },
+  "polls": {
+    "add_poll": "Añadir encuesta",
+    "add_option": "Añadir opción",
+    "option": "Opción",
+    "votes": "votos",
+    "vote": "Votar",
+    "type": "Tipo de encuesta",
+    "single_choice": "Elección única",
+    "multiple_choices": "Elección múltiple",
+    "expiry": "Tiempo de vida de la encuesta",
+    "expires_in": "La encuesta termina en {0}",
+    "expired": "La encuesta terminó hace {0}",
+    "not_enough_options": "Muy pocas opciones únicas en la encuesta"
+  },
+  "emoji": {
+    "stickers": "Pegatinas",
+    "emoji": "Emoji",
+    "keep_open": "Mantener el selector abierto",
+    "search_emoji": "Buscar un emoji",
+    "add_emoji": "Insertar un emoji",
+    "custom": "Emojis personalizados",
+    "unicode": "Emojis unicode"
+  },
+  "stickers": {
+    "add_sticker": "Añadir Pegatina"
+  },
+  "interactions": {
+    "favs_repeats": "Favoritos y Repetidos",
+    "follows": "Nuevos seguidores",
+    "load_older": "Cargar interacciones más antiguas"
+  },
+  "post_status": {
+    "new_status": "Publicar un nuevo estado",
+    "account_not_locked_warning": "Tu cuenta no está {0}. Cualquiera puede seguirte y leer las entradas para Solo-Seguidores.",
+    "account_not_locked_warning_link": "bloqueada",
+    "attachments_sensitive": "Contenido sensible",
+    "content_type": {
+      "text/plain": "Texto Plano",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
+    },
+    "content_warning": "Tema (opcional)",
+    "default": "Acabo de aterrizar en L.A.",
+    "direct_warning_to_all": "Esta publicación será visible para todos los usuarios mencionados.",
+    "direct_warning_to_first_only": "Esta publicación solo será visible para los usuarios mencionados al comienzo del mensaje.",
+    "posting": "Publicando",
+    "scope_notice": {
+      "public": "Esta publicación será visible para todo el mundo",
+      "private": "Esta publicación solo será visible para tus seguidores.",
+      "unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida"
+    },
+    "scope": {
+      "direct": "Directo - Solo para los usuarios mencionados",
+      "private": "Solo-seguidores - Solo tus seguidores leerán la publicación",
+      "public": "Público - Entradas visibles en las Líneas Temporales Públicas",
+      "unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas"
+    }
+  },
+  "registration": {
+    "bio": "Biografía",
+    "email": "Correo electrónico",
+    "fullname": "Nombre a mostrar",
+    "password_confirm": "Confirmar contraseña",
+    "registration": "Registro",
+    "token": "Token de invitación",
+    "captcha": "CAPTCHA",
+    "new_captcha": "Haz click en la imagen para obtener un nuevo captcha",
+    "username_placeholder": "p.ej. lain",
+    "fullname_placeholder": "p.ej. Lain Iwakura",
+    "bio_placeholder": "e.g.\nHola, soy un ejemplo.\nAquí puedes poner algo representativo tuyo... o no.",
+    "validations": {
+      "username_required": "no puede estar vacío",
+      "fullname_required": "no puede estar vacío",
+      "email_required": "no puede estar vacío",
+      "password_required": "no puede estar vacío",
+      "password_confirmation_required": "no puede estar vacío",
+      "password_confirmation_match": "la contraseña no coincide"
+    }
+  },
+  "selectable_list": {
+    "select_all": "Seleccionar todo"
+  },
+  "settings": {
+    "app_name": "Nombre de la aplicación",
+    "security": "Seguridad",
+    "enter_current_password_to_confirm": "Introduce la contraseña actual para confirmar tu identidad",
+    "mfa": {
+      "otp": "OTP",
+      "setup_otp": "Configurar OTP",
+      "wait_pre_setup_otp": "preconfiguración OTP",
+      "confirm_and_enable": "Confirmar y habilitar OTP",
+      "title": "Autentificación de dos factores",
+      "generate_new_recovery_codes": "Generar códigos de recuperación nuevos",
+      "warning_of_generate_new_codes": "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.",
+      "recovery_codes": "Códigos de recuperación.",
+      "waiting_a_recovery_codes": "Recibiendo códigos de respaldo",
+      "recovery_codes_warning": "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.",
+      "authentication_methods": "Métodos de autentificación",
+      "scan": {
+        "title": "Escanear",
+        "desc": "Usando su aplicación de dos factores, escanee este código QR o ingrese la clave de texto:",
+        "secret_code": "Clave"
+      },
+      "verify": {
+        "desc": "Para habilitar la autenticación de dos factores, ingrese el código de su aplicación 2FA:"
+      }
+    },
+    "attachmentRadius": "Adjuntos",
+    "attachments": "Adjuntos",
+    "autoload": "Habilitar carga automática al llegar al final de la página",
+    "avatar": "Avatar",
+    "avatarAltRadius": "Avatares (Notificaciones)",
+    "avatarRadius": "Avatares",
+    "background": "Fondo",
+    "bio": "Biografía",
+    "block_export": "Exportar usuarios bloqueados",
+    "block_export_button": "Exporta la lista de tus usuarios bloqueados a un archivo csv",
+    "block_import": "Importar usuarios bloqueados",
+    "block_import_error": "Error importando la lista de usuarios bloqueados",
+    "blocks_imported": "¡Lista de usuarios bloqueados importada! El procesado puede tardar un poco.",
+    "blocks_tab": "Bloqueados",
+    "btnRadius": "Botones",
+    "cBlue": "Azul (Responder, seguir)",
+    "cGreen": "Verde (Retweet)",
+    "cOrange": "Naranja (Favorito)",
+    "cRed": "Rojo (Cancelar)",
+    "change_password": "Cambiar contraseña",
+    "change_password_error": "Hubo un problema cambiando la contraseña.",
+    "changed_password": "¡Contraseña cambiada correctamente!",
+    "collapse_subject": "Colapsar entradas con tema",
+    "composing": "Redactando",
+    "confirm_new_password": "Confirmar la nueva contraseña",
+    "current_avatar": "Tu avatar actual",
+    "current_password": "Contraseña actual",
+    "current_profile_banner": "Tu cabecera actual",
+    "data_import_export_tab": "Importar / Exportar Datos",
+    "default_vis": "Alcance de visibilidad por defecto",
+    "delete_account": "Eliminar la cuenta",
+    "discoverable": "Permitir la aparición de esta cuenta en los resultados de búsqueda y otros servicios",
+    "delete_account_description": "Eliminar para siempre la cuenta y todos los mensajes.",
+    "pad_emoji": "Rellenar con espacios al agregar emojis desde el selector",
+    "delete_account_error": "Hubo un error al eliminar tu cuenta. Si el fallo persiste, ponte en contacto con el administrador de tu instancia.",
+    "delete_account_instructions": "Escribe tu contraseña para confirmar la eliminación de tu cuenta.",
+    "avatar_size_instruction": "El tamaño mínimo recomendado para el avatar es de 150X150 píxeles.",
+    "export_theme": "Exportar tema",
+    "filtering": "Filtrado",
+    "filtering_explanation": "Todos los estados que contengan estas palabras serán silenciados, una por línea",
+    "follow_export": "Exportar personas que tú sigues",
+    "follow_export_button": "Exporta tus seguidores a un fichero csv",
+    "follow_import": "Importar personas que tú sigues",
+    "follow_import_error": "Error al importar el fichero",
+    "follows_imported": "¡Importado! Procesarlos llevará tiempo.",
+    "foreground": "Primer plano",
+    "general": "General",
+    "hide_attachments_in_convo": "Ocultar adjuntos en las conversaciones",
+    "hide_attachments_in_tl": "Ocultar adjuntos en la línea temporal",
+    "hide_muted_posts": "Ocultar las publicaciones de los usuarios silenciados",
+    "max_thumbnails": "Cantidad máxima de miniaturas por publicación",
+    "hide_isp": "Ocultar el panel específico de la instancia",
+    "preload_images": "Precargar las imágenes",
+    "use_one_click_nsfw": "Abrir los adjuntos NSFW con un solo click.",
+    "hide_post_stats": "Ocultar las estadísticas de las entradas (p.ej. el número de favoritos)",
+    "hide_user_stats": "Ocultar las estadísticas del usuario (p.ej. el número de seguidores)",
+    "hide_filtered_statuses": "Ocultar estados filtrados",
+    "import_blocks_from_a_csv_file": "Importar lista de usuarios bloqueados dese un archivo csv",
+    "import_followers_from_a_csv_file": "Importar personas que tú sigues a partir de un archivo csv",
+    "import_theme": "Importar tema",
+    "inputRadius": "Campos de entrada",
+    "checkboxRadius": "Casillas de verificación",
+    "instance_default": "(por defecto: {value})",
+    "instance_default_simple": "(por defecto)",
+    "interface": "Interfaz",
+    "interfaceLanguage": "Idioma",
+    "invalid_theme_imported": "El archivo importado no es un tema válido de Pleroma. No se han realizado cambios.",
+    "limited_availability": "No disponible en tu navegador",
+    "links": "Enlaces",
+    "lock_account_description": "Restringir el acceso a tu cuenta solo a seguidores admitidos",
+    "loop_video": "Vídeos en bucle",
+    "loop_video_silent_only": "Bucle solo en vídeos sin sonido (p.ej. \"gifs\" de Mastodon)",
+    "mutes_tab": "Silenciados",
+    "play_videos_in_modal": "Reproducir los vídeos en un marco emergente",
+    "use_contain_fit": "No recortar los adjuntos en miniaturas",
+    "name": "Nombre",
+    "name_bio": "Nombre y Biografía",
+    "new_password": "Nueva contraseña",
+    "notification_visibility": "Tipos de notificaciones a mostrar",
+    "notification_visibility_follows": "Nuevos seguidores",
+    "notification_visibility_likes": "Me gustan (Likes)",
+    "notification_visibility_mentions": "Menciones",
+    "notification_visibility_repeats": "Repeticiones (Repeats)",
+    "no_rich_text_description": "Eliminar el formato de texto enriquecido de todas las entradas",
+    "no_blocks": "No hay usuarios bloqueados",
+    "no_mutes": "No hay usuarios silenciados",
+    "hide_follows_description": "No mostrar a quién sigo",
+    "hide_followers_description": "No mostrar quién me sigue",
+    "hide_follows_count_description": "No mostrar el número de cuentas que sigo",
+    "hide_followers_count_description": "No mostrar el número de cuentas que me siguen",
+    "show_admin_badge": "Mostrar la insignia de Administrador en mi perfil",
+    "show_moderator_badge": "Mostrar la insignia de Moderador en mi perfil",
+    "nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW",
+    "oauth_tokens": "Tokens de OAuth",
+    "token": "Token",
+    "refresh_token": "Actualizar el token",
+    "valid_until": "Válido hasta",
+    "revoke_token": "Revocar",
+    "panelRadius": "Paneles",
+    "pause_on_unfocused": "Parar la transmisión cuando no estés en foco.",
+    "presets": "Por defecto",
+    "profile_background": "Fondo del Perfil",
+    "profile_banner": "Cabecera del Perfil",
+    "profile_tab": "Perfil",
+    "radii_help": "Establezca el redondeo de las esquinas de la interfaz (en píxeles)",
+    "replies_in_timeline": "Réplicas en la línea temporal",
+    "reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encima",
+    "reply_visibility_all": "Mostrar todas las réplicas",
+    "reply_visibility_following": "Solo mostrar réplicas para mí o usuarios a los que sigo",
+    "reply_visibility_self": "Solo mostrar réplicas para mí",
+    "autohide_floating_post_button": "Ocultar automáticamente el botón 'Nueva Publicación' (para móviles)",
+    "saving_err": "Error al guardar los ajustes",
+    "saving_ok": "Ajustes guardados",
+    "search_user_to_block": "Buscar usuarios a bloquear",
+    "search_user_to_mute": "Buscar usuarios a silenciar",
+    "security_tab": "Seguridad",
+    "scope_copy": "Copiar la visibilidad de la publicación cuando contestamos (En los mensajes directos (MDs) siempre se copia)",
+    "minimal_scopes_mode": "Minimizar las opciones de publicación",
+    "set_new_avatar": "Cambiar avatar",
+    "set_new_profile_background": "Cambiar el fondo del perfil",
+    "set_new_profile_banner": "Cambiar la cabecera del perfil",
+    "settings": "Ajustes",
+    "subject_input_always_show": "Mostrar siempre el campo del tema",
+    "subject_line_behavior": "Copiar el tema en las respuestas",
+    "subject_line_email": "Como email: \"re: tema\"",
+    "subject_line_mastodon": "Como mastodon: copiar como es",
+    "subject_line_noop": "No copiar",
+    "post_status_content_type": "Formato de publicación",
+    "stop_gifs": "Iniciar GIFs al pasar el ratón",
+    "streaming": "Habilitar la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior",
+    "text": "Texto",
+    "theme": "Tema",
+    "theme_help": "Use códigos de color hexadecimales (#rrggbb) para personalizar su tema de colores.",
+    "theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación. Use el botón \"Borrar todo\" para deshacer los cambios.",
+    "theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón por encima para obtener información más detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
+    "tooltipRadius": "Información/alertas",
+    "upload_a_photo": "Subir una foto",
+    "user_settings": "Ajustes del Usuario",
+    "values": {
+      "false": "no",
+      "true": "sí"
+    },
+    "notifications": "Notificaciones",
+    "notification_setting": "Recibir notificaciones de:",
+    "notification_setting_follows": "Usuarios que sigues",
+    "notification_setting_non_follows": "Usuarios que no sigues",
+    "notification_setting_followers": "Usuarios que te siguen",
+    "notification_setting_non_followers": "Usuarios que no te siguen",
+    "notification_mutes": "Para dejar de recibir notificaciones de un usuario específico, siléncialo.",
+    "notification_blocks": "El bloqueo de un usuario detiene todas las notificaciones y también las cancela.",
+    "enable_web_push_notifications": "Habilitar las notificiaciones en el navegador",
+    "style": {
+      "switcher": {
+        "keep_color": "Mantener colores",
+        "keep_shadows": "Mantener sombras",
+        "keep_opacity": "Mantener opacidad",
+        "keep_roundness": "Mantener redondeces",
+        "keep_fonts": "Mantener fuentes",
+        "save_load_hint": "Las opciones \"Mantener\" conservan las opciones configuradas actualmente al seleccionar o cargar temas, también almacena dichas opciones al exportar un tema. Cuando se desactiven todas las casillas de verificación, el tema de exportación lo guardará todo.",
+        "reset": "Reiniciar",
+        "clear_all": "Limpiar todo",
+        "clear_opacity": "Limpiar opacidad"
+      },
+      "common": {
+        "color": "Color",
+        "opacity": "Opacidad",
+        "contrast": {
+          "hint": "El ratio de contraste es {ratio}. {level} {context}",
+          "level": {
+            "aa": "Cumple con la pauta de nivel AA (mínimo)",
+            "aaa": "Cumple con la pauta de nivel AAA (recomendado)",
+            "bad": "No cumple con las pautas de accesibilidad"
+          },
+          "context": {
+            "18pt": "para textos grandes (+18pt)",
+            "text": "para textos"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "Común",
+        "main": "Colores comunes",
+        "foreground_hint": "Vea la pestaña \"Avanzado\" para un control más detallado",
+        "rgbo": "Iconos, acentos, insignias"
+      },
+      "advanced_colors": {
+        "_tab_label": "Avanzado",
+        "alert": "Fondo de Alertas",
+        "alert_error": "Error",
+        "badge": "Fondo de Insignias",
+        "badge_notification": "Notificaciones",
+        "panel_header": "Cabecera del panel",
+        "top_bar": "Barra superior",
+        "borders": "Bordes",
+        "buttons": "Botones",
+        "inputs": "Campos de entrada",
+        "faint_text": "Texto desvanecido"
+      },
+      "radii": {
+        "_tab_label": "Redondez"
+      },
+      "shadows": {
+        "_tab_label": "Sombra e iluminación",
+        "component": "Componente",
+        "override": "Sobreescribir",
+        "shadow_id": "Sombra #{value}",
+        "blur": "Difuminar",
+        "spread": "Cantidad",
+        "inset": "Sombra interior",
+        "hint": "Para las sombras, también puede usar --variable como un valor de color para usar las variables CSS3. Tenga en cuenta que establecer la opacidad no funcionará en este caso.",
+        "filter_hint": {
+          "always_drop_shadow": "Advertencia, esta sombra siempre usa {0} cuando el navegador lo soporta.",
+          "drop_shadow_syntax": "{0} no soporta el parámetro {1} y la palabra clave {2}.",
+          "avatar_inset": "Tenga en cuenta que la combinación de sombras interiores como no-interiores en los avatares, puede dar resultados inesperados con los avatares transparentes.",
+          "spread_zero": "Sombras con una cantidad > 0 aparecerá como si estuviera puesto a cero",
+          "inset_classic": "Las sombras interiores estarán usando {0}"
+        },
+        "components": {
+          "panel": "Panel",
+          "panelHeader": "Cabecera del panel",
+          "topBar": "Barra superior",
+          "avatar": "Avatar del usuario (en la vista del perfil)",
+          "avatarStatus": "Avatar del usuario (en la vista de la entrada)",
+          "popup": "Ventanas y textos emergentes (popups & tooltips)",
+          "button": "Botones",
+          "buttonHover": "Botón (encima)",
+          "buttonPressed": "Botón (presionado)",
+          "buttonPressedHover": "Botón (presionado+encima)",
+          "input": "Campo de entrada"
+        }
+      },
+      "fonts": {
+        "_tab_label": "Fuentes",
+        "help": "Seleccione la fuente a utilizar para los elementos de la interfaz de usuario. Para \"personalizar\", debe ingresar el nombre exacto de la fuente tal como aparece en el sistema.",
+        "components": {
+          "interface": "Interfaz",
+          "input": "Campos de entrada",
+          "post": "Texto de publicaciones",
+          "postCode": "Texto monoespaciado en publicación (texto enriquecido)"
+        },
+        "family": "Nombre de la fuente",
+        "size": "Tamaño (en px)",
+        "weight": "Peso (negrita)",
+        "custom": "Personalizado"
+      },
+      "preview": {
+        "header": "Vista previa",
+        "content": "Contenido",
+        "error": "Ejemplo de error",
+        "button": "Botón",
+        "text": "Un montón de {0} y {1}",
+        "mono": "contenido",
+        "input": "Acaba de aterrizar en L.A.",
+        "faint_link": "manual útil",
+        "fine_print": "¡Lea nuestro {0} para aprender nada útil!",
+        "header_faint": "Esto está bien",
+        "checkbox": "He revisado los términos y condiciones",
+        "link": "un bonito enlace"
+      }
+    },
+    "version": {
+      "title": "Versión",
+      "backend_version": "Versión del Backend",
+      "frontend_version": "Versión del Frontend"
+    }
+  },
+  "time": {
+    "day": "{0} día",
+    "days": "{0} días",
+    "day_short": "{0}d",
+    "days_short": "{0}d",
+    "hour": "{0} hora",
+    "hours": "{0} horas",
+    "hour_short": "{0}h",
+    "hours_short": "{0}h",
+    "in_future": "en {0}",
+    "in_past": "hace {0}",
+    "minute": "{0} minuto",
+    "minutes": "{0} minutos",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} mes",
+    "months": "{0} meses",
+    "month_short": "{0}m",
+    "months_short": "{0}m",
+    "now": "justo ahora",
+    "now_short": "ahora",
+    "second": "{0} segundo",
+    "seconds": "{0} segundos",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} semana",
+    "weeks": "{0} semanas",
+    "week_short": "{0}sem",
+    "weeks_short": "{0}sem",
+    "year": "{0} año",
+    "years": "{0} años",
+    "year_short": "{0}a",
+    "years_short": "{0}a"
+  },
+  "timeline": {
+    "collapse": "Colapsar",
+    "conversation": "Conversación",
+    "error_fetching": "Error al cargar las actualizaciones",
+    "load_older": "Cargar actualizaciones anteriores",
+    "no_retweet_hint": "La publicación está marcada como solo para seguidores o directa y no se puede repetir",
+    "repeated": "repetida",
+    "show_new": "Mostrar lo nuevo",
+    "up_to_date": "Actualizado",
+    "no_more_statuses": "No hay más estados",
+    "no_statuses": "Sin estados"
+  },
+  "status": {
+    "favorites": "Favoritos",
+    "repeats": "Repetidos",
+    "delete": "Eliminar publicación",
+    "pin": "Fijar en tu perfil",
+    "unpin": "Desclavar de tu perfil",
+    "pinned": "Fijado",
+    "delete_confirm": "¿Realmente quieres borrar la publicación?",
+    "reply_to": "Respondiendo a",
+    "replies_list": "Respuestas:",
+    "mute_conversation": "Silenciar la conversación",
+    "unmute_conversation": "Mostrar la conversación"
+  },
+  "user_card": {
+    "approve": "Aprobar",
+    "block": "Bloquear",
+    "blocked": "¡Bloqueado!",
+    "deny": "Denegar",
+    "favorites": "Favoritos",
+    "follow": "Seguir",
+    "follow_sent": "¡Solicitud enviada!",
+    "follow_progress": "Solicitando…",
+    "follow_again": "¿Enviar solicitud de nuevo?",
+    "follow_unfollow": "Dejar de seguir",
+    "followees": "Siguiendo",
+    "followers": "Seguidores",
+    "following": "¡Siguiendo!",
+    "follows_you": "¡Te sigue!",
+    "its_you": "¡Eres tú!",
+    "media": "Media",
+    "mention": "Mencionar",
+    "mute": "Silenciar",
+    "muted": "Silenciado",
+    "per_day": "por día",
+    "remote_follow": "Seguir",
+    "report": "Reportar",
+    "statuses": "Estados",
+    "subscribe": "Suscribirse",
+    "unsubscribe": "Desuscribirse",
+    "unblock": "Desbloquear",
+    "unblock_progress": "Desbloqueando...",
+    "block_progress": "Bloqueando...",
+    "unmute": "Quitar silencio",
+    "unmute_progress": "Quitando silencio...",
+    "mute_progress": "Silenciando...",
+    "admin_menu": {
+      "moderation": "Moderación",
+      "grant_admin": "Conceder permisos de Administrador",
+      "revoke_admin": "Revocar permisos de Administrador",
+      "grant_moderator": "Conceder permisos de Moderador",
+      "revoke_moderator": "Revocar permisos de Moderador",
+      "activate_account": "Activar cuenta",
+      "deactivate_account": "Desactivar cuenta",
+      "delete_account": "Eliminar cuenta",
+      "force_nsfw": "Marcar todas las publicaciones como NSFW (no es seguro/apropiado para el trabajo)",
+      "strip_media": "Eliminar archivos multimedia de las publicaciones",
+      "force_unlisted": "Forzar que se publique en el modo -Sin Listar-",
+      "sandbox": "Forzar que se publique solo para tus seguidores",
+      "disable_remote_subscription": "No permitir que usuarios de instancias remotas te siga.",
+      "disable_any_subscription": "No permitir que ningún usuario te siga",
+      "quarantine": "No permitir publicaciones de usuarios de instancias remotas",
+      "delete_user": "Eliminar usuario",
+      "delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer."
+    }
+  },
+  "user_profile": {
+    "timeline_title": "Linea Temporal del Usuario",
+    "profile_does_not_exist": "Lo sentimos, este perfil no existe.",
+    "profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil."
+  },
+  "user_reporting": {
+    "title": "Reportando a {0}",
+    "add_comment_description": "El informe será enviado a los moderadores de su instancia. Puedes proporcionar una explicación de por qué estás reportando esta cuenta a continuación:",
+    "additional_comments": "Comentarios adicionales",
+    "forward_description": "La cuenta es de otro servidor. ¿Enviar una copia del informe allí también?",
+    "forward_to": "Reenviar a {0}",
+    "submit": "Enviar",
+    "generic_error": "Se produjo un error al procesar la solicitud."
+  },
+  "who_to_follow": {
+    "more": "Más",
+    "who_to_follow": "A quién seguir"
+  },
+  "tool_tip": {
+    "media_upload": "Subir Medios",
+    "repeat": "Repetir",
+    "reply": "Contestar",
+    "favorite": "Favorito",
+    "user_settings": "Ajustes de usuario"
+  },
+  "upload": {
+    "error": {
+      "base": "Subida fallida.",
+      "file_too_big": "Archivo demasiado grande [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+      "default": "Inténtalo más tarde"
+    },
+    "file_size_units": {
+      "B": "B",
+      "KiB": "KiB",
+      "MiB": "MiB",
+      "GiB": "GiB",
+      "TiB": "TiB"
+    }
+  },
+  "search": {
+    "people": "Personas",
+    "hashtags": "Etiquetas",
+    "person_talking": "{count} personas hablando",
+    "people_talking": "{count} gente hablando",
+    "no_results": "Sin resultados"
+  },
+  "password_reset": {
+    "forgot_password": "¿Contraseña olvidada?",
+    "password_reset": "Restablecer la contraseña",
+    "instruction": "Ingrese su dirección de correo electrónico o nombre de usuario. Le enviaremos un enlace para restablecer su contraseña.",
+    "placeholder": "Su correo electrónico o nombre de usuario",
+    "check_email": "Revise su correo electrónico para obtener un enlace para restablecer su contraseña.",
+    "return_home": "Volver a la página de inicio",
+    "not_found": "No pudimos encontrar ese correo electrónico o nombre de usuario.",
+    "too_many_requests": "Has alcanzado el límite de intentos, vuelve a intentarlo más tarde.",
+    "password_reset_disabled": "El restablecimiento de contraseñas está deshabilitado. Póngase en contacto con el administrador de su instancia."
+  }
 }
diff --git a/src/i18n/et.json b/src/i18n/et.json
index d7bc3a9d..b5ae4275 100644
--- a/src/i18n/et.json
+++ b/src/i18n/et.json
@@ -1,461 +1,461 @@
 {
-    "finder": {
-        "error_fetching_user": "Viga kasutaja leidmisel",
-        "find_user": "Otsi kasutajaid"
+  "finder": {
+    "error_fetching_user": "Viga kasutaja leidmisel",
+    "find_user": "Otsi kasutajaid"
+  },
+  "general": {
+    "submit": "Postita",
+    "verify": "Kinnita",
+    "confirm": "Kinnita",
+    "enable": "Luba",
+    "disable": "Keela",
+    "cancel": "Tühista",
+    "dismiss": "Olgu",
+    "show_less": "Kuva vähem",
+    "show_more": "Kuva rohkem",
+    "optional": "valikuline",
+    "generic_error": "Esines viga",
+    "more": "Rohkem",
+    "apply": "Rakenda"
+  },
+  "login": {
+    "login": "Logi sisse",
+    "logout": "Logi välja",
+    "password": "Parool",
+    "placeholder": "nt lain",
+    "register": "Registreeru",
+    "username": "Kasutajanimi",
+    "heading": {
+      "recovery": "Kaheastmelise autentimise taaste",
+      "totp": "Kaheastmeline autentimine"
     },
-    "general": {
-        "submit": "Postita",
-        "verify": "Kinnita",
-        "confirm": "Kinnita",
-        "enable": "Luba",
-        "disable": "Keela",
-        "cancel": "Tühista",
-        "dismiss": "Olgu",
-        "show_less": "Kuva vähem",
-        "show_more": "Kuva rohkem",
-        "optional": "valikuline",
-        "generic_error": "Esines viga",
-        "more": "Rohkem",
-        "apply": "Rakenda"
+    "recovery_code": "Taastekood",
+    "enter_two_factor_code": "Sisesta kaheastmelise autentimise kood",
+    "enter_recovery_code": "Sisesta taastekood",
+    "authentication_code": "Autentimiskood",
+    "hint": "Logi sisse, et liituda vestlusega",
+    "description": "Logi sisse OAuthiga"
+  },
+  "nav": {
+    "mentions": "Mainimised",
+    "public_tl": "Avalik Ajajoon",
+    "timeline": "Ajajoon",
+    "twkn": "Kogu Teadaolev Võrgustik",
+    "preferences": "Eelistused",
+    "who_to_follow": "Keda jälgida",
+    "search": "Otsing",
+    "user_search": "Kasutajaotsing",
+    "dms": "Privaatsõnumid",
+    "interactions": "Interaktsioonid",
+    "friend_requests": "Jägimistaotlused",
+    "chat": "Kohalik vestlus",
+    "back": "Tagasi",
+    "administration": "Administreerimine",
+    "about": "Meist"
+  },
+  "notifications": {
+    "followed_you": "alustas sinu jälgimist",
+    "notifications": "Teated",
+    "read": "Loe!",
+    "reacted_with": "reageeris {0}",
+    "migrated_to": "kolis",
+    "no_more_notifications": "Rohkem teateid ei ole",
+    "repeated_you": "taaspostitas su staatuse",
+    "load_older": "Laadi vanemad teated",
+    "follow_request": "soovib Teid jälgida",
+    "favorited_you": "lisas su staatuse lemmikuks",
+    "broken_favorite": "Tundmatu staatus, otsin…"
+  },
+  "post_status": {
+    "default": "Just sõitsin elektrirongiga Tallinnast Pääskülla.",
+    "posting": "Postitan",
+    "scope": {
+      "unlisted": "Peidetud - Ära postita avalikele ajajoontele",
+      "public": "Avalil - Postita avalikele ajajoontele",
+      "private": "Jälgijatele - Postita ainult jälgijatele",
+      "direct": "Privaatne - Postita ainult mainitud kasutajatele"
     },
-    "login": {
-        "login": "Logi sisse",
-        "logout": "Logi välja",
-        "password": "Parool",
-        "placeholder": "nt lain",
-        "register": "Registreeru",
-        "username": "Kasutajanimi",
-        "heading": {
-            "recovery": "Kaheastmelise autentimise taaste",
-            "totp": "Kaheastmeline autentimine"
+    "scope_notice": {
+      "unlisted": "See postitus ei ole nähtav avalikul ega kogu võrgu ajajoonel",
+      "private": "See postitus on nähtav ainult Teie jälgijatele",
+      "public": "See postitus on nähtav kõigile"
+    },
+    "direct_warning_to_first_only": "See postitus on nähtav ainult kirja alguses mainitud kasutajatele.",
+    "direct_warning_to_all": "See postitus on nähtav kõikidele mainitud kasutajatele.",
+    "content_warning": "Pealkiri (valikuline)",
+    "content_type": {
+      "text/bbcode": "BBCode",
+      "text/markdown": "Markdown",
+      "text/html": "HTML",
+      "text/plain": "Lihttekst"
+    },
+    "attachments_sensitive": "Märgi manused sensitiivseks",
+    "account_not_locked_warning_link": "lukus",
+    "account_not_locked_warning": "Teie konto ei ole {0}. Kõik võivad Teid jälgida, et näha Teie ainult-jälgijatele postitusi.",
+    "new_status": "Postita uus staatus"
+  },
+  "registration": {
+    "bio": "Bio",
+    "email": "E-post",
+    "fullname": "Kuvatav nimi",
+    "password_confirm": "Parooli kinnitamine",
+    "registration": "Registreerimine",
+    "validations": {
+      "password_confirmation_match": "peaks olema sama kui salasõna",
+      "password_confirmation_required": "ei saa jätta tühjaks",
+      "password_required": "ei saa jätta tühjaks",
+      "email_required": "ei saa jätta tühjaks",
+      "fullname_required": "ei saa jätta tühjaks",
+      "username_required": "ei saa jätta tühjaks"
+    },
+    "fullname_placeholder": "Näiteks Lain Iwakura",
+    "username_placeholder": "Näiteks lain",
+    "new_captcha": "Vajuta pildile, et saada uus captcha",
+    "captcha": "CAPTCHA",
+    "token": "Kutse võti"
+  },
+  "settings": {
+    "attachments": "Manused",
+    "autoload": "Luba ajajoone automaatne uuendamine kui ajajoon on põhja keritud",
+    "avatar": "Profiilipilt",
+    "bio": "Bio",
+    "current_avatar": "Sinu praegune profiilipilt",
+    "current_profile_banner": "Praegune profiilibänner",
+    "filtering": "Sisu filtreerimine",
+    "filtering_explanation": "Kõiki staatuseid, mis sisaldavad neid sõnu, ei kuvata. Üks sõna reale",
+    "hide_attachments_in_convo": "Peida manused vastlustes",
+    "hide_attachments_in_tl": "Peida manused ajajoonel",
+    "name": "Nimi",
+    "name_bio": "Nimi ja Bio",
+    "nsfw_clickthrough": "Peida tööks-mittesobivad(NSFW) manuste hiireklõpsu taha",
+    "profile_background": "Profiilitaust",
+    "profile_banner": "Profiilibänner",
+    "reply_link_preview": "Luba algpostituse kuvamine vastustes",
+    "set_new_avatar": "Vali uus profiilipilt",
+    "set_new_profile_background": "Vali uus profiilitaust",
+    "set_new_profile_banner": "Vali uus profiilibänner",
+    "settings": "Sätted",
+    "theme": "Teema",
+    "user_settings": "Kasutaja sätted",
+    "subject_line_noop": "Ära kopeeri",
+    "subject_line_mastodon": "Nagu mastodon: kopeeri nagu on",
+    "subject_line_email": "Nagu e-post: \"vs: pealkiri\"",
+    "subject_line_behavior": "Kopeeri pealkiri vastamisel",
+    "subject_input_always_show": "Alati kuva pealkirja välja",
+    "minimal_scopes_mode": "Peida postituse nähtavussätted",
+    "scope_copy": "Kopeeri nähtavussätted vastamisel (Privaatsed on alati kopeeritud)",
+    "security_tab": "Turvalisus",
+    "search_user_to_mute": "Otsi, keda soovid vaigistada",
+    "search_user_to_block": "Otsi, keda soovid blokeerida",
+    "saving_ok": "Sätted salvestatud",
+    "saving_err": "Sätete salvestamine ebaõnnestus",
+    "autohide_floating_post_button": "Automaatselt peida uue postituse nupp (mobiilil)",
+    "reply_visibility_self": "Näita ainult vastuseid, mis on suunatud mulle",
+    "reply_visibility_following": "Näita ainult vastuseid, mis on suunatud mulle või kasutajatele, keda jälgin",
+    "reply_visibility_all": "Näita kõiki vastuseid",
+    "replies_in_timeline": "Vastused ajajoonel",
+    "radii_help": "Liidese ümardamine (pikslites)",
+    "profile_tab": "Profiil",
+    "presets": "Salvestatud sätted",
+    "pause_on_unfocused": "Peata reaalajas voog kui leht pole fookuses",
+    "panelRadius": "Paneelid",
+    "revoke_token": "Keela",
+    "valid_until": "Kehtiv kuni",
+    "refresh_token": "Värskendustoken",
+    "token": "Token",
+    "oauth_tokens": "OAuth tokenid",
+    "show_moderator_badge": "Näita Moderaator silti mu profiilil",
+    "show_admin_badge": "Näita Admin silti mu profiilil",
+    "hide_followers_count_description": "Ära näita minu jälgijate arvu",
+    "hide_follows_count_description": "Ära näita minu jälgimiste arvu",
+    "hide_followers_description": "Ära näita minu jälgijaid",
+    "hide_follows_description": "Ära näita minu jälgimisi",
+    "no_mutes": "Vaigistusi pole",
+    "no_blocks": "Blokeeringuid pole",
+    "no_rich_text_description": "Muuda kõik postitused lihttekstiks",
+    "notification_visibility_emoji_reactions": "Reaktsioonid",
+    "notification_visibility_moves": "Kasutaja kolimised",
+    "notification_visibility_repeats": "Taaspostitused",
+    "notification_visibility_mentions": "Mainimised",
+    "notification_visibility_likes": "Lemmikud",
+    "notification_visibility_follows": "Jälgimised",
+    "notification_visibility": "Milliseid teateid kuvatakse",
+    "new_password": "Uus salasõna",
+    "new_email": "Uus e-post",
+    "use_contain_fit": "Näita eelvaadetes täis suuruses pilte",
+    "play_videos_in_modal": "Näita videoid eraldi raamis",
+    "mutes_tab": "Vaigistused",
+    "loop_video_silent_only": "Loop videod, millel pole heli (nt. Mastodoni \"gifid\")",
+    "loop_video": "Loop videod",
+    "lock_account_description": "Piira oma konto ainult lubatud jälgijatele",
+    "links": "Lingid",
+    "limited_availability": "Pole Teie veebilehitsejas saadaval",
+    "invalid_theme_imported": "Valitud fail ei ole Pleroma kujundus. Kujundusele muudatusi ei tehtud.",
+    "interfaceLanguage": "Liidese keel",
+    "interface": "Liides",
+    "instance_default_simple": "(vaikimisi)",
+    "instance_default": "(vaikimisi: {value})",
+    "checkboxRadius": "Märkeruudud",
+    "inputRadius": "Sisestuskastid",
+    "import_theme": "Lae sätted",
+    "import_followers_from_a_csv_file": "Impordi jälgimised csv failist",
+    "import_blocks_from_a_csv_file": "Impordi blokeeringud csv failist",
+    "hide_filtered_statuses": "Peida filtreeritud staatused",
+    "hide_user_stats": "Peida kasutaja statistika (nt. jälgijate arv)",
+    "hide_post_stats": "Peida postituse statistika (nt. lemmikute arv)",
+    "use_one_click_nsfw": "Ava NSFW manused ühe klikiga",
+    "preload_images": "Piltide eellaadimine",
+    "hide_isp": "Peida instantsipõhine paneel",
+    "max_thumbnails": "Maksimaalne lubatud eelvaadete arv postituste kohta",
+    "hide_muted_posts": "Peida vaigistatud kasutajate postitused",
+    "general": "Üldine",
+    "foreground": "Esiplaan",
+    "accent": "Rõhk",
+    "follows_imported": "Jälgimised imporditud! Nende töötlemine võtab natuke aega.",
+    "follow_import_error": "Jälgimiste importimisel tekkis viga",
+    "follow_import": "Impordi jälgimised",
+    "follow_export_button": "Ekspordi oma jälgimised csv failiks",
+    "follow_export": "Ekspordi jälgimised",
+    "export_theme": "Salvesta sätted",
+    "emoji_reactions_on_timeline": "Näita reaktsioone ajajoonel",
+    "pad_emoji": "Lisa emotikonidele tühikud ette ja järgi neid menüüst valides",
+    "avatar_size_instruction": "Profiilipildi soovitatud minimaalne suurus on 150x150 pikslit.",
+    "domain_mutes": "Domeenid",
+    "discoverable": "Luba selle konto ilmumine otsingutulemustes ning muudes teenustes",
+    "delete_account_instructions": "Konto kustutamise kinnitamiseks sisestage oma salasõna.",
+    "delete_account_error": "Teie konto kustutamisel tekkis viga. Kui see jätkub, palun võtke kontakti administraatoriga.",
+    "delete_account_description": "Jäädavalt kustuta oma andmed ja konto.",
+    "delete_account": "Kustuta konto",
+    "default_vis": "Vaikimisi nähtavus",
+    "data_import_export_tab": "Andmete import / eksport",
+    "current_password": "Praegune salasõna",
+    "confirm_new_password": "Kinnita uus salasõna",
+    "composing": "Koostamine",
+    "collapse_subject": "Peida postituste pealkirjad",
+    "changed_password": "Salasõna edukalt muudetud!",
+    "change_password_error": "Esines viga salasõna muutmisel.",
+    "change_password": "Muuda salasõna",
+    "changed_email": "E-post edukalt muudetud!",
+    "change_email_error": "Esines viga e-posti muutmisel.",
+    "change_email": "Muuda e-posti",
+    "cRed": "Punane (Tühista)",
+    "cOrange": "Oranž (Lisa lemmikuks)",
+    "cGreen": "Roheline (Taaspostita)",
+    "cBlue": "Sinine (Vasta, jälgi)",
+    "btnRadius": "Nupud",
+    "blocks_tab": "Blokeeringud",
+    "blocks_imported": "Blokeeringud imporditud! Nende töötlemine võtab natuke aega.",
+    "block_import_error": "Blokeeringute importimisel esines viga",
+    "block_import": "Blokeeringute import",
+    "block_export_button": "Ekspordi oma blokeeringud csv failiks",
+    "block_export": "Blokeeringute eksport",
+    "background": "Taust",
+    "avatarRadius": "Profiilipildid",
+    "avatarAltRadius": "Profiilipildid (Teated)",
+    "attachmentRadius": "Manused",
+    "allow_following_move": "Luba automaatjälgimine kui jälgitav konto kolib",
+    "mfa": {
+      "verify": {
+        "desc": "Et lubada kaheastmelist autentimist, sisestage kood oma äpist:"
+      },
+      "scan": {
+        "desc": "Kasutades oma kaheastmelise autentimise äppi, skännige see QR kood või sisestage tekstiline võti:",
+        "secret_code": "Võti",
+        "title": "Skänni"
+      },
+      "authentication_methods": "Autentimismeetodid",
+      "recovery_codes_warning": "Kirjutage need koodid üles ning hoidke need kindlas kohas. Kui Te kaotate ligipääsu oma kaheastmelise autentimise äppile ning nendele koodidele, ei ole Teil võimalik oma kontosse sisse logida.",
+      "waiting_a_recovery_codes": "Laen taastekoode…",
+      "recovery_codes": "Taastekoodid.",
+      "warning_of_generate_new_codes": "Kui Te loote uued taastekoodid, Teie vanad koodid ei tööta enam.",
+      "generate_new_recovery_codes": "Loo uued taastekoodid",
+      "title": "Kaheastmeline autentimine",
+      "confirm_and_enable": "Kinnita & luba OTP",
+      "wait_pre_setup_otp": "sean üles OTP",
+      "setup_otp": "Sea üles OTP",
+      "otp": "OTP"
+    },
+    "enter_current_password_to_confirm": "Sisetage isiku tõestamiseks oma salasõna",
+    "security": "Turvalisus",
+    "app_name": "Rakenduse nimi",
+    "style": {
+      "switcher": {
+        "help": {
+          "snapshot_present": "Kujunduse eelvaade on laetud, nii et kõik väärtused on üle kirjutatud. Te saate laadida ka kujunduse päris sisu.",
+          "older_version_imported": "Teie imporditud fail oli loodud vanemas versioonis.",
+          "future_version_imported": "Teie imporditud fail oli loodud uuemas versioonis.",
+          "v2_imported": "Teie imporditud fail oli vanema versiooni jaoks. Me üritame hoida ühilduvust, kuid ikkagi võib esineda erinevusi.",
+          "upgraded_from_v2": "PleromaFE-d uuendati, teie kujundus võib välja näha natuke erinev, kui mäletate."
         },
-        "recovery_code": "Taastekood",
-        "enter_two_factor_code": "Sisesta kaheastmelise autentimise kood",
-        "enter_recovery_code": "Sisesta taastekood",
-        "authentication_code": "Autentimiskood",
-        "hint": "Logi sisse, et liituda vestlusega",
-        "description": "Logi sisse OAuthiga"
+        "use_source": "Uus versioon",
+        "use_snapshot": "Vana versioon",
+        "keep_as_is": "Jäta nii, nagu on",
+        "load_theme": "Lae kujundus",
+        "clear_opacity": "Tühista läbipaistvus",
+        "clear_all": "Tühista kõik",
+        "reset": "Taasta algne",
+        "keep_fonts": "Jäta fondid",
+        "keep_roundness": "Jäta ümarus",
+        "keep_opacity": "Jäta läbipaistvus",
+        "keep_shadows": "Jäta varjud",
+        "keep_color": "Jäta värvid"
+      }
     },
-    "nav": {
-        "mentions": "Mainimised",
-        "public_tl": "Avalik Ajajoon",
-        "timeline": "Ajajoon",
-        "twkn": "Kogu Teadaolev Võrgustik",
-        "preferences": "Eelistused",
-        "who_to_follow": "Keda jälgida",
-        "search": "Otsing",
-        "user_search": "Kasutajaotsing",
-        "dms": "Privaatsõnumid",
-        "interactions": "Interaktsioonid",
-        "friend_requests": "Jägimistaotlused",
-        "chat": "Kohalik vestlus",
-        "back": "Tagasi",
-        "administration": "Administreerimine",
-        "about": "Meist"
+    "enable_web_push_notifications": "Luba veebipõhised push-teated",
+    "notification_blocks": "Kasutaja blokeerimisel ei tule neilt enam teateid ning nendele teilt ka mitte.",
+    "notification_setting_privacy_option": "Peida saatja ning sisu push-teadetelt",
+    "notification_setting": "Saa teateid nendelt:",
+    "notifications": "Teated",
+    "notification_mutes": "Kui soovid mõnelt kasutajalt mitte teateid saada, kasuta vaigistust.",
+    "notification_setting_privacy": "Privaatsus",
+    "notification_setting_non_followers": "Kasutajatelt, kes sind ei jälgi",
+    "notification_setting_followers": "Kasutajatelt, kes jälgivad sind",
+    "notification_setting_non_follows": "Kasutajatelt, keda sa ei jälgi",
+    "notification_setting_follows": "Kasutajatelt, keda jälgid",
+    "notification_setting_filters": "Filtrid",
+    "greentext": "Meemi nooled",
+    "fun": "Naljad",
+    "values": {
+      "true": "jah",
+      "false": "ei"
     },
-    "notifications": {
-        "followed_you": "alustas sinu jälgimist",
-        "notifications": "Teated",
-        "read": "Loe!",
-        "reacted_with": "reageeris {0}",
-        "migrated_to": "kolis",
-        "no_more_notifications": "Rohkem teateid ei ole",
-        "repeated_you": "taaspostitas su staatuse",
-        "load_older": "Laadi vanemad teated",
-        "follow_request": "soovib Teid jälgida",
-        "favorited_you": "lisas su staatuse lemmikuks",
-        "broken_favorite": "Tundmatu staatus, otsin…"
+    "upload_a_photo": "Lae üles foto",
+    "type_domains_to_mute": "Trüki siia domeene, mida vaigistada",
+    "tooltipRadius": "Vihjed/hoiatused",
+    "theme_help_v2_1": "Te saate ka mõndade komponentide värvust ning läbipaistvust üle kirjutada vajutades ruudule. Kasuta \"Tühista kõik\" nuppu, et need tühistada.",
+    "theme_help": "Kasuta hex värvikoode (#rrggbb) oma kujunduse isikupärastamiseks.",
+    "text": "Tekst",
+    "useStreamingApiWarning": "(Pole soovituslik, eksperimentaalne, on teada, et jätab postitusi vahele)",
+    "useStreamingApi": "Saa postitusi ning teateid reaalajas",
+    "user_mutes": "Kasutajad",
+    "streaming": "Luba uute postituste automaatvoog kui oled lehekülje alguses",
+    "stop_gifs": "Mängi GIFid hiirega ületades",
+    "post_status_content_type": "Postituse sisutüüp"
+  },
+  "timeline": {
+    "conversation": "Vestlus",
+    "error_fetching": "Viga uuenduste laadimisel",
+    "load_older": "Kuva vanemaid staatuseid",
+    "show_new": "Näita uusi",
+    "up_to_date": "Uuendatud"
+  },
+  "user_card": {
+    "block": "Blokeeri",
+    "blocked": "Blokeeritud!",
+    "follow": "Jälgi",
+    "followees": "Jälgitavaid",
+    "followers": "Jälgijaid",
+    "following": "Jälgin!",
+    "follows_you": "Jälgib sind!",
+    "mute": "Vaigista",
+    "muted": "Vaigistatud",
+    "per_day": "päevas",
+    "statuses": "Staatuseid"
+  },
+  "about": {
+    "mrf": {
+      "mrf_policies_desc": "MRF poliitikad mõjutavad selle instansi föderatsiooni käitumist.    Järgmised poliitikad on lubatud:",
+      "simple": {
+        "media_nsfw_desc": "See instants määrab nendest instantsidest postituste meedia sensitiivseks:",
+        "media_nsfw": "Meedia määratakse sensitiivseks",
+        "media_removal_desc": "See instants eemaldab meedia postitustelt nendest instantsidest:",
+        "media_removal": "Meedia eemaldamine",
+        "ftl_removal_desc": "See instants eemaldab postitused nendelt instantsidest \"Kogu teatud võrgu\" ajajoonelt:",
+        "ftl_removal": "\"Kogu teatud võrgu\" ajajoonelt eemaldamine",
+        "quarantine_desc": "See instants saadab ainult avalikke postitusi järgmistele instantsidele:",
+        "quarantine": "Karantiini",
+        "reject_desc": "See instants ei luba sõnumeid nendest instantsidest:",
+        "reject": "Keela",
+        "accept_desc": "See instants lubab sõnumeid ainult nendest instantsidest:",
+        "accept": "Luba",
+        "simple_policies": "Instansi-omased poliitikad"
+      },
+      "mrf_policies": "Lubatud MRF poliitikad",
+      "keyword": {
+        "is_replaced_by": "→",
+        "replace": "Vaheta",
+        "reject": "Lükka tagasi",
+        "ftl_removal": "\"Kogu teatud võrgu\" ajajoonelt eemaldamine",
+        "keyword_policies": "Võtmesõna poliitikad"
+      },
+      "federation": "Föderatsioon"
     },
-    "post_status": {
-        "default": "Just sõitsin elektrirongiga Tallinnast Pääskülla.",
-        "posting": "Postitan",
-        "scope": {
-            "unlisted": "Peidetud - Ära postita avalikele ajajoontele",
-            "public": "Avalil - Postita avalikele ajajoontele",
-            "private": "Jälgijatele - Postita ainult jälgijatele",
-            "direct": "Privaatne - Postita ainult mainitud kasutajatele"
-        },
-        "scope_notice": {
-            "unlisted": "See postitus ei ole nähtav avalikul ega kogu võrgu ajajoonel",
-            "private": "See postitus on nähtav ainult Teie jälgijatele",
-            "public": "See postitus on nähtav kõigile"
-        },
-        "direct_warning_to_first_only": "See postitus on nähtav ainult kirja alguses mainitud kasutajatele.",
-        "direct_warning_to_all": "See postitus on nähtav kõikidele mainitud kasutajatele.",
-        "content_warning": "Pealkiri (valikuline)",
-        "content_type": {
-            "text/bbcode": "BBCode",
-            "text/markdown": "Markdown",
-            "text/html": "HTML",
-            "text/plain": "Lihttekst"
-        },
-        "attachments_sensitive": "Märgi manused sensitiivseks",
-        "account_not_locked_warning_link": "lukus",
-        "account_not_locked_warning": "Teie konto ei ole {0}. Kõik võivad Teid jälgida, et näha Teie ainult-jälgijatele postitusi.",
-        "new_status": "Postita uus staatus"
-    },
-    "registration": {
-        "bio": "Bio",
-        "email": "E-post",
-        "fullname": "Kuvatav nimi",
-        "password_confirm": "Parooli kinnitamine",
-        "registration": "Registreerimine",
-        "validations": {
-            "password_confirmation_match": "peaks olema sama kui salasõna",
-            "password_confirmation_required": "ei saa jätta tühjaks",
-            "password_required": "ei saa jätta tühjaks",
-            "email_required": "ei saa jätta tühjaks",
-            "fullname_required": "ei saa jätta tühjaks",
-            "username_required": "ei saa jätta tühjaks"
-        },
-        "fullname_placeholder": "Näiteks Lain Iwakura",
-        "username_placeholder": "Näiteks lain",
-        "new_captcha": "Vajuta pildile, et saada uus captcha",
-        "captcha": "CAPTCHA",
-        "token": "Kutse võti"
-    },
-    "settings": {
-        "attachments": "Manused",
-        "autoload": "Luba ajajoone automaatne uuendamine kui ajajoon on põhja keritud",
-        "avatar": "Profiilipilt",
-        "bio": "Bio",
-        "current_avatar": "Sinu praegune profiilipilt",
-        "current_profile_banner": "Praegune profiilibänner",
-        "filtering": "Sisu filtreerimine",
-        "filtering_explanation": "Kõiki staatuseid, mis sisaldavad neid sõnu, ei kuvata. Üks sõna reale",
-        "hide_attachments_in_convo": "Peida manused vastlustes",
-        "hide_attachments_in_tl": "Peida manused ajajoonel",
-        "name": "Nimi",
-        "name_bio": "Nimi ja Bio",
-        "nsfw_clickthrough": "Peida tööks-mittesobivad(NSFW) manuste hiireklõpsu taha",
-        "profile_background": "Profiilitaust",
-        "profile_banner": "Profiilibänner",
-        "reply_link_preview": "Luba algpostituse kuvamine vastustes",
-        "set_new_avatar": "Vali uus profiilipilt",
-        "set_new_profile_background": "Vali uus profiilitaust",
-        "set_new_profile_banner": "Vali uus profiilibänner",
-        "settings": "Sätted",
-        "theme": "Teema",
-        "user_settings": "Kasutaja sätted",
-        "subject_line_noop": "Ära kopeeri",
-        "subject_line_mastodon": "Nagu mastodon: kopeeri nagu on",
-        "subject_line_email": "Nagu e-post: \"vs: pealkiri\"",
-        "subject_line_behavior": "Kopeeri pealkiri vastamisel",
-        "subject_input_always_show": "Alati kuva pealkirja välja",
-        "minimal_scopes_mode": "Peida postituse nähtavussätted",
-        "scope_copy": "Kopeeri nähtavussätted vastamisel (Privaatsed on alati kopeeritud)",
-        "security_tab": "Turvalisus",
-        "search_user_to_mute": "Otsi, keda soovid vaigistada",
-        "search_user_to_block": "Otsi, keda soovid blokeerida",
-        "saving_ok": "Sätted salvestatud",
-        "saving_err": "Sätete salvestamine ebaõnnestus",
-        "autohide_floating_post_button": "Automaatselt peida uue postituse nupp (mobiilil)",
-        "reply_visibility_self": "Näita ainult vastuseid, mis on suunatud mulle",
-        "reply_visibility_following": "Näita ainult vastuseid, mis on suunatud mulle või kasutajatele, keda jälgin",
-        "reply_visibility_all": "Näita kõiki vastuseid",
-        "replies_in_timeline": "Vastused ajajoonel",
-        "radii_help": "Liidese ümardamine (pikslites)",
-        "profile_tab": "Profiil",
-        "presets": "Salvestatud sätted",
-        "pause_on_unfocused": "Peata reaalajas voog kui leht pole fookuses",
-        "panelRadius": "Paneelid",
-        "revoke_token": "Keela",
-        "valid_until": "Kehtiv kuni",
-        "refresh_token": "Värskendustoken",
-        "token": "Token",
-        "oauth_tokens": "OAuth tokenid",
-        "show_moderator_badge": "Näita Moderaator silti mu profiilil",
-        "show_admin_badge": "Näita Admin silti mu profiilil",
-        "hide_followers_count_description": "Ära näita minu jälgijate arvu",
-        "hide_follows_count_description": "Ära näita minu jälgimiste arvu",
-        "hide_followers_description": "Ära näita minu jälgijaid",
-        "hide_follows_description": "Ära näita minu jälgimisi",
-        "no_mutes": "Vaigistusi pole",
-        "no_blocks": "Blokeeringuid pole",
-        "no_rich_text_description": "Muuda kõik postitused lihttekstiks",
-        "notification_visibility_emoji_reactions": "Reaktsioonid",
-        "notification_visibility_moves": "Kasutaja kolimised",
-        "notification_visibility_repeats": "Taaspostitused",
-        "notification_visibility_mentions": "Mainimised",
-        "notification_visibility_likes": "Lemmikud",
-        "notification_visibility_follows": "Jälgimised",
-        "notification_visibility": "Milliseid teateid kuvatakse",
-        "new_password": "Uus salasõna",
-        "new_email": "Uus e-post",
-        "use_contain_fit": "Näita eelvaadetes täis suuruses pilte",
-        "play_videos_in_modal": "Näita videoid eraldi raamis",
-        "mutes_tab": "Vaigistused",
-        "loop_video_silent_only": "Loop videod, millel pole heli (nt. Mastodoni \"gifid\")",
-        "loop_video": "Loop videod",
-        "lock_account_description": "Piira oma konto ainult lubatud jälgijatele",
-        "links": "Lingid",
-        "limited_availability": "Pole Teie veebilehitsejas saadaval",
-        "invalid_theme_imported": "Valitud fail ei ole Pleroma kujundus. Kujundusele muudatusi ei tehtud.",
-        "interfaceLanguage": "Liidese keel",
-        "interface": "Liides",
-        "instance_default_simple": "(vaikimisi)",
-        "instance_default": "(vaikimisi: {value})",
-        "checkboxRadius": "Märkeruudud",
-        "inputRadius": "Sisestuskastid",
-        "import_theme": "Lae sätted",
-        "import_followers_from_a_csv_file": "Impordi jälgimised csv failist",
-        "import_blocks_from_a_csv_file": "Impordi blokeeringud csv failist",
-        "hide_filtered_statuses": "Peida filtreeritud staatused",
-        "hide_user_stats": "Peida kasutaja statistika (nt. jälgijate arv)",
-        "hide_post_stats": "Peida postituse statistika (nt. lemmikute arv)",
-        "use_one_click_nsfw": "Ava NSFW manused ühe klikiga",
-        "preload_images": "Piltide eellaadimine",
-        "hide_isp": "Peida instantsipõhine paneel",
-        "max_thumbnails": "Maksimaalne lubatud eelvaadete arv postituste kohta",
-        "hide_muted_posts": "Peida vaigistatud kasutajate postitused",
-        "general": "Üldine",
-        "foreground": "Esiplaan",
-        "accent": "Rõhk",
-        "follows_imported": "Jälgimised imporditud! Nende töötlemine võtab natuke aega.",
-        "follow_import_error": "Jälgimiste importimisel tekkis viga",
-        "follow_import": "Impordi jälgimised",
-        "follow_export_button": "Ekspordi oma jälgimised csv failiks",
-        "follow_export": "Ekspordi jälgimised",
-        "export_theme": "Salvesta sätted",
-        "emoji_reactions_on_timeline": "Näita reaktsioone ajajoonel",
-        "pad_emoji": "Lisa emotikonidele tühikud ette ja järgi neid menüüst valides",
-        "avatar_size_instruction": "Profiilipildi soovitatud minimaalne suurus on 150x150 pikslit.",
-        "domain_mutes": "Domeenid",
-        "discoverable": "Luba selle konto ilmumine otsingutulemustes ning muudes teenustes",
-        "delete_account_instructions": "Konto kustutamise kinnitamiseks sisestage oma salasõna.",
-        "delete_account_error": "Teie konto kustutamisel tekkis viga. Kui see jätkub, palun võtke kontakti administraatoriga.",
-        "delete_account_description": "Jäädavalt kustuta oma andmed ja konto.",
-        "delete_account": "Kustuta konto",
-        "default_vis": "Vaikimisi nähtavus",
-        "data_import_export_tab": "Andmete import / eksport",
-        "current_password": "Praegune salasõna",
-        "confirm_new_password": "Kinnita uus salasõna",
-        "composing": "Koostamine",
-        "collapse_subject": "Peida postituste pealkirjad",
-        "changed_password": "Salasõna edukalt muudetud!",
-        "change_password_error": "Esines viga salasõna muutmisel.",
-        "change_password": "Muuda salasõna",
-        "changed_email": "E-post edukalt muudetud!",
-        "change_email_error": "Esines viga e-posti muutmisel.",
-        "change_email": "Muuda e-posti",
-        "cRed": "Punane (Tühista)",
-        "cOrange": "Oranž (Lisa lemmikuks)",
-        "cGreen": "Roheline (Taaspostita)",
-        "cBlue": "Sinine (Vasta, jälgi)",
-        "btnRadius": "Nupud",
-        "blocks_tab": "Blokeeringud",
-        "blocks_imported": "Blokeeringud imporditud! Nende töötlemine võtab natuke aega.",
-        "block_import_error": "Blokeeringute importimisel esines viga",
-        "block_import": "Blokeeringute import",
-        "block_export_button": "Ekspordi oma blokeeringud csv failiks",
-        "block_export": "Blokeeringute eksport",
-        "background": "Taust",
-        "avatarRadius": "Profiilipildid",
-        "avatarAltRadius": "Profiilipildid (Teated)",
-        "attachmentRadius": "Manused",
-        "allow_following_move": "Luba automaatjälgimine kui jälgitav konto kolib",
-        "mfa": {
-            "verify": {
-                "desc": "Et lubada kaheastmelist autentimist, sisestage kood oma äpist:"
-            },
-            "scan": {
-                "desc": "Kasutades oma kaheastmelise autentimise äppi, skännige see QR kood või sisestage tekstiline võti:",
-                "secret_code": "Võti",
-                "title": "Skänni"
-            },
-            "authentication_methods": "Autentimismeetodid",
-            "recovery_codes_warning": "Kirjutage need koodid üles ning hoidke need kindlas kohas. Kui Te kaotate ligipääsu oma kaheastmelise autentimise äppile ning nendele koodidele, ei ole Teil võimalik oma kontosse sisse logida.",
-            "waiting_a_recovery_codes": "Laen taastekoode…",
-            "recovery_codes": "Taastekoodid.",
-            "warning_of_generate_new_codes": "Kui Te loote uued taastekoodid, Teie vanad koodid ei tööta enam.",
-            "generate_new_recovery_codes": "Loo uued taastekoodid",
-            "title": "Kaheastmeline autentimine",
-            "confirm_and_enable": "Kinnita & luba OTP",
-            "wait_pre_setup_otp": "sean üles OTP",
-            "setup_otp": "Sea üles OTP",
-            "otp": "OTP"
-        },
-        "enter_current_password_to_confirm": "Sisetage isiku tõestamiseks oma salasõna",
-        "security": "Turvalisus",
-        "app_name": "Rakenduse nimi",
-        "style": {
-            "switcher": {
-                "help": {
-                    "snapshot_present": "Kujunduse eelvaade on laetud, nii et kõik väärtused on üle kirjutatud. Te saate laadida ka kujunduse päris sisu.",
-                    "older_version_imported": "Teie imporditud fail oli loodud vanemas versioonis.",
-                    "future_version_imported": "Teie imporditud fail oli loodud uuemas versioonis.",
-                    "v2_imported": "Teie imporditud fail oli vanema versiooni jaoks. Me üritame hoida ühilduvust, kuid ikkagi võib esineda erinevusi.",
-                    "upgraded_from_v2": "PleromaFE-d uuendati, teie kujundus võib välja näha natuke erinev, kui mäletate."
-                },
-                "use_source": "Uus versioon",
-                "use_snapshot": "Vana versioon",
-                "keep_as_is": "Jäta nii, nagu on",
-                "load_theme": "Lae kujundus",
-                "clear_opacity": "Tühista läbipaistvus",
-                "clear_all": "Tühista kõik",
-                "reset": "Taasta algne",
-                "keep_fonts": "Jäta fondid",
-                "keep_roundness": "Jäta ümarus",
-                "keep_opacity": "Jäta läbipaistvus",
-                "keep_shadows": "Jäta varjud",
-                "keep_color": "Jäta värvid"
-            }
-        },
-        "enable_web_push_notifications": "Luba veebipõhised push-teated",
-        "notification_blocks": "Kasutaja blokeerimisel ei tule neilt enam teateid ning nendele teilt ka mitte.",
-        "notification_setting_privacy_option": "Peida saatja ning sisu push-teadetelt",
-        "notification_setting": "Saa teateid nendelt:",
-        "notifications": "Teated",
-        "notification_mutes": "Kui soovid mõnelt kasutajalt mitte teateid saada, kasuta vaigistust.",
-        "notification_setting_privacy": "Privaatsus",
-        "notification_setting_non_followers": "Kasutajatelt, kes sind ei jälgi",
-        "notification_setting_followers": "Kasutajatelt, kes jälgivad sind",
-        "notification_setting_non_follows": "Kasutajatelt, keda sa ei jälgi",
-        "notification_setting_follows": "Kasutajatelt, keda jälgid",
-        "notification_setting_filters": "Filtrid",
-        "greentext": "Meemi nooled",
-        "fun": "Naljad",
-        "values": {
-            "true": "jah",
-            "false": "ei"
-        },
-        "upload_a_photo": "Lae üles foto",
-        "type_domains_to_mute": "Trüki siia domeene, mida vaigistada",
-        "tooltipRadius": "Vihjed/hoiatused",
-        "theme_help_v2_1": "Te saate ka mõndade komponentide värvust ning läbipaistvust üle kirjutada vajutades ruudule. Kasuta \"Tühista kõik\" nuppu, et need tühistada.",
-        "theme_help": "Kasuta hex värvikoode (#rrggbb) oma kujunduse isikupärastamiseks.",
-        "text": "Tekst",
-        "useStreamingApiWarning": "(Pole soovituslik, eksperimentaalne, on teada, et jätab postitusi vahele)",
-        "useStreamingApi": "Saa postitusi ning teateid reaalajas",
-        "user_mutes": "Kasutajad",
-        "streaming": "Luba uute postituste automaatvoog kui oled lehekülje alguses",
-        "stop_gifs": "Mängi GIFid hiirega ületades",
-        "post_status_content_type": "Postituse sisutüüp"
-    },
-    "timeline": {
-        "conversation": "Vestlus",
-        "error_fetching": "Viga uuenduste laadimisel",
-        "load_older": "Kuva vanemaid staatuseid",
-        "show_new": "Näita uusi",
-        "up_to_date": "Uuendatud"
-    },
-    "user_card": {
-        "block": "Blokeeri",
-        "blocked": "Blokeeritud!",
-        "follow": "Jälgi",
-        "followees": "Jälgitavaid",
-        "followers": "Jälgijaid",
-        "following": "Jälgin!",
-        "follows_you": "Jälgib sind!",
-        "mute": "Vaigista",
-        "muted": "Vaigistatud",
-        "per_day": "päevas",
-        "statuses": "Staatuseid"
-    },
-    "about": {
-        "mrf": {
-            "mrf_policies_desc": "MRF poliitikad mõjutavad selle instansi föderatsiooni käitumist.    Järgmised poliitikad on lubatud:",
-            "simple": {
-                "media_nsfw_desc": "See instants määrab nendest instantsidest postituste meedia sensitiivseks:",
-                "media_nsfw": "Meedia määratakse sensitiivseks",
-                "media_removal_desc": "See instants eemaldab meedia postitustelt nendest instantsidest:",
-                "media_removal": "Meedia eemaldamine",
-                "ftl_removal_desc": "See instants eemaldab postitused nendelt instantsidest \"Kogu teatud võrgu\" ajajoonelt:",
-                "ftl_removal": "\"Kogu teatud võrgu\" ajajoonelt eemaldamine",
-                "quarantine_desc": "See instants saadab ainult avalikke postitusi järgmistele instantsidele:",
-                "quarantine": "Karantiini",
-                "reject_desc": "See instants ei luba sõnumeid nendest instantsidest:",
-                "reject": "Keela",
-                "accept_desc": "See instants lubab sõnumeid ainult nendest instantsidest:",
-                "accept": "Luba",
-                "simple_policies": "Instansi-omased poliitikad"
-            },
-            "mrf_policies": "Lubatud MRF poliitikad",
-            "keyword": {
-                "is_replaced_by": "→",
-                "replace": "Vaheta",
-                "reject": "Lükka tagasi",
-                "ftl_removal": "\"Kogu teatud võrgu\" ajajoonelt eemaldamine",
-                "keyword_policies": "Võtmesõna poliitikad"
-            },
-            "federation": "Föderatsioon"
-        },
-        "staff": "Personal"
-    },
-    "selectable_list": {
-        "select_all": "Vali kõik"
-    },
-    "remote_user_resolver": {
-        "error": "Ei leitud.",
-        "searching_for": "Otsin",
-        "remote_user_resolver": "Kaugkasutaja leidja"
-    },
-    "interactions": {
-        "load_older": "Laadi vanemad interaktsioonid",
-        "moves": "Kasutaja kolimised",
-        "follows": "Uued jälgimised",
-        "favs_repeats": "Taaspostitused ja lemmikud"
-    },
-    "emoji": {
-        "load_all": "Laen kõik {emojiAmount} emotikoni",
-        "load_all_hint": "Laadisin esimesed {saneAmount} emotikoni, kõike laadides võib esineda probleeme jõudlusega.",
-        "unicode": "Unicode emotikonid",
-        "custom": "Kohandatud emotikonid",
-        "add_emoji": "Lisa emotikon",
-        "search_emoji": "Otsi emotikone",
-        "keep_open": "Hoia valija lahti",
-        "emoji": "Emotikonid",
-        "stickers": "Kleepsud"
-    },
-    "polls": {
-        "not_enough_options": "Liiga vähe unikaalseid valikuid hääletuses",
-        "expired": "Hääletus lõppes {0} tagasi",
-        "expires_in": "Hääletus lõppeb {0}",
-        "expiry": "Hääletuse vanus",
-        "multiple_choices": "Mitu vastust",
-        "single_choice": "Üks vastus",
-        "type": "Hääletuse tüüp",
-        "vote": "Hääleta",
-        "votes": "häält",
-        "option": "Valik",
-        "add_option": "Lisa valik",
-        "add_poll": "Lisa küsitlus"
-    },
-    "media_modal": {
-        "next": "Järgmine",
-        "previous": "Eelmine"
-    },
-    "importer": {
-        "error": "Faili importimisel tekkis viga.",
-        "success": "Import õnnestus.",
-        "submit": "Esita"
-    },
-    "image_cropper": {
-        "cancel": "Tühista",
-        "save_without_cropping": "Salvesta muudatusteta",
-        "save": "Salvesta",
-        "crop_picture": "Modifitseeri pilti"
-    },
-    "features_panel": {
-        "who_to_follow": "Keda jälgida",
-        "title": "Featuurid",
-        "text_limit": "Tekstilimiit",
-        "scope_options": "Ulatuse valikud",
-        "media_proxy": "Meedia proksi",
-        "gopher": "Gopher",
-        "chat": "Vestlus"
-    },
-    "exporter": {
-        "processing": "Töötlemine, Teilt küsitakse varsti faili allalaadimist",
-        "export": "Ekspordi"
-    },
-    "domain_mute_card": {
-        "unmute_progress": "Eemaldan vaigistuse…",
-        "unmute": "Ära vaigista",
-        "mute_progress": "Vaigistan…",
-        "mute": "Vaigista"
-    },
-    "chat": {
-        "title": "Vestlus"
-    }
+    "staff": "Personal"
+  },
+  "selectable_list": {
+    "select_all": "Vali kõik"
+  },
+  "remote_user_resolver": {
+    "error": "Ei leitud.",
+    "searching_for": "Otsin",
+    "remote_user_resolver": "Kaugkasutaja leidja"
+  },
+  "interactions": {
+    "load_older": "Laadi vanemad interaktsioonid",
+    "moves": "Kasutaja kolimised",
+    "follows": "Uued jälgimised",
+    "favs_repeats": "Taaspostitused ja lemmikud"
+  },
+  "emoji": {
+    "load_all": "Laen kõik {emojiAmount} emotikoni",
+    "load_all_hint": "Laadisin esimesed {saneAmount} emotikoni, kõike laadides võib esineda probleeme jõudlusega.",
+    "unicode": "Unicode emotikonid",
+    "custom": "Kohandatud emotikonid",
+    "add_emoji": "Lisa emotikon",
+    "search_emoji": "Otsi emotikone",
+    "keep_open": "Hoia valija lahti",
+    "emoji": "Emotikonid",
+    "stickers": "Kleepsud"
+  },
+  "polls": {
+    "not_enough_options": "Liiga vähe unikaalseid valikuid hääletuses",
+    "expired": "Hääletus lõppes {0} tagasi",
+    "expires_in": "Hääletus lõppeb {0}",
+    "expiry": "Hääletuse vanus",
+    "multiple_choices": "Mitu vastust",
+    "single_choice": "Üks vastus",
+    "type": "Hääletuse tüüp",
+    "vote": "Hääleta",
+    "votes": "häält",
+    "option": "Valik",
+    "add_option": "Lisa valik",
+    "add_poll": "Lisa küsitlus"
+  },
+  "media_modal": {
+    "next": "Järgmine",
+    "previous": "Eelmine"
+  },
+  "importer": {
+    "error": "Faili importimisel tekkis viga.",
+    "success": "Import õnnestus.",
+    "submit": "Esita"
+  },
+  "image_cropper": {
+    "cancel": "Tühista",
+    "save_without_cropping": "Salvesta muudatusteta",
+    "save": "Salvesta",
+    "crop_picture": "Modifitseeri pilti"
+  },
+  "features_panel": {
+    "who_to_follow": "Keda jälgida",
+    "title": "Featuurid",
+    "text_limit": "Tekstilimiit",
+    "scope_options": "Ulatuse valikud",
+    "media_proxy": "Meedia proksi",
+    "gopher": "Gopher",
+    "chat": "Vestlus"
+  },
+  "exporter": {
+    "processing": "Töötlemine, Teilt küsitakse varsti faili allalaadimist",
+    "export": "Ekspordi"
+  },
+  "domain_mute_card": {
+    "unmute_progress": "Eemaldan vaigistuse…",
+    "unmute": "Ära vaigista",
+    "mute_progress": "Vaigistan…",
+    "mute": "Vaigista"
+  },
+  "chat": {
+    "title": "Vestlus"
+  }
 }
diff --git a/src/i18n/fi.json b/src/i18n/fi.json
index 7248c049..99a1b53a 100644
--- a/src/i18n/fi.json
+++ b/src/i18n/fi.json
@@ -1,747 +1,747 @@
 {
-    "chat": {
-        "title": "Chat"
+  "chat": {
+    "title": "Chat"
+  },
+  "features_panel": {
+    "chat": "Chat",
+    "gopher": "Gopher",
+    "media_proxy": "Media-välityspalvelin",
+    "scope_options": "Näkyvyyden rajaus",
+    "text_limit": "Tekstin pituusraja",
+    "title": "Ominaisuudet",
+    "who_to_follow": "Seurausehdotukset"
+  },
+  "finder": {
+    "error_fetching_user": "Virhe hakiessa käyttäjää",
+    "find_user": "Hae käyttäjä"
+  },
+  "general": {
+    "apply": "Aseta",
+    "submit": "Lähetä",
+    "more": "Lisää",
+    "generic_error": "Virhe tapahtui",
+    "optional": "valinnainen",
+    "show_more": "Näytä lisää",
+    "show_less": "Näytä vähemmän",
+    "dismiss": "Sulje",
+    "cancel": "Peruuta",
+    "disable": "Poista käytöstä",
+    "confirm": "Hyväksy",
+    "verify": "Varmenna",
+    "enable": "Ota käyttöön"
+  },
+  "login": {
+    "login": "Kirjaudu sisään",
+    "description": "Kirjaudu sisään OAuthilla",
+    "logout": "Kirjaudu ulos",
+    "password": "Salasana",
+    "placeholder": "esim. Seppo",
+    "register": "Rekisteröidy",
+    "username": "Käyttäjänimi",
+    "hint": "Kirjaudu sisään liittyäksesi keskusteluun",
+    "authentication_code": "Todennuskoodi",
+    "enter_recovery_code": "Syötä palautuskoodi",
+    "recovery_code": "Palautuskoodi",
+    "heading": {
+      "totp": "Monivaihetodennus",
+      "recovery": "Monivaihepalautus"
     },
-    "features_panel": {
-        "chat": "Chat",
-        "gopher": "Gopher",
-        "media_proxy": "Media-välityspalvelin",
-        "scope_options": "Näkyvyyden rajaus",
-        "text_limit": "Tekstin pituusraja",
-        "title": "Ominaisuudet",
-        "who_to_follow": "Seurausehdotukset"
+    "enter_two_factor_code": "Syötä monivaihetodennuskoodi"
+  },
+  "nav": {
+    "about": "Tietoja",
+    "back": "Takaisin",
+    "chat": "Paikallinen Chat",
+    "friend_requests": "Seurauspyynnöt",
+    "mentions": "Maininnat",
+    "interactions": "Interaktiot",
+    "dms": "Yksityisviestit",
+    "public_tl": "Julkinen Aikajana",
+    "timeline": "Aikajana",
+    "twkn": "Koko Tunnettu Verkosto",
+    "user_search": "Käyttäjähaku",
+    "who_to_follow": "Seurausehdotukset",
+    "preferences": "Asetukset",
+    "administration": "Ylläpito",
+    "search": "Haku"
+  },
+  "notifications": {
+    "broken_favorite": "Viestiä ei löydetty...",
+    "favorited_you": "tykkäsi viestistäsi",
+    "followed_you": "seuraa sinua",
+    "load_older": "Lataa vanhempia ilmoituksia",
+    "notifications": "Ilmoitukset",
+    "read": "Lue!",
+    "repeated_you": "toisti viestisi",
+    "no_more_notifications": "Ei enempää ilmoituksia",
+    "reacted_with": "lisäsi reaktion {0}",
+    "migrated_to": "siirtyi sivulle",
+    "follow_request": "haluaa seurata sinua"
+  },
+  "polls": {
+    "add_poll": "Lisää äänestys",
+    "add_option": "Lisää vaihtoehto",
+    "option": "Vaihtoehto",
+    "votes": "ääntä",
+    "vote": "Äänestä",
+    "type": "Äänestyksen tyyppi",
+    "single_choice": "Yksi valinta",
+    "multiple_choices": "Monivalinta",
+    "expiry": "Äänestyksen kesto",
+    "expires_in": "Päättyy {0} päästä",
+    "expired": "Päättyi {0} sitten",
+    "not_enough_option": "Liian vähän uniikkeja vaihtoehtoja äänestyksessä",
+    "not_enough_options": "Liian vähän ainutkertaisia vaihtoehtoja"
+  },
+  "interactions": {
+    "favs_repeats": "Toistot ja tykkäykset",
+    "follows": "Uudet seuraukset",
+    "load_older": "Lataa vanhempia interaktioita",
+    "moves": "Käyttäjien siirtymiset"
+  },
+  "post_status": {
+    "new_status": "Uusi viesti",
+    "account_not_locked_warning": "Tilisi ei ole {0}. Kuka vain voi seurata sinua nähdäksesi 'vain-seuraajille' -viestisi",
+    "account_not_locked_warning_link": "lukittu",
+    "attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi",
+    "content_type": {
+      "text/plain": "Tavallinen teksti",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
     },
-    "finder": {
-        "error_fetching_user": "Virhe hakiessa käyttäjää",
-        "find_user": "Hae käyttäjä"
+    "content_warning": "Aihe (valinnainen)",
+    "default": "Tulin juuri saunasta.",
+    "direct_warning": "Tämä viesti näkyy vain mainituille käyttäjille.",
+    "posting": "Lähetetään",
+    "scope": {
+      "direct": "Yksityisviesti - Näkyy vain mainituille käyttäjille",
+      "private": "Vain-seuraajille - Näkyy vain seuraajillesi",
+      "public": "Julkinen - Näkyy julkisilla aikajanoilla",
+      "unlisted": "Listaamaton - Ei näy julkisilla aikajanoilla"
     },
-    "general": {
-        "apply": "Aseta",
-        "submit": "Lähetä",
-        "more": "Lisää",
-        "generic_error": "Virhe tapahtui",
-        "optional": "valinnainen",
-        "show_more": "Näytä lisää",
-        "show_less": "Näytä vähemmän",
-        "dismiss": "Sulje",
-        "cancel": "Peruuta",
-        "disable": "Poista käytöstä",
-        "confirm": "Hyväksy",
-        "verify": "Varmenna",
-        "enable": "Ota käyttöön"
-    },
-    "login": {
-        "login": "Kirjaudu sisään",
-        "description": "Kirjaudu sisään OAuthilla",
-        "logout": "Kirjaudu ulos",
-        "password": "Salasana",
-        "placeholder": "esim. Seppo",
-        "register": "Rekisteröidy",
-        "username": "Käyttäjänimi",
-        "hint": "Kirjaudu sisään liittyäksesi keskusteluun",
-        "authentication_code": "Todennuskoodi",
-        "enter_recovery_code": "Syötä palautuskoodi",
-        "recovery_code": "Palautuskoodi",
-        "heading": {
-            "totp": "Monivaihetodennus",
-            "recovery": "Monivaihepalautus"
-        },
-        "enter_two_factor_code": "Syötä monivaihetodennuskoodi"
-    },
-    "nav": {
-        "about": "Tietoja",
-        "back": "Takaisin",
-        "chat": "Paikallinen Chat",
-        "friend_requests": "Seurauspyynnöt",
-        "mentions": "Maininnat",
-        "interactions": "Interaktiot",
-        "dms": "Yksityisviestit",
-        "public_tl": "Julkinen Aikajana",
-        "timeline": "Aikajana",
-        "twkn": "Koko Tunnettu Verkosto",
-        "user_search": "Käyttäjähaku",
-        "who_to_follow": "Seurausehdotukset",
-        "preferences": "Asetukset",
-        "administration": "Ylläpito",
-        "search": "Haku"
-    },
-    "notifications": {
-        "broken_favorite": "Viestiä ei löydetty...",
-        "favorited_you": "tykkäsi viestistäsi",
-        "followed_you": "seuraa sinua",
-        "load_older": "Lataa vanhempia ilmoituksia",
-        "notifications": "Ilmoitukset",
-        "read": "Lue!",
-        "repeated_you": "toisti viestisi",
-        "no_more_notifications": "Ei enempää ilmoituksia",
-        "reacted_with": "lisäsi reaktion {0}",
-        "migrated_to": "siirtyi sivulle",
-        "follow_request": "haluaa seurata sinua"
-    },
-    "polls": {
-        "add_poll": "Lisää äänestys",
-        "add_option": "Lisää vaihtoehto",
-        "option": "Vaihtoehto",
-        "votes": "ääntä",
-        "vote": "Äänestä",
-        "type": "Äänestyksen tyyppi",
-        "single_choice": "Yksi valinta",
-        "multiple_choices": "Monivalinta",
-        "expiry": "Äänestyksen kesto",
-        "expires_in": "Päättyy {0} päästä",
-        "expired": "Päättyi {0} sitten",
-        "not_enough_option": "Liian vähän uniikkeja vaihtoehtoja äänestyksessä",
-        "not_enough_options": "Liian vähän ainutkertaisia vaihtoehtoja"
-    },
-    "interactions": {
-        "favs_repeats": "Toistot ja tykkäykset",
-        "follows": "Uudet seuraukset",
-        "load_older": "Lataa vanhempia interaktioita",
-        "moves": "Käyttäjien siirtymiset"
-    },
-    "post_status": {
-        "new_status": "Uusi viesti",
-        "account_not_locked_warning": "Tilisi ei ole {0}. Kuka vain voi seurata sinua nähdäksesi 'vain-seuraajille' -viestisi",
-        "account_not_locked_warning_link": "lukittu",
-        "attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi",
-        "content_type": {
-            "text/plain": "Tavallinen teksti",
-            "text/html": "HTML",
-            "text/markdown": "Markdown",
-            "text/bbcode": "BBCode"
-        },
-        "content_warning": "Aihe (valinnainen)",
-        "default": "Tulin juuri saunasta.",
-        "direct_warning": "Tämä viesti näkyy vain mainituille käyttäjille.",
-        "posting": "Lähetetään",
-        "scope": {
-            "direct": "Yksityisviesti - Näkyy vain mainituille käyttäjille",
-            "private": "Vain-seuraajille - Näkyy vain seuraajillesi",
-            "public": "Julkinen - Näkyy julkisilla aikajanoilla",
-            "unlisted": "Listaamaton - Ei näy julkisilla aikajanoilla"
-        },
-        "direct_warning_to_all": "Tämä viesti näkyy vain viestissä mainituille käyttäjille.",
-        "direct_warning_to_first_only": "Tämä viesti näkyy vain viestin alussa mainituille käyttäjille.",
-        "scope_notice": {
-            "public": "Tämä viesti näkyy kaikille",
-            "private": "Tämä viesti näkyy vain sinun seuraajillesi",
-            "unlisted": "Tämä viesti ei näy Julkisella Aikajanalla tai Koko Tunnettu Verkosto -aikajanalla"
-        }
-    },
-    "registration": {
-        "bio": "Kuvaus",
-        "email": "Sähköposti",
-        "fullname": "Koko nimi",
-        "password_confirm": "Salasanan vahvistaminen",
-        "registration": "Rekisteröityminen",
-        "token": "Kutsuvaltuus",
-        "captcha": "Varmenne",
-        "new_captcha": "Paina kuvaa saadaksesi uuden varmenteen",
-        "validations": {
-            "username_required": "ei voi olla tyhjä",
-            "fullname_required": "ei voi olla tyhjä",
-            "email_required": "ei voi olla tyhjä",
-            "password_required": "ei voi olla tyhjä",
-            "password_confirmation_required": "ei voi olla tyhjä",
-            "password_confirmation_match": "pitää vastata salasanaa"
-        },
-        "username_placeholder": "esim. peke",
-        "fullname_placeholder": "esim. Pekka Postaaja",
-        "bio_placeholder": "esim.\nHei, olen Pekka.\nOlen esimerkkikäyttäjä tässä verkostossa."
-    },
-    "settings": {
-        "attachmentRadius": "Liitteet",
-        "attachments": "Liitteet",
-        "autoload": "Lataa vanhempia viestejä automaattisesti ruudun pohjalla",
-        "avatar": "Profiilikuva",
-        "avatarAltRadius": "Profiilikuvat (ilmoitukset)",
-        "avatarRadius": "Profiilikuvat",
-        "background": "Tausta",
-        "bio": "Kuvaus",
-        "btnRadius": "Napit",
-        "cBlue": "Sininen (Vastaukset, seuraukset)",
-        "cGreen": "Vihreä (Toistot)",
-        "cOrange": "Oranssi (Tykkäykset)",
-        "cRed": "Punainen (Peruminen)",
-        "change_password": "Vaihda salasana",
-        "change_password_error": "Virhe vaihtaessa salasanaa.",
-        "changed_password": "Salasana vaihdettu!",
-        "collapse_subject": "Minimoi viestit, joille on asetettu aihe",
-        "composing": "Viestien laatiminen",
-        "confirm_new_password": "Vahvista uusi salasana",
-        "current_avatar": "Nykyinen profiilikuvasi",
-        "current_password": "Nykyinen salasana",
-        "current_profile_banner": "Nykyinen julisteesi",
-        "data_import_export_tab": "Tietojen tuonti / vienti",
-        "default_vis": "Oletusnäkyvyysrajaus",
-        "delete_account": "Poista tili",
-        "delete_account_description": "Poista tilisi ja viestisi pysyvästi.",
-        "delete_account_error": "Virhe poistaessa tiliäsi. Jos virhe jatkuu, ota yhteyttä palvelimesi ylläpitoon.",
-        "delete_account_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.",
-        "emoji_reactions_on_timeline": "Näytä emojireaktiot aikajanalla",
-        "export_theme": "Tallenna teema",
-        "filtering": "Suodatus",
-        "filtering_explanation": "Kaikki viestit, jotka sisältävät näitä sanoja, suodatetaan. Yksi sana per rivi.",
-        "follow_export": "Seurausten vienti",
-        "follow_export_button": "Vie seurauksesi CSV-tiedostoon",
-        "follow_export_processing": "Käsitellään, sinua pyydetään lataamaan tiedosto hetken päästä",
-        "follow_import": "Seurausten tuonti",
-        "follow_import_error": "Virhe tuodessa seuraksia",
-        "follows_imported": "Seuraukset tuotu! Niiden käsittely vie hetken.",
-        "foreground": "Etuala",
-        "general": "Yleinen",
-        "hide_attachments_in_convo": "Piilota liitteet keskusteluissa",
-        "hide_attachments_in_tl": "Piilota liitteet aikajanalla",
-        "max_thumbnails": "Suurin sallittu määrä liitteitä esikatselussa",
-        "hide_isp": "Piilota palvelimenkohtainen ruutu",
-        "preload_images": "Esilataa kuvat",
-        "use_one_click_nsfw": "Avaa NSFW-liitteet yhdellä painalluksella",
-        "hide_post_stats": "Piilota viestien statistiikka (esim. tykkäysten määrä)",
-        "hide_user_stats": "Piilota käyttäjien statistiikka (esim. seuraajien määrä)",
-        "import_followers_from_a_csv_file": "Tuo seuraukset CSV-tiedostosta",
-        "import_theme": "Tuo tallennettu teema",
-        "inputRadius": "Syöttökentät",
-        "checkboxRadius": "Valintalaatikot",
-        "instance_default": "(oletus: {value})",
-        "instance_default_simple": "(oletus)",
-        "interface": "Käyttöliittymä",
-        "interfaceLanguage": "Käyttöliittymän kieli",
-        "invalid_theme_imported": "Tuotu tallennettu teema on epäkelpo, muutoksia ei tehty nykyiseen teemaasi.",
-        "limited_availability": "Ei saatavilla selaimessasi",
-        "links": "Linkit",
-        "lock_account_description": "Vain erikseen hyväksytyt käyttäjät voivat seurata tiliäsi",
-        "loop_video": "Uudelleentoista videot",
-        "loop_video_silent_only": "Uudelleentoista ainoastaan äänettömät videot (Video-\"giffit\")",
-        "play_videos_in_modal": "Toista videot modaalissa",
-        "use_contain_fit": "Älä rajaa liitteitä esikatselussa",
-        "name": "Nimi",
-        "name_bio": "Nimi ja kuvaus",
-        "new_password": "Uusi salasana",
-        "notification_visibility": "Ilmoitusten näkyvyys",
-        "notification_visibility_follows": "Seuraukset",
-        "notification_visibility_likes": "Tykkäykset",
-        "notification_visibility_mentions": "Maininnat",
-        "notification_visibility_repeats": "Toistot",
-        "notification_visibility_emoji_reactions": "Reaktiot",
-        "no_rich_text_description": "Älä näytä tekstin muotoilua",
-        "hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
-        "nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
-        "oauth_tokens": "OAuth-merkit",
-        "token": "Token",
-        "refresh_token": "Päivitä token",
-        "valid_until": "Voimassa asti",
-        "revoke_token": "Peruuta",
-        "panelRadius": "Ruudut",
-        "pause_on_unfocused": "Pysäytä automaattinen viestien näyttö välilehden ollessa pois fokuksesta",
-        "presets": "Valmiit teemat",
-        "profile_background": "Taustakuva",
-        "profile_banner": "Juliste",
-        "profile_tab": "Profiili",
-        "radii_help": "Aseta reunojen pyöristys (pikseleinä)",
-        "replies_in_timeline": "Keskustelut aikajanalla",
-        "reply_link_preview": "Keskusteluiden vastauslinkkien esikatselu",
-        "reply_visibility_all": "Näytä kaikki vastaukset",
-        "reply_visibility_following": "Näytä vain vastaukset minulle tai seuraamilleni käyttäjille",
-        "reply_visibility_self": "Näytä vain vastaukset minulle",
-        "saving_err": "Virhe tallentaessa asetuksia",
-        "saving_ok": "Asetukset tallennettu",
-        "security_tab": "Tietoturva",
-        "scope_copy": "Kopioi näkyvyysrajaus vastatessa (Yksityisviestit aina kopioivat)",
-        "set_new_avatar": "Aseta uusi profiilikuva",
-        "set_new_profile_background": "Aseta uusi taustakuva",
-        "set_new_profile_banner": "Aseta uusi juliste",
-        "settings": "Asetukset",
-        "subject_input_always_show": "Näytä aihe-kenttä",
-        "subject_line_behavior": "Aihe-kentän kopiointi",
-        "subject_line_email": "Kuten sähköposti: \"re: aihe\"",
-        "subject_line_mastodon": "Kopioi sellaisenaan",
-        "subject_line_noop": "Älä kopioi",
-        "stop_gifs": "Toista giffit vain kohdistaessa",
-        "streaming": "Näytä uudet viestit automaattisesti ollessasi ruudun huipulla",
-        "text": "Teksti",
-        "theme": "Teema",
-        "theme_help": "Käytä heksadesimaalivärejä muokataksesi väriteemaasi.",
-        "theme_help_v2_1": "Voit asettaa tiettyjen osien värin tai läpinäkyvyyden täyttämällä valintalaatikon, käytä \"Tyhjennä kaikki\"-nappia tyhjentääksesi kaiken.",
-        "theme_help_v2_2": "Ikonit kenttien alla ovat kontrasti-indikaattoreita, lisätietoa kohdistamalla. Käyttäessä läpinäkyvyyttä ne näyttävät pahimman skenaarion.",
-        "tooltipRadius": "Ohje- tai huomioviestit",
-        "user_settings": "Käyttäjän asetukset",
-        "values": {
-            "false": "pois päältä",
-            "true": "päällä"
-        },
-        "hide_follows_description": "Älä näytä ketä seuraan",
-        "show_moderator_badge": "Näytä Moderaattori-merkki profiilissani",
-        "useStreamingApi": "Vastaanota viestiejä ja ilmoituksia reaaliajassa",
-        "notification_setting_filters": "Suodattimet",
-        "notification_setting": "Vastaanota ilmoituksia seuraavista:",
-        "notification_setting_privacy_option": "Piilota lähettäjä ja sisältö sovelluksen ulkopuolisista ilmoituksista",
-        "enable_web_push_notifications": "Ota käyttöön sovelluksen ulkopuoliset ilmoitukset",
-        "app_name": "Sovelluksen nimi",
-        "security": "Turvallisuus",
-        "mfa": {
-            "otp": "OTP",
-            "setup_otp": "OTP-asetukset",
-            "wait_pre_setup_otp": "esiasetetaan OTP:ta",
-            "confirm_and_enable": "Hyväksy ja käytä OTP",
-            "title": "Monivaihetodennus",
-            "generate_new_recovery_codes": "Luo uudet palautuskoodit",
-            "authentication_methods": "Todennus",
-            "warning_of_generate_new_codes": "Luodessasi uudet palautuskoodit, vanhat koodisi lakkaavat toimimasta.",
-            "recovery_codes": "Palautuskoodit.",
-            "waiting_a_recovery_codes": "Odotetaan palautuskoodeja...",
-            "recovery_codes_warning": "Kirjoita koodit ylös tai tallenna ne turvallisesti, muuten et näe niitä uudestaan. Jos et voi käyttää monivaihetodennusta ja sinulla ei ole palautuskoodeja, et voi enää kirjautua sisään tilillesi.",
-            "scan": {
-                "title": "Skannaa",
-                "secret_code": "Avain",
-                "desc": "Käytä monivaihetodennus-sovellusta skannakksesi tämän QR-kooding, tai syötä avain:"
-            },
-            "verify": {
-                "desc": "Kytkeäksesi päälle monivaihetodennuksen, syötä koodi monivaihetodennussovellksesta:"
-            }
-        },
-        "allow_following_move": "Salli automaattinen seuraaminen kun käyttäjä siirtää tilinsä",
-        "block_export": "Estojen vienti",
-        "block_export_button": "Vie estosi CSV-tiedostoon",
-        "block_import": "Estojen tuonti",
-        "block_import_error": "Virhe tuodessa estoja",
-        "blocks_imported": "Estot tuotu! Käsittely vie hetken.",
-        "blocks_tab": "Estot",
-        "change_email": "Vaihda sähköpostiosoite",
-        "change_email_error": "Virhe vaihtaessa sähköpostiosoitetta.",
-        "changed_email": "Sähköpostiosoite vaihdettu!",
-        "domain_mutes": "Sivut",
-        "avatar_size_instruction": "Suositeltu vähimmäiskoko profiilikuville on 150x150 pikseliä.",
-        "accent": "Korostus",
-        "hide_muted_posts": "Piilota mykistettyjen käyttäjien viestit",
-        "hide_filtered_statuses": "Piilota mykistetyt viestit",
-        "import_blocks_from_a_csv_file": "Tuo estot CSV-tiedostosta",
-        "no_blocks": "Ei estoja",
-        "no_mutes": "Ei mykistyksiä",
-        "notification_visibility_moves": "Käyttäjien siirtymiset",
-        "hide_followers_description": "Älä näytä ketkä seuraavat minua",
-        "hide_follows_count_description": "Älä näytä seurauksien määrää",
-        "hide_followers_count_description": "Älä näytä seuraajien määrää",
-        "show_admin_badge": "Näytä Ylläpitäjä-merkki proofilissani",
-        "autohide_floating_post_button": "Piilota Uusi Viesti -nappi automaattisesti (mobiili)",
-        "search_user_to_block": "Hae estettäviä käyttäjiä",
-        "search_user_to_mute": "Hae mykistettäviä käyttäjiä",
-        "minimal_scopes_mode": "Yksinkertaista näkyvyydenrajauksen vaihtoehdot",
-        "post_status_content_type": "Uuden viestin sisällön muoto",
-        "user_mutes": "Käyttäjät",
-        "useStreamingApiWarning": "(Kokeellinen)",
-        "type_domains_to_mute": "Syötä mykistettäviä sivustoja",
-        "upload_a_photo": "Lataa kuva",
-        "fun": "Hupi",
-        "greentext": "Meeminuolet",
-        "notifications": "Ilmoitukset",
-        "style": {
-            "switcher": {
-                "save_load_hint": "\"Säilytä\" asetukset säilyttävät tällä hetkellä asetetut asetukset valittaessa tai ladatessa teemaa, se myös tallentaa kyseiset asetukset viedessä teemaa. Kun kaikki laatikot ovat tyhjänä, viety teema tallentaa kaiken.",
-                "help": {
-                    "older_version_imported": "Tuomasi tiedosto on luotu vanhemmalla versiolla.",
-                    "fe_upgraded": "PleromaFE:n teemaus päivitetty versiopäivityksen yhteydessä.",
-                    "migration_snapshot_ok": "Varmuuden vuoksi teeman kaappaus ladattu. Voit koittaa ladata teeman sisällön.",
-                    "migration_napshot_gone": "Jostain syystä teeman kaappaus puuttuu, kaikki asiat eivät välttämättä näytä oikealta.",
-                    "snapshot_source_mismatch": "Versiot eivät täsmää: todennäköisesti versio vaihdettu vanhempaan ja päivitetty uudestaan, jos vaihdoit teemaa vanhalla versiolla, sinun tulisi käyttää vanhaa versiota, muutoin uutta.",
-                    "upgraded_from_v2": "PleromaFE on päivitetty, teemasi saattaa näyttää erilaiselta kuin muistat.",
-                    "v2_imported": "Tuomasi tiedosto on luotu vanhemmalla versiolla. Yhteensopivuus ei välttämättä ole täydellinen.",
-                    "future_version_imported": "Tuomasi tiedosto on luotu uudemmalla versiolla.",
-                    "snapshot_present": "Teeman kaappaus ladattu, joten kaikki arvot ovat ylikirjoitettu. Voit sen sijaan ladata teeman sisällön.",
-                    "snapshot_missing": "Teeman kaappausta ei tiedostossa, joten se voi näyttää erilaiselta kuin suunniteltu.",
-                    "fe_downgraded": "PleromaFE:n versio vaihtunut vanhempaan."
-                },
-                "keep_color": "Säilytä värit",
-                "keep_shadows": "Säilytä varjot",
-                "keep_opacity": "Säilytä läpinäkyvyys",
-                "keep_roundness": "Säilytä pyöristys",
-                "keep_fonts": "Säilytä fontit",
-                "reset": "Palauta",
-                "clear_all": "Tyhjennä kaikki",
-                "clear_opacity": "Tyhjennä läpinäkyvyys",
-                "load_theme": "Lataa teema",
-                "keep_as_is": "Pidä sellaisenaan",
-                "use_snapshot": "Vanha",
-                "use_source": "Uusi"
-            },
-            "advanced_colors": {
-                "selectedPost": "Valittu viesti",
-                "_tab_label": "Edistynyt",
-                "alert": "Varoituksen tausta",
-                "alert_error": "Virhe",
-                "alert_warning": "Varoitus",
-                "alert_neutral": "Neutraali",
-                "post": "Viestit/Käyttäjien kuvaukset",
-                "badge": "Merkin tausta",
-                "badge_notification": "Ilmoitus",
-                "panel_header": "Ruudun otsikko",
-                "top_bar": "Yläpalkki",
-                "borders": "Reunat",
-                "buttons": "Napit",
-                "inputs": "Syöttökentät",
-                "faint_text": "Häivytetty teksti",
-                "underlay": "Taustapeite",
-                "poll": "Äänestyksen kuvaaja",
-                "icons": "Ikonit",
-                "highlight": "Korostetut elementit",
-                "pressed": "Painettu",
-                "selectedMenu": "Valikon valinta",
-                "disabled": "Pois käytöstä",
-                "toggled": "Kytketty",
-                "tabs": "Välilehdet",
-                "popover": "Työkaluvinkit, valikot, ponnahdusviestit"
-            },
-            "common": {
-                "color": "Väri",
-                "opacity": "Läpinäkyvyys",
-                "contrast": {
-                    "level": {
-                        "aaa": "saavuttaa AAA-tason (suositeltu)",
-                        "aa": "saavuttaa AA-tason (minimi)",
-                        "bad": "ei saavuta mitään helppokäyttöisyyssuosituksia"
-                    },
-                    "hint": "Kontrastisuhde on {ratio}, se {level} {context}",
-                    "context": {
-                        "18pt": "suurella (18pt+) tekstillä",
-                        "text": "tekstillä"
-                    }
-                }
-            },
-            "common_colors": {
-                "_tab_label": "Yleinen",
-                "main": "Yleiset värit",
-                "foreground_hint": "Löydät \"Edistynyt\"-välilehdeltä tarkemmat asetukset",
-                "rgbo": "Ikonit, korostukset, merkit"
-            },
-            "shadows": {
-                "filter_hint": {
-                    "always_drop_shadow": "Varoitus, tämä varjo käyttää aina {0} kun selain tukee sitä.",
-                    "avatar_inset": "Huom. sisennettyjen ja ei-sisennettyjen varjojen yhdistelmät saattavat luoda ei-odotettuja lopputuloksia läpinäkyvillä profiilikuvilla.",
-                    "drop_shadow_syntax": "{0} ei tue {1} parametria ja {2} avainsanaa.",
-                    "spread_zero": "Varjot joiden levitys > 0 näyttävät samalta kuin se olisi nolla",
-                    "inset_classic": "Sisennetyt varjot käyttävät {0}"
-                },
-                "components": {
-                    "buttonPressedHover": "Nappi (painettu ja kohdistettu)",
-                    "panel": "Ruutu",
-                    "panelHeader": "Ruudun otsikko",
-                    "topBar": "Yläpalkki",
-                    "avatar": "Profiilikuva (profiilinäkymässä)",
-                    "avatarStatus": "Profiilikuva (viestin yhtyedessä)",
-                    "popup": "Ponnahdusviestit ja työkaluvinkit",
-                    "button": "Nappi",
-                    "buttonHover": "Nappi (kohdistus)",
-                    "buttonPressed": "Nappi (painettu)",
-                    "input": "Syöttökenttä"
-                },
-                "hintV3": "Voit käyttää {0} merkintää varjoille käyttääksesi väriä toisesta asetuksesta.",
-                "_tab_label": "Valo ja varjostus",
-                "component": "Komponentti",
-                "override": "Ylikirjoita",
-                "shadow_id": "Varjo #{value}",
-                "blur": "Sumennus",
-                "spread": "Levitys",
-                "inset": "Sisennys"
-            },
-            "fonts": {
-                "help": "Valitse fontti käyttöliittymälle. \"Oma\"-vaihtohdolle on syötettävä fontin nimi tarkalleen samana kuin se on järjestelmässäsi.",
-                "_tab_label": "Fontit",
-                "components": {
-                    "interface": "Käyttöliittymä",
-                    "input": "Syöttökentät",
-                    "post": "Viestin teksti",
-                    "postCode": "Tasavälistetty teksti viestissä"
-                },
-                "family": "Fontin nimi",
-                "size": "Koko (pikseleissä)",
-                "weight": "Painostus (paksuus)",
-                "custom": "Oma"
-            },
-            "preview": {
-                "input": "Tulin juuri saunasta.",
-                "header": "Esikatselu",
-                "content": "Sisältö",
-                "error": "Esimerkkivirhe",
-                "button": "Nappi",
-                "text": "Vähän lisää {0} ja {1}",
-                "mono": "sisältöä",
-                "faint_link": "manuaali",
-                "fine_print": "Lue meidän {0} vaikka huvin vuoksi!",
-                "header_faint": "Tämä on OK",
-                "checkbox": "Olen silmäillyt käyttöehdot",
-                "link": "kiva linkki"
-            },
-            "radii": {
-                "_tab_label": "Pyöristys"
-            }
-        },
-        "enter_current_password_to_confirm": "Syötä nykyinen salasanasi todentaaksesi henkilöllisyytesi",
-        "discoverable": "Salli tilisi näkyvyys hakukoneisiin ja muihin palveluihin",
-        "pad_emoji": "Välistä emojit välilyönneillä lisätessäsi niitä valitsimesta",
-        "mutes_tab": "Mykistykset",
-        "new_email": "Uusi sähköpostiosoite",
-        "notification_setting_follows": "Käyttäjät joita seuraat",
-        "notification_setting_non_follows": "Käyttäjät joita et seuraa",
-        "notification_setting_followers": "Käyttäjät jotka seuraavat sinua",
-        "notification_setting_non_followers": "Käyttäjät jotka eivät seuraa sinua",
-        "notification_setting_privacy": "Yksityisyys",
-        "notification_mutes": "Jos et halua ilmoituksia joltain käyttäjältä, käytä mykistystä.",
-        "notification_blocks": "Estäminen pysäyttää kaikki ilmoitukset käyttäjältä ja poistaa seurauksen.",
-        "version": {
-            "title": "Versio",
-            "backend_version": "Palvelimen versio",
-            "frontend_version": "Käyttöliittymän versio"
-        }
-    },
-    "time": {
-        "day": "{0} päivä",
-        "days": "{0} päivää",
-        "day_short": "{0}pv",
-        "days_short": "{0}pv",
-        "hour": "{0} tunti",
-        "hours": "{0} tuntia",
-        "hour_short": "{0}t",
-        "hours_short": "{0}t",
-        "in_future": "{0} tulevaisuudessa",
-        "in_past": "{0} sitten",
-        "minute": "{0} minuutti",
-        "minutes": "{0} minuuttia",
-        "minute_short": "{0}min",
-        "minutes_short": "{0}min",
-        "month": "{0} kuukausi",
-        "months": "{0} kuukautta",
-        "month_short": "{0}kk",
-        "months_short": "{0}kk",
-        "now": "juuri nyt",
-        "now_short": "nyt",
-        "second": "{0} sekunti",
-        "seconds": "{0} sekuntia",
-        "second_short": "{0}s",
-        "seconds_short": "{0}s",
-        "week": "{0} viikko",
-        "weeks": "{0} viikkoa",
-        "week_short": "{0}vk",
-        "weeks_short": "{0}vk",
-        "year": "{0} vuosi",
-        "years": "{0} vuotta",
-        "year_short": "{0}v",
-        "years_short": "{0}v"
-    },
-    "timeline": {
-        "collapse": "Sulje",
-        "conversation": "Keskustelu",
-        "error_fetching": "Virhe ladatessa viestejä",
-        "load_older": "Lataa vanhempia viestejä",
-        "no_retweet_hint": "Viesti ei ole julkinen, eikä sitä voi toistaa",
-        "repeated": "toisti",
-        "show_new": "Näytä uudet",
-        "up_to_date": "Ajantasalla",
-        "no_more_statuses": "Ei enempää viestejä",
-        "no_statuses": "Ei viestejä"
-    },
-    "status": {
-        "favorites": "Tykkäykset",
-        "repeats": "Toistot",
-        "delete": "Poista",
-        "pin": "Kiinnitä profiiliisi",
-        "unpin": "Poista kiinnitys",
-        "pinned": "Kiinnitetty",
-        "delete_confirm": "Haluatko varmasti postaa viestin?",
-        "reply_to": "Vastaus",
-        "replies_list": "Vastaukset:",
-        "mute_conversation": "Mykistä keskustelu",
-        "unmute_conversation": "Poista mykistys",
-        "status_unavailable": "Viesti ei saatavissa",
-        "copy_link": "Kopioi linkki"
-    },
-    "user_card": {
-        "approve": "Hyväksy",
-        "block": "Estä",
-        "blocked": "Estetty!",
-        "deny": "Älä hyväksy",
-        "follow": "Seuraa",
-        "follow_sent": "Pyyntö lähetetty!",
-        "follow_progress": "Pyydetään…",
-        "follow_again": "Lähetä pyyntö uudestaan",
-        "follow_unfollow": "Älä seuraa",
-        "followees": "Seuraa",
-        "followers": "Seuraajat",
-        "following": "Seuraat!",
-        "follows_you": "Seuraa sinua!",
-        "its_you": "Sinun tili!",
-        "mute": "Mykistä",
-        "muted": "Mykistetty",
-        "per_day": "päivässä",
-        "remote_follow": "Seuraa muualta",
-        "statuses": "Viestit",
-        "hidden": "Piilotettu",
-        "media": "Media",
-        "block_progress": "Estetään...",
-        "admin_menu": {
-            "grant_admin": "Anna Ylläpitöoikeudet",
-            "force_nsfw": "Merkitse kaikki viestit NSFW:nä",
-            "disable_any_subscription": "Estä käyttäjän seuraaminen",
-            "moderation": "Moderaatio",
-            "revoke_admin": "Poista Ylläpitöoikeudet",
-            "grant_moderator": "Anna Moderaattorioikeudet",
-            "revoke_moderator": "Poista Moderaattorioikeudet",
-            "activate_account": "Aktivoi tili",
-            "deactivate_account": "Deaktivoi tili",
-            "delete_account": "Poista tili",
-            "strip_media": "Poista media viesteistä",
-            "force_unlisted": "Pakota viestit listaamattomiksi",
-            "sandbox": "Pakota viestit vain seuraajille",
-            "disable_remote_subscription": "Estä seuraaminen ulkopuolisilta sivuilta",
-            "quarantine": "Estä käyttäjän viestin federoituminen",
-            "delete_user": "Poista käyttäjä",
-            "delete_user_confirmation": "Oletko aivan varma? Tätä ei voi kumota."
-        },
-        "favorites": "Tykkäykset",
-        "mention": "Mainitse",
-        "report": "Ilmianna",
-        "subscribe": "Tilaa",
-        "unsubscribe": "Poista tilaus",
-        "unblock": "Poista esto",
-        "unblock_progress": "Postetaan estoa...",
-        "unmute": "Poista mykistys",
-        "unmute_progress": "Poistetaan mykistystä...",
-        "mute_progress": "Mykistetään...",
-        "hide_repeats": "Piilota toistot",
-        "show_repeats": "Näytä toistot"
-    },
-    "user_profile": {
-        "timeline_title": "Käyttäjän aikajana",
-        "profile_does_not_exist": "Tätä profiilia ei ole.",
-        "profile_loading_error": "Virhe ladatessa profiilia."
-    },
-    "who_to_follow": {
-        "more": "Lisää",
-        "who_to_follow": "Seurausehdotukset"
-    },
-    "tool_tip": {
-        "media_upload": "Lataa tiedosto",
-        "repeat": "Toista",
-        "reply": "Vastaa",
-        "favorite": "Tykkää",
-        "user_settings": "Käyttäjäasetukset",
-        "add_reaction": "Lisää Reaktio",
-        "accept_follow_request": "Hyväksy seurauspyyntö",
-        "reject_follow_request": "Hylkää seurauspyyntö"
-    },
-    "upload": {
-        "error": {
-            "base": "Lataus epäonnistui.",
-            "file_too_big": "Tiedosto liian suuri [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-            "default": "Yritä uudestaan myöhemmin"
-        },
-        "file_size_units": {
-            "B": "tavua",
-            "KiB": "kt",
-            "MiB": "Mt",
-            "GiB": "Gt",
-            "TiB": "Tt"
-        }
-    },
-    "about": {
-        "mrf": {
-            "keyword": {
-                "keyword_policies": "Avainsanasäännöt",
-                "ftl_removal": "Poistettu \"Koko Tunnettu Verkosto\" -aikajanalta",
-                "reject": "Hylkää",
-                "replace": "Korvaa",
-                "is_replaced_by": "→"
-            },
-            "simple": {
-                "accept": "Hyväksy",
-                "reject": "Hylkää",
-                "quarantine": "Karanteeni",
-                "ftl_removal": "Poisto \"Koko Tunnettu Verkosto\" -aikajanalta",
-                "media_removal": "Media-tiedostojen poisto",
-                "simple_policies": "Palvelinkohtaiset Säännöt",
-                "accept_desc": "Tämä palvelin hyväksyy viestit vain seuraavilta palvelimilta:",
-                "reject_desc": "Tämä palvelin ei hyväksy viestejä seuraavilta palvelimilta:",
-                "quarantine_desc": "Tämä palvelin lähettää vain julkisia viestejä seuraaville palvelimille:",
-                "ftl_removal_desc": "Tämä palvelin poistaa nämä palvelimet \"Koko Tunnettu Verkosto\"-aikajanalta:",
-                "media_removal_desc": "Tämä palvelin postaa mediatiedostot viesteistä seuraavilta palvelimilta:",
-                "media_nsfw": "Pakota Media Arkaluontoiseksi",
-                "media_nsfw_desc": "Tämä palvelin pakottaa mediatiedostot arkaluonteisiksi seuraavilta palvelimilta:"
-            },
-            "federation": "Federaatio",
-            "mrf_policies": "Aktivoidut MRF-säännöt",
-            "mrf_policies_desc": "MRF-säännöt muuttavat federaation toimintaa sivulla. Seuraavat säännöt ovat kytketty päälle:"
-        },
-        "staff": "Henkilökunta"
-    },
-    "domain_mute_card": {
-        "mute": "Mykistä",
-        "unmute": "Poista mykistys",
-        "mute_progress": "Mykistetään...",
-        "unmute_progress": "Poistetaan mykistyst..."
-    },
-    "exporter": {
-        "export": "Vie",
-        "processing": "Käsitellään, hetken päästä voit tallentaa tiedoston"
-    },
-    "image_cropper": {
-        "crop_picture": "Rajaa kuva",
-        "save": "Tallenna",
-        "save_without_cropping": "Tallenna rajaamatta",
-        "cancel": "Peruuta"
-    },
-    "importer": {
-        "submit": "Hyväksy",
-        "error": "Virhe tapahtui tietoja tuodessa.",
-        "success": "Tuonti onnistui."
-    },
-    "media_modal": {
-        "previous": "Edellinen",
-        "next": "Seuraava"
-    },
-    "emoji": {
-        "stickers": "Tarrat",
-        "emoji": "Emoji",
-        "keep_open": "Pidä valitsin auki",
-        "search_emoji": "Hae emojia",
-        "add_emoji": "Lisää emoji",
-        "custom": "Custom-emoji",
-        "load_all": "Ladataan kaikkia {emojiAmount} emojia",
-        "unicode": "Unicode-emoji",
-        "load_all_hint": "Ensimmäiset {saneAmount} emojia ladattu, kaikkien emojien lataaminen voi aiheuttaa hidastelua."
-    },
-    "remote_user_resolver": {
-        "remote_user_resolver": "Ulkopuolinen käyttäjä",
-        "searching_for": "Etsitään käyttäjää",
-        "error": "Ei löytynyt."
-    },
-    "selectable_list": {
-        "select_all": "Valitse kaikki"
-    },
-    "password_reset": {
-        "check_email": "Tarkista sähköpostisi salasanannollausta varten.",
-        "instruction": "Syötä sähköpostiosoite tai käyttäjänimi. Lähetämme linkin salasanan nollausta varten.",
-        "password_reset_disabled": "Salasanan nollaus ei käytössä. Ota yhteyttä sivun ylläpitäjään.",
-        "password_reset_required_but_mailer_is_disabled": "Sinun täytyy vaihtaa salasana, mutta salasanan nollaus on pois käytöstä. Ota yhteyttä sivun ylläpitäjään.",
-        "forgot_password": "Unohditko salasanan?",
-        "password_reset": "Salasanan nollaus",
-        "placeholder": "Sähköpostiosoite tai käyttäjänimi",
-        "return_home": "Palaa etusivulle",
-        "not_found": "Sähköpostiosoitetta tai käyttäjänimeä ei löytynyt.",
-        "too_many_requests": "Olet käyttänyt kaikki yritykset, yritä uudelleen myöhemmin.",
-        "password_reset_required": "Sinun täytyy vaihtaa salasana kirjautuaksesi."
-    },
-    "user_reporting": {
-        "add_comment_description": "Tämä raportti lähetetään sivun moderaattoreille. Voit antaa selityksen miksi ilmiannoit tilin:",
-        "title": "Ilmiannetaan {0}",
-        "additional_comments": "Lisäkommentit",
-        "forward_description": "Tämä tili on toiselta palvelimelta. Lähetä kopio ilmiannosta sinnekin?",
-        "forward_to": "Lähetä eteenpäin: {0}",
-        "submit": "Lähetä",
-        "generic_error": "Virhe käsitellessä pyyntöä."
-    },
-    "search": {
-        "people": "Käyttäjät",
-        "hashtags": "Aihetunnisteet",
-        "people_talking": "{0} käyttäjää puhuvat",
-        "person_talking": "{0} käyttäjä puhuu",
-        "no_results": "Ei tuloksia"
+    "direct_warning_to_all": "Tämä viesti näkyy vain viestissä mainituille käyttäjille.",
+    "direct_warning_to_first_only": "Tämä viesti näkyy vain viestin alussa mainituille käyttäjille.",
+    "scope_notice": {
+      "public": "Tämä viesti näkyy kaikille",
+      "private": "Tämä viesti näkyy vain sinun seuraajillesi",
+      "unlisted": "Tämä viesti ei näy Julkisella Aikajanalla tai Koko Tunnettu Verkosto -aikajanalla"
     }
+  },
+  "registration": {
+    "bio": "Kuvaus",
+    "email": "Sähköposti",
+    "fullname": "Koko nimi",
+    "password_confirm": "Salasanan vahvistaminen",
+    "registration": "Rekisteröityminen",
+    "token": "Kutsuvaltuus",
+    "captcha": "Varmenne",
+    "new_captcha": "Paina kuvaa saadaksesi uuden varmenteen",
+    "validations": {
+      "username_required": "ei voi olla tyhjä",
+      "fullname_required": "ei voi olla tyhjä",
+      "email_required": "ei voi olla tyhjä",
+      "password_required": "ei voi olla tyhjä",
+      "password_confirmation_required": "ei voi olla tyhjä",
+      "password_confirmation_match": "pitää vastata salasanaa"
+    },
+    "username_placeholder": "esim. peke",
+    "fullname_placeholder": "esim. Pekka Postaaja",
+    "bio_placeholder": "esim.\nHei, olen Pekka.\nOlen esimerkkikäyttäjä tässä verkostossa."
+  },
+  "settings": {
+    "attachmentRadius": "Liitteet",
+    "attachments": "Liitteet",
+    "autoload": "Lataa vanhempia viestejä automaattisesti ruudun pohjalla",
+    "avatar": "Profiilikuva",
+    "avatarAltRadius": "Profiilikuvat (ilmoitukset)",
+    "avatarRadius": "Profiilikuvat",
+    "background": "Tausta",
+    "bio": "Kuvaus",
+    "btnRadius": "Napit",
+    "cBlue": "Sininen (Vastaukset, seuraukset)",
+    "cGreen": "Vihreä (Toistot)",
+    "cOrange": "Oranssi (Tykkäykset)",
+    "cRed": "Punainen (Peruminen)",
+    "change_password": "Vaihda salasana",
+    "change_password_error": "Virhe vaihtaessa salasanaa.",
+    "changed_password": "Salasana vaihdettu!",
+    "collapse_subject": "Minimoi viestit, joille on asetettu aihe",
+    "composing": "Viestien laatiminen",
+    "confirm_new_password": "Vahvista uusi salasana",
+    "current_avatar": "Nykyinen profiilikuvasi",
+    "current_password": "Nykyinen salasana",
+    "current_profile_banner": "Nykyinen julisteesi",
+    "data_import_export_tab": "Tietojen tuonti / vienti",
+    "default_vis": "Oletusnäkyvyysrajaus",
+    "delete_account": "Poista tili",
+    "delete_account_description": "Poista tilisi ja viestisi pysyvästi.",
+    "delete_account_error": "Virhe poistaessa tiliäsi. Jos virhe jatkuu, ota yhteyttä palvelimesi ylläpitoon.",
+    "delete_account_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.",
+    "emoji_reactions_on_timeline": "Näytä emojireaktiot aikajanalla",
+    "export_theme": "Tallenna teema",
+    "filtering": "Suodatus",
+    "filtering_explanation": "Kaikki viestit, jotka sisältävät näitä sanoja, suodatetaan. Yksi sana per rivi.",
+    "follow_export": "Seurausten vienti",
+    "follow_export_button": "Vie seurauksesi CSV-tiedostoon",
+    "follow_export_processing": "Käsitellään, sinua pyydetään lataamaan tiedosto hetken päästä",
+    "follow_import": "Seurausten tuonti",
+    "follow_import_error": "Virhe tuodessa seuraksia",
+    "follows_imported": "Seuraukset tuotu! Niiden käsittely vie hetken.",
+    "foreground": "Etuala",
+    "general": "Yleinen",
+    "hide_attachments_in_convo": "Piilota liitteet keskusteluissa",
+    "hide_attachments_in_tl": "Piilota liitteet aikajanalla",
+    "max_thumbnails": "Suurin sallittu määrä liitteitä esikatselussa",
+    "hide_isp": "Piilota palvelimenkohtainen ruutu",
+    "preload_images": "Esilataa kuvat",
+    "use_one_click_nsfw": "Avaa NSFW-liitteet yhdellä painalluksella",
+    "hide_post_stats": "Piilota viestien statistiikka (esim. tykkäysten määrä)",
+    "hide_user_stats": "Piilota käyttäjien statistiikka (esim. seuraajien määrä)",
+    "import_followers_from_a_csv_file": "Tuo seuraukset CSV-tiedostosta",
+    "import_theme": "Tuo tallennettu teema",
+    "inputRadius": "Syöttökentät",
+    "checkboxRadius": "Valintalaatikot",
+    "instance_default": "(oletus: {value})",
+    "instance_default_simple": "(oletus)",
+    "interface": "Käyttöliittymä",
+    "interfaceLanguage": "Käyttöliittymän kieli",
+    "invalid_theme_imported": "Tuotu tallennettu teema on epäkelpo, muutoksia ei tehty nykyiseen teemaasi.",
+    "limited_availability": "Ei saatavilla selaimessasi",
+    "links": "Linkit",
+    "lock_account_description": "Vain erikseen hyväksytyt käyttäjät voivat seurata tiliäsi",
+    "loop_video": "Uudelleentoista videot",
+    "loop_video_silent_only": "Uudelleentoista ainoastaan äänettömät videot (Video-\"giffit\")",
+    "play_videos_in_modal": "Toista videot modaalissa",
+    "use_contain_fit": "Älä rajaa liitteitä esikatselussa",
+    "name": "Nimi",
+    "name_bio": "Nimi ja kuvaus",
+    "new_password": "Uusi salasana",
+    "notification_visibility": "Ilmoitusten näkyvyys",
+    "notification_visibility_follows": "Seuraukset",
+    "notification_visibility_likes": "Tykkäykset",
+    "notification_visibility_mentions": "Maininnat",
+    "notification_visibility_repeats": "Toistot",
+    "notification_visibility_emoji_reactions": "Reaktiot",
+    "no_rich_text_description": "Älä näytä tekstin muotoilua",
+    "hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
+    "nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
+    "oauth_tokens": "OAuth-merkit",
+    "token": "Token",
+    "refresh_token": "Päivitä token",
+    "valid_until": "Voimassa asti",
+    "revoke_token": "Peruuta",
+    "panelRadius": "Ruudut",
+    "pause_on_unfocused": "Pysäytä automaattinen viestien näyttö välilehden ollessa pois fokuksesta",
+    "presets": "Valmiit teemat",
+    "profile_background": "Taustakuva",
+    "profile_banner": "Juliste",
+    "profile_tab": "Profiili",
+    "radii_help": "Aseta reunojen pyöristys (pikseleinä)",
+    "replies_in_timeline": "Keskustelut aikajanalla",
+    "reply_link_preview": "Keskusteluiden vastauslinkkien esikatselu",
+    "reply_visibility_all": "Näytä kaikki vastaukset",
+    "reply_visibility_following": "Näytä vain vastaukset minulle tai seuraamilleni käyttäjille",
+    "reply_visibility_self": "Näytä vain vastaukset minulle",
+    "saving_err": "Virhe tallentaessa asetuksia",
+    "saving_ok": "Asetukset tallennettu",
+    "security_tab": "Tietoturva",
+    "scope_copy": "Kopioi näkyvyysrajaus vastatessa (Yksityisviestit aina kopioivat)",
+    "set_new_avatar": "Aseta uusi profiilikuva",
+    "set_new_profile_background": "Aseta uusi taustakuva",
+    "set_new_profile_banner": "Aseta uusi juliste",
+    "settings": "Asetukset",
+    "subject_input_always_show": "Näytä aihe-kenttä",
+    "subject_line_behavior": "Aihe-kentän kopiointi",
+    "subject_line_email": "Kuten sähköposti: \"re: aihe\"",
+    "subject_line_mastodon": "Kopioi sellaisenaan",
+    "subject_line_noop": "Älä kopioi",
+    "stop_gifs": "Toista giffit vain kohdistaessa",
+    "streaming": "Näytä uudet viestit automaattisesti ollessasi ruudun huipulla",
+    "text": "Teksti",
+    "theme": "Teema",
+    "theme_help": "Käytä heksadesimaalivärejä muokataksesi väriteemaasi.",
+    "theme_help_v2_1": "Voit asettaa tiettyjen osien värin tai läpinäkyvyyden täyttämällä valintalaatikon, käytä \"Tyhjennä kaikki\"-nappia tyhjentääksesi kaiken.",
+    "theme_help_v2_2": "Ikonit kenttien alla ovat kontrasti-indikaattoreita, lisätietoa kohdistamalla. Käyttäessä läpinäkyvyyttä ne näyttävät pahimman skenaarion.",
+    "tooltipRadius": "Ohje- tai huomioviestit",
+    "user_settings": "Käyttäjän asetukset",
+    "values": {
+      "false": "pois päältä",
+      "true": "päällä"
+    },
+    "hide_follows_description": "Älä näytä ketä seuraan",
+    "show_moderator_badge": "Näytä Moderaattori-merkki profiilissani",
+    "useStreamingApi": "Vastaanota viestiejä ja ilmoituksia reaaliajassa",
+    "notification_setting_filters": "Suodattimet",
+    "notification_setting": "Vastaanota ilmoituksia seuraavista:",
+    "notification_setting_privacy_option": "Piilota lähettäjä ja sisältö sovelluksen ulkopuolisista ilmoituksista",
+    "enable_web_push_notifications": "Ota käyttöön sovelluksen ulkopuoliset ilmoitukset",
+    "app_name": "Sovelluksen nimi",
+    "security": "Turvallisuus",
+    "mfa": {
+      "otp": "OTP",
+      "setup_otp": "OTP-asetukset",
+      "wait_pre_setup_otp": "esiasetetaan OTP:ta",
+      "confirm_and_enable": "Hyväksy ja käytä OTP",
+      "title": "Monivaihetodennus",
+      "generate_new_recovery_codes": "Luo uudet palautuskoodit",
+      "authentication_methods": "Todennus",
+      "warning_of_generate_new_codes": "Luodessasi uudet palautuskoodit, vanhat koodisi lakkaavat toimimasta.",
+      "recovery_codes": "Palautuskoodit.",
+      "waiting_a_recovery_codes": "Odotetaan palautuskoodeja...",
+      "recovery_codes_warning": "Kirjoita koodit ylös tai tallenna ne turvallisesti, muuten et näe niitä uudestaan. Jos et voi käyttää monivaihetodennusta ja sinulla ei ole palautuskoodeja, et voi enää kirjautua sisään tilillesi.",
+      "scan": {
+        "title": "Skannaa",
+        "secret_code": "Avain",
+        "desc": "Käytä monivaihetodennus-sovellusta skannakksesi tämän QR-kooding, tai syötä avain:"
+      },
+      "verify": {
+        "desc": "Kytkeäksesi päälle monivaihetodennuksen, syötä koodi monivaihetodennussovellksesta:"
+      }
+    },
+    "allow_following_move": "Salli automaattinen seuraaminen kun käyttäjä siirtää tilinsä",
+    "block_export": "Estojen vienti",
+    "block_export_button": "Vie estosi CSV-tiedostoon",
+    "block_import": "Estojen tuonti",
+    "block_import_error": "Virhe tuodessa estoja",
+    "blocks_imported": "Estot tuotu! Käsittely vie hetken.",
+    "blocks_tab": "Estot",
+    "change_email": "Vaihda sähköpostiosoite",
+    "change_email_error": "Virhe vaihtaessa sähköpostiosoitetta.",
+    "changed_email": "Sähköpostiosoite vaihdettu!",
+    "domain_mutes": "Sivut",
+    "avatar_size_instruction": "Suositeltu vähimmäiskoko profiilikuville on 150x150 pikseliä.",
+    "accent": "Korostus",
+    "hide_muted_posts": "Piilota mykistettyjen käyttäjien viestit",
+    "hide_filtered_statuses": "Piilota mykistetyt viestit",
+    "import_blocks_from_a_csv_file": "Tuo estot CSV-tiedostosta",
+    "no_blocks": "Ei estoja",
+    "no_mutes": "Ei mykistyksiä",
+    "notification_visibility_moves": "Käyttäjien siirtymiset",
+    "hide_followers_description": "Älä näytä ketkä seuraavat minua",
+    "hide_follows_count_description": "Älä näytä seurauksien määrää",
+    "hide_followers_count_description": "Älä näytä seuraajien määrää",
+    "show_admin_badge": "Näytä Ylläpitäjä-merkki proofilissani",
+    "autohide_floating_post_button": "Piilota Uusi Viesti -nappi automaattisesti (mobiili)",
+    "search_user_to_block": "Hae estettäviä käyttäjiä",
+    "search_user_to_mute": "Hae mykistettäviä käyttäjiä",
+    "minimal_scopes_mode": "Yksinkertaista näkyvyydenrajauksen vaihtoehdot",
+    "post_status_content_type": "Uuden viestin sisällön muoto",
+    "user_mutes": "Käyttäjät",
+    "useStreamingApiWarning": "(Kokeellinen)",
+    "type_domains_to_mute": "Syötä mykistettäviä sivustoja",
+    "upload_a_photo": "Lataa kuva",
+    "fun": "Hupi",
+    "greentext": "Meeminuolet",
+    "notifications": "Ilmoitukset",
+    "style": {
+      "switcher": {
+        "save_load_hint": "\"Säilytä\" asetukset säilyttävät tällä hetkellä asetetut asetukset valittaessa tai ladatessa teemaa, se myös tallentaa kyseiset asetukset viedessä teemaa. Kun kaikki laatikot ovat tyhjänä, viety teema tallentaa kaiken.",
+        "help": {
+          "older_version_imported": "Tuomasi tiedosto on luotu vanhemmalla versiolla.",
+          "fe_upgraded": "PleromaFE:n teemaus päivitetty versiopäivityksen yhteydessä.",
+          "migration_snapshot_ok": "Varmuuden vuoksi teeman kaappaus ladattu. Voit koittaa ladata teeman sisällön.",
+          "migration_napshot_gone": "Jostain syystä teeman kaappaus puuttuu, kaikki asiat eivät välttämättä näytä oikealta.",
+          "snapshot_source_mismatch": "Versiot eivät täsmää: todennäköisesti versio vaihdettu vanhempaan ja päivitetty uudestaan, jos vaihdoit teemaa vanhalla versiolla, sinun tulisi käyttää vanhaa versiota, muutoin uutta.",
+          "upgraded_from_v2": "PleromaFE on päivitetty, teemasi saattaa näyttää erilaiselta kuin muistat.",
+          "v2_imported": "Tuomasi tiedosto on luotu vanhemmalla versiolla. Yhteensopivuus ei välttämättä ole täydellinen.",
+          "future_version_imported": "Tuomasi tiedosto on luotu uudemmalla versiolla.",
+          "snapshot_present": "Teeman kaappaus ladattu, joten kaikki arvot ovat ylikirjoitettu. Voit sen sijaan ladata teeman sisällön.",
+          "snapshot_missing": "Teeman kaappausta ei tiedostossa, joten se voi näyttää erilaiselta kuin suunniteltu.",
+          "fe_downgraded": "PleromaFE:n versio vaihtunut vanhempaan."
+        },
+        "keep_color": "Säilytä värit",
+        "keep_shadows": "Säilytä varjot",
+        "keep_opacity": "Säilytä läpinäkyvyys",
+        "keep_roundness": "Säilytä pyöristys",
+        "keep_fonts": "Säilytä fontit",
+        "reset": "Palauta",
+        "clear_all": "Tyhjennä kaikki",
+        "clear_opacity": "Tyhjennä läpinäkyvyys",
+        "load_theme": "Lataa teema",
+        "keep_as_is": "Pidä sellaisenaan",
+        "use_snapshot": "Vanha",
+        "use_source": "Uusi"
+      },
+      "advanced_colors": {
+        "selectedPost": "Valittu viesti",
+        "_tab_label": "Edistynyt",
+        "alert": "Varoituksen tausta",
+        "alert_error": "Virhe",
+        "alert_warning": "Varoitus",
+        "alert_neutral": "Neutraali",
+        "post": "Viestit/Käyttäjien kuvaukset",
+        "badge": "Merkin tausta",
+        "badge_notification": "Ilmoitus",
+        "panel_header": "Ruudun otsikko",
+        "top_bar": "Yläpalkki",
+        "borders": "Reunat",
+        "buttons": "Napit",
+        "inputs": "Syöttökentät",
+        "faint_text": "Häivytetty teksti",
+        "underlay": "Taustapeite",
+        "poll": "Äänestyksen kuvaaja",
+        "icons": "Ikonit",
+        "highlight": "Korostetut elementit",
+        "pressed": "Painettu",
+        "selectedMenu": "Valikon valinta",
+        "disabled": "Pois käytöstä",
+        "toggled": "Kytketty",
+        "tabs": "Välilehdet",
+        "popover": "Työkaluvinkit, valikot, ponnahdusviestit"
+      },
+      "common": {
+        "color": "Väri",
+        "opacity": "Läpinäkyvyys",
+        "contrast": {
+          "level": {
+            "aaa": "saavuttaa AAA-tason (suositeltu)",
+            "aa": "saavuttaa AA-tason (minimi)",
+            "bad": "ei saavuta mitään helppokäyttöisyyssuosituksia"
+          },
+          "hint": "Kontrastisuhde on {ratio}, se {level} {context}",
+          "context": {
+            "18pt": "suurella (18pt+) tekstillä",
+            "text": "tekstillä"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "Yleinen",
+        "main": "Yleiset värit",
+        "foreground_hint": "Löydät \"Edistynyt\"-välilehdeltä tarkemmat asetukset",
+        "rgbo": "Ikonit, korostukset, merkit"
+      },
+      "shadows": {
+        "filter_hint": {
+          "always_drop_shadow": "Varoitus, tämä varjo käyttää aina {0} kun selain tukee sitä.",
+          "avatar_inset": "Huom. sisennettyjen ja ei-sisennettyjen varjojen yhdistelmät saattavat luoda ei-odotettuja lopputuloksia läpinäkyvillä profiilikuvilla.",
+          "drop_shadow_syntax": "{0} ei tue {1} parametria ja {2} avainsanaa.",
+          "spread_zero": "Varjot joiden levitys > 0 näyttävät samalta kuin se olisi nolla",
+          "inset_classic": "Sisennetyt varjot käyttävät {0}"
+        },
+        "components": {
+          "buttonPressedHover": "Nappi (painettu ja kohdistettu)",
+          "panel": "Ruutu",
+          "panelHeader": "Ruudun otsikko",
+          "topBar": "Yläpalkki",
+          "avatar": "Profiilikuva (profiilinäkymässä)",
+          "avatarStatus": "Profiilikuva (viestin yhtyedessä)",
+          "popup": "Ponnahdusviestit ja työkaluvinkit",
+          "button": "Nappi",
+          "buttonHover": "Nappi (kohdistus)",
+          "buttonPressed": "Nappi (painettu)",
+          "input": "Syöttökenttä"
+        },
+        "hintV3": "Voit käyttää {0} merkintää varjoille käyttääksesi väriä toisesta asetuksesta.",
+        "_tab_label": "Valo ja varjostus",
+        "component": "Komponentti",
+        "override": "Ylikirjoita",
+        "shadow_id": "Varjo #{value}",
+        "blur": "Sumennus",
+        "spread": "Levitys",
+        "inset": "Sisennys"
+      },
+      "fonts": {
+        "help": "Valitse fontti käyttöliittymälle. \"Oma\"-vaihtohdolle on syötettävä fontin nimi tarkalleen samana kuin se on järjestelmässäsi.",
+        "_tab_label": "Fontit",
+        "components": {
+          "interface": "Käyttöliittymä",
+          "input": "Syöttökentät",
+          "post": "Viestin teksti",
+          "postCode": "Tasavälistetty teksti viestissä"
+        },
+        "family": "Fontin nimi",
+        "size": "Koko (pikseleissä)",
+        "weight": "Painostus (paksuus)",
+        "custom": "Oma"
+      },
+      "preview": {
+        "input": "Tulin juuri saunasta.",
+        "header": "Esikatselu",
+        "content": "Sisältö",
+        "error": "Esimerkkivirhe",
+        "button": "Nappi",
+        "text": "Vähän lisää {0} ja {1}",
+        "mono": "sisältöä",
+        "faint_link": "manuaali",
+        "fine_print": "Lue meidän {0} vaikka huvin vuoksi!",
+        "header_faint": "Tämä on OK",
+        "checkbox": "Olen silmäillyt käyttöehdot",
+        "link": "kiva linkki"
+      },
+      "radii": {
+        "_tab_label": "Pyöristys"
+      }
+    },
+    "enter_current_password_to_confirm": "Syötä nykyinen salasanasi todentaaksesi henkilöllisyytesi",
+    "discoverable": "Salli tilisi näkyvyys hakukoneisiin ja muihin palveluihin",
+    "pad_emoji": "Välistä emojit välilyönneillä lisätessäsi niitä valitsimesta",
+    "mutes_tab": "Mykistykset",
+    "new_email": "Uusi sähköpostiosoite",
+    "notification_setting_follows": "Käyttäjät joita seuraat",
+    "notification_setting_non_follows": "Käyttäjät joita et seuraa",
+    "notification_setting_followers": "Käyttäjät jotka seuraavat sinua",
+    "notification_setting_non_followers": "Käyttäjät jotka eivät seuraa sinua",
+    "notification_setting_privacy": "Yksityisyys",
+    "notification_mutes": "Jos et halua ilmoituksia joltain käyttäjältä, käytä mykistystä.",
+    "notification_blocks": "Estäminen pysäyttää kaikki ilmoitukset käyttäjältä ja poistaa seurauksen.",
+    "version": {
+      "title": "Versio",
+      "backend_version": "Palvelimen versio",
+      "frontend_version": "Käyttöliittymän versio"
+    }
+  },
+  "time": {
+    "day": "{0} päivä",
+    "days": "{0} päivää",
+    "day_short": "{0}pv",
+    "days_short": "{0}pv",
+    "hour": "{0} tunti",
+    "hours": "{0} tuntia",
+    "hour_short": "{0}t",
+    "hours_short": "{0}t",
+    "in_future": "{0} tulevaisuudessa",
+    "in_past": "{0} sitten",
+    "minute": "{0} minuutti",
+    "minutes": "{0} minuuttia",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} kuukausi",
+    "months": "{0} kuukautta",
+    "month_short": "{0}kk",
+    "months_short": "{0}kk",
+    "now": "juuri nyt",
+    "now_short": "nyt",
+    "second": "{0} sekunti",
+    "seconds": "{0} sekuntia",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} viikko",
+    "weeks": "{0} viikkoa",
+    "week_short": "{0}vk",
+    "weeks_short": "{0}vk",
+    "year": "{0} vuosi",
+    "years": "{0} vuotta",
+    "year_short": "{0}v",
+    "years_short": "{0}v"
+  },
+  "timeline": {
+    "collapse": "Sulje",
+    "conversation": "Keskustelu",
+    "error_fetching": "Virhe ladatessa viestejä",
+    "load_older": "Lataa vanhempia viestejä",
+    "no_retweet_hint": "Viesti ei ole julkinen, eikä sitä voi toistaa",
+    "repeated": "toisti",
+    "show_new": "Näytä uudet",
+    "up_to_date": "Ajantasalla",
+    "no_more_statuses": "Ei enempää viestejä",
+    "no_statuses": "Ei viestejä"
+  },
+  "status": {
+    "favorites": "Tykkäykset",
+    "repeats": "Toistot",
+    "delete": "Poista",
+    "pin": "Kiinnitä profiiliisi",
+    "unpin": "Poista kiinnitys",
+    "pinned": "Kiinnitetty",
+    "delete_confirm": "Haluatko varmasti postaa viestin?",
+    "reply_to": "Vastaus",
+    "replies_list": "Vastaukset:",
+    "mute_conversation": "Mykistä keskustelu",
+    "unmute_conversation": "Poista mykistys",
+    "status_unavailable": "Viesti ei saatavissa",
+    "copy_link": "Kopioi linkki"
+  },
+  "user_card": {
+    "approve": "Hyväksy",
+    "block": "Estä",
+    "blocked": "Estetty!",
+    "deny": "Älä hyväksy",
+    "follow": "Seuraa",
+    "follow_sent": "Pyyntö lähetetty!",
+    "follow_progress": "Pyydetään…",
+    "follow_again": "Lähetä pyyntö uudestaan",
+    "follow_unfollow": "Älä seuraa",
+    "followees": "Seuraa",
+    "followers": "Seuraajat",
+    "following": "Seuraat!",
+    "follows_you": "Seuraa sinua!",
+    "its_you": "Sinun tili!",
+    "mute": "Mykistä",
+    "muted": "Mykistetty",
+    "per_day": "päivässä",
+    "remote_follow": "Seuraa muualta",
+    "statuses": "Viestit",
+    "hidden": "Piilotettu",
+    "media": "Media",
+    "block_progress": "Estetään...",
+    "admin_menu": {
+      "grant_admin": "Anna Ylläpitöoikeudet",
+      "force_nsfw": "Merkitse kaikki viestit NSFW:nä",
+      "disable_any_subscription": "Estä käyttäjän seuraaminen",
+      "moderation": "Moderaatio",
+      "revoke_admin": "Poista Ylläpitöoikeudet",
+      "grant_moderator": "Anna Moderaattorioikeudet",
+      "revoke_moderator": "Poista Moderaattorioikeudet",
+      "activate_account": "Aktivoi tili",
+      "deactivate_account": "Deaktivoi tili",
+      "delete_account": "Poista tili",
+      "strip_media": "Poista media viesteistä",
+      "force_unlisted": "Pakota viestit listaamattomiksi",
+      "sandbox": "Pakota viestit vain seuraajille",
+      "disable_remote_subscription": "Estä seuraaminen ulkopuolisilta sivuilta",
+      "quarantine": "Estä käyttäjän viestin federoituminen",
+      "delete_user": "Poista käyttäjä",
+      "delete_user_confirmation": "Oletko aivan varma? Tätä ei voi kumota."
+    },
+    "favorites": "Tykkäykset",
+    "mention": "Mainitse",
+    "report": "Ilmianna",
+    "subscribe": "Tilaa",
+    "unsubscribe": "Poista tilaus",
+    "unblock": "Poista esto",
+    "unblock_progress": "Postetaan estoa...",
+    "unmute": "Poista mykistys",
+    "unmute_progress": "Poistetaan mykistystä...",
+    "mute_progress": "Mykistetään...",
+    "hide_repeats": "Piilota toistot",
+    "show_repeats": "Näytä toistot"
+  },
+  "user_profile": {
+    "timeline_title": "Käyttäjän aikajana",
+    "profile_does_not_exist": "Tätä profiilia ei ole.",
+    "profile_loading_error": "Virhe ladatessa profiilia."
+  },
+  "who_to_follow": {
+    "more": "Lisää",
+    "who_to_follow": "Seurausehdotukset"
+  },
+  "tool_tip": {
+    "media_upload": "Lataa tiedosto",
+    "repeat": "Toista",
+    "reply": "Vastaa",
+    "favorite": "Tykkää",
+    "user_settings": "Käyttäjäasetukset",
+    "add_reaction": "Lisää Reaktio",
+    "accept_follow_request": "Hyväksy seurauspyyntö",
+    "reject_follow_request": "Hylkää seurauspyyntö"
+  },
+  "upload": {
+    "error": {
+      "base": "Lataus epäonnistui.",
+      "file_too_big": "Tiedosto liian suuri [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+      "default": "Yritä uudestaan myöhemmin"
+    },
+    "file_size_units": {
+      "B": "tavua",
+      "KiB": "kt",
+      "MiB": "Mt",
+      "GiB": "Gt",
+      "TiB": "Tt"
+    }
+  },
+  "about": {
+    "mrf": {
+      "keyword": {
+        "keyword_policies": "Avainsanasäännöt",
+        "ftl_removal": "Poistettu \"Koko Tunnettu Verkosto\" -aikajanalta",
+        "reject": "Hylkää",
+        "replace": "Korvaa",
+        "is_replaced_by": "→"
+      },
+      "simple": {
+        "accept": "Hyväksy",
+        "reject": "Hylkää",
+        "quarantine": "Karanteeni",
+        "ftl_removal": "Poisto \"Koko Tunnettu Verkosto\" -aikajanalta",
+        "media_removal": "Media-tiedostojen poisto",
+        "simple_policies": "Palvelinkohtaiset Säännöt",
+        "accept_desc": "Tämä palvelin hyväksyy viestit vain seuraavilta palvelimilta:",
+        "reject_desc": "Tämä palvelin ei hyväksy viestejä seuraavilta palvelimilta:",
+        "quarantine_desc": "Tämä palvelin lähettää vain julkisia viestejä seuraaville palvelimille:",
+        "ftl_removal_desc": "Tämä palvelin poistaa nämä palvelimet \"Koko Tunnettu Verkosto\"-aikajanalta:",
+        "media_removal_desc": "Tämä palvelin postaa mediatiedostot viesteistä seuraavilta palvelimilta:",
+        "media_nsfw": "Pakota Media Arkaluontoiseksi",
+        "media_nsfw_desc": "Tämä palvelin pakottaa mediatiedostot arkaluonteisiksi seuraavilta palvelimilta:"
+      },
+      "federation": "Federaatio",
+      "mrf_policies": "Aktivoidut MRF-säännöt",
+      "mrf_policies_desc": "MRF-säännöt muuttavat federaation toimintaa sivulla. Seuraavat säännöt ovat kytketty päälle:"
+    },
+    "staff": "Henkilökunta"
+  },
+  "domain_mute_card": {
+    "mute": "Mykistä",
+    "unmute": "Poista mykistys",
+    "mute_progress": "Mykistetään...",
+    "unmute_progress": "Poistetaan mykistyst..."
+  },
+  "exporter": {
+    "export": "Vie",
+    "processing": "Käsitellään, hetken päästä voit tallentaa tiedoston"
+  },
+  "image_cropper": {
+    "crop_picture": "Rajaa kuva",
+    "save": "Tallenna",
+    "save_without_cropping": "Tallenna rajaamatta",
+    "cancel": "Peruuta"
+  },
+  "importer": {
+    "submit": "Hyväksy",
+    "error": "Virhe tapahtui tietoja tuodessa.",
+    "success": "Tuonti onnistui."
+  },
+  "media_modal": {
+    "previous": "Edellinen",
+    "next": "Seuraava"
+  },
+  "emoji": {
+    "stickers": "Tarrat",
+    "emoji": "Emoji",
+    "keep_open": "Pidä valitsin auki",
+    "search_emoji": "Hae emojia",
+    "add_emoji": "Lisää emoji",
+    "custom": "Custom-emoji",
+    "load_all": "Ladataan kaikkia {emojiAmount} emojia",
+    "unicode": "Unicode-emoji",
+    "load_all_hint": "Ensimmäiset {saneAmount} emojia ladattu, kaikkien emojien lataaminen voi aiheuttaa hidastelua."
+  },
+  "remote_user_resolver": {
+    "remote_user_resolver": "Ulkopuolinen käyttäjä",
+    "searching_for": "Etsitään käyttäjää",
+    "error": "Ei löytynyt."
+  },
+  "selectable_list": {
+    "select_all": "Valitse kaikki"
+  },
+  "password_reset": {
+    "check_email": "Tarkista sähköpostisi salasanannollausta varten.",
+    "instruction": "Syötä sähköpostiosoite tai käyttäjänimi. Lähetämme linkin salasanan nollausta varten.",
+    "password_reset_disabled": "Salasanan nollaus ei käytössä. Ota yhteyttä sivun ylläpitäjään.",
+    "password_reset_required_but_mailer_is_disabled": "Sinun täytyy vaihtaa salasana, mutta salasanan nollaus on pois käytöstä. Ota yhteyttä sivun ylläpitäjään.",
+    "forgot_password": "Unohditko salasanan?",
+    "password_reset": "Salasanan nollaus",
+    "placeholder": "Sähköpostiosoite tai käyttäjänimi",
+    "return_home": "Palaa etusivulle",
+    "not_found": "Sähköpostiosoitetta tai käyttäjänimeä ei löytynyt.",
+    "too_many_requests": "Olet käyttänyt kaikki yritykset, yritä uudelleen myöhemmin.",
+    "password_reset_required": "Sinun täytyy vaihtaa salasana kirjautuaksesi."
+  },
+  "user_reporting": {
+    "add_comment_description": "Tämä raportti lähetetään sivun moderaattoreille. Voit antaa selityksen miksi ilmiannoit tilin:",
+    "title": "Ilmiannetaan {0}",
+    "additional_comments": "Lisäkommentit",
+    "forward_description": "Tämä tili on toiselta palvelimelta. Lähetä kopio ilmiannosta sinnekin?",
+    "forward_to": "Lähetä eteenpäin: {0}",
+    "submit": "Lähetä",
+    "generic_error": "Virhe käsitellessä pyyntöä."
+  },
+  "search": {
+    "people": "Käyttäjät",
+    "hashtags": "Aihetunnisteet",
+    "people_talking": "{0} käyttäjää puhuvat",
+    "person_talking": "{0} käyttäjä puhuu",
+    "no_results": "Ei tuloksia"
+  }
 }
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 719bde76..31b69a0f 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -1,743 +1,743 @@
 {
-    "chat": {
-        "title": "Chat"
-    },
-    "exporter": {
-        "export": "Exporter",
-        "processing": "En cours de traitement, vous pourrez bientôt télécharger votre fichier"
-    },
-    "features_panel": {
-        "chat": "Chat",
-        "gopher": "Gopher",
-        "media_proxy": "Proxy média",
-        "scope_options": "Options de visibilité",
-        "text_limit": "Limite de texte",
-        "title": "Caractéristiques",
-        "who_to_follow": "Personnes à suivre"
-    },
-    "finder": {
-        "error_fetching_user": "Erreur lors de la recherche de l'utilisateur·ice",
-        "find_user": "Chercher un-e utilisateur·ice"
-    },
-    "general": {
-        "apply": "Appliquer",
-        "submit": "Envoyer",
-        "more": "Plus",
-        "generic_error": "Une erreur s'est produite",
-        "optional": "optionnel",
-        "show_more": "Montrer plus",
-        "show_less": "Montrer moins",
-        "cancel": "Annuler",
-        "disable": "Désactiver",
-        "enable": "Activer",
-        "confirm": "Confirmer",
-        "verify": "Vérifier",
-        "dismiss": "Rejeter"
-    },
-    "image_cropper": {
-        "crop_picture": "Rogner l'image",
-        "save": "Sauvegarder",
-        "save_without_cropping": "Sauvegarder sans rogner",
-        "cancel": "Annuler"
-    },
-    "importer": {
-        "submit": "Soumettre",
-        "success": "Importé avec succès.",
-        "error": "Une erreur est survenue pendant l'import de ce fichier."
-    },
-    "login": {
-        "login": "Connexion",
-        "description": "Connexion avec OAuth",
-        "logout": "Déconnexion",
-        "password": "Mot de passe",
-        "placeholder": "p.e. lain",
-        "register": "S'inscrire",
-        "username": "Identifiant",
-        "hint": "Connectez-vous pour rejoindre la discussion",
-        "authentication_code": "Code d'authentification",
-        "enter_recovery_code": "Entrez un code de récupération",
-        "enter_two_factor_code": "Entrez un code à double authentification",
-        "recovery_code": "Code de récupération",
-        "heading": {
-            "totp": "Authentification à double authentification",
-            "recovery": "Récuperation de la double authentification"
-        }
-    },
-    "media_modal": {
-        "previous": "Précédent",
-        "next": "Suivant"
-    },
-    "nav": {
-        "about": "À propos",
-        "back": "Retour",
-        "chat": "Chat local",
-        "friend_requests": "Demandes de suivi",
-        "mentions": "Notifications",
-        "interactions": "Interactions",
-        "dms": "Messages directs",
-        "public_tl": "Fil d'actualité public",
-        "timeline": "Fil d'actualité",
-        "twkn": "Ensemble du réseau connu",
-        "user_search": "Recherche d'utilisateur·ice",
-        "who_to_follow": "Qui suivre",
-        "preferences": "Préférences",
-        "search": "Recherche",
-        "administration": "Administration"
-    },
-    "notifications": {
-        "broken_favorite": "Chargement d'un message inconnu…",
-        "favorited_you": "a aimé votre statut",
-        "followed_you": "a commencé à vous suivre",
-        "load_older": "Charger les notifications précédentes",
-        "notifications": "Notifications",
-        "read": "Lu !",
-        "repeated_you": "a partagé votre statut",
-        "no_more_notifications": "Aucune notification supplémentaire",
-        "migrated_to": "a migré à",
-        "reacted_with": "a réagi avec {0}",
-        "follow_request": "veut vous suivre"
-    },
-    "interactions": {
-        "favs_repeats": "Partages et favoris",
-        "follows": "Nouveaux suivis",
-        "load_older": "Chargez d'anciennes interactions",
-        "moves": "Migrations de comptes"
-    },
-    "post_status": {
-        "new_status": "Poster un nouveau statut",
-        "account_not_locked_warning": "Votre compte n'est pas {0}. N'importe qui peut vous suivre pour voir vos billets en Abonné·e·s uniquement.",
-        "account_not_locked_warning_link": "verrouillé",
-        "attachments_sensitive": "Marquer le média comme sensible",
-        "content_type": {
-            "text/plain": "Texte brut",
-            "text/html": "HTML",
-            "text/markdown": "Markdown",
-            "text/bbcode": "BBCode"
-        },
-        "content_warning": "Sujet (optionnel)",
-        "default": "Écrivez ici votre prochain statut.",
-        "direct_warning_to_all": "Ce message sera visible pour toutes les personnes mentionnées.",
-        "direct_warning_to_first_only": "Ce message sera visible uniquement pour personnes mentionnées au début du message.",
-        "posting": "Envoi en cours",
-        "scope_notice": {
-            "public": "Ce statut sera visible par tout le monde",
-            "private": "Ce statut sera visible par seulement vos abonné⋅e⋅s",
-            "unlisted": "Ce statut ne sera pas visible dans le Fil d'actualité public et l'Ensemble du réseau connu"
-        },
-        "scope": {
-            "direct": "Direct - N'envoyer qu'aux personnes mentionnées",
-            "private": "Abonné·e·s uniquement - Seul·e·s vos abonné·e·s verront vos billets",
-            "public": "Publique - Afficher dans les fils publics",
-            "unlisted": "Non-Listé - Ne pas afficher dans les fils publics"
-        }
-    },
-    "registration": {
-        "bio": "Biographie",
-        "email": "Adresse mail",
-        "fullname": "Pseudonyme",
-        "password_confirm": "Confirmation du mot de passe",
-        "registration": "Inscription",
-        "token": "Jeton d'invitation",
-        "captcha": "CAPTCHA",
-        "new_captcha": "Cliquez sur l'image pour avoir un nouveau captcha",
-        "username_placeholder": "p.e. lain",
-        "fullname_placeholder": "p.e. Lain Iwakura",
-        "bio_placeholder": "p.e.\nSalut, je suis Lain\nJe suis une héroïne d'animé qui vit dans une banlieue japonaise. Vous me connaissez peut-être du Wired.",
-        "validations": {
-            "username_required": "ne peut pas être laissé vide",
-            "fullname_required": "ne peut pas être laissé vide",
-            "email_required": "ne peut pas être laissé vide",
-            "password_required": "ne peut pas être laissé vide",
-            "password_confirmation_required": "ne peut pas être laissé vide",
-            "password_confirmation_match": "doit être identique au mot de passe"
-        }
-    },
-    "selectable_list": {
-        "select_all": "Tout selectionner"
-    },
-    "settings": {
-        "app_name": "Nom de l'application",
-        "security": "Sécurité",
-        "enter_current_password_to_confirm": "Entrez votre mot de passe actuel pour confirmer votre identité",
-        "mfa": {
-            "otp": "OTP",
-            "setup_otp": "Configurer OTP",
-            "wait_pre_setup_otp": "préconfiguration OTP",
-            "confirm_and_enable": "Confirmer & activer OTP",
-            "title": "Double authentification",
-            "generate_new_recovery_codes": "Générer de nouveaux codes de récupération",
-            "warning_of_generate_new_codes": "Quand vous générez de nouveauc codes de récupération, vos anciens codes ne fonctionnerons plus.",
-            "recovery_codes": "Codes de récupération.",
-            "waiting_a_recovery_codes": "Réception des codes de récupération…",
-            "recovery_codes_warning": "Écrivez les codes ou sauvez les quelquepart sécurisé - sinon vous ne les verrez plus jamais. Si vous perdez l'accès à votre application de double authentification et codes de récupération vous serez vérouillé en dehors de votre compte.",
-            "authentication_methods": "Methodes d'authentification",
-            "scan": {
-                "title": "Scanner",
-                "desc": "En utilisant votre application de double authentification, scannez ce QR code ou entrez la clé textuelle :",
-                "secret_code": "Clé"
-            },
-            "verify": {
-                "desc": "Pour activer la double authentification, entrez le code depuis votre application :"
-            }
-        },
-        "attachmentRadius": "Pièces jointes",
-        "attachments": "Pièces jointes",
-        "autoload": "Charger la suite automatiquement une fois le bas de la page atteint",
-        "avatar": "Avatar",
-        "avatarAltRadius": "Avatars (Notifications)",
-        "avatarRadius": "Avatars",
-        "background": "Arrière-plan",
-        "bio": "Biographie",
-        "block_export": "Export des comptes bloqués",
-        "block_export_button": "Export des comptes bloqués vers un fichier csv",
-        "block_import": "Import des comptes bloqués",
-        "block_import_error": "Erreur lors de l'import des comptes bloqués",
-        "blocks_imported": "Blocks importés ! Le traitement va prendre un moment.",
-        "blocks_tab": "Bloqué·e·s",
-        "btnRadius": "Boutons",
-        "cBlue": "Bleu (répondre, suivre)",
-        "cGreen": "Vert (partager)",
-        "cOrange": "Orange (aimer)",
-        "cRed": "Rouge (annuler)",
-        "change_password": "Changez votre mot de passe",
-        "change_password_error": "Il y a eu un problème pour changer votre mot de passe.",
-        "changed_password": "Mot de passe modifié avec succès !",
-        "collapse_subject": "Réduire les messages avec des sujets",
-        "composing": "Composition",
-        "confirm_new_password": "Confirmation du nouveau mot de passe",
-        "current_avatar": "Avatar actuel",
-        "current_password": "Mot de passe actuel",
-        "current_profile_banner": "Bannière de profil actuelle",
-        "data_import_export_tab": "Import / Export des Données",
-        "default_vis": "Visibilité par défaut",
-        "delete_account": "Supprimer le compte",
-        "delete_account_description": "Supprimer définitivement vos données et désactiver votre compte.",
-        "delete_account_error": "Il y a eu un problème lors de la tentative de suppression de votre compte. Si le problème persiste, contactez l'administrateur⋅ice de cette instance.",
-        "delete_account_instructions": "Indiquez votre mot de passe ci-dessous pour confirmer la suppression de votre compte.",
-        "avatar_size_instruction": "La taille minimale recommandée pour l'image de l'avatar est de 150x150 pixels.",
-        "export_theme": "Enregistrer le thème",
-        "filtering": "Filtre",
-        "filtering_explanation": "Tous les statuts contenant ces mots seront masqués. Un mot par ligne",
-        "follow_export": "Exporter les abonnements",
-        "follow_export_button": "Exporter les abonnements en csv",
-        "follow_import": "Importer des abonnements",
-        "follow_import_error": "Erreur lors de l'importation des abonnements",
-        "follows_imported": "Abonnements importés ! Le traitement peut prendre un moment.",
-        "foreground": "Premier plan",
-        "general": "Général",
-        "hide_attachments_in_convo": "Masquer les pièces jointes dans les conversations",
-        "hide_attachments_in_tl": "Masquer les pièces jointes dans le journal",
-        "hide_muted_posts": "Masquer les statuts des utilisateurs masqués",
-        "max_thumbnails": "Nombre maximum de miniatures par statuts",
-        "hide_isp": "Masquer le panneau spécifique a l'instance",
-        "preload_images": "Précharger les images",
-        "use_one_click_nsfw": "Ouvrir les pièces-jointes NSFW avec un seul clic",
-        "hide_post_stats": "Masquer les statistiques de publication (le nombre de favoris)",
-        "hide_user_stats": "Masquer les statistiques de profil (le nombre d'amis)",
-        "hide_filtered_statuses": "Masquer les statuts filtrés",
-        "import_blocks_from_a_csv_file": "Importer les blocages depuis un fichier csv",
-        "import_followers_from_a_csv_file": "Importer des abonnements depuis un fichier csv",
-        "import_theme": "Charger le thème",
-        "inputRadius": "Champs de texte",
-        "checkboxRadius": "Cases à cocher",
-        "instance_default": "(default : {value})",
-        "instance_default_simple": "(default)",
-        "interface": "Interface",
-        "interfaceLanguage": "Langue de l'interface",
-        "invalid_theme_imported": "Le fichier sélectionné n'est pas un thème Pleroma pris en charge. Aucun changement n'a été apporté à votre thème.",
-        "limited_availability": "Non disponible dans votre navigateur",
-        "links": "Liens",
-        "lock_account_description": "Limitez votre compte aux abonnés acceptés uniquement",
-        "loop_video": "Vidéos en boucle",
-        "loop_video_silent_only": "Boucle uniquement les vidéos sans le son (les « gifs » de Mastodon)",
-        "mutes_tab": "Comptes silenciés",
-        "play_videos_in_modal": "Jouer les vidéos directement dans le visionneur de médias",
-        "use_contain_fit": "Ne pas rogner les miniatures des pièces-jointes",
-        "name": "Nom",
-        "name_bio": "Nom & Bio",
-        "new_password": "Nouveau mot de passe",
-        "notification_visibility": "Types de notifications à afficher",
-        "notification_visibility_follows": "Abonnements",
-        "notification_visibility_likes": "J'aime",
-        "notification_visibility_mentions": "Mentionnés",
-        "notification_visibility_repeats": "Partages",
-        "no_rich_text_description": "Ne formatez pas le texte",
-        "no_blocks": "Aucun bloqués",
-        "no_mutes": "Aucun masqués",
-        "hide_follows_description": "Ne pas afficher à qui je suis abonné",
-        "hide_followers_description": "Ne pas afficher qui est abonné à moi",
-        "show_admin_badge": "Afficher le badge d'Administrateur⋅ice sur mon profil",
-        "show_moderator_badge": "Afficher le badge de Modérateur⋅ice sur mon profil",
-        "nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible",
-        "oauth_tokens": "Jetons OAuth",
-        "token": "Jeton",
-        "refresh_token": "Rafraichir le jeton",
-        "valid_until": "Valable jusque",
-        "revoke_token": "Révoquer",
-        "panelRadius": "Fenêtres",
-        "pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas actif",
-        "presets": "Thèmes prédéfinis",
-        "profile_background": "Image de fond",
-        "profile_banner": "Bannière de profil",
-        "profile_tab": "Profil",
-        "radii_help": "Vous pouvez ici choisir le niveau d'arrondi des angles de l'interface (en pixels)",
-        "replies_in_timeline": "Réponses au journal",
-        "reply_link_preview": "Afficher un aperçu lors du survol de liens vers une réponse",
-        "reply_visibility_all": "Montrer toutes les réponses",
-        "reply_visibility_following": "Afficher uniquement les réponses adressées à moi ou aux personnes que je suis",
-        "reply_visibility_self": "Afficher uniquement les réponses adressées à moi",
-        "autohide_floating_post_button": "Automatiquement cacher le bouton de Nouveau Statut (sur mobile)",
-        "saving_err": "Erreur lors de l'enregistrement des paramètres",
-        "saving_ok": "Paramètres enregistrés",
-        "search_user_to_block": "Rechercher qui vous voulez bloquer",
-        "search_user_to_mute": "Rechercher qui vous voulez masquer",
-        "security_tab": "Sécurité",
-        "scope_copy": "Garder la même visibilité en répondant (les DMs restent toujours des DMs)",
-        "minimal_scopes_mode": "Rétrécir les options de séléction de la portée",
-        "set_new_avatar": "Changer d'avatar",
-        "set_new_profile_background": "Changer d'image de fond",
-        "set_new_profile_banner": "Changer de bannière",
-        "settings": "Paramètres",
-        "subject_input_always_show": "Toujours copier le champ de sujet",
-        "subject_line_behavior": "Copier le sujet en répondant",
-        "subject_line_email": "Similaire au courriel : « re : sujet »",
-        "subject_line_mastodon": "Comme mastodon : copier tel quel",
-        "subject_line_noop": "Ne pas copier",
-        "post_status_content_type": "Type de contenu du statuts",
-        "stop_gifs": "N'animer les GIFS que lors du survol du curseur de la souris",
-        "streaming": "Charger automatiquement les nouveaux statuts lorsque vous êtes au haut de la page",
-        "text": "Texte",
-        "theme": "Thème",
-        "theme_help": "Spécifiez des codes couleur hexadécimaux (#rrvvbb) pour personnaliser les couleurs du thème.",
-        "theme_help_v2_1": "Vous pouvez aussi surcharger certaines couleurs de composants et transparence via la case à cocher, utilisez le bouton « Vider tout » pour effacer toutes les surcharges.",
-        "theme_help_v2_2": "Les icônes sous certaines des entrées ont un indicateur de contraste du fond/texte, survolez les pour plus d'informations détailles. Veuillez garder a l'esprit que lors de l'utilisation de transparence l'indicateur de contraste indique le pire des cas.",
-        "tooltipRadius": "Info-bulles/alertes",
-        "upload_a_photo": "Envoyer une photo",
-        "user_settings": "Paramètres utilisateur",
-        "values": {
-            "false": "non",
-            "true": "oui"
-        },
-        "notifications": "Notifications",
-        "notification_setting": "Reçevoir les notifications de :",
-        "notification_setting_follows": "Utilisateurs que vous suivez",
-        "notification_setting_non_follows": "Utilisateurs que vous ne suivez pas",
-        "notification_setting_followers": "Utilisateurs qui vous suivent",
-        "notification_setting_non_followers": "Utilisateurs qui ne vous suivent pas",
-        "notification_mutes": "Pour stopper la récéption de notifications d'un utilisateur particulier, utilisez un masquage.",
-        "notification_blocks": "Bloquer un utilisateur stoppe toute notification et se désabonne de lui.",
-        "enable_web_push_notifications": "Activer les notifications de push web",
-        "style": {
-            "switcher": {
-                "keep_color": "Garder les couleurs",
-                "keep_shadows": "Garder les ombres",
-                "keep_opacity": "Garder la transparence",
-                "keep_roundness": "Garder la rondeur",
-                "keep_fonts": "Garder les polices",
-                "save_load_hint": "L'option « Garder » préserve les options activés en cours lors de la séléction ou chargement des thèmes, il sauve aussi les dites options lors de l'export d'un thème. Quand toutes les cases sont décochés, exporter un thème sauvera tout.",
-                "reset": "Remise à zéro",
-                "clear_all": "Tout vider",
-                "clear_opacity": "Vider la transparence",
-                "load_theme": "Charger le thème",
-                "use_snapshot": "Ancienne version",
-                "help": {
-                    "upgraded_from_v2": "PleromaFE à été mis à jour, le thème peut être un peu différent que dans vos souvenirs.",
-                    "v2_imported": "Le fichier que vous avez importé vient d'un version antérieure. Nous essayons de maximizer la compatibilité mais il peu y avoir quelques incohérences.",
-                    "future_version_imported": "Le fichier importé viens d'une version postérieure de PleromaFE.",
-                    "older_version_imported": "Le fichier importé viens d'une version antérieure de PleromaFE.",
-                    "snapshot_source_mismatch": "Conflict de version : Probablement due à un retour arrière puis remise à jour de la version de PleromaFE, si vous avez charger le thème en utilisant une version antérieure vous voulez probablement utiliser la version antérieure, autrement utiliser la version postérieure.",
-                    "migration_napshot_gone": "Pour une raison inconnue l'instantané est manquant, des parties peuvent rendre différentes que dans vos souvenirs.",
-                    "migration_snapshot_ok": "Pour être sûr un instantanée du thème à été chargé. Vos pouvez essayer de charger ses données.",
-                    "fe_downgraded": "Retour en arrière de la version de PleromaFE.",
-                    "fe_upgraded": "Le moteur de thème PleromaFE à été mis à jour après un changement de version.",
-                    "snapshot_missing": "Aucun instantané du thème à été trouvé dans le fichier, il peut y avoir un rendu différent à la vision originelle."
-                },
-                "keep_as_is": "Garder tel-quel",
-                "use_source": "Nouvelle version"
-            },
-            "common": {
-                "color": "Couleur",
-                "opacity": "Transparence",
-                "contrast": {
-                    "hint": "Le ratio de contraste est {ratio}, il {level} {context}",
-                    "level": {
-                        "aa": "répond aux directives de niveau AA (minimum)",
-                        "aaa": "répond aux directives de niveau AAA (recommandé)",
-                        "bad": "ne réponds à aucune directive d'accessibilité"
-                    },
-                    "context": {
-                        "18pt": "pour texte large (19pt+)",
-                        "text": "pour texte"
-                    }
-                }
-            },
-            "common_colors": {
-                "_tab_label": "Commun",
-                "main": "Couleurs communes",
-                "foreground_hint": "Voir l'onglet « Avancé » pour plus de contrôle détaillé",
-                "rgbo": "Icônes, accents, badges"
-            },
-            "advanced_colors": {
-                "_tab_label": "Avancé",
-                "alert": "Fond d'alerte",
-                "alert_error": "Erreur",
-                "badge": "Fond de badge",
-                "badge_notification": "Notification",
-                "panel_header": "Entête de panneau",
-                "top_bar": "Barre du haut",
-                "borders": "Bordures",
-                "buttons": "Boutons",
-                "inputs": "Champs de saisie",
-                "faint_text": "Texte en fondu",
-                "underlay": "sous-calque",
-                "pressed": "Appuyé",
-                "alert_warning": "Avertissement",
-                "alert_neutral": "Neutre",
-                "post": "Messages/Bios des comptes",
-                "poll": "Graphique de Sondage",
-                "icons": "Icônes",
-                "selectedPost": "Message sélectionné",
-                "selectedMenu": "Objet sélectionné du menu",
-                "disabled": "Désactivé",
-                "tabs": "Onglets",
-                "toggled": "(Dés)activé",
-                "highlight": "Éléments mis en valeur",
-                "popover": "Infobulles, menus"
-            },
-            "radii": {
-                "_tab_label": "Rondeur"
-            },
-            "shadows": {
-                "_tab_label": "Ombres et éclairage",
-                "component": "Composant",
-                "override": "Surcharger",
-                "shadow_id": "Ombre #{value}",
-                "blur": "Flou",
-                "spread": "Dispersion",
-                "inset": "Interne",
-                "hint": "Pour les ombres, vous pouvez aussi utiliser --variable comme valeur de couleur en CSS3. Veuillez noter que spécifier la transparence ne fonctionnera pas dans ce cas.",
-                "filter_hint": {
-                    "always_drop_shadow": "Attention, cette ombre utilise toujours {0} quand le navigateur le supporte.",
-                    "drop_shadow_syntax": "{0} ne supporte pas le paramètre {1} et mot-clé {2}.",
-                    "avatar_inset": "Veuillez noter que combiner a la fois les ombres internes et non-internes sur les avatars peut fournir des résultats innatendus avec la transparence des avatars.",
-                    "spread_zero": "Les ombres avec une dispersion > 0 apparaitrons comme si ils étaient à zéro",
-                    "inset_classic": "L'ombre interne utilisera toujours {0}"
-                },
-                "components": {
-                    "panel": "Panneau",
-                    "panelHeader": "En-tête de panneau",
-                    "topBar": "Barre du haut",
-                    "avatar": "Avatar utilisateur⋅ice (dans la vue de profil)",
-                    "avatarStatus": "Avatar utilisateur⋅ice (dans la vue de statuts)",
-                    "popup": "Popups et infobulles",
-                    "button": "Bouton",
-                    "buttonHover": "Bouton (survol)",
-                    "buttonPressed": "Bouton (cliqué)",
-                    "buttonPressedHover": "Bouton (cliqué+survol)",
-                    "input": "Champ de saisie"
-                },
-                "hintV3": "Pour les ombres vous pouvez aussi utiliser la notation {0} pour utiliser un autre emplacement de couleur."
-            },
-            "fonts": {
-                "_tab_label": "Polices",
-                "help": "Sélectionnez la police à utiliser pour les éléments de l'UI. Pour « personnalisé » vous avez à entrer le nom exact de la police comme il apparaît dans le système.",
-                "components": {
-                    "interface": "Interface",
-                    "input": "Champs de saisie",
-                    "post": "Post text",
-                    "postCode": "Texte à taille fixe dans un article (texte enrichi)"
-                },
-                "family": "Nom de la police",
-                "size": "Taille (en px)",
-                "weight": "Poid (gras)",
-                "custom": "Personnalisé"
-            },
-            "preview": {
-                "header": "Prévisualisation",
-                "content": "Contenu",
-                "error": "Exemple d'erreur",
-                "button": "Bouton",
-                "text": "Un certain nombre de {0} et {1}",
-                "mono": "contenu",
-                "input": "Je viens juste d’atterrir à L.A.",
-                "faint_link": "manuel utile",
-                "fine_print": "Lisez notre {0} pour n'apprendre rien d'utile !",
-                "header_faint": "Tout va bien",
-                "checkbox": "J'ai survolé les conditions d'utilisation",
-                "link": "un petit lien sympa"
-            }
-        },
-        "version": {
-            "title": "Version",
-            "backend_version": "Version du Backend",
-            "frontend_version": "Version du Frontend"
-        },
-        "change_email": "Changer de courriel",
-        "domain_mutes": "Domaines",
-        "pad_emoji": "Rajouter un espace autour de l'émoji après l’avoir choisit",
-        "notification_visibility_emoji_reactions": "Réactions",
-        "hide_follows_count_description": "Masquer le nombre de suivis",
-        "useStreamingApiWarning": "(Non recommandé, expérimental, connu pour rater des messages)",
-        "type_domains_to_mute": "Écrire les domaines à masquer",
-        "fun": "Rigolo",
-        "greentext": "greentexting",
-        "allow_following_move": "Suivre automatiquement quand ce compte migre",
-        "change_email_error": "Il y a eu un problème pour charger votre courriel.",
-        "changed_email": "Courriel changé avec succès !",
-        "discoverable": "Permettre de découvrir ce compte dans les résultats de recherche web et autres services",
-        "emoji_reactions_on_timeline": "Montrer les émojis-réactions dans le flux",
-        "new_email": "Nouveau courriel",
-        "notification_visibility_moves": "Migrations de compte",
-        "user_mutes": "Comptes",
-        "useStreamingApi": "Recevoir les messages et notifications en temps réel",
-        "notification_setting_filters": "Filtres",
-        "notification_setting_privacy_option": "Masquer l'expéditeur et le contenu des notifications push",
-        "notification_setting_privacy": "Intimité",
-        "hide_followers_count_description": "Masquer le nombre d'abonnés",
-        "accent": "Accent"
-    },
-    "timeline": {
-        "collapse": "Fermer",
-        "conversation": "Conversation",
-        "error_fetching": "Erreur en cherchant les mises à jour",
-        "load_older": "Afficher plus",
-        "no_retweet_hint": "Le message est marqué en abonnés-seulement ou direct et ne peut pas être partagé",
-        "repeated": "a partagé",
-        "show_new": "Afficher plus",
-        "up_to_date": "À jour",
-        "no_more_statuses": "Pas plus de statuts",
-        "no_statuses": "Aucun statuts"
-    },
-    "status": {
-        "favorites": "Favoris",
-        "repeats": "Partages",
-        "delete": "Supprimer statuts",
-        "pin": "Agraffer sur le profil",
-        "unpin": "Dégraffer du profil",
-        "pinned": "Agraffé",
-        "delete_confirm": "Voulez-vous vraiment supprimer ce statuts ?",
-        "reply_to": "Réponse à",
-        "replies_list": "Réponses :",
-        "mute_conversation": "Masquer la conversation",
-        "unmute_conversation": "Démasquer la conversation",
-        "status_unavailable": "Status indisponible",
-        "copy_link": "Copier le lien au status"
-    },
-    "user_card": {
-        "approve": "Accepter",
-        "block": "Bloquer",
-        "blocked": "Bloqué !",
-        "deny": "Rejeter",
-        "favorites": "Favoris",
-        "follow": "Suivre",
-        "follow_sent": "Demande envoyée !",
-        "follow_progress": "Demande en cours…",
-        "follow_again": "Renvoyer la demande ?",
-        "follow_unfollow": "Désabonner",
-        "followees": "Suivis",
-        "followers": "Vous suivent",
-        "following": "Suivi !",
-        "follows_you": "Vous suit !",
-        "its_you": "C'est vous !",
-        "media": "Media",
-        "mute": "Masquer",
-        "muted": "Masqué",
-        "per_day": "par jour",
-        "remote_follow": "Suivre d'une autre instance",
-        "report": "Signalement",
-        "statuses": "Statuts",
-        "unblock": "Débloquer",
-        "unblock_progress": "Déblocage…",
-        "block_progress": "Blocage…",
-        "unmute": "Démasquer",
-        "unmute_progress": "Démasquage…",
-        "mute_progress": "Masquage…",
-        "admin_menu": {
-            "moderation": "Moderation",
-            "grant_admin": "Promouvoir Administrateur⋅ice",
-            "revoke_admin": "Dégrader Administrateur⋅ice",
-            "grant_moderator": "Promouvoir Modérateur⋅ice",
-            "revoke_moderator": "Dégrader Modérateur⋅ice",
-            "activate_account": "Activer le compte",
-            "deactivate_account": "Désactiver le compte",
-            "delete_account": "Supprimer le compte",
-            "force_nsfw": "Marquer tous les statuts comme NSFW",
-            "strip_media": "Supprimer les medias des statuts",
-            "force_unlisted": "Forcer les statuts à être délistés",
-            "sandbox": "Forcer les statuts à être visibles seuleument pour les abonné⋅e⋅s",
-            "disable_remote_subscription": "Interdir de s'abonner a l'utilisateur depuis l'instance distante",
-            "disable_any_subscription": "Interdir de s'abonner à l'utilisateur tout court",
-            "quarantine": "Interdir les statuts de l'utilisateur à fédérer",
-            "delete_user": "Supprimer l'utilisateur",
-            "delete_user_confirmation": "Êtes-vous absolument-sûr⋅e ? Cette action ne peut être annulée."
-        },
-        "mention": "Mention",
-        "hidden": "Caché",
-        "subscribe": "Abonner",
-        "unsubscribe": "Désabonner",
-        "hide_repeats": "Cacher les partages",
-        "show_repeats": "Montrer les partages"
-    },
-    "user_profile": {
-        "timeline_title": "Journal de l'utilisateur⋅ice",
-        "profile_does_not_exist": "Désolé, ce profil n'existe pas.",
-        "profile_loading_error": "Désolé, il y a eu une erreur au chargement du profil."
-    },
-    "user_reporting": {
-        "title": "Signaler {0}",
-        "add_comment_description": "Ce signalement sera envoyé aux modérateur⋅ice⋅s de votre instance. Vous pouvez fournir une explication de pourquoi vous signalez ce compte ci-dessous :",
-        "additional_comments": "Commentaires additionnels",
-        "forward_description": "Le compte vient d'un autre serveur. Envoyer une copie du signalement à celui-ci aussi ?",
-        "forward_to": "Transmettre à {0}",
-        "submit": "Envoyer",
-        "generic_error": "Une erreur est survenue lors du traitement de votre requête."
-    },
-    "who_to_follow": {
-        "more": "Plus",
-        "who_to_follow": "À qui s'abonner"
-    },
-    "tool_tip": {
-        "media_upload": "Envoyer un media",
-        "repeat": "Répéter",
-        "reply": "Répondre",
-        "favorite": "Favoriser",
-        "user_settings": "Paramètres utilisateur",
-        "add_reaction": "Ajouter une réaction",
-        "accept_follow_request": "Accepter la demande de suivit",
-        "reject_follow_request": "Rejeter la demande de suivit"
-    },
-    "upload": {
-        "error": {
-            "base": "L'envoi a échoué.",
-            "file_too_big": "Fichier trop gros [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-            "default": "Réessayez plus tard"
-        },
-        "file_size_units": {
-            "B": "O",
-            "KiB": "KiO",
-            "MiB": "MiO",
-            "GiB": "GiO",
-            "TiB": "TiO"
-        }
-    },
-    "about": {
-        "mrf": {
-            "keyword": {
-                "reject": "Rejeté",
-                "replace": "Remplacer",
-                "keyword_policies": "Politiques par mot-clés",
-                "ftl_removal": "Suppression du flux \"Ensemble du réseau connu\"",
-                "is_replaced_by": "→"
-            },
-            "simple": {
-                "simple_policies": "Politiques par instances",
-                "accept": "Accepter",
-                "accept_desc": "Cette instance accepte des messages seulement depuis ces instances :",
-                "reject": "Rejeter",
-                "reject_desc": "Cette instance n'acceptera pas de message de ces instances :",
-                "quarantine": "Quarantaine",
-                "quarantine_desc": "Cette instance enverras seulement des messages publics à ces instances :",
-                "ftl_removal_desc": "Cette instance supprime ces instance du flux fédéré :",
-                "media_removal": "Suppression multimédia",
-                "media_removal_desc": "Cette instance supprime le contenu multimédia des instances suivantes :",
-                "media_nsfw": "Force le contenu multimédia comme sensible",
-                "ftl_removal": "Suppression du flux fédéré",
-                "media_nsfw_desc": "Cette instance force le contenu multimédia comme sensible pour les messages des instances suivantes :"
-            },
-            "federation": "Fédération",
-            "mrf_policies": "Politiques MRF activées",
-            "mrf_policies_desc": "Les politiques MRF modifient la fédération entre les instances. Les politiques suivantes sont activées :"
-        },
-        "staff": "Staff"
-    },
-    "domain_mute_card": {
-        "mute": "Muet",
-        "mute_progress": "Masquage…",
-        "unmute": "Démasquer",
-        "unmute_progress": "Démasquage…"
-    },
-    "polls": {
-        "add_poll": "Ajouter un Sondage",
-        "add_option": "Ajouter une option",
-        "option": "Option",
-        "votes": "votes",
-        "type": "Type de Sondage",
-        "single_choice": "Choix unique",
-        "multiple_choices": "Choix multiples",
-        "expiry": "Age du sondage",
-        "expires_in": "Fin du sondage dans {0}",
-        "not_enough_options": "Trop peu d'options unique au sondage",
-        "vote": "Voter",
-        "expired": "Sondage terminé il y a {0}"
-    },
-    "emoji": {
-        "emoji": "Émoji",
-        "search_emoji": "Rechercher un émoji",
-        "add_emoji": "Insérer un émoji",
-        "custom": "émoji personnalisé",
-        "unicode": "émoji unicode",
-        "load_all": "Charger tout les {emojiAmount} émojis",
-        "load_all_hint": "{saneAmount} émojis chargé, charger tout les émojis peuvent causer des problèmes de performances.",
-        "stickers": "Stickers",
-        "keep_open": "Garder le sélecteur ouvert"
-    },
-    "remote_user_resolver": {
-        "error": "Non trouvé.",
-        "searching_for": "Rechercher",
-        "remote_user_resolver": "Résolution de compte distant"
-    },
-    "time": {
-        "minutes_short": "{0}min",
-        "second_short": "{0}s",
-        "day": "{0} jour",
-        "days": "{0} jours",
-        "months": "{0} mois",
-        "month_short": "{0}m",
-        "months_short": "{0}m",
-        "now": "tout de suite",
-        "now_short": "maintenant",
-        "second": "{0} seconde",
-        "seconds": "{0} secondes",
-        "seconds_short": "{0}s",
-        "day_short": "{0}j",
-        "days_short": "{0}j",
-        "hour": "{0} heure",
-        "hours": "{0} heures",
-        "hour_short": "{0}h",
-        "hours_short": "{0}h",
-        "in_future": "dans {0}",
-        "in_past": "il y a {0}",
-        "minute": "{0} minute",
-        "minutes": "{0} minutes",
-        "minute_short": "{0}min",
-        "month": "{0} mois",
-        "week": "{0} semaine",
-        "weeks": "{0} semaines",
-        "week_short": "{0}s",
-        "weeks_short": "{0}s",
-        "year": "{0} année",
-        "years": "{0} années",
-        "year_short": "{0}a",
-        "years_short": "{0}a"
-    },
-    "search": {
-        "people": "Comptes",
-        "person_talking": "{count} personnes discutant",
-        "hashtags": "Mot-dièses",
-        "people_talking": "{count} personnes discutant",
-        "no_results": "Aucun résultats"
-    },
-    "password_reset": {
-        "forgot_password": "Mot de passe oublié ?",
-        "check_email": "Vérifiez vos courriels pour le lien permettant de changer votre mot de passe.",
-        "password_reset_disabled": "Le changement de mot de passe est désactivé. Veuillez contacter l'administration de votre instance.",
-        "password_reset_required_but_mailer_is_disabled": "Vous devez changer votre mot de passe mais sont changement est désactivé. Veuillez contacter l’administration de votre instance.",
-        "password_reset": "Nouveau mot de passe",
-        "instruction": "Entrer votre address de courriel ou votre nom utilisateur. Nous enverrons un lien pour changer votre mot de passe.",
-        "placeholder": "Votre email ou nom d'utilisateur",
-        "return_home": "Retourner à la page d'accueil",
-        "not_found": "Email ou nom d'utilisateur inconnu.",
-        "too_many_requests": "Vos avez atteint la limite d'essais, essayez plus tard.",
-        "password_reset_required": "Vous devez changer votre mot de passe pour vous authentifier."
+  "chat": {
+    "title": "Chat"
+  },
+  "exporter": {
+    "export": "Exporter",
+    "processing": "En cours de traitement, vous pourrez bientôt télécharger votre fichier"
+  },
+  "features_panel": {
+    "chat": "Chat",
+    "gopher": "Gopher",
+    "media_proxy": "Proxy média",
+    "scope_options": "Options de visibilité",
+    "text_limit": "Limite de texte",
+    "title": "Caractéristiques",
+    "who_to_follow": "Personnes à suivre"
+  },
+  "finder": {
+    "error_fetching_user": "Erreur lors de la recherche de l'utilisateur·ice",
+    "find_user": "Chercher un-e utilisateur·ice"
+  },
+  "general": {
+    "apply": "Appliquer",
+    "submit": "Envoyer",
+    "more": "Plus",
+    "generic_error": "Une erreur s'est produite",
+    "optional": "optionnel",
+    "show_more": "Montrer plus",
+    "show_less": "Montrer moins",
+    "cancel": "Annuler",
+    "disable": "Désactiver",
+    "enable": "Activer",
+    "confirm": "Confirmer",
+    "verify": "Vérifier",
+    "dismiss": "Rejeter"
+  },
+  "image_cropper": {
+    "crop_picture": "Rogner l'image",
+    "save": "Sauvegarder",
+    "save_without_cropping": "Sauvegarder sans rogner",
+    "cancel": "Annuler"
+  },
+  "importer": {
+    "submit": "Soumettre",
+    "success": "Importé avec succès.",
+    "error": "Une erreur est survenue pendant l'import de ce fichier."
+  },
+  "login": {
+    "login": "Connexion",
+    "description": "Connexion avec OAuth",
+    "logout": "Déconnexion",
+    "password": "Mot de passe",
+    "placeholder": "p.e. lain",
+    "register": "S'inscrire",
+    "username": "Identifiant",
+    "hint": "Connectez-vous pour rejoindre la discussion",
+    "authentication_code": "Code d'authentification",
+    "enter_recovery_code": "Entrez un code de récupération",
+    "enter_two_factor_code": "Entrez un code à double authentification",
+    "recovery_code": "Code de récupération",
+    "heading": {
+      "totp": "Authentification à double authentification",
+      "recovery": "Récuperation de la double authentification"
     }
+  },
+  "media_modal": {
+    "previous": "Précédent",
+    "next": "Suivant"
+  },
+  "nav": {
+    "about": "À propos",
+    "back": "Retour",
+    "chat": "Chat local",
+    "friend_requests": "Demandes de suivi",
+    "mentions": "Notifications",
+    "interactions": "Interactions",
+    "dms": "Messages directs",
+    "public_tl": "Fil d'actualité public",
+    "timeline": "Fil d'actualité",
+    "twkn": "Ensemble du réseau connu",
+    "user_search": "Recherche d'utilisateur·ice",
+    "who_to_follow": "Qui suivre",
+    "preferences": "Préférences",
+    "search": "Recherche",
+    "administration": "Administration"
+  },
+  "notifications": {
+    "broken_favorite": "Chargement d'un message inconnu…",
+    "favorited_you": "a aimé votre statut",
+    "followed_you": "a commencé à vous suivre",
+    "load_older": "Charger les notifications précédentes",
+    "notifications": "Notifications",
+    "read": "Lu !",
+    "repeated_you": "a partagé votre statut",
+    "no_more_notifications": "Aucune notification supplémentaire",
+    "migrated_to": "a migré à",
+    "reacted_with": "a réagi avec {0}",
+    "follow_request": "veut vous suivre"
+  },
+  "interactions": {
+    "favs_repeats": "Partages et favoris",
+    "follows": "Nouveaux suivis",
+    "load_older": "Chargez d'anciennes interactions",
+    "moves": "Migrations de comptes"
+  },
+  "post_status": {
+    "new_status": "Poster un nouveau statut",
+    "account_not_locked_warning": "Votre compte n'est pas {0}. N'importe qui peut vous suivre pour voir vos billets en Abonné·e·s uniquement.",
+    "account_not_locked_warning_link": "verrouillé",
+    "attachments_sensitive": "Marquer le média comme sensible",
+    "content_type": {
+      "text/plain": "Texte brut",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
+    },
+    "content_warning": "Sujet (optionnel)",
+    "default": "Écrivez ici votre prochain statut.",
+    "direct_warning_to_all": "Ce message sera visible pour toutes les personnes mentionnées.",
+    "direct_warning_to_first_only": "Ce message sera visible uniquement pour personnes mentionnées au début du message.",
+    "posting": "Envoi en cours",
+    "scope_notice": {
+      "public": "Ce statut sera visible par tout le monde",
+      "private": "Ce statut sera visible par seulement vos abonné⋅e⋅s",
+      "unlisted": "Ce statut ne sera pas visible dans le Fil d'actualité public et l'Ensemble du réseau connu"
+    },
+    "scope": {
+      "direct": "Direct - N'envoyer qu'aux personnes mentionnées",
+      "private": "Abonné·e·s uniquement - Seul·e·s vos abonné·e·s verront vos billets",
+      "public": "Publique - Afficher dans les fils publics",
+      "unlisted": "Non-Listé - Ne pas afficher dans les fils publics"
+    }
+  },
+  "registration": {
+    "bio": "Biographie",
+    "email": "Adresse mail",
+    "fullname": "Pseudonyme",
+    "password_confirm": "Confirmation du mot de passe",
+    "registration": "Inscription",
+    "token": "Jeton d'invitation",
+    "captcha": "CAPTCHA",
+    "new_captcha": "Cliquez sur l'image pour avoir un nouveau captcha",
+    "username_placeholder": "p.e. lain",
+    "fullname_placeholder": "p.e. Lain Iwakura",
+    "bio_placeholder": "p.e.\nSalut, je suis Lain\nJe suis une héroïne d'animé qui vit dans une banlieue japonaise. Vous me connaissez peut-être du Wired.",
+    "validations": {
+      "username_required": "ne peut pas être laissé vide",
+      "fullname_required": "ne peut pas être laissé vide",
+      "email_required": "ne peut pas être laissé vide",
+      "password_required": "ne peut pas être laissé vide",
+      "password_confirmation_required": "ne peut pas être laissé vide",
+      "password_confirmation_match": "doit être identique au mot de passe"
+    }
+  },
+  "selectable_list": {
+    "select_all": "Tout selectionner"
+  },
+  "settings": {
+    "app_name": "Nom de l'application",
+    "security": "Sécurité",
+    "enter_current_password_to_confirm": "Entrez votre mot de passe actuel pour confirmer votre identité",
+    "mfa": {
+      "otp": "OTP",
+      "setup_otp": "Configurer OTP",
+      "wait_pre_setup_otp": "préconfiguration OTP",
+      "confirm_and_enable": "Confirmer & activer OTP",
+      "title": "Double authentification",
+      "generate_new_recovery_codes": "Générer de nouveaux codes de récupération",
+      "warning_of_generate_new_codes": "Quand vous générez de nouveauc codes de récupération, vos anciens codes ne fonctionnerons plus.",
+      "recovery_codes": "Codes de récupération.",
+      "waiting_a_recovery_codes": "Réception des codes de récupération…",
+      "recovery_codes_warning": "Écrivez les codes ou sauvez les quelquepart sécurisé - sinon vous ne les verrez plus jamais. Si vous perdez l'accès à votre application de double authentification et codes de récupération vous serez vérouillé en dehors de votre compte.",
+      "authentication_methods": "Methodes d'authentification",
+      "scan": {
+        "title": "Scanner",
+        "desc": "En utilisant votre application de double authentification, scannez ce QR code ou entrez la clé textuelle :",
+        "secret_code": "Clé"
+      },
+      "verify": {
+        "desc": "Pour activer la double authentification, entrez le code depuis votre application :"
+      }
+    },
+    "attachmentRadius": "Pièces jointes",
+    "attachments": "Pièces jointes",
+    "autoload": "Charger la suite automatiquement une fois le bas de la page atteint",
+    "avatar": "Avatar",
+    "avatarAltRadius": "Avatars (Notifications)",
+    "avatarRadius": "Avatars",
+    "background": "Arrière-plan",
+    "bio": "Biographie",
+    "block_export": "Export des comptes bloqués",
+    "block_export_button": "Export des comptes bloqués vers un fichier csv",
+    "block_import": "Import des comptes bloqués",
+    "block_import_error": "Erreur lors de l'import des comptes bloqués",
+    "blocks_imported": "Blocks importés ! Le traitement va prendre un moment.",
+    "blocks_tab": "Bloqué·e·s",
+    "btnRadius": "Boutons",
+    "cBlue": "Bleu (répondre, suivre)",
+    "cGreen": "Vert (partager)",
+    "cOrange": "Orange (aimer)",
+    "cRed": "Rouge (annuler)",
+    "change_password": "Changez votre mot de passe",
+    "change_password_error": "Il y a eu un problème pour changer votre mot de passe.",
+    "changed_password": "Mot de passe modifié avec succès !",
+    "collapse_subject": "Réduire les messages avec des sujets",
+    "composing": "Composition",
+    "confirm_new_password": "Confirmation du nouveau mot de passe",
+    "current_avatar": "Avatar actuel",
+    "current_password": "Mot de passe actuel",
+    "current_profile_banner": "Bannière de profil actuelle",
+    "data_import_export_tab": "Import / Export des Données",
+    "default_vis": "Visibilité par défaut",
+    "delete_account": "Supprimer le compte",
+    "delete_account_description": "Supprimer définitivement vos données et désactiver votre compte.",
+    "delete_account_error": "Il y a eu un problème lors de la tentative de suppression de votre compte. Si le problème persiste, contactez l'administrateur⋅ice de cette instance.",
+    "delete_account_instructions": "Indiquez votre mot de passe ci-dessous pour confirmer la suppression de votre compte.",
+    "avatar_size_instruction": "La taille minimale recommandée pour l'image de l'avatar est de 150x150 pixels.",
+    "export_theme": "Enregistrer le thème",
+    "filtering": "Filtre",
+    "filtering_explanation": "Tous les statuts contenant ces mots seront masqués. Un mot par ligne",
+    "follow_export": "Exporter les abonnements",
+    "follow_export_button": "Exporter les abonnements en csv",
+    "follow_import": "Importer des abonnements",
+    "follow_import_error": "Erreur lors de l'importation des abonnements",
+    "follows_imported": "Abonnements importés ! Le traitement peut prendre un moment.",
+    "foreground": "Premier plan",
+    "general": "Général",
+    "hide_attachments_in_convo": "Masquer les pièces jointes dans les conversations",
+    "hide_attachments_in_tl": "Masquer les pièces jointes dans le journal",
+    "hide_muted_posts": "Masquer les statuts des utilisateurs masqués",
+    "max_thumbnails": "Nombre maximum de miniatures par statuts",
+    "hide_isp": "Masquer le panneau spécifique a l'instance",
+    "preload_images": "Précharger les images",
+    "use_one_click_nsfw": "Ouvrir les pièces-jointes NSFW avec un seul clic",
+    "hide_post_stats": "Masquer les statistiques de publication (le nombre de favoris)",
+    "hide_user_stats": "Masquer les statistiques de profil (le nombre d'amis)",
+    "hide_filtered_statuses": "Masquer les statuts filtrés",
+    "import_blocks_from_a_csv_file": "Importer les blocages depuis un fichier csv",
+    "import_followers_from_a_csv_file": "Importer des abonnements depuis un fichier csv",
+    "import_theme": "Charger le thème",
+    "inputRadius": "Champs de texte",
+    "checkboxRadius": "Cases à cocher",
+    "instance_default": "(default : {value})",
+    "instance_default_simple": "(default)",
+    "interface": "Interface",
+    "interfaceLanguage": "Langue de l'interface",
+    "invalid_theme_imported": "Le fichier sélectionné n'est pas un thème Pleroma pris en charge. Aucun changement n'a été apporté à votre thème.",
+    "limited_availability": "Non disponible dans votre navigateur",
+    "links": "Liens",
+    "lock_account_description": "Limitez votre compte aux abonnés acceptés uniquement",
+    "loop_video": "Vidéos en boucle",
+    "loop_video_silent_only": "Boucle uniquement les vidéos sans le son (les « gifs » de Mastodon)",
+    "mutes_tab": "Comptes silenciés",
+    "play_videos_in_modal": "Jouer les vidéos directement dans le visionneur de médias",
+    "use_contain_fit": "Ne pas rogner les miniatures des pièces-jointes",
+    "name": "Nom",
+    "name_bio": "Nom & Bio",
+    "new_password": "Nouveau mot de passe",
+    "notification_visibility": "Types de notifications à afficher",
+    "notification_visibility_follows": "Abonnements",
+    "notification_visibility_likes": "J'aime",
+    "notification_visibility_mentions": "Mentionnés",
+    "notification_visibility_repeats": "Partages",
+    "no_rich_text_description": "Ne formatez pas le texte",
+    "no_blocks": "Aucun bloqués",
+    "no_mutes": "Aucun masqués",
+    "hide_follows_description": "Ne pas afficher à qui je suis abonné",
+    "hide_followers_description": "Ne pas afficher qui est abonné à moi",
+    "show_admin_badge": "Afficher le badge d'Administrateur⋅ice sur mon profil",
+    "show_moderator_badge": "Afficher le badge de Modérateur⋅ice sur mon profil",
+    "nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible",
+    "oauth_tokens": "Jetons OAuth",
+    "token": "Jeton",
+    "refresh_token": "Rafraichir le jeton",
+    "valid_until": "Valable jusque",
+    "revoke_token": "Révoquer",
+    "panelRadius": "Fenêtres",
+    "pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas actif",
+    "presets": "Thèmes prédéfinis",
+    "profile_background": "Image de fond",
+    "profile_banner": "Bannière de profil",
+    "profile_tab": "Profil",
+    "radii_help": "Vous pouvez ici choisir le niveau d'arrondi des angles de l'interface (en pixels)",
+    "replies_in_timeline": "Réponses au journal",
+    "reply_link_preview": "Afficher un aperçu lors du survol de liens vers une réponse",
+    "reply_visibility_all": "Montrer toutes les réponses",
+    "reply_visibility_following": "Afficher uniquement les réponses adressées à moi ou aux personnes que je suis",
+    "reply_visibility_self": "Afficher uniquement les réponses adressées à moi",
+    "autohide_floating_post_button": "Automatiquement cacher le bouton de Nouveau Statut (sur mobile)",
+    "saving_err": "Erreur lors de l'enregistrement des paramètres",
+    "saving_ok": "Paramètres enregistrés",
+    "search_user_to_block": "Rechercher qui vous voulez bloquer",
+    "search_user_to_mute": "Rechercher qui vous voulez masquer",
+    "security_tab": "Sécurité",
+    "scope_copy": "Garder la même visibilité en répondant (les DMs restent toujours des DMs)",
+    "minimal_scopes_mode": "Rétrécir les options de séléction de la portée",
+    "set_new_avatar": "Changer d'avatar",
+    "set_new_profile_background": "Changer d'image de fond",
+    "set_new_profile_banner": "Changer de bannière",
+    "settings": "Paramètres",
+    "subject_input_always_show": "Toujours copier le champ de sujet",
+    "subject_line_behavior": "Copier le sujet en répondant",
+    "subject_line_email": "Similaire au courriel : « re : sujet »",
+    "subject_line_mastodon": "Comme mastodon : copier tel quel",
+    "subject_line_noop": "Ne pas copier",
+    "post_status_content_type": "Type de contenu du statuts",
+    "stop_gifs": "N'animer les GIFS que lors du survol du curseur de la souris",
+    "streaming": "Charger automatiquement les nouveaux statuts lorsque vous êtes au haut de la page",
+    "text": "Texte",
+    "theme": "Thème",
+    "theme_help": "Spécifiez des codes couleur hexadécimaux (#rrvvbb) pour personnaliser les couleurs du thème.",
+    "theme_help_v2_1": "Vous pouvez aussi surcharger certaines couleurs de composants et transparence via la case à cocher, utilisez le bouton « Vider tout » pour effacer toutes les surcharges.",
+    "theme_help_v2_2": "Les icônes sous certaines des entrées ont un indicateur de contraste du fond/texte, survolez les pour plus d'informations détailles. Veuillez garder a l'esprit que lors de l'utilisation de transparence l'indicateur de contraste indique le pire des cas.",
+    "tooltipRadius": "Info-bulles/alertes",
+    "upload_a_photo": "Envoyer une photo",
+    "user_settings": "Paramètres utilisateur",
+    "values": {
+      "false": "non",
+      "true": "oui"
+    },
+    "notifications": "Notifications",
+    "notification_setting": "Reçevoir les notifications de :",
+    "notification_setting_follows": "Utilisateurs que vous suivez",
+    "notification_setting_non_follows": "Utilisateurs que vous ne suivez pas",
+    "notification_setting_followers": "Utilisateurs qui vous suivent",
+    "notification_setting_non_followers": "Utilisateurs qui ne vous suivent pas",
+    "notification_mutes": "Pour stopper la récéption de notifications d'un utilisateur particulier, utilisez un masquage.",
+    "notification_blocks": "Bloquer un utilisateur stoppe toute notification et se désabonne de lui.",
+    "enable_web_push_notifications": "Activer les notifications de push web",
+    "style": {
+      "switcher": {
+        "keep_color": "Garder les couleurs",
+        "keep_shadows": "Garder les ombres",
+        "keep_opacity": "Garder la transparence",
+        "keep_roundness": "Garder la rondeur",
+        "keep_fonts": "Garder les polices",
+        "save_load_hint": "L'option « Garder » préserve les options activés en cours lors de la séléction ou chargement des thèmes, il sauve aussi les dites options lors de l'export d'un thème. Quand toutes les cases sont décochés, exporter un thème sauvera tout.",
+        "reset": "Remise à zéro",
+        "clear_all": "Tout vider",
+        "clear_opacity": "Vider la transparence",
+        "load_theme": "Charger le thème",
+        "use_snapshot": "Ancienne version",
+        "help": {
+          "upgraded_from_v2": "PleromaFE à été mis à jour, le thème peut être un peu différent que dans vos souvenirs.",
+          "v2_imported": "Le fichier que vous avez importé vient d'un version antérieure. Nous essayons de maximizer la compatibilité mais il peu y avoir quelques incohérences.",
+          "future_version_imported": "Le fichier importé viens d'une version postérieure de PleromaFE.",
+          "older_version_imported": "Le fichier importé viens d'une version antérieure de PleromaFE.",
+          "snapshot_source_mismatch": "Conflict de version : Probablement due à un retour arrière puis remise à jour de la version de PleromaFE, si vous avez charger le thème en utilisant une version antérieure vous voulez probablement utiliser la version antérieure, autrement utiliser la version postérieure.",
+          "migration_napshot_gone": "Pour une raison inconnue l'instantané est manquant, des parties peuvent rendre différentes que dans vos souvenirs.",
+          "migration_snapshot_ok": "Pour être sûr un instantanée du thème à été chargé. Vos pouvez essayer de charger ses données.",
+          "fe_downgraded": "Retour en arrière de la version de PleromaFE.",
+          "fe_upgraded": "Le moteur de thème PleromaFE à été mis à jour après un changement de version.",
+          "snapshot_missing": "Aucun instantané du thème à été trouvé dans le fichier, il peut y avoir un rendu différent à la vision originelle."
+        },
+        "keep_as_is": "Garder tel-quel",
+        "use_source": "Nouvelle version"
+      },
+      "common": {
+        "color": "Couleur",
+        "opacity": "Transparence",
+        "contrast": {
+          "hint": "Le ratio de contraste est {ratio}, il {level} {context}",
+          "level": {
+            "aa": "répond aux directives de niveau AA (minimum)",
+            "aaa": "répond aux directives de niveau AAA (recommandé)",
+            "bad": "ne réponds à aucune directive d'accessibilité"
+          },
+          "context": {
+            "18pt": "pour texte large (19pt+)",
+            "text": "pour texte"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "Commun",
+        "main": "Couleurs communes",
+        "foreground_hint": "Voir l'onglet « Avancé » pour plus de contrôle détaillé",
+        "rgbo": "Icônes, accents, badges"
+      },
+      "advanced_colors": {
+        "_tab_label": "Avancé",
+        "alert": "Fond d'alerte",
+        "alert_error": "Erreur",
+        "badge": "Fond de badge",
+        "badge_notification": "Notification",
+        "panel_header": "Entête de panneau",
+        "top_bar": "Barre du haut",
+        "borders": "Bordures",
+        "buttons": "Boutons",
+        "inputs": "Champs de saisie",
+        "faint_text": "Texte en fondu",
+        "underlay": "sous-calque",
+        "pressed": "Appuyé",
+        "alert_warning": "Avertissement",
+        "alert_neutral": "Neutre",
+        "post": "Messages/Bios des comptes",
+        "poll": "Graphique de Sondage",
+        "icons": "Icônes",
+        "selectedPost": "Message sélectionné",
+        "selectedMenu": "Objet sélectionné du menu",
+        "disabled": "Désactivé",
+        "tabs": "Onglets",
+        "toggled": "(Dés)activé",
+        "highlight": "Éléments mis en valeur",
+        "popover": "Infobulles, menus"
+      },
+      "radii": {
+        "_tab_label": "Rondeur"
+      },
+      "shadows": {
+        "_tab_label": "Ombres et éclairage",
+        "component": "Composant",
+        "override": "Surcharger",
+        "shadow_id": "Ombre #{value}",
+        "blur": "Flou",
+        "spread": "Dispersion",
+        "inset": "Interne",
+        "hint": "Pour les ombres, vous pouvez aussi utiliser --variable comme valeur de couleur en CSS3. Veuillez noter que spécifier la transparence ne fonctionnera pas dans ce cas.",
+        "filter_hint": {
+          "always_drop_shadow": "Attention, cette ombre utilise toujours {0} quand le navigateur le supporte.",
+          "drop_shadow_syntax": "{0} ne supporte pas le paramètre {1} et mot-clé {2}.",
+          "avatar_inset": "Veuillez noter que combiner a la fois les ombres internes et non-internes sur les avatars peut fournir des résultats innatendus avec la transparence des avatars.",
+          "spread_zero": "Les ombres avec une dispersion > 0 apparaitrons comme si ils étaient à zéro",
+          "inset_classic": "L'ombre interne utilisera toujours {0}"
+        },
+        "components": {
+          "panel": "Panneau",
+          "panelHeader": "En-tête de panneau",
+          "topBar": "Barre du haut",
+          "avatar": "Avatar utilisateur⋅ice (dans la vue de profil)",
+          "avatarStatus": "Avatar utilisateur⋅ice (dans la vue de statuts)",
+          "popup": "Popups et infobulles",
+          "button": "Bouton",
+          "buttonHover": "Bouton (survol)",
+          "buttonPressed": "Bouton (cliqué)",
+          "buttonPressedHover": "Bouton (cliqué+survol)",
+          "input": "Champ de saisie"
+        },
+        "hintV3": "Pour les ombres vous pouvez aussi utiliser la notation {0} pour utiliser un autre emplacement de couleur."
+      },
+      "fonts": {
+        "_tab_label": "Polices",
+        "help": "Sélectionnez la police à utiliser pour les éléments de l'UI. Pour « personnalisé » vous avez à entrer le nom exact de la police comme il apparaît dans le système.",
+        "components": {
+          "interface": "Interface",
+          "input": "Champs de saisie",
+          "post": "Post text",
+          "postCode": "Texte à taille fixe dans un article (texte enrichi)"
+        },
+        "family": "Nom de la police",
+        "size": "Taille (en px)",
+        "weight": "Poid (gras)",
+        "custom": "Personnalisé"
+      },
+      "preview": {
+        "header": "Prévisualisation",
+        "content": "Contenu",
+        "error": "Exemple d'erreur",
+        "button": "Bouton",
+        "text": "Un certain nombre de {0} et {1}",
+        "mono": "contenu",
+        "input": "Je viens juste d’atterrir à L.A.",
+        "faint_link": "manuel utile",
+        "fine_print": "Lisez notre {0} pour n'apprendre rien d'utile !",
+        "header_faint": "Tout va bien",
+        "checkbox": "J'ai survolé les conditions d'utilisation",
+        "link": "un petit lien sympa"
+      }
+    },
+    "version": {
+      "title": "Version",
+      "backend_version": "Version du Backend",
+      "frontend_version": "Version du Frontend"
+    },
+    "change_email": "Changer de courriel",
+    "domain_mutes": "Domaines",
+    "pad_emoji": "Rajouter un espace autour de l'émoji après l’avoir choisit",
+    "notification_visibility_emoji_reactions": "Réactions",
+    "hide_follows_count_description": "Masquer le nombre de suivis",
+    "useStreamingApiWarning": "(Non recommandé, expérimental, connu pour rater des messages)",
+    "type_domains_to_mute": "Écrire les domaines à masquer",
+    "fun": "Rigolo",
+    "greentext": "greentexting",
+    "allow_following_move": "Suivre automatiquement quand ce compte migre",
+    "change_email_error": "Il y a eu un problème pour charger votre courriel.",
+    "changed_email": "Courriel changé avec succès !",
+    "discoverable": "Permettre de découvrir ce compte dans les résultats de recherche web et autres services",
+    "emoji_reactions_on_timeline": "Montrer les émojis-réactions dans le flux",
+    "new_email": "Nouveau courriel",
+    "notification_visibility_moves": "Migrations de compte",
+    "user_mutes": "Comptes",
+    "useStreamingApi": "Recevoir les messages et notifications en temps réel",
+    "notification_setting_filters": "Filtres",
+    "notification_setting_privacy_option": "Masquer l'expéditeur et le contenu des notifications push",
+    "notification_setting_privacy": "Intimité",
+    "hide_followers_count_description": "Masquer le nombre d'abonnés",
+    "accent": "Accent"
+  },
+  "timeline": {
+    "collapse": "Fermer",
+    "conversation": "Conversation",
+    "error_fetching": "Erreur en cherchant les mises à jour",
+    "load_older": "Afficher plus",
+    "no_retweet_hint": "Le message est marqué en abonnés-seulement ou direct et ne peut pas être partagé",
+    "repeated": "a partagé",
+    "show_new": "Afficher plus",
+    "up_to_date": "À jour",
+    "no_more_statuses": "Pas plus de statuts",
+    "no_statuses": "Aucun statuts"
+  },
+  "status": {
+    "favorites": "Favoris",
+    "repeats": "Partages",
+    "delete": "Supprimer statuts",
+    "pin": "Agraffer sur le profil",
+    "unpin": "Dégraffer du profil",
+    "pinned": "Agraffé",
+    "delete_confirm": "Voulez-vous vraiment supprimer ce statuts ?",
+    "reply_to": "Réponse à",
+    "replies_list": "Réponses :",
+    "mute_conversation": "Masquer la conversation",
+    "unmute_conversation": "Démasquer la conversation",
+    "status_unavailable": "Status indisponible",
+    "copy_link": "Copier le lien au status"
+  },
+  "user_card": {
+    "approve": "Accepter",
+    "block": "Bloquer",
+    "blocked": "Bloqué !",
+    "deny": "Rejeter",
+    "favorites": "Favoris",
+    "follow": "Suivre",
+    "follow_sent": "Demande envoyée !",
+    "follow_progress": "Demande en cours…",
+    "follow_again": "Renvoyer la demande ?",
+    "follow_unfollow": "Désabonner",
+    "followees": "Suivis",
+    "followers": "Vous suivent",
+    "following": "Suivi !",
+    "follows_you": "Vous suit !",
+    "its_you": "C'est vous !",
+    "media": "Media",
+    "mute": "Masquer",
+    "muted": "Masqué",
+    "per_day": "par jour",
+    "remote_follow": "Suivre d'une autre instance",
+    "report": "Signalement",
+    "statuses": "Statuts",
+    "unblock": "Débloquer",
+    "unblock_progress": "Déblocage…",
+    "block_progress": "Blocage…",
+    "unmute": "Démasquer",
+    "unmute_progress": "Démasquage…",
+    "mute_progress": "Masquage…",
+    "admin_menu": {
+      "moderation": "Moderation",
+      "grant_admin": "Promouvoir Administrateur⋅ice",
+      "revoke_admin": "Dégrader Administrateur⋅ice",
+      "grant_moderator": "Promouvoir Modérateur⋅ice",
+      "revoke_moderator": "Dégrader Modérateur⋅ice",
+      "activate_account": "Activer le compte",
+      "deactivate_account": "Désactiver le compte",
+      "delete_account": "Supprimer le compte",
+      "force_nsfw": "Marquer tous les statuts comme NSFW",
+      "strip_media": "Supprimer les medias des statuts",
+      "force_unlisted": "Forcer les statuts à être délistés",
+      "sandbox": "Forcer les statuts à être visibles seuleument pour les abonné⋅e⋅s",
+      "disable_remote_subscription": "Interdir de s'abonner a l'utilisateur depuis l'instance distante",
+      "disable_any_subscription": "Interdir de s'abonner à l'utilisateur tout court",
+      "quarantine": "Interdir les statuts de l'utilisateur à fédérer",
+      "delete_user": "Supprimer l'utilisateur",
+      "delete_user_confirmation": "Êtes-vous absolument-sûr⋅e ? Cette action ne peut être annulée."
+    },
+    "mention": "Mention",
+    "hidden": "Caché",
+    "subscribe": "Abonner",
+    "unsubscribe": "Désabonner",
+    "hide_repeats": "Cacher les partages",
+    "show_repeats": "Montrer les partages"
+  },
+  "user_profile": {
+    "timeline_title": "Journal de l'utilisateur⋅ice",
+    "profile_does_not_exist": "Désolé, ce profil n'existe pas.",
+    "profile_loading_error": "Désolé, il y a eu une erreur au chargement du profil."
+  },
+  "user_reporting": {
+    "title": "Signaler {0}",
+    "add_comment_description": "Ce signalement sera envoyé aux modérateur⋅ice⋅s de votre instance. Vous pouvez fournir une explication de pourquoi vous signalez ce compte ci-dessous :",
+    "additional_comments": "Commentaires additionnels",
+    "forward_description": "Le compte vient d'un autre serveur. Envoyer une copie du signalement à celui-ci aussi ?",
+    "forward_to": "Transmettre à {0}",
+    "submit": "Envoyer",
+    "generic_error": "Une erreur est survenue lors du traitement de votre requête."
+  },
+  "who_to_follow": {
+    "more": "Plus",
+    "who_to_follow": "À qui s'abonner"
+  },
+  "tool_tip": {
+    "media_upload": "Envoyer un media",
+    "repeat": "Répéter",
+    "reply": "Répondre",
+    "favorite": "Favoriser",
+    "user_settings": "Paramètres utilisateur",
+    "add_reaction": "Ajouter une réaction",
+    "accept_follow_request": "Accepter la demande de suivit",
+    "reject_follow_request": "Rejeter la demande de suivit"
+  },
+  "upload": {
+    "error": {
+      "base": "L'envoi a échoué.",
+      "file_too_big": "Fichier trop gros [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+      "default": "Réessayez plus tard"
+    },
+    "file_size_units": {
+      "B": "O",
+      "KiB": "KiO",
+      "MiB": "MiO",
+      "GiB": "GiO",
+      "TiB": "TiO"
+    }
+  },
+  "about": {
+    "mrf": {
+      "keyword": {
+        "reject": "Rejeté",
+        "replace": "Remplacer",
+        "keyword_policies": "Politiques par mot-clés",
+        "ftl_removal": "Suppression du flux \"Ensemble du réseau connu\"",
+        "is_replaced_by": "→"
+      },
+      "simple": {
+        "simple_policies": "Politiques par instances",
+        "accept": "Accepter",
+        "accept_desc": "Cette instance accepte des messages seulement depuis ces instances :",
+        "reject": "Rejeter",
+        "reject_desc": "Cette instance n'acceptera pas de message de ces instances :",
+        "quarantine": "Quarantaine",
+        "quarantine_desc": "Cette instance enverras seulement des messages publics à ces instances :",
+        "ftl_removal_desc": "Cette instance supprime ces instance du flux fédéré :",
+        "media_removal": "Suppression multimédia",
+        "media_removal_desc": "Cette instance supprime le contenu multimédia des instances suivantes :",
+        "media_nsfw": "Force le contenu multimédia comme sensible",
+        "ftl_removal": "Suppression du flux fédéré",
+        "media_nsfw_desc": "Cette instance force le contenu multimédia comme sensible pour les messages des instances suivantes :"
+      },
+      "federation": "Fédération",
+      "mrf_policies": "Politiques MRF activées",
+      "mrf_policies_desc": "Les politiques MRF modifient la fédération entre les instances. Les politiques suivantes sont activées :"
+    },
+    "staff": "Staff"
+  },
+  "domain_mute_card": {
+    "mute": "Muet",
+    "mute_progress": "Masquage…",
+    "unmute": "Démasquer",
+    "unmute_progress": "Démasquage…"
+  },
+  "polls": {
+    "add_poll": "Ajouter un Sondage",
+    "add_option": "Ajouter une option",
+    "option": "Option",
+    "votes": "votes",
+    "type": "Type de Sondage",
+    "single_choice": "Choix unique",
+    "multiple_choices": "Choix multiples",
+    "expiry": "Age du sondage",
+    "expires_in": "Fin du sondage dans {0}",
+    "not_enough_options": "Trop peu d'options unique au sondage",
+    "vote": "Voter",
+    "expired": "Sondage terminé il y a {0}"
+  },
+  "emoji": {
+    "emoji": "Émoji",
+    "search_emoji": "Rechercher un émoji",
+    "add_emoji": "Insérer un émoji",
+    "custom": "émoji personnalisé",
+    "unicode": "émoji unicode",
+    "load_all": "Charger tout les {emojiAmount} émojis",
+    "load_all_hint": "{saneAmount} émojis chargé, charger tout les émojis peuvent causer des problèmes de performances.",
+    "stickers": "Stickers",
+    "keep_open": "Garder le sélecteur ouvert"
+  },
+  "remote_user_resolver": {
+    "error": "Non trouvé.",
+    "searching_for": "Rechercher",
+    "remote_user_resolver": "Résolution de compte distant"
+  },
+  "time": {
+    "minutes_short": "{0}min",
+    "second_short": "{0}s",
+    "day": "{0} jour",
+    "days": "{0} jours",
+    "months": "{0} mois",
+    "month_short": "{0}m",
+    "months_short": "{0}m",
+    "now": "tout de suite",
+    "now_short": "maintenant",
+    "second": "{0} seconde",
+    "seconds": "{0} secondes",
+    "seconds_short": "{0}s",
+    "day_short": "{0}j",
+    "days_short": "{0}j",
+    "hour": "{0} heure",
+    "hours": "{0} heures",
+    "hour_short": "{0}h",
+    "hours_short": "{0}h",
+    "in_future": "dans {0}",
+    "in_past": "il y a {0}",
+    "minute": "{0} minute",
+    "minutes": "{0} minutes",
+    "minute_short": "{0}min",
+    "month": "{0} mois",
+    "week": "{0} semaine",
+    "weeks": "{0} semaines",
+    "week_short": "{0}s",
+    "weeks_short": "{0}s",
+    "year": "{0} année",
+    "years": "{0} années",
+    "year_short": "{0}a",
+    "years_short": "{0}a"
+  },
+  "search": {
+    "people": "Comptes",
+    "person_talking": "{count} personnes discutant",
+    "hashtags": "Mot-dièses",
+    "people_talking": "{count} personnes discutant",
+    "no_results": "Aucun résultats"
+  },
+  "password_reset": {
+    "forgot_password": "Mot de passe oublié ?",
+    "check_email": "Vérifiez vos courriels pour le lien permettant de changer votre mot de passe.",
+    "password_reset_disabled": "Le changement de mot de passe est désactivé. Veuillez contacter l'administration de votre instance.",
+    "password_reset_required_but_mailer_is_disabled": "Vous devez changer votre mot de passe mais sont changement est désactivé. Veuillez contacter l’administration de votre instance.",
+    "password_reset": "Nouveau mot de passe",
+    "instruction": "Entrer votre address de courriel ou votre nom utilisateur. Nous enverrons un lien pour changer votre mot de passe.",
+    "placeholder": "Votre email ou nom d'utilisateur",
+    "return_home": "Retourner à la page d'accueil",
+    "not_found": "Email ou nom d'utilisateur inconnu.",
+    "too_many_requests": "Vos avez atteint la limite d'essais, essayez plus tard.",
+    "password_reset_required": "Vous devez changer votre mot de passe pour vous authentifier."
+  }
 }
diff --git a/src/i18n/it.json b/src/i18n/it.json
index cddcb489..360c72aa 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -1,489 +1,489 @@
 {
-    "general": {
-        "submit": "Invia",
-        "apply": "Applica",
-        "more": "Altro",
-        "generic_error": "Errore",
-        "optional": "facoltativo",
-        "show_more": "Mostra tutto",
-        "show_less": "Ripiega",
-        "dismiss": "Chiudi",
-        "cancel": "Annulla",
-        "disable": "Disabilita",
-        "enable": "Abilita",
-        "confirm": "Conferma",
-        "verify": "Verifica"
+  "general": {
+    "submit": "Invia",
+    "apply": "Applica",
+    "more": "Altro",
+    "generic_error": "Errore",
+    "optional": "facoltativo",
+    "show_more": "Mostra tutto",
+    "show_less": "Ripiega",
+    "dismiss": "Chiudi",
+    "cancel": "Annulla",
+    "disable": "Disabilita",
+    "enable": "Abilita",
+    "confirm": "Conferma",
+    "verify": "Verifica"
+  },
+  "nav": {
+    "mentions": "Menzioni",
+    "public_tl": "Sequenza pubblica",
+    "timeline": "Sequenza personale",
+    "twkn": "Sequenza globale",
+    "chat": "Chat della stanza",
+    "friend_requests": "Vogliono seguirti",
+    "about": "Informazioni",
+    "administration": "Amministrazione",
+    "back": "Indietro",
+    "interactions": "Interazioni",
+    "dms": "Messaggi diretti",
+    "user_search": "Ricerca utenti",
+    "search": "Ricerca",
+    "who_to_follow": "Chi seguire",
+    "preferences": "Preferenze"
+  },
+  "notifications": {
+    "followed_you": "ti segue",
+    "notifications": "Notifiche",
+    "read": "Letto!",
+    "broken_favorite": "Stato sconosciuto, lo sto cercando…",
+    "favorited_you": "ha gradito il tuo messaggio",
+    "load_older": "Carica notifiche precedenti",
+    "repeated_you": "ha condiviso il tuo messaggio",
+    "follow_request": "vuole seguirti",
+    "no_more_notifications": "Fine delle notifiche",
+    "migrated_to": "è migrato verso",
+    "reacted_with": "ha reagito con {0}"
+  },
+  "settings": {
+    "attachments": "Allegati",
+    "autoload": "Abilita caricamento automatico quando raggiungi il fondo pagina",
+    "avatar": "Icona utente",
+    "bio": "Introduzione",
+    "current_avatar": "La tua icona attuale",
+    "current_profile_banner": "Il tuo stendardo attuale",
+    "filtering": "Filtri",
+    "filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, una per riga",
+    "hide_attachments_in_convo": "Nascondi gli allegati presenti nelle conversazioni",
+    "hide_attachments_in_tl": "Nascondi gli allegati presenti nelle sequenze",
+    "name": "Nome",
+    "name_bio": "Nome ed introduzione",
+    "nsfw_clickthrough": "Fai click per visualizzare gli allegati offuscati",
+    "profile_background": "Sfondo della tua pagina",
+    "profile_banner": "Stendardo del tuo profilo",
+    "reply_link_preview": "Visualizza le risposte al passaggio del cursore",
+    "set_new_avatar": "Scegli una nuova icona",
+    "set_new_profile_background": "Scegli un nuovo sfondo per la tua pagina",
+    "set_new_profile_banner": "Scegli un nuovo stendardo per il tuo profilo",
+    "settings": "Impostazioni",
+    "theme": "Tema",
+    "user_settings": "Impostazioni Utente",
+    "attachmentRadius": "Allegati",
+    "avatarAltRadius": "Icone utente (Notifiche)",
+    "avatarRadius": "Icone utente",
+    "background": "Sfondo",
+    "btnRadius": "Pulsanti",
+    "cBlue": "Blu (risposte, seguire)",
+    "cGreen": "Verde (ripeti)",
+    "cOrange": "Arancione (gradire)",
+    "cRed": "Rosso (annulla)",
+    "change_password": "Cambia password",
+    "change_password_error": "C'è stato un problema durante il cambiamento della password.",
+    "changed_password": "Password cambiata correttamente!",
+    "collapse_subject": "Ripiega messaggi con Oggetto",
+    "confirm_new_password": "Conferma la nuova password",
+    "current_password": "La tua password attuale",
+    "data_import_export_tab": "Importa o esporta dati",
+    "default_vis": "Visibilità predefinita dei messaggi",
+    "delete_account": "Elimina profilo",
+    "delete_account_description": "Elimina definitivamente i tuoi dati e disattiva il tuo profilo.",
+    "delete_account_error": "C'è stato un problema durante l'eliminazione del tuo profilo. Se il problema persiste contatta l'amministratore della tua stanza.",
+    "delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione del tuo profilo.",
+    "export_theme": "Salva impostazioni",
+    "follow_export": "Esporta la lista di chi segui",
+    "follow_export_button": "Esporta la lista di chi segui in un file CSV",
+    "follow_export_processing": "Sto elaborando, presto ti sarà chiesto di scaricare il tuo file",
+    "follow_import": "Importa la lista di chi segui",
+    "follow_import_error": "Errore nell'importazione della lista di chi segui",
+    "follows_imported": "Importazione riuscita! L'elaborazione richiederà un po' di tempo.",
+    "foreground": "Primo piano",
+    "general": "Generale",
+    "hide_post_stats": "Nascondi statistiche dei messaggi (es. il numero di preferenze)",
+    "hide_user_stats": "Nascondi statistiche dell'utente (es. il numero dei tuoi seguaci)",
+    "import_followers_from_a_csv_file": "Importa una lista di chi segui da un file CSV",
+    "import_theme": "Carica impostazioni",
+    "inputRadius": "Campi di testo",
+    "instance_default": "(predefinito: {value})",
+    "interfaceLanguage": "Lingua dell'interfaccia",
+    "invalid_theme_imported": "Il file selezionato non è un tema supportato da Pleroma. Il tuo tema non è stato modificato.",
+    "limited_availability": "Non disponibile nel tuo browser",
+    "links": "Collegamenti",
+    "lock_account_description": "Limita il tuo account solo a seguaci approvati",
+    "loop_video": "Riproduci video in ciclo continuo",
+    "loop_video_silent_only": "Riproduci solo video senza audio in ciclo continuo (es. le \"gif\" di Mastodon)",
+    "new_password": "Nuova password",
+    "notification_visibility": "Tipi di notifiche da mostrare",
+    "notification_visibility_follows": "Nuove persone ti seguono",
+    "notification_visibility_likes": "Preferiti",
+    "notification_visibility_mentions": "Menzioni",
+    "notification_visibility_repeats": "Condivisioni",
+    "no_rich_text_description": "Togli la formattazione del testo da tutti i messaggi",
+    "oauth_tokens": "Token OAuth",
+    "token": "Token",
+    "refresh_token": "Aggiorna token",
+    "valid_until": "Valido fino a",
+    "revoke_token": "Revoca",
+    "panelRadius": "Pannelli",
+    "pause_on_unfocused": "Interrompi l'aggiornamento continuo mentre la scheda è in secondo piano",
+    "presets": "Valori predefiniti",
+    "profile_tab": "Profilo",
+    "radii_help": "Imposta il raggio degli angoli (in pixel)",
+    "replies_in_timeline": "Risposte nella sequenza personale",
+    "reply_visibility_all": "Mostra tutte le risposte",
+    "reply_visibility_following": "Mostra solo le risposte rivolte a me o agli utenti che seguo",
+    "reply_visibility_self": "Mostra solo risposte rivolte a me",
+    "saving_err": "Errore nel salvataggio delle impostazioni",
+    "saving_ok": "Impostazioni salvate",
+    "security_tab": "Sicurezza",
+    "stop_gifs": "Riproduci GIF al passaggio del cursore",
+    "streaming": "Mostra automaticamente i nuovi messaggi quando sei in cima alla pagina",
+    "text": "Testo",
+    "theme_help": "Usa codici colore esadecimali (#rrggbb) per personalizzare il tuo schema di colori.",
+    "tooltipRadius": "Suggerimenti/avvisi",
+    "values": {
+      "false": "no",
+      "true": "sì"
     },
-    "nav": {
-        "mentions": "Menzioni",
-        "public_tl": "Sequenza pubblica",
-        "timeline": "Sequenza personale",
-        "twkn": "Sequenza globale",
-        "chat": "Chat della stanza",
-        "friend_requests": "Vogliono seguirti",
-        "about": "Informazioni",
-        "administration": "Amministrazione",
-        "back": "Indietro",
-        "interactions": "Interazioni",
-        "dms": "Messaggi diretti",
-        "user_search": "Ricerca utenti",
-        "search": "Ricerca",
-        "who_to_follow": "Chi seguire",
-        "preferences": "Preferenze"
+    "avatar_size_instruction": "La taglia minima per l'icona personale è 150x150 pixel.",
+    "domain_mutes": "Domini",
+    "discoverable": "Permetti la scoperta di questo profilo da servizi di ricerca ed altro",
+    "composing": "Composizione",
+    "changed_email": "Email cambiata con successo!",
+    "change_email_error": "C'è stato un problema nel cambiare la tua email.",
+    "change_email": "Cambia email",
+    "blocks_tab": "Bloccati",
+    "blocks_imported": "Blocchi importati! Saranno elaborati a breve.",
+    "block_import_error": "Errore nell'importazione",
+    "block_import": "Importa blocchi",
+    "block_export_button": "Esporta i tuoi blocchi in un file CSV",
+    "block_export": "Esporta blocchi",
+    "allow_following_move": "Consenti",
+    "mfa": {
+      "verify": {
+        "desc": "Per abilitare l'autenticazione bifattoriale, inserisci il codice fornito dalla tua applicazione:"
+      },
+      "scan": {
+        "secret_code": "Codice",
+        "desc": "Con la tua applicazione bifattoriale, acquisisci questo QR o inserisci il codice manualmente:",
+        "title": "Acquisisci"
+      },
+      "authentication_methods": "Metodi di accesso",
+      "recovery_codes_warning": "Appuntati i codici o salvali in un posto sicuro, altrimenti rischi di non rivederli mai più. Se perderai l'accesso sia alla tua applicazione bifattoriale che ai codici di recupero non potrai più accedere al tuo profilo.",
+      "waiting_a_recovery_codes": "Ricevo codici di recupero…",
+      "recovery_codes": "Codici di recupero.",
+      "warning_of_generate_new_codes": "Alla generazione di nuovi codici di recupero, quelli vecchi saranno disattivati.",
+      "generate_new_recovery_codes": "Genera nuovi codici di recupero",
+      "title": "Accesso bifattoriale",
+      "confirm_and_enable": "Conferma ed abilita OTP",
+      "wait_pre_setup_otp": "preimposto OTP",
+      "setup_otp": "Imposta OTP",
+      "otp": "OTP"
     },
-    "notifications": {
-        "followed_you": "ti segue",
-        "notifications": "Notifiche",
-        "read": "Letto!",
-        "broken_favorite": "Stato sconosciuto, lo sto cercando…",
-        "favorited_you": "ha gradito il tuo messaggio",
-        "load_older": "Carica notifiche precedenti",
-        "repeated_you": "ha condiviso il tuo messaggio",
-        "follow_request": "vuole seguirti",
-        "no_more_notifications": "Fine delle notifiche",
-        "migrated_to": "è migrato verso",
-        "reacted_with": "ha reagito con {0}"
-    },
-    "settings": {
-        "attachments": "Allegati",
-        "autoload": "Abilita caricamento automatico quando raggiungi il fondo pagina",
-        "avatar": "Icona utente",
-        "bio": "Introduzione",
-        "current_avatar": "La tua icona attuale",
-        "current_profile_banner": "Il tuo stendardo attuale",
-        "filtering": "Filtri",
-        "filtering_explanation": "Tutti i post contenenti queste parole saranno silenziati, una per riga",
-        "hide_attachments_in_convo": "Nascondi gli allegati presenti nelle conversazioni",
-        "hide_attachments_in_tl": "Nascondi gli allegati presenti nelle sequenze",
-        "name": "Nome",
-        "name_bio": "Nome ed introduzione",
-        "nsfw_clickthrough": "Fai click per visualizzare gli allegati offuscati",
-        "profile_background": "Sfondo della tua pagina",
-        "profile_banner": "Stendardo del tuo profilo",
-        "reply_link_preview": "Visualizza le risposte al passaggio del cursore",
-        "set_new_avatar": "Scegli una nuova icona",
-        "set_new_profile_background": "Scegli un nuovo sfondo per la tua pagina",
-        "set_new_profile_banner": "Scegli un nuovo stendardo per il tuo profilo",
-        "settings": "Impostazioni",
-        "theme": "Tema",
-        "user_settings": "Impostazioni Utente",
-        "attachmentRadius": "Allegati",
-        "avatarAltRadius": "Icone utente (Notifiche)",
-        "avatarRadius": "Icone utente",
-        "background": "Sfondo",
-        "btnRadius": "Pulsanti",
-        "cBlue": "Blu (risposte, seguire)",
-        "cGreen": "Verde (ripeti)",
-        "cOrange": "Arancione (gradire)",
-        "cRed": "Rosso (annulla)",
-        "change_password": "Cambia password",
-        "change_password_error": "C'è stato un problema durante il cambiamento della password.",
-        "changed_password": "Password cambiata correttamente!",
-        "collapse_subject": "Ripiega messaggi con Oggetto",
-        "confirm_new_password": "Conferma la nuova password",
-        "current_password": "La tua password attuale",
-        "data_import_export_tab": "Importa o esporta dati",
-        "default_vis": "Visibilità predefinita dei messaggi",
-        "delete_account": "Elimina profilo",
-        "delete_account_description": "Elimina definitivamente i tuoi dati e disattiva il tuo profilo.",
-        "delete_account_error": "C'è stato un problema durante l'eliminazione del tuo profilo. Se il problema persiste contatta l'amministratore della tua stanza.",
-        "delete_account_instructions": "Digita la tua password nel campo sottostante per confermare l'eliminazione del tuo profilo.",
-        "export_theme": "Salva impostazioni",
-        "follow_export": "Esporta la lista di chi segui",
-        "follow_export_button": "Esporta la lista di chi segui in un file CSV",
-        "follow_export_processing": "Sto elaborando, presto ti sarà chiesto di scaricare il tuo file",
-        "follow_import": "Importa la lista di chi segui",
-        "follow_import_error": "Errore nell'importazione della lista di chi segui",
-        "follows_imported": "Importazione riuscita! L'elaborazione richiederà un po' di tempo.",
-        "foreground": "Primo piano",
-        "general": "Generale",
-        "hide_post_stats": "Nascondi statistiche dei messaggi (es. il numero di preferenze)",
-        "hide_user_stats": "Nascondi statistiche dell'utente (es. il numero dei tuoi seguaci)",
-        "import_followers_from_a_csv_file": "Importa una lista di chi segui da un file CSV",
-        "import_theme": "Carica impostazioni",
-        "inputRadius": "Campi di testo",
-        "instance_default": "(predefinito: {value})",
-        "interfaceLanguage": "Lingua dell'interfaccia",
-        "invalid_theme_imported": "Il file selezionato non è un tema supportato da Pleroma. Il tuo tema non è stato modificato.",
-        "limited_availability": "Non disponibile nel tuo browser",
-        "links": "Collegamenti",
-        "lock_account_description": "Limita il tuo account solo a seguaci approvati",
-        "loop_video": "Riproduci video in ciclo continuo",
-        "loop_video_silent_only": "Riproduci solo video senza audio in ciclo continuo (es. le \"gif\" di Mastodon)",
-        "new_password": "Nuova password",
-        "notification_visibility": "Tipi di notifiche da mostrare",
-        "notification_visibility_follows": "Nuove persone ti seguono",
-        "notification_visibility_likes": "Preferiti",
-        "notification_visibility_mentions": "Menzioni",
-        "notification_visibility_repeats": "Condivisioni",
-        "no_rich_text_description": "Togli la formattazione del testo da tutti i messaggi",
-        "oauth_tokens": "Token OAuth",
-        "token": "Token",
-        "refresh_token": "Aggiorna token",
-        "valid_until": "Valido fino a",
-        "revoke_token": "Revoca",
-        "panelRadius": "Pannelli",
-        "pause_on_unfocused": "Interrompi l'aggiornamento continuo mentre la scheda è in secondo piano",
-        "presets": "Valori predefiniti",
-        "profile_tab": "Profilo",
-        "radii_help": "Imposta il raggio degli angoli (in pixel)",
-        "replies_in_timeline": "Risposte nella sequenza personale",
-        "reply_visibility_all": "Mostra tutte le risposte",
-        "reply_visibility_following": "Mostra solo le risposte rivolte a me o agli utenti che seguo",
-        "reply_visibility_self": "Mostra solo risposte rivolte a me",
-        "saving_err": "Errore nel salvataggio delle impostazioni",
-        "saving_ok": "Impostazioni salvate",
-        "security_tab": "Sicurezza",
-        "stop_gifs": "Riproduci GIF al passaggio del cursore",
-        "streaming": "Mostra automaticamente i nuovi messaggi quando sei in cima alla pagina",
-        "text": "Testo",
-        "theme_help": "Usa codici colore esadecimali (#rrggbb) per personalizzare il tuo schema di colori.",
-        "tooltipRadius": "Suggerimenti/avvisi",
-        "values": {
-            "false": "no",
-            "true": "sì"
+    "enter_current_password_to_confirm": "Inserisci la tua password per identificarti",
+    "security": "Sicurezza",
+    "app_name": "Nome applicazione",
+    "style": {
+      "switcher": {
+        "help": {
+          "older_version_imported": "Il tema importato è stato creato per una versione precedente dell'interfaccia.",
+          "future_version_imported": "Il tema importato è stato creato per una versione più recente dell'interfaccia.",
+          "v2_imported": "Il tema importato è stato creato per una vecchia interfaccia. Non tutto potrebbe essere come prima.",
+          "upgraded_from_v2": "L'interfaccia è stata aggiornata, il tema potrebbe essere diverso da come lo intendevi.",
+          "migration_snapshot_ok": "Ho caricato l'anteprima del tema. Puoi provare a caricarne i contenuti.",
+          "fe_downgraded": "L'interfaccia è stata portata ad una versione precedente.",
+          "fe_upgraded": "Lo schema dei temi è stato aggiornato insieme all'interfaccia.",
+          "snapshot_missing": "Il tema non è provvisto di anteprima, quindi potrebbe essere diverso da come appare.",
+          "snapshot_present": "Tutti i valori sono sostituiti dall'anteprima del tema. Puoi invece caricare i suoi contenuti.",
+          "snapshot_source_mismatch": "Conflitto di versione: probabilmente l'interfaccia è stata portata ad una versione precedente e poi aggiornata di nuovo. Se hai modificato il tema con una versione precedente dell'interfaccia, usa la vecchia versione del tema, altrimenti puoi usare la nuova.",
+          "migration_napshot_gone": "Anteprima del tema non trovata, non tutto potrebbe essere come ricordi."
         },
-        "avatar_size_instruction": "La taglia minima per l'icona personale è 150x150 pixel.",
-        "domain_mutes": "Domini",
-        "discoverable": "Permetti la scoperta di questo profilo da servizi di ricerca ed altro",
-        "composing": "Composizione",
-        "changed_email": "Email cambiata con successo!",
-        "change_email_error": "C'è stato un problema nel cambiare la tua email.",
-        "change_email": "Cambia email",
-        "blocks_tab": "Bloccati",
-        "blocks_imported": "Blocchi importati! Saranno elaborati a breve.",
-        "block_import_error": "Errore nell'importazione",
-        "block_import": "Importa blocchi",
-        "block_export_button": "Esporta i tuoi blocchi in un file CSV",
-        "block_export": "Esporta blocchi",
-        "allow_following_move": "Consenti",
-        "mfa": {
-            "verify": {
-                "desc": "Per abilitare l'autenticazione bifattoriale, inserisci il codice fornito dalla tua applicazione:"
-            },
-            "scan": {
-                "secret_code": "Codice",
-                "desc": "Con la tua applicazione bifattoriale, acquisisci questo QR o inserisci il codice manualmente:",
-                "title": "Acquisisci"
-            },
-            "authentication_methods": "Metodi di accesso",
-            "recovery_codes_warning": "Appuntati i codici o salvali in un posto sicuro, altrimenti rischi di non rivederli mai più. Se perderai l'accesso sia alla tua applicazione bifattoriale che ai codici di recupero non potrai più accedere al tuo profilo.",
-            "waiting_a_recovery_codes": "Ricevo codici di recupero…",
-            "recovery_codes": "Codici di recupero.",
-            "warning_of_generate_new_codes": "Alla generazione di nuovi codici di recupero, quelli vecchi saranno disattivati.",
-            "generate_new_recovery_codes": "Genera nuovi codici di recupero",
-            "title": "Accesso bifattoriale",
-            "confirm_and_enable": "Conferma ed abilita OTP",
-            "wait_pre_setup_otp": "preimposto OTP",
-            "setup_otp": "Imposta OTP",
-            "otp": "OTP"
-        },
-        "enter_current_password_to_confirm": "Inserisci la tua password per identificarti",
-        "security": "Sicurezza",
-        "app_name": "Nome applicazione",
-        "style": {
-            "switcher": {
-                "help": {
-                    "older_version_imported": "Il tema importato è stato creato per una versione precedente dell'interfaccia.",
-                    "future_version_imported": "Il tema importato è stato creato per una versione più recente dell'interfaccia.",
-                    "v2_imported": "Il tema importato è stato creato per una vecchia interfaccia. Non tutto potrebbe essere come prima.",
-                    "upgraded_from_v2": "L'interfaccia è stata aggiornata, il tema potrebbe essere diverso da come lo intendevi.",
-                    "migration_snapshot_ok": "Ho caricato l'anteprima del tema. Puoi provare a caricarne i contenuti.",
-                    "fe_downgraded": "L'interfaccia è stata portata ad una versione precedente.",
-                    "fe_upgraded": "Lo schema dei temi è stato aggiornato insieme all'interfaccia.",
-                    "snapshot_missing": "Il tema non è provvisto di anteprima, quindi potrebbe essere diverso da come appare.",
-                    "snapshot_present": "Tutti i valori sono sostituiti dall'anteprima del tema. Puoi invece caricare i suoi contenuti.",
-                    "snapshot_source_mismatch": "Conflitto di versione: probabilmente l'interfaccia è stata portata ad una versione precedente e poi aggiornata di nuovo. Se hai modificato il tema con una versione precedente dell'interfaccia, usa la vecchia versione del tema, altrimenti puoi usare la nuova.",
-                    "migration_napshot_gone": "Anteprima del tema non trovata, non tutto potrebbe essere come ricordi."
-                },
-                "use_source": "Nuova versione",
-                "use_snapshot": "Versione precedente",
-                "keep_as_is": "Mantieni tal quale",
-                "load_theme": "Carica tema",
-                "clear_opacity": "Rimuovi opacità",
-                "clear_all": "Azzera tutto",
-                "reset": "Reimposta",
-                "save_load_hint": "Le opzioni \"mantieni\" conservano le impostazioni correnti quando selezioni o carichi un tema, e le salvano quando ne esporti uno. Quando nessuna casella è selezionata, tutte le impostazioni correnti saranno salvate nel tema.",
-                "keep_fonts": "Mantieni font",
-                "keep_roundness": "Mantieni vertici",
-                "keep_opacity": "Mantieni opacità",
-                "keep_shadows": "Mantieni ombre",
-                "keep_color": "Mantieni colori"
-            },
-            "common": {
-                "opacity": "Opacità",
-                "color": "Colore"
-            }
-        },
-        "enable_web_push_notifications": "Abilita notifiche web push",
-        "fun": "Divertimento",
-        "notification_mutes": "Per non ricevere notifiche da uno specifico utente, zittiscilo.",
-        "notification_setting_privacy_option": "Nascondi mittente e contenuti delle notifiche push",
-        "notification_setting_privacy": "Privacy",
-        "notification_setting_followers": "Utenti che ti seguono",
-        "notification_setting_non_followers": "Utenti che non ti seguono",
-        "notification_setting_non_follows": "Utenti che non segui",
-        "notification_setting_follows": "Utenti che segui",
-        "notification_setting": "Ricevi notifiche da:",
-        "notification_setting_filters": "Filtri",
-        "notifications": "Notifiche",
-        "greentext": "Frecce da meme",
-        "upload_a_photo": "Carica un'immagine",
-        "type_domains_to_mute": "Inserisci domini da zittire",
-        "theme_help_v2_2": "Le icone dietro alcuni elementi sono indicatori del contrasto fra testo e sfondo, passaci sopra col puntatore per ulteriori informazioni. Se si usano delle trasparenze, questi indicatori mostrano il peggior caso possibile.",
-        "theme_help_v2_1": "Puoi anche forzare colore ed opacità di alcuni elementi selezionando la casella. Usa il pulsante \"Azzera\" per azzerare tutte le forzature.",
-        "useStreamingApiWarning": "(Sconsigliato, sperimentale, può saltare messaggi)",
-        "useStreamingApi": "Ricevi messaggi e notifiche in tempo reale",
-        "user_mutes": "Utenti",
-        "post_status_content_type": "Tipo di contenuto dei messaggi",
-        "subject_line_noop": "Non copiare",
-        "subject_line_mastodon": "Come in Mastodon: copia tal quale",
-        "subject_line_email": "Come nelle email: \"re: oggetto\"",
-        "subject_line_behavior": "Copia oggetto quando rispondi",
-        "subject_input_always_show": "Mostra sempre il campo Oggetto",
-        "minimal_scopes_mode": "Riduci opzioni di visibilità",
-        "scope_copy": "Risposte ereditano la visibilità (messaggi privati lo fanno sempre)",
-        "search_user_to_mute": "Cerca utente da zittire",
-        "search_user_to_block": "Cerca utente da bloccare",
-        "autohide_floating_post_button": "Nascondi automaticamente il pulsante di composizione (mobile)",
-        "show_moderator_badge": "Mostra l'insegna di moderatore sulla mia pagina",
-        "show_admin_badge": "Mostra l'insegna di amministratore sulla mia pagina",
-        "hide_followers_count_description": "Non mostrare quanti seguaci ho",
-        "hide_follows_count_description": "Non mostrare quanti utenti seguo",
-        "hide_followers_description": "Non mostrare i miei seguaci",
-        "hide_follows_description": "Non mostrare chi seguo",
-        "no_mutes": "Nessun utente zittito",
-        "no_blocks": "Nessun utente bloccato",
-        "notification_visibility_emoji_reactions": "Reazioni",
-        "notification_visibility_moves": "Migrazioni utenti",
-        "new_email": "Nuova email",
-        "use_contain_fit": "Non ritagliare le anteprime degli allegati",
-        "play_videos_in_modal": "Riproduci video in un riquadro a sbalzo",
-        "mutes_tab": "Zittiti",
-        "interface": "Interfaccia",
-        "instance_default_simple": "(predefinito)",
-        "checkboxRadius": "Caselle di selezione",
-        "import_blocks_from_a_csv_file": "Importa blocchi da un file CSV",
-        "hide_filtered_statuses": "Nascondi messaggi filtrati",
-        "use_one_click_nsfw": "Apri media offuscati con un solo click",
-        "preload_images": "Precarica immagini",
-        "hide_isp": "Nascondi pannello della stanza",
-        "max_thumbnails": "Numero massimo di anteprime per messaggio",
-        "hide_muted_posts": "Nascondi messaggi degli utenti zittiti",
-        "accent": "Accento",
-        "emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze",
-        "pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore",
-        "notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più."
+        "use_source": "Nuova versione",
+        "use_snapshot": "Versione precedente",
+        "keep_as_is": "Mantieni tal quale",
+        "load_theme": "Carica tema",
+        "clear_opacity": "Rimuovi opacità",
+        "clear_all": "Azzera tutto",
+        "reset": "Reimposta",
+        "save_load_hint": "Le opzioni \"mantieni\" conservano le impostazioni correnti quando selezioni o carichi un tema, e le salvano quando ne esporti uno. Quando nessuna casella è selezionata, tutte le impostazioni correnti saranno salvate nel tema.",
+        "keep_fonts": "Mantieni font",
+        "keep_roundness": "Mantieni vertici",
+        "keep_opacity": "Mantieni opacità",
+        "keep_shadows": "Mantieni ombre",
+        "keep_color": "Mantieni colori"
+      },
+      "common": {
+        "opacity": "Opacità",
+        "color": "Colore"
+      }
     },
-    "timeline": {
-        "error_fetching": "Errore nell'aggiornamento",
-        "load_older": "Carica messaggi più vecchi",
-        "show_new": "Mostra nuovi",
-        "up_to_date": "Aggiornato",
-        "collapse": "Riduci",
-        "conversation": "Conversazione",
-        "no_retweet_hint": "Il messaggio è diretto o solo per seguaci e non può essere condiviso",
-        "repeated": "condiviso"
-    },
-    "user_card": {
-        "follow": "Segui",
-        "followees": "Chi stai seguendo",
-        "followers": "Seguaci",
-        "following": "Seguìto!",
-        "follows_you": "Ti segue!",
-        "mute": "Silenzia",
-        "muted": "Silenziato",
-        "per_day": "al giorno",
-        "statuses": "Messaggi",
-        "approve": "Approva",
-        "block": "Blocca",
-        "blocked": "Bloccato!",
-        "deny": "Nega",
-        "remote_follow": "Segui da remoto"
-    },
-    "chat": {
-        "title": "Chat"
-    },
-    "features_panel": {
-        "chat": "Chat",
-        "gopher": "Gopher",
-        "media_proxy": "Proxy multimedia",
-        "scope_options": "Opzioni visibilità",
-        "text_limit": "Lunghezza massima",
-        "title": "Caratteristiche",
-        "who_to_follow": "Chi seguire"
-    },
-    "finder": {
-        "error_fetching_user": "Errore nel recupero dell'utente",
-        "find_user": "Trova utente"
-    },
-    "login": {
-        "login": "Accedi",
-        "logout": "Disconnettiti",
-        "password": "Password",
-        "placeholder": "es. Lupo Lucio",
-        "register": "Registrati",
-        "username": "Nome utente",
-        "description": "Accedi con OAuth",
-        "hint": "Accedi per partecipare alla discussione",
-        "authentication_code": "Codice di autenticazione",
-        "enter_recovery_code": "Inserisci un codice di recupero",
-        "enter_two_factor_code": "Inserisci un codice two-factor",
-        "recovery_code": "Codice di recupero",
-        "heading": {
-            "totp": "Autenticazione two-factor",
-            "recovery": "Recupero two-factor"
-        }
-    },
-    "post_status": {
-        "account_not_locked_warning": "Il tuo profilo non è {0}. Chiunque può seguirti e vedere i tuoi messaggi riservati ai tuoi seguaci.",
-        "account_not_locked_warning_link": "protetto",
-        "attachments_sensitive": "Nascondi gli allegati",
-        "content_type": {
-            "text/plain": "Testo normale",
-            "text/bbcode": "BBCode",
-            "text/markdown": "Markdown",
-            "text/html": "HTML"
-        },
-        "content_warning": "Oggetto (facoltativo)",
-        "default": "Sono appena atterrato a Fiumicino.",
-        "direct_warning": "Questo post sarà visibile solo dagli utenti menzionati.",
-        "posting": "Sto pubblicando",
-        "scope": {
-            "direct": "Diretto - Visibile solo agli utenti menzionati",
-            "private": "Solo per seguaci - Visibile solo dai tuoi seguaci",
-            "public": "Pubblico - Visibile sulla sequenza pubblica",
-            "unlisted": "Non elencato - Non visibile sulla sequenza pubblica"
-        },
-        "scope_notice": {
-            "unlisted": "Questo messaggio non sarà visibile sulla sequenza locale né su quella pubblica",
-            "private": "Questo messaggio sarà visibile solo ai tuoi seguaci",
-            "public": "Questo messaggio sarà visibile a tutti"
-        },
-        "direct_warning_to_first_only": "Questo messaggio sarà visibile solo agli utenti menzionati all'inizio.",
-        "direct_warning_to_all": "Questo messaggio sarà visibile a tutti i menzionati.",
-        "new_status": "Nuovo messaggio"
-    },
-    "registration": {
-        "bio": "Introduzione",
-        "email": "Email",
-        "fullname": "Nome visualizzato",
-        "password_confirm": "Conferma password",
-        "registration": "Registrazione",
-        "token": "Codice d'invito",
-        "validations": {
-            "password_confirmation_match": "dovrebbe essere uguale alla password",
-            "password_confirmation_required": "non può essere vuoto",
-            "password_required": "non può essere vuoto",
-            "email_required": "non può essere vuoto",
-            "fullname_required": "non può essere vuoto",
-            "username_required": "non può essere vuoto"
-        },
-        "bio_placeholder": "es.\nCiao, sono Lupo Lucio.\nSono un lupo fantastico che vive nel Fantabosco. Forse mi hai visto alla Melevisione.",
-        "fullname_placeholder": "es. Lupo Lucio",
-        "username_placeholder": "es. mister_wolf",
-        "new_captcha": "Clicca l'immagine per avere un altro captcha",
-        "captcha": "CAPTCHA"
-    },
-    "user_profile": {
-        "timeline_title": "Sequenza dell'Utente"
-    },
-    "who_to_follow": {
-        "more": "Altro",
-        "who_to_follow": "Chi seguire"
-    },
-    "about": {
-        "mrf": {
-            "federation": "Federazione",
-            "keyword": {
-                "reject": "Rifiuta",
-                "replace": "Sostituisci",
-                "is_replaced_by": "→",
-                "keyword_policies": "Regole per parole chiave",
-                "ftl_removal": "Rimozione dalla sequenza globale"
-            },
-            "simple": {
-                "reject": "Rifiuta",
-                "accept": "Accetta",
-                "simple_policies": "Regole specifiche alla stanza",
-                "accept_desc": "Questa stanza accetta messaggi solo dalle seguenti stanze:",
-                "reject_desc": "Questa stanza non accetterà messaggi dalle stanze seguenti:",
-                "quarantine": "Quarantena",
-                "quarantine_desc": "Questa stanza inoltrerà solo messaggi pubblici alle seguenti stanze:",
-                "ftl_removal": "Rimozione dalla sequenza globale",
-                "ftl_removal_desc": "Questa stanza rimuove le seguenti stanze dalla sequenza globale:",
-                "media_removal": "Rimozione multimedia",
-                "media_removal_desc": "Questa istanza rimuove gli allegati dalle seguenti stanze:",
-                "media_nsfw": "Allegati oscurati forzatamente",
-                "media_nsfw_desc": "Questa stanza oscura gli allegati dei messaggi provenienti da queste stanze:"
-            },
-            "mrf_policies": "Regole RM abilitate",
-            "mrf_policies_desc": "Le regole RM cambiano il comportamento federativo della stanza. Vigono le seguenti regole:"
-        },
-        "staff": "Equipaggio"
-    },
-    "domain_mute_card": {
-        "mute": "Zittisci",
-        "mute_progress": "Zittisco…",
-        "unmute": "Ascolta",
-        "unmute_progress": "Procedo…"
-    },
-    "exporter": {
-        "export": "Esporta",
-        "processing": "In elaborazione, il tuo file sarà scaricabile a breve"
-    },
-    "image_cropper": {
-        "crop_picture": "Ritaglia immagine",
-        "save": "Salva",
-        "save_without_cropping": "Salva senza ritagliare",
-        "cancel": "Annulla"
-    },
-    "importer": {
-        "submit": "Invia",
-        "success": "Importato.",
-        "error": "L'importazione non è andata a buon fine."
-    },
-    "media_modal": {
-        "previous": "Precedente",
-        "next": "Prossimo"
-    },
-    "polls": {
-        "add_poll": "Sondaggio",
-        "add_option": "Alternativa",
-        "option": "Opzione",
-        "votes": "voti",
-        "vote": "Vota",
-        "type": "Tipo di sondaggio",
-        "single_choice": "Scelta singola",
-        "multiple_choices": "Scelta multipla",
-        "expiry": "Scadenza",
-        "expires_in": "Scade fra {0}",
-        "expired": "Scaduto {0} fa",
-        "not_enough_options": "Aggiungi altre risposte"
-    },
-    "interactions": {
-        "favs_repeats": "Condivisi e preferiti",
-        "load_older": "Carica vecchie interazioni",
-        "moves": "Utenti migrati",
-        "follows": "Nuovi seguìti"
-    },
-    "emoji": {
-        "load_all": "Carico tutti i {emojiAmount} emoji",
-        "load_all_hint": "Primi {saneAmount} emoji caricati, caricarli tutti potrebbe causare rallentamenti.",
-        "unicode": "Emoji Unicode",
-        "custom": "Emoji personale",
-        "add_emoji": "Inserisci Emoji",
-        "search_emoji": "Cerca un emoji",
-        "keep_open": "Tieni aperto il menù",
-        "emoji": "Emoji",
-        "stickers": "Adesivi"
-    },
-    "selectable_list": {
-        "select_all": "Seleziona tutto"
-    },
-    "remote_user_resolver": {
-        "error": "Non trovato.",
-        "searching_for": "Cerco",
-        "remote_user_resolver": "Cerca utenti remoti"
+    "enable_web_push_notifications": "Abilita notifiche web push",
+    "fun": "Divertimento",
+    "notification_mutes": "Per non ricevere notifiche da uno specifico utente, zittiscilo.",
+    "notification_setting_privacy_option": "Nascondi mittente e contenuti delle notifiche push",
+    "notification_setting_privacy": "Privacy",
+    "notification_setting_followers": "Utenti che ti seguono",
+    "notification_setting_non_followers": "Utenti che non ti seguono",
+    "notification_setting_non_follows": "Utenti che non segui",
+    "notification_setting_follows": "Utenti che segui",
+    "notification_setting": "Ricevi notifiche da:",
+    "notification_setting_filters": "Filtri",
+    "notifications": "Notifiche",
+    "greentext": "Frecce da meme",
+    "upload_a_photo": "Carica un'immagine",
+    "type_domains_to_mute": "Inserisci domini da zittire",
+    "theme_help_v2_2": "Le icone dietro alcuni elementi sono indicatori del contrasto fra testo e sfondo, passaci sopra col puntatore per ulteriori informazioni. Se si usano delle trasparenze, questi indicatori mostrano il peggior caso possibile.",
+    "theme_help_v2_1": "Puoi anche forzare colore ed opacità di alcuni elementi selezionando la casella. Usa il pulsante \"Azzera\" per azzerare tutte le forzature.",
+    "useStreamingApiWarning": "(Sconsigliato, sperimentale, può saltare messaggi)",
+    "useStreamingApi": "Ricevi messaggi e notifiche in tempo reale",
+    "user_mutes": "Utenti",
+    "post_status_content_type": "Tipo di contenuto dei messaggi",
+    "subject_line_noop": "Non copiare",
+    "subject_line_mastodon": "Come in Mastodon: copia tal quale",
+    "subject_line_email": "Come nelle email: \"re: oggetto\"",
+    "subject_line_behavior": "Copia oggetto quando rispondi",
+    "subject_input_always_show": "Mostra sempre il campo Oggetto",
+    "minimal_scopes_mode": "Riduci opzioni di visibilità",
+    "scope_copy": "Risposte ereditano la visibilità (messaggi privati lo fanno sempre)",
+    "search_user_to_mute": "Cerca utente da zittire",
+    "search_user_to_block": "Cerca utente da bloccare",
+    "autohide_floating_post_button": "Nascondi automaticamente il pulsante di composizione (mobile)",
+    "show_moderator_badge": "Mostra l'insegna di moderatore sulla mia pagina",
+    "show_admin_badge": "Mostra l'insegna di amministratore sulla mia pagina",
+    "hide_followers_count_description": "Non mostrare quanti seguaci ho",
+    "hide_follows_count_description": "Non mostrare quanti utenti seguo",
+    "hide_followers_description": "Non mostrare i miei seguaci",
+    "hide_follows_description": "Non mostrare chi seguo",
+    "no_mutes": "Nessun utente zittito",
+    "no_blocks": "Nessun utente bloccato",
+    "notification_visibility_emoji_reactions": "Reazioni",
+    "notification_visibility_moves": "Migrazioni utenti",
+    "new_email": "Nuova email",
+    "use_contain_fit": "Non ritagliare le anteprime degli allegati",
+    "play_videos_in_modal": "Riproduci video in un riquadro a sbalzo",
+    "mutes_tab": "Zittiti",
+    "interface": "Interfaccia",
+    "instance_default_simple": "(predefinito)",
+    "checkboxRadius": "Caselle di selezione",
+    "import_blocks_from_a_csv_file": "Importa blocchi da un file CSV",
+    "hide_filtered_statuses": "Nascondi messaggi filtrati",
+    "use_one_click_nsfw": "Apri media offuscati con un solo click",
+    "preload_images": "Precarica immagini",
+    "hide_isp": "Nascondi pannello della stanza",
+    "max_thumbnails": "Numero massimo di anteprime per messaggio",
+    "hide_muted_posts": "Nascondi messaggi degli utenti zittiti",
+    "accent": "Accento",
+    "emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze",
+    "pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore",
+    "notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più."
+  },
+  "timeline": {
+    "error_fetching": "Errore nell'aggiornamento",
+    "load_older": "Carica messaggi più vecchi",
+    "show_new": "Mostra nuovi",
+    "up_to_date": "Aggiornato",
+    "collapse": "Riduci",
+    "conversation": "Conversazione",
+    "no_retweet_hint": "Il messaggio è diretto o solo per seguaci e non può essere condiviso",
+    "repeated": "condiviso"
+  },
+  "user_card": {
+    "follow": "Segui",
+    "followees": "Chi stai seguendo",
+    "followers": "Seguaci",
+    "following": "Seguìto!",
+    "follows_you": "Ti segue!",
+    "mute": "Silenzia",
+    "muted": "Silenziato",
+    "per_day": "al giorno",
+    "statuses": "Messaggi",
+    "approve": "Approva",
+    "block": "Blocca",
+    "blocked": "Bloccato!",
+    "deny": "Nega",
+    "remote_follow": "Segui da remoto"
+  },
+  "chat": {
+    "title": "Chat"
+  },
+  "features_panel": {
+    "chat": "Chat",
+    "gopher": "Gopher",
+    "media_proxy": "Proxy multimedia",
+    "scope_options": "Opzioni visibilità",
+    "text_limit": "Lunghezza massima",
+    "title": "Caratteristiche",
+    "who_to_follow": "Chi seguire"
+  },
+  "finder": {
+    "error_fetching_user": "Errore nel recupero dell'utente",
+    "find_user": "Trova utente"
+  },
+  "login": {
+    "login": "Accedi",
+    "logout": "Disconnettiti",
+    "password": "Password",
+    "placeholder": "es. Lupo Lucio",
+    "register": "Registrati",
+    "username": "Nome utente",
+    "description": "Accedi con OAuth",
+    "hint": "Accedi per partecipare alla discussione",
+    "authentication_code": "Codice di autenticazione",
+    "enter_recovery_code": "Inserisci un codice di recupero",
+    "enter_two_factor_code": "Inserisci un codice two-factor",
+    "recovery_code": "Codice di recupero",
+    "heading": {
+      "totp": "Autenticazione two-factor",
+      "recovery": "Recupero two-factor"
     }
+  },
+  "post_status": {
+    "account_not_locked_warning": "Il tuo profilo non è {0}. Chiunque può seguirti e vedere i tuoi messaggi riservati ai tuoi seguaci.",
+    "account_not_locked_warning_link": "protetto",
+    "attachments_sensitive": "Nascondi gli allegati",
+    "content_type": {
+      "text/plain": "Testo normale",
+      "text/bbcode": "BBCode",
+      "text/markdown": "Markdown",
+      "text/html": "HTML"
+    },
+    "content_warning": "Oggetto (facoltativo)",
+    "default": "Sono appena atterrato a Fiumicino.",
+    "direct_warning": "Questo post sarà visibile solo dagli utenti menzionati.",
+    "posting": "Sto pubblicando",
+    "scope": {
+      "direct": "Diretto - Visibile solo agli utenti menzionati",
+      "private": "Solo per seguaci - Visibile solo dai tuoi seguaci",
+      "public": "Pubblico - Visibile sulla sequenza pubblica",
+      "unlisted": "Non elencato - Non visibile sulla sequenza pubblica"
+    },
+    "scope_notice": {
+      "unlisted": "Questo messaggio non sarà visibile sulla sequenza locale né su quella pubblica",
+      "private": "Questo messaggio sarà visibile solo ai tuoi seguaci",
+      "public": "Questo messaggio sarà visibile a tutti"
+    },
+    "direct_warning_to_first_only": "Questo messaggio sarà visibile solo agli utenti menzionati all'inizio.",
+    "direct_warning_to_all": "Questo messaggio sarà visibile a tutti i menzionati.",
+    "new_status": "Nuovo messaggio"
+  },
+  "registration": {
+    "bio": "Introduzione",
+    "email": "Email",
+    "fullname": "Nome visualizzato",
+    "password_confirm": "Conferma password",
+    "registration": "Registrazione",
+    "token": "Codice d'invito",
+    "validations": {
+      "password_confirmation_match": "dovrebbe essere uguale alla password",
+      "password_confirmation_required": "non può essere vuoto",
+      "password_required": "non può essere vuoto",
+      "email_required": "non può essere vuoto",
+      "fullname_required": "non può essere vuoto",
+      "username_required": "non può essere vuoto"
+    },
+    "bio_placeholder": "es.\nCiao, sono Lupo Lucio.\nSono un lupo fantastico che vive nel Fantabosco. Forse mi hai visto alla Melevisione.",
+    "fullname_placeholder": "es. Lupo Lucio",
+    "username_placeholder": "es. mister_wolf",
+    "new_captcha": "Clicca l'immagine per avere un altro captcha",
+    "captcha": "CAPTCHA"
+  },
+  "user_profile": {
+    "timeline_title": "Sequenza dell'Utente"
+  },
+  "who_to_follow": {
+    "more": "Altro",
+    "who_to_follow": "Chi seguire"
+  },
+  "about": {
+    "mrf": {
+      "federation": "Federazione",
+      "keyword": {
+        "reject": "Rifiuta",
+        "replace": "Sostituisci",
+        "is_replaced_by": "→",
+        "keyword_policies": "Regole per parole chiave",
+        "ftl_removal": "Rimozione dalla sequenza globale"
+      },
+      "simple": {
+        "reject": "Rifiuta",
+        "accept": "Accetta",
+        "simple_policies": "Regole specifiche alla stanza",
+        "accept_desc": "Questa stanza accetta messaggi solo dalle seguenti stanze:",
+        "reject_desc": "Questa stanza non accetterà messaggi dalle stanze seguenti:",
+        "quarantine": "Quarantena",
+        "quarantine_desc": "Questa stanza inoltrerà solo messaggi pubblici alle seguenti stanze:",
+        "ftl_removal": "Rimozione dalla sequenza globale",
+        "ftl_removal_desc": "Questa stanza rimuove le seguenti stanze dalla sequenza globale:",
+        "media_removal": "Rimozione multimedia",
+        "media_removal_desc": "Questa istanza rimuove gli allegati dalle seguenti stanze:",
+        "media_nsfw": "Allegati oscurati forzatamente",
+        "media_nsfw_desc": "Questa stanza oscura gli allegati dei messaggi provenienti da queste stanze:"
+      },
+      "mrf_policies": "Regole RM abilitate",
+      "mrf_policies_desc": "Le regole RM cambiano il comportamento federativo della stanza. Vigono le seguenti regole:"
+    },
+    "staff": "Equipaggio"
+  },
+  "domain_mute_card": {
+    "mute": "Zittisci",
+    "mute_progress": "Zittisco…",
+    "unmute": "Ascolta",
+    "unmute_progress": "Procedo…"
+  },
+  "exporter": {
+    "export": "Esporta",
+    "processing": "In elaborazione, il tuo file sarà scaricabile a breve"
+  },
+  "image_cropper": {
+    "crop_picture": "Ritaglia immagine",
+    "save": "Salva",
+    "save_without_cropping": "Salva senza ritagliare",
+    "cancel": "Annulla"
+  },
+  "importer": {
+    "submit": "Invia",
+    "success": "Importato.",
+    "error": "L'importazione non è andata a buon fine."
+  },
+  "media_modal": {
+    "previous": "Precedente",
+    "next": "Prossimo"
+  },
+  "polls": {
+    "add_poll": "Sondaggio",
+    "add_option": "Alternativa",
+    "option": "Opzione",
+    "votes": "voti",
+    "vote": "Vota",
+    "type": "Tipo di sondaggio",
+    "single_choice": "Scelta singola",
+    "multiple_choices": "Scelta multipla",
+    "expiry": "Scadenza",
+    "expires_in": "Scade fra {0}",
+    "expired": "Scaduto {0} fa",
+    "not_enough_options": "Aggiungi altre risposte"
+  },
+  "interactions": {
+    "favs_repeats": "Condivisi e preferiti",
+    "load_older": "Carica vecchie interazioni",
+    "moves": "Utenti migrati",
+    "follows": "Nuovi seguìti"
+  },
+  "emoji": {
+    "load_all": "Carico tutti i {emojiAmount} emoji",
+    "load_all_hint": "Primi {saneAmount} emoji caricati, caricarli tutti potrebbe causare rallentamenti.",
+    "unicode": "Emoji Unicode",
+    "custom": "Emoji personale",
+    "add_emoji": "Inserisci Emoji",
+    "search_emoji": "Cerca un emoji",
+    "keep_open": "Tieni aperto il menù",
+    "emoji": "Emoji",
+    "stickers": "Adesivi"
+  },
+  "selectable_list": {
+    "select_all": "Seleziona tutto"
+  },
+  "remote_user_resolver": {
+    "error": "Non trovato.",
+    "searching_for": "Cerco",
+    "remote_user_resolver": "Cerca utenti remoti"
+  }
 }
diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index 243e9ab9..af728b6e 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -1,747 +1,747 @@
 {
-    "chat": {
-        "title": "Chat"
-    },
-    "features_panel": {
-        "chat": "Chat",
-        "gopher": "Gopher",
-        "media_proxy": "Media proxy",
-        "scope_options": "Zichtbaarheidsopties",
-        "text_limit": "Tekst limiet",
-        "title": "Kenmerken",
-        "who_to_follow": "Wie te volgen"
-    },
-    "finder": {
-        "error_fetching_user": "Fout tijdens ophalen gebruiker",
-        "find_user": "Gebruiker zoeken"
-    },
-    "general": {
-        "apply": "Toepassen",
-        "submit": "Verzend",
-        "more": "Meer",
-        "optional": "optioneel",
-        "show_more": "Bekijk meer",
-        "show_less": "Bekijk minder",
-        "dismiss": "Opheffen",
-        "cancel": "Annuleren",
-        "disable": "Uitschakelen",
-        "enable": "Inschakelen",
-        "confirm": "Bevestigen",
-        "verify": "Verifiëren",
-        "generic_error": "Er is een fout opgetreden"
-    },
-    "login": {
-        "login": "Log in",
-        "description": "Log in met OAuth",
-        "logout": "Uitloggen",
-        "password": "Wachtwoord",
-        "placeholder": "bijv. lain",
-        "register": "Registreren",
-        "username": "Gebruikersnaam",
-        "hint": "Log in om deel te nemen aan de discussie",
-        "authentication_code": "Authenticatie code",
-        "enter_recovery_code": "Voer een herstelcode in",
-        "enter_two_factor_code": "Voer een twee-factor code in",
-        "recovery_code": "Herstelcode",
-        "heading": {
-            "totp": "Twee-factor authenticatie",
-            "recovery": "Twee-factor herstelling"
-        }
-    },
-    "nav": {
-        "about": "Over",
-        "back": "Terug",
-        "chat": "Lokale Chat",
-        "friend_requests": "Volgverzoeken",
-        "mentions": "Vermeldingen",
-        "dms": "Directe Berichten",
-        "public_tl": "Publieke Tijdlijn",
-        "timeline": "Tijdlijn",
-        "twkn": "Het Geheel Bekende Netwerk",
-        "user_search": "Gebruiker Zoeken",
-        "who_to_follow": "Wie te volgen",
-        "preferences": "Voorkeuren",
-        "administration": "Administratie",
-        "search": "Zoeken",
-        "interactions": "Interacties"
-    },
-    "notifications": {
-        "broken_favorite": "Onbekende status, aan het zoeken…",
-        "favorited_you": "vond je status leuk",
-        "followed_you": "volgt jou",
-        "load_older": "Laad oudere meldingen",
-        "notifications": "Meldingen",
-        "read": "Gelezen!",
-        "repeated_you": "Herhaalde je status",
-        "no_more_notifications": "Geen meldingen meer",
-        "migrated_to": "is gemigreerd naar",
-        "follow_request": "wil je volgen",
-        "reacted_with": "reageerde met {0}"
-    },
-    "post_status": {
-        "new_status": "Nieuwe status plaatsen",
-        "account_not_locked_warning": "Je account is niet {0}. Iedereen kan je volgen om je alleen-volgers berichten te lezen.",
-        "account_not_locked_warning_link": "gesloten",
-        "attachments_sensitive": "Markeer bijlagen als gevoelig",
-        "content_type": {
-            "text/plain": "Platte tekst",
-            "text/html": "HTML",
-            "text/markdown": "Markdown",
-            "text/bbcode": "BBCode"
-        },
-        "content_warning": "Onderwerp (optioneel)",
-        "default": "Zojuist geland in L.A.",
-        "direct_warning": "Deze post zal enkel zichtbaar zijn voor de personen die genoemd zijn.",
-        "posting": "Plaatsen",
-        "scope": {
-            "direct": "Direct - Post enkel naar vermelde gebruikers",
-            "private": "Enkel volgers - Post enkel naar volgers",
-            "public": "Publiek - Post op publieke tijdlijnen",
-            "unlisted": "Niet Vermelden - Niet tonen op publieke tijdlijnen"
-        },
-        "direct_warning_to_all": "Dit bericht zal zichtbaar zijn voor alle vermelde gebruikers.",
-        "direct_warning_to_first_only": "Dit bericht zal alleen zichtbaar zijn voor de vermelde gebruikers aan het begin van het bericht.",
-        "scope_notice": {
-            "public": "Dit bericht zal voor iedereen zichtbaar zijn",
-            "unlisted": "Dit bericht zal niet zichtbaar zijn in de Publieke Tijdlijn en Het Geheel Bekende Netwerk",
-            "private": "Dit bericht zal voor alleen je volgers zichtbaar zijn"
-        }
-    },
-    "registration": {
-        "bio": "Bio",
-        "email": "Email",
-        "fullname": "Weergave naam",
-        "password_confirm": "Wachtwoord bevestiging",
-        "registration": "Registratie",
-        "token": "Uitnodigings-token",
-        "captcha": "CAPTCHA",
-        "new_captcha": "Klik op de afbeelding voor een nieuwe captcha",
-        "validations": {
-            "username_required": "moet ingevuld zijn",
-            "fullname_required": "moet ingevuld zijn",
-            "email_required": "moet ingevuld zijn",
-            "password_required": "moet ingevuld zijn",
-            "password_confirmation_required": "moet ingevuld zijn",
-            "password_confirmation_match": "komt niet overeen met het wachtwoord"
-        },
-        "username_placeholder": "bijv. lain",
-        "fullname_placeholder": "bijv. Lain Iwakura",
-        "bio_placeholder": "bijv.\nHallo, ik ben Lain.\nIk ben een anime meisje woonachtig in een buitenwijk in Japan. Je kent me misschien van the Wired."
-    },
-    "settings": {
-        "attachmentRadius": "Bijlages",
-        "attachments": "Bijlages",
-        "autoload": "Automatisch laden inschakelen wanneer tot de bodem gescrold wordt",
-        "avatar": "Avatar",
-        "avatarAltRadius": "Avatars (Meldingen)",
-        "avatarRadius": "Avatars",
-        "background": "Achtergrond",
-        "bio": "Bio",
-        "btnRadius": "Knoppen",
-        "cBlue": "Blauw (Beantwoorden, volgen)",
-        "cGreen": "Groen (Herhalen)",
-        "cOrange": "Oranje (Favoriet)",
-        "cRed": "Rood (Annuleren)",
-        "change_password": "Wachtwoord Wijzigen",
-        "change_password_error": "Er is een fout opgetreden bij het wijzigen van je wachtwoord.",
-        "changed_password": "Wachtwoord succesvol gewijzigd!",
-        "collapse_subject": "Klap berichten met een onderwerp in",
-        "composing": "Opstellen",
-        "confirm_new_password": "Nieuw wachtwoord bevestigen",
-        "current_avatar": "Je huidige avatar",
-        "current_password": "Huidig wachtwoord",
-        "current_profile_banner": "Je huidige profiel banner",
-        "data_import_export_tab": "Data Import / Export",
-        "default_vis": "Standaard zichtbaarheidsbereik",
-        "delete_account": "Account Verwijderen",
-        "delete_account_description": "Permanent je gegevens verwijderen en account deactiveren.",
-        "delete_account_error": "Er is een fout opgetreden bij het verwijderen van je account. Indien dit probleem zich voor blijft doen, neem dan contact op met de beheerder van deze instantie.",
-        "delete_account_instructions": "Voer je wachtwoord in het onderstaande invoerveld in om het verwijderen van je account te bevestigen.",
-        "export_theme": "Preset opslaan",
-        "filtering": "Filtering",
-        "filtering_explanation": "Alle statussen die deze woorden bevatten worden genegeerd, één filter per lijn",
-        "follow_export": "Volgers exporteren",
-        "follow_export_button": "Exporteer je volgers naar een csv bestand",
-        "follow_export_processing": "Aan het verwerken, binnen enkele ogenblikken wordt je gevraagd je bestand te downloaden",
-        "follow_import": "Volgers importeren",
-        "follow_import_error": "Fout bij importeren volgers",
-        "follows_imported": "Volgers geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
-        "foreground": "Voorgrond",
-        "general": "Algemeen",
-        "hide_attachments_in_convo": "Verberg bijlages in conversaties",
-        "hide_attachments_in_tl": "Verberg bijlages in de tijdlijn",
-        "hide_isp": "Verberg instantie-specifiek paneel",
-        "preload_images": "Afbeeldingen vooraf laden",
-        "hide_post_stats": "Verberg bericht statistieken (bijv. het aantal favorieten)",
-        "hide_user_stats": "Verberg bericht statistieken (bijv. het aantal volgers)",
-        "import_followers_from_a_csv_file": "Importeer volgers uit een csv bestand",
-        "import_theme": "Preset laden",
-        "inputRadius": "Invoervelden",
-        "checkboxRadius": "Checkboxen",
-        "instance_default": "(standaard: {value})",
-        "instance_default_simple": "(standaard)",
-        "interface": "Interface",
-        "interfaceLanguage": "Interface taal",
-        "invalid_theme_imported": "Het geselecteerde bestand is geen door Pleroma ondersteund thema. Er zijn geen aanpassingen gedaan.",
-        "limited_availability": "Niet beschikbaar in je browser",
-        "links": "Links",
-        "lock_account_description": "Laat volgers enkel toe na expliciete toestemming",
-        "loop_video": "Herhaal video's",
-        "loop_video_silent_only": "Herhaal enkel video's zonder geluid (bijv. Mastodon's \"gifs\")",
-        "name": "Naam",
-        "name_bio": "Naam & Bio",
-        "new_password": "Nieuw wachtwoord",
-        "notification_visibility": "Type meldingen die getoond worden",
-        "notification_visibility_follows": "Volgingen",
-        "notification_visibility_likes": "Vind-ik-leuks",
-        "notification_visibility_mentions": "Vermeldingen",
-        "notification_visibility_repeats": "Herhalingen",
-        "no_rich_text_description": "Verwijder rich text formattering van alle berichten",
-        "hide_network_description": "Toon niet wie mij volgt en wie ik volg.",
-        "nsfw_clickthrough": "Doorklikbaar verbergen van gevoelige bijlages inschakelen",
-        "oauth_tokens": "OAuth-tokens",
-        "token": "Token",
-        "refresh_token": "Token Vernieuwen",
-        "valid_until": "Geldig tot",
-        "revoke_token": "Intrekken",
-        "panelRadius": "Panelen",
-        "pause_on_unfocused": "Streamen pauzeren wanneer de tab niet in focus is",
-        "presets": "Presets",
-        "profile_background": "Profiel Achtergrond",
-        "profile_banner": "Profiel Banner",
-        "profile_tab": "Profiel",
-        "radii_help": "Stel afronding van hoeken in de interface in (in pixels)",
-        "replies_in_timeline": "Antwoorden in tijdlijn",
-        "reply_link_preview": "Antwoord-link weergave inschakelen bij aanwijzen met muisaanwijzer",
-        "reply_visibility_all": "Alle antwoorden tonen",
-        "reply_visibility_following": "Enkel antwoorden tonen die aan mij of gevolgde gebruikers gericht zijn",
-        "reply_visibility_self": "Enkel antwoorden tonen die aan mij gericht zijn",
-        "saving_err": "Fout tijdens opslaan van instellingen",
-        "saving_ok": "Instellingen opgeslagen",
-        "security_tab": "Beveiliging",
-        "scope_copy": "Neem bereik over bij beantwoorden (Directe Berichten blijven altijd Direct)",
-        "set_new_avatar": "Nieuwe avatar instellen",
-        "set_new_profile_background": "Nieuwe profiel achtergrond instellen",
-        "set_new_profile_banner": "Nieuwe profiel banner instellen",
-        "settings": "Instellingen",
-        "subject_input_always_show": "Altijd onderwerpveld tonen",
-        "subject_line_behavior": "Onderwerp kopiëren bij antwoorden",
-        "subject_line_email": "Zoals email: \"re: onderwerp\"",
-        "subject_line_mastodon": "Zoals mastodon: kopieer zoals het is",
-        "subject_line_noop": "Niet kopiëren",
-        "stop_gifs": "GIFs afspelen bij zweven",
-        "streaming": "Automatisch streamen van nieuwe berichten inschakelen wanneer tot boven gescrold is",
-        "text": "Tekst",
-        "theme": "Thema",
-        "theme_help": "Gebruik hex color codes (#rrggbb) om je kleurschema te wijzigen.",
-        "theme_help_v2_1": "Je kan ook de kleur en transparantie van bepaalde componenten overschrijven door de checkbox aan te vinken, gebruik de \"Alles wissen\" knop om alle overschrijvingen te annuleren.",
-        "theme_help_v2_2": "Iconen onder sommige onderdelen zijn achtergrond/tekst contrast indicatoren, zweef er over voor gedetailleerde info. Hou er rekening mee dat bij doorzichtigheid de ergst mogelijke situatie wordt weer gegeven.",
-        "tooltipRadius": "Tooltips/alarmen",
-        "user_settings": "Gebruikersinstellingen",
-        "values": {
-            "false": "nee",
-            "true": "ja"
-        },
-        "notifications": "Meldingen",
-        "enable_web_push_notifications": "Web push meldingen inschakelen",
-        "style": {
-            "switcher": {
-                "keep_color": "Kleuren behouden",
-                "keep_shadows": "Schaduwen behouden",
-                "keep_opacity": "Transparantie behouden",
-                "keep_roundness": "Rondingen behouden",
-                "keep_fonts": "Lettertypes behouden",
-                "save_load_hint": "\"Behoud\" opties behouden de momenteel ingestelde opties bij het selecteren of laden van thema's, maar slaan ook de genoemde opties op bij het exporteren van een thema. Wanneer alle selectievakjes zijn uitgeschakeld, zal het exporteren van thema's alles opslaan.",
-                "reset": "Reset",
-                "clear_all": "Alles wissen",
-                "clear_opacity": "Transparantie wissen",
-                "keep_as_is": "Hou zoals het is",
-                "use_snapshot": "Oude versie",
-                "use_source": "Nieuwe versie",
-                "help": {
-                    "future_version_imported": "Het geïmporteerde bestand is gemaakt voor een nieuwere versie van FE.",
-                    "older_version_imported": "Het geïmporteerde bestand is gemaakt voor een oudere versie van FE.",
-                    "upgraded_from_v2": "PleromaFE is bijgewerkt, het thema kan iets anders uitzien dan dat je gewend bent.",
-                    "v2_imported": "Het geïmporteerde bestand is gemaakt voor een oudere FE. We proberen compatibiliteit te maximaliseren, maar het kan toch voorkomen dat er inconsistenties zijn.",
-                    "snapshot_source_mismatch": "Versie conflict: waarschijnlijk was FE terug gerold en opnieuw bijgewerkt, indien je het thema aangepast hebt met de oudere versie van FE wil je waarschijnlijk de oude versie gebruiken, gebruik anders de nieuwe versie.",
-                    "migration_napshot_gone": "Voor een onduidelijke reden mist de momentopname, dus sommige dingen kunnen anders uitzien dan je gewend bent.",
-                    "migration_snapshot_ok": "Voor de zekerheid is een momentopname van het thema geladen. Je kunt proberen om de thema gegevens te laden.",
-                    "fe_downgraded": "PleromaFE's versie is terug gerold.",
-                    "fe_upgraded": "De thema-engine van PleromaFE is bijgewerkt na de versie update.",
-                    "snapshot_missing": "Het bestand bevat geen thema momentopname, dus het thema kan anders uitzien dan je oorspronkelijk bedacht had.",
-                    "snapshot_present": "Thema momentopname is geladen, alle waarden zijn overschreven. Je kunt in plaats daarvan ook de daadwerkelijke data van het thema laden."
-                },
-                "load_theme": "Thema laden"
-            },
-            "common": {
-                "color": "Kleur",
-                "opacity": "Transparantie",
-                "contrast": {
-                    "hint": "Contrast verhouding is {ratio}, {level} {context}",
-                    "level": {
-                        "aa": "voldoet aan de richtlijn van niveau AA (minimum)",
-                        "aaa": "voldoet aan de richtlijn van niveau AAA (aangeraden)",
-                        "bad": "voldoet aan geen enkele toegankelijkheidsrichtlijn"
-                    },
-                    "context": {
-                        "18pt": "voor grote (18pt+) tekst",
-                        "text": "voor tekst"
-                    }
-                }
-            },
-            "common_colors": {
-                "_tab_label": "Algemeen",
-                "main": "Algemene kleuren",
-                "foreground_hint": "Zie \"Geavanceerd\" tab voor meer gedetailleerde controle",
-                "rgbo": "Iconen, accenten, badges"
-            },
-            "advanced_colors": {
-                "_tab_label": "Geavanceerd",
-                "alert": "Alarm achtergrond",
-                "alert_error": "Fout",
-                "badge": "Badge achtergrond",
-                "badge_notification": "Meldingen",
-                "panel_header": "Paneel koptekst",
-                "top_bar": "Top balk",
-                "borders": "Randen",
-                "buttons": "Knoppen",
-                "inputs": "Invoervelden",
-                "faint_text": "Vervaagde tekst",
-                "tabs": "Tabbladen",
-                "toggled": "Geschakeld",
-                "disabled": "Uitgeschakeld",
-                "selectedMenu": "Geselecteerd menu item",
-                "selectedPost": "Geselecteerd bericht",
-                "pressed": "Ingedrukt",
-                "highlight": "Gemarkeerde elementen",
-                "icons": "Iconen",
-                "poll": "Poll grafiek",
-                "underlay": "Onderlaag",
-                "popover": "Tooltips, menu's, popovers",
-                "post": "Berichten / Gebruiker bios",
-                "alert_neutral": "Neutraal",
-                "alert_warning": "Waarschuwing"
-            },
-            "radii": {
-                "_tab_label": "Rondheid"
-            },
-            "shadows": {
-                "_tab_label": "Schaduw en belichting",
-                "component": "Onderdeel",
-                "override": "Overschrijven",
-                "shadow_id": "Schaduw #{value}",
-                "blur": "Vervagen",
-                "spread": "Spreiding",
-                "inset": "Inzet",
-                "hint": "Voor schaduw kan je ook --variable gebruiken als een kleur waarde om CSS3 variabelen te gebruiken. Houd er rekening mee dat het instellen van opaciteit in dit geval niet werkt.",
-                "filter_hint": {
-                    "always_drop_shadow": "Waarschuwing, deze schaduw gebruikt altijd {0} als de browser dit ondersteund.",
-                    "drop_shadow_syntax": "{0} ondersteund niet de {1} parameter en {2} sleutelwoord.",
-                    "avatar_inset": "Houdt er rekening mee dat het combineren van zowel inzet and niet-inzet schaduwen op transparante avatars onverwachte resultaten kan opleveren.",
-                    "spread_zero": "Schaduw met spreiding > 0 worden weergegeven alsof ze op nul staan",
-                    "inset_classic": "Inzet schaduw zal {0} gebruiken"
-                },
-                "components": {
-                    "panel": "Paneel",
-                    "panelHeader": "Paneel koptekst",
-                    "topBar": "Top balk",
-                    "avatar": "Gebruikers avatar (in profiel weergave)",
-                    "avatarStatus": "Gebruikers avatar (in bericht weergave)",
-                    "popup": "Popups en tooltips",
-                    "button": "Knop",
-                    "buttonHover": "Knop (zweven)",
-                    "buttonPressed": "Knop (ingedrukt)",
-                    "buttonPressedHover": "Knop (ingedrukt+zweven)",
-                    "input": "Invoerveld"
-                },
-                "hintV3": "Voor schaduwen kun je ook de {0} notatie gebruiken om de andere kleur invoer te gebruiken."
-            },
-            "fonts": {
-                "_tab_label": "Lettertypes",
-                "help": "Selecteer het lettertype om te gebruiken voor elementen van de UI. Voor \"aangepast\" dien je de exacte naam van het lettertype in te voeren zoals die in het systeem wordt weergegeven.",
-                "components": {
-                    "interface": "Interface",
-                    "input": "Invoervelden",
-                    "post": "Bericht tekst",
-                    "postCode": "Monospaced tekst in een bericht (rich text)"
-                },
-                "family": "Lettertype naam",
-                "size": "Grootte (in px)",
-                "weight": "Gewicht (dikgedruktheid)",
-                "custom": "Aangepast"
-            },
-            "preview": {
-                "header": "Voorvertoning",
-                "content": "Inhoud",
-                "error": "Voorbeeld fout",
-                "button": "Knop",
-                "text": "Nog een boel andere {0} en {1}",
-                "mono": "inhoud",
-                "input": "Zojuist geland in L.A.",
-                "faint_link": "handige gebruikershandleiding",
-                "fine_print": "Lees onze {0} om niets nuttig te leren!",
-                "header_faint": "Alles komt goed",
-                "checkbox": "Ik heb de gebruikersvoorwaarden gelezen",
-                "link": "een leuke kleine link"
-            }
-        },
-        "notification_setting_follows": "Gebruikers die je volgt",
-        "notification_setting_non_follows": "Gebruikers die je niet volgt",
-        "notification_setting_followers": "Gebruikers die je volgen",
-        "notification_setting_privacy": "Privacy",
-        "notification_setting_privacy_option": "Verberg de afzender en inhoud van push meldingen",
-        "notification_mutes": "Om niet langer meldingen te ontvangen van een specifieke gebruiker, kun je deze negeren.",
-        "app_name": "App naam",
-        "security": "Beveiliging",
-        "enter_current_password_to_confirm": "Voer je huidige wachtwoord in om je identiteit te bevestigen",
-        "mfa": {
-            "otp": "OTP",
-            "setup_otp": "OTP instellen",
-            "wait_pre_setup_otp": "OTP voorinstellen",
-            "confirm_and_enable": "Bevestig en schakel OTP in",
-            "title": "Twee-factor Authenticatie",
-            "generate_new_recovery_codes": "Genereer nieuwe herstelcodes",
-            "recovery_codes": "Herstelcodes.",
-            "waiting_a_recovery_codes": "Backup codes ontvangen…",
-            "authentication_methods": "Authenticatie methodes",
-            "scan": {
-                "title": "Scannen",
-                "desc": "Scan de QR code of voer een sleutel in met je twee-factor applicatie:",
-                "secret_code": "Sleutel"
-            },
-            "verify": {
-                "desc": "Voer de code van je twee-factor applicatie in om twee-factor authenticatie in te schakelen:"
-            },
-            "warning_of_generate_new_codes": "Wanneer je nieuwe herstelcodes genereert, zullen je oude code niet langer werken.",
-            "recovery_codes_warning": "Schrijf de codes op of sla ze op een veilige locatie op - anders kun je ze niet meer inzien. Als je toegang tot je 2FA app en herstelcodes verliest, zal je buitengesloten zijn uit je account."
-        },
-        "allow_following_move": "Automatisch volgen toestaan wanneer een gevolgd account migreert",
-        "block_export": "Blokkades exporteren",
-        "block_import": "Blokkades importeren",
-        "blocks_imported": "Blokkades geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
-        "blocks_tab": "Blokkades",
-        "change_email": "Email wijzigen",
-        "change_email_error": "Er is een fout opgetreden tijdens het wijzigen van je email.",
-        "changed_email": "Email succesvol gewijzigd!",
-        "domain_mutes": "Domeinen",
-        "avatar_size_instruction": "De aangeraden minimale afmeting voor avatar afbeeldingen is 150x150 pixels.",
-        "pad_emoji": "Vul emoji aan met spaties wanneer deze met de picker ingevoegd worden",
-        "emoji_reactions_on_timeline": "Toon emoji reacties op de tijdlijn",
-        "accent": "Accent",
-        "hide_muted_posts": "Verberg berichten van genegeerde gebruikers",
-        "max_thumbnails": "Maximaal aantal miniaturen per bericht",
-        "use_one_click_nsfw": "Open gevoelige bijlagen met slechts één klik",
-        "hide_filtered_statuses": "Gefilterde statussen verbergen",
-        "import_blocks_from_a_csv_file": "Importeer blokkades van een csv bestand",
-        "mutes_tab": "Negeringen",
-        "play_videos_in_modal": "Speel video's af in een popup frame",
-        "new_email": "Nieuwe Email",
-        "notification_visibility_emoji_reactions": "Reacties",
-        "no_blocks": "Geen blokkades",
-        "no_mutes": "Geen negeringen",
-        "hide_followers_description": "Niet tonen wie mij volgt",
-        "hide_followers_count_description": "Niet mijn volgers aantal tonen",
-        "hide_follows_count_description": "Niet mijn gevolgde aantal tonen",
-        "show_admin_badge": "Beheerders badge tonen in mijn profiel",
-        "autohide_floating_post_button": "Nieuw Bericht knop automatisch verbergen (mobiel)",
-        "search_user_to_block": "Zoek wie je wilt blokkeren",
-        "search_user_to_mute": "Zoek wie je wilt negeren",
-        "minimal_scopes_mode": "Bericht bereik-opties minimaliseren",
-        "post_status_content_type": "Bericht status content type",
-        "user_mutes": "Gebruikers",
-        "useStreamingApi": "Berichten en meldingen in real-time ontvangen",
-        "useStreamingApiWarning": "(Afgeraden, experimenteel, kan berichten overslaan)",
-        "type_domains_to_mute": "Voer domeinen in om te negeren",
-        "upload_a_photo": "Upload een foto",
-        "fun": "Plezier",
-        "greentext": "Meme pijlen",
-        "notification_setting": "Ontvang meldingen van:",
-        "block_export_button": "Exporteer je geblokkeerde gebruikers naar een csv bestand",
-        "block_import_error": "Fout bij importeren blokkades",
-        "discoverable": "Sta toe dat dit account ontdekt kan worden in zoekresultaten en andere diensten",
-        "use_contain_fit": "Snij bijlage in miniaturen niet bij",
-        "notification_visibility_moves": "Gebruiker Migraties",
-        "hide_follows_description": "Niet tonen wie ik volg",
-        "show_moderator_badge": "Moderators badge tonen in mijn profiel",
-        "notification_setting_filters": "Filters",
-        "notification_setting_non_followers": "Gebruikers die je niet volgen",
-        "notification_blocks": "Door een gebruiker te blokkeren, ontvang je geen meldingen meer van de gebruiker en wordt je abonnement op de gebruiker opgeheven.",
-        "version": {
-            "frontend_version": "Frontend Versie",
-            "backend_version": "Backend Versie",
-            "title": "Versie"
-        }
-    },
-    "timeline": {
-        "collapse": "Inklappen",
-        "conversation": "Conversatie",
-        "error_fetching": "Fout bij ophalen van updates",
-        "load_older": "Oudere statussen laden",
-        "no_retweet_hint": "Bericht is gemarkeerd als enkel volgers of direct en kan niet worden herhaald",
-        "repeated": "herhaalde",
-        "show_new": "Nieuwe tonen",
-        "up_to_date": "Up-to-date",
-        "no_statuses": "Geen statussen",
-        "no_more_statuses": "Geen statussen meer"
-    },
-    "user_card": {
-        "approve": "Goedkeuren",
-        "block": "Blokkeren",
-        "blocked": "Geblokkeerd!",
-        "deny": "Weigeren",
-        "favorites": "Favorieten",
-        "follow": "Volgen",
-        "follow_sent": "Aanvraag verzonden!",
-        "follow_progress": "Aanvragen…",
-        "follow_again": "Aanvraag opnieuw zenden?",
-        "follow_unfollow": "Stop volgen",
-        "followees": "Aan het volgen",
-        "followers": "Volgers",
-        "following": "Aan het volgen!",
-        "follows_you": "Volgt jou!",
-        "its_you": "'t is jij!",
-        "mute": "Negeren",
-        "muted": "Genegeerd",
-        "per_day": "per dag",
-        "remote_follow": "Volg vanop afstand",
-        "statuses": "Statussen",
-        "admin_menu": {
-            "delete_user_confirmation": "Weet je het heel zeker? Deze uitvoering kan niet ongedaan worden gemaakt.",
-            "delete_user": "Gebruiker verwijderen",
-            "quarantine": "Federeren van gebruikers berichten verbieden",
-            "disable_any_subscription": "Volgen van gebruiker in zijn geheel verbieden",
-            "disable_remote_subscription": "Volgen van gebruiker vanaf andere instanties verbieden",
-            "sandbox": "Berichten forceren om alleen voor volgers zichtbaar te zijn",
-            "force_unlisted": "Berichten forceren om niet publiekelijk getoond te worden",
-            "strip_media": "Media van berichten verwijderen",
-            "force_nsfw": "Alle berichten als gevoelig markeren",
-            "delete_account": "Account verwijderen",
-            "deactivate_account": "Account deactiveren",
-            "activate_account": "Account activeren",
-            "revoke_moderator": "Moderatorsrechten intrekken",
-            "grant_moderator": "Moderatorsrechten toekennen",
-            "revoke_admin": "Beheerdersrechten intrekken",
-            "grant_admin": "Beheerdersrechten toekennen",
-            "moderation": "Moderatie"
-        },
-        "show_repeats": "Herhalingen tonen",
-        "hide_repeats": "Herhalingen verbergen",
-        "mute_progress": "Negeren…",
-        "unmute_progress": "Negering opheffen…",
-        "unmute": "Negering opheffen",
-        "block_progress": "Blokkeren…",
-        "unblock_progress": "Blokkade opheffen…",
-        "unblock": "Blokkade opheffen",
-        "unsubscribe": "Abonnement opzeggen",
-        "subscribe": "Abonneren",
-        "report": "Aangeven",
-        "mention": "Vermelding",
-        "media": "Media",
-        "hidden": "Verborgen"
-    },
-    "user_profile": {
-        "timeline_title": "Gebruikers Tijdlijn",
-        "profile_loading_error": "Sorry, er is een fout opgetreden bij het laden van dit profiel.",
-        "profile_does_not_exist": "Sorry, dit profiel bestaat niet."
-    },
-    "who_to_follow": {
-        "more": "Meer",
-        "who_to_follow": "Wie te volgen"
-    },
-    "tool_tip": {
-        "media_upload": "Media Uploaden",
-        "repeat": "Herhalen",
-        "reply": "Beantwoorden",
-        "favorite": "Favoriet maken",
-        "user_settings": "Gebruikers Instellingen",
-        "reject_follow_request": "Volg-verzoek afwijzen",
-        "accept_follow_request": "Volg-aanvraag accepteren",
-        "add_reaction": "Reactie toevoegen"
-    },
-    "upload": {
-        "error": {
-            "base": "Upload mislukt.",
-            "file_too_big": "Bestand is te groot [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-            "default": "Probeer het later opnieuw"
-        },
-        "file_size_units": {
-            "B": "B",
-            "KiB": "KiB",
-            "MiB": "MiB",
-            "GiB": "GiB",
-            "TiB": "TiB"
-        }
-    },
-    "about": {
-        "mrf": {
-            "federation": "Federatie",
-            "keyword": {
-                "reject": "Afwijzen",
-                "replace": "Vervangen",
-                "is_replaced_by": "→",
-                "keyword_policies": "Zoekwoord Beleid",
-                "ftl_removal": "Verwijdering van \"Het Geheel Bekende Netwerk\" Tijdlijn"
-            },
-            "mrf_policies_desc": "MRF regels beïnvloeden het federatie gedrag van de instantie. De volgende regels zijn ingeschakeld:",
-            "mrf_policies": "Ingeschakelde MRF Regels",
-            "simple": {
-                "simple_policies": "Instantie-specifieke Regels",
-                "accept": "Accepteren",
-                "accept_desc": "Deze instantie accepteert alleen berichten van de volgende instanties:",
-                "reject": "Afwijzen",
-                "reject_desc": "Deze instantie zal geen berichten accepteren van de volgende instanties:",
-                "quarantine": "Quarantaine",
-                "quarantine_desc": "Deze instantie zal alleen publieke berichten sturen naar de volgende instanties:",
-                "ftl_removal_desc": "Deze instantie verwijdert de volgende instanties van \"Het Geheel Bekende Netwerk\" tijdlijn:",
-                "media_removal_desc": "Deze instantie verwijdert media van berichten van de volgende instanties:",
-                "media_nsfw_desc": "Deze instantie stelt media in als gevoelig in berichten van de volgende instanties:",
-                "ftl_removal": "Verwijderen van \"Het Geheel Bekende Netwerk\" Tijdlijn",
-                "media_removal": "Media Verwijdering",
-                "media_nsfw": "Forceer Media als Gevoelig"
-            }
-        },
-        "staff": "Personeel"
-    },
-    "domain_mute_card": {
-        "mute": "Negeren",
-        "mute_progress": "Negeren…",
-        "unmute": "Negering opheffen",
-        "unmute_progress": "Negering wordt opgeheven…"
-    },
-    "exporter": {
-        "export": "Exporteren",
-        "processing": "Verwerken, er wordt zo gevraagd om je bestand te downloaden"
-    },
-    "image_cropper": {
-        "save": "Opslaan",
-        "save_without_cropping": "Opslaan zonder bijsnijden",
-        "cancel": "Annuleren",
-        "crop_picture": "Afbeelding bijsnijden"
-    },
-    "importer": {
-        "submit": "Verzenden",
-        "success": "Succesvol geïmporteerd.",
-        "error": "Er is een fout opgetreden bij het importeren van dit bestand."
-    },
-    "media_modal": {
-        "previous": "Vorige",
-        "next": "Volgende"
-    },
-    "polls": {
-        "add_poll": "Poll Toevoegen",
-        "add_option": "Optie Toevoegen",
-        "option": "Optie",
-        "votes": "stemmen",
-        "vote": "Stem",
-        "single_choice": "Enkele keuze",
-        "multiple_choices": "Meerkeuze",
-        "expiry": "Poll leeftijd",
-        "expires_in": "Poll eindigt in {0}",
-        "expired": "Poll is {0} geleden beëindigd",
-        "not_enough_options": "Te weinig opties in poll",
-        "type": "Poll type"
-    },
-    "emoji": {
-        "emoji": "Emoji",
-        "keep_open": "Picker openhouden",
-        "search_emoji": "Zoek voor een emoji",
-        "add_emoji": "Emoji invoegen",
-        "unicode": "Unicode emoji",
-        "load_all": "Alle {emojiAmount} emoji worden geladen",
-        "stickers": "Stickers",
-        "load_all_hint": "Eerste {saneAmount} emoji geladen, alle emoji tegelijk laden kan problemen veroorzaken met prestaties.",
-        "custom": "Gepersonaliseerde emoji"
-    },
-    "interactions": {
-        "favs_repeats": "Herhalingen en Favorieten",
-        "follows": "Nieuwe volgingen",
-        "moves": "Gebruiker migreert",
-        "load_older": "Oudere interacties laden"
-    },
-    "remote_user_resolver": {
-        "searching_for": "Zoeken naar",
-        "error": "Niet gevonden.",
-        "remote_user_resolver": "Externe gebruikers zoeker"
-    },
-    "selectable_list": {
-        "select_all": "Alles selecteren"
-    },
-    "password_reset": {
-        "password_reset_required_but_mailer_is_disabled": "Je dient je wachtwoord opnieuw in te stellen, maar wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
-        "password_reset_required": "Je dient je wachtwoord opnieuw in te stellen om in te kunnen loggen.",
-        "password_reset_disabled": "Wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
-        "too_many_requests": "Je hebt het maximaal aantal pogingen bereikt, probeer het later opnieuw.",
-        "not_found": "We kunnen die email of gebruikersnaam niet vinden.",
-        "return_home": "Terugkeren naar de home pagina",
-        "check_email": "Controleer je email inbox voor een link om je wachtwoord opnieuw in te stellen.",
-        "placeholder": "Je email of gebruikersnaam",
-        "instruction": "Voer je email adres of gebruikersnaam in. We sturen je een link om je wachtwoord opnieuw in te stellen.",
-        "password_reset": "Wachtwoord opnieuw instellen",
-        "forgot_password": "Wachtwoord vergeten?"
-    },
-    "search": {
-        "no_results": "Geen resultaten",
-        "people_talking": "{count} personen aan het praten",
-        "person_talking": "{count} persoon aan het praten",
-        "hashtags": "Hashtags",
-        "people": "Personen"
-    },
-    "user_reporting": {
-        "generic_error": "Er is een fout opgetreden tijdens het verwerken van je verzoek.",
-        "submit": "Verzenden",
-        "forward_to": "Doorsturen naar {0}",
-        "forward_description": "Dit account hoort bij een andere server. Wil je een kopie van het rapport ook daarheen sturen?",
-        "additional_comments": "Aanvullende opmerkingen",
-        "add_comment_description": "Het rapport zal naar de moderators van de instantie worden verstuurd. Je kunt hieronder uitleg bijvoegen waarom je dit account wilt aangeven:",
-        "title": "{0} aangeven"
-    },
-    "status": {
-        "copy_link": "Link naar status kopiëren",
-        "status_unavailable": "Status niet beschikbaar",
-        "unmute_conversation": "Conversatie niet meer negeren",
-        "mute_conversation": "Conversatie negeren",
-        "replies_list": "Antwoorden:",
-        "reply_to": "Antwoorden aan",
-        "delete_confirm": "Wil je echt deze status verwijderen?",
-        "pin": "Aan profiel vastmaken",
-        "pinned": "Vastgezet",
-        "unpin": "Van profiel losmaken",
-        "delete": "Status verwijderen",
-        "repeats": "Herhalingen",
-        "favorites": "Favorieten"
-    },
-    "time": {
-        "years_short": "{0}j",
-        "year_short": "{0}j",
-        "years": "{0} jaren",
-        "year": "{0} jaar",
-        "weeks_short": "{0}w",
-        "week_short": "{0}w",
-        "weeks": "{0} weken",
-        "week": "{0} week",
-        "seconds_short": "{0}s",
-        "second_short": "{0}s",
-        "seconds": "{0} seconden",
-        "second": "{0} seconde",
-        "now_short": "nu",
-        "now": "zojuist",
-        "months_short": "{0}ma",
-        "month_short": "{0}ma",
-        "months": "{0} maanden",
-        "month": "{0} maand",
-        "minutes_short": "{0}min",
-        "minute_short": "{0}min",
-        "minutes": "{0} minuten",
-        "minute": "{0} minuut",
-        "in_past": "{0} geleden",
-        "in_future": "over {0}",
-        "hours_short": "{0}u",
-        "hour_short": "{0}u",
-        "hours": "{0} uren",
-        "hour": "{0} uur",
-        "days_short": "{0}d",
-        "day_short": "{0}d",
-        "days": "{0} dagen",
-        "day": "{0} dag"
+  "chat": {
+    "title": "Chat"
+  },
+  "features_panel": {
+    "chat": "Chat",
+    "gopher": "Gopher",
+    "media_proxy": "Media proxy",
+    "scope_options": "Zichtbaarheidsopties",
+    "text_limit": "Tekst limiet",
+    "title": "Kenmerken",
+    "who_to_follow": "Wie te volgen"
+  },
+  "finder": {
+    "error_fetching_user": "Fout tijdens ophalen gebruiker",
+    "find_user": "Gebruiker zoeken"
+  },
+  "general": {
+    "apply": "Toepassen",
+    "submit": "Verzend",
+    "more": "Meer",
+    "optional": "optioneel",
+    "show_more": "Bekijk meer",
+    "show_less": "Bekijk minder",
+    "dismiss": "Opheffen",
+    "cancel": "Annuleren",
+    "disable": "Uitschakelen",
+    "enable": "Inschakelen",
+    "confirm": "Bevestigen",
+    "verify": "Verifiëren",
+    "generic_error": "Er is een fout opgetreden"
+  },
+  "login": {
+    "login": "Log in",
+    "description": "Log in met OAuth",
+    "logout": "Uitloggen",
+    "password": "Wachtwoord",
+    "placeholder": "bijv. lain",
+    "register": "Registreren",
+    "username": "Gebruikersnaam",
+    "hint": "Log in om deel te nemen aan de discussie",
+    "authentication_code": "Authenticatie code",
+    "enter_recovery_code": "Voer een herstelcode in",
+    "enter_two_factor_code": "Voer een twee-factor code in",
+    "recovery_code": "Herstelcode",
+    "heading": {
+      "totp": "Twee-factor authenticatie",
+      "recovery": "Twee-factor herstelling"
     }
+  },
+  "nav": {
+    "about": "Over",
+    "back": "Terug",
+    "chat": "Lokale Chat",
+    "friend_requests": "Volgverzoeken",
+    "mentions": "Vermeldingen",
+    "dms": "Directe Berichten",
+    "public_tl": "Publieke Tijdlijn",
+    "timeline": "Tijdlijn",
+    "twkn": "Het Geheel Bekende Netwerk",
+    "user_search": "Gebruiker Zoeken",
+    "who_to_follow": "Wie te volgen",
+    "preferences": "Voorkeuren",
+    "administration": "Administratie",
+    "search": "Zoeken",
+    "interactions": "Interacties"
+  },
+  "notifications": {
+    "broken_favorite": "Onbekende status, aan het zoeken…",
+    "favorited_you": "vond je status leuk",
+    "followed_you": "volgt jou",
+    "load_older": "Laad oudere meldingen",
+    "notifications": "Meldingen",
+    "read": "Gelezen!",
+    "repeated_you": "Herhaalde je status",
+    "no_more_notifications": "Geen meldingen meer",
+    "migrated_to": "is gemigreerd naar",
+    "follow_request": "wil je volgen",
+    "reacted_with": "reageerde met {0}"
+  },
+  "post_status": {
+    "new_status": "Nieuwe status plaatsen",
+    "account_not_locked_warning": "Je account is niet {0}. Iedereen kan je volgen om je alleen-volgers berichten te lezen.",
+    "account_not_locked_warning_link": "gesloten",
+    "attachments_sensitive": "Markeer bijlagen als gevoelig",
+    "content_type": {
+      "text/plain": "Platte tekst",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
+    },
+    "content_warning": "Onderwerp (optioneel)",
+    "default": "Zojuist geland in L.A.",
+    "direct_warning": "Deze post zal enkel zichtbaar zijn voor de personen die genoemd zijn.",
+    "posting": "Plaatsen",
+    "scope": {
+      "direct": "Direct - Post enkel naar vermelde gebruikers",
+      "private": "Enkel volgers - Post enkel naar volgers",
+      "public": "Publiek - Post op publieke tijdlijnen",
+      "unlisted": "Niet Vermelden - Niet tonen op publieke tijdlijnen"
+    },
+    "direct_warning_to_all": "Dit bericht zal zichtbaar zijn voor alle vermelde gebruikers.",
+    "direct_warning_to_first_only": "Dit bericht zal alleen zichtbaar zijn voor de vermelde gebruikers aan het begin van het bericht.",
+    "scope_notice": {
+      "public": "Dit bericht zal voor iedereen zichtbaar zijn",
+      "unlisted": "Dit bericht zal niet zichtbaar zijn in de Publieke Tijdlijn en Het Geheel Bekende Netwerk",
+      "private": "Dit bericht zal voor alleen je volgers zichtbaar zijn"
+    }
+  },
+  "registration": {
+    "bio": "Bio",
+    "email": "Email",
+    "fullname": "Weergave naam",
+    "password_confirm": "Wachtwoord bevestiging",
+    "registration": "Registratie",
+    "token": "Uitnodigings-token",
+    "captcha": "CAPTCHA",
+    "new_captcha": "Klik op de afbeelding voor een nieuwe captcha",
+    "validations": {
+      "username_required": "moet ingevuld zijn",
+      "fullname_required": "moet ingevuld zijn",
+      "email_required": "moet ingevuld zijn",
+      "password_required": "moet ingevuld zijn",
+      "password_confirmation_required": "moet ingevuld zijn",
+      "password_confirmation_match": "komt niet overeen met het wachtwoord"
+    },
+    "username_placeholder": "bijv. lain",
+    "fullname_placeholder": "bijv. Lain Iwakura",
+    "bio_placeholder": "bijv.\nHallo, ik ben Lain.\nIk ben een anime meisje woonachtig in een buitenwijk in Japan. Je kent me misschien van the Wired."
+  },
+  "settings": {
+    "attachmentRadius": "Bijlages",
+    "attachments": "Bijlages",
+    "autoload": "Automatisch laden inschakelen wanneer tot de bodem gescrold wordt",
+    "avatar": "Avatar",
+    "avatarAltRadius": "Avatars (Meldingen)",
+    "avatarRadius": "Avatars",
+    "background": "Achtergrond",
+    "bio": "Bio",
+    "btnRadius": "Knoppen",
+    "cBlue": "Blauw (Beantwoorden, volgen)",
+    "cGreen": "Groen (Herhalen)",
+    "cOrange": "Oranje (Favoriet)",
+    "cRed": "Rood (Annuleren)",
+    "change_password": "Wachtwoord Wijzigen",
+    "change_password_error": "Er is een fout opgetreden bij het wijzigen van je wachtwoord.",
+    "changed_password": "Wachtwoord succesvol gewijzigd!",
+    "collapse_subject": "Klap berichten met een onderwerp in",
+    "composing": "Opstellen",
+    "confirm_new_password": "Nieuw wachtwoord bevestigen",
+    "current_avatar": "Je huidige avatar",
+    "current_password": "Huidig wachtwoord",
+    "current_profile_banner": "Je huidige profiel banner",
+    "data_import_export_tab": "Data Import / Export",
+    "default_vis": "Standaard zichtbaarheidsbereik",
+    "delete_account": "Account Verwijderen",
+    "delete_account_description": "Permanent je gegevens verwijderen en account deactiveren.",
+    "delete_account_error": "Er is een fout opgetreden bij het verwijderen van je account. Indien dit probleem zich voor blijft doen, neem dan contact op met de beheerder van deze instantie.",
+    "delete_account_instructions": "Voer je wachtwoord in het onderstaande invoerveld in om het verwijderen van je account te bevestigen.",
+    "export_theme": "Preset opslaan",
+    "filtering": "Filtering",
+    "filtering_explanation": "Alle statussen die deze woorden bevatten worden genegeerd, één filter per lijn",
+    "follow_export": "Volgers exporteren",
+    "follow_export_button": "Exporteer je volgers naar een csv bestand",
+    "follow_export_processing": "Aan het verwerken, binnen enkele ogenblikken wordt je gevraagd je bestand te downloaden",
+    "follow_import": "Volgers importeren",
+    "follow_import_error": "Fout bij importeren volgers",
+    "follows_imported": "Volgers geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
+    "foreground": "Voorgrond",
+    "general": "Algemeen",
+    "hide_attachments_in_convo": "Verberg bijlages in conversaties",
+    "hide_attachments_in_tl": "Verberg bijlages in de tijdlijn",
+    "hide_isp": "Verberg instantie-specifiek paneel",
+    "preload_images": "Afbeeldingen vooraf laden",
+    "hide_post_stats": "Verberg bericht statistieken (bijv. het aantal favorieten)",
+    "hide_user_stats": "Verberg bericht statistieken (bijv. het aantal volgers)",
+    "import_followers_from_a_csv_file": "Importeer volgers uit een csv bestand",
+    "import_theme": "Preset laden",
+    "inputRadius": "Invoervelden",
+    "checkboxRadius": "Checkboxen",
+    "instance_default": "(standaard: {value})",
+    "instance_default_simple": "(standaard)",
+    "interface": "Interface",
+    "interfaceLanguage": "Interface taal",
+    "invalid_theme_imported": "Het geselecteerde bestand is geen door Pleroma ondersteund thema. Er zijn geen aanpassingen gedaan.",
+    "limited_availability": "Niet beschikbaar in je browser",
+    "links": "Links",
+    "lock_account_description": "Laat volgers enkel toe na expliciete toestemming",
+    "loop_video": "Herhaal video's",
+    "loop_video_silent_only": "Herhaal enkel video's zonder geluid (bijv. Mastodon's \"gifs\")",
+    "name": "Naam",
+    "name_bio": "Naam & Bio",
+    "new_password": "Nieuw wachtwoord",
+    "notification_visibility": "Type meldingen die getoond worden",
+    "notification_visibility_follows": "Volgingen",
+    "notification_visibility_likes": "Vind-ik-leuks",
+    "notification_visibility_mentions": "Vermeldingen",
+    "notification_visibility_repeats": "Herhalingen",
+    "no_rich_text_description": "Verwijder rich text formattering van alle berichten",
+    "hide_network_description": "Toon niet wie mij volgt en wie ik volg.",
+    "nsfw_clickthrough": "Doorklikbaar verbergen van gevoelige bijlages inschakelen",
+    "oauth_tokens": "OAuth-tokens",
+    "token": "Token",
+    "refresh_token": "Token Vernieuwen",
+    "valid_until": "Geldig tot",
+    "revoke_token": "Intrekken",
+    "panelRadius": "Panelen",
+    "pause_on_unfocused": "Streamen pauzeren wanneer de tab niet in focus is",
+    "presets": "Presets",
+    "profile_background": "Profiel Achtergrond",
+    "profile_banner": "Profiel Banner",
+    "profile_tab": "Profiel",
+    "radii_help": "Stel afronding van hoeken in de interface in (in pixels)",
+    "replies_in_timeline": "Antwoorden in tijdlijn",
+    "reply_link_preview": "Antwoord-link weergave inschakelen bij aanwijzen met muisaanwijzer",
+    "reply_visibility_all": "Alle antwoorden tonen",
+    "reply_visibility_following": "Enkel antwoorden tonen die aan mij of gevolgde gebruikers gericht zijn",
+    "reply_visibility_self": "Enkel antwoorden tonen die aan mij gericht zijn",
+    "saving_err": "Fout tijdens opslaan van instellingen",
+    "saving_ok": "Instellingen opgeslagen",
+    "security_tab": "Beveiliging",
+    "scope_copy": "Neem bereik over bij beantwoorden (Directe Berichten blijven altijd Direct)",
+    "set_new_avatar": "Nieuwe avatar instellen",
+    "set_new_profile_background": "Nieuwe profiel achtergrond instellen",
+    "set_new_profile_banner": "Nieuwe profiel banner instellen",
+    "settings": "Instellingen",
+    "subject_input_always_show": "Altijd onderwerpveld tonen",
+    "subject_line_behavior": "Onderwerp kopiëren bij antwoorden",
+    "subject_line_email": "Zoals email: \"re: onderwerp\"",
+    "subject_line_mastodon": "Zoals mastodon: kopieer zoals het is",
+    "subject_line_noop": "Niet kopiëren",
+    "stop_gifs": "GIFs afspelen bij zweven",
+    "streaming": "Automatisch streamen van nieuwe berichten inschakelen wanneer tot boven gescrold is",
+    "text": "Tekst",
+    "theme": "Thema",
+    "theme_help": "Gebruik hex color codes (#rrggbb) om je kleurschema te wijzigen.",
+    "theme_help_v2_1": "Je kan ook de kleur en transparantie van bepaalde componenten overschrijven door de checkbox aan te vinken, gebruik de \"Alles wissen\" knop om alle overschrijvingen te annuleren.",
+    "theme_help_v2_2": "Iconen onder sommige onderdelen zijn achtergrond/tekst contrast indicatoren, zweef er over voor gedetailleerde info. Hou er rekening mee dat bij doorzichtigheid de ergst mogelijke situatie wordt weer gegeven.",
+    "tooltipRadius": "Tooltips/alarmen",
+    "user_settings": "Gebruikersinstellingen",
+    "values": {
+      "false": "nee",
+      "true": "ja"
+    },
+    "notifications": "Meldingen",
+    "enable_web_push_notifications": "Web push meldingen inschakelen",
+    "style": {
+      "switcher": {
+        "keep_color": "Kleuren behouden",
+        "keep_shadows": "Schaduwen behouden",
+        "keep_opacity": "Transparantie behouden",
+        "keep_roundness": "Rondingen behouden",
+        "keep_fonts": "Lettertypes behouden",
+        "save_load_hint": "\"Behoud\" opties behouden de momenteel ingestelde opties bij het selecteren of laden van thema's, maar slaan ook de genoemde opties op bij het exporteren van een thema. Wanneer alle selectievakjes zijn uitgeschakeld, zal het exporteren van thema's alles opslaan.",
+        "reset": "Reset",
+        "clear_all": "Alles wissen",
+        "clear_opacity": "Transparantie wissen",
+        "keep_as_is": "Hou zoals het is",
+        "use_snapshot": "Oude versie",
+        "use_source": "Nieuwe versie",
+        "help": {
+          "future_version_imported": "Het geïmporteerde bestand is gemaakt voor een nieuwere versie van FE.",
+          "older_version_imported": "Het geïmporteerde bestand is gemaakt voor een oudere versie van FE.",
+          "upgraded_from_v2": "PleromaFE is bijgewerkt, het thema kan iets anders uitzien dan dat je gewend bent.",
+          "v2_imported": "Het geïmporteerde bestand is gemaakt voor een oudere FE. We proberen compatibiliteit te maximaliseren, maar het kan toch voorkomen dat er inconsistenties zijn.",
+          "snapshot_source_mismatch": "Versie conflict: waarschijnlijk was FE terug gerold en opnieuw bijgewerkt, indien je het thema aangepast hebt met de oudere versie van FE wil je waarschijnlijk de oude versie gebruiken, gebruik anders de nieuwe versie.",
+          "migration_napshot_gone": "Voor een onduidelijke reden mist de momentopname, dus sommige dingen kunnen anders uitzien dan je gewend bent.",
+          "migration_snapshot_ok": "Voor de zekerheid is een momentopname van het thema geladen. Je kunt proberen om de thema gegevens te laden.",
+          "fe_downgraded": "PleromaFE's versie is terug gerold.",
+          "fe_upgraded": "De thema-engine van PleromaFE is bijgewerkt na de versie update.",
+          "snapshot_missing": "Het bestand bevat geen thema momentopname, dus het thema kan anders uitzien dan je oorspronkelijk bedacht had.",
+          "snapshot_present": "Thema momentopname is geladen, alle waarden zijn overschreven. Je kunt in plaats daarvan ook de daadwerkelijke data van het thema laden."
+        },
+        "load_theme": "Thema laden"
+      },
+      "common": {
+        "color": "Kleur",
+        "opacity": "Transparantie",
+        "contrast": {
+          "hint": "Contrast verhouding is {ratio}, {level} {context}",
+          "level": {
+            "aa": "voldoet aan de richtlijn van niveau AA (minimum)",
+            "aaa": "voldoet aan de richtlijn van niveau AAA (aangeraden)",
+            "bad": "voldoet aan geen enkele toegankelijkheidsrichtlijn"
+          },
+          "context": {
+            "18pt": "voor grote (18pt+) tekst",
+            "text": "voor tekst"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "Algemeen",
+        "main": "Algemene kleuren",
+        "foreground_hint": "Zie \"Geavanceerd\" tab voor meer gedetailleerde controle",
+        "rgbo": "Iconen, accenten, badges"
+      },
+      "advanced_colors": {
+        "_tab_label": "Geavanceerd",
+        "alert": "Alarm achtergrond",
+        "alert_error": "Fout",
+        "badge": "Badge achtergrond",
+        "badge_notification": "Meldingen",
+        "panel_header": "Paneel koptekst",
+        "top_bar": "Top balk",
+        "borders": "Randen",
+        "buttons": "Knoppen",
+        "inputs": "Invoervelden",
+        "faint_text": "Vervaagde tekst",
+        "tabs": "Tabbladen",
+        "toggled": "Geschakeld",
+        "disabled": "Uitgeschakeld",
+        "selectedMenu": "Geselecteerd menu item",
+        "selectedPost": "Geselecteerd bericht",
+        "pressed": "Ingedrukt",
+        "highlight": "Gemarkeerde elementen",
+        "icons": "Iconen",
+        "poll": "Poll grafiek",
+        "underlay": "Onderlaag",
+        "popover": "Tooltips, menu's, popovers",
+        "post": "Berichten / Gebruiker bios",
+        "alert_neutral": "Neutraal",
+        "alert_warning": "Waarschuwing"
+      },
+      "radii": {
+        "_tab_label": "Rondheid"
+      },
+      "shadows": {
+        "_tab_label": "Schaduw en belichting",
+        "component": "Onderdeel",
+        "override": "Overschrijven",
+        "shadow_id": "Schaduw #{value}",
+        "blur": "Vervagen",
+        "spread": "Spreiding",
+        "inset": "Inzet",
+        "hint": "Voor schaduw kan je ook --variable gebruiken als een kleur waarde om CSS3 variabelen te gebruiken. Houd er rekening mee dat het instellen van opaciteit in dit geval niet werkt.",
+        "filter_hint": {
+          "always_drop_shadow": "Waarschuwing, deze schaduw gebruikt altijd {0} als de browser dit ondersteund.",
+          "drop_shadow_syntax": "{0} ondersteund niet de {1} parameter en {2} sleutelwoord.",
+          "avatar_inset": "Houdt er rekening mee dat het combineren van zowel inzet and niet-inzet schaduwen op transparante avatars onverwachte resultaten kan opleveren.",
+          "spread_zero": "Schaduw met spreiding > 0 worden weergegeven alsof ze op nul staan",
+          "inset_classic": "Inzet schaduw zal {0} gebruiken"
+        },
+        "components": {
+          "panel": "Paneel",
+          "panelHeader": "Paneel koptekst",
+          "topBar": "Top balk",
+          "avatar": "Gebruikers avatar (in profiel weergave)",
+          "avatarStatus": "Gebruikers avatar (in bericht weergave)",
+          "popup": "Popups en tooltips",
+          "button": "Knop",
+          "buttonHover": "Knop (zweven)",
+          "buttonPressed": "Knop (ingedrukt)",
+          "buttonPressedHover": "Knop (ingedrukt+zweven)",
+          "input": "Invoerveld"
+        },
+        "hintV3": "Voor schaduwen kun je ook de {0} notatie gebruiken om de andere kleur invoer te gebruiken."
+      },
+      "fonts": {
+        "_tab_label": "Lettertypes",
+        "help": "Selecteer het lettertype om te gebruiken voor elementen van de UI. Voor \"aangepast\" dien je de exacte naam van het lettertype in te voeren zoals die in het systeem wordt weergegeven.",
+        "components": {
+          "interface": "Interface",
+          "input": "Invoervelden",
+          "post": "Bericht tekst",
+          "postCode": "Monospaced tekst in een bericht (rich text)"
+        },
+        "family": "Lettertype naam",
+        "size": "Grootte (in px)",
+        "weight": "Gewicht (dikgedruktheid)",
+        "custom": "Aangepast"
+      },
+      "preview": {
+        "header": "Voorvertoning",
+        "content": "Inhoud",
+        "error": "Voorbeeld fout",
+        "button": "Knop",
+        "text": "Nog een boel andere {0} en {1}",
+        "mono": "inhoud",
+        "input": "Zojuist geland in L.A.",
+        "faint_link": "handige gebruikershandleiding",
+        "fine_print": "Lees onze {0} om niets nuttig te leren!",
+        "header_faint": "Alles komt goed",
+        "checkbox": "Ik heb de gebruikersvoorwaarden gelezen",
+        "link": "een leuke kleine link"
+      }
+    },
+    "notification_setting_follows": "Gebruikers die je volgt",
+    "notification_setting_non_follows": "Gebruikers die je niet volgt",
+    "notification_setting_followers": "Gebruikers die je volgen",
+    "notification_setting_privacy": "Privacy",
+    "notification_setting_privacy_option": "Verberg de afzender en inhoud van push meldingen",
+    "notification_mutes": "Om niet langer meldingen te ontvangen van een specifieke gebruiker, kun je deze negeren.",
+    "app_name": "App naam",
+    "security": "Beveiliging",
+    "enter_current_password_to_confirm": "Voer je huidige wachtwoord in om je identiteit te bevestigen",
+    "mfa": {
+      "otp": "OTP",
+      "setup_otp": "OTP instellen",
+      "wait_pre_setup_otp": "OTP voorinstellen",
+      "confirm_and_enable": "Bevestig en schakel OTP in",
+      "title": "Twee-factor Authenticatie",
+      "generate_new_recovery_codes": "Genereer nieuwe herstelcodes",
+      "recovery_codes": "Herstelcodes.",
+      "waiting_a_recovery_codes": "Backup codes ontvangen…",
+      "authentication_methods": "Authenticatie methodes",
+      "scan": {
+        "title": "Scannen",
+        "desc": "Scan de QR code of voer een sleutel in met je twee-factor applicatie:",
+        "secret_code": "Sleutel"
+      },
+      "verify": {
+        "desc": "Voer de code van je twee-factor applicatie in om twee-factor authenticatie in te schakelen:"
+      },
+      "warning_of_generate_new_codes": "Wanneer je nieuwe herstelcodes genereert, zullen je oude code niet langer werken.",
+      "recovery_codes_warning": "Schrijf de codes op of sla ze op een veilige locatie op - anders kun je ze niet meer inzien. Als je toegang tot je 2FA app en herstelcodes verliest, zal je buitengesloten zijn uit je account."
+    },
+    "allow_following_move": "Automatisch volgen toestaan wanneer een gevolgd account migreert",
+    "block_export": "Blokkades exporteren",
+    "block_import": "Blokkades importeren",
+    "blocks_imported": "Blokkades geïmporteerd! Het kan even duren voordat deze verwerkt zijn.",
+    "blocks_tab": "Blokkades",
+    "change_email": "Email wijzigen",
+    "change_email_error": "Er is een fout opgetreden tijdens het wijzigen van je email.",
+    "changed_email": "Email succesvol gewijzigd!",
+    "domain_mutes": "Domeinen",
+    "avatar_size_instruction": "De aangeraden minimale afmeting voor avatar afbeeldingen is 150x150 pixels.",
+    "pad_emoji": "Vul emoji aan met spaties wanneer deze met de picker ingevoegd worden",
+    "emoji_reactions_on_timeline": "Toon emoji reacties op de tijdlijn",
+    "accent": "Accent",
+    "hide_muted_posts": "Verberg berichten van genegeerde gebruikers",
+    "max_thumbnails": "Maximaal aantal miniaturen per bericht",
+    "use_one_click_nsfw": "Open gevoelige bijlagen met slechts één klik",
+    "hide_filtered_statuses": "Gefilterde statussen verbergen",
+    "import_blocks_from_a_csv_file": "Importeer blokkades van een csv bestand",
+    "mutes_tab": "Negeringen",
+    "play_videos_in_modal": "Speel video's af in een popup frame",
+    "new_email": "Nieuwe Email",
+    "notification_visibility_emoji_reactions": "Reacties",
+    "no_blocks": "Geen blokkades",
+    "no_mutes": "Geen negeringen",
+    "hide_followers_description": "Niet tonen wie mij volgt",
+    "hide_followers_count_description": "Niet mijn volgers aantal tonen",
+    "hide_follows_count_description": "Niet mijn gevolgde aantal tonen",
+    "show_admin_badge": "Beheerders badge tonen in mijn profiel",
+    "autohide_floating_post_button": "Nieuw Bericht knop automatisch verbergen (mobiel)",
+    "search_user_to_block": "Zoek wie je wilt blokkeren",
+    "search_user_to_mute": "Zoek wie je wilt negeren",
+    "minimal_scopes_mode": "Bericht bereik-opties minimaliseren",
+    "post_status_content_type": "Bericht status content type",
+    "user_mutes": "Gebruikers",
+    "useStreamingApi": "Berichten en meldingen in real-time ontvangen",
+    "useStreamingApiWarning": "(Afgeraden, experimenteel, kan berichten overslaan)",
+    "type_domains_to_mute": "Voer domeinen in om te negeren",
+    "upload_a_photo": "Upload een foto",
+    "fun": "Plezier",
+    "greentext": "Meme pijlen",
+    "notification_setting": "Ontvang meldingen van:",
+    "block_export_button": "Exporteer je geblokkeerde gebruikers naar een csv bestand",
+    "block_import_error": "Fout bij importeren blokkades",
+    "discoverable": "Sta toe dat dit account ontdekt kan worden in zoekresultaten en andere diensten",
+    "use_contain_fit": "Snij bijlage in miniaturen niet bij",
+    "notification_visibility_moves": "Gebruiker Migraties",
+    "hide_follows_description": "Niet tonen wie ik volg",
+    "show_moderator_badge": "Moderators badge tonen in mijn profiel",
+    "notification_setting_filters": "Filters",
+    "notification_setting_non_followers": "Gebruikers die je niet volgen",
+    "notification_blocks": "Door een gebruiker te blokkeren, ontvang je geen meldingen meer van de gebruiker en wordt je abonnement op de gebruiker opgeheven.",
+    "version": {
+      "frontend_version": "Frontend Versie",
+      "backend_version": "Backend Versie",
+      "title": "Versie"
+    }
+  },
+  "timeline": {
+    "collapse": "Inklappen",
+    "conversation": "Conversatie",
+    "error_fetching": "Fout bij ophalen van updates",
+    "load_older": "Oudere statussen laden",
+    "no_retweet_hint": "Bericht is gemarkeerd als enkel volgers of direct en kan niet worden herhaald",
+    "repeated": "herhaalde",
+    "show_new": "Nieuwe tonen",
+    "up_to_date": "Up-to-date",
+    "no_statuses": "Geen statussen",
+    "no_more_statuses": "Geen statussen meer"
+  },
+  "user_card": {
+    "approve": "Goedkeuren",
+    "block": "Blokkeren",
+    "blocked": "Geblokkeerd!",
+    "deny": "Weigeren",
+    "favorites": "Favorieten",
+    "follow": "Volgen",
+    "follow_sent": "Aanvraag verzonden!",
+    "follow_progress": "Aanvragen…",
+    "follow_again": "Aanvraag opnieuw zenden?",
+    "follow_unfollow": "Stop volgen",
+    "followees": "Aan het volgen",
+    "followers": "Volgers",
+    "following": "Aan het volgen!",
+    "follows_you": "Volgt jou!",
+    "its_you": "'t is jij!",
+    "mute": "Negeren",
+    "muted": "Genegeerd",
+    "per_day": "per dag",
+    "remote_follow": "Volg vanop afstand",
+    "statuses": "Statussen",
+    "admin_menu": {
+      "delete_user_confirmation": "Weet je het heel zeker? Deze uitvoering kan niet ongedaan worden gemaakt.",
+      "delete_user": "Gebruiker verwijderen",
+      "quarantine": "Federeren van gebruikers berichten verbieden",
+      "disable_any_subscription": "Volgen van gebruiker in zijn geheel verbieden",
+      "disable_remote_subscription": "Volgen van gebruiker vanaf andere instanties verbieden",
+      "sandbox": "Berichten forceren om alleen voor volgers zichtbaar te zijn",
+      "force_unlisted": "Berichten forceren om niet publiekelijk getoond te worden",
+      "strip_media": "Media van berichten verwijderen",
+      "force_nsfw": "Alle berichten als gevoelig markeren",
+      "delete_account": "Account verwijderen",
+      "deactivate_account": "Account deactiveren",
+      "activate_account": "Account activeren",
+      "revoke_moderator": "Moderatorsrechten intrekken",
+      "grant_moderator": "Moderatorsrechten toekennen",
+      "revoke_admin": "Beheerdersrechten intrekken",
+      "grant_admin": "Beheerdersrechten toekennen",
+      "moderation": "Moderatie"
+    },
+    "show_repeats": "Herhalingen tonen",
+    "hide_repeats": "Herhalingen verbergen",
+    "mute_progress": "Negeren…",
+    "unmute_progress": "Negering opheffen…",
+    "unmute": "Negering opheffen",
+    "block_progress": "Blokkeren…",
+    "unblock_progress": "Blokkade opheffen…",
+    "unblock": "Blokkade opheffen",
+    "unsubscribe": "Abonnement opzeggen",
+    "subscribe": "Abonneren",
+    "report": "Aangeven",
+    "mention": "Vermelding",
+    "media": "Media",
+    "hidden": "Verborgen"
+  },
+  "user_profile": {
+    "timeline_title": "Gebruikers Tijdlijn",
+    "profile_loading_error": "Sorry, er is een fout opgetreden bij het laden van dit profiel.",
+    "profile_does_not_exist": "Sorry, dit profiel bestaat niet."
+  },
+  "who_to_follow": {
+    "more": "Meer",
+    "who_to_follow": "Wie te volgen"
+  },
+  "tool_tip": {
+    "media_upload": "Media Uploaden",
+    "repeat": "Herhalen",
+    "reply": "Beantwoorden",
+    "favorite": "Favoriet maken",
+    "user_settings": "Gebruikers Instellingen",
+    "reject_follow_request": "Volg-verzoek afwijzen",
+    "accept_follow_request": "Volg-aanvraag accepteren",
+    "add_reaction": "Reactie toevoegen"
+  },
+  "upload": {
+    "error": {
+      "base": "Upload mislukt.",
+      "file_too_big": "Bestand is te groot [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+      "default": "Probeer het later opnieuw"
+    },
+    "file_size_units": {
+      "B": "B",
+      "KiB": "KiB",
+      "MiB": "MiB",
+      "GiB": "GiB",
+      "TiB": "TiB"
+    }
+  },
+  "about": {
+    "mrf": {
+      "federation": "Federatie",
+      "keyword": {
+        "reject": "Afwijzen",
+        "replace": "Vervangen",
+        "is_replaced_by": "→",
+        "keyword_policies": "Zoekwoord Beleid",
+        "ftl_removal": "Verwijdering van \"Het Geheel Bekende Netwerk\" Tijdlijn"
+      },
+      "mrf_policies_desc": "MRF regels beïnvloeden het federatie gedrag van de instantie. De volgende regels zijn ingeschakeld:",
+      "mrf_policies": "Ingeschakelde MRF Regels",
+      "simple": {
+        "simple_policies": "Instantie-specifieke Regels",
+        "accept": "Accepteren",
+        "accept_desc": "Deze instantie accepteert alleen berichten van de volgende instanties:",
+        "reject": "Afwijzen",
+        "reject_desc": "Deze instantie zal geen berichten accepteren van de volgende instanties:",
+        "quarantine": "Quarantaine",
+        "quarantine_desc": "Deze instantie zal alleen publieke berichten sturen naar de volgende instanties:",
+        "ftl_removal_desc": "Deze instantie verwijdert de volgende instanties van \"Het Geheel Bekende Netwerk\" tijdlijn:",
+        "media_removal_desc": "Deze instantie verwijdert media van berichten van de volgende instanties:",
+        "media_nsfw_desc": "Deze instantie stelt media in als gevoelig in berichten van de volgende instanties:",
+        "ftl_removal": "Verwijderen van \"Het Geheel Bekende Netwerk\" Tijdlijn",
+        "media_removal": "Media Verwijdering",
+        "media_nsfw": "Forceer Media als Gevoelig"
+      }
+    },
+    "staff": "Personeel"
+  },
+  "domain_mute_card": {
+    "mute": "Negeren",
+    "mute_progress": "Negeren…",
+    "unmute": "Negering opheffen",
+    "unmute_progress": "Negering wordt opgeheven…"
+  },
+  "exporter": {
+    "export": "Exporteren",
+    "processing": "Verwerken, er wordt zo gevraagd om je bestand te downloaden"
+  },
+  "image_cropper": {
+    "save": "Opslaan",
+    "save_without_cropping": "Opslaan zonder bijsnijden",
+    "cancel": "Annuleren",
+    "crop_picture": "Afbeelding bijsnijden"
+  },
+  "importer": {
+    "submit": "Verzenden",
+    "success": "Succesvol geïmporteerd.",
+    "error": "Er is een fout opgetreden bij het importeren van dit bestand."
+  },
+  "media_modal": {
+    "previous": "Vorige",
+    "next": "Volgende"
+  },
+  "polls": {
+    "add_poll": "Poll Toevoegen",
+    "add_option": "Optie Toevoegen",
+    "option": "Optie",
+    "votes": "stemmen",
+    "vote": "Stem",
+    "single_choice": "Enkele keuze",
+    "multiple_choices": "Meerkeuze",
+    "expiry": "Poll leeftijd",
+    "expires_in": "Poll eindigt in {0}",
+    "expired": "Poll is {0} geleden beëindigd",
+    "not_enough_options": "Te weinig opties in poll",
+    "type": "Poll type"
+  },
+  "emoji": {
+    "emoji": "Emoji",
+    "keep_open": "Picker openhouden",
+    "search_emoji": "Zoek voor een emoji",
+    "add_emoji": "Emoji invoegen",
+    "unicode": "Unicode emoji",
+    "load_all": "Alle {emojiAmount} emoji worden geladen",
+    "stickers": "Stickers",
+    "load_all_hint": "Eerste {saneAmount} emoji geladen, alle emoji tegelijk laden kan problemen veroorzaken met prestaties.",
+    "custom": "Gepersonaliseerde emoji"
+  },
+  "interactions": {
+    "favs_repeats": "Herhalingen en Favorieten",
+    "follows": "Nieuwe volgingen",
+    "moves": "Gebruiker migreert",
+    "load_older": "Oudere interacties laden"
+  },
+  "remote_user_resolver": {
+    "searching_for": "Zoeken naar",
+    "error": "Niet gevonden.",
+    "remote_user_resolver": "Externe gebruikers zoeker"
+  },
+  "selectable_list": {
+    "select_all": "Alles selecteren"
+  },
+  "password_reset": {
+    "password_reset_required_but_mailer_is_disabled": "Je dient je wachtwoord opnieuw in te stellen, maar wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
+    "password_reset_required": "Je dient je wachtwoord opnieuw in te stellen om in te kunnen loggen.",
+    "password_reset_disabled": "Wachtwoord reset is uitgeschakeld. Neem contact op met de beheerder van deze instantie.",
+    "too_many_requests": "Je hebt het maximaal aantal pogingen bereikt, probeer het later opnieuw.",
+    "not_found": "We kunnen die email of gebruikersnaam niet vinden.",
+    "return_home": "Terugkeren naar de home pagina",
+    "check_email": "Controleer je email inbox voor een link om je wachtwoord opnieuw in te stellen.",
+    "placeholder": "Je email of gebruikersnaam",
+    "instruction": "Voer je email adres of gebruikersnaam in. We sturen je een link om je wachtwoord opnieuw in te stellen.",
+    "password_reset": "Wachtwoord opnieuw instellen",
+    "forgot_password": "Wachtwoord vergeten?"
+  },
+  "search": {
+    "no_results": "Geen resultaten",
+    "people_talking": "{count} personen aan het praten",
+    "person_talking": "{count} persoon aan het praten",
+    "hashtags": "Hashtags",
+    "people": "Personen"
+  },
+  "user_reporting": {
+    "generic_error": "Er is een fout opgetreden tijdens het verwerken van je verzoek.",
+    "submit": "Verzenden",
+    "forward_to": "Doorsturen naar {0}",
+    "forward_description": "Dit account hoort bij een andere server. Wil je een kopie van het rapport ook daarheen sturen?",
+    "additional_comments": "Aanvullende opmerkingen",
+    "add_comment_description": "Het rapport zal naar de moderators van de instantie worden verstuurd. Je kunt hieronder uitleg bijvoegen waarom je dit account wilt aangeven:",
+    "title": "{0} aangeven"
+  },
+  "status": {
+    "copy_link": "Link naar status kopiëren",
+    "status_unavailable": "Status niet beschikbaar",
+    "unmute_conversation": "Conversatie niet meer negeren",
+    "mute_conversation": "Conversatie negeren",
+    "replies_list": "Antwoorden:",
+    "reply_to": "Antwoorden aan",
+    "delete_confirm": "Wil je echt deze status verwijderen?",
+    "pin": "Aan profiel vastmaken",
+    "pinned": "Vastgezet",
+    "unpin": "Van profiel losmaken",
+    "delete": "Status verwijderen",
+    "repeats": "Herhalingen",
+    "favorites": "Favorieten"
+  },
+  "time": {
+    "years_short": "{0}j",
+    "year_short": "{0}j",
+    "years": "{0} jaren",
+    "year": "{0} jaar",
+    "weeks_short": "{0}w",
+    "week_short": "{0}w",
+    "weeks": "{0} weken",
+    "week": "{0} week",
+    "seconds_short": "{0}s",
+    "second_short": "{0}s",
+    "seconds": "{0} seconden",
+    "second": "{0} seconde",
+    "now_short": "nu",
+    "now": "zojuist",
+    "months_short": "{0}ma",
+    "month_short": "{0}ma",
+    "months": "{0} maanden",
+    "month": "{0} maand",
+    "minutes_short": "{0}min",
+    "minute_short": "{0}min",
+    "minutes": "{0} minuten",
+    "minute": "{0} minuut",
+    "in_past": "{0} geleden",
+    "in_future": "over {0}",
+    "hours_short": "{0}u",
+    "hour_short": "{0}u",
+    "hours": "{0} uren",
+    "hour": "{0} uur",
+    "days_short": "{0}d",
+    "day_short": "{0}d",
+    "days": "{0} dagen",
+    "day": "{0} dag"
+  }
 }
diff --git a/src/i18n/pl.json b/src/i18n/pl.json
index 101e5d39..61e09318 100644
--- a/src/i18n/pl.json
+++ b/src/i18n/pl.json
@@ -1,743 +1,743 @@
 {
-    "about": {
-        "mrf": {
-            "federation": "Federacja",
-            "keyword": {
-                "keyword_policies": "Zasady słów kluczowych",
-                "ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
-                "reject": "Odrzucanie",
-                "replace": "Zastąpienie",
-                "is_replaced_by": "→"
-            },
-            "mrf_policies": "Włączone zasady MRF",
-            "mrf_policies_desc": "Zasady MRF zmieniają zachowanie federowania instancji. Następujące zasady są włączone:",
-            "simple": {
-                "simple_policies": "Zasady specyficzne dla instancji",
-                "accept": "Akceptowanie",
-                "accept_desc": "Ta instancja akceptuje tylko posty z wymienionych instancji:",
-                "reject": "Odrzucanie",
-                "reject_desc": "Ta instancja odrzuca posty z wymienionych instancji:",
-                "quarantine": "Kwarantanna",
-                "quarantine_desc": "Ta instancja wysyła tylko publiczne posty do wymienionych instancji:",
-                "ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
-                "ftl_removal_desc": "Ta instancja usuwa wymienionych instancje z \"Całej znanej sieci\":",
-                "media_removal": "Usuwanie multimediów",
-                "media_removal_desc": "Ta instancja usuwa multimedia z postów od wymienionych instancji:",
-                "media_nsfw": "Multimedia ustawione jako wrażliwe",
-                "media_nsfw_desc": "Ta instancja wymusza, by multimedia z wymienionych instancji były ustawione jako wrażliwe:"
-            }
-        },
-        "staff": "Administracja"
+  "about": {
+    "mrf": {
+      "federation": "Federacja",
+      "keyword": {
+        "keyword_policies": "Zasady słów kluczowych",
+        "ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
+        "reject": "Odrzucanie",
+        "replace": "Zastąpienie",
+        "is_replaced_by": "→"
+      },
+      "mrf_policies": "Włączone zasady MRF",
+      "mrf_policies_desc": "Zasady MRF zmieniają zachowanie federowania instancji. Następujące zasady są włączone:",
+      "simple": {
+        "simple_policies": "Zasady specyficzne dla instancji",
+        "accept": "Akceptowanie",
+        "accept_desc": "Ta instancja akceptuje tylko posty z wymienionych instancji:",
+        "reject": "Odrzucanie",
+        "reject_desc": "Ta instancja odrzuca posty z wymienionych instancji:",
+        "quarantine": "Kwarantanna",
+        "quarantine_desc": "Ta instancja wysyła tylko publiczne posty do wymienionych instancji:",
+        "ftl_removal": "Usunięcie z \"Całej znanej sieci\"",
+        "ftl_removal_desc": "Ta instancja usuwa wymienionych instancje z \"Całej znanej sieci\":",
+        "media_removal": "Usuwanie multimediów",
+        "media_removal_desc": "Ta instancja usuwa multimedia z postów od wymienionych instancji:",
+        "media_nsfw": "Multimedia ustawione jako wrażliwe",
+        "media_nsfw_desc": "Ta instancja wymusza, by multimedia z wymienionych instancji były ustawione jako wrażliwe:"
+      }
     },
-    "chat": {
-        "title": "Czat"
-    },
-    "domain_mute_card": {
-        "mute": "Wycisz",
-        "mute_progress": "Wyciszam...",
-        "unmute": "Odcisz",
-        "unmute_progress": "Odciszam..."
-    },
-    "exporter": {
-        "export": "Eksportuj",
-        "processing": "Przetwarzam, za chwilę zostaniesz zapytany(-na) o ściągnięcie pliku"
-    },
-    "features_panel": {
-        "chat": "Czat",
-        "gopher": "Gopher",
-        "media_proxy": "Proxy mediów",
-        "scope_options": "Ustawienia zakresu",
-        "text_limit": "Limit tekstu",
-        "title": "Funkcje",
-        "who_to_follow": "Propozycje obserwacji"
-    },
-    "finder": {
-        "error_fetching_user": "Błąd przy pobieraniu profilu",
-        "find_user": "Znajdź użytkownika"
-    },
-    "general": {
-        "apply": "Zastosuj",
-        "submit": "Wyślij",
-        "more": "Więcej",
-        "generic_error": "Wystąpił błąd",
-        "optional": "nieobowiązkowe",
-        "show_more": "Pokaż więcej",
-        "show_less": "Pokaż mniej",
-        "dismiss": "Odrzuć",
-        "cancel": "Anuluj",
-        "disable": "Wyłącz",
-        "enable": "Włącz",
-        "confirm": "Potwierdź",
-        "verify": "Zweryfikuj"
-    },
-    "image_cropper": {
-        "crop_picture": "Przytnij obrazek",
-        "save": "Zapisz",
-        "save_without_cropping": "Zapisz bez przycinania",
-        "cancel": "Anuluj"
-    },
-    "importer": {
-        "submit": "Wyślij",
-        "success": "Zaimportowano pomyślnie.",
-        "error": "Wystąpił błąd podczas importowania pliku."
-    },
-    "login": {
-        "login": "Zaloguj",
-        "description": "Zaloguj używając OAuth",
-        "logout": "Wyloguj",
-        "password": "Hasło",
-        "placeholder": "n.p. lain",
-        "register": "Zarejestruj",
-        "username": "Użytkownik",
-        "hint": "Zaloguj się, aby dołączyć do dyskusji",
-        "authentication_code": "Kod weryfikacyjny",
-        "enter_recovery_code": "Wprowadź kod zapasowy",
-        "enter_two_factor_code": "Wprowadź kod weryfikacyjny",
-        "recovery_code": "Kod zapasowy",
-        "heading": {
-            "totp": "Weryfikacja dwuetapowa",
-            "recovery": "Zapasowa weryfikacja dwuetapowa"
-        }
-    },
-    "media_modal": {
-        "previous": "Poprzednie",
-        "next": "Następne"
-    },
-    "nav": {
-        "about": "O nas",
-        "administration": "Administracja",
-        "back": "Wróć",
-        "chat": "Lokalny czat",
-        "friend_requests": "Prośby o możliwość obserwacji",
-        "mentions": "Wzmianki",
-        "interactions": "Interakcje",
-        "dms": "Wiadomości prywatne",
-        "public_tl": "Publiczna oś czasu",
-        "timeline": "Oś czasu",
-        "twkn": "Cała znana sieć",
-        "user_search": "Wyszukiwanie użytkowników",
-        "search": "Wyszukiwanie",
-        "who_to_follow": "Sugestie obserwacji",
-        "preferences": "Preferencje"
-    },
-    "notifications": {
-        "broken_favorite": "Nieznany status, szukam go…",
-        "favorited_you": "dodał(-a) twój status do ulubionych",
-        "followed_you": "obserwuje cię",
-        "load_older": "Załaduj starsze powiadomienia",
-        "notifications": "Powiadomienia",
-        "read": "Przeczytane!",
-        "repeated_you": "powtórzył(-a) twój status",
-        "no_more_notifications": "Nie masz więcej powiadomień",
-        "migrated_to": "wyemigrował do",
-        "reacted_with": "zareagował z {0}",
-        "follow_request": "chce ciebie obserwować"
-    },
-    "polls": {
-        "add_poll": "Dodaj ankietę",
-        "add_option": "Dodaj opcję",
-        "option": "Opcja",
-        "votes": "głosów",
-        "vote": "Głosuj",
-        "type": "Typ ankiety",
-        "single_choice": "jednokrotnego wyboru",
-        "multiple_choices": "wielokrotnego wyboru",
-        "expiry": "Czas trwania ankiety",
-        "expires_in": "Ankieta kończy się za {0}",
-        "expired": "Ankieta skończyła się {0} temu",
-        "not_enough_options": "Zbyt mało unikalnych opcji w ankiecie"
-    },
-    "emoji": {
-        "stickers": "Naklejki",
-        "emoji": "Emoji",
-        "keep_open": "Zostaw selektor otwarty",
-        "search_emoji": "Wyszukaj emoji",
-        "add_emoji": "Wstaw emoji",
-        "custom": "Niestandardowe emoji",
-        "unicode": "Emoji unicode",
-        "load_all_hint": "Załadowano pierwsze {saneAmount} emoji, Załadowanie wszystkich emoji może spowodować problemy z wydajnością.",
-        "load_all": "Ładuję wszystkie {emojiAmount} emoji"
-    },
-    "interactions": {
-        "favs_repeats": "Powtórzenia i ulubione",
-        "follows": "Nowi obserwujący",
-        "moves": "Użytkownik migruje",
-        "load_older": "Załaduj starsze interakcje"
-    },
-    "post_status": {
-        "new_status": "Dodaj nowy status",
-        "account_not_locked_warning": "Twoje konto nie jest {0}. Każdy może cię zaobserwować aby zobaczyć wpisy tylko dla obserwujących.",
-        "account_not_locked_warning_link": "zablokowane",
-        "attachments_sensitive": "Oznacz załączniki jako wrażliwe",
-        "content_type": {
-            "text/plain": "Czysty tekst",
-            "text/html": "HTML",
-            "text/markdown": "Markdown",
-            "text/bbcode": "BBCode"
-        },
-        "content_warning": "Temat (nieobowiązkowy)",
-        "default": "Właśnie wróciłem z kościoła",
-        "direct_warning_to_all": "Ten wpis zobaczą wszystkie osoby, o których wspomniałeś(-aś).",
-        "direct_warning_to_first_only": "Ten wpis zobaczą tylko te osoby, o których wspomniałeś(-aś) na początku wiadomości.",
-        "posting": "Wysyłanie",
-        "scope_notice": {
-            "public": "Ten post będzie widoczny dla każdego",
-            "private": "Ten post będzie widoczny tylko dla twoich obserwujących",
-            "unlisted": "Ten post nie będzie widoczny na publicznej osi czasu i całej znanej sieci"
-        },
-        "scope": {
-            "direct": "Bezpośredni – Tylko dla wspomnianych użytkowników",
-            "private": "Tylko dla obserwujących – Umieść dla osób, które cię obserwują",
-            "public": "Publiczny – Umieść na publicznych osiach czasu",
-            "unlisted": "Niewidoczny – Nie umieszczaj na publicznych osiach czasu"
-        }
-    },
-    "registration": {
-        "bio": "Bio",
-        "email": "E-mail",
-        "fullname": "Wyświetlana nazwa profilu",
-        "password_confirm": "Potwierdzenie hasła",
-        "registration": "Rejestracja",
-        "token": "Token zaproszenia",
-        "captcha": "CAPTCHA",
-        "new_captcha": "Naciśnij na obrazek, aby dostać nowy kod captcha",
-        "username_placeholder": "np. lain",
-        "fullname_placeholder": "np. Lain Iwakura",
-        "bio_placeholder": "e.g.\nCześć, jestem Lain.\nJestem dziewczynką z anime żyjącą na peryferiach Japonii. Możesz znać mnie z Wired.",
-        "validations": {
-            "username_required": "nie może być pusta",
-            "fullname_required": "nie może być pusta",
-            "email_required": "nie może być pusty",
-            "password_required": "nie może być puste",
-            "password_confirmation_required": "nie może być puste",
-            "password_confirmation_match": "musi być takie jak hasło"
-        }
-    },
-    "remote_user_resolver": {
-        "remote_user_resolver": "Wyszukiwarka użytkowników nietutejszych",
-        "searching_for": "Szukam",
-        "error": "Nie znaleziono."
-    },
-    "selectable_list": {
-        "select_all": "Zaznacz wszystko"
-    },
-    "settings": {
-        "app_name": "Nazwa aplikacji",
-        "security": "Bezpieczeństwo",
-        "enter_current_password_to_confirm": "Wprowadź obecne hasło, by potwierdzić twoją tożsamość",
-        "mfa": {
-            "otp": "OTP",
-            "setup_otp": "Ustaw OTP",
-            "wait_pre_setup_otp": "początkowe ustawianie OTP",
-            "confirm_and_enable": "Potwierdź i włącz OTP",
-            "title": "Weryfikacja dwuetapowa",
-            "generate_new_recovery_codes": "Wygeneruj nowe kody zapasowe",
-            "warning_of_generate_new_codes": "Po tym gdy wygenerujesz nowe kody zapasowe, stare przestaną działać.",
-            "recovery_codes": "Kody zapasowe.",
-            "waiting_a_recovery_codes": "Otrzymuję kody zapasowe...",
-            "recovery_codes_warning": "Spisz kody na kartce papieru, albo zapisz je w bezpiecznym miejscu - inaczej nie zobaczysz ich już nigdy. Jeśli stracisz dostęp do twojej aplikacji 2FA i kodów zapasowych, nie będziesz miał(-a) dostępu do swojego konta.",
-            "authentication_methods": "Metody weryfikacji",
-            "scan": {
-                "title": "Skanuj",
-                "desc": "Zeskanuj ten kod QR używając twojej aplikacji 2FA albo wpisz ten klucz:",
-                "secret_code": "Klucz"
-            },
-            "verify": {
-                "desc": "By włączyć weryfikację dwuetapową, wpisz kod z twojej aplikacji 2FA:"
-            }
-        },
-        "allow_following_move": "Zezwalaj na automatyczną obserwację gdy obserwowane konto migruje",
-        "attachmentRadius": "Załączniki",
-        "attachments": "Załączniki",
-        "autoload": "Włącz automatyczne ładowanie po przewinięciu do końca strony",
-        "avatar": "Awatar",
-        "avatarAltRadius": "Awatary (powiadomienia)",
-        "avatarRadius": "Awatary",
-        "background": "Tło",
-        "bio": "Bio",
-        "block_export": "Eksport blokad",
-        "block_export_button": "Eksportuj twoje blokady do pliku .csv",
-        "block_import": "Import blokad",
-        "block_import_error": "Wystąpił błąd podczas importowania blokad",
-        "blocks_imported": "Zaimportowano blokady, przetwarzanie może zająć trochę czasu.",
-        "blocks_tab": "Bloki",
-        "btnRadius": "Przyciski",
-        "cBlue": "Niebieski (odpowiedz, obserwuj)",
-        "cGreen": "Zielony (powtórzenia)",
-        "cOrange": "Pomarańczowy (ulubione)",
-        "cRed": "Czerwony (anuluj)",
-        "change_email": "Zmień email",
-        "change_email_error": "Wystąpił problem podczas zmiany emaila.",
-        "changed_email": "Pomyślnie zmieniono email!",
-        "change_password": "Zmień hasło",
-        "change_password_error": "Podczas zmiany hasła wystąpił problem.",
-        "changed_password": "Pomyślnie zmieniono hasło!",
-        "collapse_subject": "Zwijaj posty z tematami",
-        "composing": "Pisanie",
-        "confirm_new_password": "Potwierdź nowe hasło",
-        "current_avatar": "Twój obecny awatar",
-        "current_password": "Obecne hasło",
-        "current_profile_banner": "Twój obecny banner profilu",
-        "data_import_export_tab": "Import/eksport danych",
-        "default_vis": "Domyślny zakres widoczności",
-        "delete_account": "Usuń konto",
-        "delete_account_description": "Trwale usuń dane i zdezaktywuj konto.",
-        "delete_account_error": "Wystąpił problem z usuwaniem twojego konta. Jeżeli problem powtarza się, poinformuj administratora swojej instancji.",
-        "delete_account_instructions": "Wprowadź swoje hasło w poniższe pole aby potwierdzić usunięcie konta.",
-        "discoverable": "Zezwól na odkrywanie tego konta w wynikach wyszukiwania i innych usługach",
-        "domain_mutes": "Domeny",
-        "avatar_size_instruction": "Zalecany minimalny rozmiar awatarów to 150x150 pikseli.",
-        "pad_emoji": "Dodaj odstęp z obu stron emoji podczas dodawania selektorem",
-        "emoji_reactions_on_timeline": "Pokaż reakcje emoji na osi czasu",
-        "export_theme": "Zapisz motyw",
-        "filtering": "Filtrowanie",
-        "filtering_explanation": "Wszystkie statusy zawierające te słowa będą wyciszone. Jedno słowo na linijkę.",
-        "follow_export": "Eksport obserwowanych",
-        "follow_export_button": "Eksportuj swoją listę obserwowanych do pliku CSV",
-        "follow_import": "Import obserwowanych",
-        "follow_import_error": "Błąd przy importowaniu obserwowanych",
-        "follows_imported": "Obserwowani zaimportowani! Przetwarzanie może trochę potrwać.",
-        "accent": "Akcent",
-        "foreground": "Pierwszy plan",
-        "general": "Ogólne",
-        "hide_attachments_in_convo": "Ukrywaj załączniki w rozmowach",
-        "hide_attachments_in_tl": "Ukrywaj załączniki w osi czasu",
-        "hide_muted_posts": "Ukrywaj wpisy wyciszonych użytkowników",
-        "max_thumbnails": "Maksymalna liczba miniatur w poście",
-        "hide_isp": "Ukryj panel informacji o instancji",
-        "preload_images": "Ładuj wstępnie obrazy",
-        "use_one_click_nsfw": "Otwieraj załączniki NSFW jednym kliknięciem",
-        "hide_post_stats": "Ukrywaj statysyki postów (np. liczbę polubień)",
-        "hide_user_stats": "Ukrywaj statysyki użytkowników (np. liczbę obserwujących)",
-        "hide_filtered_statuses": "Ukrywaj filtrowane statusy",
-        "import_blocks_from_a_csv_file": "Importuj blokady z pliku CSV",
-        "import_followers_from_a_csv_file": "Importuj obserwowanych z pliku CSV",
-        "import_theme": "Załaduj motyw",
-        "inputRadius": "Pola tekstowe",
-        "checkboxRadius": "Pola wyboru",
-        "instance_default": "(domyślnie: {value})",
-        "instance_default_simple": "(domyślne)",
-        "interface": "Interfejs",
-        "interfaceLanguage": "Język interfejsu",
-        "invalid_theme_imported": "Wybrany plik nie jest obsługiwanym motywem Pleromy. Nie dokonano zmian w twoim motywie.",
-        "limited_availability": "Niedostępne w twojej przeglądarce",
-        "links": "Łącza",
-        "lock_account_description": "Spraw, by konto mogli wyświetlać tylko zatwierdzeni obserwujący",
-        "loop_video": "Zapętlaj filmy",
-        "loop_video_silent_only": "Zapętlaj tylko filmy bez dźwięku (np. mastodonowe „gify”)",
-        "mutes_tab": "Wyciszenia",
-        "play_videos_in_modal": "Odtwarzaj filmy bezpośrednio w przeglądarce mediów",
-        "use_contain_fit": "Nie przycinaj załączników na miniaturach",
-        "name": "Imię",
-        "name_bio": "Imię i bio",
-        "new_email": "Nowy email",
-        "new_password": "Nowe hasło",
-        "notification_visibility": "Rodzaje powiadomień do wyświetlania",
-        "notification_visibility_follows": "Obserwacje",
-        "notification_visibility_likes": "Ulubione",
-        "notification_visibility_mentions": "Wzmianki",
-        "notification_visibility_repeats": "Powtórzenia",
-        "notification_visibility_moves": "Użytkownik migruje",
-        "notification_visibility_emoji_reactions": "Reakcje",
-        "no_rich_text_description": "Usuwaj formatowanie ze wszystkich postów",
-        "no_blocks": "Bez blokad",
-        "no_mutes": "Bez wyciszeń",
-        "hide_follows_description": "Nie pokazuj kogo obserwuję",
-        "hide_followers_description": "Nie pokazuj kto mnie obserwuje",
-        "hide_follows_count_description": "Nie pokazuj licznika obserwowanych",
-        "hide_followers_count_description": "Nie pokazuj licznika obserwujących",
-        "show_admin_badge": "Pokazuj odznakę Administrator na moim profilu",
-        "show_moderator_badge": "Pokazuj odznakę Moderator na moim profilu",
-        "nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)",
-        "oauth_tokens": "Tokeny OAuth",
-        "token": "Token",
-        "refresh_token": "Odśwież token",
-        "valid_until": "Ważne do",
-        "revoke_token": "Odwołać",
-        "panelRadius": "Panele",
-        "pause_on_unfocused": "Wstrzymuj strumieniowanie kiedy karta nie jest aktywna",
-        "presets": "Gotowe motywy",
-        "profile_background": "Tło profilu",
-        "profile_banner": "Banner profilu",
-        "profile_tab": "Profil",
-        "radii_help": "Ustaw zaokrąglenie krawędzi interfejsu (w pikselach)",
-        "replies_in_timeline": "Odpowiedzi na osi czasu",
-        "reply_link_preview": "Włącz dymek z podglądem postu po najechaniu na znak odpowiedzi",
-        "reply_visibility_all": "Pokazuj wszystkie odpowiedzi",
-        "reply_visibility_following": "Pokazuj tylko odpowiedzi skierowane do mnie i osób które obserwuję",
-        "reply_visibility_self": "Pokazuj tylko odpowiedzi skierowane do mnie",
-        "autohide_floating_post_button": "Ukryj automatycznie przycisk \"Nowy post\" (mobile)",
-        "saving_err": "Nie udało się zapisać ustawień",
-        "saving_ok": "Zapisano ustawienia",
-        "search_user_to_block": "Wyszukaj kogo chcesz zablokować",
-        "search_user_to_mute": "Wyszukaj kogo chcesz wyciszyć",
-        "security_tab": "Bezpieczeństwo",
-        "scope_copy": "Kopiuj zakres podczas odpowiadania (DM-y zawsze są kopiowane)",
-        "minimal_scopes_mode": "Zminimalizuj opcje wyboru zakresu postów",
-        "set_new_avatar": "Ustaw nowy awatar",
-        "set_new_profile_background": "Ustaw nowe tło profilu",
-        "set_new_profile_banner": "Ustaw nowy banner profilu",
-        "settings": "Ustawienia",
-        "subject_input_always_show": "Zawsze pokazuj pole tematu",
-        "subject_line_behavior": "Kopiuj temat podczas odpowiedzi",
-        "subject_line_email": "Jak w mailach – „re: temat”",
-        "subject_line_mastodon": "Jak na Mastodonie – po prostu kopiuj",
-        "subject_line_noop": "Nie kopiuj",
-        "post_status_content_type": "Post status content type",
-        "stop_gifs": "Odtwarzaj GIFy po najechaniu kursorem",
-        "streaming": "Włącz automatycznie strumieniowanie nowych postów gdy jesteś na początku strony",
-        "user_mutes": "Użytkownicy",
-        "useStreamingApi": "Otrzymuj posty i powiadomienia w czasie rzeczywistym",
-        "useStreamingApiWarning": "(Niezalecane, eksperymentalne, pomija posty)",
-        "text": "Tekst",
-        "theme": "Motyw",
-        "theme_help": "Użyj kolorów w notacji szesnastkowej (#rrggbb), by stworzyć swój motyw.",
-        "theme_help_v2_1": "Możesz też zastąpić kolory i widoczność poszczególnych komponentów przełączając pola wyboru, użyj „Wyczyść wszystko” aby usunąć wszystkie zastąpienia.",
-        "theme_help_v2_2": "Ikony pod niektórych wpisami są wskaźnikami kontrastu pomiędzy tłem a tekstem, po najechaniu na nie otrzymasz szczegółowe informacje. Zapamiętaj, że jeżeli używasz przezroczystości, wskaźniki pokazują najgorszy możliwy przypadek.",
-        "tooltipRadius": "Etykiety/alerty",
-        "type_domains_to_mute": "Wpisz domeny, które chcesz wyciszyć",
-        "upload_a_photo": "Wyślij zdjęcie",
-        "user_settings": "Ustawienia użytkownika",
-        "values": {
-            "false": "nie",
-            "true": "tak"
-        },
-        "fun": "Zabawa",
-        "greentext": "Memiczne strzałki",
-        "notifications": "Powiadomienia",
-        "notification_setting": "Otrzymuj powiadomienia od:",
-        "notification_setting_follows": "Ludzi których obserwujesz",
-        "notification_setting_non_follows": "Ludzi których nie obserwujesz",
-        "notification_setting_followers": "Ludzi którzy obserwują ciebie",
-        "notification_setting_non_followers": "Ludzi którzy nie obserwują ciebie",
-        "notification_mutes": "By przestać otrzymywać powiadomienia od jednego użytkownika, wycisz go.",
-        "notification_blocks": "Blokowanie uzytkownika zatrzymuje wszystkie powiadomienia i odsubskrybowuje go.",
-        "enable_web_push_notifications": "Włącz powiadomienia push",
-        "style": {
-            "switcher": {
-                "keep_color": "Zachowaj kolory",
-                "keep_shadows": "Zachowaj cienie",
-                "keep_opacity": "Zachowaj widoczność",
-                "keep_roundness": "Zachowaj zaokrąglenie",
-                "keep_fonts": "Zachowaj czcionki",
-                "save_load_hint": "Opcje „zachowaj” pozwalają na pozostanie przy obecnych opcjach po wybraniu lub załadowaniu motywu, jak i przechowywanie ich podczas eksportowania motywu. Jeżeli wszystkie opcje są odznaczone, eksportowanie motywu spowoduje zapisanie wszystkiego.",
-                "reset": "Wyzeruj",
-                "clear_all": "Wyczyść wszystko",
-                "clear_opacity": "Wyczyść widoczność",
-                "load_theme": "Załaduj motyw",
-                "keep_as_is": "Zostaw po staremu",
-                "use_snapshot": "Stara wersja",
-                "use_source": "Nowa wersja",
-                "help": {
-                    "upgraded_from_v2": "PleromaFE zostało zaaktualizowane, motyw może wyglądać nieco inaczej niż zapamiętałeś(-aś).",
-                    "v2_imported": "Plik który zaimportowałeś(-aś) został stworzony dla starszego FE. Próbujemy zwiększyć kompatybilność, lecz wciąż mogą występować rozbieżności.",
-                    "future_version_imported": "Plik który zaimportowałeś(-aś) został stworzony w nowszej wersji FE.",
-                    "older_version_imported": "Plik który zaimportowałeś(-aś) został stworzony w starszej wersji FE.",
-                    "snapshot_present": "Migawka motywu jest załadowana, więc wszystkie wartości zostały nadpisane. Zamiast tego możesz załadować właściwe dane motywu.",
-                    "snapshot_missing": "Nie znaleziono migawki motywu w pliku, więc motyw może wyglądać inaczej niż pierwotnie zaplanowano.",
-                    "fe_upgraded": "Silnik motywów PleromaFE został zaaktualizowany.",
-                    "fe_downgraded": "Wersja PleromaFE została cofnięta.",
-                    "migration_snapshot_ok": "Żeby być bezpiecznym, migawka motywu została załadowana. Możesz spróbować załadować dane motywu.",
-                    "migration_napshot_gone": "Z jakiegoś powodu migawka zniknęła, niektóre rzeczy mogą wyglądać inaczej niż zapamiętałeś(-aś).",
-                    "snapshot_source_mismatch": "Konflikt wersji: najprawdopodobniej FE zostało cofnięte do poprzedniej wersji i zaktualizowane ponownie, jeśli zmieniłeś(-aś) motyw używając starszej wersji FE, najprawdopodobniej chcesz używać starszej wersji, w przeciwnym razie użyj nowej wersji."
-                }
-            },
-            "common": {
-                "color": "Kolor",
-                "opacity": "Widoczność",
-                "contrast": {
-                    "hint": "Współczynnik kontrastu wynosi {ratio}, {level} {context}",
-                    "level": {
-                        "aa": "spełnia wymogi poziomu AA (minimalne)",
-                        "aaa": "spełnia wymogi poziomu AAA (zalecane)",
-                        "bad": "nie spełnia żadnych wymogów dostępności"
-                    },
-                    "context": {
-                        "18pt": "dla dużego tekstu (18pt+)",
-                        "text": "dla tekstu"
-                    }
-                }
-            },
-            "common_colors": {
-                "_tab_label": "Ogólne",
-                "main": "Ogólne kolory",
-                "foreground_hint": "Zajrzyj do karty „Zaawansowane”, aby uzyskać dokładniejszą kontrolę",
-                "rgbo": "Ikony, wyróżnienia, odznaki"
-            },
-            "advanced_colors": {
-                "_tab_label": "Zaawansowane",
-                "alert": "Tło alertu",
-                "alert_error": "Błąd",
-                "alert_warning": "Ostrzeżenie",
-                "alert_neutral": "Neutralne",
-                "post": "Posty/Bio użytkowników",
-                "badge": "Tło odznaki",
-                "popover": "Etykiety, menu, popovery",
-                "badge_notification": "Powiadomienie",
-                "panel_header": "Nagłówek panelu",
-                "top_bar": "Górny pasek",
-                "borders": "Granice",
-                "buttons": "Przyciski",
-                "inputs": "Pola wejścia",
-                "faint_text": "Zanikający tekst",
-                "underlay": "Podkład",
-                "poll": "Wykres ankiety",
-                "icons": "Ikony",
-                "highlight": "Podświetlone elementy",
-                "pressed": "Naciśnięte",
-                "selectedPost": "Wybrany post",
-                "selectedMenu": "Wybrany element menu",
-                "disabled": "Wyłączone",
-                "toggled": "Przełączone",
-                "tabs": "Karty"
-            },
-            "radii": {
-                "_tab_label": "Zaokrąglenie"
-            },
-            "shadows": {
-                "_tab_label": "Cień i podświetlenie",
-                "component": "Komponent",
-                "override": "Zastąp",
-                "shadow_id": "Cień #{value}",
-                "blur": "Rozmycie",
-                "spread": "Szerokość",
-                "inset": "Inset",
-                "hintV3": "Dla cieni możesz również użyć notacji {0} by użyć inny slot koloru.",
-                "filter_hint": {
-                    "always_drop_shadow": "Ostrzeżenie, ten cień zawsze używa {0} jeżeli to obsługiwane przez przeglądarkę.",
-                    "drop_shadow_syntax": "{0} nie obsługuje parametru {1} i słowa kluczowego {2}.",
-                    "avatar_inset": "Pamiętaj że użycie jednocześnie cieni inset i nie inset na awatarach może dać nieoczekiwane wyniki z przezroczystymi awatarami.",
-                    "spread_zero": "Cienie o ujemnej szerokości będą widoczne tak, jakby wynosiła ona zero",
-                    "inset_classic": "Cienie inset będą używały {0}"
-                },
-                "components": {
-                    "panel": "Panel",
-                    "panelHeader": "Nagłówek panelu",
-                    "topBar": "Górny pasek",
-                    "avatar": "Awatar użytkownika (w widoku profilu)",
-                    "avatarStatus": "Awatar użytkownika (w widoku wpisu)",
-                    "popup": "Wyskakujące okna i podpowiedzi",
-                    "button": "Przycisk",
-                    "buttonHover": "Przycisk (po najechaniu)",
-                    "buttonPressed": "Przycisk (naciśnięty)",
-                    "buttonPressedHover": "Przycisk(naciśnięty+najechany)",
-                    "input": "Pole wejścia"
-                }
-            },
-            "fonts": {
-                "_tab_label": "Czcionki",
-                "help": "Wybierz czcionkę używaną przez elementy UI. Jeżeli wybierzesz niestandardową, musisz wpisać dokładnie tę nazwę, pod którą pojawia się w systemie.",
-                "components": {
-                    "interface": "Interfejs",
-                    "input": "Pola wejścia",
-                    "post": "Tekst postu",
-                    "postCode": "Tekst o stałej szerokości znaków w sformatowanym poście"
-                },
-                "family": "Nazwa czcionki",
-                "size": "Rozmiar (w pikselach)",
-                "weight": "Grubość",
-                "custom": "Niestandardowa"
-            },
-            "preview": {
-                "header": "Podgląd",
-                "content": "Zawartość",
-                "error": "Przykładowy błąd",
-                "button": "Przycisk",
-                "text": "Trochę więcej {0} i {1}",
-                "mono": "treści",
-                "input": "Właśnie wróciłem z kościoła",
-                "faint_link": "pomocny podręcznik",
-                "fine_print": "Przeczytaj nasz {0}, aby nie nauczyć się niczego przydatnego!",
-                "header_faint": "W porządku",
-                "checkbox": "Przeleciałem(-am) przez zasady użytkowania",
-                "link": "i fajny mały odnośnik"
-            }
-        },
-        "version": {
-            "title": "Wersja",
-            "backend_version": "Wersja back-endu",
-            "frontend_version": "Wersja front-endu"
-        },
-        "notification_setting_privacy": "Prywatność",
-        "notification_setting_filters": "Filtry",
-        "notification_setting_privacy_option": "Ukryj nadawcę i zawartość powiadomień push"
-    },
-    "time": {
-        "day": "{0} dzień",
-        "days": "{0} dni",
-        "day_short": "{0} d",
-        "days_short": "{0} d",
-        "hour": "{0} godzina",
-        "hours": "{0} godzin",
-        "hour_short": "{0} godz.",
-        "hours_short": "{0} godz.",
-        "in_future": "za {0}",
-        "in_past": "{0} temu",
-        "minute": "{0} minuta",
-        "minutes": "{0} minut",
-        "minute_short": "{0} min",
-        "minutes_short": "{0} min",
-        "month": "{0} miesiąc",
-        "months": "{0} miesięcy",
-        "month_short": "{0} mies.",
-        "months_short": "{0} mies.",
-        "now": "teraz",
-        "now_short": "teraz",
-        "second": "{0} sekunda",
-        "seconds": "{0} sekund",
-        "second_short": "{0} s",
-        "seconds_short": "{0} s",
-        "week": "{0} tydzień",
-        "weeks": "{0} tygodni",
-        "week_short": "{0} tydz.",
-        "weeks_short": "{0} tyg.",
-        "year": "{0} rok",
-        "years": "{0} lata",
-        "year_short": "{0} r.",
-        "years_short": "{0} lata"
-    },
-    "timeline": {
-        "collapse": "Zwiń",
-        "conversation": "Rozmowa",
-        "error_fetching": "Błąd pobierania",
-        "load_older": "Załaduj starsze statusy",
-        "no_retweet_hint": "Wpis oznaczony jako tylko dla obserwujących lub bezpośredni nie może zostać powtórzony",
-        "repeated": "powtórzył(-a)",
-        "show_new": "Pokaż nowe",
-        "up_to_date": "Na bieżąco",
-        "no_more_statuses": "Brak kolejnych statusów",
-        "no_statuses": "Brak statusów"
-    },
-    "status": {
-        "favorites": "Ulubione",
-        "repeats": "Powtórzenia",
-        "delete": "Usuń status",
-        "pin": "Przypnij na profilu",
-        "unpin": "Odepnij z profilu",
-        "pinned": "Przypnięte",
-        "delete_confirm": "Czy naprawdę chcesz usunąć ten status?",
-        "reply_to": "Odpowiedź dla",
-        "replies_list": "Odpowiedzi:",
-        "mute_conversation": "Wycisz konwersację",
-        "unmute_conversation": "Odcisz konwersację",
-        "status_unavailable": "Status niedostępny",
-        "copy_link": "Kopiuj link do statusu"
-    },
-    "user_card": {
-        "approve": "Przyjmij",
-        "block": "Zablokuj",
-        "blocked": "Zablokowany!",
-        "deny": "Odrzuć",
-        "favorites": "Ulubione",
-        "follow": "Obserwuj",
-        "follow_sent": "Wysłano prośbę!",
-        "follow_progress": "Wysyłam prośbę…",
-        "follow_again": "Wysłać prośbę ponownie?",
-        "follow_unfollow": "Przestań obserwować",
-        "followees": "Obserwowani",
-        "followers": "Obserwujący",
-        "following": "Obserwowany!",
-        "follows_you": "Obserwuje cię!",
-        "hidden": "Ukryte",
-        "its_you": "To ty!",
-        "media": "Media",
-        "mention": "Wspomnienie",
-        "mute": "Wycisz",
-        "muted": "Wyciszony(-a)",
-        "per_day": "dziennie",
-        "remote_follow": "Zdalna obserwacja",
-        "report": "Zgłoś",
-        "statuses": "Statusy",
-        "subscribe": "Subskrybuj",
-        "unsubscribe": "Odsubskrybuj",
-        "unblock": "Odblokuj",
-        "unblock_progress": "Odblokowuję…",
-        "block_progress": "Blokuję…",
-        "unmute": "Cofnij wyciszenie",
-        "unmute_progress": "Cofam wyciszenie…",
-        "mute_progress": "Wyciszam…",
-        "hide_repeats": "Ukryj powtórzenia",
-        "show_repeats": "Pokaż powtórzenia",
-        "admin_menu": {
-            "moderation": "Moderacja",
-            "grant_admin": "Przyznaj admina",
-            "revoke_admin": "Odwołaj admina",
-            "grant_moderator": "Przyznaj moderatora",
-            "revoke_moderator": "Odwołaj moderatora",
-            "activate_account": "Aktywuj konto",
-            "deactivate_account": "Dezaktywuj konto",
-            "delete_account": "Usuń konto",
-            "force_nsfw": "Oznacz wszystkie posty jako NSFW",
-            "strip_media": "Usuń multimedia z postów",
-            "force_unlisted": "Wymuś posty na niepubliczne",
-            "sandbox": "Wymuś by posty były tylko dla obserwujących",
-            "disable_remote_subscription": "Zakaż obserwowania użytkownika ze zdalnych instancji",
-            "disable_any_subscription": "Zakaż całkowicie obserwowania użytkownika",
-            "quarantine": "Zakaż federowania postów od tego użytkownika",
-            "delete_user": "Usuń użytkownika",
-            "delete_user_confirmation": "Czy jesteś absolutnie pewny(-a)? Ta operacja nie może być cofnięta."
-        }
-    },
-    "user_profile": {
-        "timeline_title": "Oś czasu użytkownika",
-        "profile_does_not_exist": "Przepraszamy, ten profil nie istnieje.",
-        "profile_loading_error": "Przepraszamy, wystąpił błąd podczas ładowania tego profilu."
-    },
-    "user_reporting": {
-        "title": "Raportowanie {0}",
-        "add_comment_description": "Zgłoszenie zostanie wysłane do moderatorów instancji. Możesz dodać powód dlaczego zgłaszasz owe konto poniżej:",
-        "additional_comments": "Dodatkowe komentarze",
-        "forward_description": "To konto jest z innego serwera. Wysłać również tam kopię zgłoszenia?",
-        "forward_to": "Przekaż do {0}",
-        "submit": "Wyślij",
-        "generic_error": "Wystąpił błąd podczas przetwarzania twojej prośby."
-    },
-    "who_to_follow": {
-        "more": "Więcej",
-        "who_to_follow": "Propozycje obserwacji"
-    },
-    "tool_tip": {
-        "media_upload": "Wyślij media",
-        "repeat": "Powtórz",
-        "reply": "Odpowiedz",
-        "favorite": "Dodaj do ulubionych",
-        "add_reaction": "Dodaj reakcję",
-        "user_settings": "Ustawienia użytkownika",
-        "accept_follow_request": "Akceptuj prośbę o możliwość obserwacji",
-        "reject_follow_request": "Odrzuć prośbę o możliwość obserwacji"
-    },
-    "upload": {
-        "error": {
-            "base": "Wysyłanie nie powiodło się.",
-            "file_too_big": "Zbyt duży plik [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-            "default": "Spróbuj ponownie później"
-        },
-        "file_size_units": {
-            "B": "B",
-            "KiB": "KiB",
-            "MiB": "MiB",
-            "GiB": "GiB",
-            "TiB": "TiB"
-        }
-    },
-    "search": {
-        "people": "Ludzie",
-        "hashtags": "Hasztagi",
-        "person_talking": "{count} osoba rozmawia o tym",
-        "people_talking": "{count} osób rozmawia o tym",
-        "no_results": "Brak wyników"
-    },
-    "password_reset": {
-        "forgot_password": "Zapomniałeś(-aś) hasła?",
-        "password_reset": "Reset hasła",
-        "instruction": "Wprowadź swój adres email lub nazwę użytkownika. Wyślemy ci link z którym możesz zresetować hasło.",
-        "placeholder": "Twój email lub nazwa użytkownika",
-        "check_email": "Sprawdź pocztę, aby uzyskać link do zresetowania hasła.",
-        "return_home": "Wróć do strony głównej",
-        "not_found": "Nie mogliśmy znaleźć tego emaila lub nazwy użytkownika.",
-        "too_many_requests": "Przekroczyłeś(-aś) limit prób, spróbuj ponownie później.",
-        "password_reset_disabled": "Resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji.",
-        "password_reset_required": "Musisz zresetować hasło, by się zalogować.",
-        "password_reset_required_but_mailer_is_disabled": "Musisz zresetować hasło, ale resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji."
+    "staff": "Administracja"
+  },
+  "chat": {
+    "title": "Czat"
+  },
+  "domain_mute_card": {
+    "mute": "Wycisz",
+    "mute_progress": "Wyciszam...",
+    "unmute": "Odcisz",
+    "unmute_progress": "Odciszam..."
+  },
+  "exporter": {
+    "export": "Eksportuj",
+    "processing": "Przetwarzam, za chwilę zostaniesz zapytany(-na) o ściągnięcie pliku"
+  },
+  "features_panel": {
+    "chat": "Czat",
+    "gopher": "Gopher",
+    "media_proxy": "Proxy mediów",
+    "scope_options": "Ustawienia zakresu",
+    "text_limit": "Limit tekstu",
+    "title": "Funkcje",
+    "who_to_follow": "Propozycje obserwacji"
+  },
+  "finder": {
+    "error_fetching_user": "Błąd przy pobieraniu profilu",
+    "find_user": "Znajdź użytkownika"
+  },
+  "general": {
+    "apply": "Zastosuj",
+    "submit": "Wyślij",
+    "more": "Więcej",
+    "generic_error": "Wystąpił błąd",
+    "optional": "nieobowiązkowe",
+    "show_more": "Pokaż więcej",
+    "show_less": "Pokaż mniej",
+    "dismiss": "Odrzuć",
+    "cancel": "Anuluj",
+    "disable": "Wyłącz",
+    "enable": "Włącz",
+    "confirm": "Potwierdź",
+    "verify": "Zweryfikuj"
+  },
+  "image_cropper": {
+    "crop_picture": "Przytnij obrazek",
+    "save": "Zapisz",
+    "save_without_cropping": "Zapisz bez przycinania",
+    "cancel": "Anuluj"
+  },
+  "importer": {
+    "submit": "Wyślij",
+    "success": "Zaimportowano pomyślnie.",
+    "error": "Wystąpił błąd podczas importowania pliku."
+  },
+  "login": {
+    "login": "Zaloguj",
+    "description": "Zaloguj używając OAuth",
+    "logout": "Wyloguj",
+    "password": "Hasło",
+    "placeholder": "n.p. lain",
+    "register": "Zarejestruj",
+    "username": "Użytkownik",
+    "hint": "Zaloguj się, aby dołączyć do dyskusji",
+    "authentication_code": "Kod weryfikacyjny",
+    "enter_recovery_code": "Wprowadź kod zapasowy",
+    "enter_two_factor_code": "Wprowadź kod weryfikacyjny",
+    "recovery_code": "Kod zapasowy",
+    "heading": {
+      "totp": "Weryfikacja dwuetapowa",
+      "recovery": "Zapasowa weryfikacja dwuetapowa"
     }
+  },
+  "media_modal": {
+    "previous": "Poprzednie",
+    "next": "Następne"
+  },
+  "nav": {
+    "about": "O nas",
+    "administration": "Administracja",
+    "back": "Wróć",
+    "chat": "Lokalny czat",
+    "friend_requests": "Prośby o możliwość obserwacji",
+    "mentions": "Wzmianki",
+    "interactions": "Interakcje",
+    "dms": "Wiadomości prywatne",
+    "public_tl": "Publiczna oś czasu",
+    "timeline": "Oś czasu",
+    "twkn": "Cała znana sieć",
+    "user_search": "Wyszukiwanie użytkowników",
+    "search": "Wyszukiwanie",
+    "who_to_follow": "Sugestie obserwacji",
+    "preferences": "Preferencje"
+  },
+  "notifications": {
+    "broken_favorite": "Nieznany status, szukam go…",
+    "favorited_you": "dodał(-a) twój status do ulubionych",
+    "followed_you": "obserwuje cię",
+    "load_older": "Załaduj starsze powiadomienia",
+    "notifications": "Powiadomienia",
+    "read": "Przeczytane!",
+    "repeated_you": "powtórzył(-a) twój status",
+    "no_more_notifications": "Nie masz więcej powiadomień",
+    "migrated_to": "wyemigrował do",
+    "reacted_with": "zareagował z {0}",
+    "follow_request": "chce ciebie obserwować"
+  },
+  "polls": {
+    "add_poll": "Dodaj ankietę",
+    "add_option": "Dodaj opcję",
+    "option": "Opcja",
+    "votes": "głosów",
+    "vote": "Głosuj",
+    "type": "Typ ankiety",
+    "single_choice": "jednokrotnego wyboru",
+    "multiple_choices": "wielokrotnego wyboru",
+    "expiry": "Czas trwania ankiety",
+    "expires_in": "Ankieta kończy się za {0}",
+    "expired": "Ankieta skończyła się {0} temu",
+    "not_enough_options": "Zbyt mało unikalnych opcji w ankiecie"
+  },
+  "emoji": {
+    "stickers": "Naklejki",
+    "emoji": "Emoji",
+    "keep_open": "Zostaw selektor otwarty",
+    "search_emoji": "Wyszukaj emoji",
+    "add_emoji": "Wstaw emoji",
+    "custom": "Niestandardowe emoji",
+    "unicode": "Emoji unicode",
+    "load_all_hint": "Załadowano pierwsze {saneAmount} emoji, Załadowanie wszystkich emoji może spowodować problemy z wydajnością.",
+    "load_all": "Ładuję wszystkie {emojiAmount} emoji"
+  },
+  "interactions": {
+    "favs_repeats": "Powtórzenia i ulubione",
+    "follows": "Nowi obserwujący",
+    "moves": "Użytkownik migruje",
+    "load_older": "Załaduj starsze interakcje"
+  },
+  "post_status": {
+    "new_status": "Dodaj nowy status",
+    "account_not_locked_warning": "Twoje konto nie jest {0}. Każdy może cię zaobserwować aby zobaczyć wpisy tylko dla obserwujących.",
+    "account_not_locked_warning_link": "zablokowane",
+    "attachments_sensitive": "Oznacz załączniki jako wrażliwe",
+    "content_type": {
+      "text/plain": "Czysty tekst",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
+    },
+    "content_warning": "Temat (nieobowiązkowy)",
+    "default": "Właśnie wróciłem z kościoła",
+    "direct_warning_to_all": "Ten wpis zobaczą wszystkie osoby, o których wspomniałeś(-aś).",
+    "direct_warning_to_first_only": "Ten wpis zobaczą tylko te osoby, o których wspomniałeś(-aś) na początku wiadomości.",
+    "posting": "Wysyłanie",
+    "scope_notice": {
+      "public": "Ten post będzie widoczny dla każdego",
+      "private": "Ten post będzie widoczny tylko dla twoich obserwujących",
+      "unlisted": "Ten post nie będzie widoczny na publicznej osi czasu i całej znanej sieci"
+    },
+    "scope": {
+      "direct": "Bezpośredni – Tylko dla wspomnianych użytkowników",
+      "private": "Tylko dla obserwujących – Umieść dla osób, które cię obserwują",
+      "public": "Publiczny – Umieść na publicznych osiach czasu",
+      "unlisted": "Niewidoczny – Nie umieszczaj na publicznych osiach czasu"
+    }
+  },
+  "registration": {
+    "bio": "Bio",
+    "email": "E-mail",
+    "fullname": "Wyświetlana nazwa profilu",
+    "password_confirm": "Potwierdzenie hasła",
+    "registration": "Rejestracja",
+    "token": "Token zaproszenia",
+    "captcha": "CAPTCHA",
+    "new_captcha": "Naciśnij na obrazek, aby dostać nowy kod captcha",
+    "username_placeholder": "np. lain",
+    "fullname_placeholder": "np. Lain Iwakura",
+    "bio_placeholder": "e.g.\nCześć, jestem Lain.\nJestem dziewczynką z anime żyjącą na peryferiach Japonii. Możesz znać mnie z Wired.",
+    "validations": {
+      "username_required": "nie może być pusta",
+      "fullname_required": "nie może być pusta",
+      "email_required": "nie może być pusty",
+      "password_required": "nie może być puste",
+      "password_confirmation_required": "nie może być puste",
+      "password_confirmation_match": "musi być takie jak hasło"
+    }
+  },
+  "remote_user_resolver": {
+    "remote_user_resolver": "Wyszukiwarka użytkowników nietutejszych",
+    "searching_for": "Szukam",
+    "error": "Nie znaleziono."
+  },
+  "selectable_list": {
+    "select_all": "Zaznacz wszystko"
+  },
+  "settings": {
+    "app_name": "Nazwa aplikacji",
+    "security": "Bezpieczeństwo",
+    "enter_current_password_to_confirm": "Wprowadź obecne hasło, by potwierdzić twoją tożsamość",
+    "mfa": {
+      "otp": "OTP",
+      "setup_otp": "Ustaw OTP",
+      "wait_pre_setup_otp": "początkowe ustawianie OTP",
+      "confirm_and_enable": "Potwierdź i włącz OTP",
+      "title": "Weryfikacja dwuetapowa",
+      "generate_new_recovery_codes": "Wygeneruj nowe kody zapasowe",
+      "warning_of_generate_new_codes": "Po tym gdy wygenerujesz nowe kody zapasowe, stare przestaną działać.",
+      "recovery_codes": "Kody zapasowe.",
+      "waiting_a_recovery_codes": "Otrzymuję kody zapasowe...",
+      "recovery_codes_warning": "Spisz kody na kartce papieru, albo zapisz je w bezpiecznym miejscu - inaczej nie zobaczysz ich już nigdy. Jeśli stracisz dostęp do twojej aplikacji 2FA i kodów zapasowych, nie będziesz miał(-a) dostępu do swojego konta.",
+      "authentication_methods": "Metody weryfikacji",
+      "scan": {
+        "title": "Skanuj",
+        "desc": "Zeskanuj ten kod QR używając twojej aplikacji 2FA albo wpisz ten klucz:",
+        "secret_code": "Klucz"
+      },
+      "verify": {
+        "desc": "By włączyć weryfikację dwuetapową, wpisz kod z twojej aplikacji 2FA:"
+      }
+    },
+    "allow_following_move": "Zezwalaj na automatyczną obserwację gdy obserwowane konto migruje",
+    "attachmentRadius": "Załączniki",
+    "attachments": "Załączniki",
+    "autoload": "Włącz automatyczne ładowanie po przewinięciu do końca strony",
+    "avatar": "Awatar",
+    "avatarAltRadius": "Awatary (powiadomienia)",
+    "avatarRadius": "Awatary",
+    "background": "Tło",
+    "bio": "Bio",
+    "block_export": "Eksport blokad",
+    "block_export_button": "Eksportuj twoje blokady do pliku .csv",
+    "block_import": "Import blokad",
+    "block_import_error": "Wystąpił błąd podczas importowania blokad",
+    "blocks_imported": "Zaimportowano blokady, przetwarzanie może zająć trochę czasu.",
+    "blocks_tab": "Bloki",
+    "btnRadius": "Przyciski",
+    "cBlue": "Niebieski (odpowiedz, obserwuj)",
+    "cGreen": "Zielony (powtórzenia)",
+    "cOrange": "Pomarańczowy (ulubione)",
+    "cRed": "Czerwony (anuluj)",
+    "change_email": "Zmień email",
+    "change_email_error": "Wystąpił problem podczas zmiany emaila.",
+    "changed_email": "Pomyślnie zmieniono email!",
+    "change_password": "Zmień hasło",
+    "change_password_error": "Podczas zmiany hasła wystąpił problem.",
+    "changed_password": "Pomyślnie zmieniono hasło!",
+    "collapse_subject": "Zwijaj posty z tematami",
+    "composing": "Pisanie",
+    "confirm_new_password": "Potwierdź nowe hasło",
+    "current_avatar": "Twój obecny awatar",
+    "current_password": "Obecne hasło",
+    "current_profile_banner": "Twój obecny banner profilu",
+    "data_import_export_tab": "Import/eksport danych",
+    "default_vis": "Domyślny zakres widoczności",
+    "delete_account": "Usuń konto",
+    "delete_account_description": "Trwale usuń dane i zdezaktywuj konto.",
+    "delete_account_error": "Wystąpił problem z usuwaniem twojego konta. Jeżeli problem powtarza się, poinformuj administratora swojej instancji.",
+    "delete_account_instructions": "Wprowadź swoje hasło w poniższe pole aby potwierdzić usunięcie konta.",
+    "discoverable": "Zezwól na odkrywanie tego konta w wynikach wyszukiwania i innych usługach",
+    "domain_mutes": "Domeny",
+    "avatar_size_instruction": "Zalecany minimalny rozmiar awatarów to 150x150 pikseli.",
+    "pad_emoji": "Dodaj odstęp z obu stron emoji podczas dodawania selektorem",
+    "emoji_reactions_on_timeline": "Pokaż reakcje emoji na osi czasu",
+    "export_theme": "Zapisz motyw",
+    "filtering": "Filtrowanie",
+    "filtering_explanation": "Wszystkie statusy zawierające te słowa będą wyciszone. Jedno słowo na linijkę.",
+    "follow_export": "Eksport obserwowanych",
+    "follow_export_button": "Eksportuj swoją listę obserwowanych do pliku CSV",
+    "follow_import": "Import obserwowanych",
+    "follow_import_error": "Błąd przy importowaniu obserwowanych",
+    "follows_imported": "Obserwowani zaimportowani! Przetwarzanie może trochę potrwać.",
+    "accent": "Akcent",
+    "foreground": "Pierwszy plan",
+    "general": "Ogólne",
+    "hide_attachments_in_convo": "Ukrywaj załączniki w rozmowach",
+    "hide_attachments_in_tl": "Ukrywaj załączniki w osi czasu",
+    "hide_muted_posts": "Ukrywaj wpisy wyciszonych użytkowników",
+    "max_thumbnails": "Maksymalna liczba miniatur w poście",
+    "hide_isp": "Ukryj panel informacji o instancji",
+    "preload_images": "Ładuj wstępnie obrazy",
+    "use_one_click_nsfw": "Otwieraj załączniki NSFW jednym kliknięciem",
+    "hide_post_stats": "Ukrywaj statysyki postów (np. liczbę polubień)",
+    "hide_user_stats": "Ukrywaj statysyki użytkowników (np. liczbę obserwujących)",
+    "hide_filtered_statuses": "Ukrywaj filtrowane statusy",
+    "import_blocks_from_a_csv_file": "Importuj blokady z pliku CSV",
+    "import_followers_from_a_csv_file": "Importuj obserwowanych z pliku CSV",
+    "import_theme": "Załaduj motyw",
+    "inputRadius": "Pola tekstowe",
+    "checkboxRadius": "Pola wyboru",
+    "instance_default": "(domyślnie: {value})",
+    "instance_default_simple": "(domyślne)",
+    "interface": "Interfejs",
+    "interfaceLanguage": "Język interfejsu",
+    "invalid_theme_imported": "Wybrany plik nie jest obsługiwanym motywem Pleromy. Nie dokonano zmian w twoim motywie.",
+    "limited_availability": "Niedostępne w twojej przeglądarce",
+    "links": "Łącza",
+    "lock_account_description": "Spraw, by konto mogli wyświetlać tylko zatwierdzeni obserwujący",
+    "loop_video": "Zapętlaj filmy",
+    "loop_video_silent_only": "Zapętlaj tylko filmy bez dźwięku (np. mastodonowe „gify”)",
+    "mutes_tab": "Wyciszenia",
+    "play_videos_in_modal": "Odtwarzaj filmy bezpośrednio w przeglądarce mediów",
+    "use_contain_fit": "Nie przycinaj załączników na miniaturach",
+    "name": "Imię",
+    "name_bio": "Imię i bio",
+    "new_email": "Nowy email",
+    "new_password": "Nowe hasło",
+    "notification_visibility": "Rodzaje powiadomień do wyświetlania",
+    "notification_visibility_follows": "Obserwacje",
+    "notification_visibility_likes": "Ulubione",
+    "notification_visibility_mentions": "Wzmianki",
+    "notification_visibility_repeats": "Powtórzenia",
+    "notification_visibility_moves": "Użytkownik migruje",
+    "notification_visibility_emoji_reactions": "Reakcje",
+    "no_rich_text_description": "Usuwaj formatowanie ze wszystkich postów",
+    "no_blocks": "Bez blokad",
+    "no_mutes": "Bez wyciszeń",
+    "hide_follows_description": "Nie pokazuj kogo obserwuję",
+    "hide_followers_description": "Nie pokazuj kto mnie obserwuje",
+    "hide_follows_count_description": "Nie pokazuj licznika obserwowanych",
+    "hide_followers_count_description": "Nie pokazuj licznika obserwujących",
+    "show_admin_badge": "Pokazuj odznakę Administrator na moim profilu",
+    "show_moderator_badge": "Pokazuj odznakę Moderator na moim profilu",
+    "nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)",
+    "oauth_tokens": "Tokeny OAuth",
+    "token": "Token",
+    "refresh_token": "Odśwież token",
+    "valid_until": "Ważne do",
+    "revoke_token": "Odwołać",
+    "panelRadius": "Panele",
+    "pause_on_unfocused": "Wstrzymuj strumieniowanie kiedy karta nie jest aktywna",
+    "presets": "Gotowe motywy",
+    "profile_background": "Tło profilu",
+    "profile_banner": "Banner profilu",
+    "profile_tab": "Profil",
+    "radii_help": "Ustaw zaokrąglenie krawędzi interfejsu (w pikselach)",
+    "replies_in_timeline": "Odpowiedzi na osi czasu",
+    "reply_link_preview": "Włącz dymek z podglądem postu po najechaniu na znak odpowiedzi",
+    "reply_visibility_all": "Pokazuj wszystkie odpowiedzi",
+    "reply_visibility_following": "Pokazuj tylko odpowiedzi skierowane do mnie i osób które obserwuję",
+    "reply_visibility_self": "Pokazuj tylko odpowiedzi skierowane do mnie",
+    "autohide_floating_post_button": "Ukryj automatycznie przycisk \"Nowy post\" (mobile)",
+    "saving_err": "Nie udało się zapisać ustawień",
+    "saving_ok": "Zapisano ustawienia",
+    "search_user_to_block": "Wyszukaj kogo chcesz zablokować",
+    "search_user_to_mute": "Wyszukaj kogo chcesz wyciszyć",
+    "security_tab": "Bezpieczeństwo",
+    "scope_copy": "Kopiuj zakres podczas odpowiadania (DM-y zawsze są kopiowane)",
+    "minimal_scopes_mode": "Zminimalizuj opcje wyboru zakresu postów",
+    "set_new_avatar": "Ustaw nowy awatar",
+    "set_new_profile_background": "Ustaw nowe tło profilu",
+    "set_new_profile_banner": "Ustaw nowy banner profilu",
+    "settings": "Ustawienia",
+    "subject_input_always_show": "Zawsze pokazuj pole tematu",
+    "subject_line_behavior": "Kopiuj temat podczas odpowiedzi",
+    "subject_line_email": "Jak w mailach – „re: temat”",
+    "subject_line_mastodon": "Jak na Mastodonie – po prostu kopiuj",
+    "subject_line_noop": "Nie kopiuj",
+    "post_status_content_type": "Post status content type",
+    "stop_gifs": "Odtwarzaj GIFy po najechaniu kursorem",
+    "streaming": "Włącz automatycznie strumieniowanie nowych postów gdy jesteś na początku strony",
+    "user_mutes": "Użytkownicy",
+    "useStreamingApi": "Otrzymuj posty i powiadomienia w czasie rzeczywistym",
+    "useStreamingApiWarning": "(Niezalecane, eksperymentalne, pomija posty)",
+    "text": "Tekst",
+    "theme": "Motyw",
+    "theme_help": "Użyj kolorów w notacji szesnastkowej (#rrggbb), by stworzyć swój motyw.",
+    "theme_help_v2_1": "Możesz też zastąpić kolory i widoczność poszczególnych komponentów przełączając pola wyboru, użyj „Wyczyść wszystko” aby usunąć wszystkie zastąpienia.",
+    "theme_help_v2_2": "Ikony pod niektórych wpisami są wskaźnikami kontrastu pomiędzy tłem a tekstem, po najechaniu na nie otrzymasz szczegółowe informacje. Zapamiętaj, że jeżeli używasz przezroczystości, wskaźniki pokazują najgorszy możliwy przypadek.",
+    "tooltipRadius": "Etykiety/alerty",
+    "type_domains_to_mute": "Wpisz domeny, które chcesz wyciszyć",
+    "upload_a_photo": "Wyślij zdjęcie",
+    "user_settings": "Ustawienia użytkownika",
+    "values": {
+      "false": "nie",
+      "true": "tak"
+    },
+    "fun": "Zabawa",
+    "greentext": "Memiczne strzałki",
+    "notifications": "Powiadomienia",
+    "notification_setting": "Otrzymuj powiadomienia od:",
+    "notification_setting_follows": "Ludzi których obserwujesz",
+    "notification_setting_non_follows": "Ludzi których nie obserwujesz",
+    "notification_setting_followers": "Ludzi którzy obserwują ciebie",
+    "notification_setting_non_followers": "Ludzi którzy nie obserwują ciebie",
+    "notification_mutes": "By przestać otrzymywać powiadomienia od jednego użytkownika, wycisz go.",
+    "notification_blocks": "Blokowanie uzytkownika zatrzymuje wszystkie powiadomienia i odsubskrybowuje go.",
+    "enable_web_push_notifications": "Włącz powiadomienia push",
+    "style": {
+      "switcher": {
+        "keep_color": "Zachowaj kolory",
+        "keep_shadows": "Zachowaj cienie",
+        "keep_opacity": "Zachowaj widoczność",
+        "keep_roundness": "Zachowaj zaokrąglenie",
+        "keep_fonts": "Zachowaj czcionki",
+        "save_load_hint": "Opcje „zachowaj” pozwalają na pozostanie przy obecnych opcjach po wybraniu lub załadowaniu motywu, jak i przechowywanie ich podczas eksportowania motywu. Jeżeli wszystkie opcje są odznaczone, eksportowanie motywu spowoduje zapisanie wszystkiego.",
+        "reset": "Wyzeruj",
+        "clear_all": "Wyczyść wszystko",
+        "clear_opacity": "Wyczyść widoczność",
+        "load_theme": "Załaduj motyw",
+        "keep_as_is": "Zostaw po staremu",
+        "use_snapshot": "Stara wersja",
+        "use_source": "Nowa wersja",
+        "help": {
+          "upgraded_from_v2": "PleromaFE zostało zaaktualizowane, motyw może wyglądać nieco inaczej niż zapamiętałeś(-aś).",
+          "v2_imported": "Plik który zaimportowałeś(-aś) został stworzony dla starszego FE. Próbujemy zwiększyć kompatybilność, lecz wciąż mogą występować rozbieżności.",
+          "future_version_imported": "Plik który zaimportowałeś(-aś) został stworzony w nowszej wersji FE.",
+          "older_version_imported": "Plik który zaimportowałeś(-aś) został stworzony w starszej wersji FE.",
+          "snapshot_present": "Migawka motywu jest załadowana, więc wszystkie wartości zostały nadpisane. Zamiast tego możesz załadować właściwe dane motywu.",
+          "snapshot_missing": "Nie znaleziono migawki motywu w pliku, więc motyw może wyglądać inaczej niż pierwotnie zaplanowano.",
+          "fe_upgraded": "Silnik motywów PleromaFE został zaaktualizowany.",
+          "fe_downgraded": "Wersja PleromaFE została cofnięta.",
+          "migration_snapshot_ok": "Żeby być bezpiecznym, migawka motywu została załadowana. Możesz spróbować załadować dane motywu.",
+          "migration_napshot_gone": "Z jakiegoś powodu migawka zniknęła, niektóre rzeczy mogą wyglądać inaczej niż zapamiętałeś(-aś).",
+          "snapshot_source_mismatch": "Konflikt wersji: najprawdopodobniej FE zostało cofnięte do poprzedniej wersji i zaktualizowane ponownie, jeśli zmieniłeś(-aś) motyw używając starszej wersji FE, najprawdopodobniej chcesz używać starszej wersji, w przeciwnym razie użyj nowej wersji."
+        }
+      },
+      "common": {
+        "color": "Kolor",
+        "opacity": "Widoczność",
+        "contrast": {
+          "hint": "Współczynnik kontrastu wynosi {ratio}, {level} {context}",
+          "level": {
+            "aa": "spełnia wymogi poziomu AA (minimalne)",
+            "aaa": "spełnia wymogi poziomu AAA (zalecane)",
+            "bad": "nie spełnia żadnych wymogów dostępności"
+          },
+          "context": {
+            "18pt": "dla dużego tekstu (18pt+)",
+            "text": "dla tekstu"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "Ogólne",
+        "main": "Ogólne kolory",
+        "foreground_hint": "Zajrzyj do karty „Zaawansowane”, aby uzyskać dokładniejszą kontrolę",
+        "rgbo": "Ikony, wyróżnienia, odznaki"
+      },
+      "advanced_colors": {
+        "_tab_label": "Zaawansowane",
+        "alert": "Tło alertu",
+        "alert_error": "Błąd",
+        "alert_warning": "Ostrzeżenie",
+        "alert_neutral": "Neutralne",
+        "post": "Posty/Bio użytkowników",
+        "badge": "Tło odznaki",
+        "popover": "Etykiety, menu, popovery",
+        "badge_notification": "Powiadomienie",
+        "panel_header": "Nagłówek panelu",
+        "top_bar": "Górny pasek",
+        "borders": "Granice",
+        "buttons": "Przyciski",
+        "inputs": "Pola wejścia",
+        "faint_text": "Zanikający tekst",
+        "underlay": "Podkład",
+        "poll": "Wykres ankiety",
+        "icons": "Ikony",
+        "highlight": "Podświetlone elementy",
+        "pressed": "Naciśnięte",
+        "selectedPost": "Wybrany post",
+        "selectedMenu": "Wybrany element menu",
+        "disabled": "Wyłączone",
+        "toggled": "Przełączone",
+        "tabs": "Karty"
+      },
+      "radii": {
+        "_tab_label": "Zaokrąglenie"
+      },
+      "shadows": {
+        "_tab_label": "Cień i podświetlenie",
+        "component": "Komponent",
+        "override": "Zastąp",
+        "shadow_id": "Cień #{value}",
+        "blur": "Rozmycie",
+        "spread": "Szerokość",
+        "inset": "Inset",
+        "hintV3": "Dla cieni możesz również użyć notacji {0} by użyć inny slot koloru.",
+        "filter_hint": {
+          "always_drop_shadow": "Ostrzeżenie, ten cień zawsze używa {0} jeżeli to obsługiwane przez przeglądarkę.",
+          "drop_shadow_syntax": "{0} nie obsługuje parametru {1} i słowa kluczowego {2}.",
+          "avatar_inset": "Pamiętaj że użycie jednocześnie cieni inset i nie inset na awatarach może dać nieoczekiwane wyniki z przezroczystymi awatarami.",
+          "spread_zero": "Cienie o ujemnej szerokości będą widoczne tak, jakby wynosiła ona zero",
+          "inset_classic": "Cienie inset będą używały {0}"
+        },
+        "components": {
+          "panel": "Panel",
+          "panelHeader": "Nagłówek panelu",
+          "topBar": "Górny pasek",
+          "avatar": "Awatar użytkownika (w widoku profilu)",
+          "avatarStatus": "Awatar użytkownika (w widoku wpisu)",
+          "popup": "Wyskakujące okna i podpowiedzi",
+          "button": "Przycisk",
+          "buttonHover": "Przycisk (po najechaniu)",
+          "buttonPressed": "Przycisk (naciśnięty)",
+          "buttonPressedHover": "Przycisk(naciśnięty+najechany)",
+          "input": "Pole wejścia"
+        }
+      },
+      "fonts": {
+        "_tab_label": "Czcionki",
+        "help": "Wybierz czcionkę używaną przez elementy UI. Jeżeli wybierzesz niestandardową, musisz wpisać dokładnie tę nazwę, pod którą pojawia się w systemie.",
+        "components": {
+          "interface": "Interfejs",
+          "input": "Pola wejścia",
+          "post": "Tekst postu",
+          "postCode": "Tekst o stałej szerokości znaków w sformatowanym poście"
+        },
+        "family": "Nazwa czcionki",
+        "size": "Rozmiar (w pikselach)",
+        "weight": "Grubość",
+        "custom": "Niestandardowa"
+      },
+      "preview": {
+        "header": "Podgląd",
+        "content": "Zawartość",
+        "error": "Przykładowy błąd",
+        "button": "Przycisk",
+        "text": "Trochę więcej {0} i {1}",
+        "mono": "treści",
+        "input": "Właśnie wróciłem z kościoła",
+        "faint_link": "pomocny podręcznik",
+        "fine_print": "Przeczytaj nasz {0}, aby nie nauczyć się niczego przydatnego!",
+        "header_faint": "W porządku",
+        "checkbox": "Przeleciałem(-am) przez zasady użytkowania",
+        "link": "i fajny mały odnośnik"
+      }
+    },
+    "version": {
+      "title": "Wersja",
+      "backend_version": "Wersja back-endu",
+      "frontend_version": "Wersja front-endu"
+    },
+    "notification_setting_privacy": "Prywatność",
+    "notification_setting_filters": "Filtry",
+    "notification_setting_privacy_option": "Ukryj nadawcę i zawartość powiadomień push"
+  },
+  "time": {
+    "day": "{0} dzień",
+    "days": "{0} dni",
+    "day_short": "{0} d",
+    "days_short": "{0} d",
+    "hour": "{0} godzina",
+    "hours": "{0} godzin",
+    "hour_short": "{0} godz.",
+    "hours_short": "{0} godz.",
+    "in_future": "za {0}",
+    "in_past": "{0} temu",
+    "minute": "{0} minuta",
+    "minutes": "{0} minut",
+    "minute_short": "{0} min",
+    "minutes_short": "{0} min",
+    "month": "{0} miesiąc",
+    "months": "{0} miesięcy",
+    "month_short": "{0} mies.",
+    "months_short": "{0} mies.",
+    "now": "teraz",
+    "now_short": "teraz",
+    "second": "{0} sekunda",
+    "seconds": "{0} sekund",
+    "second_short": "{0} s",
+    "seconds_short": "{0} s",
+    "week": "{0} tydzień",
+    "weeks": "{0} tygodni",
+    "week_short": "{0} tydz.",
+    "weeks_short": "{0} tyg.",
+    "year": "{0} rok",
+    "years": "{0} lata",
+    "year_short": "{0} r.",
+    "years_short": "{0} lata"
+  },
+  "timeline": {
+    "collapse": "Zwiń",
+    "conversation": "Rozmowa",
+    "error_fetching": "Błąd pobierania",
+    "load_older": "Załaduj starsze statusy",
+    "no_retweet_hint": "Wpis oznaczony jako tylko dla obserwujących lub bezpośredni nie może zostać powtórzony",
+    "repeated": "powtórzył(-a)",
+    "show_new": "Pokaż nowe",
+    "up_to_date": "Na bieżąco",
+    "no_more_statuses": "Brak kolejnych statusów",
+    "no_statuses": "Brak statusów"
+  },
+  "status": {
+    "favorites": "Ulubione",
+    "repeats": "Powtórzenia",
+    "delete": "Usuń status",
+    "pin": "Przypnij na profilu",
+    "unpin": "Odepnij z profilu",
+    "pinned": "Przypnięte",
+    "delete_confirm": "Czy naprawdę chcesz usunąć ten status?",
+    "reply_to": "Odpowiedź dla",
+    "replies_list": "Odpowiedzi:",
+    "mute_conversation": "Wycisz konwersację",
+    "unmute_conversation": "Odcisz konwersację",
+    "status_unavailable": "Status niedostępny",
+    "copy_link": "Kopiuj link do statusu"
+  },
+  "user_card": {
+    "approve": "Przyjmij",
+    "block": "Zablokuj",
+    "blocked": "Zablokowany!",
+    "deny": "Odrzuć",
+    "favorites": "Ulubione",
+    "follow": "Obserwuj",
+    "follow_sent": "Wysłano prośbę!",
+    "follow_progress": "Wysyłam prośbę…",
+    "follow_again": "Wysłać prośbę ponownie?",
+    "follow_unfollow": "Przestań obserwować",
+    "followees": "Obserwowani",
+    "followers": "Obserwujący",
+    "following": "Obserwowany!",
+    "follows_you": "Obserwuje cię!",
+    "hidden": "Ukryte",
+    "its_you": "To ty!",
+    "media": "Media",
+    "mention": "Wspomnienie",
+    "mute": "Wycisz",
+    "muted": "Wyciszony(-a)",
+    "per_day": "dziennie",
+    "remote_follow": "Zdalna obserwacja",
+    "report": "Zgłoś",
+    "statuses": "Statusy",
+    "subscribe": "Subskrybuj",
+    "unsubscribe": "Odsubskrybuj",
+    "unblock": "Odblokuj",
+    "unblock_progress": "Odblokowuję…",
+    "block_progress": "Blokuję…",
+    "unmute": "Cofnij wyciszenie",
+    "unmute_progress": "Cofam wyciszenie…",
+    "mute_progress": "Wyciszam…",
+    "hide_repeats": "Ukryj powtórzenia",
+    "show_repeats": "Pokaż powtórzenia",
+    "admin_menu": {
+      "moderation": "Moderacja",
+      "grant_admin": "Przyznaj admina",
+      "revoke_admin": "Odwołaj admina",
+      "grant_moderator": "Przyznaj moderatora",
+      "revoke_moderator": "Odwołaj moderatora",
+      "activate_account": "Aktywuj konto",
+      "deactivate_account": "Dezaktywuj konto",
+      "delete_account": "Usuń konto",
+      "force_nsfw": "Oznacz wszystkie posty jako NSFW",
+      "strip_media": "Usuń multimedia z postów",
+      "force_unlisted": "Wymuś posty na niepubliczne",
+      "sandbox": "Wymuś by posty były tylko dla obserwujących",
+      "disable_remote_subscription": "Zakaż obserwowania użytkownika ze zdalnych instancji",
+      "disable_any_subscription": "Zakaż całkowicie obserwowania użytkownika",
+      "quarantine": "Zakaż federowania postów od tego użytkownika",
+      "delete_user": "Usuń użytkownika",
+      "delete_user_confirmation": "Czy jesteś absolutnie pewny(-a)? Ta operacja nie może być cofnięta."
+    }
+  },
+  "user_profile": {
+    "timeline_title": "Oś czasu użytkownika",
+    "profile_does_not_exist": "Przepraszamy, ten profil nie istnieje.",
+    "profile_loading_error": "Przepraszamy, wystąpił błąd podczas ładowania tego profilu."
+  },
+  "user_reporting": {
+    "title": "Raportowanie {0}",
+    "add_comment_description": "Zgłoszenie zostanie wysłane do moderatorów instancji. Możesz dodać powód dlaczego zgłaszasz owe konto poniżej:",
+    "additional_comments": "Dodatkowe komentarze",
+    "forward_description": "To konto jest z innego serwera. Wysłać również tam kopię zgłoszenia?",
+    "forward_to": "Przekaż do {0}",
+    "submit": "Wyślij",
+    "generic_error": "Wystąpił błąd podczas przetwarzania twojej prośby."
+  },
+  "who_to_follow": {
+    "more": "Więcej",
+    "who_to_follow": "Propozycje obserwacji"
+  },
+  "tool_tip": {
+    "media_upload": "Wyślij media",
+    "repeat": "Powtórz",
+    "reply": "Odpowiedz",
+    "favorite": "Dodaj do ulubionych",
+    "add_reaction": "Dodaj reakcję",
+    "user_settings": "Ustawienia użytkownika",
+    "accept_follow_request": "Akceptuj prośbę o możliwość obserwacji",
+    "reject_follow_request": "Odrzuć prośbę o możliwość obserwacji"
+  },
+  "upload": {
+    "error": {
+      "base": "Wysyłanie nie powiodło się.",
+      "file_too_big": "Zbyt duży plik [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+      "default": "Spróbuj ponownie później"
+    },
+    "file_size_units": {
+      "B": "B",
+      "KiB": "KiB",
+      "MiB": "MiB",
+      "GiB": "GiB",
+      "TiB": "TiB"
+    }
+  },
+  "search": {
+    "people": "Ludzie",
+    "hashtags": "Hasztagi",
+    "person_talking": "{count} osoba rozmawia o tym",
+    "people_talking": "{count} osób rozmawia o tym",
+    "no_results": "Brak wyników"
+  },
+  "password_reset": {
+    "forgot_password": "Zapomniałeś(-aś) hasła?",
+    "password_reset": "Reset hasła",
+    "instruction": "Wprowadź swój adres email lub nazwę użytkownika. Wyślemy ci link z którym możesz zresetować hasło.",
+    "placeholder": "Twój email lub nazwa użytkownika",
+    "check_email": "Sprawdź pocztę, aby uzyskać link do zresetowania hasła.",
+    "return_home": "Wróć do strony głównej",
+    "not_found": "Nie mogliśmy znaleźć tego emaila lub nazwy użytkownika.",
+    "too_many_requests": "Przekroczyłeś(-aś) limit prób, spróbuj ponownie później.",
+    "password_reset_disabled": "Resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji.",
+    "password_reset_required": "Musisz zresetować hasło, by się zalogować.",
+    "password_reset_required_but_mailer_is_disabled": "Musisz zresetować hasło, ale resetowanie hasła jest wyłączone. Proszę skontaktuj się z administratorem tej instancji."
+  }
 }
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 357d97a8..69b22618 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -1,478 +1,478 @@
 {
-    "chat": {
-        "title": "Чат"
-    },
-    "finder": {
-        "error_fetching_user": "Пользователь не найден",
-        "find_user": "Найти пользователя"
-    },
-    "general": {
-        "apply": "Применить",
-        "submit": "Отправить",
-        "cancel": "Отмена",
-        "disable": "Оключить",
-        "enable": "Включить",
-        "confirm": "Подтвердить",
-        "verify": "Проверить",
-        "more": "Больше",
-        "generic_error": "Произошла ошибка",
-        "optional": "не обязательно",
-        "show_less": "Показать меньше",
-        "show_more": "Показать больше"
-    },
-    "login": {
-        "login": "Войти",
-        "logout": "Выйти",
-        "password": "Пароль",
-        "placeholder": "e.c. lain",
-        "register": "Зарегистрироваться",
-        "username": "Имя пользователя",
-        "authentication_code": "Код аутентификации",
-        "enter_recovery_code": "Ввести код восстановления",
-        "enter_two_factor_code": "Ввести код аутентификации",
-        "recovery_code": "Код восстановления",
-        "heading": {
-            "TotpForm": "Двухфакторная аутентификация",
-            "RecoveryForm": "Two-factor recovery"
-        }
-    },
-    "nav": {
-        "back": "Назад",
-        "chat": "Локальный чат",
-        "mentions": "Упоминания",
-        "interactions": "Взаимодействия",
-        "public_tl": "Публичная лента",
-        "timeline": "Лента",
-        "twkn": "Федеративная лента",
-        "search": "Поиск",
-        "friend_requests": "Запросы на чтение"
-    },
-    "notifications": {
-        "broken_favorite": "Неизвестный статус, ищем...",
-        "favorited_you": "нравится ваш статус",
-        "followed_you": "начал(а) читать вас",
-        "load_older": "Загрузить старые уведомления",
-        "notifications": "Уведомления",
-        "read": "Прочесть",
-        "repeated_you": "повторил(а) ваш статус",
-        "follow_request": "хочет читать вас"
-    },
-    "interactions": {
-        "favs_repeats": "Повторы и фавориты",
-        "follows": "Новые подписки",
-        "load_older": "Загрузить старые взаимодействия"
-    },
-    "post_status": {
-        "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может начать читать вас чтобы видеть посты только для подписчиков.",
-        "account_not_locked_warning_link": "залочен",
-        "attachments_sensitive": "Вложения содержат чувствительный контент",
-        "content_warning": "Тема (не обязательно)",
-        "default": "Что нового?",
-        "direct_warning": "Этот пост будет виден только упомянутым пользователям",
-        "posting": "Отправляется",
-        "scope_notice": {
-            "public": "Этот пост будет виден всем",
-            "private": "Этот пост будет виден только вашим подписчикам",
-            "unlisted": "Этот пост не будет виден в публичной и федеративной ленте"
-        },
-        "scope": {
-            "direct": "Личное - этот пост видят только те кто в нём упомянут",
-            "private": "Для подписчиков - этот пост видят только подписчики",
-            "public": "Публичный - этот пост виден всем",
-            "unlisted": "Непубличный - этот пост не виден на публичных лентах"
-        }
-    },
-    "registration": {
-        "bio": "Описание",
-        "email": "Email",
-        "fullname": "Отображаемое имя",
-        "password_confirm": "Подтверждение пароля",
-        "registration": "Регистрация",
-        "token": "Код приглашения",
-        "validations": {
-            "username_required": "не должно быть пустым",
-            "fullname_required": "не должно быть пустым",
-            "email_required": "не должен быть пустым",
-            "password_required": "не должен быть пустым",
-            "password_confirmation_required": "не должно быть пустым",
-            "password_confirmation_match": "должно совпадать с паролем"
-        }
-    },
-    "settings": {
-        "enter_current_password_to_confirm": "Введите свой текущий пароль",
-        "mfa": {
-            "otp": "OTP",
-            "setup_otp": "Настройка OTP",
-            "wait_pre_setup_otp": "предварительная настройка OTP",
-            "confirm_and_enable": "Подтвердить и включить OTP",
-            "title": "Двухфакторная аутентификация",
-            "generate_new_recovery_codes": "Получить новые коды востановления",
-            "warning_of_generate_new_codes": "После получения новых кодов восстановления, старые больше не будут работать.",
-            "recovery_codes": "Коды восстановления.",
-            "waiting_a_recovery_codes": "Получение кодов восстановления ...",
-            "recovery_codes_warning": "Запишите эти коды и держите в безопасном месте - иначе вы их больше не увидите. Если вы потеряете доступ к OTP приложению - без резервных кодов вы больше не сможете залогиниться.",
-            "authentication_methods": "Методы аутентификации",
-            "scan": {
-                "title": "Сканирование",
-                "desc": "Используйте приложение для двухэтапной аутентификации для сканирования этого QR-код или введите текстовый ключ:",
-                "secret_code": "Ключ"
-            },
-            "verify": {
-                "desc": "Чтобы включить двухэтапную аутентификации, введите код из вашего приложение для двухэтапной аутентификации:"
-            }
-        },
-        "attachmentRadius": "Прикреплённые файлы",
-        "attachments": "Вложения",
-        "autoload": "Включить автоматическую загрузку при прокрутке вниз",
-        "avatar": "Аватар",
-        "avatarAltRadius": "Аватары в уведомлениях",
-        "avatarRadius": "Аватары",
-        "background": "Фон",
-        "bio": "Описание",
-        "btnRadius": "Кнопки",
-        "cBlue": "Ответить, читать",
-        "cGreen": "Повторить",
-        "cOrange": "Нравится",
-        "cRed": "Отменить",
-        "change_email": "Сменить email",
-        "change_email_error": "Произошла ошибка при попытке изменить email.",
-        "changed_email": "Email изменён успешно!",
-        "change_password": "Сменить пароль",
-        "change_password_error": "Произошла ошибка при попытке изменить пароль.",
-        "changed_password": "Пароль изменён успешно!",
-        "collapse_subject": "Сворачивать посты с темой",
-        "confirm_new_password": "Подтверждение нового пароля",
-        "current_avatar": "Текущий аватар",
-        "current_password": "Текущий пароль",
-        "current_profile_banner": "Текущий баннер профиля",
-        "data_import_export_tab": "Импорт / Экспорт данных",
-        "delete_account": "Удалить аккаунт",
-        "delete_account_description": "Удалить ваш аккаунт и все ваши сообщения.",
-        "delete_account_error": "Возникла ошибка в процессе удаления вашего аккаунта. Если это повторяется, свяжитесь с администратором вашего сервера.",
-        "delete_account_instructions": "Введите ваш пароль в поле ниже для подтверждения удаления.",
-        "export_theme": "Сохранить Тему",
-        "filtering": "Фильтрация",
-        "filtering_explanation": "Все статусы, содержащие данные слова, будут игнорироваться, по одному в строке",
-        "follow_export": "Экспортировать читаемых",
-        "follow_export_button": "Экспортировать читаемых в файл .csv",
-        "follow_export_processing": "Ведётся обработка, скоро вам будет предложено загрузить файл",
-        "follow_import": "Импортировать читаемых",
-        "follow_import_error": "Ошибка при импортировании читаемых",
-        "follows_imported": "Список читаемых импортирован. Обработка займёт некоторое время..",
-        "foreground": "Передний план",
-        "general": "Общие",
-        "hide_attachments_in_convo": "Прятать вложения в разговорах",
-        "hide_attachments_in_tl": "Прятать вложения в ленте",
-        "hide_isp": "Скрыть серверную панель",
-        "import_followers_from_a_csv_file": "Импортировать читаемых из файла .csv",
-        "import_theme": "Загрузить Тему",
-        "inputRadius": "Поля ввода",
-        "checkboxRadius": "Чекбоксы",
-        "instance_default": "(по умолчанию: {value})",
-        "instance_default_simple": "(по умолчанию)",
-        "interface": "Интерфейс",
-        "interfaceLanguage": "Язык интерфейса",
-        "limited_availability": "Не доступно в вашем браузере",
-        "links": "Ссылки",
-        "lock_account_description": "Аккаунт доступен только подтверждённым подписчикам",
-        "loop_video": "Зациливать видео",
-        "loop_video_silent_only": "Зацикливать только беззвучные видео (т.е. \"гифки\" с Mastodon)",
-        "name": "Имя",
-        "name_bio": "Имя и описание",
-        "new_email": "Новый email",
-        "new_password": "Новый пароль",
-        "fun": "Потешное",
-        "greentext": "Мемные стрелочки",
-        "notification_visibility": "Показывать уведомления",
-        "notification_visibility_follows": "Подписки",
-        "notification_visibility_likes": "Лайки",
-        "notification_visibility_mentions": "Упоминания",
-        "notification_visibility_repeats": "Повторы",
-        "no_rich_text_description": "Убрать форматирование из всех постов",
-        "hide_follows_description": "Не показывать кого я читаю",
-        "hide_followers_description": "Не показывать кто читает меня",
-        "hide_follows_count_description": "Не показывать число читаемых пользователей",
-        "hide_followers_count_description": "Не показывать число моих подписчиков",
-        "show_admin_badge": "Показывать значок администратора в моем профиле",
-        "show_moderator_badge": "Показывать значок модератора в моем профиле",
-        "nsfw_clickthrough": "Включить скрытие NSFW вложений",
-        "oauth_tokens": "OAuth токены",
-        "token": "Токен",
-        "refresh_token": "Рефреш токен",
-        "valid_until": "Годен до",
-        "revoke_token": "Удалить",
-        "panelRadius": "Панели",
-        "pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе",
-        "presets": "Пресеты",
-        "profile_background": "Фон профиля",
-        "profile_banner": "Баннер профиля",
-        "profile_tab": "Профиль",
-        "radii_help": "Скругление углов элементов интерфейса (в пикселях)",
-        "replies_in_timeline": "Ответы в ленте",
-        "reply_link_preview": "Включить предварительный просмотр ответа при наведении мыши",
-        "reply_visibility_all": "Показывать все ответы",
-        "reply_visibility_following": "Показывать только ответы мне или тех на кого я подписан",
-        "reply_visibility_self": "Показывать только ответы мне",
-        "autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)",
-        "saving_err": "Не удалось сохранить настройки",
-        "saving_ok": "Сохранено",
-        "security_tab": "Безопасность",
-        "scope_copy": "Копировать видимость поста при ответе (всегда включено для Личных Сообщений)",
-        "minimal_scopes_mode": "Минимизировать набор опций видимости поста",
-        "set_new_avatar": "Загрузить новый аватар",
-        "set_new_profile_background": "Загрузить новый фон профиля",
-        "set_new_profile_banner": "Загрузить новый баннер профиля",
-        "settings": "Настройки",
-        "subject_input_always_show": "Всегда показывать поле ввода темы",
-        "stop_gifs": "Проигрывать GIF анимации только при наведении",
-        "streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
-        "useStreamingApi": "Получать сообщения и уведомления в реальном времени",
-        "useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)",
-        "text": "Текст",
-        "theme": "Тема",
-        "theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",
-        "theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения.",
-        "theme_help_v2_2": "Под некоторыми полями ввода это идикаторы контрастности, наведите на них мышью чтобы узнать больше. Приспользовании прозрачности контраст расчитывается для наихудшего варианта.",
-        "tooltipRadius": "Всплывающие подсказки/уведомления",
-        "user_settings": "Настройки пользователя",
-        "values": {
-            "false": "нет",
-            "true": "да"
-        },
-        "style": {
-            "switcher": {
-                "keep_color": "Оставить цвета",
-                "keep_shadows": "Оставить тени",
-                "keep_opacity": "Оставить прозрачность",
-                "keep_roundness": "Оставить скругление",
-                "keep_fonts": "Оставить шрифты",
-                "save_load_hint": "Опции \"оставить...\" позволяют сохранить текущие настройки при выборе другой темы или импорта её из файла. Так же они влияют на то какие компоненты будут сохранены при экспорте темы. Когда все галочки сняты все компоненты будут экспортированы.",
-                "reset": "Сбросить",
-                "clear_all": "Очистить всё",
-                "clear_opacity": "Очистить прозрачность"
-            },
-            "common": {
-                "color": "Цвет",
-                "opacity": "Прозрачность",
-                "contrast": {
-                    "hint": "Уровень контраста: {ratio}, что {level} {context}",
-                    "level": {
-                        "aa": "соответствует гайдлайну Level AA (минимальный)",
-                        "aaa": "соответствует гайдлайну Level AAA (рекомендуемый)",
-                        "bad": "не соответствует каким либо гайдлайнам"
-                    },
-                    "context": {
-                        "18pt": "для крупного (18pt+) текста",
-                        "text": "для текста"
-                    }
-                }
-            },
-            "common_colors": {
-                "_tab_label": "Общие",
-                "main": "Общие цвета",
-                "foreground_hint": "См. вкладку \"Дополнительно\" для более детального контроля",
-                "rgbo": "Иконки, акценты, ярылки"
-            },
-            "advanced_colors": {
-                "_tab_label": "Дополнительно",
-                "alert": "Фон уведомлений",
-                "alert_error": "Ошибки",
-                "badge": "Фон значков",
-                "badge_notification": "Уведомления",
-                "panel_header": "Заголовок панели",
-                "top_bar": "Верняя полоска",
-                "borders": "Границы",
-                "buttons": "Кнопки",
-                "inputs": "Поля ввода",
-                "faint_text": "Маловажный текст"
-            },
-            "radii": {
-                "_tab_label": "Скругление"
-            },
-            "shadows": {
-                "_tab_label": "Светотень",
-                "component": "Компонент",
-                "override": "Переопределить",
-                "shadow_id": "Тень №{value}",
-                "blur": "Размытие",
-                "spread": "Разброс",
-                "inset": "Внутренняя",
-                "hint": "Для теней вы так же можете использовать --variable в качестве цвета чтобы использовать CSS3-переменные. В таком случае прозрачность работать не будет.",
-                "filter_hint": {
-                    "always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это.",
-                    "drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}.",
-                    "avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете.",
-                    "spread_zero": "Тени с разбросом > 0 будут выглядеть как если бы разброс установлен в 0",
-                    "inset_classic": "Внутренние тени будут использовать {0}"
-                },
-                "components": {
-                    "panel": "Панель",
-                    "panelHeader": "Заголовок панели",
-                    "topBar": "Верхняя полоска",
-                    "avatar": "Аватарка (профиль)",
-                    "avatarStatus": "Аватарка (в ленте)",
-                    "popup": "Всплывающие подсказки",
-                    "button": "Кнопки",
-                    "buttonHover": "Кнопки (наведен курсор)",
-                    "buttonPressed": "Кнопки (нажата)",
-                    "buttonPressedHover": "Кнопки (нажата+наведен курсор)",
-                    "input": "Поля ввода"
-                }
-            },
-            "fonts": {
-                "_tab_label": "Шрифты",
-                "help": "Выберите тип шрифта для использования в интерфейсе. При выборе варианта \"другой\" надо ввести название шрифта в точности как он называется в системе.",
-                "components": {
-                    "interface": "Интерфейс",
-                    "input": "Поля ввода",
-                    "post": "Текст постов",
-                    "postCode": "Моноширинный текст в посте (форматирование)"
-                },
-                "family": "Шрифт",
-                "size": "Размер (в пикселях)",
-                "weight": "Ширина",
-                "custom": "Другой"
-            },
-            "preview": {
-                "header": "Пример",
-                "content": "Контент",
-                "error": "Ошибка стоп 000",
-                "button": "Кнопка",
-                "text": "Еще немного {0} и масенькая {1}",
-                "mono": "контента",
-                "input": "Что нового?",
-                "faint_link": "Его придется убрать",
-                "fine_print": "Если проблемы остались — ваш гуртовщик мыши плохо стоит. {0}.",
-                "header_faint": "Все идет по плану",
-                "checkbox": "Я подтверждаю что не было ни единого разрыва",
-                "link": "ссылка"
-            }
-        },
-        "notification_setting_non_followers": "Не читающие вас",
-        "allow_following_move": "Разрешить автоматически читать новый аккаунт при перемещении на другой сервер",
-        "hide_user_stats": "Не показывать статистику пользователей (например количество читателей)",
-        "notification_setting_followers": "Читающие вас",
-        "notification_setting_follows": "Читаемые вами",
-        "notification_setting_non_follows": "Не читаемые вами"
-    },
-    "timeline": {
-        "collapse": "Свернуть",
-        "conversation": "Разговор",
-        "error_fetching": "Ошибка при обновлении",
-        "load_older": "Загрузить старые статусы",
-        "no_retweet_hint": "Пост помечен как \"только для подписчиков\" или \"личное\" и поэтому не может быть повторён",
-        "repeated": "повторил(а)",
-        "show_new": "Показать новые",
-        "up_to_date": "Обновлено"
-    },
-    "user_card": {
-        "block": "Заблокировать",
-        "blocked": "Заблокирован",
-        "favorites": "Понравившиеся",
-        "follow": "Читать",
-        "follow_sent": "Запрос отправлен!",
-        "follow_progress": "Запрашиваем…",
-        "follow_again": "Запросить еще раз?",
-        "follow_unfollow": "Перестать читать",
-        "followees": "Читаемые",
-        "followers": "Читатели",
-        "following": "Читаю!",
-        "follows_you": "Читает вас!",
-        "mute": "Игнорировать",
-        "muted": "Игнорирую",
-        "per_day": "в день",
-        "remote_follow": "Читать удалённо",
-        "statuses": "Статусы",
-        "admin_menu": {
-            "moderation": "Опции модератора",
-            "grant_admin": "Сделать администратором",
-            "revoke_admin": "Забрать права администратора",
-            "grant_moderator": "Сделать модератором",
-            "revoke_moderator": "Забрать права модератора",
-            "activate_account": "Активировать аккаунт",
-            "deactivate_account": "Деактивировать аккаунт",
-            "delete_account": "Удалить аккаунт",
-            "force_nsfw": "Отмечать посты пользователя как NSFW",
-            "strip_media": "Убирать вложения из постов пользователя",
-            "force_unlisted": "Не добавлять посты в публичные ленты",
-            "sandbox": "Принудить видимость постов только читателям",
-            "disable_remote_subscription": "Запретить читать с удаленных серверов",
-            "disable_any_subscription": "Запретить читать пользователя",
-            "quarantine": "Не федерировать посты пользователя",
-            "delete_user": "Удалить пользователя",
-            "delete_user_confirmation": "Вы уверены? Это действие нельзя отменить."
-        }
-    },
-    "user_profile": {
-        "timeline_title": "Лента пользователя"
-    },
-    "search": {
-        "people": "Люди",
-        "hashtags": "Хэштэги",
-        "person_talking": "Популярно у {count} человека",
-        "people_talking": "Популярно у {count} человек",
-        "no_results": "Ничего не найдено"
-    },
-    "password_reset": {
-        "forgot_password": "Забыли пароль?",
-        "password_reset": "Сброс пароля",
-        "instruction": "Введите ваш email или имя пользователя, и мы отправим вам ссылку для сброса пароля.",
-        "placeholder": "Ваш email или имя пользователя",
-        "check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.",
-        "return_home": "Вернуться на главную страницу",
-        "not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.",
-        "too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.",
-        "password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера."
-    },
-    "about": {
-        "mrf": {
-            "federation": "Федерация",
-            "simple": {
-                "accept_desc": "Данный сервер принимает сообщения только со следующих серверов:",
-                "ftl_removal_desc": "Данный сервер скрывает следующие сервера с федеративной ленты:",
-                "media_nsfw_desc": "Данный сервер принужденно помечает вложения со следущих серверов как NSFW:",
-                "simple_policies": "Правила для определенных серверов",
-                "accept": "Принимаемые сообщения",
-                "reject": "Отклоняемые сообщения",
-                "reject_desc": "Данный сервер не принимает сообщения со следующих серверов:",
-                "quarantine": "Зона карантина",
-                "quarantine_desc": "Данный сервер отправляет только публичные посты следующим серверам:",
-                "ftl_removal": "Скрытие с федеративной ленты",
-                "media_removal": "Удаление вложений",
-                "media_removal_desc": "Данный сервер удаляет вложения со следующих серверов:",
-                "media_nsfw": "Принужденно помеченно как NSFW"
-            },
-            "keyword": {
-                "ftl_removal": "Убрать из федеративной ленты",
-                "reject": "Отклонить",
-                "keyword_policies": "Действия на ключевые слова",
-                "replace": "Заменить",
-                "is_replaced_by": "→"
-            },
-            "mrf_policies": "Активные правила MRF (модуль переписывания сообщений)",
-            "mrf_policies_desc": "Правила MRF (модуль переписывания сообщений) влияют на федерацию данного сервера. Следующие правила активны:"
-        },
-        "staff": "Администрация"
-    },
-    "domain_mute_card": {
-        "mute": "Игнорировать",
-        "mute_progress": "В процессе...",
-        "unmute": "Прекратить игнорирование",
-        "unmute_progress": "В процессе..."
-    },
-    "exporter": {
-        "export": "Экспорт",
-        "processing": "Запрос в обработке, вам скоро будет предложено загрузить файл"
-    },
-    "features_panel": {
-        "chat": "Чат",
-        "media_proxy": "Прокси для внешних вложений",
-        "text_limit": "Лимит символов",
-        "title": "Особенности",
-        "gopher": "Gopher"
-    },
-    "tool_tip": {
-        "accept_follow_request": "Принять запрос на чтение",
-        "reject_follow_request": "Отклонить запрос на чтение"
+  "chat": {
+    "title": "Чат"
+  },
+  "finder": {
+    "error_fetching_user": "Пользователь не найден",
+    "find_user": "Найти пользователя"
+  },
+  "general": {
+    "apply": "Применить",
+    "submit": "Отправить",
+    "cancel": "Отмена",
+    "disable": "Оключить",
+    "enable": "Включить",
+    "confirm": "Подтвердить",
+    "verify": "Проверить",
+    "more": "Больше",
+    "generic_error": "Произошла ошибка",
+    "optional": "не обязательно",
+    "show_less": "Показать меньше",
+    "show_more": "Показать больше"
+  },
+  "login": {
+    "login": "Войти",
+    "logout": "Выйти",
+    "password": "Пароль",
+    "placeholder": "e.c. lain",
+    "register": "Зарегистрироваться",
+    "username": "Имя пользователя",
+    "authentication_code": "Код аутентификации",
+    "enter_recovery_code": "Ввести код восстановления",
+    "enter_two_factor_code": "Ввести код аутентификации",
+    "recovery_code": "Код восстановления",
+    "heading": {
+      "TotpForm": "Двухфакторная аутентификация",
+      "RecoveryForm": "Two-factor recovery"
     }
+  },
+  "nav": {
+    "back": "Назад",
+    "chat": "Локальный чат",
+    "mentions": "Упоминания",
+    "interactions": "Взаимодействия",
+    "public_tl": "Публичная лента",
+    "timeline": "Лента",
+    "twkn": "Федеративная лента",
+    "search": "Поиск",
+    "friend_requests": "Запросы на чтение"
+  },
+  "notifications": {
+    "broken_favorite": "Неизвестный статус, ищем...",
+    "favorited_you": "нравится ваш статус",
+    "followed_you": "начал(а) читать вас",
+    "load_older": "Загрузить старые уведомления",
+    "notifications": "Уведомления",
+    "read": "Прочесть",
+    "repeated_you": "повторил(а) ваш статус",
+    "follow_request": "хочет читать вас"
+  },
+  "interactions": {
+    "favs_repeats": "Повторы и фавориты",
+    "follows": "Новые подписки",
+    "load_older": "Загрузить старые взаимодействия"
+  },
+  "post_status": {
+    "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может начать читать вас чтобы видеть посты только для подписчиков.",
+    "account_not_locked_warning_link": "залочен",
+    "attachments_sensitive": "Вложения содержат чувствительный контент",
+    "content_warning": "Тема (не обязательно)",
+    "default": "Что нового?",
+    "direct_warning": "Этот пост будет виден только упомянутым пользователям",
+    "posting": "Отправляется",
+    "scope_notice": {
+      "public": "Этот пост будет виден всем",
+      "private": "Этот пост будет виден только вашим подписчикам",
+      "unlisted": "Этот пост не будет виден в публичной и федеративной ленте"
+    },
+    "scope": {
+      "direct": "Личное - этот пост видят только те кто в нём упомянут",
+      "private": "Для подписчиков - этот пост видят только подписчики",
+      "public": "Публичный - этот пост виден всем",
+      "unlisted": "Непубличный - этот пост не виден на публичных лентах"
+    }
+  },
+  "registration": {
+    "bio": "Описание",
+    "email": "Email",
+    "fullname": "Отображаемое имя",
+    "password_confirm": "Подтверждение пароля",
+    "registration": "Регистрация",
+    "token": "Код приглашения",
+    "validations": {
+      "username_required": "не должно быть пустым",
+      "fullname_required": "не должно быть пустым",
+      "email_required": "не должен быть пустым",
+      "password_required": "не должен быть пустым",
+      "password_confirmation_required": "не должно быть пустым",
+      "password_confirmation_match": "должно совпадать с паролем"
+    }
+  },
+  "settings": {
+    "enter_current_password_to_confirm": "Введите свой текущий пароль",
+    "mfa": {
+      "otp": "OTP",
+      "setup_otp": "Настройка OTP",
+      "wait_pre_setup_otp": "предварительная настройка OTP",
+      "confirm_and_enable": "Подтвердить и включить OTP",
+      "title": "Двухфакторная аутентификация",
+      "generate_new_recovery_codes": "Получить новые коды востановления",
+      "warning_of_generate_new_codes": "После получения новых кодов восстановления, старые больше не будут работать.",
+      "recovery_codes": "Коды восстановления.",
+      "waiting_a_recovery_codes": "Получение кодов восстановления ...",
+      "recovery_codes_warning": "Запишите эти коды и держите в безопасном месте - иначе вы их больше не увидите. Если вы потеряете доступ к OTP приложению - без резервных кодов вы больше не сможете залогиниться.",
+      "authentication_methods": "Методы аутентификации",
+      "scan": {
+        "title": "Сканирование",
+        "desc": "Используйте приложение для двухэтапной аутентификации для сканирования этого QR-код или введите текстовый ключ:",
+        "secret_code": "Ключ"
+      },
+      "verify": {
+        "desc": "Чтобы включить двухэтапную аутентификации, введите код из вашего приложение для двухэтапной аутентификации:"
+      }
+    },
+    "attachmentRadius": "Прикреплённые файлы",
+    "attachments": "Вложения",
+    "autoload": "Включить автоматическую загрузку при прокрутке вниз",
+    "avatar": "Аватар",
+    "avatarAltRadius": "Аватары в уведомлениях",
+    "avatarRadius": "Аватары",
+    "background": "Фон",
+    "bio": "Описание",
+    "btnRadius": "Кнопки",
+    "cBlue": "Ответить, читать",
+    "cGreen": "Повторить",
+    "cOrange": "Нравится",
+    "cRed": "Отменить",
+    "change_email": "Сменить email",
+    "change_email_error": "Произошла ошибка при попытке изменить email.",
+    "changed_email": "Email изменён успешно!",
+    "change_password": "Сменить пароль",
+    "change_password_error": "Произошла ошибка при попытке изменить пароль.",
+    "changed_password": "Пароль изменён успешно!",
+    "collapse_subject": "Сворачивать посты с темой",
+    "confirm_new_password": "Подтверждение нового пароля",
+    "current_avatar": "Текущий аватар",
+    "current_password": "Текущий пароль",
+    "current_profile_banner": "Текущий баннер профиля",
+    "data_import_export_tab": "Импорт / Экспорт данных",
+    "delete_account": "Удалить аккаунт",
+    "delete_account_description": "Удалить ваш аккаунт и все ваши сообщения.",
+    "delete_account_error": "Возникла ошибка в процессе удаления вашего аккаунта. Если это повторяется, свяжитесь с администратором вашего сервера.",
+    "delete_account_instructions": "Введите ваш пароль в поле ниже для подтверждения удаления.",
+    "export_theme": "Сохранить Тему",
+    "filtering": "Фильтрация",
+    "filtering_explanation": "Все статусы, содержащие данные слова, будут игнорироваться, по одному в строке",
+    "follow_export": "Экспортировать читаемых",
+    "follow_export_button": "Экспортировать читаемых в файл .csv",
+    "follow_export_processing": "Ведётся обработка, скоро вам будет предложено загрузить файл",
+    "follow_import": "Импортировать читаемых",
+    "follow_import_error": "Ошибка при импортировании читаемых",
+    "follows_imported": "Список читаемых импортирован. Обработка займёт некоторое время..",
+    "foreground": "Передний план",
+    "general": "Общие",
+    "hide_attachments_in_convo": "Прятать вложения в разговорах",
+    "hide_attachments_in_tl": "Прятать вложения в ленте",
+    "hide_isp": "Скрыть серверную панель",
+    "import_followers_from_a_csv_file": "Импортировать читаемых из файла .csv",
+    "import_theme": "Загрузить Тему",
+    "inputRadius": "Поля ввода",
+    "checkboxRadius": "Чекбоксы",
+    "instance_default": "(по умолчанию: {value})",
+    "instance_default_simple": "(по умолчанию)",
+    "interface": "Интерфейс",
+    "interfaceLanguage": "Язык интерфейса",
+    "limited_availability": "Не доступно в вашем браузере",
+    "links": "Ссылки",
+    "lock_account_description": "Аккаунт доступен только подтверждённым подписчикам",
+    "loop_video": "Зациливать видео",
+    "loop_video_silent_only": "Зацикливать только беззвучные видео (т.е. \"гифки\" с Mastodon)",
+    "name": "Имя",
+    "name_bio": "Имя и описание",
+    "new_email": "Новый email",
+    "new_password": "Новый пароль",
+    "fun": "Потешное",
+    "greentext": "Мемные стрелочки",
+    "notification_visibility": "Показывать уведомления",
+    "notification_visibility_follows": "Подписки",
+    "notification_visibility_likes": "Лайки",
+    "notification_visibility_mentions": "Упоминания",
+    "notification_visibility_repeats": "Повторы",
+    "no_rich_text_description": "Убрать форматирование из всех постов",
+    "hide_follows_description": "Не показывать кого я читаю",
+    "hide_followers_description": "Не показывать кто читает меня",
+    "hide_follows_count_description": "Не показывать число читаемых пользователей",
+    "hide_followers_count_description": "Не показывать число моих подписчиков",
+    "show_admin_badge": "Показывать значок администратора в моем профиле",
+    "show_moderator_badge": "Показывать значок модератора в моем профиле",
+    "nsfw_clickthrough": "Включить скрытие NSFW вложений",
+    "oauth_tokens": "OAuth токены",
+    "token": "Токен",
+    "refresh_token": "Рефреш токен",
+    "valid_until": "Годен до",
+    "revoke_token": "Удалить",
+    "panelRadius": "Панели",
+    "pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе",
+    "presets": "Пресеты",
+    "profile_background": "Фон профиля",
+    "profile_banner": "Баннер профиля",
+    "profile_tab": "Профиль",
+    "radii_help": "Скругление углов элементов интерфейса (в пикселях)",
+    "replies_in_timeline": "Ответы в ленте",
+    "reply_link_preview": "Включить предварительный просмотр ответа при наведении мыши",
+    "reply_visibility_all": "Показывать все ответы",
+    "reply_visibility_following": "Показывать только ответы мне или тех на кого я подписан",
+    "reply_visibility_self": "Показывать только ответы мне",
+    "autohide_floating_post_button": "Автоматически скрывать кнопку постинга (в мобильной версии)",
+    "saving_err": "Не удалось сохранить настройки",
+    "saving_ok": "Сохранено",
+    "security_tab": "Безопасность",
+    "scope_copy": "Копировать видимость поста при ответе (всегда включено для Личных Сообщений)",
+    "minimal_scopes_mode": "Минимизировать набор опций видимости поста",
+    "set_new_avatar": "Загрузить новый аватар",
+    "set_new_profile_background": "Загрузить новый фон профиля",
+    "set_new_profile_banner": "Загрузить новый баннер профиля",
+    "settings": "Настройки",
+    "subject_input_always_show": "Всегда показывать поле ввода темы",
+    "stop_gifs": "Проигрывать GIF анимации только при наведении",
+    "streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
+    "useStreamingApi": "Получать сообщения и уведомления в реальном времени",
+    "useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)",
+    "text": "Текст",
+    "theme": "Тема",
+    "theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",
+    "theme_help_v2_1": "Вы так же можете перепоределить цвета определенных компонентов нажав соотв. галочку. Используйте кнопку \"Очистить всё\" чтобы снять все переопределения.",
+    "theme_help_v2_2": "Под некоторыми полями ввода это идикаторы контрастности, наведите на них мышью чтобы узнать больше. Приспользовании прозрачности контраст расчитывается для наихудшего варианта.",
+    "tooltipRadius": "Всплывающие подсказки/уведомления",
+    "user_settings": "Настройки пользователя",
+    "values": {
+      "false": "нет",
+      "true": "да"
+    },
+    "style": {
+      "switcher": {
+        "keep_color": "Оставить цвета",
+        "keep_shadows": "Оставить тени",
+        "keep_opacity": "Оставить прозрачность",
+        "keep_roundness": "Оставить скругление",
+        "keep_fonts": "Оставить шрифты",
+        "save_load_hint": "Опции \"оставить...\" позволяют сохранить текущие настройки при выборе другой темы или импорта её из файла. Так же они влияют на то какие компоненты будут сохранены при экспорте темы. Когда все галочки сняты все компоненты будут экспортированы.",
+        "reset": "Сбросить",
+        "clear_all": "Очистить всё",
+        "clear_opacity": "Очистить прозрачность"
+      },
+      "common": {
+        "color": "Цвет",
+        "opacity": "Прозрачность",
+        "contrast": {
+          "hint": "Уровень контраста: {ratio}, что {level} {context}",
+          "level": {
+            "aa": "соответствует гайдлайну Level AA (минимальный)",
+            "aaa": "соответствует гайдлайну Level AAA (рекомендуемый)",
+            "bad": "не соответствует каким либо гайдлайнам"
+          },
+          "context": {
+            "18pt": "для крупного (18pt+) текста",
+            "text": "для текста"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "Общие",
+        "main": "Общие цвета",
+        "foreground_hint": "См. вкладку \"Дополнительно\" для более детального контроля",
+        "rgbo": "Иконки, акценты, ярылки"
+      },
+      "advanced_colors": {
+        "_tab_label": "Дополнительно",
+        "alert": "Фон уведомлений",
+        "alert_error": "Ошибки",
+        "badge": "Фон значков",
+        "badge_notification": "Уведомления",
+        "panel_header": "Заголовок панели",
+        "top_bar": "Верняя полоска",
+        "borders": "Границы",
+        "buttons": "Кнопки",
+        "inputs": "Поля ввода",
+        "faint_text": "Маловажный текст"
+      },
+      "radii": {
+        "_tab_label": "Скругление"
+      },
+      "shadows": {
+        "_tab_label": "Светотень",
+        "component": "Компонент",
+        "override": "Переопределить",
+        "shadow_id": "Тень №{value}",
+        "blur": "Размытие",
+        "spread": "Разброс",
+        "inset": "Внутренняя",
+        "hint": "Для теней вы так же можете использовать --variable в качестве цвета чтобы использовать CSS3-переменные. В таком случае прозрачность работать не будет.",
+        "filter_hint": {
+          "always_drop_shadow": "Внимание, эта тень всегда использует {0} когда браузер поддерживает это.",
+          "drop_shadow_syntax": "{0} не поддерживает параметр {1} и ключевое слово {2}.",
+          "avatar_inset": "Одновременное использование внутренних и внешних теней на (прозрачных) аватарках может дать не те результаты что вы ожидаете.",
+          "spread_zero": "Тени с разбросом > 0 будут выглядеть как если бы разброс установлен в 0",
+          "inset_classic": "Внутренние тени будут использовать {0}"
+        },
+        "components": {
+          "panel": "Панель",
+          "panelHeader": "Заголовок панели",
+          "topBar": "Верхняя полоска",
+          "avatar": "Аватарка (профиль)",
+          "avatarStatus": "Аватарка (в ленте)",
+          "popup": "Всплывающие подсказки",
+          "button": "Кнопки",
+          "buttonHover": "Кнопки (наведен курсор)",
+          "buttonPressed": "Кнопки (нажата)",
+          "buttonPressedHover": "Кнопки (нажата+наведен курсор)",
+          "input": "Поля ввода"
+        }
+      },
+      "fonts": {
+        "_tab_label": "Шрифты",
+        "help": "Выберите тип шрифта для использования в интерфейсе. При выборе варианта \"другой\" надо ввести название шрифта в точности как он называется в системе.",
+        "components": {
+          "interface": "Интерфейс",
+          "input": "Поля ввода",
+          "post": "Текст постов",
+          "postCode": "Моноширинный текст в посте (форматирование)"
+        },
+        "family": "Шрифт",
+        "size": "Размер (в пикселях)",
+        "weight": "Ширина",
+        "custom": "Другой"
+      },
+      "preview": {
+        "header": "Пример",
+        "content": "Контент",
+        "error": "Ошибка стоп 000",
+        "button": "Кнопка",
+        "text": "Еще немного {0} и масенькая {1}",
+        "mono": "контента",
+        "input": "Что нового?",
+        "faint_link": "Его придется убрать",
+        "fine_print": "Если проблемы остались — ваш гуртовщик мыши плохо стоит. {0}.",
+        "header_faint": "Все идет по плану",
+        "checkbox": "Я подтверждаю что не было ни единого разрыва",
+        "link": "ссылка"
+      }
+    },
+    "notification_setting_non_followers": "Не читающие вас",
+    "allow_following_move": "Разрешить автоматически читать новый аккаунт при перемещении на другой сервер",
+    "hide_user_stats": "Не показывать статистику пользователей (например количество читателей)",
+    "notification_setting_followers": "Читающие вас",
+    "notification_setting_follows": "Читаемые вами",
+    "notification_setting_non_follows": "Не читаемые вами"
+  },
+  "timeline": {
+    "collapse": "Свернуть",
+    "conversation": "Разговор",
+    "error_fetching": "Ошибка при обновлении",
+    "load_older": "Загрузить старые статусы",
+    "no_retweet_hint": "Пост помечен как \"только для подписчиков\" или \"личное\" и поэтому не может быть повторён",
+    "repeated": "повторил(а)",
+    "show_new": "Показать новые",
+    "up_to_date": "Обновлено"
+  },
+  "user_card": {
+    "block": "Заблокировать",
+    "blocked": "Заблокирован",
+    "favorites": "Понравившиеся",
+    "follow": "Читать",
+    "follow_sent": "Запрос отправлен!",
+    "follow_progress": "Запрашиваем…",
+    "follow_again": "Запросить еще раз?",
+    "follow_unfollow": "Перестать читать",
+    "followees": "Читаемые",
+    "followers": "Читатели",
+    "following": "Читаю!",
+    "follows_you": "Читает вас!",
+    "mute": "Игнорировать",
+    "muted": "Игнорирую",
+    "per_day": "в день",
+    "remote_follow": "Читать удалённо",
+    "statuses": "Статусы",
+    "admin_menu": {
+      "moderation": "Опции модератора",
+      "grant_admin": "Сделать администратором",
+      "revoke_admin": "Забрать права администратора",
+      "grant_moderator": "Сделать модератором",
+      "revoke_moderator": "Забрать права модератора",
+      "activate_account": "Активировать аккаунт",
+      "deactivate_account": "Деактивировать аккаунт",
+      "delete_account": "Удалить аккаунт",
+      "force_nsfw": "Отмечать посты пользователя как NSFW",
+      "strip_media": "Убирать вложения из постов пользователя",
+      "force_unlisted": "Не добавлять посты в публичные ленты",
+      "sandbox": "Принудить видимость постов только читателям",
+      "disable_remote_subscription": "Запретить читать с удаленных серверов",
+      "disable_any_subscription": "Запретить читать пользователя",
+      "quarantine": "Не федерировать посты пользователя",
+      "delete_user": "Удалить пользователя",
+      "delete_user_confirmation": "Вы уверены? Это действие нельзя отменить."
+    }
+  },
+  "user_profile": {
+    "timeline_title": "Лента пользователя"
+  },
+  "search": {
+    "people": "Люди",
+    "hashtags": "Хэштэги",
+    "person_talking": "Популярно у {count} человека",
+    "people_talking": "Популярно у {count} человек",
+    "no_results": "Ничего не найдено"
+  },
+  "password_reset": {
+    "forgot_password": "Забыли пароль?",
+    "password_reset": "Сброс пароля",
+    "instruction": "Введите ваш email или имя пользователя, и мы отправим вам ссылку для сброса пароля.",
+    "placeholder": "Ваш email или имя пользователя",
+    "check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.",
+    "return_home": "Вернуться на главную страницу",
+    "not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.",
+    "too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.",
+    "password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера."
+  },
+  "about": {
+    "mrf": {
+      "federation": "Федерация",
+      "simple": {
+        "accept_desc": "Данный сервер принимает сообщения только со следующих серверов:",
+        "ftl_removal_desc": "Данный сервер скрывает следующие сервера с федеративной ленты:",
+        "media_nsfw_desc": "Данный сервер принужденно помечает вложения со следущих серверов как NSFW:",
+        "simple_policies": "Правила для определенных серверов",
+        "accept": "Принимаемые сообщения",
+        "reject": "Отклоняемые сообщения",
+        "reject_desc": "Данный сервер не принимает сообщения со следующих серверов:",
+        "quarantine": "Зона карантина",
+        "quarantine_desc": "Данный сервер отправляет только публичные посты следующим серверам:",
+        "ftl_removal": "Скрытие с федеративной ленты",
+        "media_removal": "Удаление вложений",
+        "media_removal_desc": "Данный сервер удаляет вложения со следующих серверов:",
+        "media_nsfw": "Принужденно помеченно как NSFW"
+      },
+      "keyword": {
+        "ftl_removal": "Убрать из федеративной ленты",
+        "reject": "Отклонить",
+        "keyword_policies": "Действия на ключевые слова",
+        "replace": "Заменить",
+        "is_replaced_by": "→"
+      },
+      "mrf_policies": "Активные правила MRF (модуль переписывания сообщений)",
+      "mrf_policies_desc": "Правила MRF (модуль переписывания сообщений) влияют на федерацию данного сервера. Следующие правила активны:"
+    },
+    "staff": "Администрация"
+  },
+  "domain_mute_card": {
+    "mute": "Игнорировать",
+    "mute_progress": "В процессе...",
+    "unmute": "Прекратить игнорирование",
+    "unmute_progress": "В процессе..."
+  },
+  "exporter": {
+    "export": "Экспорт",
+    "processing": "Запрос в обработке, вам скоро будет предложено загрузить файл"
+  },
+  "features_panel": {
+    "chat": "Чат",
+    "media_proxy": "Прокси для внешних вложений",
+    "text_limit": "Лимит символов",
+    "title": "Особенности",
+    "gopher": "Gopher"
+  },
+  "tool_tip": {
+    "accept_follow_request": "Принять запрос на чтение",
+    "reject_follow_request": "Отклонить запрос на чтение"
+  }
 }
diff --git a/src/i18n/te.json b/src/i18n/te.json
index f0953d97..6022349d 100644
--- a/src/i18n/te.json
+++ b/src/i18n/te.json
@@ -1,352 +1,352 @@
 {
-    "chat.title": "చాట్",
-    "features_panel.chat": "చాట్",
-    "features_panel.gopher": "గోఫర్",
-    "features_panel.media_proxy": "మీడియా ప్రాక్సీ",
-    "features_panel.scope_options": "స్కోప్ ఎంపికలు",
-    "features_panel.text_limit": "వచన పరిమితి",
-    "features_panel.title": "లక్షణాలు",
-    "features_panel.who_to_follow": "ఎవరిని అనుసరించాలి",
-    "finder.error_fetching_user": "వినియోగదారుని పొందడంలో లోపం",
-    "finder.find_user": "వినియోగదారుని కనుగొనండి",
-    "general.apply": "వర్తించు",
-    "general.submit": "సమర్పించు",
-    "general.more": "మరిన్ని",
-    "general.generic_error": "ఒక తప్పిదం సంభవించినది",
-    "general.optional": "ఐచ్చికం",
-    "image_cropper.crop_picture": "చిత్రాన్ని కత్తిరించండి",
-    "image_cropper.save": "దాచు",
-    "image_cropper.save_without_cropping": "కత్తిరించకుండా సేవ్ చేయి",
-    "image_cropper.cancel": "రద్దుచేయి",
-    "login.login": "లాగిన్",
-    "login.description": "OAuth తో లాగిన్ అవ్వండి",
-    "login.logout": "లాగౌట్",
-    "login.password": "సంకేతపదము",
-    "login.placeholder": "ఉదా. lain",
-    "login.register": "నమోదు చేసుకోండి",
-    "login.username": "వాడుకరి పేరు",
-    "login.hint": "చర్చలో చేరడానికి లాగిన్ అవ్వండి",
-    "media_modal.previous": "ముందరి పుట",
-    "media_modal.next": "తరువాత",
-    "nav.about": "గురించి",
-    "nav.back": "వెనక్కి",
-    "nav.chat": "స్థానిక చాట్",
-    "nav.friend_requests": "అనుసరించడానికి అభ్యర్థనలు",
-    "nav.mentions": "ప్రస్తావనలు",
-    "nav.dms": "నేరుగా పంపిన సందేశాలు",
-    "nav.public_tl": "ప్రజా కాలక్రమం",
-    "nav.timeline": "కాలక్రమం",
-    "nav.twkn": "మొత్తం తెలిసిన నెట్వర్క్",
-    "nav.user_search": "వాడుకరి శోధన",
-    "nav.who_to_follow": "ఎవరిని అనుసరించాలి",
-    "nav.preferences": "ప్రాధాన్యతలు",
-    "notifications.broken_favorite": "తెలియని స్థితి, దాని కోసం శోధిస్తోంది...",
-    "notifications.favorited_you": "మీ స్థితిని ఇష్టపడ్డారు",
-    "notifications.followed_you": "మిమ్మల్ని అనుసరించారు",
-    "notifications.load_older": "పాత నోటిఫికేషన్లను లోడ్ చేయండి",
-    "notifications.notifications": "ప్రకటనలు",
-    "notifications.read": "చదివాను!",
-    "notifications.repeated_you": "మీ స్థితిని పునరావృతం చేసారు",
-    "notifications.no_more_notifications": "ఇక నోటిఫికేషన్లు లేవు",
-    "post_status.new_status": "క్రొత్త స్థితిని పోస్ట్ చేయండి",
-    "post_status.account_not_locked_warning": "మీ ఖాతా {౦} కాదు. ఎవరైనా మిమ్మల్ని అనుసరించి అనుచరులకు మాత్రమే ఉద్దేశించిన పోస్టులను చూడవచ్చు.",
-    "post_status.account_not_locked_warning_link": "తాళం వేయబడినది",
-    "post_status.attachments_sensitive": "జోడింపులను సున్నితమైనవిగా గుర్తించండి",
-    "post_status.content_type.text/plain": "సాధారణ అక్షరాలు",
-    "post_status.content_type.text/html": "హెచ్‌టిఎమ్ఎల్",
-    "post_status.content_type.text/markdown": "మార్క్డౌన్",
-    "post_status.content_warning": "విషయం (ఐచ్ఛికం)",
-    "post_status.default": "ఇప్పుడే విజయవాడలో దిగాను.",
-    "post_status.direct_warning": "ఈ పోస్ట్ మాత్రమే పేర్కొన్న వినియోగదారులకు మాత్రమే కనిపిస్తుంది.",
-    "post_status.posting": "పోస్ట్ చేస్తున్నా",
-    "post_status.scope.direct": "ప్రత్యక్ష - పేర్కొన్న వినియోగదారులకు మాత్రమే పోస్ట్ చేయబడుతుంది",
-    "post_status.scope.private": "అనుచరులకు మాత్రమే - అనుచరులకు మాత్రమే పోస్ట్ చేయబడుతుంది",
-    "post_status.scope.public": "పబ్లిక్ - ప్రజా కాలక్రమాలకు పోస్ట్ చేయబడుతుంది",
-    "post_status.scope.unlisted": "జాబితా చేయబడనిది - ప్రజా కాలక్రమాలకు పోస్ట్ చేయవద్దు",
-    "registration.bio": "బయో",
-    "registration.email": "ఈ మెయిల్",
-    "registration.fullname": "ప్రదర్శన పేరు",
-    "registration.password_confirm": "పాస్వర్డ్ నిర్ధారణ",
-    "registration.registration": "నమోదు",
-    "registration.token": "ఆహ్వాన టోకెన్",
-    "registration.captcha": "కాప్చా",
-    "registration.new_captcha": "కొత్త కాప్చా పొందుటకు చిత్రం మీద క్లిక్ చేయండి",
-    "registration.username_placeholder": "ఉదా. lain",
-    "registration.fullname_placeholder": "ఉదా. Lain Iwakura",
-    "registration.bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
-    "registration.validations.username_required": "ఖాళీగా విడిచిపెట్టరాదు",
-    "registration.validations.fullname_required": "ఖాళీగా విడిచిపెట్టరాదు",
-    "registration.validations.email_required": "ఖాళీగా విడిచిపెట్టరాదు",
-    "registration.validations.password_required": "ఖాళీగా విడిచిపెట్టరాదు",
-    "registration.validations.password_confirmation_required": "ఖాళీగా విడిచిపెట్టరాదు",
-    "registration.validations.password_confirmation_match": "సంకేతపదం వలె ఉండాలి",
-    "settings.app_name": "అనువర్తన పేరు",
-    "settings.attachmentRadius": "జోడింపులు",
-    "settings.attachments": "జోడింపులు",
-    "settings.autoload": "క్రిందికి స్క్రోల్ చేయబడినప్పుడు స్వయంచాలక లోడింగ్ని ప్రారంభించు",
-    "settings.avatar": "అవతారం",
-    "settings.avatarAltRadius": "అవతారాలు (ప్రకటనలు)",
-    "settings.avatarRadius": "అవతారాలు",
-    "settings.background": "బ్యాక్‌గ్రౌండు",
-    "settings.bio": "బయో",
-    "settings.blocks_tab": "బ్లాక్‌లు",
-    "settings.btnRadius": "బటన్లు",
-    "settings.cBlue": "నీలం (ప్రత్యుత్తరం, అనుసరించండి)",
-    "settings.cGreen": "Green (Retweet)",
-    "settings.cOrange": "ఆరెంజ్ (ఇష్టపడు)",
-    "settings.cRed": "Red (Cancel)",
-    "settings.change_password": "పాస్‌వర్డ్ మార్చండి",
-    "settings.change_password_error": "మీ పాస్వర్డ్ను మార్చడంలో సమస్య ఉంది.",
-    "settings.changed_password": "పాస్వర్డ్ విజయవంతంగా మార్చబడింది!",
-    "settings.collapse_subject": "Collapse posts with subjects",
-    "settings.composing": "Composing",
-    "settings.confirm_new_password": "కొత్త పాస్వర్డ్ను నిర్ధారించండి",
-    "settings.current_avatar": "మీ ప్రస్తుత అవతారం",
-    "settings.current_password": "ప్రస్తుత పాస్వర్డ్",
-    "settings.current_profile_banner": "మీ ప్రస్తుత ప్రొఫైల్ బ్యానర్",
-    "settings.data_import_export_tab": "Data Import / Export",
-    "settings.default_vis": "Default visibility scope",
-    "settings.delete_account": "Delete Account",
-    "settings.delete_account_description": "మీ ఖాతా మరియు మీ అన్ని సందేశాలను శాశ్వతంగా తొలగించండి.",
-    "settings.delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
-    "settings.delete_account_instructions": "ఖాతా తొలగింపును నిర్ధారించడానికి దిగువ ఇన్పుట్లో మీ పాస్వర్డ్ను టైప్ చేయండి.",
-    "settings.avatar_size_instruction": "అవతార్ చిత్రాలకు సిఫార్సు చేసిన కనీస పరిమాణం 150x150 పిక్సెల్స్.",
-    "settings.export_theme": "Save preset",
-    "settings.filtering": "వడపోత",
-    "settings.filtering_explanation": "All statuses containing these words will be muted, one per line",
-    "settings.follow_export": "Follow export",
-    "settings.follow_export_button": "Export your follows to a csv file",
-    "settings.follow_export_processing": "Processing, you'll soon be asked to download your file",
-    "settings.follow_import": "Follow import",
-    "settings.follow_import_error": "అనుచరులను దిగుమతి చేయడంలో లోపం",
-    "settings.follows_imported": "Follows imported! Processing them will take a while.",
-    "settings.foreground": "Foreground",
-    "settings.general": "General",
-    "settings.hide_attachments_in_convo": "సంభాషణలలో జోడింపులను దాచు",
-    "settings.hide_attachments_in_tl": "కాలక్రమంలో జోడింపులను దాచు",
-    "settings.hide_muted_posts": "మ్యూట్ చేసిన వినియోగదారుల యొక్క పోస్ట్లను దాచిపెట్టు",
-    "settings.max_thumbnails": "Maximum amount of thumbnails per post",
-    "settings.hide_isp": "Hide instance-specific panel",
-    "settings.preload_images": "Preload images",
-    "settings.use_one_click_nsfw": "కేవలం ఒక క్లిక్ తో NSFW జోడింపులను తెరవండి",
-    "settings.hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
-    "settings.hide_user_stats": "Hide user statistics (e.g. the number of followers)",
-    "settings.hide_filtered_statuses": "Hide filtered statuses",
-    "settings.import_followers_from_a_csv_file": "Import follows from a csv file",
-    "settings.import_theme": "Load preset",
-    "settings.inputRadius": "Input fields",
-    "settings.checkboxRadius": "Checkboxes",
-    "settings.instance_default": "(default: {value})",
-    "settings.instance_default_simple": "(default)",
-    "settings.interface": "Interface",
-    "settings.interfaceLanguage": "Interface language",
-    "settings.invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
-    "settings.limited_availability": "మీ బ్రౌజర్లో అందుబాటులో లేదు",
-    "settings.links": "Links",
-    "settings.lock_account_description": "మీ ఖాతాను ఆమోదించిన అనుచరులకు మాత్రమే పరిమితం చేయండి",
-    "settings.loop_video": "Loop videos",
-    "settings.loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
-    "settings.mutes_tab": "మ్యూట్ చేయబడినవి",
-    "settings.play_videos_in_modal": "మీడియా వీక్షికలో నేరుగా వీడియోలను ప్లే చేయి",
-    "settings.use_contain_fit": "అటాచ్మెంట్ సూక్ష్మచిత్రాలను కత్తిరించవద్దు",
-    "settings.name": "Name",
-    "settings.name_bio": "పేరు & బయో",
-    "settings.new_password": "కొత్త సంకేతపదం",
-    "settings.notification_visibility": "చూపించవలసిన నోటిఫికేషన్ రకాలు",
-    "settings.notification_visibility_follows": "Follows",
-    "settings.notification_visibility_likes": "ఇష్టాలు",
-    "settings.notification_visibility_mentions": "ప్రస్తావనలు",
-    "settings.notification_visibility_repeats": "పునఃప్రసారాలు",
-    "settings.no_rich_text_description": "అన్ని పోస్ట్ల నుండి రిచ్ టెక్స్ట్ ఫార్మాటింగ్ను స్ట్రిప్ చేయండి",
-    "settings.no_blocks": "బ్లాక్స్ లేవు",
-    "settings.no_mutes": "మ్యూట్లు లేవు",
-    "settings.hide_follows_description": "నేను ఎవరిని అనుసరిస్తున్నానో చూపించవద్దు",
-    "settings.hide_followers_description": "నన్ను ఎవరు అనుసరిస్తున్నారో చూపవద్దు",
-    "settings.show_admin_badge": "నా ప్రొఫైల్ లో అడ్మిన్ బ్యాడ్జ్ చూపించు",
-    "settings.show_moderator_badge": "నా ప్రొఫైల్లో మోడరేటర్ బ్యాడ్జ్ని చూపించు",
-    "settings.nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
-    "settings.oauth_tokens": "OAuth tokens",
-    "settings.token": "Token",
-    "settings.refresh_token": "Refresh Token",
-    "settings.valid_until": "Valid Until",
-    "settings.revoke_token": "Revoke",
-    "settings.panelRadius": "Panels",
-    "settings.pause_on_unfocused": "Pause streaming when tab is not focused",
-    "settings.presets": "Presets",
-    "settings.profile_background": "Profile Background",
-    "settings.profile_banner": "Profile Banner",
-    "settings.profile_tab": "Profile",
-    "settings.radii_help": "Set up interface edge rounding (in pixels)",
-    "settings.replies_in_timeline": "Replies in timeline",
-    "settings.reply_link_preview": "Enable reply-link preview on mouse hover",
-    "settings.reply_visibility_all": "Show all replies",
-    "settings.reply_visibility_following": "Only show replies directed at me or users I'm following",
-    "settings.reply_visibility_self": "Only show replies directed at me",
-    "settings.saving_err": "Error saving settings",
-    "settings.saving_ok": "Settings saved",
-    "settings.security_tab": "Security",
-    "settings.scope_copy": "Copy scope when replying (DMs are always copied)",
-    "settings.set_new_avatar": "Set new avatar",
-    "settings.set_new_profile_background": "Set new profile background",
-    "settings.set_new_profile_banner": "Set new profile banner",
-    "settings.settings": "Settings",
-    "settings.subject_input_always_show": "Always show subject field",
-    "settings.subject_line_behavior": "Copy subject when replying",
-    "settings.subject_line_email": "Like email: \"re: subject\"",
-    "settings.subject_line_mastodon": "Like mastodon: copy as is",
-    "settings.subject_line_noop": "Do not copy",
-    "settings.post_status_content_type": "Post status content type",
-    "settings.stop_gifs": "Play-on-hover GIFs",
-    "settings.streaming": "Enable automatic streaming of new posts when scrolled to the top",
-    "settings.text": "Text",
-    "settings.theme": "Theme",
-    "settings.theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
-    "settings.theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
-    "settings.theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
-    "settings.tooltipRadius": "Tooltips/alerts",
-    "settings.upload_a_photo": "Upload a photo",
-    "settings.user_settings": "User Settings",
-    "settings.values.false": "no",
-    "settings.values.true": "yes",
-    "settings.notifications": "Notifications",
-    "settings.enable_web_push_notifications": "Enable web push notifications",
-    "settings.style.switcher.keep_color": "Keep colors",
-    "settings.style.switcher.keep_shadows": "Keep shadows",
-    "settings.style.switcher.keep_opacity": "Keep opacity",
-    "settings.style.switcher.keep_roundness": "Keep roundness",
-    "settings.style.switcher.keep_fonts": "Keep fonts",
-    "settings.style.switcher.save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
-    "settings.style.switcher.reset": "Reset",
-    "settings.style.switcher.clear_all": "Clear all",
-    "settings.style.switcher.clear_opacity": "Clear opacity",
-    "settings.style.common.color": "Color",
-    "settings.style.common.opacity": "Opacity",
-    "settings.style.common.contrast.hint": "Contrast ratio is {ratio}, it {level} {context}",
-    "settings.style.common.contrast.level.aa": "meets Level AA guideline (minimal)",
-    "settings.style.common.contrast.level.aaa": "meets Level AAA guideline (recommended)",
-    "settings.style.common.contrast.level.bad": "doesn't meet any accessibility guidelines",
-    "settings.style.common.contrast.context.18pt": "for large (18pt+) text",
-    "settings.style.common.contrast.context.text": "for text",
-    "settings.style.common_colors._tab_label": "Common",
-    "settings.style.common_colors.main": "Common colors",
-    "settings.style.common_colors.foreground_hint": "See \"Advanced\" tab for more detailed control",
-    "settings.style.common_colors.rgbo": "Icons, accents, badges",
-    "settings.style.advanced_colors._tab_label": "Advanced",
-    "settings.style.advanced_colors.alert": "Alert background",
-    "settings.style.advanced_colors.alert_error": "Error",
-    "settings.style.advanced_colors.badge": "Badge background",
-    "settings.style.advanced_colors.badge_notification": "Notification",
-    "settings.style.advanced_colors.panel_header": "Panel header",
-    "settings.style.advanced_colors.top_bar": "Top bar",
-    "settings.style.advanced_colors.borders": "Borders",
-    "settings.style.advanced_colors.buttons": "Buttons",
-    "settings.style.advanced_colors.inputs": "Input fields",
-    "settings.style.advanced_colors.faint_text": "Faded text",
-    "settings.style.radii._tab_label": "Roundness",
-    "settings.style.shadows._tab_label": "Shadow and lighting",
-    "settings.style.shadows.component": "Component",
-    "settings.style.shadows.override": "Override",
-    "settings.style.shadows.shadow_id": "Shadow #{value}",
-    "settings.style.shadows.blur": "Blur",
-    "settings.style.shadows.spread": "Spread",
-    "settings.style.shadows.inset": "Inset",
-    "settings.style.shadows.hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.",
-    "settings.style.shadows.filter_hint.always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
-    "settings.style.shadows.filter_hint.drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
-    "settings.style.shadows.filter_hint.avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
-    "settings.style.shadows.filter_hint.spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
-    "settings.style.shadows.filter_hint.inset_classic": "Inset shadows will be using {0}",
-    "settings.style.shadows.components.panel": "Panel",
-    "settings.style.shadows.components.panelHeader": "Panel header",
-    "settings.style.shadows.components.topBar": "Top bar",
-    "settings.style.shadows.components.avatar": "User avatar (in profile view)",
-    "settings.style.shadows.components.avatarStatus": "User avatar (in post display)",
-    "settings.style.shadows.components.popup": "Popups and tooltips",
-    "settings.style.shadows.components.button": "Button",
-    "settings.style.shadows.components.buttonHover": "Button (hover)",
-    "settings.style.shadows.components.buttonPressed": "Button (pressed)",
-    "settings.style.shadows.components.buttonPressedHover": "Button (pressed+hover)",
-    "settings.style.shadows.components.input": "Input field",
-    "settings.style.fonts._tab_label": "Fonts",
-    "settings.style.fonts.help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
-    "settings.style.fonts.components.interface": "Interface",
-    "settings.style.fonts.components.input": "Input fields",
-    "settings.style.fonts.components.post": "Post text",
-    "settings.style.fonts.components.postCode": "Monospaced text in a post (rich text)",
-    "settings.style.fonts.family": "Font name",
-    "settings.style.fonts.size": "Size (in px)",
-    "settings.style.fonts.weight": "Weight (boldness)",
-    "settings.style.fonts.custom": "Custom",
-    "settings.style.preview.header": "Preview",
-    "settings.style.preview.content": "Content",
-    "settings.style.preview.error": "Example error",
-    "settings.style.preview.button": "Button",
-    "settings.style.preview.text": "A bunch of more {0} and {1}",
-    "settings.style.preview.mono": "content",
-    "settings.style.preview.input": "Just landed in L.A.",
-    "settings.style.preview.faint_link": "helpful manual",
-    "settings.style.preview.fine_print": "Read our {0} to learn nothing useful!",
-    "settings.style.preview.header_faint": "This is fine",
-    "settings.style.preview.checkbox": "I have skimmed over terms and conditions",
-    "settings.style.preview.link": "a nice lil' link",
-    "settings.version.title": "Version",
-    "settings.version.backend_version": "Backend Version",
-    "settings.version.frontend_version": "Frontend Version",
-    "timeline.collapse": "Collapse",
-    "timeline.conversation": "Conversation",
-    "timeline.error_fetching": "Error fetching updates",
-    "timeline.load_older": "Load older statuses",
-    "timeline.no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
-    "timeline.repeated": "repeated",
-    "timeline.show_new": "Show new",
-    "timeline.up_to_date": "Up-to-date",
-    "timeline.no_more_statuses": "No more statuses",
-    "timeline.no_statuses": "No statuses",
-    "status.reply_to": "Reply to",
-    "status.replies_list": "Replies:",
-    "user_card.approve": "Approve",
-    "user_card.block": "Block",
-    "user_card.blocked": "Blocked!",
-    "user_card.deny": "Deny",
-    "user_card.favorites": "Favorites",
-    "user_card.follow": "Follow",
-    "user_card.follow_sent": "Request sent!",
-    "user_card.follow_progress": "Requesting…",
-    "user_card.follow_again": "Send request again?",
-    "user_card.follow_unfollow": "Unfollow",
-    "user_card.followees": "Following",
-    "user_card.followers": "Followers",
-    "user_card.following": "Following!",
-    "user_card.follows_you": "Follows you!",
-    "user_card.its_you": "It's you!",
-    "user_card.media": "Media",
-    "user_card.mute": "Mute",
-    "user_card.muted": "Muted",
-    "user_card.per_day": "per day",
-    "user_card.remote_follow": "Remote follow",
-    "user_card.statuses": "Statuses",
-    "user_card.unblock": "Unblock",
-    "user_card.unblock_progress": "Unblocking...",
-    "user_card.block_progress": "Blocking...",
-    "user_card.unmute": "Unmute",
-    "user_card.unmute_progress": "Unmuting...",
-    "user_card.mute_progress": "Muting...",
-    "user_profile.timeline_title": "User Timeline",
-    "user_profile.profile_does_not_exist": "Sorry, this profile does not exist.",
-    "user_profile.profile_loading_error": "Sorry, there was an error loading this profile.",
-    "who_to_follow.more": "More",
-    "who_to_follow.who_to_follow": "Who to follow",
-    "tool_tip.media_upload": "Upload Media",
-    "tool_tip.repeat": "Repeat",
-    "tool_tip.reply": "Reply",
-    "tool_tip.favorite": "Favorite",
-    "tool_tip.user_settings": "User Settings",
-    "upload.error.base": "Upload failed.",
-    "upload.error.file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-    "upload.error.default": "Try again later",
-    "upload.file_size_units.B": "B",
-    "upload.file_size_units.KiB": "KiB",
-    "upload.file_size_units.MiB": "MiB",
-    "upload.file_size_units.GiB": "GiB",
-    "upload.file_size_units.TiB": "TiB"
+  "chat.title": "చాట్",
+  "features_panel.chat": "చాట్",
+  "features_panel.gopher": "గోఫర్",
+  "features_panel.media_proxy": "మీడియా ప్రాక్సీ",
+  "features_panel.scope_options": "స్కోప్ ఎంపికలు",
+  "features_panel.text_limit": "వచన పరిమితి",
+  "features_panel.title": "లక్షణాలు",
+  "features_panel.who_to_follow": "ఎవరిని అనుసరించాలి",
+  "finder.error_fetching_user": "వినియోగదారుని పొందడంలో లోపం",
+  "finder.find_user": "వినియోగదారుని కనుగొనండి",
+  "general.apply": "వర్తించు",
+  "general.submit": "సమర్పించు",
+  "general.more": "మరిన్ని",
+  "general.generic_error": "ఒక తప్పిదం సంభవించినది",
+  "general.optional": "ఐచ్చికం",
+  "image_cropper.crop_picture": "చిత్రాన్ని కత్తిరించండి",
+  "image_cropper.save": "దాచు",
+  "image_cropper.save_without_cropping": "కత్తిరించకుండా సేవ్ చేయి",
+  "image_cropper.cancel": "రద్దుచేయి",
+  "login.login": "లాగిన్",
+  "login.description": "OAuth తో లాగిన్ అవ్వండి",
+  "login.logout": "లాగౌట్",
+  "login.password": "సంకేతపదము",
+  "login.placeholder": "ఉదా. lain",
+  "login.register": "నమోదు చేసుకోండి",
+  "login.username": "వాడుకరి పేరు",
+  "login.hint": "చర్చలో చేరడానికి లాగిన్ అవ్వండి",
+  "media_modal.previous": "ముందరి పుట",
+  "media_modal.next": "తరువాత",
+  "nav.about": "గురించి",
+  "nav.back": "వెనక్కి",
+  "nav.chat": "స్థానిక చాట్",
+  "nav.friend_requests": "అనుసరించడానికి అభ్యర్థనలు",
+  "nav.mentions": "ప్రస్తావనలు",
+  "nav.dms": "నేరుగా పంపిన సందేశాలు",
+  "nav.public_tl": "ప్రజా కాలక్రమం",
+  "nav.timeline": "కాలక్రమం",
+  "nav.twkn": "మొత్తం తెలిసిన నెట్వర్క్",
+  "nav.user_search": "వాడుకరి శోధన",
+  "nav.who_to_follow": "ఎవరిని అనుసరించాలి",
+  "nav.preferences": "ప్రాధాన్యతలు",
+  "notifications.broken_favorite": "తెలియని స్థితి, దాని కోసం శోధిస్తోంది...",
+  "notifications.favorited_you": "మీ స్థితిని ఇష్టపడ్డారు",
+  "notifications.followed_you": "మిమ్మల్ని అనుసరించారు",
+  "notifications.load_older": "పాత నోటిఫికేషన్లను లోడ్ చేయండి",
+  "notifications.notifications": "ప్రకటనలు",
+  "notifications.read": "చదివాను!",
+  "notifications.repeated_you": "మీ స్థితిని పునరావృతం చేసారు",
+  "notifications.no_more_notifications": "ఇక నోటిఫికేషన్లు లేవు",
+  "post_status.new_status": "క్రొత్త స్థితిని పోస్ట్ చేయండి",
+  "post_status.account_not_locked_warning": "మీ ఖాతా {౦} కాదు. ఎవరైనా మిమ్మల్ని అనుసరించి అనుచరులకు మాత్రమే ఉద్దేశించిన పోస్టులను చూడవచ్చు.",
+  "post_status.account_not_locked_warning_link": "తాళం వేయబడినది",
+  "post_status.attachments_sensitive": "జోడింపులను సున్నితమైనవిగా గుర్తించండి",
+  "post_status.content_type.text/plain": "సాధారణ అక్షరాలు",
+  "post_status.content_type.text/html": "హెచ్‌టిఎమ్ఎల్",
+  "post_status.content_type.text/markdown": "మార్క్డౌన్",
+  "post_status.content_warning": "విషయం (ఐచ్ఛికం)",
+  "post_status.default": "ఇప్పుడే విజయవాడలో దిగాను.",
+  "post_status.direct_warning": "ఈ పోస్ట్ మాత్రమే పేర్కొన్న వినియోగదారులకు మాత్రమే కనిపిస్తుంది.",
+  "post_status.posting": "పోస్ట్ చేస్తున్నా",
+  "post_status.scope.direct": "ప్రత్యక్ష - పేర్కొన్న వినియోగదారులకు మాత్రమే పోస్ట్ చేయబడుతుంది",
+  "post_status.scope.private": "అనుచరులకు మాత్రమే - అనుచరులకు మాత్రమే పోస్ట్ చేయబడుతుంది",
+  "post_status.scope.public": "పబ్లిక్ - ప్రజా కాలక్రమాలకు పోస్ట్ చేయబడుతుంది",
+  "post_status.scope.unlisted": "జాబితా చేయబడనిది - ప్రజా కాలక్రమాలకు పోస్ట్ చేయవద్దు",
+  "registration.bio": "బయో",
+  "registration.email": "ఈ మెయిల్",
+  "registration.fullname": "ప్రదర్శన పేరు",
+  "registration.password_confirm": "పాస్వర్డ్ నిర్ధారణ",
+  "registration.registration": "నమోదు",
+  "registration.token": "ఆహ్వాన టోకెన్",
+  "registration.captcha": "కాప్చా",
+  "registration.new_captcha": "కొత్త కాప్చా పొందుటకు చిత్రం మీద క్లిక్ చేయండి",
+  "registration.username_placeholder": "ఉదా. lain",
+  "registration.fullname_placeholder": "ఉదా. Lain Iwakura",
+  "registration.bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
+  "registration.validations.username_required": "ఖాళీగా విడిచిపెట్టరాదు",
+  "registration.validations.fullname_required": "ఖాళీగా విడిచిపెట్టరాదు",
+  "registration.validations.email_required": "ఖాళీగా విడిచిపెట్టరాదు",
+  "registration.validations.password_required": "ఖాళీగా విడిచిపెట్టరాదు",
+  "registration.validations.password_confirmation_required": "ఖాళీగా విడిచిపెట్టరాదు",
+  "registration.validations.password_confirmation_match": "సంకేతపదం వలె ఉండాలి",
+  "settings.app_name": "అనువర్తన పేరు",
+  "settings.attachmentRadius": "జోడింపులు",
+  "settings.attachments": "జోడింపులు",
+  "settings.autoload": "క్రిందికి స్క్రోల్ చేయబడినప్పుడు స్వయంచాలక లోడింగ్ని ప్రారంభించు",
+  "settings.avatar": "అవతారం",
+  "settings.avatarAltRadius": "అవతారాలు (ప్రకటనలు)",
+  "settings.avatarRadius": "అవతారాలు",
+  "settings.background": "బ్యాక్‌గ్రౌండు",
+  "settings.bio": "బయో",
+  "settings.blocks_tab": "బ్లాక్‌లు",
+  "settings.btnRadius": "బటన్లు",
+  "settings.cBlue": "నీలం (ప్రత్యుత్తరం, అనుసరించండి)",
+  "settings.cGreen": "Green (Retweet)",
+  "settings.cOrange": "ఆరెంజ్ (ఇష్టపడు)",
+  "settings.cRed": "Red (Cancel)",
+  "settings.change_password": "పాస్‌వర్డ్ మార్చండి",
+  "settings.change_password_error": "మీ పాస్వర్డ్ను మార్చడంలో సమస్య ఉంది.",
+  "settings.changed_password": "పాస్వర్డ్ విజయవంతంగా మార్చబడింది!",
+  "settings.collapse_subject": "Collapse posts with subjects",
+  "settings.composing": "Composing",
+  "settings.confirm_new_password": "కొత్త పాస్వర్డ్ను నిర్ధారించండి",
+  "settings.current_avatar": "మీ ప్రస్తుత అవతారం",
+  "settings.current_password": "ప్రస్తుత పాస్వర్డ్",
+  "settings.current_profile_banner": "మీ ప్రస్తుత ప్రొఫైల్ బ్యానర్",
+  "settings.data_import_export_tab": "Data Import / Export",
+  "settings.default_vis": "Default visibility scope",
+  "settings.delete_account": "Delete Account",
+  "settings.delete_account_description": "మీ ఖాతా మరియు మీ అన్ని సందేశాలను శాశ్వతంగా తొలగించండి.",
+  "settings.delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
+  "settings.delete_account_instructions": "ఖాతా తొలగింపును నిర్ధారించడానికి దిగువ ఇన్పుట్లో మీ పాస్వర్డ్ను టైప్ చేయండి.",
+  "settings.avatar_size_instruction": "అవతార్ చిత్రాలకు సిఫార్సు చేసిన కనీస పరిమాణం 150x150 పిక్సెల్స్.",
+  "settings.export_theme": "Save preset",
+  "settings.filtering": "వడపోత",
+  "settings.filtering_explanation": "All statuses containing these words will be muted, one per line",
+  "settings.follow_export": "Follow export",
+  "settings.follow_export_button": "Export your follows to a csv file",
+  "settings.follow_export_processing": "Processing, you'll soon be asked to download your file",
+  "settings.follow_import": "Follow import",
+  "settings.follow_import_error": "అనుచరులను దిగుమతి చేయడంలో లోపం",
+  "settings.follows_imported": "Follows imported! Processing them will take a while.",
+  "settings.foreground": "Foreground",
+  "settings.general": "General",
+  "settings.hide_attachments_in_convo": "సంభాషణలలో జోడింపులను దాచు",
+  "settings.hide_attachments_in_tl": "కాలక్రమంలో జోడింపులను దాచు",
+  "settings.hide_muted_posts": "మ్యూట్ చేసిన వినియోగదారుల యొక్క పోస్ట్లను దాచిపెట్టు",
+  "settings.max_thumbnails": "Maximum amount of thumbnails per post",
+  "settings.hide_isp": "Hide instance-specific panel",
+  "settings.preload_images": "Preload images",
+  "settings.use_one_click_nsfw": "కేవలం ఒక క్లిక్ తో NSFW జోడింపులను తెరవండి",
+  "settings.hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
+  "settings.hide_user_stats": "Hide user statistics (e.g. the number of followers)",
+  "settings.hide_filtered_statuses": "Hide filtered statuses",
+  "settings.import_followers_from_a_csv_file": "Import follows from a csv file",
+  "settings.import_theme": "Load preset",
+  "settings.inputRadius": "Input fields",
+  "settings.checkboxRadius": "Checkboxes",
+  "settings.instance_default": "(default: {value})",
+  "settings.instance_default_simple": "(default)",
+  "settings.interface": "Interface",
+  "settings.interfaceLanguage": "Interface language",
+  "settings.invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
+  "settings.limited_availability": "మీ బ్రౌజర్లో అందుబాటులో లేదు",
+  "settings.links": "Links",
+  "settings.lock_account_description": "మీ ఖాతాను ఆమోదించిన అనుచరులకు మాత్రమే పరిమితం చేయండి",
+  "settings.loop_video": "Loop videos",
+  "settings.loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
+  "settings.mutes_tab": "మ్యూట్ చేయబడినవి",
+  "settings.play_videos_in_modal": "మీడియా వీక్షికలో నేరుగా వీడియోలను ప్లే చేయి",
+  "settings.use_contain_fit": "అటాచ్మెంట్ సూక్ష్మచిత్రాలను కత్తిరించవద్దు",
+  "settings.name": "Name",
+  "settings.name_bio": "పేరు & బయో",
+  "settings.new_password": "కొత్త సంకేతపదం",
+  "settings.notification_visibility": "చూపించవలసిన నోటిఫికేషన్ రకాలు",
+  "settings.notification_visibility_follows": "Follows",
+  "settings.notification_visibility_likes": "ఇష్టాలు",
+  "settings.notification_visibility_mentions": "ప్రస్తావనలు",
+  "settings.notification_visibility_repeats": "పునఃప్రసారాలు",
+  "settings.no_rich_text_description": "అన్ని పోస్ట్ల నుండి రిచ్ టెక్స్ట్ ఫార్మాటింగ్ను స్ట్రిప్ చేయండి",
+  "settings.no_blocks": "బ్లాక్స్ లేవు",
+  "settings.no_mutes": "మ్యూట్లు లేవు",
+  "settings.hide_follows_description": "నేను ఎవరిని అనుసరిస్తున్నానో చూపించవద్దు",
+  "settings.hide_followers_description": "నన్ను ఎవరు అనుసరిస్తున్నారో చూపవద్దు",
+  "settings.show_admin_badge": "నా ప్రొఫైల్ లో అడ్మిన్ బ్యాడ్జ్ చూపించు",
+  "settings.show_moderator_badge": "నా ప్రొఫైల్లో మోడరేటర్ బ్యాడ్జ్ని చూపించు",
+  "settings.nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
+  "settings.oauth_tokens": "OAuth tokens",
+  "settings.token": "Token",
+  "settings.refresh_token": "Refresh Token",
+  "settings.valid_until": "Valid Until",
+  "settings.revoke_token": "Revoke",
+  "settings.panelRadius": "Panels",
+  "settings.pause_on_unfocused": "Pause streaming when tab is not focused",
+  "settings.presets": "Presets",
+  "settings.profile_background": "Profile Background",
+  "settings.profile_banner": "Profile Banner",
+  "settings.profile_tab": "Profile",
+  "settings.radii_help": "Set up interface edge rounding (in pixels)",
+  "settings.replies_in_timeline": "Replies in timeline",
+  "settings.reply_link_preview": "Enable reply-link preview on mouse hover",
+  "settings.reply_visibility_all": "Show all replies",
+  "settings.reply_visibility_following": "Only show replies directed at me or users I'm following",
+  "settings.reply_visibility_self": "Only show replies directed at me",
+  "settings.saving_err": "Error saving settings",
+  "settings.saving_ok": "Settings saved",
+  "settings.security_tab": "Security",
+  "settings.scope_copy": "Copy scope when replying (DMs are always copied)",
+  "settings.set_new_avatar": "Set new avatar",
+  "settings.set_new_profile_background": "Set new profile background",
+  "settings.set_new_profile_banner": "Set new profile banner",
+  "settings.settings": "Settings",
+  "settings.subject_input_always_show": "Always show subject field",
+  "settings.subject_line_behavior": "Copy subject when replying",
+  "settings.subject_line_email": "Like email: \"re: subject\"",
+  "settings.subject_line_mastodon": "Like mastodon: copy as is",
+  "settings.subject_line_noop": "Do not copy",
+  "settings.post_status_content_type": "Post status content type",
+  "settings.stop_gifs": "Play-on-hover GIFs",
+  "settings.streaming": "Enable automatic streaming of new posts when scrolled to the top",
+  "settings.text": "Text",
+  "settings.theme": "Theme",
+  "settings.theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
+  "settings.theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
+  "settings.theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
+  "settings.tooltipRadius": "Tooltips/alerts",
+  "settings.upload_a_photo": "Upload a photo",
+  "settings.user_settings": "User Settings",
+  "settings.values.false": "no",
+  "settings.values.true": "yes",
+  "settings.notifications": "Notifications",
+  "settings.enable_web_push_notifications": "Enable web push notifications",
+  "settings.style.switcher.keep_color": "Keep colors",
+  "settings.style.switcher.keep_shadows": "Keep shadows",
+  "settings.style.switcher.keep_opacity": "Keep opacity",
+  "settings.style.switcher.keep_roundness": "Keep roundness",
+  "settings.style.switcher.keep_fonts": "Keep fonts",
+  "settings.style.switcher.save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
+  "settings.style.switcher.reset": "Reset",
+  "settings.style.switcher.clear_all": "Clear all",
+  "settings.style.switcher.clear_opacity": "Clear opacity",
+  "settings.style.common.color": "Color",
+  "settings.style.common.opacity": "Opacity",
+  "settings.style.common.contrast.hint": "Contrast ratio is {ratio}, it {level} {context}",
+  "settings.style.common.contrast.level.aa": "meets Level AA guideline (minimal)",
+  "settings.style.common.contrast.level.aaa": "meets Level AAA guideline (recommended)",
+  "settings.style.common.contrast.level.bad": "doesn't meet any accessibility guidelines",
+  "settings.style.common.contrast.context.18pt": "for large (18pt+) text",
+  "settings.style.common.contrast.context.text": "for text",
+  "settings.style.common_colors._tab_label": "Common",
+  "settings.style.common_colors.main": "Common colors",
+  "settings.style.common_colors.foreground_hint": "See \"Advanced\" tab for more detailed control",
+  "settings.style.common_colors.rgbo": "Icons, accents, badges",
+  "settings.style.advanced_colors._tab_label": "Advanced",
+  "settings.style.advanced_colors.alert": "Alert background",
+  "settings.style.advanced_colors.alert_error": "Error",
+  "settings.style.advanced_colors.badge": "Badge background",
+  "settings.style.advanced_colors.badge_notification": "Notification",
+  "settings.style.advanced_colors.panel_header": "Panel header",
+  "settings.style.advanced_colors.top_bar": "Top bar",
+  "settings.style.advanced_colors.borders": "Borders",
+  "settings.style.advanced_colors.buttons": "Buttons",
+  "settings.style.advanced_colors.inputs": "Input fields",
+  "settings.style.advanced_colors.faint_text": "Faded text",
+  "settings.style.radii._tab_label": "Roundness",
+  "settings.style.shadows._tab_label": "Shadow and lighting",
+  "settings.style.shadows.component": "Component",
+  "settings.style.shadows.override": "Override",
+  "settings.style.shadows.shadow_id": "Shadow #{value}",
+  "settings.style.shadows.blur": "Blur",
+  "settings.style.shadows.spread": "Spread",
+  "settings.style.shadows.inset": "Inset",
+  "settings.style.shadows.hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.",
+  "settings.style.shadows.filter_hint.always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
+  "settings.style.shadows.filter_hint.drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
+  "settings.style.shadows.filter_hint.avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
+  "settings.style.shadows.filter_hint.spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
+  "settings.style.shadows.filter_hint.inset_classic": "Inset shadows will be using {0}",
+  "settings.style.shadows.components.panel": "Panel",
+  "settings.style.shadows.components.panelHeader": "Panel header",
+  "settings.style.shadows.components.topBar": "Top bar",
+  "settings.style.shadows.components.avatar": "User avatar (in profile view)",
+  "settings.style.shadows.components.avatarStatus": "User avatar (in post display)",
+  "settings.style.shadows.components.popup": "Popups and tooltips",
+  "settings.style.shadows.components.button": "Button",
+  "settings.style.shadows.components.buttonHover": "Button (hover)",
+  "settings.style.shadows.components.buttonPressed": "Button (pressed)",
+  "settings.style.shadows.components.buttonPressedHover": "Button (pressed+hover)",
+  "settings.style.shadows.components.input": "Input field",
+  "settings.style.fonts._tab_label": "Fonts",
+  "settings.style.fonts.help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
+  "settings.style.fonts.components.interface": "Interface",
+  "settings.style.fonts.components.input": "Input fields",
+  "settings.style.fonts.components.post": "Post text",
+  "settings.style.fonts.components.postCode": "Monospaced text in a post (rich text)",
+  "settings.style.fonts.family": "Font name",
+  "settings.style.fonts.size": "Size (in px)",
+  "settings.style.fonts.weight": "Weight (boldness)",
+  "settings.style.fonts.custom": "Custom",
+  "settings.style.preview.header": "Preview",
+  "settings.style.preview.content": "Content",
+  "settings.style.preview.error": "Example error",
+  "settings.style.preview.button": "Button",
+  "settings.style.preview.text": "A bunch of more {0} and {1}",
+  "settings.style.preview.mono": "content",
+  "settings.style.preview.input": "Just landed in L.A.",
+  "settings.style.preview.faint_link": "helpful manual",
+  "settings.style.preview.fine_print": "Read our {0} to learn nothing useful!",
+  "settings.style.preview.header_faint": "This is fine",
+  "settings.style.preview.checkbox": "I have skimmed over terms and conditions",
+  "settings.style.preview.link": "a nice lil' link",
+  "settings.version.title": "Version",
+  "settings.version.backend_version": "Backend Version",
+  "settings.version.frontend_version": "Frontend Version",
+  "timeline.collapse": "Collapse",
+  "timeline.conversation": "Conversation",
+  "timeline.error_fetching": "Error fetching updates",
+  "timeline.load_older": "Load older statuses",
+  "timeline.no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
+  "timeline.repeated": "repeated",
+  "timeline.show_new": "Show new",
+  "timeline.up_to_date": "Up-to-date",
+  "timeline.no_more_statuses": "No more statuses",
+  "timeline.no_statuses": "No statuses",
+  "status.reply_to": "Reply to",
+  "status.replies_list": "Replies:",
+  "user_card.approve": "Approve",
+  "user_card.block": "Block",
+  "user_card.blocked": "Blocked!",
+  "user_card.deny": "Deny",
+  "user_card.favorites": "Favorites",
+  "user_card.follow": "Follow",
+  "user_card.follow_sent": "Request sent!",
+  "user_card.follow_progress": "Requesting…",
+  "user_card.follow_again": "Send request again?",
+  "user_card.follow_unfollow": "Unfollow",
+  "user_card.followees": "Following",
+  "user_card.followers": "Followers",
+  "user_card.following": "Following!",
+  "user_card.follows_you": "Follows you!",
+  "user_card.its_you": "It's you!",
+  "user_card.media": "Media",
+  "user_card.mute": "Mute",
+  "user_card.muted": "Muted",
+  "user_card.per_day": "per day",
+  "user_card.remote_follow": "Remote follow",
+  "user_card.statuses": "Statuses",
+  "user_card.unblock": "Unblock",
+  "user_card.unblock_progress": "Unblocking...",
+  "user_card.block_progress": "Blocking...",
+  "user_card.unmute": "Unmute",
+  "user_card.unmute_progress": "Unmuting...",
+  "user_card.mute_progress": "Muting...",
+  "user_profile.timeline_title": "User Timeline",
+  "user_profile.profile_does_not_exist": "Sorry, this profile does not exist.",
+  "user_profile.profile_loading_error": "Sorry, there was an error loading this profile.",
+  "who_to_follow.more": "More",
+  "who_to_follow.who_to_follow": "Who to follow",
+  "tool_tip.media_upload": "Upload Media",
+  "tool_tip.repeat": "Repeat",
+  "tool_tip.reply": "Reply",
+  "tool_tip.favorite": "Favorite",
+  "tool_tip.user_settings": "User Settings",
+  "upload.error.base": "Upload failed.",
+  "upload.error.file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+  "upload.error.default": "Try again later",
+  "upload.file_size_units.B": "B",
+  "upload.file_size_units.KiB": "KiB",
+  "upload.file_size_units.MiB": "MiB",
+  "upload.file_size_units.GiB": "GiB",
+  "upload.file_size_units.TiB": "TiB"
 }
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index 095db754..f95dc498 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -1,698 +1,698 @@
 {
-    "chat": {
-        "title": "聊天"
-    },
-    "exporter": {
-        "export": "导出",
-        "processing": "正在处理,稍后会提示您下载文件"
-    },
-    "features_panel": {
-        "chat": "聊天",
-        "gopher": "Gopher",
-        "media_proxy": "媒体代理",
-        "scope_options": "可见范围设置",
-        "text_limit": "文本长度限制",
-        "title": "功能",
-        "who_to_follow": "推荐关注"
-    },
-    "finder": {
-        "error_fetching_user": "获取用户时发生错误",
-        "find_user": "寻找用户"
-    },
-    "general": {
-        "apply": "应用",
-        "submit": "提交",
-        "more": "更多",
-        "generic_error": "发生一个错误",
-        "optional": "可选项",
-        "show_more": "展开",
-        "show_less": "收起",
-        "cancel": "取消",
-        "disable": "禁用",
-        "enable": "启用",
-        "confirm": "确认",
-        "verify": "验证",
-        "dismiss": "忽略"
-    },
-    "image_cropper": {
-        "crop_picture": "裁剪图片",
-        "save": "保存",
-        "save_without_cropping": "保存未经裁剪的图片",
-        "cancel": "取消"
-    },
-    "importer": {
-        "submit": "提交",
-        "success": "导入成功。",
-        "error": "导入此文件时出现一个错误。"
-    },
-    "login": {
-        "login": "登录",
-        "description": "用 OAuth 登录",
-        "logout": "登出",
-        "password": "密码",
-        "placeholder": "例如:lain",
-        "register": "注册",
-        "username": "用户名",
-        "hint": "登录后加入讨论",
-        "authentication_code": "验证码",
-        "enter_recovery_code": "输入一个恢复码",
-        "enter_two_factor_code": "输入一个双重因素验证码",
-        "recovery_code": "恢复码",
-        "heading": {
-            "totp": "双重因素验证",
-            "recovery": "双重因素恢复"
-        }
-    },
-    "media_modal": {
-        "previous": "往前",
-        "next": "往后"
-    },
-    "nav": {
-        "about": "关于",
-        "back": "后退",
-        "chat": "本站聊天",
-        "friend_requests": "关注请求",
-        "mentions": "提及",
-        "interactions": "互动",
-        "dms": "私信",
-        "public_tl": "公共时间线",
-        "timeline": "时间线",
-        "twkn": "所有已知网络",
-        "user_search": "用户搜索",
-        "search": "搜索",
-        "who_to_follow": "推荐关注",
-        "preferences": "偏好设置",
-        "administration": "管理员"
-    },
-    "notifications": {
-        "broken_favorite": "未知的状态,正在搜索中...",
-        "favorited_you": "收藏了你的状态",
-        "followed_you": "关注了你",
-        "load_older": "加载更早的通知",
-        "notifications": "通知",
-        "read": "阅读!",
-        "repeated_you": "转发了你的状态",
-        "no_more_notifications": "没有更多的通知",
-        "reacted_with": "和 {0} 互动过",
-        "migrated_to": "迁移到",
-        "follow_request": "想要关注你"
-    },
-    "polls": {
-        "add_poll": "增加问卷调查",
-        "add_option": "增加选项",
-        "option": "选项",
-        "votes": "投票",
-        "vote": "投票",
-        "type": "问卷类型",
-        "single_choice": "单选项",
-        "multiple_choices": "多选项",
-        "expiry": "问卷的时间",
-        "expires_in": "投票于 {0} 内结束",
-        "expired": "投票 {0} 前已结束",
-        "not_enough_options": "投票的选项太少"
-    },
-    "stickers": {
-        "add_sticker": "添加贴纸"
-    },
-    "interactions": {
-        "favs_repeats": "转发和收藏",
-        "follows": "新的关注者",
-        "load_older": "加载更早的互动",
-        "moves": "用户迁移"
-    },
-    "post_status": {
-        "new_status": "发布新状态",
-        "account_not_locked_warning": "你的帐号没有 {0}。任何人都可以关注你并浏览你的上锁内容。",
-        "account_not_locked_warning_link": "上锁",
-        "attachments_sensitive": "标记附件为敏感内容",
-        "content_type": {
-            "text/plain": "纯文本",
-            "text/html": "HTML",
-            "text/markdown": "Markdown",
-            "text/bbcode": "BBCode"
-        },
-        "content_warning": "主题(可选)",
-        "default": "刚刚抵达上海",
-        "direct_warning_to_all": "本条内容只有被提及的用户能够看到。",
-        "direct_warning_to_first_only": "本条内容只有被在消息开始处提及的用户能够看到。",
-        "posting": "发送",
-        "scope_notice": {
-            "public": "本条内容可以被所有人看到",
-            "private": "关注你的人才能看到本条内容",
-            "unlisted": "本条内容既不在公共时间线,也不会在所有已知网络上可见"
-        },
-        "scope": {
-            "direct": "私信 - 只发送给被提及的用户",
-            "private": "仅关注者 - 只有关注了你的人能看到",
-            "public": "公共 - 发送到公共时间轴",
-            "unlisted": "不公开 - 不会发送到公共时间轴"
-        }
-    },
-    "registration": {
-        "bio": "简介",
-        "email": "电子邮箱",
-        "fullname": "全名",
-        "password_confirm": "确认密码",
-        "registration": "注册",
-        "token": "邀请码",
-        "captcha": "CAPTCHA",
-        "new_captcha": "点击图片获取新的验证码",
-        "username_placeholder": "例如:lain",
-        "fullname_placeholder": "例如:岩仓玲音",
-        "bio_placeholder": "例如:\n你好,我是玲音。\n我是一个住在日本郊区的动画少女。你可能在 Wired 见过我。",
-        "validations": {
-            "username_required": "不能留空",
-            "fullname_required": "不能留空",
-            "email_required": "不能留空",
-            "password_required": "不能留空",
-            "password_confirmation_required": "不能留空",
-            "password_confirmation_match": "密码不一致"
-        }
-    },
-    "selectable_list": {
-        "select_all": "选择全部"
-    },
-    "settings": {
-        "app_name": "App 名称",
-        "security": "安全",
-        "enter_current_password_to_confirm": "输入你当前密码来确认你的身份",
-        "mfa": {
-            "otp": "OTP",
-            "setup_otp": "设置 OTP",
-            "wait_pre_setup_otp": "预设 OTP",
-            "confirm_and_enable": "确认并启用 OTP",
-            "title": "双因素验证",
-            "generate_new_recovery_codes": "生成新的恢复码",
-            "warning_of_generate_new_codes": "当你生成新的恢复码时,你的旧恢复码就失效了。",
-            "recovery_codes": "恢复码。",
-            "waiting_a_recovery_codes": "正在接收备份码……",
-            "recovery_codes_warning": "抄写这些号码,或者保存在安全的地方。这些号码不会再次显示。如果你无法访问你的 2FA app,也丢失了你的恢复码,你的账号就再也无法登录了。",
-            "authentication_methods": "身份验证方法",
-            "scan": {
-                "title": "扫一下",
-                "desc": "使用你的双因素验证 app,扫描这个二维码,或者输入这些文字密钥:",
-                "secret_code": "密钥"
-            },
-            "verify": {
-                "desc": "要启用双因素验证,请把你的双因素验证 app 里的数字输入:"
-            }
-        },
-        "attachmentRadius": "附件",
-        "attachments": "附件",
-        "autoload": "启用滚动到底部时的自动加载",
-        "avatar": "头像",
-        "avatarAltRadius": "头像(通知)",
-        "avatarRadius": "头像",
-        "background": "背景",
-        "bio": "简介",
-        "block_export": "拉黑名单导出",
-        "block_export_button": "导出你的拉黑名单到一个 csv 文件",
-        "block_import": "拉黑名单导入",
-        "block_import_error": "导入拉黑名单出错",
-        "blocks_imported": "拉黑名单导入成功!需要一点时间来处理。",
-        "blocks_tab": "块",
-        "btnRadius": "按钮",
-        "cBlue": "蓝色(回复,关注)",
-        "cGreen": "绿色(转发)",
-        "cOrange": "橙色(收藏)",
-        "cRed": "红色(取消)",
-        "change_password": "修改密码",
-        "change_password_error": "修改密码的时候出了点问题。",
-        "changed_password": "成功修改了密码!",
-        "collapse_subject": "折叠带主题的内容",
-        "composing": "正在书写",
-        "confirm_new_password": "确认新密码",
-        "current_avatar": "当前头像",
-        "current_password": "当前密码",
-        "current_profile_banner": "您当前的横幅图片",
-        "data_import_export_tab": "数据导入/导出",
-        "default_vis": "默认可见范围",
-        "delete_account": "删除账户",
-        "delete_account_description": "永久删除你的帐号和所有数据。",
-        "delete_account_error": "删除账户时发生错误,如果一直删除不了,请联系实例管理员。",
-        "delete_account_instructions": "在下面输入你的密码来确认删除账户",
-        "avatar_size_instruction": "推荐的头像图片最小的尺寸是 150x150 像素。",
-        "export_theme": "导出预置主题",
-        "filtering": "过滤器",
-        "filtering_explanation": "所有包含以下词汇的内容都会被隐藏,一行一个",
-        "follow_export": "导出关注",
-        "follow_export_button": "将关注导出成 csv 文件",
-        "follow_import": "导入关注",
-        "follow_import_error": "导入关注时错误",
-        "follows_imported": "关注已导入!尚需要一些时间来处理。",
-        "foreground": "前景",
-        "general": "通用",
-        "hide_attachments_in_convo": "在对话中隐藏附件",
-        "hide_attachments_in_tl": "在时间线上隐藏附件",
-        "hide_muted_posts": "不显示被隐藏的用户的帖子",
-        "max_thumbnails": "最多再每个帖子所能显示的缩略图数量",
-        "hide_isp": "隐藏指定实例的面板H",
-        "preload_images": "预载图片",
-        "use_one_click_nsfw": "点击一次以打开工作场所不适宜的附件",
-        "hide_post_stats": "隐藏推文相关的统计数据(例如:收藏的次数)",
-        "hide_user_stats": "隐藏用户的统计数据(例如:关注者的数量)",
-        "hide_filtered_statuses": "隐藏过滤的状态",
-        "import_blocks_from_a_csv_file": "从 csv 文件中导入拉黑名单",
-        "import_followers_from_a_csv_file": "从 csv 文件中导入关注",
-        "import_theme": "导入预置主题",
-        "inputRadius": "输入框",
-        "checkboxRadius": "复选框",
-        "instance_default": "(默认:{value})",
-        "instance_default_simple": "(默认)",
-        "interface": "界面",
-        "interfaceLanguage": "界面语言",
-        "invalid_theme_imported": "您所选择的主题文件不被 Pleroma 支持,因此主题未被修改。",
-        "limited_availability": "在您的浏览器中无法使用",
-        "links": "链接",
-        "lock_account_description": "你需要手动审核关注请求",
-        "loop_video": "循环视频",
-        "loop_video_silent_only": "只循环没有声音的视频(例如:Mastodon 里的“GIF”)",
-        "mutes_tab": "隐藏",
-        "play_videos_in_modal": "在弹出框内播放视频",
-        "use_contain_fit": "生成缩略图时不要裁剪附件",
-        "name": "名字",
-        "name_bio": "名字及简介",
-        "new_password": "新密码",
-        "notification_visibility": "要显示的通知类型",
-        "notification_visibility_follows": "关注",
-        "notification_visibility_likes": "点赞",
-        "notification_visibility_mentions": "提及",
-        "notification_visibility_repeats": "转发",
-        "no_rich_text_description": "不显示富文本格式",
-        "no_blocks": "没有拉黑的",
-        "no_mutes": "没有隐藏",
-        "hide_follows_description": "不要显示我所关注的人",
-        "hide_followers_description": "不要显示关注我的人",
-        "show_admin_badge": "显示管理徽章",
-        "show_moderator_badge": "显示版主徽章",
-        "nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开",
-        "oauth_tokens": "OAuth令牌",
-        "token": "令牌",
-        "refresh_token": "刷新令牌",
-        "valid_until": "有效期至",
-        "revoke_token": "撤消",
-        "panelRadius": "面板",
-        "pause_on_unfocused": "在离开页面时暂停时间线推送",
-        "presets": "预置",
-        "profile_background": "个人资料背景图",
-        "profile_banner": "横幅图片",
-        "profile_tab": "个人资料",
-        "radii_help": "设置界面边缘的圆角 (单位:像素)",
-        "replies_in_timeline": "时间线中的回复",
-        "reply_link_preview": "启用鼠标悬停时预览回复链接",
-        "reply_visibility_all": "显示所有回复",
-        "reply_visibility_following": "只显示发送给我的回复/发送给我关注的用户的回复",
-        "reply_visibility_self": "只显示发送给我的回复",
-        "autohide_floating_post_button": "自动隐藏新帖子的按钮(移动设备)",
-        "saving_err": "保存设置时发生错误",
-        "saving_ok": "设置已保存",
-        "search_user_to_block": "搜索你想屏蔽的用户",
-        "search_user_to_mute": "搜索你想要隐藏的用户",
-        "security_tab": "安全",
-        "scope_copy": "回复时的复制范围(私信是总是复制的)",
-        "minimal_scopes_mode": "最小发文范围",
-        "set_new_avatar": "设置新头像",
-        "set_new_profile_background": "设置新的个人资料背景",
-        "set_new_profile_banner": "设置新的横幅图片",
-        "settings": "设置",
-        "subject_input_always_show": "总是显示主题框",
-        "subject_line_behavior": "回复时复制主题",
-        "subject_line_email": "比如电邮: \"re: 主题\"",
-        "subject_line_mastodon": "比如 mastodon: copy as is",
-        "subject_line_noop": "不要复制",
-        "post_status_content_type": "发文状态内容类型",
-        "stop_gifs": "鼠标悬停时播放GIF",
-        "streaming": "开启滚动到顶部时的自动推送",
-        "text": "文本",
-        "theme": "主题",
-        "theme_help": "使用十六进制代码(#rrggbb)来设置主题颜色。",
-        "theme_help_v2_1": "你也可以通过切换复选框来覆盖某些组件的颜色和透明。使用“清除所有”来清楚所有覆盖设置。",
-        "theme_help_v2_2": "某些条目下的图标是背景或文本对比指示器,鼠标悬停可以获取详细信息。请记住,使用透明度来显示最差的情况。",
-        "tooltipRadius": "提醒",
-        "upload_a_photo": "上传照片",
-        "user_settings": "用户设置",
-        "values": {
-            "false": "否",
-            "true": "是"
-        },
-        "notifications": "通知",
-        "notification_setting": "通知来源:",
-        "notification_setting_follows": "你所关注的用户",
-        "notification_setting_non_follows": "你没有关注的用户",
-        "notification_setting_followers": "关注你的用户",
-        "notification_setting_non_followers": "没有关注你的用户",
-        "notification_mutes": "要停止收到某个指定的用户的通知,请使用隐藏功能。",
-        "notification_blocks": "拉黑一个用户会停掉所有他的通知,等同于取消关注。",
-        "enable_web_push_notifications": "启用 web 推送通知",
-        "style": {
-            "switcher": {
-                "keep_color": "保留颜色",
-                "keep_shadows": "保留阴影",
-                "keep_opacity": "保留透明度",
-                "keep_roundness": "保留圆角",
-                "keep_fonts": "保留字体",
-                "save_load_hint": "\"保留\" 选项在选择或加载主题时保留当前设置的选项,在导出主题时还会存储上述选项。当所有复选框未设置时,导出主题将保存所有内容。",
-                "reset": "重置",
-                "clear_all": "清除全部",
-                "clear_opacity": "清除透明度",
-                "load_theme": "加载主题",
-                "help": {
-                    "upgraded_from_v2": "PleromaFE 已升级,主题会和你记忆中的不太一样。"
-                },
-                "use_source": "新版本",
-                "use_snapshot": "老版本",
-                "keep_as_is": "保持原状"
-            },
-            "common": {
-                "color": "颜色",
-                "opacity": "透明度",
-                "contrast": {
-                    "hint": "对比度是 {ratio}, 它 {level} {context}",
-                    "level": {
-                        "aa": "符合 AA 等级准则(最低)",
-                        "aaa": "符合 AAA 等级准则(推荐)",
-                        "bad": "不符合任何辅助功能指南"
-                    },
-                    "context": {
-                        "18pt": "大字文本 (18pt+)",
-                        "text": "文本"
-                    }
-                }
-            },
-            "common_colors": {
-                "_tab_label": "常规",
-                "main": "常用颜色",
-                "foreground_hint": "点击”高级“ 标签进行细致的控制",
-                "rgbo": "图标,口音,徽章"
-            },
-            "advanced_colors": {
-                "_tab_label": "高级",
-                "alert": "提醒或警告背景色",
-                "alert_error": "错误",
-                "badge": "徽章背景",
-                "badge_notification": "通知",
-                "panel_header": "面板标题",
-                "top_bar": "顶栏",
-                "borders": "边框",
-                "buttons": "按钮",
-                "inputs": "输入框",
-                "faint_text": "灰度文字"
-            },
-            "radii": {
-                "_tab_label": "圆角"
-            },
-            "shadows": {
-                "_tab_label": "阴影和照明",
-                "component": "组件",
-                "override": "覆盖",
-                "shadow_id": "阴影 #{value}",
-                "blur": "模糊",
-                "spread": "扩散",
-                "inset": "插入内部",
-                "hint": "对于阴影你还可以使用 --variable 作为颜色值来使用 CSS3 变量。请注意,这种情况下,透明设置将不起作用。",
-                "filter_hint": {
-                    "always_drop_shadow": "警告,此阴影设置会总是使用 {0} ,如果浏览器支持的话。",
-                    "drop_shadow_syntax": "{0} 不支持参数 {1} 和关键词 {2} 。",
-                    "avatar_inset": "请注意组合两个内部和非内部的阴影到头像上,在透明头像上可能会有意料之外的效果。",
-                    "spread_zero": "阴影的扩散 > 0 会同设置成零一样",
-                    "inset_classic": "插入内部的阴影会使用 {0}"
-                },
-                "components": {
-                    "panel": "面板",
-                    "panelHeader": "面板标题",
-                    "topBar": "顶栏",
-                    "avatar": "用户头像(在个人资料栏)",
-                    "avatarStatus": "用户头像(在帖子显示栏)",
-                    "popup": "弹窗和工具提示",
-                    "button": "按钮",
-                    "buttonHover": "按钮(悬停)",
-                    "buttonPressed": "按钮(按下)",
-                    "buttonPressedHover": "按钮(按下和悬停)",
-                    "input": "输入框"
-                }
-            },
-            "fonts": {
-                "_tab_label": "字体",
-                "help": "给用户界面的元素选择字体。选择 “自选”的你必须输入确切的字体名称。",
-                "components": {
-                    "interface": "界面",
-                    "input": "输入框",
-                    "post": "发帖文字",
-                    "postCode": "帖子中使用等间距文字(富文本)"
-                },
-                "family": "字体名称",
-                "size": "大小 (in px)",
-                "weight": "字重 (粗体))",
-                "custom": "自选"
-            },
-            "preview": {
-                "header": "预览",
-                "content": "内容",
-                "error": "例子错误",
-                "button": "按钮",
-                "text": "有堆 {0} 和 {1}",
-                "mono": "内容",
-                "input": "刚刚抵达上海",
-                "faint_link": "帮助菜单",
-                "fine_print": "阅读我们的 {0} ,然而什么也学不到!",
-                "header_faint": "这很正常",
-                "checkbox": "我已经浏览了 TOC",
-                "link": "一个很棒的摇滚链接"
-            }
-        },
-        "version": {
-            "title": "版本",
-            "backend_version": "后端版本",
-            "frontend_version": "前端版本"
-        },
-        "notification_setting_filters": "过滤器",
-        "domain_mutes": "域名",
-        "changed_email": "邮箱修改成功!",
-        "change_email_error": "修改你的电子邮箱时发生错误",
-        "change_email": "修改电子邮箱",
-        "allow_following_move": "正在关注的账号迁移时自动重新关注",
-        "notification_setting_privacy_option": "在通知推送中隐藏发送者和内容",
-        "notification_setting_privacy": "隐私",
-        "hide_follows_count_description": "不显示关注数",
-        "notification_visibility_emoji_reactions": "互动",
-        "notification_visibility_moves": "用户迁移",
-        "new_email": "新邮箱",
-        "emoji_reactions_on_timeline": "在时间线上显示表情符号互动"
-    },
-    "time": {
-        "day": "{0} 天",
-        "days": "{0} 天",
-        "day_short": "{0}d",
-        "days_short": "{0}d",
-        "hour": "{0} 小时",
-        "hours": "{0} 小时",
-        "hour_short": "{0}h",
-        "hours_short": "{0}h",
-        "in_future": "还有 {0}",
-        "in_past": "{0} 之前",
-        "minute": "{0} 分钟",
-        "minutes": "{0} 分钟",
-        "minute_short": "{0}min",
-        "minutes_short": "{0}min",
-        "month": "{0} 月",
-        "months": "{0} 月",
-        "month_short": "{0}mo",
-        "months_short": "{0}mo",
-        "now": "刚刚",
-        "now_short": "刚刚",
-        "second": "{0} 秒",
-        "seconds": "{0} 秒",
-        "second_short": "{0}s",
-        "seconds_short": "{0}s",
-        "week": "{0} 周",
-        "weeks": "{0} 周",
-        "week_short": "{0}w",
-        "weeks_short": "{0}w",
-        "year": "{0} 年",
-        "years": "{0} 年",
-        "year_short": "{0}y",
-        "years_short": "{0}y"
-    },
-    "timeline": {
-        "collapse": "折叠",
-        "conversation": "对话",
-        "error_fetching": "获取更新时发生错误",
-        "load_older": "加载更早的状态",
-        "no_retweet_hint": "这条内容仅关注者可见,或者是私信,因此不能转发。",
-        "repeated": "已转发",
-        "show_new": "显示新内容",
-        "up_to_date": "已是最新",
-        "no_more_statuses": "没有更多的状态",
-        "no_statuses": "没有状态更新"
-    },
-    "status": {
-        "favorites": "收藏",
-        "repeats": "转发",
-        "delete": "删除状态",
-        "pin": "在个人资料置顶",
-        "unpin": "取消在个人资料置顶",
-        "pinned": "置顶",
-        "delete_confirm": "你真的想要删除这条状态吗?",
-        "reply_to": "回复",
-        "replies_list": "回复:",
-        "mute_conversation": "隐藏对话",
-        "unmute_conversation": "对话取消隐藏"
-    },
-    "user_card": {
-        "approve": "允许",
-        "block": "屏蔽",
-        "blocked": "已屏蔽!",
-        "deny": "拒绝",
-        "favorites": "收藏",
-        "follow": "关注",
-        "follow_sent": "请求已发送!",
-        "follow_progress": "请求中",
-        "follow_again": "再次发送请求?",
-        "follow_unfollow": "取消关注",
-        "followees": "正在关注",
-        "followers": "关注者",
-        "following": "正在关注!",
-        "follows_you": "关注了你!",
-        "its_you": "就是你!!",
-        "media": "媒体",
-        "mute": "隐藏",
-        "muted": "已隐藏",
-        "per_day": "每天",
-        "remote_follow": "跨站关注",
-        "report": "报告",
-        "statuses": "状态",
-        "subscribe": "订阅",
-        "unsubscribe": "退订",
-        "unblock": "取消拉黑",
-        "unblock_progress": "取消拉黑中...",
-        "block_progress": "拉黑中...",
-        "unmute": "取消隐藏",
-        "unmute_progress": "取消隐藏中...",
-        "mute_progress": "隐藏中...",
-        "admin_menu": {
-            "moderation": "权限",
-            "grant_admin": "赋予管理权限",
-            "revoke_admin": "撤销管理权限",
-            "grant_moderator": "赋予版主权限",
-            "revoke_moderator": "撤销版主权限",
-            "activate_account": "激活账号",
-            "deactivate_account": "关闭账号",
-            "delete_account": "删除账号",
-            "force_nsfw": "标记所有的帖子都是 - 工作场合不适",
-            "strip_media": "从帖子里删除媒体文件",
-            "force_unlisted": "强制帖子为不公开",
-            "sandbox": "强制帖子为只有关注者可看",
-            "disable_remote_subscription": "禁止从远程实例关注用户",
-            "disable_any_subscription": "完全禁止关注用户",
-            "quarantine": "从联合实例中禁止用户帖子",
-            "delete_user": "删除用户",
-            "delete_user_confirmation": "你确认吗?此操作无法撤销。"
-        },
-        "hidden": "已隐藏",
-        "show_repeats": "显示转发",
-        "hide_repeats": "隐藏转发"
-    },
-    "user_profile": {
-        "timeline_title": "用户时间线",
-        "profile_does_not_exist": "抱歉,此个人资料不存在。",
-        "profile_loading_error": "抱歉,载入个人资料时出错。"
-    },
-    "user_reporting": {
-        "title": "报告 {0}",
-        "add_comment_description": "此报告会发送给你的实例管理员。你可以在下面提供更多详细信息解释报告的缘由:",
-        "additional_comments": "其它信息",
-        "forward_description": "这个账号是从另外一个服务器。同时发送一个副本到那里?",
-        "forward_to": "转发 {0}",
-        "submit": "提交",
-        "generic_error": "当处理你的请求时,发生了一个错误。"
-    },
-    "who_to_follow": {
-        "more": "更多",
-        "who_to_follow": "推荐关注"
-    },
-    "tool_tip": {
-        "media_upload": "上传多媒体",
-        "repeat": "转发",
-        "reply": "回复",
-        "favorite": "收藏",
-        "user_settings": "用户设置",
-        "reject_follow_request": "拒绝关注请求",
-        "add_reaction": "添加互动"
-    },
-    "upload": {
-        "error": {
-            "base": "上传不成功。",
-            "file_too_big": "文件太大了 [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
-            "default": "迟些再试"
-        },
-        "file_size_units": {
-            "B": "B",
-            "KiB": "KiB",
-            "MiB": "MiB",
-            "GiB": "GiB",
-            "TiB": "TiB"
-        }
-    },
-    "search": {
-        "people": "人",
-        "hashtags": "Hashtags",
-        "person_talking": "{count} 人正在讨论",
-        "people_talking": "{count} 人正在讨论",
-        "no_results": "没有搜索结果"
-    },
-    "password_reset": {
-        "forgot_password": "忘记密码了?",
-        "password_reset": "重置密码",
-        "instruction": "输入你的电邮地址或者用户名,我们将发送一个链接到你的邮箱,用于重置密码。",
-        "placeholder": "你的电邮地址或者用户名",
-        "check_email": "检查你的邮箱,会有一个链接用于重置密码。",
-        "return_home": "回到首页",
-        "not_found": "我们无法找到匹配的邮箱地址或者用户名。",
-        "too_many_requests": "你触发了尝试的限制,请稍后再试。",
-        "password_reset_disabled": "密码重置已经被禁用。请联系你的实例管理员。"
-    },
-    "remote_user_resolver": {
-        "error": "未找到。",
-        "searching_for": "搜索",
-        "remote_user_resolver": "远程用户解析器"
-    },
-    "emoji": {
-        "keep_open": "选择器保持打开",
-        "stickers": "贴图",
-        "unicode": "Unicode 表情符号",
-        "custom": "自定义表情符号",
-        "add_emoji": "插入表情符号",
-        "search_emoji": "搜索表情符号",
-        "emoji": "表情符号"
-    },
-    "about": {
-        "mrf": {
-            "simple": {
-                "quarantine_desc": "本实例只会把公开状态发送非下列实例:",
-                "quarantine": "隔离",
-                "reject_desc": "本实例不会接收来自下列实例的消息:",
-                "reject": "拒绝",
-                "accept_desc": "本实例只接收来自下列实例的消息:",
-                "simple_policies": "站规",
-                "accept": "接受",
-                "media_removal": "移除媒体"
-            },
-            "mrf_policies_desc": "MRF 策略会影响本实例的互通行为。以下策略已启用:",
-            "mrf_policies": "已启动 MRF 策略",
-            "keyword": {
-                "ftl_removal": "从“全部已知网络”时间线上移除",
-                "keyword_policies": "关键词策略",
-                "is_replaced_by": "→",
-                "replace": "替换",
-                "reject": "拒绝"
-            },
-            "federation": "联邦"
-        }
-    },
-    "domain_mute_card": {
-        "unmute_progress": "正在取消隐藏……",
-        "unmute": "取消隐藏",
-        "mute_progress": "隐藏中……",
-        "mute": "隐藏"
+  "chat": {
+    "title": "聊天"
+  },
+  "exporter": {
+    "export": "导出",
+    "processing": "正在处理,稍后会提示您下载文件"
+  },
+  "features_panel": {
+    "chat": "聊天",
+    "gopher": "Gopher",
+    "media_proxy": "媒体代理",
+    "scope_options": "可见范围设置",
+    "text_limit": "文本长度限制",
+    "title": "功能",
+    "who_to_follow": "推荐关注"
+  },
+  "finder": {
+    "error_fetching_user": "获取用户时发生错误",
+    "find_user": "寻找用户"
+  },
+  "general": {
+    "apply": "应用",
+    "submit": "提交",
+    "more": "更多",
+    "generic_error": "发生一个错误",
+    "optional": "可选项",
+    "show_more": "展开",
+    "show_less": "收起",
+    "cancel": "取消",
+    "disable": "禁用",
+    "enable": "启用",
+    "confirm": "确认",
+    "verify": "验证",
+    "dismiss": "忽略"
+  },
+  "image_cropper": {
+    "crop_picture": "裁剪图片",
+    "save": "保存",
+    "save_without_cropping": "保存未经裁剪的图片",
+    "cancel": "取消"
+  },
+  "importer": {
+    "submit": "提交",
+    "success": "导入成功。",
+    "error": "导入此文件时出现一个错误。"
+  },
+  "login": {
+    "login": "登录",
+    "description": "用 OAuth 登录",
+    "logout": "登出",
+    "password": "密码",
+    "placeholder": "例如:lain",
+    "register": "注册",
+    "username": "用户名",
+    "hint": "登录后加入讨论",
+    "authentication_code": "验证码",
+    "enter_recovery_code": "输入一个恢复码",
+    "enter_two_factor_code": "输入一个双重因素验证码",
+    "recovery_code": "恢复码",
+    "heading": {
+      "totp": "双重因素验证",
+      "recovery": "双重因素恢复"
     }
+  },
+  "media_modal": {
+    "previous": "往前",
+    "next": "往后"
+  },
+  "nav": {
+    "about": "关于",
+    "back": "后退",
+    "chat": "本站聊天",
+    "friend_requests": "关注请求",
+    "mentions": "提及",
+    "interactions": "互动",
+    "dms": "私信",
+    "public_tl": "公共时间线",
+    "timeline": "时间线",
+    "twkn": "所有已知网络",
+    "user_search": "用户搜索",
+    "search": "搜索",
+    "who_to_follow": "推荐关注",
+    "preferences": "偏好设置",
+    "administration": "管理员"
+  },
+  "notifications": {
+    "broken_favorite": "未知的状态,正在搜索中...",
+    "favorited_you": "收藏了你的状态",
+    "followed_you": "关注了你",
+    "load_older": "加载更早的通知",
+    "notifications": "通知",
+    "read": "阅读!",
+    "repeated_you": "转发了你的状态",
+    "no_more_notifications": "没有更多的通知",
+    "reacted_with": "和 {0} 互动过",
+    "migrated_to": "迁移到",
+    "follow_request": "想要关注你"
+  },
+  "polls": {
+    "add_poll": "增加问卷调查",
+    "add_option": "增加选项",
+    "option": "选项",
+    "votes": "投票",
+    "vote": "投票",
+    "type": "问卷类型",
+    "single_choice": "单选项",
+    "multiple_choices": "多选项",
+    "expiry": "问卷的时间",
+    "expires_in": "投票于 {0} 内结束",
+    "expired": "投票 {0} 前已结束",
+    "not_enough_options": "投票的选项太少"
+  },
+  "stickers": {
+    "add_sticker": "添加贴纸"
+  },
+  "interactions": {
+    "favs_repeats": "转发和收藏",
+    "follows": "新的关注者",
+    "load_older": "加载更早的互动",
+    "moves": "用户迁移"
+  },
+  "post_status": {
+    "new_status": "发布新状态",
+    "account_not_locked_warning": "你的帐号没有 {0}。任何人都可以关注你并浏览你的上锁内容。",
+    "account_not_locked_warning_link": "上锁",
+    "attachments_sensitive": "标记附件为敏感内容",
+    "content_type": {
+      "text/plain": "纯文本",
+      "text/html": "HTML",
+      "text/markdown": "Markdown",
+      "text/bbcode": "BBCode"
+    },
+    "content_warning": "主题(可选)",
+    "default": "刚刚抵达上海",
+    "direct_warning_to_all": "本条内容只有被提及的用户能够看到。",
+    "direct_warning_to_first_only": "本条内容只有被在消息开始处提及的用户能够看到。",
+    "posting": "发送",
+    "scope_notice": {
+      "public": "本条内容可以被所有人看到",
+      "private": "关注你的人才能看到本条内容",
+      "unlisted": "本条内容既不在公共时间线,也不会在所有已知网络上可见"
+    },
+    "scope": {
+      "direct": "私信 - 只发送给被提及的用户",
+      "private": "仅关注者 - 只有关注了你的人能看到",
+      "public": "公共 - 发送到公共时间轴",
+      "unlisted": "不公开 - 不会发送到公共时间轴"
+    }
+  },
+  "registration": {
+    "bio": "简介",
+    "email": "电子邮箱",
+    "fullname": "全名",
+    "password_confirm": "确认密码",
+    "registration": "注册",
+    "token": "邀请码",
+    "captcha": "CAPTCHA",
+    "new_captcha": "点击图片获取新的验证码",
+    "username_placeholder": "例如:lain",
+    "fullname_placeholder": "例如:岩仓玲音",
+    "bio_placeholder": "例如:\n你好,我是玲音。\n我是一个住在日本郊区的动画少女。你可能在 Wired 见过我。",
+    "validations": {
+      "username_required": "不能留空",
+      "fullname_required": "不能留空",
+      "email_required": "不能留空",
+      "password_required": "不能留空",
+      "password_confirmation_required": "不能留空",
+      "password_confirmation_match": "密码不一致"
+    }
+  },
+  "selectable_list": {
+    "select_all": "选择全部"
+  },
+  "settings": {
+    "app_name": "App 名称",
+    "security": "安全",
+    "enter_current_password_to_confirm": "输入你当前密码来确认你的身份",
+    "mfa": {
+      "otp": "OTP",
+      "setup_otp": "设置 OTP",
+      "wait_pre_setup_otp": "预设 OTP",
+      "confirm_and_enable": "确认并启用 OTP",
+      "title": "双因素验证",
+      "generate_new_recovery_codes": "生成新的恢复码",
+      "warning_of_generate_new_codes": "当你生成新的恢复码时,你的旧恢复码就失效了。",
+      "recovery_codes": "恢复码。",
+      "waiting_a_recovery_codes": "正在接收备份码……",
+      "recovery_codes_warning": "抄写这些号码,或者保存在安全的地方。这些号码不会再次显示。如果你无法访问你的 2FA app,也丢失了你的恢复码,你的账号就再也无法登录了。",
+      "authentication_methods": "身份验证方法",
+      "scan": {
+        "title": "扫一下",
+        "desc": "使用你的双因素验证 app,扫描这个二维码,或者输入这些文字密钥:",
+        "secret_code": "密钥"
+      },
+      "verify": {
+        "desc": "要启用双因素验证,请把你的双因素验证 app 里的数字输入:"
+      }
+    },
+    "attachmentRadius": "附件",
+    "attachments": "附件",
+    "autoload": "启用滚动到底部时的自动加载",
+    "avatar": "头像",
+    "avatarAltRadius": "头像(通知)",
+    "avatarRadius": "头像",
+    "background": "背景",
+    "bio": "简介",
+    "block_export": "拉黑名单导出",
+    "block_export_button": "导出你的拉黑名单到一个 csv 文件",
+    "block_import": "拉黑名单导入",
+    "block_import_error": "导入拉黑名单出错",
+    "blocks_imported": "拉黑名单导入成功!需要一点时间来处理。",
+    "blocks_tab": "块",
+    "btnRadius": "按钮",
+    "cBlue": "蓝色(回复,关注)",
+    "cGreen": "绿色(转发)",
+    "cOrange": "橙色(收藏)",
+    "cRed": "红色(取消)",
+    "change_password": "修改密码",
+    "change_password_error": "修改密码的时候出了点问题。",
+    "changed_password": "成功修改了密码!",
+    "collapse_subject": "折叠带主题的内容",
+    "composing": "正在书写",
+    "confirm_new_password": "确认新密码",
+    "current_avatar": "当前头像",
+    "current_password": "当前密码",
+    "current_profile_banner": "您当前的横幅图片",
+    "data_import_export_tab": "数据导入/导出",
+    "default_vis": "默认可见范围",
+    "delete_account": "删除账户",
+    "delete_account_description": "永久删除你的帐号和所有数据。",
+    "delete_account_error": "删除账户时发生错误,如果一直删除不了,请联系实例管理员。",
+    "delete_account_instructions": "在下面输入你的密码来确认删除账户",
+    "avatar_size_instruction": "推荐的头像图片最小的尺寸是 150x150 像素。",
+    "export_theme": "导出预置主题",
+    "filtering": "过滤器",
+    "filtering_explanation": "所有包含以下词汇的内容都会被隐藏,一行一个",
+    "follow_export": "导出关注",
+    "follow_export_button": "将关注导出成 csv 文件",
+    "follow_import": "导入关注",
+    "follow_import_error": "导入关注时错误",
+    "follows_imported": "关注已导入!尚需要一些时间来处理。",
+    "foreground": "前景",
+    "general": "通用",
+    "hide_attachments_in_convo": "在对话中隐藏附件",
+    "hide_attachments_in_tl": "在时间线上隐藏附件",
+    "hide_muted_posts": "不显示被隐藏的用户的帖子",
+    "max_thumbnails": "最多再每个帖子所能显示的缩略图数量",
+    "hide_isp": "隐藏指定实例的面板H",
+    "preload_images": "预载图片",
+    "use_one_click_nsfw": "点击一次以打开工作场所不适宜的附件",
+    "hide_post_stats": "隐藏推文相关的统计数据(例如:收藏的次数)",
+    "hide_user_stats": "隐藏用户的统计数据(例如:关注者的数量)",
+    "hide_filtered_statuses": "隐藏过滤的状态",
+    "import_blocks_from_a_csv_file": "从 csv 文件中导入拉黑名单",
+    "import_followers_from_a_csv_file": "从 csv 文件中导入关注",
+    "import_theme": "导入预置主题",
+    "inputRadius": "输入框",
+    "checkboxRadius": "复选框",
+    "instance_default": "(默认:{value})",
+    "instance_default_simple": "(默认)",
+    "interface": "界面",
+    "interfaceLanguage": "界面语言",
+    "invalid_theme_imported": "您所选择的主题文件不被 Pleroma 支持,因此主题未被修改。",
+    "limited_availability": "在您的浏览器中无法使用",
+    "links": "链接",
+    "lock_account_description": "你需要手动审核关注请求",
+    "loop_video": "循环视频",
+    "loop_video_silent_only": "只循环没有声音的视频(例如:Mastodon 里的“GIF”)",
+    "mutes_tab": "隐藏",
+    "play_videos_in_modal": "在弹出框内播放视频",
+    "use_contain_fit": "生成缩略图时不要裁剪附件",
+    "name": "名字",
+    "name_bio": "名字及简介",
+    "new_password": "新密码",
+    "notification_visibility": "要显示的通知类型",
+    "notification_visibility_follows": "关注",
+    "notification_visibility_likes": "点赞",
+    "notification_visibility_mentions": "提及",
+    "notification_visibility_repeats": "转发",
+    "no_rich_text_description": "不显示富文本格式",
+    "no_blocks": "没有拉黑的",
+    "no_mutes": "没有隐藏",
+    "hide_follows_description": "不要显示我所关注的人",
+    "hide_followers_description": "不要显示关注我的人",
+    "show_admin_badge": "显示管理徽章",
+    "show_moderator_badge": "显示版主徽章",
+    "nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开",
+    "oauth_tokens": "OAuth令牌",
+    "token": "令牌",
+    "refresh_token": "刷新令牌",
+    "valid_until": "有效期至",
+    "revoke_token": "撤消",
+    "panelRadius": "面板",
+    "pause_on_unfocused": "在离开页面时暂停时间线推送",
+    "presets": "预置",
+    "profile_background": "个人资料背景图",
+    "profile_banner": "横幅图片",
+    "profile_tab": "个人资料",
+    "radii_help": "设置界面边缘的圆角 (单位:像素)",
+    "replies_in_timeline": "时间线中的回复",
+    "reply_link_preview": "启用鼠标悬停时预览回复链接",
+    "reply_visibility_all": "显示所有回复",
+    "reply_visibility_following": "只显示发送给我的回复/发送给我关注的用户的回复",
+    "reply_visibility_self": "只显示发送给我的回复",
+    "autohide_floating_post_button": "自动隐藏新帖子的按钮(移动设备)",
+    "saving_err": "保存设置时发生错误",
+    "saving_ok": "设置已保存",
+    "search_user_to_block": "搜索你想屏蔽的用户",
+    "search_user_to_mute": "搜索你想要隐藏的用户",
+    "security_tab": "安全",
+    "scope_copy": "回复时的复制范围(私信是总是复制的)",
+    "minimal_scopes_mode": "最小发文范围",
+    "set_new_avatar": "设置新头像",
+    "set_new_profile_background": "设置新的个人资料背景",
+    "set_new_profile_banner": "设置新的横幅图片",
+    "settings": "设置",
+    "subject_input_always_show": "总是显示主题框",
+    "subject_line_behavior": "回复时复制主题",
+    "subject_line_email": "比如电邮: \"re: 主题\"",
+    "subject_line_mastodon": "比如 mastodon: copy as is",
+    "subject_line_noop": "不要复制",
+    "post_status_content_type": "发文状态内容类型",
+    "stop_gifs": "鼠标悬停时播放GIF",
+    "streaming": "开启滚动到顶部时的自动推送",
+    "text": "文本",
+    "theme": "主题",
+    "theme_help": "使用十六进制代码(#rrggbb)来设置主题颜色。",
+    "theme_help_v2_1": "你也可以通过切换复选框来覆盖某些组件的颜色和透明。使用“清除所有”来清楚所有覆盖设置。",
+    "theme_help_v2_2": "某些条目下的图标是背景或文本对比指示器,鼠标悬停可以获取详细信息。请记住,使用透明度来显示最差的情况。",
+    "tooltipRadius": "提醒",
+    "upload_a_photo": "上传照片",
+    "user_settings": "用户设置",
+    "values": {
+      "false": "否",
+      "true": "是"
+    },
+    "notifications": "通知",
+    "notification_setting": "通知来源:",
+    "notification_setting_follows": "你所关注的用户",
+    "notification_setting_non_follows": "你没有关注的用户",
+    "notification_setting_followers": "关注你的用户",
+    "notification_setting_non_followers": "没有关注你的用户",
+    "notification_mutes": "要停止收到某个指定的用户的通知,请使用隐藏功能。",
+    "notification_blocks": "拉黑一个用户会停掉所有他的通知,等同于取消关注。",
+    "enable_web_push_notifications": "启用 web 推送通知",
+    "style": {
+      "switcher": {
+        "keep_color": "保留颜色",
+        "keep_shadows": "保留阴影",
+        "keep_opacity": "保留透明度",
+        "keep_roundness": "保留圆角",
+        "keep_fonts": "保留字体",
+        "save_load_hint": "\"保留\" 选项在选择或加载主题时保留当前设置的选项,在导出主题时还会存储上述选项。当所有复选框未设置时,导出主题将保存所有内容。",
+        "reset": "重置",
+        "clear_all": "清除全部",
+        "clear_opacity": "清除透明度",
+        "load_theme": "加载主题",
+        "help": {
+          "upgraded_from_v2": "PleromaFE 已升级,主题会和你记忆中的不太一样。"
+        },
+        "use_source": "新版本",
+        "use_snapshot": "老版本",
+        "keep_as_is": "保持原状"
+      },
+      "common": {
+        "color": "颜色",
+        "opacity": "透明度",
+        "contrast": {
+          "hint": "对比度是 {ratio}, 它 {level} {context}",
+          "level": {
+            "aa": "符合 AA 等级准则(最低)",
+            "aaa": "符合 AAA 等级准则(推荐)",
+            "bad": "不符合任何辅助功能指南"
+          },
+          "context": {
+            "18pt": "大字文本 (18pt+)",
+            "text": "文本"
+          }
+        }
+      },
+      "common_colors": {
+        "_tab_label": "常规",
+        "main": "常用颜色",
+        "foreground_hint": "点击”高级“ 标签进行细致的控制",
+        "rgbo": "图标,口音,徽章"
+      },
+      "advanced_colors": {
+        "_tab_label": "高级",
+        "alert": "提醒或警告背景色",
+        "alert_error": "错误",
+        "badge": "徽章背景",
+        "badge_notification": "通知",
+        "panel_header": "面板标题",
+        "top_bar": "顶栏",
+        "borders": "边框",
+        "buttons": "按钮",
+        "inputs": "输入框",
+        "faint_text": "灰度文字"
+      },
+      "radii": {
+        "_tab_label": "圆角"
+      },
+      "shadows": {
+        "_tab_label": "阴影和照明",
+        "component": "组件",
+        "override": "覆盖",
+        "shadow_id": "阴影 #{value}",
+        "blur": "模糊",
+        "spread": "扩散",
+        "inset": "插入内部",
+        "hint": "对于阴影你还可以使用 --variable 作为颜色值来使用 CSS3 变量。请注意,这种情况下,透明设置将不起作用。",
+        "filter_hint": {
+          "always_drop_shadow": "警告,此阴影设置会总是使用 {0} ,如果浏览器支持的话。",
+          "drop_shadow_syntax": "{0} 不支持参数 {1} 和关键词 {2} 。",
+          "avatar_inset": "请注意组合两个内部和非内部的阴影到头像上,在透明头像上可能会有意料之外的效果。",
+          "spread_zero": "阴影的扩散 > 0 会同设置成零一样",
+          "inset_classic": "插入内部的阴影会使用 {0}"
+        },
+        "components": {
+          "panel": "面板",
+          "panelHeader": "面板标题",
+          "topBar": "顶栏",
+          "avatar": "用户头像(在个人资料栏)",
+          "avatarStatus": "用户头像(在帖子显示栏)",
+          "popup": "弹窗和工具提示",
+          "button": "按钮",
+          "buttonHover": "按钮(悬停)",
+          "buttonPressed": "按钮(按下)",
+          "buttonPressedHover": "按钮(按下和悬停)",
+          "input": "输入框"
+        }
+      },
+      "fonts": {
+        "_tab_label": "字体",
+        "help": "给用户界面的元素选择字体。选择 “自选”的你必须输入确切的字体名称。",
+        "components": {
+          "interface": "界面",
+          "input": "输入框",
+          "post": "发帖文字",
+          "postCode": "帖子中使用等间距文字(富文本)"
+        },
+        "family": "字体名称",
+        "size": "大小 (in px)",
+        "weight": "字重 (粗体))",
+        "custom": "自选"
+      },
+      "preview": {
+        "header": "预览",
+        "content": "内容",
+        "error": "例子错误",
+        "button": "按钮",
+        "text": "有堆 {0} 和 {1}",
+        "mono": "内容",
+        "input": "刚刚抵达上海",
+        "faint_link": "帮助菜单",
+        "fine_print": "阅读我们的 {0} ,然而什么也学不到!",
+        "header_faint": "这很正常",
+        "checkbox": "我已经浏览了 TOC",
+        "link": "一个很棒的摇滚链接"
+      }
+    },
+    "version": {
+      "title": "版本",
+      "backend_version": "后端版本",
+      "frontend_version": "前端版本"
+    },
+    "notification_setting_filters": "过滤器",
+    "domain_mutes": "域名",
+    "changed_email": "邮箱修改成功!",
+    "change_email_error": "修改你的电子邮箱时发生错误",
+    "change_email": "修改电子邮箱",
+    "allow_following_move": "正在关注的账号迁移时自动重新关注",
+    "notification_setting_privacy_option": "在通知推送中隐藏发送者和内容",
+    "notification_setting_privacy": "隐私",
+    "hide_follows_count_description": "不显示关注数",
+    "notification_visibility_emoji_reactions": "互动",
+    "notification_visibility_moves": "用户迁移",
+    "new_email": "新邮箱",
+    "emoji_reactions_on_timeline": "在时间线上显示表情符号互动"
+  },
+  "time": {
+    "day": "{0} 天",
+    "days": "{0} 天",
+    "day_short": "{0}d",
+    "days_short": "{0}d",
+    "hour": "{0} 小时",
+    "hours": "{0} 小时",
+    "hour_short": "{0}h",
+    "hours_short": "{0}h",
+    "in_future": "还有 {0}",
+    "in_past": "{0} 之前",
+    "minute": "{0} 分钟",
+    "minutes": "{0} 分钟",
+    "minute_short": "{0}min",
+    "minutes_short": "{0}min",
+    "month": "{0} 月",
+    "months": "{0} 月",
+    "month_short": "{0}mo",
+    "months_short": "{0}mo",
+    "now": "刚刚",
+    "now_short": "刚刚",
+    "second": "{0} 秒",
+    "seconds": "{0} 秒",
+    "second_short": "{0}s",
+    "seconds_short": "{0}s",
+    "week": "{0} 周",
+    "weeks": "{0} 周",
+    "week_short": "{0}w",
+    "weeks_short": "{0}w",
+    "year": "{0} 年",
+    "years": "{0} 年",
+    "year_short": "{0}y",
+    "years_short": "{0}y"
+  },
+  "timeline": {
+    "collapse": "折叠",
+    "conversation": "对话",
+    "error_fetching": "获取更新时发生错误",
+    "load_older": "加载更早的状态",
+    "no_retweet_hint": "这条内容仅关注者可见,或者是私信,因此不能转发。",
+    "repeated": "已转发",
+    "show_new": "显示新内容",
+    "up_to_date": "已是最新",
+    "no_more_statuses": "没有更多的状态",
+    "no_statuses": "没有状态更新"
+  },
+  "status": {
+    "favorites": "收藏",
+    "repeats": "转发",
+    "delete": "删除状态",
+    "pin": "在个人资料置顶",
+    "unpin": "取消在个人资料置顶",
+    "pinned": "置顶",
+    "delete_confirm": "你真的想要删除这条状态吗?",
+    "reply_to": "回复",
+    "replies_list": "回复:",
+    "mute_conversation": "隐藏对话",
+    "unmute_conversation": "对话取消隐藏"
+  },
+  "user_card": {
+    "approve": "允许",
+    "block": "屏蔽",
+    "blocked": "已屏蔽!",
+    "deny": "拒绝",
+    "favorites": "收藏",
+    "follow": "关注",
+    "follow_sent": "请求已发送!",
+    "follow_progress": "请求中",
+    "follow_again": "再次发送请求?",
+    "follow_unfollow": "取消关注",
+    "followees": "正在关注",
+    "followers": "关注者",
+    "following": "正在关注!",
+    "follows_you": "关注了你!",
+    "its_you": "就是你!!",
+    "media": "媒体",
+    "mute": "隐藏",
+    "muted": "已隐藏",
+    "per_day": "每天",
+    "remote_follow": "跨站关注",
+    "report": "报告",
+    "statuses": "状态",
+    "subscribe": "订阅",
+    "unsubscribe": "退订",
+    "unblock": "取消拉黑",
+    "unblock_progress": "取消拉黑中...",
+    "block_progress": "拉黑中...",
+    "unmute": "取消隐藏",
+    "unmute_progress": "取消隐藏中...",
+    "mute_progress": "隐藏中...",
+    "admin_menu": {
+      "moderation": "权限",
+      "grant_admin": "赋予管理权限",
+      "revoke_admin": "撤销管理权限",
+      "grant_moderator": "赋予版主权限",
+      "revoke_moderator": "撤销版主权限",
+      "activate_account": "激活账号",
+      "deactivate_account": "关闭账号",
+      "delete_account": "删除账号",
+      "force_nsfw": "标记所有的帖子都是 - 工作场合不适",
+      "strip_media": "从帖子里删除媒体文件",
+      "force_unlisted": "强制帖子为不公开",
+      "sandbox": "强制帖子为只有关注者可看",
+      "disable_remote_subscription": "禁止从远程实例关注用户",
+      "disable_any_subscription": "完全禁止关注用户",
+      "quarantine": "从联合实例中禁止用户帖子",
+      "delete_user": "删除用户",
+      "delete_user_confirmation": "你确认吗?此操作无法撤销。"
+    },
+    "hidden": "已隐藏",
+    "show_repeats": "显示转发",
+    "hide_repeats": "隐藏转发"
+  },
+  "user_profile": {
+    "timeline_title": "用户时间线",
+    "profile_does_not_exist": "抱歉,此个人资料不存在。",
+    "profile_loading_error": "抱歉,载入个人资料时出错。"
+  },
+  "user_reporting": {
+    "title": "报告 {0}",
+    "add_comment_description": "此报告会发送给你的实例管理员。你可以在下面提供更多详细信息解释报告的缘由:",
+    "additional_comments": "其它信息",
+    "forward_description": "这个账号是从另外一个服务器。同时发送一个副本到那里?",
+    "forward_to": "转发 {0}",
+    "submit": "提交",
+    "generic_error": "当处理你的请求时,发生了一个错误。"
+  },
+  "who_to_follow": {
+    "more": "更多",
+    "who_to_follow": "推荐关注"
+  },
+  "tool_tip": {
+    "media_upload": "上传多媒体",
+    "repeat": "转发",
+    "reply": "回复",
+    "favorite": "收藏",
+    "user_settings": "用户设置",
+    "reject_follow_request": "拒绝关注请求",
+    "add_reaction": "添加互动"
+  },
+  "upload": {
+    "error": {
+      "base": "上传不成功。",
+      "file_too_big": "文件太大了 [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+      "default": "迟些再试"
+    },
+    "file_size_units": {
+      "B": "B",
+      "KiB": "KiB",
+      "MiB": "MiB",
+      "GiB": "GiB",
+      "TiB": "TiB"
+    }
+  },
+  "search": {
+    "people": "人",
+    "hashtags": "Hashtags",
+    "person_talking": "{count} 人正在讨论",
+    "people_talking": "{count} 人正在讨论",
+    "no_results": "没有搜索结果"
+  },
+  "password_reset": {
+    "forgot_password": "忘记密码了?",
+    "password_reset": "重置密码",
+    "instruction": "输入你的电邮地址或者用户名,我们将发送一个链接到你的邮箱,用于重置密码。",
+    "placeholder": "你的电邮地址或者用户名",
+    "check_email": "检查你的邮箱,会有一个链接用于重置密码。",
+    "return_home": "回到首页",
+    "not_found": "我们无法找到匹配的邮箱地址或者用户名。",
+    "too_many_requests": "你触发了尝试的限制,请稍后再试。",
+    "password_reset_disabled": "密码重置已经被禁用。请联系你的实例管理员。"
+  },
+  "remote_user_resolver": {
+    "error": "未找到。",
+    "searching_for": "搜索",
+    "remote_user_resolver": "远程用户解析器"
+  },
+  "emoji": {
+    "keep_open": "选择器保持打开",
+    "stickers": "贴图",
+    "unicode": "Unicode 表情符号",
+    "custom": "自定义表情符号",
+    "add_emoji": "插入表情符号",
+    "search_emoji": "搜索表情符号",
+    "emoji": "表情符号"
+  },
+  "about": {
+    "mrf": {
+      "simple": {
+        "quarantine_desc": "本实例只会把公开状态发送非下列实例:",
+        "quarantine": "隔离",
+        "reject_desc": "本实例不会接收来自下列实例的消息:",
+        "reject": "拒绝",
+        "accept_desc": "本实例只接收来自下列实例的消息:",
+        "simple_policies": "站规",
+        "accept": "接受",
+        "media_removal": "移除媒体"
+      },
+      "mrf_policies_desc": "MRF 策略会影响本实例的互通行为。以下策略已启用:",
+      "mrf_policies": "已启动 MRF 策略",
+      "keyword": {
+        "ftl_removal": "从“全部已知网络”时间线上移除",
+        "keyword_policies": "关键词策略",
+        "is_replaced_by": "→",
+        "replace": "替换",
+        "reject": "拒绝"
+      },
+      "federation": "联邦"
+    }
+  },
+  "domain_mute_card": {
+    "unmute_progress": "正在取消隐藏……",
+    "unmute": "取消隐藏",
+    "mute_progress": "隐藏中……",
+    "mute": "隐藏"
+  }
 }

From 10070394780ac79e9ee1e8548500586a1f78f65b Mon Sep 17 00:00:00 2001
From: Sergey Suprunenko <suprunenko.s@gmail.com>
Date: Sun, 10 May 2020 12:54:55 +0200
Subject: [PATCH 461/483] Autocomplete domain mutes from list of known
 instances

---
 CHANGELOG.md                                  |  1 +
 .../domain_mute_card/domain_mute_card.js      | 11 ++++++++
 .../domain_mute_card/domain_mute_card.vue     | 15 ++++++++++
 .../tabs/mutes_and_blocks_tab.js              | 28 +++++++++++++------
 .../tabs/mutes_and_blocks_tab.vue             | 21 ++++++--------
 src/i18n/en.json                              |  2 +-
 src/modules/instance.js                       | 17 +++++++++++
 src/services/api/api.service.js               |  6 ++++
 8 files changed, 79 insertions(+), 22 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e467bc9..11f539da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Add
 - Added private notifications option for push notifications
 - 'Copy link' button for statuses (in the ellipsis menu)
+- Autocomplete domains from list of known instances
 
 ### Changed
 - Registration page no longer requires email if the server is configured not to require it
diff --git a/src/components/domain_mute_card/domain_mute_card.js b/src/components/domain_mute_card/domain_mute_card.js
index c8e838ba..f234dcb0 100644
--- a/src/components/domain_mute_card/domain_mute_card.js
+++ b/src/components/domain_mute_card/domain_mute_card.js
@@ -5,9 +5,20 @@ const DomainMuteCard = {
   components: {
     ProgressButton
   },
+  computed: {
+    user () {
+      return this.$store.state.users.currentUser
+    },
+    muted () {
+      return this.user.domainMutes.includes(this.domain)
+    }
+  },
   methods: {
     unmuteDomain () {
       return this.$store.dispatch('unmuteDomain', this.domain)
+    },
+    muteDomain () {
+      return this.$store.dispatch('muteDomain', this.domain)
     }
   }
 }
diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue
index 567d81c5..97aee243 100644
--- a/src/components/domain_mute_card/domain_mute_card.vue
+++ b/src/components/domain_mute_card/domain_mute_card.vue
@@ -4,6 +4,7 @@
       {{ domain }}
     </div>
     <ProgressButton
+      v-if="muted"
       :click="unmuteDomain"
       class="btn btn-default"
     >
@@ -12,6 +13,16 @@
         {{ $t('domain_mute_card.unmute_progress') }}
       </template>
     </ProgressButton>
+    <ProgressButton
+      v-else
+      :click="muteDomain"
+      class="btn btn-default"
+    >
+      {{ $t('domain_mute_card.mute') }}
+      <template slot="progress">
+        {{ $t('domain_mute_card.mute_progress') }}
+      </template>
+    </ProgressButton>
   </div>
 </template>
 
@@ -34,5 +45,9 @@
   button {
     width: 10em;
   }
+
+  .autosuggest-results & {
+    padding-left: 1em;
+  }
 }
 </style>
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
index b0043dbb..40a87b81 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
@@ -32,12 +32,12 @@ const DomainMuteList = withSubscription({
 const MutesAndBlocks = {
   data () {
     return {
-      activeTab: 'profile',
-      newDomainToMute: ''
+      activeTab: 'profile'
     }
   },
   created () {
     this.$store.dispatch('fetchTokens')
+    this.$store.dispatch('getKnownDomains')
   },
   components: {
     TabSwitcher,
@@ -51,6 +51,14 @@ const MutesAndBlocks = {
     Autosuggest,
     Checkbox
   },
+  computed: {
+    knownDomains () {
+      return this.$store.state.instance.knownDomains
+    },
+    user () {
+      return this.$store.state.users.currentUser
+    }
+  },
   methods: {
     importFollows (file) {
       return this.$store.state.api.backendInteractor.importFollows({ file })
@@ -86,13 +94,13 @@ const MutesAndBlocks = {
     filterUnblockedUsers (userIds) {
       return reject(userIds, (userId) => {
         const relationship = this.$store.getters.relationship(this.userId)
-        return relationship.blocking || userId === this.$store.state.users.currentUser.id
+        return relationship.blocking || userId === this.user.id
       })
     },
     filterUnMutedUsers (userIds) {
       return reject(userIds, (userId) => {
         const relationship = this.$store.getters.relationship(this.userId)
-        return relationship.muting || userId === this.$store.state.users.currentUser.id
+        return relationship.muting || userId === this.user.id
       })
     },
     queryUserIds (query) {
@@ -111,12 +119,16 @@ const MutesAndBlocks = {
     unmuteUsers (ids) {
       return this.$store.dispatch('unmuteUsers', ids)
     },
+    filterUnMutedDomains (urls) {
+      return urls.filter(url => !this.user.domainMutes.includes(url))
+    },
+    queryKnownDomains (query) {
+      return new Promise((resolve, reject) => {
+        resolve(this.knownDomains.filter(url => url.toLowerCase().includes(query)))
+      })
+    },
     unmuteDomains (domains) {
       return this.$store.dispatch('unmuteDomains', domains)
-    },
-    muteDomain () {
-      return this.$store.dispatch('muteDomain', this.newDomainToMute)
-        .then(() => { this.newDomainToMute = '' })
     }
   }
 }
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
index 6884b7be..5a1cf2c0 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
@@ -119,21 +119,16 @@
 
         <div :label="$t('settings.domain_mutes')">
           <div class="domain-mute-form">
-            <input
-              v-model="newDomainToMute"
+            <Autosuggest
+              :filter="filterUnMutedDomains"
+              :query="queryKnownDomains"
               :placeholder="$t('settings.type_domains_to_mute')"
-              type="text"
-              @keyup.enter="muteDomain"
             >
-            <ProgressButton
-              class="btn btn-default domain-mute-button"
-              :click="muteDomain"
-            >
-              {{ $t('domain_mute_card.mute') }}
-              <template slot="progress">
-                {{ $t('domain_mute_card.mute_progress') }}
-              </template>
-            </ProgressButton>
+              <DomainMuteCard
+                slot-scope="row"
+                :domain="row.item"
+              />
+            </Autosuggest>
           </div>
           <DomainMuteList
             :refresh="true"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index c322e538..eefe10e5 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -401,7 +401,7 @@
     "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
     "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
     "tooltipRadius": "Tooltips/alerts",
-    "type_domains_to_mute": "Type in domains to mute",
+    "type_domains_to_mute": "Search domains to mute",
     "upload_a_photo": "Upload a photo",
     "user_settings": "User Settings",
     "values": {
diff --git a/src/modules/instance.js b/src/modules/instance.js
index da82eb01..ec5f4e54 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -1,6 +1,7 @@
 import { set } from 'vue'
 import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
 import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
+import apiService from '../services/api/api.service.js'
 import { instanceDefaultProperties } from './config.js'
 
 const defaultState = {
@@ -48,6 +49,7 @@ const defaultState = {
   postFormats: [],
   restrictedNicknames: [],
   safeDM: true,
+  knownDomains: [],
 
   // Feature-set, apparently, not everything here is reported...
   chatAvailable: false,
@@ -80,6 +82,9 @@ const instance = {
       if (typeof value !== 'undefined') {
         set(state, name, value)
       }
+    },
+    setKnownDomains (state, domains) {
+      state.knownDomains = domains
     }
   },
   getters: {
@@ -182,6 +187,18 @@ const instance = {
         state.emojiFetched = true
         dispatch('getStaticEmoji')
       }
+    },
+
+    async getKnownDomains ({ commit, rootState }) {
+      try {
+        const result = await apiService.fetchKnownDomains({
+          credentials: rootState.users.currentUser.credentials
+        })
+        commit('setKnownDomains', result)
+      } catch (e) {
+        console.warn("Can't load known domains")
+        console.warn(e)
+      }
     }
   }
 }
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 9c7530a2..b3082bc5 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -75,6 +75,7 @@ const MASTODON_SEARCH_2 = `/api/v2/search`
 const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
 const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
 const MASTODON_STREAMING = '/api/v1/streaming'
+const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
 const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions`
 const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
 const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
@@ -995,6 +996,10 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
     })
 }
 
+const fetchKnownDomains = ({ credentials }) => {
+  return promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials })
+}
+
 const fetchDomainMutes = ({ credentials }) => {
   return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
 }
@@ -1193,6 +1198,7 @@ const apiService = {
   updateNotificationSettings,
   search2,
   searchUsers,
+  fetchKnownDomains,
   fetchDomainMutes,
   muteDomain,
   unmuteDomain

From e6a27bcaca57c4c6351f7c41c2892c4e5222829e Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Mon, 8 Jun 2020 13:30:16 +0200
Subject: [PATCH 462/483] MediaUpload: Allow drag-and-drop of multiple files at
 once

---
 src/components/media_upload/media_upload.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
index f457d022..4568224b 100644
--- a/src/components/media_upload/media_upload.js
+++ b/src/components/media_upload/media_upload.js
@@ -37,7 +37,9 @@ const mediaUpload = {
     fileDrop (e) {
       if (e.dataTransfer.files.length > 0) {
         e.preventDefault() // allow dropping text like before
-        this.uploadFile(e.dataTransfer.files[0])
+        for (const file of e.dataTransfer.files) {
+          this.uploadFile(file)
+        }
       }
     },
     fileDrag (e) {

From 99eaec85478f384ddb0ea45a9d9c95c4dce646f5 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Mon, 8 Jun 2020 17:22:07 +0200
Subject: [PATCH 463/483] Messages: Load languages asynchronously.

Reduces the size of the initial app bundle by about half.
---
 src/App.js                                    |  3 +-
 .../interface_language_switcher.vue           |  3 +-
 src/i18n/messages.js                          | 95 +++++++++++++------
 src/main.js                                   |  6 +-
 src/modules/config.js                         |  5 +
 5 files changed, 80 insertions(+), 32 deletions(-)

diff --git a/src/App.js b/src/App.js
index 6445335a..040138c9 100644
--- a/src/App.js
+++ b/src/App.js
@@ -47,7 +47,8 @@ export default {
   }),
   created () {
     // Load the locale from the storage
-    this.$i18n.locale = this.$store.getters.mergedConfig.interfaceLanguage
+    const val = this.$store.getters.mergedConfig.interfaceLanguage
+    this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
     window.addEventListener('resize', this.updateMobileState)
   },
   destroyed () {
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index f5ace0cc..dd6800a3 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -32,7 +32,7 @@ import _ from 'lodash'
 export default {
   computed: {
     languageCodes () {
-      return Object.keys(languagesObject)
+      return languagesObject.languages
     },
 
     languageNames () {
@@ -43,7 +43,6 @@ export default {
       get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
       set: function (val) {
         this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
-        this.$i18n.locale = val
       }
     }
   },
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index c56ae205..a257486f 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -7,34 +7,75 @@
 // sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json
 // There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry.
 
+const loaders = {
+  ar: () => import('./ar.json'),
+  ca: () => import('./ca.json'),
+  cs: () => import('./cs.json'),
+  de: () => import('./de.json'),
+  eo: () => import('./eo.json'),
+  es: () => import('./es.json'),
+  et: () => import('./et.json'),
+  eu: () => import('./eu.json'),
+  fi: () => import('./fi.json'),
+  fr: () => import('./fr.json'),
+  ga: () => import('./ga.json'),
+  he: () => import('./he.json'),
+  hu: () => import('./hu.json'),
+  it: () => import('./it.json'),
+  ja: () => import('./ja_pedantic.json'),
+  ja_easy: () => import('./ja_easy.json'),
+  ko: () => import('./ko.json'),
+  nb: () => import('./nb.json'),
+  nl: () => import('./nl.json'),
+  oc: () => import('./oc.json'),
+  pl: () => import('./pl.json'),
+  pt: () => import('./pt.json'),
+  ro: () => import('./ro.json'),
+  ru: () => import('./ru.json'),
+  te: () => import('./te.json'),
+  zh: () => import('./zh.json')
+}
+
 const messages = {
-  ar: require('./ar.json'),
-  ca: require('./ca.json'),
-  cs: require('./cs.json'),
-  de: require('./de.json'),
-  en: require('./en.json'),
-  eo: require('./eo.json'),
-  es: require('./es.json'),
-  et: require('./et.json'),
-  eu: require('./eu.json'),
-  fi: require('./fi.json'),
-  fr: require('./fr.json'),
-  ga: require('./ga.json'),
-  he: require('./he.json'),
-  hu: require('./hu.json'),
-  it: require('./it.json'),
-  ja: require('./ja_pedantic.json'),
-  ja_easy: require('./ja_easy.json'),
-  ko: require('./ko.json'),
-  nb: require('./nb.json'),
-  nl: require('./nl.json'),
-  oc: require('./oc.json'),
-  pl: require('./pl.json'),
-  pt: require('./pt.json'),
-  ro: require('./ro.json'),
-  ru: require('./ru.json'),
-  te: require('./te.json'),
-  zh: require('./zh.json')
+  languages: [
+    'ar',
+    'ca',
+    'cs',
+    'de',
+    'en',
+    'eo',
+    'es',
+    'et',
+    'eu',
+    'fi',
+    'fr',
+    'ga',
+    'he',
+    'hu',
+    'it',
+    'ja',
+    'ja_easy',
+    'ko',
+    'nb',
+    'nl',
+    'oc',
+    'pl',
+    'pt',
+    'ro',
+    'ru',
+    'te',
+    'zh'
+  ],
+  default: {
+    en: require('./en.json')
+  },
+  setLanguage: async (i18n, language) => {
+    if (loaders[language]) {
+      let messages = await loaders[language]()
+      i18n.setLocaleMessage(language, messages)
+    }
+    i18n.locale = language
+  }
 }
 
 export default messages
diff --git a/src/main.js b/src/main.js
index be4213fa..9a201e4f 100644
--- a/src/main.js
+++ b/src/main.js
@@ -46,11 +46,13 @@ Vue.use(VBodyScrollLock)
 
 const i18n = new VueI18n({
   // By default, use the browser locale, we will update it if neccessary
-  locale: currentLocale,
+  locale: 'en',
   fallbackLocale: 'en',
-  messages
+  messages: messages.default
 })
 
+messages.setLanguage(i18n, currentLocale)
+
 const persistedStateOptions = {
   paths: [
     'config',
diff --git a/src/modules/config.js b/src/modules/config.js
index b6b1b241..47b24d77 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -1,5 +1,6 @@
 import { set, delete as del } from 'vue'
 import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
+import messages from '../i18n/messages'
 
 const browserLocale = (window.navigator.language || 'en').split('-')[0]
 
@@ -115,6 +116,10 @@ const config = {
         case 'customTheme':
         case 'customThemeSource':
           applyTheme(value)
+          break
+        case 'interfaceLanguage':
+          messages.setLanguage(this.getters.i18n, value)
+          break
       }
     }
   }

From e45f7fe8772f538c6824718ec26067849bde34c6 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Mon, 8 Jun 2020 18:22:17 +0200
Subject: [PATCH 464/483] MediaUpload: Correctly handle multiple uploads.

---
 src/components/media_upload/media_upload.js   | 33 ++++++++++++-------
 .../post_status_form/post_status_form.js      |  2 --
 .../post_status_form/post_status_form.vue     |  1 +
 3 files changed, 23 insertions(+), 13 deletions(-)

diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
index 4568224b..16e47ef7 100644
--- a/src/components/media_upload/media_upload.js
+++ b/src/components/media_upload/media_upload.js
@@ -5,10 +5,15 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
 const mediaUpload = {
   data () {
     return {
-      uploading: false,
+      uploadCount: 0,
       uploadReady: true
     }
   },
+  computed: {
+    uploading () {
+      return this.uploadCount > 0
+    }
+  },
   methods: {
     uploadFile (file) {
       const self = this
@@ -23,23 +28,27 @@ const mediaUpload = {
       formData.append('file', file)
 
       self.$emit('uploading')
-      self.uploading = true
+      self.uploadCount++
 
       statusPosterService.uploadMedia({ store, formData })
         .then((fileData) => {
           self.$emit('uploaded', fileData)
-          self.uploading = false
+          self.decreaseUploadCount()
         }, (error) => { // eslint-disable-line handle-callback-err
           self.$emit('upload-failed', 'default')
-          self.uploading = false
+          self.decreaseUploadCount()
         })
     },
+    decreaseUploadCount() {
+      this.uploadCount--
+      if (this.uploadCount === 0) {
+        this.$emit('all-uploaded')
+      }
+    },
     fileDrop (e) {
       if (e.dataTransfer.files.length > 0) {
         e.preventDefault() // allow dropping text like before
-        for (const file of e.dataTransfer.files) {
-          this.uploadFile(file)
-        }
+        this.multiUpload(e.dataTransfer.files)
       }
     },
     fileDrag (e) {
@@ -56,11 +65,13 @@ const mediaUpload = {
         this.uploadReady = true
       })
     },
-    change ({ target }) {
-      for (var i = 0; i < target.files.length; i++) {
-        let file = target.files[i]
+    multiUpload (files) {
+      for (const file of files) {
         this.uploadFile(file)
       }
+    },
+    change ({ target }) {
+      this.multiUpload(target.files)
     }
   },
   props: [
@@ -69,7 +80,7 @@ const mediaUpload = {
   watch: {
     'dropFiles': function (fileInfos) {
       if (!this.uploading) {
-        this.uploadFile(fileInfos[0])
+        this.multiUpload(fileInfos)
       }
     }
   }
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index a98e1e31..6164caa0 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -218,7 +218,6 @@ const PostStatusForm = {
     },
     addMediaFile (fileInfo) {
       this.newStatus.files.push(fileInfo)
-      this.enableSubmit()
     },
     removeMediaFile (fileInfo) {
       let index = this.newStatus.files.indexOf(fileInfo)
@@ -227,7 +226,6 @@ const PostStatusForm = {
     uploadFailed (errString, templateArgs) {
       templateArgs = templateArgs || {}
       this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs)
-      this.enableSubmit()
     },
     disableSubmit () {
       this.submitDisabled = true
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 9789a481..5629ceac 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -172,6 +172,7 @@
             @uploading="disableSubmit"
             @uploaded="addMediaFile"
             @upload-failed="uploadFailed"
+            @all-uploaded="enableSubmit"
           />
           <div
             class="emoji-icon"

From fbc4889ece185bbe6386493562b89c83d6a89916 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Mon, 8 Jun 2020 18:26:16 +0200
Subject: [PATCH 465/483] Linting.

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

diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
index 16e47ef7..5849b065 100644
--- a/src/components/media_upload/media_upload.js
+++ b/src/components/media_upload/media_upload.js
@@ -39,7 +39,7 @@ const mediaUpload = {
           self.decreaseUploadCount()
         })
     },
-    decreaseUploadCount() {
+    decreaseUploadCount () {
       this.uploadCount--
       if (this.uploadCount === 0) {
         this.$emit('all-uploaded')

From d5ec269d884ab13b641acb816a951ab3db3ec428 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Mon, 8 Jun 2020 19:14:13 +0200
Subject: [PATCH 466/483] Update changelog.

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e467bc9..85e03095 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Cropped images look correct in Chrome
 - Newlines in the muted words settings work again
 - Clicking on non-latin hashtags won't open a new window
+- Uploading and drag-dropping files works correctly now.
 
 ## [2.0.3] - 2020-05-02
 ### Fixed

From d8211392c425564598328a760a659f46a47147fd Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Mon, 8 Jun 2020 17:24:08 +0000
Subject: [PATCH 467/483] Apply suggestion to CHANGELOG.md

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85e03095..1467f133 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,7 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Cropped images look correct in Chrome
 - Newlines in the muted words settings work again
 - Clicking on non-latin hashtags won't open a new window
-- Uploading and drag-dropping files works correctly now.
+- Uploading and drag-dropping multiple files works correctly now.
 
 ## [2.0.3] - 2020-05-02
 ### Fixed

From 44db3af0db55454c537a26bf9ea3fc8e121c7e43 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Tue, 9 Jun 2020 09:26:46 +0200
Subject: [PATCH 468/483] Messages: DRY things up a bit.

---
 src/i18n/messages.js | 30 +-----------------------------
 1 file changed, 1 insertion(+), 29 deletions(-)

diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index a257486f..c3195f10 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -37,35 +37,7 @@ const loaders = {
 }
 
 const messages = {
-  languages: [
-    'ar',
-    'ca',
-    'cs',
-    'de',
-    'en',
-    'eo',
-    'es',
-    'et',
-    'eu',
-    'fi',
-    'fr',
-    'ga',
-    'he',
-    'hu',
-    'it',
-    'ja',
-    'ja_easy',
-    'ko',
-    'nb',
-    'nl',
-    'oc',
-    'pl',
-    'pt',
-    'ro',
-    'ru',
-    'te',
-    'zh'
-  ],
+  languages: ['en', ...Object.keys(loaders)],
   default: {
     en: require('./en.json')
   },

From cd9d732afa6d33f5db70161992a20e56ffd1d8cc Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 10 Jun 2020 11:01:38 +0300
Subject: [PATCH 469/483] add better visual indication for dropping files, make
 dropzone bigger

---
 src/components/media_upload/media_upload.js   | 14 --------
 src/components/media_upload/media_upload.vue  |  7 +---
 .../post_status_form/post_status_form.js      | 19 +++++++++--
 .../post_status_form/post_status_form.vue     | 32 +++++++++++++++++--
 4 files changed, 47 insertions(+), 25 deletions(-)

diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
index 5849b065..fbb2d03d 100644
--- a/src/components/media_upload/media_upload.js
+++ b/src/components/media_upload/media_upload.js
@@ -45,20 +45,6 @@ const mediaUpload = {
         this.$emit('all-uploaded')
       }
     },
-    fileDrop (e) {
-      if (e.dataTransfer.files.length > 0) {
-        e.preventDefault() // allow dropping text like before
-        this.multiUpload(e.dataTransfer.files)
-      }
-    },
-    fileDrag (e) {
-      let types = e.dataTransfer.types
-      if (types.contains('Files')) {
-        e.dataTransfer.dropEffect = 'copy'
-      } else {
-        e.dataTransfer.dropEffect = 'none'
-      }
-    },
     clearFile () {
       this.uploadReady = false
       this.$nextTick(() => {
diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue
index 0fc305ac..5e31730b 100644
--- a/src/components/media_upload/media_upload.vue
+++ b/src/components/media_upload/media_upload.vue
@@ -1,10 +1,5 @@
 <template>
-  <div
-    class="media-upload"
-    @drop.prevent
-    @dragover.prevent="fileDrag"
-    @drop="fileDrop"
-  >
+  <div class="media-upload">
     <label
       class="label"
       :title="$t('tool_tip.media_upload')"
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 6164caa0..3de6f70a 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -82,7 +82,9 @@ const PostStatusForm = {
         contentType
       },
       caret: 0,
-      pollFormVisible: false
+      pollFormVisible: false,
+      showDropIcon: false,
+      dropStopTimeout: null
     }
   },
   computed: {
@@ -248,13 +250,26 @@ const PostStatusForm = {
       }
     },
     fileDrop (e) {
-      if (e.dataTransfer.files.length > 0) {
+      if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
         e.preventDefault() // allow dropping text like before
         this.dropFiles = e.dataTransfer.files
+        clearTimeout(this.dropStopTimeout)
+        this.showDropIcon = false
       }
     },
+    fileDragStop (e) {
+      // The false-setting is done with delay because just using leave-events
+      // directly caused unwanted flickering, this is not perfect either but
+      // much less noticable.
+      clearTimeout(this.dropStopTimeout)
+      this.dropStopTimeout = setTimeout(() => (this.showDropIcon = false), 100)
+    },
     fileDrag (e) {
       e.dataTransfer.dropEffect = 'copy'
+      if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
+        clearTimeout(this.dropStopTimeout)
+        this.showDropIcon = true
+      }
     },
     onEmojiInputInput (e) {
       this.$nextTick(() => {
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 5629ceac..8e71d7b4 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -6,7 +6,14 @@
     <form
       autocomplete="off"
       @submit.prevent="postStatus(newStatus)"
+      @dragover.prevent.capture="fileDrag"
     >
+      <div
+        v-show="showDropIcon"
+        class="drop-indicator icon-upload"
+        @dragleave.capture="fileDragStop"
+        @drop.stop="fileDrop"
+      />
       <div class="form-group">
         <i18n
           v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'"
@@ -97,8 +104,6 @@
             class="form-post-body"
             @keydown.meta.enter="postStatus(newStatus)"
             @keyup.ctrl.enter="postStatus(newStatus)"
-            @drop="fileDrop"
-            @dragover.prevent="fileDrag"
             @input="resize"
             @compositionupdate="resize"
             @paste="paste"
@@ -447,7 +452,8 @@
   form {
     display: flex;
     flex-direction: column;
-    padding: 0.6em;
+    margin: 0.6em;
+    position: relative;
   }
 
   .form-group {
@@ -505,5 +511,25 @@
     cursor: pointer;
     z-index: 4;
   }
+
+  .drop-indicator {
+    position: absolute;
+    z-index: 1;
+    width: 100%;
+    height: 100%;
+    font-size: 5em;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: $fallback--text;
+    color: var(--text, $fallback--text);
+    opacity: 0.6;
+    background-color: $fallback--bg;
+    background-color: var(--bg, $fallback--bg);
+    border-radius: $fallback--tooltipRadius;
+    border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+    border: 2px dashed $fallback--text;
+    border: 2px dashed var(--text, $fallback--text);
+  }
 }
 </style>

From 3cfdfec72dfd0fd390152c2bf2e80ee11f8ae525 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 10 Jun 2020 11:18:07 +0300
Subject: [PATCH 470/483] attempt to fix that one bug with submitting on
 copy-pasting

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

diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 5629ceac..6477594c 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -96,7 +96,7 @@
             :disabled="posting"
             class="form-post-body"
             @keydown.meta.enter="postStatus(newStatus)"
-            @keyup.ctrl.enter="postStatus(newStatus)"
+            @keydown.ctrl.enter="postStatus(newStatus)"
             @drop="fileDrop"
             @dragover.prevent="fileDrag"
             @input="resize"

From cad42eec2a4cf80026f8ddc072a105d99cb15d15 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 10 Jun 2020 11:20:40 +0300
Subject: [PATCH 471/483] changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1467f133..32d72bb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Removed the use of with_move parameters when fetching notifications
 
 ### Fixed
+- Weird bug related to post being sent seemingly after pasting with keyboard (hopefully)
 - Multiple issues with muted statuses/notifications
 
 ## [Unreleased patch]

From 1308fdd3bc4cc29264fcac6e3367e7f0cf93d498 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 10 Jun 2020 11:29:28 +0300
Subject: [PATCH 472/483] update changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1467f133..dc1be973 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Change heart to thumbs up in reaction picker
 - Close the media modal on navigation events
 - Add colons to the emoji alt text, to make them copyable
+- Add better visual indication for drag-and-drop for files
 
 ### Fixed
 - Status ellipsis menu closes properly when selecting certain options

From d3187720c5cb2599b2dc2d83898e3f6d544cb6ae Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 10 Jun 2020 11:49:04 +0300
Subject: [PATCH 473/483] remove useless captures

---
 src/components/post_status_form/post_status_form.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 8e71d7b4..873328f5 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -6,12 +6,12 @@
     <form
       autocomplete="off"
       @submit.prevent="postStatus(newStatus)"
-      @dragover.prevent.capture="fileDrag"
+      @dragover.prevent="fileDrag"
     >
       <div
         v-show="showDropIcon"
         class="drop-indicator icon-upload"
-        @dragleave.capture="fileDragStop"
+        @dragleave="fileDragStop"
         @drop.stop="fileDrop"
       />
       <div class="form-group">

From ea2b2a35bb5c419970b796ec010085302d8c9bd1 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 10 Jun 2020 12:41:02 +0300
Subject: [PATCH 474/483] add fade-in fade-out

---
 .../post_status_form/post_status_form.js          |  9 +++++----
 .../post_status_form/post_status_form.vue         | 15 +++++++++++++--
 2 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 3de6f70a..9027566f 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -83,7 +83,7 @@ const PostStatusForm = {
       },
       caret: 0,
       pollFormVisible: false,
-      showDropIcon: false,
+      showDropIcon: 'hide',
       dropStopTimeout: null
     }
   },
@@ -254,7 +254,7 @@ const PostStatusForm = {
         e.preventDefault() // allow dropping text like before
         this.dropFiles = e.dataTransfer.files
         clearTimeout(this.dropStopTimeout)
-        this.showDropIcon = false
+        this.showDropIcon = 'hide'
       }
     },
     fileDragStop (e) {
@@ -262,13 +262,14 @@ const PostStatusForm = {
       // directly caused unwanted flickering, this is not perfect either but
       // much less noticable.
       clearTimeout(this.dropStopTimeout)
-      this.dropStopTimeout = setTimeout(() => (this.showDropIcon = false), 100)
+      this.showDropIcon = 'fade'
+      this.dropStopTimeout = setTimeout(() => (this.showDropIcon = 'hide'), 500)
     },
     fileDrag (e) {
       e.dataTransfer.dropEffect = 'copy'
       if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
         clearTimeout(this.dropStopTimeout)
-        this.showDropIcon = true
+        this.showDropIcon = 'show'
       }
     },
     onEmojiInputInput (e) {
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index e23e9e48..c4d7f7e2 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -9,7 +9,8 @@
       @dragover.prevent="fileDrag"
     >
       <div
-        v-show="showDropIcon"
+        v-show="showDropIcon !== 'hide'"
+        :style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
         class="drop-indicator icon-upload"
         @dragleave="fileDragStop"
         @drop.stop="fileDrop"
@@ -512,6 +513,16 @@
     z-index: 4;
   }
 
+  @keyframes fade-in {
+    from { opacity: 0; }
+    to   { opacity: 0.6; }
+  }
+
+  @keyframes fade-out {
+    from { opacity: 0.6; }
+    to   { opacity: 0; }
+  }
+
   .drop-indicator {
     position: absolute;
     z-index: 1;
@@ -521,9 +532,9 @@
     display: flex;
     align-items: center;
     justify-content: center;
+    opacity: 0.6;
     color: $fallback--text;
     color: var(--text, $fallback--text);
-    opacity: 0.6;
     background-color: $fallback--bg;
     background-color: var(--bg, $fallback--bg);
     border-radius: $fallback--tooltipRadius;

From ff2cd3d672f7bec9e5985ecb86a491004cc53f74 Mon Sep 17 00:00:00 2001
From: hj <spam@hjkos.com>
Date: Sun, 7 Jun 2020 15:23:21 +0000
Subject: [PATCH 475/483] Translated using Weblate (Russian)

Currently translated at 60.0% (373 of 621 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
---
 src/i18n/ru.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 69b22618..f9a72954 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -456,9 +456,9 @@
   },
   "domain_mute_card": {
     "mute": "Игнорировать",
-    "mute_progress": "В процессе...",
+    "mute_progress": "В процессе…",
     "unmute": "Прекратить игнорирование",
-    "unmute_progress": "В процессе..."
+    "unmute_progress": "В процессе…"
   },
   "exporter": {
     "export": "Экспорт",

From 7b9fa325859683c31d5c5cfacab59b27921bb740 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Tue, 9 Jun 2020 16:32:41 +0000
Subject: [PATCH 476/483] Translated using Weblate (Italian)

Currently translated at 68.5% (426 of 621 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 39 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 36 insertions(+), 3 deletions(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index 360c72aa..101e1dd3 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -12,7 +12,12 @@
     "disable": "Disabilita",
     "enable": "Abilita",
     "confirm": "Conferma",
-    "verify": "Verifica"
+    "verify": "Verifica",
+    "peek": "Anteprima",
+    "close": "Chiudi",
+    "retry": "Riprova",
+    "error_retry": "Per favore, riprova",
+    "loading": "Carico…"
   },
   "nav": {
     "mentions": "Menzioni",
@@ -212,7 +217,34 @@
       },
       "common": {
         "opacity": "Opacità",
-        "color": "Colore"
+        "color": "Colore",
+        "contrast": {
+          "context": {
+            "text": "per il testo",
+            "18pt": "per il testo grande (oltre 17pt)"
+          },
+          "level": {
+            "bad": "non soddisfa le linee guida di alcun livello",
+            "aaa": "soddisfa le linee guida di livello AAA (ottimo)",
+            "aa": "soddisfa le linee guida di livello AA (sufficiente)"
+          },
+          "hint": "Il rapporto di contrasto è {ratio}, e {level} {context}"
+        }
+      },
+      "advanced_colors": {
+        "badge": "Sfondo medaglie",
+        "post": "Messaggi / Biografie",
+        "alert_neutral": "Neutro",
+        "alert_warning": "Attenzione",
+        "alert_error": "Errore",
+        "alert": "Sfondo degli avvertimenti",
+        "_tab_label": "Avanzate"
+      },
+      "common_colors": {
+        "rgbo": "Icone, accenti, medaglie",
+        "foreground_hint": "Seleziona l'etichetta \"Avanzate\" per controlli più fini",
+        "main": "Colori comuni",
+        "_tab_label": "Comuni"
       }
     },
     "enable_web_push_notifications": "Abilita notifiche web push",
@@ -273,7 +305,8 @@
     "accent": "Accento",
     "emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze",
     "pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore",
-    "notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più."
+    "notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più.",
+    "mutes_and_blocks": "Zittiti e bloccati"
   },
   "timeline": {
     "error_fetching": "Errore nell'aggiornamento",

From 2b6a6fe7ea31e4e5b77d5ad0ff580f9e29df1849 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 11 Jun 2020 13:44:32 +0300
Subject: [PATCH 477/483] make object-fit cover work like it should

---
 src/components/still-image/still-image.vue | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
index 08af26f6..f2ddeb7b 100644
--- a/src/components/still-image/still-image.vue
+++ b/src/components/still-image/still-image.vue
@@ -23,13 +23,6 @@
 
 <style lang="scss">
 @import '../../_variables.scss';
-.contain-fit {
-  .still-image {
-    img {
-      height: 100%;
-    }
-  }
-}
 
 .still-image {
   position: relative;
@@ -38,6 +31,7 @@
   width: 100%;
   height: 100%;
   display: flex;
+  align-items: center;
 
   &:hover canvas {
     display: none;
@@ -45,8 +39,8 @@
 
   img {
     width: 100%;
+    min-height: 100%;
     object-fit: contain;
-    align-self: center;
   }
 
   &.animated {

From 14348d8ddf15b3a5366de711daf3acf498fb570a Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Thu, 11 Jun 2020 14:27:36 +0300
Subject: [PATCH 478/483] fix the contain option

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

diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
index 7abc2161..1ffa7b3c 100644
--- a/src/components/gallery/gallery.vue
+++ b/src/components/gallery/gallery.vue
@@ -78,6 +78,7 @@
     video,
     canvas {
       object-fit: contain;
+      height: 100%;
     }
   }
 

From c59c0ec4be4a1d253c89e912ddce19029a5b5ba2 Mon Sep 17 00:00:00 2001
From: Ben Is <srsbzns@cock.li>
Date: Wed, 10 Jun 2020 13:46:42 +0000
Subject: [PATCH 479/483] Translated using Weblate (Italian)

Currently translated at 72.6% (451 of 621 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
---
 src/i18n/it.json | 33 +++++++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/src/i18n/it.json b/src/i18n/it.json
index 101e1dd3..6c8be351 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -238,13 +238,42 @@
         "alert_warning": "Attenzione",
         "alert_error": "Errore",
         "alert": "Sfondo degli avvertimenti",
-        "_tab_label": "Avanzate"
+        "_tab_label": "Avanzate",
+        "tabs": "Etichette",
+        "disabled": "Disabilitato",
+        "selectedMenu": "Voce menù selezionata",
+        "selectedPost": "Messaggio selezionato",
+        "pressed": "Premuto",
+        "highlight": "Elementi evidenziati",
+        "icons": "Icone",
+        "poll": "Grafico sondaggi",
+        "underlay": "Sottostante",
+        "faint_text": "Testo sbiadito",
+        "inputs": "Campi d'immissione",
+        "buttons": "Pulsanti",
+        "borders": "Bordi",
+        "top_bar": "Barra superiore",
+        "panel_header": "Titolo pannello",
+        "badge_notification": "Notifica",
+        "popover": "Suggerimenti, menù, sbalzi"
       },
       "common_colors": {
         "rgbo": "Icone, accenti, medaglie",
         "foreground_hint": "Seleziona l'etichetta \"Avanzate\" per controlli più fini",
         "main": "Colori comuni",
         "_tab_label": "Comuni"
+      },
+      "shadows": {
+        "inset": "Includi",
+        "spread": "Spandi",
+        "blur": "Sfoca",
+        "shadow_id": "Ombra numero {value}",
+        "override": "Sostituisci",
+        "component": "Componente",
+        "_tab_label": "Luci ed ombre"
+      },
+      "radii": {
+        "_tab_label": "Raggio"
       }
     },
     "enable_web_push_notifications": "Abilita notifiche web push",
@@ -261,7 +290,7 @@
     "notifications": "Notifiche",
     "greentext": "Frecce da meme",
     "upload_a_photo": "Carica un'immagine",
-    "type_domains_to_mute": "Inserisci domini da zittire",
+    "type_domains_to_mute": "Cerca domini da zittire",
     "theme_help_v2_2": "Le icone dietro alcuni elementi sono indicatori del contrasto fra testo e sfondo, passaci sopra col puntatore per ulteriori informazioni. Se si usano delle trasparenze, questi indicatori mostrano il peggior caso possibile.",
     "theme_help_v2_1": "Puoi anche forzare colore ed opacità di alcuni elementi selezionando la casella. Usa il pulsante \"Azzera\" per azzerare tutte le forzature.",
     "useStreamingApiWarning": "(Sconsigliato, sperimentale, può saltare messaggi)",

From 8d7d4980b9a9c68e3c36dc00dfc85e39842b03f7 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 11 Jun 2020 18:39:19 +0200
Subject: [PATCH 480/483] StatusParser: Remove unused removeAttachmentLinks.

Brings down the vendor by over 200kb
---
 package.json                                    |  3 +--
 src/services/status_parser/status_parser.js     | 15 ---------------
 .../status_parser/status_parses.spec.js         | 17 -----------------
 3 files changed, 1 insertion(+), 34 deletions(-)
 delete mode 100644 test/unit/specs/services/status_parser/status_parses.spec.js

diff --git a/package.json b/package.json
index 4d68cc6e..42efc7e9 100644
--- a/package.json
+++ b/package.json
@@ -22,12 +22,10 @@
     "cropperjs": "^1.4.3",
     "diff": "^3.0.1",
     "escape-html": "^1.0.3",
-    "karma-mocha-reporter": "^2.2.1",
     "localforage": "^1.5.0",
     "object-path": "^0.11.3",
     "phoenix": "^1.3.0",
     "portal-vue": "^2.1.4",
-    "sanitize-html": "^1.13.0",
     "v-click-outside": "^2.1.1",
     "vue": "^2.6.11",
     "vue-chat-scroll": "^1.2.1",
@@ -39,6 +37,7 @@
     "whatwg-fetch": "^2.0.3"
   },
   "devDependencies": {
+    "karma-mocha-reporter": "^2.2.1",
     "@babel/core": "^7.7.5",
     "@babel/plugin-transform-runtime": "^7.7.6",
     "@babel/preset-env": "^7.7.6",
diff --git a/src/services/status_parser/status_parser.js b/src/services/status_parser/status_parser.js
index 3d517e3c..ed0f6d57 100644
--- a/src/services/status_parser/status_parser.js
+++ b/src/services/status_parser/status_parser.js
@@ -1,17 +1,4 @@
 import { filter } from 'lodash'
-import sanitize from 'sanitize-html'
-
-export const removeAttachmentLinks = (html) => {
-  return sanitize(html, {
-    allowedTags: false,
-    allowedAttributes: false,
-    exclusiveFilter: ({ tag, attribs }) => tag === 'a' && typeof attribs.class === 'string' && attribs.class.match(/attachment/)
-  })
-}
-
-export const parse = (html) => {
-  return removeAttachmentLinks(html)
-}
 
 export const muteWordHits = (status, muteWords) => {
   const statusText = status.text.toLowerCase()
@@ -22,5 +9,3 @@ export const muteWordHits = (status, muteWords) => {
 
   return hits
 }
-
-export default parse
diff --git a/test/unit/specs/services/status_parser/status_parses.spec.js b/test/unit/specs/services/status_parser/status_parses.spec.js
deleted file mode 100644
index 7afd5042..00000000
--- a/test/unit/specs/services/status_parser/status_parses.spec.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { removeAttachmentLinks } from '../../../../../src/services/status_parser/status_parser.js'
-
-const example = '<div class="status-content">@<a href="https://sealion.club/user/4" class="h-card mention" title="dewoo">dwmatiz</a> <a href="https://social.heldscal.la/file/3deb764ada10ce64a61b7a070b75dac45f86d2d5bf213bf18873da71d8714d86.png" title="https://social.heldscal.la/file/3deb764ada10ce64a61b7a070b75dac45f86d2d5bf213bf18873da71d8714d86.png" class="attachment" id="attachment-159853" rel="nofollow external">https://social.heldscal.la/attachment/159853</a></div>'
-
-describe('statusParser.removeAttachmentLinks', () => {
-  const exampleWithoutAttachmentLinks = '<div class="status-content">@<a href="https://sealion.club/user/4" class="h-card mention" title="dewoo">dwmatiz</a> </div>'
-
-  it('removes attachment links', () => {
-    const parsed = removeAttachmentLinks(example)
-    expect(parsed).to.eql(exampleWithoutAttachmentLinks)
-  })
-
-  it('works when the class is empty', () => {
-    const parsed = removeAttachmentLinks('<a></a>')
-    expect(parsed).to.eql('<a></a>')
-  })
-})

From 178ff1672d60320a5fbb8c11133eaee4a14c3781 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 11 Jun 2020 18:44:45 +0200
Subject: [PATCH 481/483] PersistedState: Replace object-path with lodash
 function

We were loading that one anyway.
---
 package.json               | 1 -
 src/lib/persisted_state.js | 5 ++---
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/package.json b/package.json
index 42efc7e9..c131d21a 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,6 @@
     "diff": "^3.0.1",
     "escape-html": "^1.0.3",
     "localforage": "^1.5.0",
-    "object-path": "^0.11.3",
     "phoenix": "^1.3.0",
     "portal-vue": "^2.1.4",
     "v-click-outside": "^2.1.1",
diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js
index cad7ea25..8ecb66a8 100644
--- a/src/lib/persisted_state.js
+++ b/src/lib/persisted_state.js
@@ -1,13 +1,12 @@
 import merge from 'lodash.merge'
-import objectPath from 'object-path'
 import localforage from 'localforage'
-import { each } from 'lodash'
+import { each, get, set } from 'lodash'
 
 let loaded = false
 
 const defaultReducer = (state, paths) => (
   paths.length === 0 ? state : paths.reduce((substate, path) => {
-    objectPath.set(substate, path, objectPath.get(state, path))
+    set(substate, path, get(state, path))
     return substate
   }, {})
 )

From 0c364862991907ecd3073503af6b389c305fd53e Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 11 Jun 2020 18:49:39 +0200
Subject: [PATCH 482/483] API: Remove fetch polyfill

All browser except IE have supported this for longer than
Pleroma even exists.
---
 package.json                    | 3 +--
 src/services/api/api.service.js | 1 -
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/package.json b/package.json
index c131d21a..c0665f6e 100644
--- a/package.json
+++ b/package.json
@@ -32,8 +32,7 @@
     "vue-router": "^3.0.1",
     "vue-template-compiler": "^2.6.11",
     "vuelidate": "^0.7.4",
-    "vuex": "^3.0.1",
-    "whatwg-fetch": "^2.0.3"
+    "vuex": "^3.0.1"
   },
   "devDependencies": {
     "karma-mocha-reporter": "^2.2.1",
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index b3082bc5..dfffc291 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1,6 +1,5 @@
 import { each, map, concat, last, get } from 'lodash'
 import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
-import 'whatwg-fetch'
 import { RegistrationError, StatusCodeError } from '../errors/errors'
 
 /* eslint-env browser */

From 17b6396333de072d2bd271bbe88f02f266cc2d0e Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 11 Jun 2020 19:11:56 +0200
Subject: [PATCH 483/483] Update lockfile

---
 yarn.lock | 100 +++++-------------------------------------------------
 1 file changed, 9 insertions(+), 91 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 61afa7ca..f05b00b1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1062,7 +1062,7 @@ array-union@^1.0.1:
   dependencies:
     array-uniq "^1.0.1"
 
-array-uniq@^1.0.1, array-uniq@^1.0.2:
+array-uniq@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
 
@@ -2545,7 +2545,7 @@ domain-browser@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
 
-domelementtype@1, domelementtype@^1.3.0:
+domelementtype@1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
 
@@ -2559,12 +2559,6 @@ domhandler@2.1:
   dependencies:
     domelementtype "1"
 
-domhandler@^2.3.0:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
-  dependencies:
-    domelementtype "1"
-
 domutils@1.1:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485"
@@ -2578,13 +2572,6 @@ domutils@1.5.1:
     dom-serializer "0"
     domelementtype "1"
 
-domutils@^1.5.1:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
-  dependencies:
-    dom-serializer "0"
-    domelementtype "1"
-
 duplexer2@~0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
@@ -2711,7 +2698,7 @@ ent@~2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
 
-entities@^1.1.1, entities@~1.1.1:
+entities@~1.1.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
 
@@ -3762,17 +3749,6 @@ html-webpack-plugin@^3.0.0, html-webpack-plugin@^3.2.0:
     toposort "^1.0.0"
     util.promisify "1.0.0"
 
-htmlparser2@^3.10.0:
-  version "3.10.0"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"
-  dependencies:
-    domelementtype "^1.3.0"
-    domhandler "^2.3.0"
-    domutils "^1.5.1"
-    entities "^1.1.1"
-    inherits "^2.0.1"
-    readable-stream "^3.0.6"
-
 htmlparser2@~3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe"
@@ -4757,10 +4733,6 @@ lodash.clone@3.0.3:
     lodash._bindcallback "^3.0.0"
     lodash._isiterateecall "^3.0.0"
 
-lodash.clonedeep@^4.5.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
-
 lodash.create@3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7"
@@ -4780,10 +4752,6 @@ lodash.defaultsdeep@4.3.2:
     lodash.mergewith "^4.0.0"
     lodash.rest "^4.0.0"
 
-lodash.escaperegexp@^4.1.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
-
 lodash.find@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-3.2.1.tgz#046e319f3ace912ac6c9246c7f683c5ec07b36ad"
@@ -4815,14 +4783,10 @@ lodash.isplainobject@^3.0.0, lodash.isplainobject@^3.2.0:
     lodash.isarguments "^3.0.0"
     lodash.keysin "^3.0.0"
 
-lodash.isplainobject@^4.0.0, lodash.isplainobject@^4.0.6:
+lodash.isplainobject@^4.0.0:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
 
-lodash.isstring@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
-
 lodash.istypedarray@^3.0.0:
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62"
@@ -4871,7 +4835,7 @@ lodash.merge@^3.3.2:
     lodash.keysin "^3.0.0"
     lodash.toplainobject "^3.0.0"
 
-lodash.mergewith@^4.0.0, lodash.mergewith@^4.6.1:
+lodash.mergewith@^4.0.0:
   version "4.6.1"
   resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
 
@@ -5538,10 +5502,6 @@ object-keys@^1.0.11, object-keys@^1.0.12:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
 
-object-path@^0.11.3:
-  version "0.11.4"
-  resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949"
-
 object-visit@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
@@ -6245,14 +6205,6 @@ postcss@^7.0.0:
     source-map "^0.6.1"
     supports-color "^6.1.0"
 
-postcss@^7.0.5:
-  version "7.0.8"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.8.tgz#2a3c5f2bdd00240cd0d0901fd998347c93d36696"
-  dependencies:
-    chalk "^2.4.2"
-    source-map "^0.6.1"
-    supports-color "^6.0.0"
-
 prelude-ls@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -6521,14 +6473,6 @@ readable-stream@1.1.x:
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-readable-stream@^3.0.6:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06"
-  dependencies:
-    inherits "^2.0.3"
-    string_decoder "^1.1.1"
-    util-deprecate "^1.0.1"
-
 readdirp@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
@@ -6839,21 +6783,6 @@ samsam@1.x, samsam@^1.1.3:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
 
-sanitize-html@^1.13.0:
-  version "1.20.0"
-  resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.0.tgz#9a602beb1c9faf960fb31f9890f61911cc4d9156"
-  dependencies:
-    chalk "^2.4.1"
-    htmlparser2 "^3.10.0"
-    lodash.clonedeep "^4.5.0"
-    lodash.escaperegexp "^4.1.2"
-    lodash.isplainobject "^4.0.6"
-    lodash.isstring "^4.0.1"
-    lodash.mergewith "^4.6.1"
-    postcss "^7.0.5"
-    srcset "^1.0.0"
-    xtend "^4.0.1"
-
 "sass-loader@git://github.com/webpack-contrib/sass-loader":
   version "7.1.0"
   resolved "git://github.com/webpack-contrib/sass-loader#e279f2a129eee0bd0b624b5acd498f23a81ee35e"
@@ -7225,13 +7154,6 @@ sprintf-js@~1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
 
-srcset@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
-  dependencies:
-    array-uniq "^1.0.2"
-    number-is-nan "^1.0.0"
-
 sshpk@^1.7.0:
   version "1.16.0"
   resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de"
@@ -7331,7 +7253,7 @@ string-width@^3.0.0:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^5.1.0"
 
-string_decoder@^1.0.0, string_decoder@^1.1.1:
+string_decoder@^1.0.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
   dependencies:
@@ -7415,7 +7337,7 @@ supports-color@^5.3.0, supports-color@^5.4.0:
   dependencies:
     has-flag "^3.0.0"
 
-supports-color@^6.0.0, supports-color@^6.1.0:
+supports-color@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
   dependencies:
@@ -7780,7 +7702,7 @@ useragent@2.3.0:
     lru-cache "4.1.x"
     tmp "0.0.x"
 
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
+util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
 
@@ -8015,10 +7937,6 @@ webpack@^4.0.0:
     watchpack "^1.5.0"
     webpack-sources "^1.3.0"
 
-whatwg-fetch@^2.0.3:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
-
 whet.extend@~0.9.9:
   version "0.9.9"
   resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
@@ -8090,7 +8008,7 @@ xregexp@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
 
-xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
+xtend@^4.0.0, xtend@~4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"