From f9c85c0c491fa800054250a814978e6f8fcc439a Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@kotobank.ch>
Date: Thu, 21 Dec 2023 00:16:16 +0300
Subject: Initial incomplete admin emoji settings implementation

---
 .../settings_modal/admin_tabs/emoji_tab.vue        | 93 ++++++++++++++++++++++
 1 file changed, 93 insertions(+)
 create mode 100644 src/components/settings_modal/admin_tabs/emoji_tab.vue

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
new file mode 100644
index 00000000..699e4afe
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -0,0 +1,93 @@
+<template>
+  <div
+    class="emoji-tab"
+    :label="$t('admin_dash.tabs.emoji')"
+  >
+    <div class="setting-item">
+      <h2>{{ $t('admin_dash.tabs.emoji') }}</h2>
+
+      <span class="btn-group">
+        <button
+          class="button button-default btn"
+          type="button"
+          @click="reloadEmoji">
+          {{ $t('admin_dash.emoji.reload') }}
+        </button>
+        <button
+          class="button button-default btn"
+          type="button"
+          @click="importFromFS">
+          {{ $t('admin_dash.emoji.importFS') }}
+        </button>
+      </span>
+
+      <tab-switcher :scrollable-tabs="true" v-if="Object.keys(knownPacks).length > 0">
+        <div v-for="(pack, packName) in knownPacks" :label="packName" :key="packName">
+          <div class="pack-info-wrapper">
+            <ul class="setting-list">
+              <li>
+                <div>Description</div>
+                <textarea
+                  v-model="pack.pack.description"
+                  class="bio resize-height" />
+              </li>
+              <li>
+                <div>Homepage</div>
+                <input class="emoji-info-input" v-model="pack.pack.homepage">
+              </li>
+              <li>
+                <div>Fallback source</div>
+                <input class="emoji-info-input" v-model="pack.pack['fallback-src']">
+              </li>
+              <li>
+                <Checkbox v-model="pack.pack['can-download']">Downloadable</Checkbox>
+              </li>
+            </ul>
+          </div>
+
+          <h2>Files</h2>
+
+          <ul class="setting-list">
+            <li v-for="(file, shortcode) in pack.files" :key="shortcode">
+              <StillImage
+                class="emoji img"
+                :src="emojiAddr(packName, file)"
+                :title="`:${shortcode}:`"
+                :alt="`:${shortcode}:`"
+              />
+
+              <template v-if="editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined">
+                <input class="emoji-data-input"
+                  v-model="editedParts[packName][shortcode].shortcode">
+                <input class="emoji-data-input"
+                  v-model="editedParts[packName][shortcode].file">
+
+                <button
+                  class="button button-default btn"
+                  type="button"
+                  @click="saveEditedEmoji(packName, shortcode)">
+                  Save
+                </button>
+              </template>
+              <template v-else>
+                <input disabled class="emoji-data-input" :value="shortcode">
+                <input disabled class="emoji-data-input" :value="file">
+
+                <button
+                  class="button button-default btn"
+                  type="button"
+                  @click="editEmoji(packName, shortcode)">
+                  Edit
+                </button>
+              </template>
+            </li>
+          </ul>
+        </div>
+      </tab-switcher>
+    </div>
+  </div>
+</template>
+
+<script src="./emoji_tab.js"></script>
+
+<style lang="scss" src="./emoji_tab.scss"></style>
-- 
cgit v1.2.3-70-g09d2


From 091532d577ae9a23f7a43f681bfd344f040c7738 Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@kotobank.ch>
Date: Sun, 7 Jan 2024 02:45:49 +0300
Subject: Editing emojis in popover, pack creation/deletion

Also fixed some API calls since they weren't working apparently
---
 .../settings_modal/admin_tabs/emoji_tab.js         | 117 ++++++++++--
 .../settings_modal/admin_tabs/emoji_tab.scss       |  22 +++
 .../settings_modal/admin_tabs/emoji_tab.vue        | 206 ++++++++++++++-------
 .../settings_modal_admin_content.vue               |   2 +-
 src/services/api/api.service.js                    |  14 +-
 5 files changed, 272 insertions(+), 89 deletions(-)

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
index f9d3b24e..874d1c3b 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.js
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -1,20 +1,46 @@
+import { clone } from 'lodash'
 import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
 import StringSetting from '../helpers/string_setting.vue'
 import Checkbox from 'components/checkbox/checkbox.vue'
 import StillImage from 'components/still-image/still-image.vue'
+import Select from 'components/select/select.vue'
+import Popover from 'components/popover/popover.vue'
+import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
+import ModifiedIndicator from '../helpers/modified_indicator.vue'
 
 const EmojiTab = {
   components: {
     TabSwitcher,
     StringSetting,
     Checkbox,
-    StillImage
+    StillImage,
+    Select,
+    Popover,
+    ConfirmModal,
+    ModifiedIndicator
   },
 
   data () {
     return {
       knownPacks: { },
-      editedParts: { }
+      editedParts: { },
+      editedMetadata: { },
+      packName: '',
+      newPackName: '',
+      deleteModalVisible: false
+    }
+  },
+
+  computed: {
+    pack () {
+      return this.packName !== '' ? this.knownPacks[this.packName] : undefined
+    },
+    packMeta () {
+      if (this.editedMetadata[this.packName] === undefined) {
+        this.editedMetadata[this.packName] = clone(this.knownPacks[this.packName].pack)
+      }
+
+      return this.editedMetadata[this.packName]
     }
   },
 
@@ -25,37 +51,90 @@ const EmojiTab = {
     importFromFS () {
       this.$store.state.api.backendInteractor.importEmojiFromFS()
     },
-    emojiAddr (packName, name) {
-      return `${this.$store.state.instance.server}/emoji/${encodeURIComponent(packName)}/${name}`
+    emojiAddr (name) {
+      return `${this.$store.state.instance.server}/emoji/${encodeURIComponent(this.packName)}/${name}`
     },
-    editEmoji (packName, shortcode) {
-      if (this.editedParts[packName] === undefined) { this.editedParts[packName] = {} }
 
-      this.editedParts[packName][shortcode] = {
-        shortcode, file: this.knownPacks[packName].files[shortcode]
+    createEmojiPack () {
+      this.$store.state.api.backendInteractor.createEmojiPack(
+        { name: this.newPackName }
+      ).then(resp => resp.json()).then(resp => {
+        if (resp === 'ok') {
+          return this.refreshPackList()
+        } else {
+          return Promise.reject(resp)
+        }
+      }).then(done => {
+        this.$refs.createPackPopover.hidePopover()
+
+        this.packName = this.newPackName
+        this.newPackName = ''
+      })
+    },
+    deleteEmojiPack () {
+      this.$store.state.api.backendInteractor.deleteEmojiPack(
+        { name: this.packName }
+      ).then(resp => resp.json()).then(resp => {
+        if (resp === 'ok') {
+          return this.refreshPackList()
+        } else {
+          return Promise.reject(resp)
+        }
+      }).then(done => {
+        delete this.editedMetadata[this.packName]
+        delete this.editedParts[this.packName]
+
+        this.deleteModalVisible = false
+        this.packName = ''
+      })
+    },
+
+    metaEdited (prop) {
+      return this.pack && this.packMeta[prop] !== this.pack.pack[prop]
+    },
+    savePackMetadata () {
+      this.$store.state.api.backendInteractor.saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }).then(
+        resp => resp.json()
+      ).then(resp => {
+        // Update actual pack data
+        this.pack.pack = resp
+        // Delete edited pack data, should auto-update itself
+        delete this.editedMetadata[this.packName]
+      })
+    },
+
+    editEmoji (shortcode) {
+      if (this.editedParts[this.packName] === undefined) { this.editedParts[this.packName] = {} }
+
+      if (this.editedParts[this.packName][shortcode] === undefined) {
+        this.editedParts[this.packName][shortcode] = {
+          shortcode, file: this.knownPacks[this.packName].files[shortcode]
+        }
       }
     },
-    saveEditedEmoji (packName, shortcode) {
-      const edited = this.editedParts[packName][shortcode]
+    saveEditedEmoji (shortcode) {
+      const edited = this.editedParts[this.packName][shortcode]
 
       this.$store.state.api.backendInteractor.updateEmojiFile(
-        { packName, shortcode, newShortcode: edited.shortcode, newFilename: edited.file, force: false }
+        { packName: this.packName, shortcode, newShortcode: edited.shortcode, newFilename: edited.file, force: false }
       ).then(resp =>
         resp.ok ? resp.json() : resp.text().then(respText => Promise.reject(respText))
       ).then(resp => {
-        this.knownPacks[packName].files = resp
-        delete this.editedParts[packName][shortcode]
+        this.knownPacks[this.packName].files = resp
+        delete this.editedParts[this.packName][shortcode]
       })
+    },
+    refreshPackList () {
+      return this.$store.state.api.backendInteractor.listEmojiPacks()
+        .then(data => data.json())
+        .then(packData => {
+          this.knownPacks = packData.packs
+        })
     }
   },
 
   mounted () {
-    this.$store.state.api.backendInteractor.listEmojiPacks()
-      .then(data => data.json())
-      .then(packData => {
-        this.knownPacks = packData.packs
-        console.log(this.knownPacks)
-      })
+    this.refreshPackList()
   }
 }
 
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
index 397580af..89bf2f7f 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.scss
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -1,3 +1,5 @@
+@import "src/variables";
+
 .emoji-tab {
   .btn-group .btn {
     margin-left: 0.5em;
@@ -21,4 +23,24 @@
     width: 32px;
     height: 32px;
   }
+
+  .emoji-unsaved {
+    box-shadow: 2px 3px 5px var(--cBlue, $fallback--cBlue);
+  }
+
+  .emoji-list {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 1em 1em;
+  }
+}
+
+.emoji-tab-edit-popover {
+  padding-left: 0.5em;
+  padding-right: 0.5em;
+  padding-bottom: 0.5em;
+}
+
+.emoji-tab-popover-button {
+  margin-left: 0.5em;
 }
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index 699e4afe..534f881a 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -6,84 +6,166 @@
     <div class="setting-item">
       <h2>{{ $t('admin_dash.tabs.emoji') }}</h2>
 
-      <span class="btn-group">
-        <button
-          class="button button-default btn"
-          type="button"
-          @click="reloadEmoji">
-          {{ $t('admin_dash.emoji.reload') }}
-        </button>
-        <button
-          class="button button-default btn"
-          type="button"
-          @click="importFromFS">
-          {{ $t('admin_dash.emoji.importFS') }}
-        </button>
-      </span>
+      <ul class="setting-list">
+        <li class="btn-group setting-item">
+          <button
+            class="button button-default btn"
+            type="button"
+            @click="reloadEmoji">
+            {{ $t('admin_dash.emoji.reload') }}
+          </button>
+          <button
+            class="button button-default btn"
+            type="button"
+            @click="importFromFS">
+            {{ $t('admin_dash.emoji.importFS') }}
+          </button>
+        </li>
 
-      <tab-switcher :scrollable-tabs="true" v-if="Object.keys(knownPacks).length > 0">
-        <div v-for="(pack, packName) in knownPacks" :label="packName" :key="packName">
-          <div class="pack-info-wrapper">
-            <ul class="setting-list">
-              <li>
-                <div>Description</div>
-                <textarea
-                  v-model="pack.pack.description"
-                  class="bio resize-height" />
-              </li>
-              <li>
-                <div>Homepage</div>
-                <input class="emoji-info-input" v-model="pack.pack.homepage">
-              </li>
-              <li>
-                <div>Fallback source</div>
-                <input class="emoji-info-input" v-model="pack.pack['fallback-src']">
-              </li>
-              <li>
-                <Checkbox v-model="pack.pack['can-download']">Downloadable</Checkbox>
-              </li>
-            </ul>
-          </div>
+        <li class="setting-item btn-group">
+          <button
+            class="button button-default btn"
+            type="button"
+            @click="$refs.createPackPopover.showPopover">
+            Create pack
+          </button>
+          <Popover
+            ref="createPackPopover"
+            trigger="click"
+            placement="bottom"
+            bound-to-selector=".emoji-tab"
+            :bound-to="{ x: 'container' }"
+            :offset="{ y: 5 }"
+          >
+            <template #content>
+              <input v-model="newPackName" placeholder="New pack name">
+              <button
+                class="button button-default btn emoji-tab-popover-button"
+                type="button"
+                @click="createEmojiPack">
+                Create
+              </button>
+            </template>
+          </Popover>
 
-          <h2>Files</h2>
+          <button
+            class="button button-default btn"
+            :disabled="packName == ''"
+            type="button"
+            @click="deleteModalVisible = true">
+            Delete pack
+          </button>
+          <ConfirmModal
+            v-if="deleteModalVisible"
+            title="Delete?"
+            :cancel-text="$t('status.delete_confirm_cancel_button')"
+            :confirm-text="$t('status.delete_confirm_accept_button')"
+            @cancelled="deleteModalVisible = false"
+            @accepted="deleteEmojiPack" >
+            Are you sure you want to delete {{ packName }}?
+          </ConfirmModal>
+        </li>
 
+        <li>
+          <Select class="form-control" v-model="packName">
+            <option value="" disabled hidden>Emoji pack</option>
+            <option v-for="(pack, listPackName) in knownPacks" :label="listPackName" :key="listPackName">
+              {{ listPackName }}
+            </option>
+          </Select>
+        </li>
+      </ul>
+
+      <div v-if="packName !== ''">
+        <div class="pack-info-wrapper">
           <ul class="setting-list">
-            <li v-for="(file, shortcode) in pack.files" :key="shortcode">
-              <StillImage
-                class="emoji img"
-                :src="emojiAddr(packName, file)"
-                :title="`:${shortcode}:`"
-                :alt="`:${shortcode}:`"
-              />
+            <li>
+              <div>
+                Description
+                <ModifiedIndicator :changed="metaEdited('description')" />
+              </div>
+              <textarea
+                v-model="packMeta.description"
+                class="bio resize-height" />
+            </li>
+            <li>
+              <div>
+                Homepage
+                <ModifiedIndicator :changed="metaEdited('homepage')" />
+              </div>
+              <input class="emoji-info-input" v-model="packMeta.homepage">
+            </li>
+            <li>
+              <div>
+                Fallback source
+                <ModifiedIndicator :changed="metaEdited('fallback-src')" />
+              </div>
+              <input class="emoji-info-input" v-model="packMeta['fallback-src']">
+            </li>
+            <li>
+              <div>Fallback SHA256</div>
+              <input :disabled="true" class="emoji-info-input" v-model="packMeta['fallback-src-sha256']">
+            </li>
+            <li>
+              <Checkbox v-model="packMeta['share-files']">Share</Checkbox>
+
+              <ModifiedIndicator :changed="metaEdited('share-files')" />
+            </li>
+            <li>
+              <button
+                class="button button-default btn"
+                type="button"
+                @click="savePackMetadata">
+                Save
+              </button>
+            </li>
+          </ul>
+        </div>
+
+        <h2>Files</h2>
 
-              <template v-if="editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined">
+        <div class="emoji-list" v-if="pack">
+          <Popover
+            v-for="(file, shortcode) in pack.files" :key="shortcode"
+            trigger="click"
+            placement="top"
+            :class="{'emoji-unsaved': editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined}"
+            popover-class="emoji-tab-edit-popover popover-default"
+            bound-to-selector=".emoji-list"
+            :bound-to="{ x: 'container' }"
+            :offset="{ y: 5 }"
+            @show="editEmoji(shortcode)"
+          >
+            <template #content>
+              <h3>
+                Editing <i>{{ shortcode }}</i>
+              </h3>
+              <div v-if="editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined">
                 <input class="emoji-data-input"
                   v-model="editedParts[packName][shortcode].shortcode">
                 <input class="emoji-data-input"
                   v-model="editedParts[packName][shortcode].file">
 
                 <button
-                  class="button button-default btn"
+                  class="button button-default btn emoji-tab-popover-button"
                   type="button"
-                  @click="saveEditedEmoji(packName, shortcode)">
+                  @click="saveEditedEmoji(shortcode)">
                   Save
                 </button>
-              </template>
-              <template v-else>
-                <input disabled class="emoji-data-input" :value="shortcode">
-                <input disabled class="emoji-data-input" :value="file">
 
-                <button
-                  class="button button-default btn"
-                  type="button"
-                  @click="editEmoji(packName, shortcode)">
-                  Edit
-                </button>
-              </template>
-            </li>
-          </ul>
+              </div>
+            </template>
+            <template #trigger>
+              <StillImage
+                class="emoji"
+                :src="emojiAddr(file)"
+                :title="`:${shortcode}:`"
+                :alt="`:${shortcode}:`"
+              />
+            </template>
+          </Popover>
         </div>
-      </tab-switcher>
+      </div>
     </div>
   </div>
 </template>
diff --git a/src/components/settings_modal/settings_modal_admin_content.vue b/src/components/settings_modal/settings_modal_admin_content.vue
index ba2d0a30..65e23b7e 100644
--- a/src/components/settings_modal/settings_modal_admin_content.vue
+++ b/src/components/settings_modal/settings_modal_admin_content.vue
@@ -63,7 +63,7 @@
 
     <div
       :label="$t('admin_dash.tabs.emoji')"
-      icon="laptop-code"
+      icon="face-smile-beam"
       data-tab-name="emoji"
     >
       <EmojiTab />
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 4d8fdae2..c8e5aa16 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -116,12 +116,12 @@ const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/pleroma/admin/frontends/instal
 
 const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji'
 const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import'
-const PLEROMA_EMOJI_PACKS_URL = (page, pageSize) => `/api/pleroma/emoji/packs?page=${page}&page_size=${pageSize}`
-const PLEROMA_EMOJI_PACK_URL = (name) => `/api/pleroma/emoji/pack?name=${name}`
-const PLEROMA_EMOJI_PACKS_DL_REMOTE_URL = '/api/pleroma/emoji/packs/download'
+const PLEROMA_EMOJI_PACKS_URL = (page, pageSize) => `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}`
+const PLEROMA_EMOJI_PACK_URL = (name) => `/api/v1/pleroma/emoji/pack?name=${name}`
+const PLEROMA_EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download'
 const PLEROMA_EMOJI_PACKS_LS_REMOTE_URL =
-  (url, page, pageSize) => `/api/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}`
-const PLEROMA_EMOJI_UPDATE_FILE_URL = (name) => `/api/pleroma/emoji/packs/files?name=${name}`
+  (url, page, pageSize) => `/api/v1/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}`
+const PLEROMA_EMOJI_UPDATE_FILE_URL = (name) => `/api/v1/pleroma/emoji/packs/files?name=${name}`
 
 const oldfetch = window.fetch
 
@@ -1809,7 +1809,7 @@ const importEmojiFromFS = () => {
 }
 
 const createEmojiPack = ({ name }) => {
-  return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'PUT' })
+  return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'POST' })
 }
 
 const listEmojiPacks = () => {
@@ -1850,7 +1850,7 @@ const saveEmojiPackMetadata = ({ name, newData }) => {
     {
       method: 'PATCH',
       headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({ name, new_data: newData })
+      body: JSON.stringify({ metadata: newData })
     }
   )
 }
-- 
cgit v1.2.3-70-g09d2


From 4451cccb3c1244a179b9f5017e6a4536eb5456f1 Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@kotobank.ch>
Date: Sun, 7 Jan 2024 12:26:40 +0300
Subject: Error handling, uploading/deleting new emojis, sorting

---
 .../settings_modal/admin_tabs/emoji_tab.js         | 96 ++++++++++++++++++++--
 .../settings_modal/admin_tabs/emoji_tab.scss       | 11 ++-
 .../settings_modal/admin_tabs/emoji_tab.vue        | 67 ++++++++++++++-
 src/services/api/api.service.js                    |  2 +-
 4 files changed, 164 insertions(+), 12 deletions(-)

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
index 874d1c3b..f3adc3d5 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.js
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -8,6 +8,8 @@ import Popover from 'components/popover/popover.vue'
 import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
 import ModifiedIndicator from '../helpers/modified_indicator.vue'
 
+const newEmojiUploadBase = { shortcode: '', file: '', upload: [] }
+
 const EmojiTab = {
   components: {
     TabSwitcher,
@@ -27,7 +29,8 @@ const EmojiTab = {
       editedMetadata: { },
       packName: '',
       newPackName: '',
-      deleteModalVisible: false
+      deleteModalVisible: false,
+      newEmojiUpload: clone(newEmojiUploadBase)
     }
   },
 
@@ -37,7 +40,7 @@ const EmojiTab = {
     },
     packMeta () {
       if (this.editedMetadata[this.packName] === undefined) {
-        this.editedMetadata[this.packName] = clone(this.knownPacks[this.packName].pack)
+        this.editedMetadata[this.packName] = clone(this.pack.pack)
       }
 
       return this.editedMetadata[this.packName]
@@ -55,6 +58,27 @@ const EmojiTab = {
       return `${this.$store.state.instance.server}/emoji/${encodeURIComponent(this.packName)}/${name}`
     },
 
+    uploadEmoji () {
+      this.$refs.addEmojiPopover.hidePopover()
+
+      this.$store.state.api.backendInteractor.addNewEmojiFile({
+        packName: this.packName,
+        file: this.newEmojiUpload.upload[0],
+        shortcode: this.newEmojiUpload.shortcode,
+        filename: this.newEmojiUpload.file
+      }).then(resp => resp.json()).then(resp => {
+        if (resp.error !== undefined) {
+          this.displayError(resp.error)
+          return
+        }
+
+        this.pack.files = resp
+        this.sortPackFiles(this.packName)
+
+        this.newEmojiUpload = clone(newEmojiUploadBase)
+      })
+    },
+
     createEmojiPack () {
       this.$store.state.api.backendInteractor.createEmojiPack(
         { name: this.newPackName }
@@ -62,6 +86,7 @@ const EmojiTab = {
         if (resp === 'ok') {
           return this.refreshPackList()
         } else {
+          this.displayError(resp.error)
           return Promise.reject(resp)
         }
       }).then(done => {
@@ -78,6 +103,7 @@ const EmojiTab = {
         if (resp === 'ok') {
           return this.refreshPackList()
         } else {
+          this.displayError(resp.error)
           return Promise.reject(resp)
         }
       }).then(done => {
@@ -96,6 +122,11 @@ const EmojiTab = {
       this.$store.state.api.backendInteractor.saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }).then(
         resp => resp.json()
       ).then(resp => {
+        if (resp.error !== undefined) {
+          this.displayError(resp.error)
+          return
+        }
+
         // Update actual pack data
         this.pack.pack = resp
         // Delete edited pack data, should auto-update itself
@@ -108,28 +139,81 @@ const EmojiTab = {
 
       if (this.editedParts[this.packName][shortcode] === undefined) {
         this.editedParts[this.packName][shortcode] = {
-          shortcode, file: this.knownPacks[this.packName].files[shortcode]
+          shortcode, file: this.pack.files[shortcode]
         }
       }
     },
+    deleteEmoji (shortcode) {
+      this.editedParts[this.packName][shortcode].deleteModalVisible = false
+
+      this.$store.state.api.backendInteractor.deleteEmojiFile(
+        { packName: this.packName, shortcode }
+      ).then(resp => resp.json()).then(resp => {
+        if (resp.error !== undefined) {
+          this.displayError(resp.error)
+          return
+        }
+
+        this.pack.files = resp
+        delete this.editedParts[this.packName][shortcode]
+
+        this.sortPackFiles(this.packName)
+      })
+    },
     saveEditedEmoji (shortcode) {
       const edited = this.editedParts[this.packName][shortcode]
 
+      if (edited.shortcode === shortcode && edited.file === this.pack.files[shortcode]) {
+        delete this.editedParts[this.packName][shortcode]
+        return
+      }
+
       this.$store.state.api.backendInteractor.updateEmojiFile(
         { packName: this.packName, shortcode, newShortcode: edited.shortcode, newFilename: edited.file, force: false }
-      ).then(resp =>
-        resp.ok ? resp.json() : resp.text().then(respText => Promise.reject(respText))
       ).then(resp => {
-        this.knownPacks[this.packName].files = resp
+        if (resp.error !== undefined) {
+          this.displayError(resp.error)
+          return Promise.reject(resp.error)
+        }
+
+        return resp.json()
+      }).then(resp => {
+        this.pack.files = resp
         delete this.editedParts[this.packName][shortcode]
+
+        this.sortPackFiles(this.packName)
       })
     },
     refreshPackList () {
       return this.$store.state.api.backendInteractor.listEmojiPacks()
         .then(data => data.json())
         .then(packData => {
+          if (packData.error !== undefined) {
+            this.displayError(packData.error)
+            return
+          }
+
           this.knownPacks = packData.packs
+          for (const name of Object.keys(this.knownPacks)) {
+            this.sortPackFiles(name)
+          }
         })
+    },
+    displayError (msg) {
+      this.$store.dispatch('pushGlobalNotice', {
+        messageKey: 'upload.error.message',
+        messageArgs: [msg],
+        level: 'error'
+      })
+    },
+    sortPackFiles (nameOfPack) {
+      // Sort by key
+      const sorted = Object.keys(this.knownPacks[nameOfPack].files).sort().reduce((acc, key) => {
+        if (key.length === 0) return acc
+        acc[key] = this.knownPacks[nameOfPack].files[key]
+        return acc
+      }, {})
+      this.knownPacks[nameOfPack].files = sorted
     }
   },
 
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
index 89bf2f7f..52b9e2bb 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.scss
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -25,7 +25,7 @@
   }
 
   .emoji-unsaved {
-    box-shadow: 2px 3px 5px var(--cBlue, $fallback--cBlue);
+    box-shadow: 0 3px 5px var(--cBlue, $fallback--cBlue);
   }
 
   .emoji-list {
@@ -44,3 +44,12 @@
 .emoji-tab-popover-button {
   margin-left: 0.5em;
 }
+
+.emoji-tab-popover-input {
+  width: 20em;
+  margin-bottom: 0.5em;
+
+  &:not(:first-child) {
+    margin-left: 0.5em;
+  }
+}
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index 534f881a..cdde95c3 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -22,7 +22,7 @@
           </button>
         </li>
 
-        <li class="setting-item btn-group">
+        <li class="btn-group setting-item">
           <button
             class="button button-default btn"
             type="button"
@@ -62,7 +62,7 @@
             :confirm-text="$t('status.delete_confirm_accept_button')"
             @cancelled="deleteModalVisible = false"
             @accepted="deleteEmojiPack" >
-            Are you sure you want to delete {{ packName }}?
+            Are you sure you want to delete <i>{{ packName }}</i>?
           </ConfirmModal>
         </li>
 
@@ -111,13 +111,58 @@
 
               <ModifiedIndicator :changed="metaEdited('share-files')" />
             </li>
-            <li>
+            <li class="btn-group">
               <button
                 class="button button-default btn"
                 type="button"
                 @click="savePackMetadata">
                 Save
               </button>
+
+              <Popover
+                ref="addEmojiPopover"
+                trigger="click"
+                placement="bottom"
+                bound-to-selector=".emoji-tab"
+                popover-class="emoji-tab-edit-popover popover-default"
+                :bound-to="{ x: 'container' }"
+                :offset="{ y: 5 }"
+              >
+                <template #content>
+                  <h3>Adding new emoji</h3>
+                  <div>
+                    <input
+                      type="file"
+                      class="emoji-tab-popover-input emoji-tab-popover-file"
+                      @change="newEmojiUpload.upload = $event.target.files"
+                    >
+                  </div>
+                  <div>
+                    <div>
+                      <input class="emoji-data-input emoji-tab-popover-input"
+                        v-model="newEmojiUpload.shortcode"
+                        placeholder="Shortcode, leave blank to infer">
+                      <input class="emoji-data-input emoji-tab-popover-input"
+                        v-model="newEmojiUpload.file"
+                        placeholder="Filename, leave blank infer">
+
+                      <button
+                        class="button button-default btn emoji-tab-popover-button"
+                        type="button"
+                        :disabled="this.newEmojiUpload.upload.length == 0"
+                        @click="uploadEmoji">
+                        Save
+                      </button>
+                    </div>
+                  </div>
+                </template>
+              </Popover>
+              <button
+                class="button button-default btn"
+                type="button"
+                @click="$refs.addEmojiPopover.showPopover">
+                Add file
+              </button>
             </li>
           </ul>
         </div>
@@ -152,7 +197,21 @@
                   @click="saveEditedEmoji(shortcode)">
                   Save
                 </button>
-
+                <button
+                  class="button button-default btn emoji-tab-popover-button"
+                  type="button"
+                  @click="editedParts[packName][shortcode].deleteModalVisible = true">
+                  Delete
+                </button>
+                <ConfirmModal
+                  v-if="editedParts[packName][shortcode].deleteModalVisible"
+                  title="Delete?"
+                  :cancel-text="$t('status.delete_confirm_cancel_button')"
+                  :confirm-text="$t('status.delete_confirm_accept_button')"
+                  @cancelled="editedParts[packName][shortcode].deleteModalVisible = false"
+                  @accepted="deleteEmoji(shortcode)" >
+                  Are you sure you want to delete <i>{{ shortcode }}</i>?
+                </ConfirmModal>
               </div>
             </template>
             <template #trigger>
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index c8e5aa16..5239909a 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1863,7 +1863,7 @@ const addNewEmojiFile = ({ packName, file, shortcode, filename }) => {
 
   return fetch(
     PLEROMA_EMOJI_UPDATE_FILE_URL(packName),
-    { method: 'POST', data }
+    { method: 'POST', body: data }
   )
 }
 
-- 
cgit v1.2.3-70-g09d2


From f8e1d5e3e0600a0a113ee40787b7589b61c2654f Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@kotobank.ch>
Date: Sun, 7 Jan 2024 14:28:34 +0300
Subject: Remote pack download, localization

---
 .../settings_modal/admin_tabs/emoji_tab.js         |  80 +++++++-
 .../settings_modal/admin_tabs/emoji_tab.vue        | 208 ++++++++++++++-------
 src/i18n/en.json                                   |  30 ++-
 src/services/api/api.service.js                    |  16 +-
 4 files changed, 250 insertions(+), 84 deletions(-)

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
index f3adc3d5..d88c9e55 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.js
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -24,13 +24,16 @@ const EmojiTab = {
 
   data () {
     return {
-      knownPacks: { },
+      knownLocalPacks: { },
+      knownRemotePacks: { },
       editedParts: { },
       editedMetadata: { },
       packName: '',
       newPackName: '',
       deleteModalVisible: false,
-      newEmojiUpload: clone(newEmojiUploadBase)
+      newEmojiUpload: clone(newEmojiUploadBase),
+      remotePackInstance: '',
+      remotePackDownloadAs: ''
     }
   },
 
@@ -44,6 +47,17 @@ const EmojiTab = {
       }
 
       return this.editedMetadata[this.packName]
+    },
+    knownPacks () {
+      // Copy the object itself but not the children, so they are still passed by reference and modified
+      const result = clone(this.knownLocalPacks)
+      for (const instName in this.knownRemotePacks) {
+        for (const instPack in this.knownRemotePacks[instName]) {
+          result[`${instPack}@${instName}`] = this.knownRemotePacks[instName][instPack]
+        }
+      }
+
+      return result
     }
   },
 
@@ -55,7 +69,12 @@ const EmojiTab = {
       this.$store.state.api.backendInteractor.importEmojiFromFS()
     },
     emojiAddr (name) {
-      return `${this.$store.state.instance.server}/emoji/${encodeURIComponent(this.packName)}/${name}`
+      if (this.pack.remote !== undefined) {
+        // Remote pack
+        return `${this.pack.remote.instance}/emoji/${encodeURIComponent(this.pack.remote.baseName)}/${name}`
+      } else {
+        return `${this.$store.state.instance.server}/emoji/${encodeURIComponent(this.packName)}/${name}`
+      }
     },
 
     uploadEmoji () {
@@ -193,15 +212,64 @@ const EmojiTab = {
             return
           }
 
-          this.knownPacks = packData.packs
-          for (const name of Object.keys(this.knownPacks)) {
+          this.knownLocalPacks = packData.packs
+          for (const name of Object.keys(this.knownLocalPacks)) {
             this.sortPackFiles(name)
           }
         })
     },
+    listRemotePacks () {
+      this.$store.state.api.backendInteractor.listRemoteEmojiPacks({ instance: this.remotePackInstance })
+        .then(data => data.json())
+        .then(packData => {
+          if (packData.error !== undefined) {
+            this.displayError(packData.error)
+            return
+          }
+
+          let inst = this.remotePackInstance
+          if (!inst.startsWith('http')) { inst = 'https://' + inst }
+          const instUrl = new URL(inst)
+          inst = instUrl.host
+
+          for (const packName in packData.packs) {
+            packData.packs[packName].remote = {
+              baseName: packName,
+              instance: instUrl.origin
+            }
+          }
+
+          this.knownRemotePacks[inst] = packData.packs
+
+          this.$refs.remotePackPopover.hidePopover()
+        })
+    },
+    downloadRemotePack () {
+      if (this.remotePackDownloadAs.trim() === '') {
+        this.remotePackDownloadAs = this.pack.remote.baseName
+      }
+
+      this.$store.state.api.backendInteractor.downloadRemoteEmojiPack({
+        instance: this.pack.remote.instance, packName: this.pack.remote.baseName, as: this.remotePackDownloadAs
+      })
+        .then(data => data.json())
+        .then(resp => {
+          if (resp === 'ok') {
+            this.$refs.dlPackPopover.hidePopover()
+
+            return this.refreshPackList()
+          } else {
+            this.displayError(resp.error)
+            return Promise.reject(resp)
+          }
+        }).then(done => {
+          this.packName = this.remotePackDownloadAs
+          this.remotePackDownloadAs = ''
+        })
+    },
     displayError (msg) {
       this.$store.dispatch('pushGlobalNotice', {
-        messageKey: 'upload.error.message',
+        messageKey: 'admin_dash.emoji.error',
         messageArgs: [msg],
         level: 'error'
       })
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index cdde95c3..860eaddd 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -27,7 +27,7 @@
             class="button button-default btn"
             type="button"
             @click="$refs.createPackPopover.showPopover">
-            Create pack
+            {{ $t('admin_dash.emoji.create_pack') }}
           </button>
           <Popover
             ref="createPackPopover"
@@ -38,37 +38,46 @@
             :offset="{ y: 5 }"
           >
             <template #content>
-              <input v-model="newPackName" placeholder="New pack name">
+              <input v-model="newPackName" :placeholder="$t('admin_dash.emoji.new_pack_name')">
               <button
                 class="button button-default btn emoji-tab-popover-button"
                 type="button"
                 @click="createEmojiPack">
-                Create
+                {{ $t('admin_dash.emoji.create') }}
               </button>
             </template>
           </Popover>
 
           <button
             class="button button-default btn"
-            :disabled="packName == ''"
             type="button"
-            @click="deleteModalVisible = true">
-            Delete pack
+            @click="$refs.remotePackPopover.showPopover">
+            {{ $t('admin_dash.emoji.remote_packs') }}
+
+            <Popover
+              ref="remotePackPopover"
+              trigger="click"
+              placement="bottom"
+              bound-to-selector=".emoji-tab"
+              :bound-to="{ x: 'container' }"
+              :offset="{ y: 5 }"
+            >
+              <template #content>
+                <input v-model="remotePackInstance" :placeholder="$t('admin_dash.emoji.remote_pack_instance')">
+                <button
+                  class="button button-default btn emoji-tab-popover-button"
+                  type="button"
+                  @click="listRemotePacks">
+                  {{ $t('admin_dash.emoji.do_list') }}
+                </button>
+              </template>
+            </Popover>
           </button>
-          <ConfirmModal
-            v-if="deleteModalVisible"
-            title="Delete?"
-            :cancel-text="$t('status.delete_confirm_cancel_button')"
-            :confirm-text="$t('status.delete_confirm_accept_button')"
-            @cancelled="deleteModalVisible = false"
-            @accepted="deleteEmojiPack" >
-            Are you sure you want to delete <i>{{ packName }}</i>?
-          </ConfirmModal>
         </li>
 
         <li>
           <Select class="form-control" v-model="packName">
-            <option value="" disabled hidden>Emoji pack</option>
+            <option value="" disabled hidden>{{ $t('admin_dash.emoji.emoji_pack') }}</option>
             <option v-for="(pack, listPackName) in knownPacks" :label="listPackName" :key="listPackName">
               {{ listPackName }}
             </option>
@@ -81,33 +90,38 @@
           <ul class="setting-list">
             <li>
               <div>
-                Description
+                {{ $t('admin_dash.emoji.description') }}
                 <ModifiedIndicator :changed="metaEdited('description')" />
               </div>
               <textarea
                 v-model="packMeta.description"
+                :disabled="pack.remote !== undefined"
                 class="bio resize-height" />
             </li>
             <li>
               <div>
-                Homepage
+                {{ $t('admin_dash.emoji.homepage') }}
                 <ModifiedIndicator :changed="metaEdited('homepage')" />
               </div>
-              <input class="emoji-info-input" v-model="packMeta.homepage">
+              <input
+                class="emoji-info-input" v-model="packMeta.homepage"
+                :disabled="pack.remote !== undefined">
             </li>
             <li>
               <div>
-                Fallback source
+                {{ $t('admin_dash.emoji.fallback_src') }}
                 <ModifiedIndicator :changed="metaEdited('fallback-src')" />
               </div>
-              <input class="emoji-info-input" v-model="packMeta['fallback-src']">
+              <input class="emoji-info-input" v-model="packMeta['fallback-src']" :disabled="pack.remote !== undefined">
             </li>
             <li>
-              <div>Fallback SHA256</div>
+              <div>{{ $t('admin_dash.emoji.fallback_sha256') }}</div>
               <input :disabled="true" class="emoji-info-input" v-model="packMeta['fallback-src-sha256']">
             </li>
             <li>
-              <Checkbox v-model="packMeta['share-files']">Share</Checkbox>
+              <Checkbox :disabled="pack.remote !== undefined" v-model="packMeta['share-files']">
+                {{ $t('admin_dash.emoji.share') }}
+              </Checkbox>
 
               <ModifiedIndicator :changed="metaEdited('share-files')" />
             </li>
@@ -115,64 +129,122 @@
               <button
                 class="button button-default btn"
                 type="button"
+                v-if="pack.remote === undefined"
                 @click="savePackMetadata">
-                Save
+                {{ $t('admin_dash.emoji.save') }}
               </button>
 
-              <Popover
-                ref="addEmojiPopover"
-                trigger="click"
-                placement="bottom"
-                bound-to-selector=".emoji-tab"
-                popover-class="emoji-tab-edit-popover popover-default"
-                :bound-to="{ x: 'container' }"
-                :offset="{ y: 5 }"
-              >
-                <template #content>
-                  <h3>Adding new emoji</h3>
-                  <div>
-                    <input
-                      type="file"
-                      class="emoji-tab-popover-input emoji-tab-popover-file"
-                      @change="newEmojiUpload.upload = $event.target.files"
-                    >
-                  </div>
-                  <div>
+              <button
+                class="button button-default btn"
+                type="button"
+                v-if="pack.remote === undefined"
+                @click="$refs.addEmojiPopover.showPopover">
+                {{ $t('admin_dash.emoji.add_file') }}
+
+                <Popover
+                  ref="addEmojiPopover"
+                  trigger="click"
+                  placement="bottom"
+                  bound-to-selector=".emoji-tab"
+                  popover-class="emoji-tab-edit-popover popover-default"
+                  :bound-to="{ x: 'container' }"
+                  :offset="{ y: 5 }"
+                >
+                  <template #content>
+                    <h3>{{ $t('admin_dash.emoji.adding_new') }}</h3>
                     <div>
-                      <input class="emoji-data-input emoji-tab-popover-input"
-                        v-model="newEmojiUpload.shortcode"
-                        placeholder="Shortcode, leave blank to infer">
-                      <input class="emoji-data-input emoji-tab-popover-input"
-                        v-model="newEmojiUpload.file"
-                        placeholder="Filename, leave blank infer">
-
-                      <button
-                        class="button button-default btn emoji-tab-popover-button"
-                        type="button"
-                        :disabled="this.newEmojiUpload.upload.length == 0"
-                        @click="uploadEmoji">
-                        Save
-                      </button>
+                      <input
+                        type="file"
+                        class="emoji-tab-popover-input emoji-tab-popover-file"
+                        @change="newEmojiUpload.upload = $event.target.files"
+                      >
                     </div>
-                  </div>
-                </template>
-              </Popover>
+                    <div>
+                      <div>
+                        <input class="emoji-data-input emoji-tab-popover-input"
+                          v-model="newEmojiUpload.shortcode"
+                          :placeholder="$t('admin_dash.emoji.new_shortcode')">
+                        <input class="emoji-data-input emoji-tab-popover-input"
+                          v-model="newEmojiUpload.file"
+                          :placeholder="$t('admin_dash.emoji.new_filename')">
+
+                        <button
+                          class="button button-default btn emoji-tab-popover-button"
+                          type="button"
+                          :disabled="this.newEmojiUpload.upload.length == 0"
+                          @click="uploadEmoji">
+                          {{ $t('admin_dash.emoji.save') }}
+                        </button>
+                      </div>
+                    </div>
+                  </template>
+                </Popover>
+              </button>
+
               <button
                 class="button button-default btn"
+                v-if="pack.remote === undefined"
                 type="button"
-                @click="$refs.addEmojiPopover.showPopover">
-                Add file
+                @click="deleteModalVisible = true">
+                {{ $t('admin_dash.emoji.delete_pack') }}
+
+                <ConfirmModal
+                  v-if="deleteModalVisible"
+                  :title="$t('admin_dash.emoji.delete_title')"
+                  :cancel-text="$t('status.delete_confirm_cancel_button')"
+                  :confirm-text="$t('status.delete_confirm_accept_button')"
+                  @cancelled="deleteModalVisible = false"
+                  @accepted="deleteEmojiPack" >
+                  {{ $t('admin_dash.emoji.delete_confirm', packName) }}
+                </ConfirmModal>
+              </button>
+
+              <button
+                class="button button-default btn"
+                type="button"
+                v-if="pack.remote !== undefined"
+                @click="$refs.dlPackPopover.showPopover">
+                {{ $t('admin_dash.emoji.download_pack') }}
+
+                <Popover
+                  ref="dlPackPopover"
+                  trigger="click"
+                  placement="bottom"
+                  bound-to-selector=".emoji-tab"
+                  popover-class="emoji-tab-edit-popover popover-default"
+                  :bound-to="{ x: 'container' }"
+                  :offset="{ y: 5 }"
+                >
+                  <template #content>
+                    <h3>{{ $t('admin_dash.emoji.downloading_pack', [packName]) }}</h3>
+                    <div>
+                      <div>
+                        <input class="emoji-data-input emoji-tab-popover-input"
+                          v-model="remotePackDownloadAs"
+                          :placeholder="$t('admin_dash.emoji.download_as_name')">
+
+                        <button
+                          class="button button-default btn emoji-tab-popover-button"
+                          type="button"
+                          @click="downloadRemotePack">
+                          {{ $t('admin_dash.emoji.download') }}
+                        </button>
+                      </div>
+                    </div>
+                  </template>
+                </Popover>
               </button>
             </li>
           </ul>
         </div>
 
-        <h2>Files</h2>
+        <h2>{{ $t('admin_dash.emoji.files') }}</h2>
 
         <div class="emoji-list" v-if="pack">
           <Popover
             v-for="(file, shortcode) in pack.files" :key="shortcode"
             trigger="click"
+            :disabled="pack.remote !== undefined"
             placement="top"
             :class="{'emoji-unsaved': editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined}"
             popover-class="emoji-tab-edit-popover popover-default"
@@ -183,7 +255,7 @@
           >
             <template #content>
               <h3>
-                Editing <i>{{ shortcode }}</i>
+                {{ $t('admin_dash.emoji.editing', [shortcode]) }}
               </h3>
               <div v-if="editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined">
                 <input class="emoji-data-input"
@@ -195,22 +267,22 @@
                   class="button button-default btn emoji-tab-popover-button"
                   type="button"
                   @click="saveEditedEmoji(shortcode)">
-                  Save
+                  {{ $t('admin_dash.emoji.save') }}
                 </button>
                 <button
                   class="button button-default btn emoji-tab-popover-button"
                   type="button"
                   @click="editedParts[packName][shortcode].deleteModalVisible = true">
-                  Delete
+                  {{ $t('admin_dash.emoji.delete') }}
                 </button>
                 <ConfirmModal
                   v-if="editedParts[packName][shortcode].deleteModalVisible"
-                  title="Delete?"
+                  :title="$t('admin_dash.emoji.delete_title')"
                   :cancel-text="$t('status.delete_confirm_cancel_button')"
                   :confirm-text="$t('status.delete_confirm_accept_button')"
                   @cancelled="editedParts[packName][shortcode].deleteModalVisible = false"
                   @accepted="deleteEmoji(shortcode)" >
-                  Are you sure you want to delete <i>{{ shortcode }}</i>?
+                  {{ $t('admin_dash.emoji.delete_confirm', shortcode) }}
                 </ConfirmModal>
               </div>
             </template>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 62316a3f..3ba96c73 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -935,7 +935,35 @@
     },
     "emoji": {
       "reload": "Reload emoji",
-      "importFS": "Import emoji from filesystem"
+      "importFS": "Import emoji from filesystem",
+      "error": "Error: {0}",
+      "create_pack": "Create pack",
+      "delete_pack": "Delete pack",
+      "new_pack_name": "New pack name",
+      "create": "Create",
+      "remote_packs": "Remote packs",
+      "do_list": "List",
+      "remote_pack_instance": "Remote pack instance",
+      "emoji_pack": "Emoji pack",
+      "description": "Description",
+      "homepage": "Homepage",
+      "fallback_src": "Fallback source",
+      "fallback_sha256": "Fallback SHA256",
+      "share": "Share",
+      "save": "Save",
+      "delete": "Delete",
+      "add_file": "Add file",
+      "adding_new": "Adding new emoji",
+      "new_shortcode": "Shortcode, leave blank to infer",
+      "new_filename": "Filename, leave blank infer",
+      "delete_confirm": "Are you sure you want to delete {0}?",
+      "download_pack": "Download pack",
+      "downloading_pack": "Downloading {0}",
+      "download": "Download",
+      "download_as_name": "New name, leave blank to reuse",
+      "files": "Files",
+      "editing": "Editing {0}",
+      "delete_title": "Delete?"
     },
     "temp_overrides": {
       ":pleroma": {
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 5239909a..8aa209b5 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1817,28 +1817,26 @@ const listEmojiPacks = () => {
 }
 
 const listRemoteEmojiPacks = ({ instance }) => {
+  if (!instance.startsWith('http')) {
+    instance = 'https://' + instance
+  }
+
   return fetch(
-    PLEROMA_EMOJI_PACKS_LS_REMOTE_URL,
+    PLEROMA_EMOJI_PACKS_LS_REMOTE_URL(instance, 1, 25),
     {
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({ instance_address: instance })
+      headers: { 'Content-Type': 'application/json' }
     }
   )
 }
 
 const downloadRemoteEmojiPack = ({ instance, packName, as }) => {
-  if (as.trim() === '') {
-    as = null
-  }
-
   return fetch(
     PLEROMA_EMOJI_PACKS_DL_REMOTE_URL,
     {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
       body: JSON.stringify({
-        instance_address: instance, pack_name: packName, as
+        url: instance, name: packName, as
       })
     }
   )
-- 
cgit v1.2.3-70-g09d2


From 872dffe51b4d96aafb2089917928f82d5a1350db Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@kotobank.ch>
Date: Sun, 7 Jan 2024 16:41:17 +0300
Subject: Implement pack pagination, more localization fixes

---
 .../settings_modal/admin_tabs/emoji_tab.js         | 60 +++++++++++++++-------
 .../settings_modal/admin_tabs/emoji_tab.vue        |  4 +-
 src/services/api/api.service.js                    |  8 +--
 3 files changed, 47 insertions(+), 25 deletions(-)

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
index d88c9e55..ed7ed797 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.js
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -1,4 +1,4 @@
-import { clone } from 'lodash'
+import { clone, assign } from 'lodash'
 import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
 import StringSetting from '../helpers/string_setting.vue'
 import Checkbox from 'components/checkbox/checkbox.vue'
@@ -203,46 +203,68 @@ const EmojiTab = {
         this.sortPackFiles(this.packName)
       })
     },
-    refreshPackList () {
-      return this.$store.state.api.backendInteractor.listEmojiPacks()
+
+    loadPacksPaginated (listFunction) {
+      const pageSize = 25
+      const allPacks = {}
+
+      return listFunction({ instance: this.remotePackInstance, page: 1, pageSize: 0 })
         .then(data => data.json())
-        .then(packData => {
-          if (packData.error !== undefined) {
-            this.displayError(packData.error)
-            return
+        .then(data => {
+          if (data.error !== undefined) { return Promise.reject(data.error) }
+
+          let resultingPromise = Promise.resolve({})
+          for (let i = 0; i < Math.ceil(data.count / pageSize); i++) {
+            resultingPromise = resultingPromise.then(() => listFunction({ instance: this.remotePackInstance, page: i, pageSize })
+            ).then(data => data.json()).then(pageData => {
+              if (pageData.error !== undefined) { return Promise.reject(pageData.error) }
+
+              assign(allPacks, pageData.packs)
+            })
           }
 
-          this.knownLocalPacks = packData.packs
+          return resultingPromise
+        })
+        .then(finished => allPacks)
+        .catch(data => {
+          this.displayError(data)
+        })
+    },
+
+    refreshPackList () {
+      this.loadPacksPaginated(this.$store.state.api.backendInteractor.listEmojiPacks)
+        .then(allPacks => {
+          this.knownLocalPacks = allPacks
           for (const name of Object.keys(this.knownLocalPacks)) {
             this.sortPackFiles(name)
           }
         })
     },
     listRemotePacks () {
-      this.$store.state.api.backendInteractor.listRemoteEmojiPacks({ instance: this.remotePackInstance })
-        .then(data => data.json())
-        .then(packData => {
-          if (packData.error !== undefined) {
-            this.displayError(packData.error)
-            return
-          }
-
+      this.loadPacksPaginated(this.$store.state.api.backendInteractor.listRemoteEmojiPacks)
+        .then(allPacks => {
           let inst = this.remotePackInstance
           if (!inst.startsWith('http')) { inst = 'https://' + inst }
           const instUrl = new URL(inst)
           inst = instUrl.host
 
-          for (const packName in packData.packs) {
-            packData.packs[packName].remote = {
+          for (const packName in allPacks) {
+            allPacks[packName].remote = {
               baseName: packName,
               instance: instUrl.origin
             }
           }
 
-          this.knownRemotePacks[inst] = packData.packs
+          this.knownRemotePacks[inst] = allPacks
+          for (const pack in this.knownRemotePacks[inst]) {
+            this.sortPackFiles(`${pack}@${inst}`)
+          }
 
           this.$refs.remotePackPopover.hidePopover()
         })
+        .catch(data => {
+          this.displayError(data)
+        })
     },
     downloadRemotePack () {
       if (this.remotePackDownloadAs.trim() === '') {
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index 860eaddd..818e5973 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -195,7 +195,7 @@
                   :confirm-text="$t('status.delete_confirm_accept_button')"
                   @cancelled="deleteModalVisible = false"
                   @accepted="deleteEmojiPack" >
-                  {{ $t('admin_dash.emoji.delete_confirm', packName) }}
+                  {{ $t('admin_dash.emoji.delete_confirm', [packName]) }}
                 </ConfirmModal>
               </button>
 
@@ -282,7 +282,7 @@
                   :confirm-text="$t('status.delete_confirm_accept_button')"
                   @cancelled="editedParts[packName][shortcode].deleteModalVisible = false"
                   @accepted="deleteEmoji(shortcode)" >
-                  {{ $t('admin_dash.emoji.delete_confirm', shortcode) }}
+                  {{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
                 </ConfirmModal>
               </div>
             </template>
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 8aa209b5..11dbfe0e 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1812,17 +1812,17 @@ const createEmojiPack = ({ name }) => {
   return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'POST' })
 }
 
-const listEmojiPacks = () => {
-  return fetch(PLEROMA_EMOJI_PACKS_URL(1, 25))
+const listEmojiPacks = ({ page, pageSize }) => {
+  return fetch(PLEROMA_EMOJI_PACKS_URL(page, pageSize))
 }
 
-const listRemoteEmojiPacks = ({ instance }) => {
+const listRemoteEmojiPacks = ({ instance, page, pageSize }) => {
   if (!instance.startsWith('http')) {
     instance = 'https://' + instance
   }
 
   return fetch(
-    PLEROMA_EMOJI_PACKS_LS_REMOTE_URL(instance, 1, 25),
+    PLEROMA_EMOJI_PACKS_LS_REMOTE_URL(instance, page, pageSize),
     {
       headers: { 'Content-Type': 'application/json' }
     }
-- 
cgit v1.2.3-70-g09d2


From a2133f528333f5255296c15fb2b7abcab688bf1c Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@kotobank.ch>
Date: Wed, 17 Jan 2024 22:41:18 +0300
Subject: Rearrange stuff in accordance with comments on the MR, mostly

Also, add support for a custom message to the modified indicator
---
 .../settings_modal/admin_tabs/emoji_tab.js         |  28 +-
 .../settings_modal/admin_tabs/emoji_tab.scss       |  27 +-
 .../settings_modal/admin_tabs/emoji_tab.vue        | 337 ++++++++++++---------
 .../settings_modal/helpers/modified_indicator.vue  |  10 +-
 src/i18n/en.json                                   |  14 +-
 5 files changed, 263 insertions(+), 153 deletions(-)

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
index ed7ed797..940f0c6c 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.js
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -58,6 +58,11 @@ const EmojiTab = {
       }
 
       return result
+    },
+    newEmojiUploadPreview () {
+      if (this.newEmojiUpload.upload.length > 0) {
+        return URL.createObjectURL(this.newEmojiUpload.upload[0])
+      }
     }
   },
 
@@ -135,7 +140,11 @@ const EmojiTab = {
     },
 
     metaEdited (prop) {
-      return this.pack && this.packMeta[prop] !== this.pack.pack[prop]
+      if (!this.pack) return
+
+      const def = this.pack.pack[prop] || ''
+      const edited = this.packMeta[prop] || ''
+      return edited !== def
     },
     savePackMetadata () {
       this.$store.state.api.backendInteractor.saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }).then(
@@ -153,6 +162,11 @@ const EmojiTab = {
       })
     },
 
+    revertEmoji (shortcode) {
+      // Delete current changes and overwrite them with defaults. If the window is closed, they'll get cleared anyway
+      delete this.editedParts[this.packName][shortcode]
+      this.editEmoji(shortcode)
+    },
     editEmoji (shortcode) {
       if (this.editedParts[this.packName] === undefined) { this.editedParts[this.packName] = {} }
 
@@ -179,14 +193,22 @@ const EmojiTab = {
         this.sortPackFiles(this.packName)
       })
     },
-    saveEditedEmoji (shortcode) {
+    closedEditedEmoji (shortcode) {
       const edited = this.editedParts[this.packName][shortcode]
 
       if (edited.shortcode === shortcode && edited.file === this.pack.files[shortcode]) {
         delete this.editedParts[this.packName][shortcode]
-        return
+
+        return true
       }
 
+      return false
+    },
+    saveEditedEmoji (shortcode) {
+      if (this.closedEditedEmoji(shortcode)) return
+
+      const edited = this.editedParts[this.packName][shortcode]
+
       this.$store.state.api.backendInteractor.updateEmojiFile(
         { packName: this.packName, shortcode, newShortcode: edited.shortcode, newFilename: edited.file, force: false }
       ).then(resp => {
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
index 52b9e2bb..71f46cc2 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.scss
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -1,7 +1,7 @@
 @import "src/variables";
 
 .emoji-tab {
-  .btn-group .btn {
+  .btn-group .btn:not(:first-child) {
     margin-left: 0.5em;
   }
 
@@ -35,21 +35,34 @@
   }
 }
 
+.emoji-tab-popover-button:not(:first-child) {
+  margin-left: 0.5em;
+}
+
 .emoji-tab-edit-popover {
   padding-left: 0.5em;
   padding-right: 0.5em;
   padding-bottom: 0.5em;
-}
 
-.emoji-tab-popover-button {
-  margin-left: 0.5em;
+  .emoji {
+    width: 32px;
+    height: 32px;
+  }
 }
 
 .emoji-tab-popover-input {
-  width: 20em;
   margin-bottom: 0.5em;
 
-  &:not(:first-child) {
-    margin-left: 0.5em;
+  label {
+    display: block;
+    margin-bottom: 0.5em;
+  }
+
+  input {
+    width: 20em;
+  }
+
+  .emoji-tab-popover-file {
+    padding-top: 3px;
   }
 }
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index 818e5973..729496a2 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -7,6 +7,8 @@
       <h2>{{ $t('admin_dash.tabs.emoji') }}</h2>
 
       <ul class="setting-list">
+        <h2>{{ $t('admin_dash.emoji.global_actions') }}</h2>
+
         <li class="btn-group setting-item">
           <button
             class="button button-default btn"
@@ -23,31 +25,6 @@
         </li>
 
         <li class="btn-group setting-item">
-          <button
-            class="button button-default btn"
-            type="button"
-            @click="$refs.createPackPopover.showPopover">
-            {{ $t('admin_dash.emoji.create_pack') }}
-          </button>
-          <Popover
-            ref="createPackPopover"
-            trigger="click"
-            placement="bottom"
-            bound-to-selector=".emoji-tab"
-            :bound-to="{ x: 'container' }"
-            :offset="{ y: 5 }"
-          >
-            <template #content>
-              <input v-model="newPackName" :placeholder="$t('admin_dash.emoji.new_pack_name')">
-              <button
-                class="button button-default btn emoji-tab-popover-button"
-                type="button"
-                @click="createEmojiPack">
-                {{ $t('admin_dash.emoji.create') }}
-              </button>
-            </template>
-          </Popover>
-
           <button
             class="button button-default btn"
             type="button"
@@ -56,6 +33,7 @@
 
             <Popover
               ref="remotePackPopover"
+              popover-class="emoji-tab-edit-popover popover-default"
               trigger="click"
               placement="bottom"
               bound-to-selector=".emoji-tab"
@@ -63,18 +41,23 @@
               :offset="{ y: 5 }"
             >
               <template #content>
-                <input v-model="remotePackInstance" :placeholder="$t('admin_dash.emoji.remote_pack_instance')">
-                <button
-                  class="button button-default btn emoji-tab-popover-button"
-                  type="button"
-                  @click="listRemotePacks">
-                  {{ $t('admin_dash.emoji.do_list') }}
-                </button>
+                <div class="emoji-tab-popover-input">
+                  <h3>{{ $t('admin_dash.emoji.remote_pack_instance') }}</h3>
+                  <input v-model="remotePackInstance" :placeholder="$t('admin_dash.emoji.remote_pack_instance')">
+                  <button
+                    class="button button-default btn emoji-tab-popover-button"
+                    type="button"
+                    @click="listRemotePacks">
+                    {{ $t('admin_dash.emoji.do_list') }}
+                  </button>
+                </div>
               </template>
             </Popover>
           </button>
         </li>
 
+        <h2>{{ $t('admin_dash.emoji.emoji_packs') }}</h2>
+
         <li>
           <Select class="form-control" v-model="packName">
             <option value="" disabled hidden>{{ $t('admin_dash.emoji.emoji_pack') }}</option>
@@ -82,6 +65,35 @@
               {{ listPackName }}
             </option>
           </Select>
+
+          <button
+            class="button button-default btn emoji-tab-popover-button"
+            type="button"
+            @click="$refs.createPackPopover.showPopover">
+            {{ $t('admin_dash.emoji.create_pack') }}
+          </button>
+          <Popover
+            ref="createPackPopover"
+            popover-class="emoji-tab-edit-popover popover-default"
+            trigger="click"
+            placement="bottom"
+            bound-to-selector=".emoji-tab"
+            :bound-to="{ x: 'container' }"
+            :offset="{ y: 5 }"
+          >
+            <template #content>
+              <div class="emoji-tab-popover-input">
+                <h3>{{ $t('admin_dash.emoji.new_pack_name') }}</h3>
+                <input v-model="newPackName" :placeholder="$t('admin_dash.emoji.new_pack_name')">
+                <button
+                  class="button button-default btn emoji-tab-popover-button"
+                  type="button"
+                  @click="createEmojiPack">
+                  {{ $t('admin_dash.emoji.create') }}
+                </button>
+              </div>
+            </template>
+          </Popover>
         </li>
       </ul>
 
@@ -91,7 +103,7 @@
             <li>
               <div>
                 {{ $t('admin_dash.emoji.description') }}
-                <ModifiedIndicator :changed="metaEdited('description')" />
+                <ModifiedIndicator :changed="metaEdited('description')" message-key="admin_dash.emoji.metadata_changed" />
               </div>
               <textarea
                 v-model="packMeta.description"
@@ -101,7 +113,7 @@
             <li>
               <div>
                 {{ $t('admin_dash.emoji.homepage') }}
-                <ModifiedIndicator :changed="metaEdited('homepage')" />
+                <ModifiedIndicator :changed="metaEdited('homepage')" message-key="admin_dash.emoji.metadata_changed" />
               </div>
               <input
                 class="emoji-info-input" v-model="packMeta.homepage"
@@ -110,7 +122,7 @@
             <li>
               <div>
                 {{ $t('admin_dash.emoji.fallback_src') }}
-                <ModifiedIndicator :changed="metaEdited('fallback-src')" />
+                <ModifiedIndicator :changed="metaEdited('fallback-src')" message-key="admin_dash.emoji.metadata_changed" />
               </div>
               <input class="emoji-info-input" v-model="packMeta['fallback-src']" :disabled="pack.remote !== undefined">
             </li>
@@ -123,7 +135,7 @@
                 {{ $t('admin_dash.emoji.share') }}
               </Checkbox>
 
-              <ModifiedIndicator :changed="metaEdited('share-files')" />
+              <ModifiedIndicator :changed="metaEdited('share-files')" message-key="admin_dash.emoji.metadata_changed" />
             </li>
             <li class="btn-group">
               <button
@@ -131,54 +143,14 @@
                 type="button"
                 v-if="pack.remote === undefined"
                 @click="savePackMetadata">
-                {{ $t('admin_dash.emoji.save') }}
+                {{ $t('admin_dash.emoji.save_meta') }}
               </button>
-
               <button
                 class="button button-default btn"
                 type="button"
                 v-if="pack.remote === undefined"
-                @click="$refs.addEmojiPopover.showPopover">
-                {{ $t('admin_dash.emoji.add_file') }}
-
-                <Popover
-                  ref="addEmojiPopover"
-                  trigger="click"
-                  placement="bottom"
-                  bound-to-selector=".emoji-tab"
-                  popover-class="emoji-tab-edit-popover popover-default"
-                  :bound-to="{ x: 'container' }"
-                  :offset="{ y: 5 }"
-                >
-                  <template #content>
-                    <h3>{{ $t('admin_dash.emoji.adding_new') }}</h3>
-                    <div>
-                      <input
-                        type="file"
-                        class="emoji-tab-popover-input emoji-tab-popover-file"
-                        @change="newEmojiUpload.upload = $event.target.files"
-                      >
-                    </div>
-                    <div>
-                      <div>
-                        <input class="emoji-data-input emoji-tab-popover-input"
-                          v-model="newEmojiUpload.shortcode"
-                          :placeholder="$t('admin_dash.emoji.new_shortcode')">
-                        <input class="emoji-data-input emoji-tab-popover-input"
-                          v-model="newEmojiUpload.file"
-                          :placeholder="$t('admin_dash.emoji.new_filename')">
-
-                        <button
-                          class="button button-default btn emoji-tab-popover-button"
-                          type="button"
-                          :disabled="this.newEmojiUpload.upload.length == 0"
-                          @click="uploadEmoji">
-                          {{ $t('admin_dash.emoji.save') }}
-                        </button>
-                      </div>
-                    </div>
-                  </template>
-                </Popover>
+                @click="savePackMetadata">
+                {{ $t('admin_dash.emoji.revert_meta') }}
               </button>
 
               <button
@@ -219,12 +191,15 @@
                     <h3>{{ $t('admin_dash.emoji.downloading_pack', [packName]) }}</h3>
                     <div>
                       <div>
-                        <input class="emoji-data-input emoji-tab-popover-input"
-                          v-model="remotePackDownloadAs"
-                          :placeholder="$t('admin_dash.emoji.download_as_name')">
+                        <div class="emoji-tab-popover-input">
+                          <label for="remote-download-as-input">{{ $t('admin_dash.emoji.download_as_name') }}</label>
+                          <input id="remote-download-as-input" class="emoji-data-input"
+                            v-model="remotePackDownloadAs"
+                            :placeholder="$t('admin_dash.emoji.download_as_name_full')">
+                        </div>
 
                         <button
-                          class="button button-default btn emoji-tab-popover-button"
+                          class="button button-default btn"
                           type="button"
                           @click="downloadRemotePack">
                           {{ $t('admin_dash.emoji.download') }}
@@ -238,64 +213,148 @@
           </ul>
         </div>
 
-        <h2>{{ $t('admin_dash.emoji.files') }}</h2>
+        <ul class="setting-list">
+          <h3>
+            {{ $t('admin_dash.emoji.files') }}
 
-        <div class="emoji-list" v-if="pack">
-          <Popover
-            v-for="(file, shortcode) in pack.files" :key="shortcode"
-            trigger="click"
-            :disabled="pack.remote !== undefined"
-            placement="top"
-            :class="{'emoji-unsaved': editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined}"
-            popover-class="emoji-tab-edit-popover popover-default"
-            bound-to-selector=".emoji-list"
-            :bound-to="{ x: 'container' }"
-            :offset="{ y: 5 }"
-            @show="editEmoji(shortcode)"
-          >
-            <template #content>
-              <h3>
-                {{ $t('admin_dash.emoji.editing', [shortcode]) }}
-              </h3>
-              <div v-if="editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined">
-                <input class="emoji-data-input"
-                  v-model="editedParts[packName][shortcode].shortcode">
-                <input class="emoji-data-input"
-                  v-model="editedParts[packName][shortcode].file">
+            <ModifiedIndicator v-if="pack"
+              :changed="editedParts[packName] && Object.keys(editedParts[packName]).length > 0"
+              message-key="admin_dash.emoji.emoji_changed"/>
+          </h3>
 
-                <button
-                  class="button button-default btn emoji-tab-popover-button"
-                  type="button"
-                  @click="saveEditedEmoji(shortcode)">
-                  {{ $t('admin_dash.emoji.save') }}
-                </button>
-                <button
-                  class="button button-default btn emoji-tab-popover-button"
-                  type="button"
-                  @click="editedParts[packName][shortcode].deleteModalVisible = true">
-                  {{ $t('admin_dash.emoji.delete') }}
-                </button>
-                <ConfirmModal
-                  v-if="editedParts[packName][shortcode].deleteModalVisible"
-                  :title="$t('admin_dash.emoji.delete_title')"
-                  :cancel-text="$t('status.delete_confirm_cancel_button')"
-                  :confirm-text="$t('status.delete_confirm_accept_button')"
-                  @cancelled="editedParts[packName][shortcode].deleteModalVisible = false"
-                  @accepted="deleteEmoji(shortcode)" >
-                  {{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
-                </ConfirmModal>
-              </div>
-            </template>
-            <template #trigger>
-              <StillImage
-                class="emoji"
-                :src="emojiAddr(file)"
-                :title="`:${shortcode}:`"
-                :alt="`:${shortcode}:`"
-              />
-            </template>
-          </Popover>
-        </div>
+          <div class="emoji-list" v-if="pack">
+            <Popover
+              v-if="pack.remote === undefined"
+              ref="addEmojiPopover"
+              trigger="click"
+              placement="bottom"
+              bound-to-selector=".emoji-tab"
+              popover-class="emoji-tab-edit-popover popover-default"
+              :bound-to="{ x: 'container' }"
+              :offset="{ y: 5 }"
+            >
+              <template #trigger>
+                <FAIcon icon="plus" size="2x" :title="$t('admin_dash.emoji.add_file')" />
+              </template>
+              <template #content>
+                <h3>
+                  {{ $t('admin_dash.emoji.adding_new') }}
+                </h3>
+
+                <StillImage
+                  class="emoji" v-if="newEmojiUploadPreview"
+                  :src="newEmojiUploadPreview"
+                />
+                <div v-else class="emoji"></div>
+
+                <div class="emoji-tab-popover-input">
+                  <input
+                    type="file"
+                    accept="image/*"
+                    class="emoji-tab-popover-file"
+                    @change="newEmojiUpload.upload = $event.target.files"
+                  >
+                </div>
+                <div>
+                  <div class="emoji-tab-popover-input">
+                    <div>{{ $t('admin_dash.emoji.shortcode') }}</div>
+                    <input class="emoji-data-input"
+                      v-model="newEmojiUpload.shortcode"
+                      :placeholder="$t('admin_dash.emoji.new_shortcode')">
+                  </div>
+
+                  <div class="emoji-tab-popover-input">
+                    <div>{{ $t('admin_dash.emoji.filename') }}</div>
+                    <input class="emoji-data-input"
+                      v-model="newEmojiUpload.file"
+                      :placeholder="$t('admin_dash.emoji.new_filename')">
+                  </div>
+
+                  <button
+                    class="button button-default btn"
+                    type="button"
+                    :disabled="this.newEmojiUpload.upload.length == 0"
+                    @click="uploadEmoji">
+                    {{ $t('admin_dash.emoji.save') }}
+                  </button>
+                </div>
+              </template>
+            </Popover>
+
+            <Popover
+              v-for="(file, shortcode) in pack.files" :key="shortcode"
+              trigger="click"
+              :disabled="pack.remote !== undefined"
+              placement="top"
+              :class="{'emoji-unsaved': editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined}"
+              popover-class="emoji-tab-edit-popover popover-default"
+              bound-to-selector=".emoji-list"
+              :bound-to="{ x: 'container' }"
+              :offset="{ y: 5 }"
+              @show="editEmoji(shortcode)"
+              @close="closedEditedEmoji(shortcode)"
+            >
+              <template #content>
+                <h3>
+                  {{ $t('admin_dash.emoji.editing', [shortcode]) }}
+                </h3>
+
+                <StillImage class="emoji" :src="emojiAddr(file)" />
+
+                <div v-if="editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined">
+                  <div class="emoji-tab-popover-input">
+                    <label for="emoji-edit-shortcode">{{ $t('admin_dash.emoji.shortcode') }}</label>
+                    <input id="emoji-edit-shortcode" class="emoji-data-input"
+                      v-model="editedParts[packName][shortcode].shortcode">
+                  </div>
+                  <div class="emoji-tab-popover-input">
+                    <label for="emoji-edit-filename">{{ $t('admin_dash.emoji.filename') }}</label>
+                    <input id="emoji-edit-filename" class="emoji-data-input"
+                      v-model="editedParts[packName][shortcode].file">
+                  </div>
+
+                  <div>
+                    <button
+                      class="button button-default btn emoji-tab-popover-button"
+                      type="button"
+                      @click="saveEditedEmoji(shortcode)">
+                      {{ $t('admin_dash.emoji.save') }}
+                    </button>
+                    <button
+                      class="button button-default btn emoji-tab-popover-button"
+                      type="button"
+                      @click="editedParts[packName][shortcode].deleteModalVisible = true">
+                      {{ $t('admin_dash.emoji.delete') }}
+                    </button>
+                    <button
+                      class="button button-default btn emoji-tab-popover-button"
+                      type="button"
+                      @click="revertEmoji(shortcode)">
+                      {{ $t('admin_dash.emoji.revert') }}
+                    </button>
+                    <ConfirmModal
+                      v-if="editedParts[packName][shortcode].deleteModalVisible"
+                      :title="$t('admin_dash.emoji.delete_title')"
+                      :cancel-text="$t('status.delete_confirm_cancel_button')"
+                      :confirm-text="$t('status.delete_confirm_accept_button')"
+                      @cancelled="editedParts[packName][shortcode].deleteModalVisible = false"
+                      @accepted="deleteEmoji(shortcode)" >
+                      {{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
+                    </ConfirmModal>
+                  </div>
+                </div>
+              </template>
+              <template #trigger>
+                <StillImage
+                  class="emoji"
+                  :src="emojiAddr(file)"
+                  :title="`:${shortcode}:`"
+                  :alt="`:${shortcode}:`"
+                />
+              </template>
+            </Popover>
+          </div>
+        </ul>
       </div>
     </div>
   </div>
diff --git a/src/components/settings_modal/helpers/modified_indicator.vue b/src/components/settings_modal/helpers/modified_indicator.vue
index 45db3fc2..a747cebd 100644
--- a/src/components/settings_modal/helpers/modified_indicator.vue
+++ b/src/components/settings_modal/helpers/modified_indicator.vue
@@ -15,7 +15,7 @@
       </template>
       <template #content>
         <div class="modified-tooltip">
-          {{ $t('settings.setting_changed') }}
+          {{ $t(messageKey) }}
         </div>
       </template>
     </Popover>
@@ -33,7 +33,13 @@ library.add(
 
 export default {
   components: { Popover },
-  props: ['changed']
+  props: {
+    changed: Boolean,
+    messageKey: {
+      type: String,
+      default: 'settings.setting_changed'
+    }
+  }
 }
 </script>
 
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 6d3b52bb..8378613d 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -934,6 +934,7 @@
       "success_installing_frontend": "Frontend {version} successfully installed"
     },
     "emoji": {
+      "global_actions": "Global actions",
       "reload": "Reload emoji",
       "importFS": "Import emoji from filesystem",
       "error": "Error: {0}",
@@ -941,6 +942,7 @@
       "delete_pack": "Delete pack",
       "new_pack_name": "New pack name",
       "create": "Create",
+      "emoji_packs": "Emoji packs",
       "remote_packs": "Remote packs",
       "do_list": "List",
       "remote_pack_instance": "Remote pack instance",
@@ -951,19 +953,27 @@
       "fallback_sha256": "Fallback SHA256",
       "share": "Share",
       "save": "Save",
+      "save_meta": "Save metadata",
+      "revert_meta": "Revert metadata",
       "delete": "Delete",
+      "revert": "Revert",
       "add_file": "Add file",
       "adding_new": "Adding new emoji",
+      "shortcode": "Shortcode",
+      "filename": "Filename",
       "new_shortcode": "Shortcode, leave blank to infer",
       "new_filename": "Filename, leave blank to infer",
       "delete_confirm": "Are you sure you want to delete {0}?",
       "download_pack": "Download pack",
       "downloading_pack": "Downloading {0}",
       "download": "Download",
-      "download_as_name": "New name, leave blank to reuse",
+      "download_as_name": "New name",
+      "download_as_name_full": "New name, leave blank to reuse",
       "files": "Files",
       "editing": "Editing {0}",
-      "delete_title": "Delete?"
+      "delete_title": "Delete?",
+      "metadata_changed": "Metadata different from saved",
+      "emoji_changed": "Unsaved emoji file changes, check highlighted emoji"
     },
     "temp_overrides": {
       ":pleroma": {
-- 
cgit v1.2.3-70-g09d2


From aa4983835587e20a7ad0ce45e7c706a35d045774 Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@kotobank.ch>
Date: Fri, 19 Jan 2024 19:24:36 +0300
Subject: Adjust h-element levels to be smaller

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

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index 729496a2..6b4268bc 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -7,7 +7,7 @@
       <h2>{{ $t('admin_dash.tabs.emoji') }}</h2>
 
       <ul class="setting-list">
-        <h2>{{ $t('admin_dash.emoji.global_actions') }}</h2>
+        <h3>{{ $t('admin_dash.emoji.global_actions') }}</h3>
 
         <li class="btn-group setting-item">
           <button
@@ -56,7 +56,7 @@
           </button>
         </li>
 
-        <h2>{{ $t('admin_dash.emoji.emoji_packs') }}</h2>
+        <h3>{{ $t('admin_dash.emoji.emoji_packs') }}</h3>
 
         <li>
           <Select class="form-control" v-model="packName">
@@ -214,13 +214,13 @@
         </div>
 
         <ul class="setting-list">
-          <h3>
+          <h4>
             {{ $t('admin_dash.emoji.files') }}
 
             <ModifiedIndicator v-if="pack"
               :changed="editedParts[packName] && Object.keys(editedParts[packName]).length > 0"
               message-key="admin_dash.emoji.emoji_changed"/>
-          </h3>
+          </h4>
 
           <div class="emoji-list" v-if="pack">
             <Popover
-- 
cgit v1.2.3-70-g09d2


From 8a030d935bda6fdf91015d6001d985919b022394 Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@kotobank.ch>
Date: Fri, 19 Jan 2024 23:16:21 +0300
Subject: Separate emoji editing and upload into a separate component

Handle all state in that component
---
 .../settings_modal/admin_tabs/emoji_tab.js         | 108 ++---------
 .../settings_modal/admin_tabs/emoji_tab.scss       |  11 --
 .../settings_modal/admin_tabs/emoji_tab.vue        | 160 ++++------------
 .../helpers/emoji_editing_popover.vue              | 207 +++++++++++++++++++++
 4 files changed, 253 insertions(+), 233 deletions(-)
 create mode 100644 src/components/settings_modal/helpers/emoji_editing_popover.vue

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
index 940f0c6c..58f3ceae 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.js
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -7,8 +7,7 @@ import Select from 'components/select/select.vue'
 import Popover from 'components/popover/popover.vue'
 import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
 import ModifiedIndicator from '../helpers/modified_indicator.vue'
-
-const newEmojiUploadBase = { shortcode: '', file: '', upload: [] }
+import EmojiEditingPopover from '../helpers/emoji_editing_popover.vue'
 
 const EmojiTab = {
   components: {
@@ -19,24 +18,31 @@ const EmojiTab = {
     Select,
     Popover,
     ConfirmModal,
-    ModifiedIndicator
+    ModifiedIndicator,
+    EmojiEditingPopover
   },
 
   data () {
     return {
       knownLocalPacks: { },
       knownRemotePacks: { },
-      editedParts: { },
       editedMetadata: { },
       packName: '',
       newPackName: '',
       deleteModalVisible: false,
-      newEmojiUpload: clone(newEmojiUploadBase),
       remotePackInstance: '',
       remotePackDownloadAs: ''
     }
   },
 
+  provide () {
+    return {
+      // Functions
+      emojiAddr: this.emojiAddr,
+      displayError: this.displayError
+    }
+  },
+
   computed: {
     pack () {
       return this.packName !== '' ? this.knownPacks[this.packName] : undefined
@@ -58,11 +64,6 @@ const EmojiTab = {
       }
 
       return result
-    },
-    newEmojiUploadPreview () {
-      if (this.newEmojiUpload.upload.length > 0) {
-        return URL.createObjectURL(this.newEmojiUpload.upload[0])
-      }
     }
   },
 
@@ -82,27 +83,6 @@ const EmojiTab = {
       }
     },
 
-    uploadEmoji () {
-      this.$refs.addEmojiPopover.hidePopover()
-
-      this.$store.state.api.backendInteractor.addNewEmojiFile({
-        packName: this.packName,
-        file: this.newEmojiUpload.upload[0],
-        shortcode: this.newEmojiUpload.shortcode,
-        filename: this.newEmojiUpload.file
-      }).then(resp => resp.json()).then(resp => {
-        if (resp.error !== undefined) {
-          this.displayError(resp.error)
-          return
-        }
-
-        this.pack.files = resp
-        this.sortPackFiles(this.packName)
-
-        this.newEmojiUpload = clone(newEmojiUploadBase)
-      })
-    },
-
     createEmojiPack () {
       this.$store.state.api.backendInteractor.createEmojiPack(
         { name: this.newPackName }
@@ -132,7 +112,6 @@ const EmojiTab = {
         }
       }).then(done => {
         delete this.editedMetadata[this.packName]
-        delete this.editedParts[this.packName]
 
         this.deleteModalVisible = false
         this.packName = ''
@@ -162,68 +141,9 @@ const EmojiTab = {
       })
     },
 
-    revertEmoji (shortcode) {
-      // Delete current changes and overwrite them with defaults. If the window is closed, they'll get cleared anyway
-      delete this.editedParts[this.packName][shortcode]
-      this.editEmoji(shortcode)
-    },
-    editEmoji (shortcode) {
-      if (this.editedParts[this.packName] === undefined) { this.editedParts[this.packName] = {} }
-
-      if (this.editedParts[this.packName][shortcode] === undefined) {
-        this.editedParts[this.packName][shortcode] = {
-          shortcode, file: this.pack.files[shortcode]
-        }
-      }
-    },
-    deleteEmoji (shortcode) {
-      this.editedParts[this.packName][shortcode].deleteModalVisible = false
-
-      this.$store.state.api.backendInteractor.deleteEmojiFile(
-        { packName: this.packName, shortcode }
-      ).then(resp => resp.json()).then(resp => {
-        if (resp.error !== undefined) {
-          this.displayError(resp.error)
-          return
-        }
-
-        this.pack.files = resp
-        delete this.editedParts[this.packName][shortcode]
-
-        this.sortPackFiles(this.packName)
-      })
-    },
-    closedEditedEmoji (shortcode) {
-      const edited = this.editedParts[this.packName][shortcode]
-
-      if (edited.shortcode === shortcode && edited.file === this.pack.files[shortcode]) {
-        delete this.editedParts[this.packName][shortcode]
-
-        return true
-      }
-
-      return false
-    },
-    saveEditedEmoji (shortcode) {
-      if (this.closedEditedEmoji(shortcode)) return
-
-      const edited = this.editedParts[this.packName][shortcode]
-
-      this.$store.state.api.backendInteractor.updateEmojiFile(
-        { packName: this.packName, shortcode, newShortcode: edited.shortcode, newFilename: edited.file, force: false }
-      ).then(resp => {
-        if (resp.error !== undefined) {
-          this.displayError(resp.error)
-          return Promise.reject(resp.error)
-        }
-
-        return resp.json()
-      }).then(resp => {
-        this.pack.files = resp
-        delete this.editedParts[this.packName][shortcode]
-
-        this.sortPackFiles(this.packName)
-      })
+    updatePackFiles (newFiles) {
+      this.pack.files = newFiles
+      this.sortPackFiles(this.packName)
     },
 
     loadPacksPaginated (listFunction) {
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
index 71f46cc2..68711aee 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.scss
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -39,17 +39,6 @@
   margin-left: 0.5em;
 }
 
-.emoji-tab-edit-popover {
-  padding-left: 0.5em;
-  padding-right: 0.5em;
-  padding-bottom: 0.5em;
-
-  .emoji {
-    width: 32px;
-    height: 32px;
-  }
-}
-
 .emoji-tab-popover-input {
   margin-bottom: 0.5em;
 
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index 6b4268bc..dcb8e843 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -97,38 +97,44 @@
         </li>
       </ul>
 
-      <div v-if="packName !== ''">
+      <div v-if="pack">
         <div class="pack-info-wrapper">
           <ul class="setting-list">
             <li>
-              <div>
+              <label>
                 {{ $t('admin_dash.emoji.description') }}
                 <ModifiedIndicator :changed="metaEdited('description')" message-key="admin_dash.emoji.metadata_changed" />
-              </div>
-              <textarea
-                v-model="packMeta.description"
-                :disabled="pack.remote !== undefined"
-                class="bio resize-height" />
+
+                <textarea
+                  v-model="packMeta.description"
+                  :disabled="pack.remote !== undefined"
+                  class="bio resize-height" />
+              </label>
             </li>
             <li>
-              <div>
+              <label>
                 {{ $t('admin_dash.emoji.homepage') }}
                 <ModifiedIndicator :changed="metaEdited('homepage')" message-key="admin_dash.emoji.metadata_changed" />
-              </div>
+
               <input
                 class="emoji-info-input" v-model="packMeta.homepage"
                 :disabled="pack.remote !== undefined">
+              </label>
             </li>
             <li>
-              <div>
+              <label>
                 {{ $t('admin_dash.emoji.fallback_src') }}
                 <ModifiedIndicator :changed="metaEdited('fallback-src')" message-key="admin_dash.emoji.metadata_changed" />
-              </div>
-              <input class="emoji-info-input" v-model="packMeta['fallback-src']" :disabled="pack.remote !== undefined">
+
+                <input class="emoji-info-input" v-model="packMeta['fallback-src']" :disabled="pack.remote !== undefined">
+              </label>
             </li>
             <li>
-              <div>{{ $t('admin_dash.emoji.fallback_sha256') }}</div>
-              <input :disabled="true" class="emoji-info-input" v-model="packMeta['fallback-src-sha256']">
+              <label>
+                {{ $t('admin_dash.emoji.fallback_sha256') }}
+
+                <input :disabled="true" class="emoji-info-input" v-model="packMeta['fallback-src-sha256']">
+              </label>
             </li>
             <li>
               <Checkbox :disabled="pack.remote !== undefined" v-model="packMeta['share-files']">
@@ -218,132 +224,30 @@
             {{ $t('admin_dash.emoji.files') }}
 
             <ModifiedIndicator v-if="pack"
-              :changed="editedParts[packName] && Object.keys(editedParts[packName]).length > 0"
+              :changed="$refs.emojiPopovers && $refs.emojiPopovers.some(p => p.isEdited)"
               message-key="admin_dash.emoji.emoji_changed"/>
           </h4>
 
           <div class="emoji-list" v-if="pack">
-            <Popover
+            <EmojiEditingPopover
               v-if="pack.remote === undefined"
-              ref="addEmojiPopover"
-              trigger="click"
-              placement="bottom"
-              bound-to-selector=".emoji-tab"
-              popover-class="emoji-tab-edit-popover popover-default"
-              :bound-to="{ x: 'container' }"
-              :offset="{ y: 5 }"
+              placement="bottom" new-upload
+              :title="$t('admin_dash.emoji.adding_new')"
+              :packName="packName" @updatePackFiles="updatePackFiles"
             >
               <template #trigger>
                 <FAIcon icon="plus" size="2x" :title="$t('admin_dash.emoji.add_file')" />
               </template>
-              <template #content>
-                <h3>
-                  {{ $t('admin_dash.emoji.adding_new') }}
-                </h3>
-
-                <StillImage
-                  class="emoji" v-if="newEmojiUploadPreview"
-                  :src="newEmojiUploadPreview"
-                />
-                <div v-else class="emoji"></div>
-
-                <div class="emoji-tab-popover-input">
-                  <input
-                    type="file"
-                    accept="image/*"
-                    class="emoji-tab-popover-file"
-                    @change="newEmojiUpload.upload = $event.target.files"
-                  >
-                </div>
-                <div>
-                  <div class="emoji-tab-popover-input">
-                    <div>{{ $t('admin_dash.emoji.shortcode') }}</div>
-                    <input class="emoji-data-input"
-                      v-model="newEmojiUpload.shortcode"
-                      :placeholder="$t('admin_dash.emoji.new_shortcode')">
-                  </div>
+            </EmojiEditingPopover>
 
-                  <div class="emoji-tab-popover-input">
-                    <div>{{ $t('admin_dash.emoji.filename') }}</div>
-                    <input class="emoji-data-input"
-                      v-model="newEmojiUpload.file"
-                      :placeholder="$t('admin_dash.emoji.new_filename')">
-                  </div>
-
-                  <button
-                    class="button button-default btn"
-                    type="button"
-                    :disabled="this.newEmojiUpload.upload.length == 0"
-                    @click="uploadEmoji">
-                    {{ $t('admin_dash.emoji.save') }}
-                  </button>
-                </div>
-              </template>
-            </Popover>
-
-            <Popover
+            <EmojiEditingPopover
+              placement="top" ref="emojiPopovers"
               v-for="(file, shortcode) in pack.files" :key="shortcode"
-              trigger="click"
+              :title="$t('admin_dash.emoji.editing', [shortcode])"
               :disabled="pack.remote !== undefined"
-              placement="top"
-              :class="{'emoji-unsaved': editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined}"
-              popover-class="emoji-tab-edit-popover popover-default"
-              bound-to-selector=".emoji-list"
-              :bound-to="{ x: 'container' }"
-              :offset="{ y: 5 }"
-              @show="editEmoji(shortcode)"
-              @close="closedEditedEmoji(shortcode)"
+              :shortcode="shortcode" :file="file" :packName="packName"
+              @updatePackFiles="updatePackFiles"
             >
-              <template #content>
-                <h3>
-                  {{ $t('admin_dash.emoji.editing', [shortcode]) }}
-                </h3>
-
-                <StillImage class="emoji" :src="emojiAddr(file)" />
-
-                <div v-if="editedParts[packName] !== undefined && editedParts[packName][shortcode] !== undefined">
-                  <div class="emoji-tab-popover-input">
-                    <label for="emoji-edit-shortcode">{{ $t('admin_dash.emoji.shortcode') }}</label>
-                    <input id="emoji-edit-shortcode" class="emoji-data-input"
-                      v-model="editedParts[packName][shortcode].shortcode">
-                  </div>
-                  <div class="emoji-tab-popover-input">
-                    <label for="emoji-edit-filename">{{ $t('admin_dash.emoji.filename') }}</label>
-                    <input id="emoji-edit-filename" class="emoji-data-input"
-                      v-model="editedParts[packName][shortcode].file">
-                  </div>
-
-                  <div>
-                    <button
-                      class="button button-default btn emoji-tab-popover-button"
-                      type="button"
-                      @click="saveEditedEmoji(shortcode)">
-                      {{ $t('admin_dash.emoji.save') }}
-                    </button>
-                    <button
-                      class="button button-default btn emoji-tab-popover-button"
-                      type="button"
-                      @click="editedParts[packName][shortcode].deleteModalVisible = true">
-                      {{ $t('admin_dash.emoji.delete') }}
-                    </button>
-                    <button
-                      class="button button-default btn emoji-tab-popover-button"
-                      type="button"
-                      @click="revertEmoji(shortcode)">
-                      {{ $t('admin_dash.emoji.revert') }}
-                    </button>
-                    <ConfirmModal
-                      v-if="editedParts[packName][shortcode].deleteModalVisible"
-                      :title="$t('admin_dash.emoji.delete_title')"
-                      :cancel-text="$t('status.delete_confirm_cancel_button')"
-                      :confirm-text="$t('status.delete_confirm_accept_button')"
-                      @cancelled="editedParts[packName][shortcode].deleteModalVisible = false"
-                      @accepted="deleteEmoji(shortcode)" >
-                      {{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
-                    </ConfirmModal>
-                  </div>
-                </div>
-              </template>
               <template #trigger>
                 <StillImage
                   class="emoji"
@@ -352,7 +256,7 @@
                   :alt="`:${shortcode}:`"
                 />
               </template>
-            </Popover>
+            </EmojiEditingPopover>
           </div>
         </ul>
       </div>
diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
new file mode 100644
index 00000000..ec426be8
--- /dev/null
+++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue
@@ -0,0 +1,207 @@
+<template>
+  <Popover
+    trigger="click"
+    :placement="placement"
+    bound-to-selector=".emoji-list"
+    popover-class="emoji-tab-edit-popover popover-default"
+    ref="emojiPopover"
+    :bound-to="{ x: 'container' }"
+    :offset="{ y: 5 }"
+    :disabled="disabled"
+    :class="{'emoji-unsaved': isEdited}"
+  >
+    <template #trigger>
+      <slot name="trigger" />
+    </template>
+    <template #content>
+      <h3>
+        {{ title }}
+      </h3>
+
+      <StillImage class="emoji" v-if="emojiPreview" :src="emojiPreview" />
+      <div v-else class="emoji"></div>
+
+      <div class="emoji-tab-popover-input" v-if="newUpload">
+        <input
+          type="file"
+          accept="image/*"
+          class="emoji-tab-popover-file"
+          @change="uploadFile = $event.target.files">
+      </div>
+      <div>
+        <div class="emoji-tab-popover-input">
+          <label>
+            {{ $t('admin_dash.emoji.shortcode') }}
+            <input class="emoji-data-input"
+              v-model="editedShortcode"
+              :placeholder="$t('admin_dash.emoji.new_shortcode')">
+          </label>
+        </div>
+
+        <div class="emoji-tab-popover-input">
+          <label>
+            {{ $t('admin_dash.emoji.filename') }}
+
+            <input class="emoji-data-input"
+              v-model="editedFile"
+              :placeholder="$t('admin_dash.emoji.new_filename')">
+          </label>
+        </div>
+
+        <button
+          class="button button-default btn"
+          type="button"
+          :disabled="uploadFile.length == 0"
+          @click="newUpload ? uploadEmoji() : saveEditedEmoji()">
+          {{ $t('admin_dash.emoji.save') }}
+        </button>
+
+        <template v-if="!newUpload">
+          <button
+            class="button button-default btn emoji-tab-popover-button"
+            type="button"
+            @click="deleteModalVisible = true">
+            {{ $t('admin_dash.emoji.delete') }}
+          </button>
+          <button
+            class="button button-default btn emoji-tab-popover-button"
+            type="button"
+            @click="revertEmoji">
+            {{ $t('admin_dash.emoji.revert') }}
+          </button>
+          <ConfirmModal
+            v-if="deleteModalVisible"
+            :title="$t('admin_dash.emoji.delete_title')"
+            :cancel-text="$t('status.delete_confirm_cancel_button')"
+            :confirm-text="$t('status.delete_confirm_accept_button')"
+            @cancelled="deleteModalVisible = false"
+            @accepted="deleteEmoji" >
+            {{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
+          </ConfirmModal>
+        </template>
+      </div>
+    </template>
+  </Popover>
+</template>
+
+<script>
+import Popover from 'components/popover/popover.vue'
+import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
+import StillImage from 'components/still-image/still-image.vue'
+
+export default {
+  components: { Popover, ConfirmModal, StillImage },
+  data () {
+    return {
+      uploadFile: [],
+      editedShortcode: this.shortcode,
+      editedFile: this.file,
+      deleteModalVisible: false
+    }
+  },
+  computed: {
+    emojiPreview () {
+      if (this.newUpload && this.uploadFile.length > 0) {
+        return URL.createObjectURL(this.uploadFile[0])
+      } else if (!this.newUpload) {
+        return this.emojiAddr(this.file)
+      }
+
+      return null
+    },
+    isEdited () {
+      return !this.newUpload && (this.editedShortcode !== this.shortcode || this.editedFile !== this.file)
+    }
+  },
+  inject: ['emojiAddr', 'displayError'],
+  methods: {
+    saveEditedEmoji () {
+      if (!this.isEdited) return
+
+      this.$store.state.api.backendInteractor.updateEmojiFile(
+        { packName: this.packName, shortcode: this.shortcode, newShortcode: this.editedShortcode, newFilename: this.editedFile, force: false }
+      ).then(resp => {
+        if (resp.error !== undefined) {
+          this.displayError(resp.error)
+          return Promise.reject(resp.error)
+        }
+
+        return resp.json()
+      }).then(resp => this.$emit('updatePackFiles', resp))
+    },
+    uploadEmoji () {
+      this.$store.state.api.backendInteractor.addNewEmojiFile({
+        packName: this.packName,
+        file: this.uploadFile[0],
+        shortcode: this.editedShortcode,
+        filename: this.editedFile
+      }).then(resp => resp.json()).then(resp => {
+        if (resp.error !== undefined) {
+          this.displayError(resp.error)
+          return
+        }
+
+        this.$emit('updatePackFiles', resp)
+        this.$refs.emojiPopover.hidePopover()
+
+        this.editedFile = ''
+        this.editedShortcode = ''
+        this.uploadFile = []
+      })
+    },
+    revertEmoji () {
+      this.editedFile = this.file
+      this.editedShortcode = this.shortcode
+    },
+    deleteEmoji () {
+      this.deleteModalVisible = false
+
+      this.$store.state.api.backendInteractor.deleteEmojiFile(
+        { packName: this.packName, shortcode: this.editedShortcode }
+      ).then(resp => resp.json()).then(resp => {
+        if (resp.error !== undefined) {
+          this.displayError(resp.error)
+          return
+        }
+
+        this.$emit('updatePackFiles', resp)
+      })
+    }
+  },
+  props: {
+    placement: String,
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+
+    newUpload: Boolean,
+
+    title: String,
+    packName: String,
+    shortcode: {
+      type: String,
+      // Only exists when this is not a new upload
+      default: ''
+    },
+    file: {
+      type: String,
+      // Only exists when this is not a new upload
+      default: ''
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+  .emoji-tab-edit-popover {
+    padding-left: 0.5em;
+    padding-right: 0.5em;
+    padding-bottom: 0.5em;
+
+    .emoji {
+      width: 32px;
+      height: 32px;
+    }
+  }
+</style>
-- 
cgit v1.2.3-70-g09d2


From 4d7a9151542da6298a68bf577ad61acf6feb339b Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@kotobank.ch>
Date: Mon, 22 Jan 2024 23:14:49 +0300
Subject: displayError event, 'emits', "Edit pack" label

---
 src/components/settings_modal/admin_tabs/emoji_tab.js           | 6 +-----
 src/components/settings_modal/admin_tabs/emoji_tab.vue          | 7 +++++--
 src/components/settings_modal/helpers/emoji_editing_popover.vue | 9 +++++----
 src/i18n/en.json                                                | 1 +
 4 files changed, 12 insertions(+), 11 deletions(-)

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
index 58f3ceae..a97cfd7a 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.js
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -36,11 +36,7 @@ const EmojiTab = {
   },
 
   provide () {
-    return {
-      // Functions
-      emojiAddr: this.emojiAddr,
-      displayError: this.displayError
-    }
+    return { emojiAddr: this.emojiAddr }
   },
 
   computed: {
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index dcb8e843..f2dc343a 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -59,6 +59,8 @@
         <h3>{{ $t('admin_dash.emoji.emoji_packs') }}</h3>
 
         <li>
+          <h4>{{ $t('admin_dash.emoji.edit_pack') }}</h4>
+
           <Select class="form-control" v-model="packName">
             <option value="" disabled hidden>{{ $t('admin_dash.emoji.emoji_pack') }}</option>
             <option v-for="(pack, listPackName) in knownPacks" :label="listPackName" :key="listPackName">
@@ -233,7 +235,8 @@
               v-if="pack.remote === undefined"
               placement="bottom" new-upload
               :title="$t('admin_dash.emoji.adding_new')"
-              :packName="packName" @updatePackFiles="updatePackFiles"
+              :packName="packName"
+              @updatePackFiles="updatePackFiles" @displayError="displayError"
             >
               <template #trigger>
                 <FAIcon icon="plus" size="2x" :title="$t('admin_dash.emoji.add_file')" />
@@ -246,7 +249,7 @@
               :title="$t('admin_dash.emoji.editing', [shortcode])"
               :disabled="pack.remote !== undefined"
               :shortcode="shortcode" :file="file" :packName="packName"
-              @updatePackFiles="updatePackFiles"
+              @updatePackFiles="updatePackFiles" @displayError="displayError"
             >
               <template #trigger>
                 <StillImage
diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
index ec426be8..12f7a4f8 100644
--- a/src/components/settings_modal/helpers/emoji_editing_popover.vue
+++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue
@@ -113,7 +113,7 @@ export default {
       return !this.newUpload && (this.editedShortcode !== this.shortcode || this.editedFile !== this.file)
     }
   },
-  inject: ['emojiAddr', 'displayError'],
+  inject: ['emojiAddr'],
   methods: {
     saveEditedEmoji () {
       if (!this.isEdited) return
@@ -122,7 +122,7 @@ export default {
         { packName: this.packName, shortcode: this.shortcode, newShortcode: this.editedShortcode, newFilename: this.editedFile, force: false }
       ).then(resp => {
         if (resp.error !== undefined) {
-          this.displayError(resp.error)
+          this.$emit('displayError', resp.error)
           return Promise.reject(resp.error)
         }
 
@@ -137,7 +137,7 @@ export default {
         filename: this.editedFile
       }).then(resp => resp.json()).then(resp => {
         if (resp.error !== undefined) {
-          this.displayError(resp.error)
+          this.$emit('displayError', resp.error)
           return
         }
 
@@ -160,7 +160,7 @@ export default {
         { packName: this.packName, shortcode: this.editedShortcode }
       ).then(resp => resp.json()).then(resp => {
         if (resp.error !== undefined) {
-          this.displayError(resp.error)
+          this.$emit('displayError', resp.error)
           return
         }
 
@@ -168,6 +168,7 @@ export default {
       })
     }
   },
+  emits: ['updatePackFiles', 'displaError'],
   props: {
     placement: String,
     disabled: {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 8378613d..4c6fe376 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -947,6 +947,7 @@
       "do_list": "List",
       "remote_pack_instance": "Remote pack instance",
       "emoji_pack": "Emoji pack",
+      "edit_pack": "Edit pack",
       "description": "Description",
       "homepage": "Homepage",
       "fallback_src": "Fallback source",
-- 
cgit v1.2.3-70-g09d2


From d5a5b8e25430e44690c51e56ad7f862ce363e458 Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@kotobank.ch>
Date: Mon, 22 Jan 2024 23:43:20 +0300
Subject: Fix typo, add warning on replacing local pack

---
 src/components/settings_modal/admin_tabs/emoji_tab.js      |  4 ++++
 src/components/settings_modal/admin_tabs/emoji_tab.scss    |  4 ++++
 src/components/settings_modal/admin_tabs/emoji_tab.vue     | 14 ++++++++++----
 .../settings_modal/helpers/emoji_editing_popover.vue       |  2 +-
 src/i18n/en.json                                           |  3 ++-
 5 files changed, 21 insertions(+), 6 deletions(-)

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
index a97cfd7a..58e1468f 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.js
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -60,6 +60,10 @@ const EmojiTab = {
       }
 
       return result
+    },
+    downloadWillReplaceLocal () {
+      return (this.remotePackDownloadAs.trim() === '' && this.pack.remote && this.pack.remote.baseName in this.knownLocalPacks) ||
+             (this.remotePackDownloadAs in this.knownLocalPacks)
     }
   },
 
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
index 68711aee..cc918870 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.scss
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -54,4 +54,8 @@
   .emoji-tab-popover-file {
     padding-top: 3px;
   }
+
+  .warning {
+    color: var(--cOrange, $fallback--cOrange);
+  }
 }
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index f2dc343a..5231f860 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -200,10 +200,16 @@
                     <div>
                       <div>
                         <div class="emoji-tab-popover-input">
-                          <label for="remote-download-as-input">{{ $t('admin_dash.emoji.download_as_name') }}</label>
-                          <input id="remote-download-as-input" class="emoji-data-input"
-                            v-model="remotePackDownloadAs"
-                            :placeholder="$t('admin_dash.emoji.download_as_name_full')">
+                          <label>
+                            {{ $t('admin_dash.emoji.download_as_name') }}
+                            <input class="emoji-data-input"
+                              v-model="remotePackDownloadAs"
+                              :placeholder="$t('admin_dash.emoji.download_as_name_full')">
+                          </label>
+
+                          <div v-if="downloadWillReplaceLocal" class="warning">
+                            <em>{{ $t('admin_dash.emoji.replace_warning') }}</em>
+                          </div>
                         </div>
 
                         <button
diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
index 12f7a4f8..dfece424 100644
--- a/src/components/settings_modal/helpers/emoji_editing_popover.vue
+++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue
@@ -168,7 +168,7 @@ export default {
       })
     }
   },
-  emits: ['updatePackFiles', 'displaError'],
+  emits: ['updatePackFiles', 'displayError'],
   props: {
     placement: String,
     disabled: {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 4c6fe376..9a8f9a44 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -974,7 +974,8 @@
       "editing": "Editing {0}",
       "delete_title": "Delete?",
       "metadata_changed": "Metadata different from saved",
-      "emoji_changed": "Unsaved emoji file changes, check highlighted emoji"
+      "emoji_changed": "Unsaved emoji file changes, check highlighted emoji",
+      "replace_warning": "This will REPLACE the local pack of the same name"
     },
     "temp_overrides": {
       ":pleroma": {
-- 
cgit v1.2.3-70-g09d2


From 7df207c9d479a4641d7695d9d0b83eaa9308b510 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 6 Mar 2024 10:34:56 +0200
Subject: emoji management fixes for themes3

---
 .../settings_modal/admin_tabs/emoji_tab.scss       |  6 ++--
 .../settings_modal/admin_tabs/emoji_tab.vue        | 36 +++++++++++++++++-----
 .../helpers/emoji_editing_popover.vue              | 14 ++++++---
 3 files changed, 39 insertions(+), 17 deletions(-)

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
index cc918870..3e77e019 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.scss
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -1,5 +1,3 @@
-@import "src/variables";
-
 .emoji-tab {
   .btn-group .btn:not(:first-child) {
     margin-left: 0.5em;
@@ -25,7 +23,7 @@
   }
 
   .emoji-unsaved {
-    box-shadow: 0 3px 5px var(--cBlue, $fallback--cBlue);
+    box-shadow: 0 3px 5px var(--cBlue);
   }
 
   .emoji-list {
@@ -56,6 +54,6 @@
   }
 
   .warning {
-    color: var(--cOrange, $fallback--cOrange);
+    color: var(--cOrange);
   }
 }
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index 5231f860..2324dc23 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -43,7 +43,11 @@
               <template #content>
                 <div class="emoji-tab-popover-input">
                   <h3>{{ $t('admin_dash.emoji.remote_pack_instance') }}</h3>
-                  <input v-model="remotePackInstance" :placeholder="$t('admin_dash.emoji.remote_pack_instance')">
+                  <input
+                    v-model="remotePackInstance"
+                    class="input"
+                    :placeholder="$t('admin_dash.emoji.remote_pack_instance')"
+                  >
                   <button
                     class="button button-default btn emoji-tab-popover-button"
                     type="button"
@@ -86,7 +90,11 @@
             <template #content>
               <div class="emoji-tab-popover-input">
                 <h3>{{ $t('admin_dash.emoji.new_pack_name') }}</h3>
-                <input v-model="newPackName" :placeholder="$t('admin_dash.emoji.new_pack_name')">
+                <input
+                  v-model="newPackName"
+                  :placeholder="$t('admin_dash.emoji.new_pack_name')"
+                  class="input"
+                >
                 <button
                   class="button button-default btn emoji-tab-popover-button"
                   type="button"
@@ -110,7 +118,8 @@
                 <textarea
                   v-model="packMeta.description"
                   :disabled="pack.remote !== undefined"
-                  class="bio resize-height" />
+                  class="bio resize-height input"
+                />
               </label>
             </li>
             <li>
@@ -119,7 +128,8 @@
                 <ModifiedIndicator :changed="metaEdited('homepage')" message-key="admin_dash.emoji.metadata_changed" />
 
               <input
-                class="emoji-info-input" v-model="packMeta.homepage"
+                class="emoji-info-input input"
+                v-model="packMeta.homepage"
                 :disabled="pack.remote !== undefined">
               </label>
             </li>
@@ -128,14 +138,22 @@
                 {{ $t('admin_dash.emoji.fallback_src') }}
                 <ModifiedIndicator :changed="metaEdited('fallback-src')" message-key="admin_dash.emoji.metadata_changed" />
 
-                <input class="emoji-info-input" v-model="packMeta['fallback-src']" :disabled="pack.remote !== undefined">
+                <input
+                  class="emoji-info-input input"
+                  v-model="packMeta['fallback-src']"
+                  :disabled="pack.remote !== undefined"
+                >
               </label>
             </li>
             <li>
               <label>
                 {{ $t('admin_dash.emoji.fallback_sha256') }}
 
-                <input :disabled="true" class="emoji-info-input" v-model="packMeta['fallback-src-sha256']">
+                <input
+                  :disabled="true"
+                  class="emoji-info-input input"
+                  v-model="packMeta['fallback-src-sha256']"
+                >
               </label>
             </li>
             <li>
@@ -202,9 +220,11 @@
                         <div class="emoji-tab-popover-input">
                           <label>
                             {{ $t('admin_dash.emoji.download_as_name') }}
-                            <input class="emoji-data-input"
+                            <input
+                              class="emoji-data-input input"
                               v-model="remotePackDownloadAs"
-                              :placeholder="$t('admin_dash.emoji.download_as_name_full')">
+                              :placeholder="$t('admin_dash.emoji.download_as_name_full')"
+                            >
                           </label>
 
                           <div v-if="downloadWillReplaceLocal" class="warning">
diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
index cdd3e403..c0b03310 100644
--- a/src/components/settings_modal/helpers/emoji_editing_popover.vue
+++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue
@@ -25,16 +25,18 @@
         <input
           type="file"
           accept="image/*"
-          class="emoji-tab-popover-file"
+          class="emoji-tab-popover-file input"
           @change="uploadFile = $event.target.files">
       </div>
       <div>
         <div class="emoji-tab-popover-input">
           <label>
             {{ $t('admin_dash.emoji.shortcode') }}
-            <input class="emoji-data-input"
+            <input
+              class="emoji-data-input input"
               v-model="editedShortcode"
-              :placeholder="$t('admin_dash.emoji.new_shortcode')">
+              :placeholder="$t('admin_dash.emoji.new_shortcode')"
+            >
           </label>
         </div>
 
@@ -42,9 +44,11 @@
           <label>
             {{ $t('admin_dash.emoji.filename') }}
 
-            <input class="emoji-data-input"
+            <input
+              class="emoji-data-input input"
               v-model="editedFile"
-              :placeholder="$t('admin_dash.emoji.new_filename')">
+              :placeholder="$t('admin_dash.emoji.new_filename')"
+            >
           </label>
         </div>
 
-- 
cgit v1.2.3-70-g09d2


From afc94c6801bdcc454f98e7fb9deae93dc6e4a739 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 7 Mar 2024 00:38:19 +0200
Subject: lint

---
 .../settings_modal/admin_tabs/emoji_tab.vue        | 146 +++++++++++++++------
 .../settings_modal/admin_tabs/frontends_tab.vue    |  10 +-
 .../helpers/emoji_editing_popover.vue              |  87 +++++++-----
 src/components/user_profile/user_profile.vue       |   2 +-
 4 files changed, 163 insertions(+), 82 deletions(-)

(limited to 'src/components/settings_modal/admin_tabs/emoji_tab.vue')

diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index 2324dc23..5742d2ce 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -13,13 +13,15 @@
           <button
             class="button button-default btn"
             type="button"
-            @click="reloadEmoji">
+            @click="reloadEmoji"
+          >
             {{ $t('admin_dash.emoji.reload') }}
           </button>
           <button
             class="button button-default btn"
             type="button"
-            @click="importFromFS">
+            @click="importFromFS"
+          >
             {{ $t('admin_dash.emoji.importFS') }}
           </button>
         </li>
@@ -28,7 +30,8 @@
           <button
             class="button button-default btn"
             type="button"
-            @click="$refs.remotePackPopover.showPopover">
+            @click="$refs.remotePackPopover.showPopover"
+          >
             {{ $t('admin_dash.emoji.remote_packs') }}
 
             <Popover
@@ -51,7 +54,8 @@
                   <button
                     class="button button-default btn emoji-tab-popover-button"
                     type="button"
-                    @click="listRemotePacks">
+                    @click="listRemotePacks"
+                  >
                     {{ $t('admin_dash.emoji.do_list') }}
                   </button>
                 </div>
@@ -65,9 +69,22 @@
         <li>
           <h4>{{ $t('admin_dash.emoji.edit_pack') }}</h4>
 
-          <Select class="form-control" v-model="packName">
-            <option value="" disabled hidden>{{ $t('admin_dash.emoji.emoji_pack') }}</option>
-            <option v-for="(pack, listPackName) in knownPacks" :label="listPackName" :key="listPackName">
+          <Select
+            v-model="packName"
+            class="form-control"
+          >
+            <option
+              value=""
+              disabled
+              hidden
+            >
+              {{ $t('admin_dash.emoji.emoji_pack') }}
+            </option>
+            <option
+              v-for="(pack, listPackName) in knownPacks"
+              :key="listPackName"
+              :label="listPackName"
+            >
               {{ listPackName }}
             </option>
           </Select>
@@ -75,7 +92,8 @@
           <button
             class="button button-default btn emoji-tab-popover-button"
             type="button"
-            @click="$refs.createPackPopover.showPopover">
+            @click="$refs.createPackPopover.showPopover"
+          >
             {{ $t('admin_dash.emoji.create_pack') }}
           </button>
           <Popover
@@ -98,7 +116,8 @@
                 <button
                   class="button button-default btn emoji-tab-popover-button"
                   type="button"
-                  @click="createEmojiPack">
+                  @click="createEmojiPack"
+                >
                   {{ $t('admin_dash.emoji.create') }}
                 </button>
               </div>
@@ -113,7 +132,10 @@
             <li>
               <label>
                 {{ $t('admin_dash.emoji.description') }}
-                <ModifiedIndicator :changed="metaEdited('description')" message-key="admin_dash.emoji.metadata_changed" />
+                <ModifiedIndicator
+                  :changed="metaEdited('description')"
+                  message-key="admin_dash.emoji.metadata_changed"
+                />
 
                 <textarea
                   v-model="packMeta.description"
@@ -125,22 +147,29 @@
             <li>
               <label>
                 {{ $t('admin_dash.emoji.homepage') }}
-                <ModifiedIndicator :changed="metaEdited('homepage')" message-key="admin_dash.emoji.metadata_changed" />
+                <ModifiedIndicator
+                  :changed="metaEdited('homepage')"
+                  message-key="admin_dash.emoji.metadata_changed"
+                />
 
-              <input
-                class="emoji-info-input input"
-                v-model="packMeta.homepage"
-                :disabled="pack.remote !== undefined">
+                <input
+                  v-model="packMeta.homepage"
+                  class="emoji-info-input input"
+                  :disabled="pack.remote !== undefined"
+                >
               </label>
             </li>
             <li>
               <label>
                 {{ $t('admin_dash.emoji.fallback_src') }}
-                <ModifiedIndicator :changed="metaEdited('fallback-src')" message-key="admin_dash.emoji.metadata_changed" />
+                <ModifiedIndicator
+                  :changed="metaEdited('fallback-src')"
+                  message-key="admin_dash.emoji.metadata_changed"
+                />
 
                 <input
-                  class="emoji-info-input input"
                   v-model="packMeta['fallback-src']"
+                  class="emoji-info-input input"
                   :disabled="pack.remote !== undefined"
                 >
               </label>
@@ -150,40 +179,49 @@
                 {{ $t('admin_dash.emoji.fallback_sha256') }}
 
                 <input
+                  v-model="packMeta['fallback-src-sha256']"
                   :disabled="true"
                   class="emoji-info-input input"
-                  v-model="packMeta['fallback-src-sha256']"
                 >
               </label>
             </li>
             <li>
-              <Checkbox :disabled="pack.remote !== undefined" v-model="packMeta['share-files']">
+              <Checkbox
+                v-model="packMeta['share-files']"
+                :disabled="pack.remote !== undefined"
+              >
                 {{ $t('admin_dash.emoji.share') }}
               </Checkbox>
 
-              <ModifiedIndicator :changed="metaEdited('share-files')" message-key="admin_dash.emoji.metadata_changed" />
+              <ModifiedIndicator
+                :changed="metaEdited('share-files')"
+                message-key="admin_dash.emoji.metadata_changed"
+              />
             </li>
             <li class="btn-group">
               <button
+                v-if="pack.remote === undefined"
                 class="button button-default btn"
                 type="button"
-                v-if="pack.remote === undefined"
-                @click="savePackMetadata">
+                @click="savePackMetadata"
+              >
                 {{ $t('admin_dash.emoji.save_meta') }}
               </button>
               <button
+                v-if="pack.remote === undefined"
                 class="button button-default btn"
                 type="button"
-                v-if="pack.remote === undefined"
-                @click="savePackMetadata">
+                @click="savePackMetadata"
+              >
                 {{ $t('admin_dash.emoji.revert_meta') }}
               </button>
 
               <button
-                class="button button-default btn"
                 v-if="pack.remote === undefined"
+                class="button button-default btn"
                 type="button"
-                @click="deleteModalVisible = true">
+                @click="deleteModalVisible = true"
+              >
                 {{ $t('admin_dash.emoji.delete_pack') }}
 
                 <ConfirmModal
@@ -192,16 +230,18 @@
                   :cancel-text="$t('status.delete_confirm_cancel_button')"
                   :confirm-text="$t('status.delete_confirm_accept_button')"
                   @cancelled="deleteModalVisible = false"
-                  @accepted="deleteEmojiPack" >
+                  @accepted="deleteEmojiPack"
+                >
                   {{ $t('admin_dash.emoji.delete_confirm', [packName]) }}
                 </ConfirmModal>
               </button>
 
               <button
+                v-if="pack.remote !== undefined"
                 class="button button-default btn"
                 type="button"
-                v-if="pack.remote !== undefined"
-                @click="$refs.dlPackPopover.showPopover">
+                @click="$refs.dlPackPopover.showPopover"
+              >
                 {{ $t('admin_dash.emoji.download_pack') }}
 
                 <Popover
@@ -221,13 +261,16 @@
                           <label>
                             {{ $t('admin_dash.emoji.download_as_name') }}
                             <input
-                              class="emoji-data-input input"
                               v-model="remotePackDownloadAs"
+                              class="emoji-data-input input"
                               :placeholder="$t('admin_dash.emoji.download_as_name_full')"
                             >
                           </label>
 
-                          <div v-if="downloadWillReplaceLocal" class="warning">
+                          <div
+                            v-if="downloadWillReplaceLocal"
+                            class="warning"
+                          >
                             <em>{{ $t('admin_dash.emoji.replace_warning') }}</em>
                           </div>
                         </div>
@@ -235,7 +278,8 @@
                         <button
                           class="button button-default btn"
                           type="button"
-                          @click="downloadRemotePack">
+                          @click="downloadRemotePack"
+                        >
                           {{ $t('admin_dash.emoji.download') }}
                         </button>
                       </div>
@@ -251,31 +295,47 @@
           <h4>
             {{ $t('admin_dash.emoji.files') }}
 
-            <ModifiedIndicator v-if="pack"
+            <ModifiedIndicator
+              v-if="pack"
               :changed="$refs.emojiPopovers && $refs.emojiPopovers.some(p => p.isEdited)"
-              message-key="admin_dash.emoji.emoji_changed"/>
+              message-key="admin_dash.emoji.emoji_changed"
+            />
           </h4>
 
-          <div class="emoji-list" v-if="pack">
+          <div
+            v-if="pack"
+            class="emoji-list"
+          >
             <EmojiEditingPopover
               v-if="pack.remote === undefined"
-              placement="bottom" new-upload
+              placement="bottom"
+              new-upload
               :title="$t('admin_dash.emoji.adding_new')"
-              :packName="packName"
-              @updatePackFiles="updatePackFiles" @displayError="displayError"
+              :pack-name="packName"
+              @updatePackFiles="updatePackFiles"
+              @displayError="displayError"
             >
               <template #trigger>
-                <FAIcon icon="plus" size="2x" :title="$t('admin_dash.emoji.add_file')" />
+                <FAIcon
+                  icon="plus"
+                  size="2x"
+                  :title="$t('admin_dash.emoji.add_file')"
+                />
               </template>
             </EmojiEditingPopover>
 
             <EmojiEditingPopover
-              placement="top" ref="emojiPopovers"
-              v-for="(file, shortcode) in pack.files" :key="shortcode"
+              v-for="(file, shortcode) in pack.files"
+              ref="emojiPopovers"
+              :key="shortcode"
+              placement="top"
               :title="$t('admin_dash.emoji.editing', [shortcode])"
               :disabled="pack.remote !== undefined"
-              :shortcode="shortcode" :file="file" :packName="packName"
-              @updatePackFiles="updatePackFiles" @displayError="displayError"
+              :shortcode="shortcode"
+              :file="file"
+              :pack-name="packName"
+              @updatePackFiles="updatePackFiles"
+              @displayError="displayError"
             >
               <template #trigger>
                 <StillImage
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.vue b/src/components/settings_modal/admin_tabs/frontends_tab.vue
index ed3818a0..8fb3d399 100644
--- a/src/components/settings_modal/admin_tabs/frontends_tab.vue
+++ b/src/components/settings_modal/admin_tabs/frontends_tab.vue
@@ -6,7 +6,10 @@
     <div class="setting-item">
       <h2>{{ $t('admin_dash.tabs.frontends') }}</h2>
       <p>{{ $t('admin_dash.frontend.wip_notice') }}</p>
-      <ul class="setting-list" v-if="adminDraft">
+      <ul
+        v-if="adminDraft"
+        class="setting-list"
+      >
         <li>
           <h3>{{ $t('admin_dash.frontend.default_frontend') }}</h3>
           <p>{{ $t('admin_dash.frontend.default_frontend_tip') }}</p>
@@ -23,7 +26,10 @@
           </ul>
         </li>
       </ul>
-      <div v-else class="setting-list">
+      <div
+        v-else
+        class="setting-list"
+      >
         {{ $t('admin_dash.frontend.default_frontend_unavail') }}
       </div>
 
diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
index c0b03310..f0465dd5 100644
--- a/src/components/settings_modal/helpers/emoji_editing_popover.vue
+++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue
@@ -1,10 +1,10 @@
 <template>
   <Popover
+    ref="emojiPopover"
     trigger="click"
     :placement="placement"
     bound-to-selector=".emoji-list"
     popover-class="emoji-tab-edit-popover popover-default"
-    ref="emojiPopover"
     :bound-to="{ x: 'container' }"
     :offset="{ y: 5 }"
     :disabled="disabled"
@@ -18,23 +18,34 @@
         {{ title }}
       </h3>
 
-      <StillImage class="emoji" v-if="emojiPreview" :src="emojiPreview" />
-      <div v-else class="emoji"></div>
-
-      <div class="emoji-tab-popover-input" v-if="newUpload">
+      <StillImage
+        v-if="emojiPreview"
+        class="emoji"
+        :src="emojiPreview"
+      />
+      <div
+        v-else
+        class="emoji"
+      />
+
+      <div
+        v-if="newUpload"
+        class="emoji-tab-popover-input"
+      >
         <input
           type="file"
           accept="image/*"
           class="emoji-tab-popover-file input"
-          @change="uploadFile = $event.target.files">
+          @change="uploadFile = $event.target.files"
+        >
       </div>
       <div>
         <div class="emoji-tab-popover-input">
           <label>
             {{ $t('admin_dash.emoji.shortcode') }}
             <input
-              class="emoji-data-input input"
               v-model="editedShortcode"
+              class="emoji-data-input input"
               :placeholder="$t('admin_dash.emoji.new_shortcode')"
             >
           </label>
@@ -45,8 +56,8 @@
             {{ $t('admin_dash.emoji.filename') }}
 
             <input
-              class="emoji-data-input input"
               v-model="editedFile"
+              class="emoji-data-input input"
               :placeholder="$t('admin_dash.emoji.new_filename')"
             >
           </label>
@@ -56,7 +67,8 @@
           class="button button-default btn"
           type="button"
           :disabled="newUpload ? uploadFile.length == 0 : !isEdited"
-          @click="newUpload ? uploadEmoji() : saveEditedEmoji()">
+          @click="newUpload ? uploadEmoji() : saveEditedEmoji()"
+        >
           {{ $t('admin_dash.emoji.save') }}
         </button>
 
@@ -64,13 +76,15 @@
           <button
             class="button button-default btn emoji-tab-popover-button"
             type="button"
-            @click="deleteModalVisible = true">
+            @click="deleteModalVisible = true"
+          >
             {{ $t('admin_dash.emoji.delete') }}
           </button>
           <button
             class="button button-default btn emoji-tab-popover-button"
             type="button"
-            @click="revertEmoji">
+            @click="revertEmoji"
+          >
             {{ $t('admin_dash.emoji.revert') }}
           </button>
           <ConfirmModal
@@ -79,7 +93,8 @@
             :cancel-text="$t('status.delete_confirm_cancel_button')"
             :confirm-text="$t('status.delete_confirm_accept_button')"
             @cancelled="deleteModalVisible = false"
-            @accepted="deleteEmoji" >
+            @accepted="deleteEmoji"
+          >
             {{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
           </ConfirmModal>
         </template>
@@ -95,6 +110,30 @@ import StillImage from 'components/still-image/still-image.vue'
 
 export default {
   components: { Popover, ConfirmModal, StillImage },
+  inject: ['emojiAddr'],
+  props: {
+    placement: String,
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+
+    newUpload: Boolean,
+
+    title: String,
+    packName: String,
+    shortcode: {
+      type: String,
+      // Only exists when this is not a new upload
+      default: ''
+    },
+    file: {
+      type: String,
+      // Only exists when this is not a new upload
+      default: ''
+    }
+  },
+  emits: ['updatePackFiles', 'displayError'],
   data () {
     return {
       uploadFile: [],
@@ -117,7 +156,6 @@ export default {
       return !this.newUpload && (this.editedShortcode !== this.shortcode || this.editedFile !== this.file)
     }
   },
-  inject: ['emojiAddr'],
   methods: {
     saveEditedEmoji () {
       if (!this.isEdited) return
@@ -171,29 +209,6 @@ export default {
         this.$emit('updatePackFiles', resp)
       })
     }
-  },
-  emits: ['updatePackFiles', 'displayError'],
-  props: {
-    placement: String,
-    disabled: {
-      type: Boolean,
-      default: false
-    },
-
-    newUpload: Boolean,
-
-    title: String,
-    packName: String,
-    shortcode: {
-      type: String,
-      // Only exists when this is not a new upload
-      default: ''
-    },
-    file: {
-      type: String,
-      // Only exists when this is not a new upload
-      default: ''
-    }
   }
 }
 </script>
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index b3f48907..b1e87d37 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -89,8 +89,8 @@
         </div>
         <div
           v-if="followersTabVisible"
-          class="panel-body"
           key="followers"
+          class="panel-body"
           :label="$t('user_card.followers')"
           :disabled="!user.followers_count"
         >
-- 
cgit v1.2.3-70-g09d2

