Improve custom emoji managemant
This commit is contained in:
		
							parent
							
								
									5ac4c48ad1
								
							
						
					
					
						commit
						f5f7654f4b
					
				
					 5 changed files with 86 additions and 81 deletions
				
			
		|  | @ -393,6 +393,9 @@ noGroups: "グループがありません" | |||
| joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。" | ||||
| noHistory: "履歴はありません" | ||||
| disableAnimatedMfm: "動きのあるMFMを無効にする" | ||||
| doing: "やっています" | ||||
| category: "カテゴリ" | ||||
| tags: "タグ" | ||||
| 
 | ||||
| _ago: | ||||
|   unknown: "謎" | ||||
|  |  | |||
|  | @ -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; | ||||
| 						} | ||||
| 					} | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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']); | ||||
|  |  | |||
|  | @ -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']); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue