From f5f7654f4b9a99d3b57d55435e6d467084186c4d Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 14 Feb 2020 01:09:39 +0900 Subject: [PATCH] Improve custom emoji managemant --- locales/ja-JP.yml | 3 + src/client/pages/instance/emojis.vue | 100 +++++++++++------- src/client/scripts/select-file.ts | 8 +- src/server/api/endpoints/admin/emoji/add.ts | 47 +++----- .../api/endpoints/admin/emoji/update.ts | 9 -- 5 files changed, 86 insertions(+), 81 deletions(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 6d9402650a..ae4f76f3d1 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -393,6 +393,9 @@ noGroups: "グループがありません" joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。" noHistory: "履歴はありません" disableAnimatedMfm: "動きのあるMFMを無効にする" +doing: "やっています" +category: "カテゴリ" +tags: "タグ" _ago: unknown: "謎" diff --git a/src/client/pages/instance/emojis.vue b/src/client/pages/instance/emojis.vue index a53508db84..66b52492b9 100644 --- a/src/client/pages/instance/emojis.vue +++ b/src/client/pages/instance/emojis.vue @@ -2,10 +2,10 @@ <div class="mk-instance-emojis"> <portal to="icon"><fa :icon="faLaugh"/></portal> <portal to="title">{{ $t('customEmojis') }}</portal> + <section class="_card local"> <div class="_title"><fa :icon="faLaugh"/> {{ $t('customEmojis') }}</div> <div class="_content"> - <input ref="file" type="file" style="display: none;" @change="onChangeFile"/> <mk-pagination :pagination="pagination" class="emojis" ref="emojis"> <template #empty><span>{{ $t('noCustomEmojis') }}</span></template> <template #default="{items}"> @@ -13,15 +13,25 @@ <img :src="emoji.url" class="img" :alt="emoji.name"/> <div class="body"> <span class="name">{{ emoji.name }}</span> + <span class="info"> + <b class="category">{{ emoji.category }}</b> + <span class="aliases">{{ emoji.aliases.join(' ') }}</span> + </span> </div> </div> </template> </mk-pagination> </div> - <div class="_footer"> - <mk-button inline primary @click="add()"><fa :icon="faPlus"/> {{ $t('addEmoji') }}</mk-button> + <div class="_content" v-if="selected"> + <mk-input v-model="name"><span>{{ $t('name') }}</span></mk-input> + <mk-input v-model="category"><span>{{ $t('category') }}</span></mk-input> + <mk-input v-model="aliases"><span>{{ $t('tags') }}</span></mk-input> + <mk-button inline primary @click="update"><fa :icon="faSave"/> {{ $t('save') }}</mk-button> <mk-button inline :disabled="selected == null" @click="del()"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</mk-button> </div> + <div class="_footer"> + <mk-button inline primary @click="add"><fa :icon="faPlus"/> {{ $t('addEmoji') }}</mk-button> + </div> </section> <section class="_card remote"> <div class="_title"><fa :icon="faLaugh"/> {{ $t('customEmojisOfRemote') }}</div> @@ -34,7 +44,7 @@ <img :src="emoji.url" class="img" :alt="emoji.name"/> <div class="body"> <span class="name">{{ emoji.name }}</span> - <span class="host">{{ emoji.host }}</span> + <span class="info">{{ emoji.host }}</span> </div> </div> </template> @@ -49,12 +59,12 @@ <script lang="ts"> import Vue from 'vue'; -import { faPlus } from '@fortawesome/free-solid-svg-icons'; +import { faPlus, faSave } from '@fortawesome/free-solid-svg-icons'; import { faTrashAlt, faLaugh } from '@fortawesome/free-regular-svg-icons'; import MkButton from '../../components/ui/button.vue'; import MkInput from '../../components/ui/input.vue'; import MkPagination from '../../components/ui/pagination.vue'; -import { apiUrl } from '../../config'; +import { selectFile } from '../../scripts/select-file'; export default Vue.extend({ metaInfo() { @@ -71,9 +81,11 @@ export default Vue.extend({ data() { return { - name: null, selected: null, selectedRemote: null, + name: null, + category: null, + aliases: null, host: '', pagination: { endpoint: 'admin/emoji/list', @@ -86,52 +98,38 @@ export default Vue.extend({ host: this.host ? this.host : null }) }, - faTrashAlt, faPlus, faLaugh + faTrashAlt, faPlus, faLaugh, faSave } }, watch: { host() { this.$refs.remoteEmojis.reload(); + }, + + selected() { + this.name = this.selected ? this.selected.name : null; + this.category = this.selected ? this.selected.category : null; + this.aliases = this.selected ? this.selected.aliases.join(' ') : null; } }, methods: { - async add() { - const { canceled: canceled, result: name } = await this.$root.dialog({ - title: this.$t('emojiName'), - input: true - }); - if (canceled) return; - - this.name = name; - - (this.$refs.file as any).click(); - }, - - onChangeFile() { - const [file] = Array.from((this.$refs.file as any).files); - if (file == null) return; - - const data = new FormData(); - data.append('file', file); - data.append('name', this.name); - data.append('i', this.$store.state.i.token); + async add(e) { + const files = await selectFile(this, e.currentTarget || e.target, null, true); const dialog = this.$root.dialog({ type: 'waiting', - text: this.$t('uploading') + '...', + text: this.$t('doing') + '...', showOkButton: false, showCancelButton: false, cancelableByBgClick: false }); - - fetch(apiUrl + '/admin/emoji/add', { - method: 'POST', - body: data - }) - .then(response => response.json()) - .then(f => { + + Promise.all(files.map(file => this.$root.api('admin/emoji/add', { + fileId: file.id, + }))) + .then(() => { this.$refs.emojis.reload(); this.$root.dialog({ type: 'success', @@ -143,6 +141,22 @@ export default Vue.extend({ }); }, + async update() { + await this.$root.api('admin/emoji/update', { + id: this.selected.id, + name: this.name, + category: this.category, + aliases: this.aliases.split(' '), + }); + + this.$root.dialog({ + type: 'success', + iconOnly: true, autoClose: true + }); + + this.$refs.emojis.reload(); + }, + async del() { const { canceled } = await this.$root.dialog({ type: 'warning', @@ -207,6 +221,18 @@ export default Vue.extend({ > .name { display: block; } + + > .info { + opacity: 0.5; + + > .category { + margin-right: 16px; + } + + > .aliases { + font-style: oblique; + } + } } } } @@ -241,7 +267,7 @@ export default Vue.extend({ display: block; } - > .host { + > .info { opacity: 0.5; } } diff --git a/src/client/scripts/select-file.ts b/src/client/scripts/select-file.ts index 1025b23e03..70e68e88c0 100644 --- a/src/client/scripts/select-file.ts +++ b/src/client/scripts/select-file.ts @@ -1,8 +1,8 @@ -import { faUpload, faCloud, faLink } from '@fortawesome/free-solid-svg-icons'; +import { faUpload, faCloud } from '@fortawesome/free-solid-svg-icons'; import { selectDriveFile } from './select-drive-file'; import { apiUrl } from '../config'; -export function selectFile(component: any, src: any, label: string, multiple = false) { +export function selectFile(component: any, src: any, label: string | null, multiple = false) { return new Promise((res, rej) => { const chooseFileFromPc = () => { const input = document.createElement('input'); @@ -56,10 +56,10 @@ export function selectFile(component: any, src: any, label: string, multiple = f }; component.$root.menu({ - items: [{ + items: [label ? { text: label, type: 'label' - }, { + } : undefined, { text: component.$t('upload'), icon: faUpload, action: chooseFileFromPc diff --git a/src/server/api/endpoints/admin/emoji/add.ts b/src/server/api/endpoints/admin/emoji/add.ts index 3a17760e53..610efbbe8f 100644 --- a/src/server/api/endpoints/admin/emoji/add.ts +++ b/src/server/api/endpoints/admin/emoji/add.ts @@ -1,11 +1,12 @@ import $ from 'cafy'; import define from '../../../define'; -import { detectUrlMime } from '../../../../../misc/detect-url-mime'; -import { Emojis } from '../../../../../models'; +import { Emojis, DriveFiles } from '../../../../../models'; import { genId } from '../../../../../misc/gen-id'; import { getConnection } from 'typeorm'; import { insertModerationLog } from '../../../../../services/insert-moderation-log'; import { ApiError } from '../../../error'; +import { ID } from '../../../../../misc/cafy-id'; +import rndstr from 'rndstr'; export const meta = { desc: { @@ -18,52 +19,36 @@ export const meta = { requireModerator: true, params: { - name: { - validator: $.str.min(1) + fileId: { + validator: $.type(ID) }, - - url: { - validator: $.str.min(1) - }, - - category: { - validator: $.optional.str - }, - - aliases: { - validator: $.optional.arr($.str.min(1)), - default: [] as string[] - } }, errors: { - emojiAlredyExists: { - message: 'Emoji already exists.', - code: 'EMOJI_ALREADY_EXISTS', + noSuchFile: { + message: 'No such file.', + code: 'MO_SUCH_FILE', id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf' } } }; export default define(meta, async (ps, me) => { - const type = await detectUrlMime(ps.url); + const file = await DriveFiles.findOne(ps.fileId); - const exists = await Emojis.findOne({ - name: ps.name, - host: null - }); + if (file == null) throw new ApiError(meta.errors.noSuchFile); - if (exists != null) throw new ApiError(meta.errors.emojiAlredyExists); + const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; const emoji = await Emojis.save({ id: genId(), updatedAt: new Date(), - name: ps.name, - category: ps.category, + name: name, + category: null, host: null, - aliases: ps.aliases, - url: ps.url, - type, + aliases: [], + url: file.url, + type: file.type, }); await getConnection().queryResultCache!.remove(['meta_emojis']); diff --git a/src/server/api/endpoints/admin/emoji/update.ts b/src/server/api/endpoints/admin/emoji/update.ts index 0651b8d283..b6ecb39b43 100644 --- a/src/server/api/endpoints/admin/emoji/update.ts +++ b/src/server/api/endpoints/admin/emoji/update.ts @@ -1,6 +1,5 @@ import $ from 'cafy'; import define from '../../../define'; -import { detectUrlMime } from '../../../../../misc/detect-url-mime'; import { ID } from '../../../../../misc/cafy-id'; import { Emojis } from '../../../../../models'; import { getConnection } from 'typeorm'; @@ -29,10 +28,6 @@ export const meta = { validator: $.optional.str }, - url: { - validator: $.str - }, - aliases: { validator: $.arr($.str) } @@ -52,15 +47,11 @@ export default define(meta, async (ps) => { if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); - const type = await detectUrlMime(ps.url); - await Emojis.update(emoji.id, { updatedAt: new Date(), name: ps.name, category: ps.category, aliases: ps.aliases, - url: ps.url, - type, }); await getConnection().queryResultCache!.remove(['meta_emojis']);