feat: multiple emojis editing
This commit is contained in:
		
							parent
							
								
									b17726c9da
								
							
						
					
					
						commit
						1f2dab0a83
					
				
					 10 changed files with 428 additions and 139 deletions
				
			
		|  | @ -0,0 +1,39 @@ | ||||||
|  | import $ from 'cafy'; | ||||||
|  | import define from '../../../define'; | ||||||
|  | import { ID } from '@/misc/cafy-id'; | ||||||
|  | import { Emojis } from '@/models/index'; | ||||||
|  | import { getConnection, In } from 'typeorm'; | ||||||
|  | import { ApiError } from '../../../error'; | ||||||
|  | 
 | ||||||
|  | export const meta = { | ||||||
|  | 	tags: ['admin'], | ||||||
|  | 
 | ||||||
|  | 	requireCredential: true as const, | ||||||
|  | 	requireModerator: true, | ||||||
|  | 
 | ||||||
|  | 	params: { | ||||||
|  | 		ids: { | ||||||
|  | 			validator: $.arr($.type(ID)), | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		aliases: { | ||||||
|  | 			validator: $.arr($.str), | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // eslint-disable-next-line import/no-default-export
 | ||||||
|  | export default define(meta, async (ps) => { | ||||||
|  | 	const emojis = await Emojis.find({ | ||||||
|  | 		id: In(ps.ids), | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	for (const emoji of emojis) { | ||||||
|  | 		await Emojis.update(emoji.id, { | ||||||
|  | 			updatedAt: new Date(), | ||||||
|  | 			aliases: [...new Set(emoji.aliases.concat(ps.aliases))], | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	await getConnection().queryResultCache!.remove(['meta_emojis']); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,37 @@ | ||||||
|  | import $ from 'cafy'; | ||||||
|  | import define from '../../../define'; | ||||||
|  | import { ID } from '@/misc/cafy-id'; | ||||||
|  | import { Emojis } from '@/models/index'; | ||||||
|  | import { getConnection, In } from 'typeorm'; | ||||||
|  | import { insertModerationLog } from '@/services/insert-moderation-log'; | ||||||
|  | import { ApiError } from '../../../error'; | ||||||
|  | 
 | ||||||
|  | export const meta = { | ||||||
|  | 	tags: ['admin'], | ||||||
|  | 
 | ||||||
|  | 	requireCredential: true as const, | ||||||
|  | 	requireModerator: true, | ||||||
|  | 
 | ||||||
|  | 	params: { | ||||||
|  | 		ids: { | ||||||
|  | 			validator: $.arr($.type(ID)), | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // eslint-disable-next-line import/no-default-export
 | ||||||
|  | export default define(meta, async (ps, me) => { | ||||||
|  | 	const emojis = await Emojis.find({ | ||||||
|  | 		id: In(ps.ids), | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	for (const emoji of emojis) { | ||||||
|  | 		await Emojis.delete(emoji.id); | ||||||
|  | 	 | ||||||
|  | 		await getConnection().queryResultCache!.remove(['meta_emojis']); | ||||||
|  | 	 | ||||||
|  | 		insertModerationLog(me, 'deleteEmoji', { | ||||||
|  | 			emoji: emoji, | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | @ -37,7 +37,7 @@ export default define(meta, async (ps, me) => { | ||||||
| 
 | 
 | ||||||
| 	await getConnection().queryResultCache!.remove(['meta_emojis']); | 	await getConnection().queryResultCache!.remove(['meta_emojis']); | ||||||
| 
 | 
 | ||||||
| 	insertModerationLog(me, 'removeEmoji', { | 	insertModerationLog(me, 'deleteEmoji', { | ||||||
| 		emoji: emoji, | 		emoji: emoji, | ||||||
| 	}); | 	}); | ||||||
| }); | }); | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | import $ from 'cafy'; | ||||||
|  | import define from '../../../define'; | ||||||
|  | import { createImportCustomEmojisJob } from '@/queue/index'; | ||||||
|  | import ms from 'ms'; | ||||||
|  | import { ID } from '@/misc/cafy-id'; | ||||||
|  | 
 | ||||||
|  | export const meta = { | ||||||
|  | 	secure: true, | ||||||
|  | 	requireCredential: true as const, | ||||||
|  | 	requireModerator: true, | ||||||
|  | 	params: { | ||||||
|  | 		fileId: { | ||||||
|  | 			validator: $.type(ID), | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // eslint-disable-next-line import/no-default-export
 | ||||||
|  | export default define(meta, async (ps, user) => { | ||||||
|  | 	createImportCustomEmojisJob(user, ps.fileId); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,39 @@ | ||||||
|  | import $ from 'cafy'; | ||||||
|  | import define from '../../../define'; | ||||||
|  | import { ID } from '@/misc/cafy-id'; | ||||||
|  | import { Emojis } from '@/models/index'; | ||||||
|  | import { getConnection, In } from 'typeorm'; | ||||||
|  | import { ApiError } from '../../../error'; | ||||||
|  | 
 | ||||||
|  | export const meta = { | ||||||
|  | 	tags: ['admin'], | ||||||
|  | 
 | ||||||
|  | 	requireCredential: true as const, | ||||||
|  | 	requireModerator: true, | ||||||
|  | 
 | ||||||
|  | 	params: { | ||||||
|  | 		ids: { | ||||||
|  | 			validator: $.arr($.type(ID)), | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		aliases: { | ||||||
|  | 			validator: $.arr($.str), | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // eslint-disable-next-line import/no-default-export
 | ||||||
|  | export default define(meta, async (ps) => { | ||||||
|  | 	const emojis = await Emojis.find({ | ||||||
|  | 		id: In(ps.ids), | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	for (const emoji of emojis) { | ||||||
|  | 		await Emojis.update(emoji.id, { | ||||||
|  | 			updatedAt: new Date(), | ||||||
|  | 			aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)), | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	await getConnection().queryResultCache!.remove(['meta_emojis']); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | import $ from 'cafy'; | ||||||
|  | import define from '../../../define'; | ||||||
|  | import { ID } from '@/misc/cafy-id'; | ||||||
|  | import { Emojis } from '@/models/index'; | ||||||
|  | import { getConnection, In } from 'typeorm'; | ||||||
|  | import { ApiError } from '../../../error'; | ||||||
|  | 
 | ||||||
|  | export const meta = { | ||||||
|  | 	tags: ['admin'], | ||||||
|  | 
 | ||||||
|  | 	requireCredential: true as const, | ||||||
|  | 	requireModerator: true, | ||||||
|  | 
 | ||||||
|  | 	params: { | ||||||
|  | 		ids: { | ||||||
|  | 			validator: $.arr($.type(ID)), | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		aliases: { | ||||||
|  | 			validator: $.arr($.str), | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // eslint-disable-next-line import/no-default-export
 | ||||||
|  | export default define(meta, async (ps) => { | ||||||
|  | 	await Emojis.update({ | ||||||
|  | 		id: In(ps.ids), | ||||||
|  | 	}, { | ||||||
|  | 		updatedAt: new Date(), | ||||||
|  | 		aliases: ps.aliases, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	await getConnection().queryResultCache!.remove(['meta_emojis']); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | import $ from 'cafy'; | ||||||
|  | import define from '../../../define'; | ||||||
|  | import { ID } from '@/misc/cafy-id'; | ||||||
|  | import { Emojis } from '@/models/index'; | ||||||
|  | import { getConnection, In } from 'typeorm'; | ||||||
|  | import { ApiError } from '../../../error'; | ||||||
|  | 
 | ||||||
|  | export const meta = { | ||||||
|  | 	tags: ['admin'], | ||||||
|  | 
 | ||||||
|  | 	requireCredential: true as const, | ||||||
|  | 	requireModerator: true, | ||||||
|  | 
 | ||||||
|  | 	params: { | ||||||
|  | 		ids: { | ||||||
|  | 			validator: $.arr($.type(ID)), | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		category: { | ||||||
|  | 			validator: $.optional.nullable.str, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // eslint-disable-next-line import/no-default-export
 | ||||||
|  | export default define(meta, async (ps) => { | ||||||
|  | 	await Emojis.update({ | ||||||
|  | 		id: In(ps.ids), | ||||||
|  | 	}, { | ||||||
|  | 		updatedAt: new Date(), | ||||||
|  | 		category: ps.category, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	await getConnection().queryResultCache!.remove(['meta_emojis']); | ||||||
|  | }); | ||||||
|  | @ -95,7 +95,7 @@ export default defineComponent({ | ||||||
| 			}); | 			}); | ||||||
| 			if (canceled) return; | 			if (canceled) return; | ||||||
| 
 | 
 | ||||||
| 			os.api('admin/emoji/remove', { | 			os.api('admin/emoji/delete', { | ||||||
| 				id: this.emoji.id | 				id: this.emoji.id | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
| 				this.$emit('done', { | 				this.$emit('done', { | ||||||
|  |  | ||||||
|  | @ -6,11 +6,22 @@ | ||||||
| 				<template #prefix><i class="fas fa-search"></i></template> | 				<template #prefix><i class="fas fa-search"></i></template> | ||||||
| 				<template #label>{{ $ts.search }}</template> | 				<template #label>{{ $ts.search }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<MkPagination ref="emojis" :pagination="pagination"> | 			<MkSwitch v-model="selectMode" style="margin: 8px 0;"> | ||||||
|  | 				<template #label>Select mode</template> | ||||||
|  | 			</MkSwitch> | ||||||
|  | 			<div v-if="selectMode" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> | ||||||
|  | 				<MkButton inline @click="selectAll">Select all</MkButton> | ||||||
|  | 				<MkButton inline @click="setCategoryBulk">Set category</MkButton> | ||||||
|  | 				<MkButton inline @click="addTagBulk">Add tag</MkButton> | ||||||
|  | 				<MkButton inline @click="removeTagBulk">Remove tag</MkButton> | ||||||
|  | 				<MkButton inline @click="setTagBulk">Set tag</MkButton> | ||||||
|  | 				<MkButton inline danger @click="delBulk">Delete</MkButton> | ||||||
|  | 			</div> | ||||||
|  | 			<MkPagination ref="emojisPaginationComponent" :pagination="pagination"> | ||||||
| 				<template #empty><span>{{ $ts.noCustomEmojis }}</span></template> | 				<template #empty><span>{{ $ts.noCustomEmojis }}</span></template> | ||||||
| 				<template v-slot="{items}"> | 				<template v-slot="{items}"> | ||||||
| 					<div class="ldhfsamy"> | 					<div class="ldhfsamy"> | ||||||
| 						<button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="edit(emoji)"> | 						<button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)"> | ||||||
| 							<img :src="emoji.url" class="img" :alt="emoji.name"/> | 							<img :src="emoji.url" class="img" :alt="emoji.name"/> | ||||||
| 							<div class="body"> | 							<div class="body"> | ||||||
| 								<div class="name _monospace">{{ emoji.name }}</div> | 								<div class="name _monospace">{{ emoji.name }}</div> | ||||||
|  | @ -32,7 +43,7 @@ | ||||||
| 					<template #label>{{ $ts.host }}</template> | 					<template #label>{{ $ts.host }}</template> | ||||||
| 				</MkInput> | 				</MkInput> | ||||||
| 			</FormSplit> | 			</FormSplit> | ||||||
| 			<MkPagination ref="remoteEmojis" :pagination="remotePagination"> | 			<MkPagination :pagination="remotePagination"> | ||||||
| 				<template #empty><span>{{ $ts.noCustomEmojis }}</span></template> | 				<template #empty><span>{{ $ts.noCustomEmojis }}</span></template> | ||||||
| 				<template v-slot="{items}"> | 				<template v-slot="{items}"> | ||||||
| 					<div class="ldhfsamy"> | 					<div class="ldhfsamy"> | ||||||
|  | @ -51,148 +62,213 @@ | ||||||
| </MkSpacer> | </MkSpacer> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts" setup> | ||||||
| import { computed, defineComponent, toRef } from 'vue'; | import { computed, defineComponent, ref, toRef } from 'vue'; | ||||||
| import MkButton from '@/components/ui/button.vue'; | import MkButton from '@/components/ui/button.vue'; | ||||||
| import MkInput from '@/components/form/input.vue'; | import MkInput from '@/components/form/input.vue'; | ||||||
| import MkPagination from '@/components/ui/pagination.vue'; | import MkPagination from '@/components/ui/pagination.vue'; | ||||||
| import MkTab from '@/components/tab.vue'; | import MkTab from '@/components/tab.vue'; | ||||||
|  | import MkSwitch from '@/components/form/switch.vue'; | ||||||
| import FormSplit from '@/components/form/split.vue'; | import FormSplit from '@/components/form/split.vue'; | ||||||
| import { selectFiles } from '@/scripts/select-file'; | import { selectFile, selectFiles } from '@/scripts/select-file'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import * as symbols from '@/symbols'; | import * as symbols from '@/symbols'; | ||||||
|  | import { i18n } from '@/i18n'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | const emojisPaginationComponent = ref<InstanceType<typeof MkPagination>>(); | ||||||
| 	components: { |  | ||||||
| 		MkTab, |  | ||||||
| 		MkButton, |  | ||||||
| 		MkInput, |  | ||||||
| 		MkPagination, |  | ||||||
| 		FormSplit, |  | ||||||
| 	}, |  | ||||||
| 
 | 
 | ||||||
| 	emits: ['info'], | const tab = ref('local'); | ||||||
|  | const query = ref(null); | ||||||
|  | const queryRemote = ref(null); | ||||||
|  | const host = ref(null); | ||||||
|  | const selectMode = ref(false); | ||||||
|  | const selectedEmojis = ref<string[]>([]); | ||||||
| 
 | 
 | ||||||
| 	data() { | const pagination = { | ||||||
| 		return { | 	endpoint: 'admin/emoji/list', | ||||||
| 			[symbols.PAGE_INFO]: computed(() => ({ | 	limit: 30, | ||||||
| 				title: this.$ts.customEmojis, | 	params: computed(() => ({ | ||||||
| 				icon: 'fas fa-laugh', | 		query: (query.value && query.value !== '') ? query.value : null, | ||||||
| 				bg: 'var(--bg)', | 	})), | ||||||
| 				actions: [{ | }; | ||||||
| 					asFullButton: true, |  | ||||||
| 					icon: 'fas fa-plus', |  | ||||||
| 					text: this.$ts.addEmoji, |  | ||||||
| 					handler: this.add, |  | ||||||
| 				}, { |  | ||||||
| 					icon: 'fas fa-ellipsis-h', |  | ||||||
| 					handler: this.menu, |  | ||||||
| 				}], |  | ||||||
| 				tabs: [{ |  | ||||||
| 					active: this.tab === 'local', |  | ||||||
| 					title: this.$ts.local, |  | ||||||
| 					onClick: () => { this.tab = 'local'; }, |  | ||||||
| 				}, { |  | ||||||
| 					active: this.tab === 'remote', |  | ||||||
| 					title: this.$ts.remote, |  | ||||||
| 					onClick: () => { this.tab = 'remote'; }, |  | ||||||
| 				},] |  | ||||||
| 			})), |  | ||||||
| 			tab: 'local', |  | ||||||
| 			query: null, |  | ||||||
| 			queryRemote: null, |  | ||||||
| 			host: '', |  | ||||||
| 			pagination: { |  | ||||||
| 				endpoint: 'admin/emoji/list', |  | ||||||
| 				limit: 30, |  | ||||||
| 				params: computed(() => ({ |  | ||||||
| 					query: (this.query && this.query !== '') ? this.query : null |  | ||||||
| 				})) |  | ||||||
| 			}, |  | ||||||
| 			remotePagination: { |  | ||||||
| 				endpoint: 'admin/emoji/list-remote', |  | ||||||
| 				limit: 30, |  | ||||||
| 				params: computed(() => ({ |  | ||||||
| 					query: (this.queryRemote && this.queryRemote !== '') ? this.queryRemote : null, |  | ||||||
| 					host: (this.host && this.host !== '') ? this.host : null |  | ||||||
| 				})) |  | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 | 
 | ||||||
| 	async mounted() { | const remotePagination = { | ||||||
| 		this.$emit('info', toRef(this, symbols.PAGE_INFO)); | 	endpoint: 'admin/emoji/list-remote', | ||||||
| 	}, | 	limit: 30, | ||||||
|  | 	params: computed(() => ({ | ||||||
|  | 		query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null, | ||||||
|  | 		host: (host.value && host.value !== '') ? host.value : null, | ||||||
|  | 	})), | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| 	methods: { | const selectAll = () => { | ||||||
| 		async add(e) { | 	if (selectedEmojis.value.length > 0) { | ||||||
| 			const files = await selectFiles(e.currentTarget || e.target, null); | 		selectedEmojis.value = []; | ||||||
| 
 | 	} else { | ||||||
| 			const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { | 		selectedEmojis.value = emojisPaginationComponent.value.items.map(item => item.id); | ||||||
| 				fileId: file.id, |  | ||||||
| 			}))); |  | ||||||
| 			promise.then(() => { |  | ||||||
| 				this.$refs.emojis.reload(); |  | ||||||
| 			}); |  | ||||||
| 			os.promiseDialog(promise); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		edit(emoji) { |  | ||||||
| 			os.popup(import('./emoji-edit-dialog.vue'), { |  | ||||||
| 				emoji: emoji |  | ||||||
| 			}, { |  | ||||||
| 				done: result => { |  | ||||||
| 					if (result.updated) { |  | ||||||
| 						this.$refs.emojis.replaceItem(item => item.id === emoji.id, { |  | ||||||
| 							...emoji, |  | ||||||
| 							...result.updated |  | ||||||
| 						}); |  | ||||||
| 					} else if (result.deleted) { |  | ||||||
| 						this.$refs.emojis.removeItem(item => item.id === emoji.id); |  | ||||||
| 					} |  | ||||||
| 				}, |  | ||||||
| 			}, 'closed'); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		im(emoji) { |  | ||||||
| 			os.apiWithDialog('admin/emoji/copy', { |  | ||||||
| 				emojiId: emoji.id, |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		remoteMenu(emoji, ev) { |  | ||||||
| 			os.popupMenu([{ |  | ||||||
| 				type: 'label', |  | ||||||
| 				text: ':' + emoji.name + ':', |  | ||||||
| 			}, { |  | ||||||
| 				text: this.$ts.import, |  | ||||||
| 				icon: 'fas fa-plus', |  | ||||||
| 				action: () => { this.im(emoji) } |  | ||||||
| 			}], ev.currentTarget || ev.target); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		menu(ev) { |  | ||||||
| 			os.popupMenu([{ |  | ||||||
| 				icon: 'fas fa-download', |  | ||||||
| 				text: this.$ts.export, |  | ||||||
| 				action: async () => { |  | ||||||
| 					os.api('export-custom-emojis', { |  | ||||||
| 					}) |  | ||||||
| 					.then(() => { |  | ||||||
| 						os.alert({ |  | ||||||
| 							type: 'info', |  | ||||||
| 							text: this.$ts.exportRequested, |  | ||||||
| 						}); |  | ||||||
| 					}).catch((e) => { |  | ||||||
| 						os.alert({ |  | ||||||
| 							type: 'error', |  | ||||||
| 							text: e.message, |  | ||||||
| 						}); |  | ||||||
| 					}); |  | ||||||
| 				} |  | ||||||
| 			}], ev.currentTarget || ev.target); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const toggleSelect = (emoji) => { | ||||||
|  | 	if (selectedEmojis.value.includes(emoji.id)) { | ||||||
|  | 		selectedEmojis.value = selectedEmojis.value.filter(x => x !== emoji.id); | ||||||
|  | 	} else { | ||||||
|  | 		selectedEmojis.value.push(emoji.id); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const add = async (ev: MouseEvent) => { | ||||||
|  | 	const files = await selectFiles(ev.currentTarget || ev.target, null); | ||||||
|  | 
 | ||||||
|  | 	const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { | ||||||
|  | 		fileId: file.id, | ||||||
|  | 	}))); | ||||||
|  | 	promise.then(() => { | ||||||
|  | 		emojisPaginationComponent.value.reload(); | ||||||
|  | 	}); | ||||||
|  | 	os.promiseDialog(promise); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const edit = (emoji) => { | ||||||
|  | 	os.popup(import('./emoji-edit-dialog.vue'), { | ||||||
|  | 		emoji: emoji | ||||||
|  | 	}, { | ||||||
|  | 		done: result => { | ||||||
|  | 			if (result.updated) { | ||||||
|  | 				emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, { | ||||||
|  | 					...emoji, | ||||||
|  | 					...result.updated | ||||||
|  | 				}); | ||||||
|  | 			} else if (result.deleted) { | ||||||
|  | 				emojisPaginationComponent.value.removeItem(item => item.id === emoji.id); | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 	}, 'closed'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const im = (emoji) => { | ||||||
|  | 	os.apiWithDialog('admin/emoji/copy', { | ||||||
|  | 		emojiId: emoji.id, | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const remoteMenu = (emoji, ev: MouseEvent) => { | ||||||
|  | 	os.popupMenu([{ | ||||||
|  | 		type: 'label', | ||||||
|  | 		text: ':' + emoji.name + ':', | ||||||
|  | 	}, { | ||||||
|  | 		text: i18n.locale.import, | ||||||
|  | 		icon: 'fas fa-plus', | ||||||
|  | 		action: () => { im(emoji) } | ||||||
|  | 	}], ev.currentTarget || ev.target); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const menu = (ev: MouseEvent) => { | ||||||
|  | 	os.popupMenu([{ | ||||||
|  | 		icon: 'fas fa-download', | ||||||
|  | 		text: i18n.locale.export, | ||||||
|  | 		action: async () => { | ||||||
|  | 			os.api('export-custom-emojis', { | ||||||
|  | 			}) | ||||||
|  | 			.then(() => { | ||||||
|  | 				os.alert({ | ||||||
|  | 					type: 'info', | ||||||
|  | 					text: i18n.locale.exportRequested, | ||||||
|  | 				}); | ||||||
|  | 			}).catch((e) => { | ||||||
|  | 				os.alert({ | ||||||
|  | 					type: 'error', | ||||||
|  | 					text: e.message, | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}], ev.currentTarget || ev.target); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const setCategoryBulk = async () => { | ||||||
|  | 	const { canceled, result } = await os.inputText({ | ||||||
|  | 		title: 'Category', | ||||||
|  | 	}); | ||||||
|  | 	if (canceled) return; | ||||||
|  | 	await os.apiWithDialog('admin/emoji/set-category-bulk', { | ||||||
|  | 		ids: selectedEmojis.value, | ||||||
|  | 		category: result, | ||||||
|  | 	}); | ||||||
|  | 	emojisPaginationComponent.value.reload(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const addTagBulk = async () => { | ||||||
|  | 	const { canceled, result } = await os.inputText({ | ||||||
|  | 		title: 'Tag', | ||||||
|  | 	}); | ||||||
|  | 	if (canceled) return; | ||||||
|  | 	await os.apiWithDialog('admin/emoji/add-aliases-bulk', { | ||||||
|  | 		ids: selectedEmojis.value, | ||||||
|  | 		aliases: result.split(' '), | ||||||
|  | 	}); | ||||||
|  | 	emojisPaginationComponent.value.reload(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const removeTagBulk = async () => { | ||||||
|  | 	const { canceled, result } = await os.inputText({ | ||||||
|  | 		title: 'Tag', | ||||||
|  | 	}); | ||||||
|  | 	if (canceled) return; | ||||||
|  | 	await os.apiWithDialog('admin/emoji/remove-aliases-bulk', { | ||||||
|  | 		ids: selectedEmojis.value, | ||||||
|  | 		aliases: result.split(' '), | ||||||
|  | 	}); | ||||||
|  | 	emojisPaginationComponent.value.reload(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const setTagBulk = async () => { | ||||||
|  | 	const { canceled, result } = await os.inputText({ | ||||||
|  | 		title: 'Tag', | ||||||
|  | 	}); | ||||||
|  | 	if (canceled) return; | ||||||
|  | 	await os.apiWithDialog('admin/emoji/set-aliases-bulk', { | ||||||
|  | 		ids: selectedEmojis.value, | ||||||
|  | 		aliases: result.split(' '), | ||||||
|  | 	}); | ||||||
|  | 	emojisPaginationComponent.value.reload(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const delBulk = async () => { | ||||||
|  | 	const { canceled } = await os.confirm({ | ||||||
|  | 		type: 'warning', | ||||||
|  | 		text: i18n.locale.deleteConfirm, | ||||||
|  | 	}); | ||||||
|  | 	if (canceled) return; | ||||||
|  | 	await os.apiWithDialog('admin/emoji/delete-bulk', { | ||||||
|  | 		ids: selectedEmojis.value, | ||||||
|  | 	}); | ||||||
|  | 	emojisPaginationComponent.value.reload(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | defineExpose({ | ||||||
|  | 	[symbols.PAGE_INFO]: computed(() => ({ | ||||||
|  | 		title: i18n.locale.customEmojis, | ||||||
|  | 		icon: 'fas fa-laugh', | ||||||
|  | 		bg: 'var(--bg)', | ||||||
|  | 		actions: [{ | ||||||
|  | 			asFullButton: true, | ||||||
|  | 			icon: 'fas fa-plus', | ||||||
|  | 			text: i18n.locale.addEmoji, | ||||||
|  | 			handler: add, | ||||||
|  | 		}, { | ||||||
|  | 			icon: 'fas fa-ellipsis-h', | ||||||
|  | 			handler: menu, | ||||||
|  | 		}], | ||||||
|  | 		tabs: [{ | ||||||
|  | 			active: tab.value === 'local', | ||||||
|  | 			title: i18n.locale.local, | ||||||
|  | 			onClick: () => { tab.value = 'local'; }, | ||||||
|  | 		}, { | ||||||
|  | 			active: tab.value === 'remote', | ||||||
|  | 			title: i18n.locale.remote, | ||||||
|  | 			onClick: () => { tab.value = 'remote'; }, | ||||||
|  | 		},] | ||||||
|  | 	})), | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | @ -212,11 +288,16 @@ export default defineComponent({ | ||||||
| 			> .emoji { | 			> .emoji { | ||||||
| 				display: flex; | 				display: flex; | ||||||
| 				align-items: center; | 				align-items: center; | ||||||
| 				padding: 12px; | 				padding: 11px; | ||||||
| 				text-align: left; | 				text-align: left; | ||||||
|  | 				border: solid 1px var(--panel); | ||||||
| 
 | 
 | ||||||
| 				&:hover { | 				&:hover { | ||||||
| 					color: var(--accent); | 					border-color: var(--inputBorderHover); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				&.selected { | ||||||
|  | 					border-color: var(--accent); | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				> .img { | 				> .img { | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ | ||||||
| 	<div class="main"> | 	<div class="main"> | ||||||
| 		<MkStickyContainer> | 		<MkStickyContainer> | ||||||
| 			<template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template> | 			<template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template> | ||||||
| 			<component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/> | 			<component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> | ||||||
| 		</MkStickyContainer> | 		</MkStickyContainer> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
|  | @ -66,7 +66,9 @@ export default defineComponent({ | ||||||
| 		const narrow = ref(false); | 		const narrow = ref(false); | ||||||
| 		const view = ref(null); | 		const view = ref(null); | ||||||
| 		const el = ref(null); | 		const el = ref(null); | ||||||
| 		const onInfo = (viewInfo) => { | 		const pageChanged = (page) => { | ||||||
|  | 			if (page == null) return; | ||||||
|  | 			const viewInfo = page[symbols.PAGE_INFO]; | ||||||
| 			if (isRef(viewInfo)) { | 			if (isRef(viewInfo)) { | ||||||
| 				watch(viewInfo, () => { | 				watch(viewInfo, () => { | ||||||
| 					childInfo.value = viewInfo.value; | 					childInfo.value = viewInfo.value; | ||||||
|  | @ -311,7 +313,7 @@ export default defineComponent({ | ||||||
| 			narrow, | 			narrow, | ||||||
| 			view, | 			view, | ||||||
| 			el, | 			el, | ||||||
| 			onInfo, | 			pageChanged, | ||||||
| 			childInfo, | 			childInfo, | ||||||
| 			pageProps, | 			pageProps, | ||||||
| 			component, | 			component, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue