From 4c7cd47fb6e7b3d7e9db02343e3de9e8ab8a0c0f Mon Sep 17 00:00:00 2001 From: tar_bin <tar.bin.master@gmail.com> Date: Fri, 18 Aug 2023 21:41:33 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=B5=B5=E6=96=87=E5=AD=97=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=94=BB=E9=9D=A2=E3=81=AE=E8=A1=A8=E7=A4=BA=E4=B8=8D?= =?UTF-8?q?=E5=85=B7=E5=90=88=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 5300ff763bb008699b35dc6e219b50ac1c4fd7af) --- .../src/components/MKCustomEmojiEditDraft.vue | 229 +++++++ .../src/components/MkCustomEmojiEditLocal.vue | 225 +++++++ .../components/MkCustomEmojiEditRemote.vue | 110 ++++ .../MkEmojiEditDialog.vue} | 0 packages/frontend/src/pages/about.emojis.vue | 2 +- .../src/pages/custom-emojis-manager.vue | 562 +----------------- 6 files changed, 582 insertions(+), 546 deletions(-) create mode 100644 packages/frontend/src/components/MKCustomEmojiEditDraft.vue create mode 100644 packages/frontend/src/components/MkCustomEmojiEditLocal.vue create mode 100644 packages/frontend/src/components/MkCustomEmojiEditRemote.vue rename packages/frontend/src/{pages/emoji-edit-dialog.vue => components/MkEmojiEditDialog.vue} (100%) diff --git a/packages/frontend/src/components/MKCustomEmojiEditDraft.vue b/packages/frontend/src/components/MKCustomEmojiEditDraft.vue new file mode 100644 index 0000000000..17576b7b06 --- /dev/null +++ b/packages/frontend/src/components/MKCustomEmojiEditDraft.vue @@ -0,0 +1,229 @@ +<template> +<MkPagination ref="emojisDraftPaginationComponent" :pagination="paginationDraft"> + <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template> + <template #default="{items}"> + <div class="ldhfsamy"> + <template v-for="emoji in items" :key="emoji.id"> + <div class="emoji _panel"> + <div class="img"> + <div class="imgLight"><img :src="emoji.url" :alt="emoji.name"/></div> + <div class="imgDark"><img :src="emoji.url" :alt="emoji.name"/></div> + </div> + <div class="info"> + <div class="name _monospace">{{ i18n.ts.name }}: {{ emoji.name }}</div> + <div class="category">{{ i18n.ts.category }}:{{ emoji.category }}</div> + <div class="aliases">{{ i18n.ts.tags }}:{{ emoji.aliases.join(' ') }}</div> + <div class="license">{{ i18n.ts.license }}:{{ emoji.license }}</div> + </div> + <div class="edit-button"> + <button class="edit _button" @click="editDraft(emoji)"> + {{ i18n.ts.edit }} + </button> + <button class="draft _button" @click="undrafted(emoji)"> + {{ i18n.ts.undrafted }} + </button> + <button class="delete _button" @click="deleteDraft(emoji)"> + {{ i18n.ts.delete }} + </button> + </div> + </div> + </template> + </div> + </template> +</MkPagination> +</template> + +<script lang="ts" setup> +import { computed, defineAsyncComponent, ref, shallowRef } from 'vue'; +import MkPagination from '@/components/MkPagination.vue'; +import * as os from '@/os'; +import { i18n } from '@/i18n'; + +const emojisDraftPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>(); + +const query = ref(null); + +const paginationDraft = { + endpoint: 'admin/emoji/list' as const, + limit: 30, + params: computed(() => ({ + query: (query.value && query.value !== '') ? query.value : null, + draft: true, + })), +}; + +const editDraft = (emoji) => { + os.popup(defineAsyncComponent(() => import('@/components/MkEmojiEditDialog.vue')), { + emoji: emoji, + isRequest: false, + }, { + done: result => { + if (result.updated) { + emojisDraftPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({ + ...oldEmoji, + ...result.updated, + })); + emojisDraftPaginationComponent.value.reload(); + } else if (result.deleted) { + emojisDraftPaginationComponent.value.removeItem((item) => item.id === emoji.id); + emojisDraftPaginationComponent.value.reload(); + } + }, + }, 'closed'); +}; + +async function undrafted(emoji) { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('undraftAreYouSure', { x: emoji.name }), + }); + if (canceled) return; + + await os.api('admin/emoji/update', { + id: emoji.id, + name: emoji.name, + category: emoji.category, + aliases: emoji.aliases, + license: emoji.license, + draft: false, + isSensitive: emoji.isSensitive, + localOnly: emoji.localOnly, + roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction, + }); + + emojisDraftPaginationComponent.value.removeItem((item) => item.id === emoji.id); + emojisDraftPaginationComponent.value.reload(); +} + +async function deleteDraft(emoji) { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: emoji.name }), + }); + if (canceled) return; + + os.api('admin/emoji/delete', { + id: emoji.id, + }).then(() => { + emojisDraftPaginationComponent.value.removeItem((item) => item.id === emoji.id); + emojisDraftPaginationComponent.value.reload(); + }); +} +</script> + +<style lang="scss" scoped> +.empty { + margin: var(--margin); +} + +.ldhfsamy { + > .emoji { + display: grid; + grid-template-rows: 40px 1fr; + grid-template-columns: 1fr 150px; + align-items: center; + padding: 11px; + text-align: left; + border: solid 1px var(--panel); + width: 100%; + margin: 10px; + + > .img { + display: grid; + grid-row: 1; + grid-column: 1/ span 2; + grid-template-columns: 50% 50%; + place-content: center; + place-items: center; + + > .imgLight { + display: grid; + grid-column: 1; + background-color: #fff; + + > img { + max-height: 30px; + max-width: 100%; + } + } + + > .imgDark { + display: grid; + grid-column: 2; + background-color: #000; + + > img { + max-height: 30px; + max-width: 100%; + } + } + } + + > .info { + display: grid; + grid-row: 2; + grid-template-rows: 30px 30px 30px; + + > .name { + grid-row: 1; + text-overflow: ellipsis; + overflow: hidden; + } + + > .category { + grid-row: 2; + text-overflow: ellipsis; + overflow: hidden; + } + + > .aliases { + grid-row: 3; + text-overflow: ellipsis; + overflow: hidden; + } + + > .license { + grid-row: 4; + text-overflow: ellipsis; + overflow: hidden; + } + } + + > .edit-button { + display: grid; + grid-row: 2; + grid-template-rows: 30px 30px 30px; + + > .edit { + grid-row: 1; + background-color: var(--buttonBg); + margin: 2px; + + &:hover { + color: var(--accent); + } + } + + > .draft { + grid-row: 2; + background-color: var(--buttonBg); + margin: 2px; + + &:hover { + color: var(--accent); + } + } + + > .delete { + background-color: var(--buttonBg); + grid-row: 3; + margin: 2px; + + &:hover { + color: var(--accent); + } + } + } + } +} +</style> diff --git a/packages/frontend/src/components/MkCustomEmojiEditLocal.vue b/packages/frontend/src/components/MkCustomEmojiEditLocal.vue new file mode 100644 index 0000000000..7112a38430 --- /dev/null +++ b/packages/frontend/src/components/MkCustomEmojiEditLocal.vue @@ -0,0 +1,225 @@ +<template> +<MkInput v-model="query" :debounce="true" type="search"> + <template #prefix><i class="ti ti-search"></i></template> + <template #label>{{ i18n.ts.search }}</template> +</MkInput> +<MkSwitch v-model="selectMode" style="margin: 8px 0;"> + <template #label>Select mode</template> +</MkSwitch> +<div v-if="selectMode" class="_buttons"> + <MkButton inline @click="selectAll">Select all</MkButton> + <MkButton inline @click="setCategoryBulk">Set category</MkButton> + <MkButton inline @click="setTagBulk">Set tag</MkButton> + <MkButton inline @click="addTagBulk">Add tag</MkButton> + <MkButton inline @click="removeTagBulk">Remove tag</MkButton> + <MkButton inline @click="setLisenceBulk">Set Lisence</MkButton> + <MkButton inline danger @click="delBulk">Delete</MkButton> +</div> +<MkPagination ref="emojisPaginationComponent" :pagination="pagination" :displayLimit="100"> + <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template> + <template #default="{items}"> + <div class="ldhfsamy"> + <div v-for="emoji in items" :key="emoji.id"> + <button v-if="emoji.draft" class="emoji _panel _button emoji-draft" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)"> + <img :src="emoji.url" class="img" :alt="emoji.name"/> + <div class="body"> + <div class="name _monospace">{{ emoji.name + ' (draft)' }}</div> + <div class="info">{{ emoji.category }}</div> + </div> + </button> + <button v-else 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"/> + <div class="body"> + <div class="name _monospace">{{ emoji.name }}</div> + <div class="info">{{ emoji.category }}</div> + </div> + </button> + </div> + </div> + </template> +</MkPagination> +</template> + +<script lang="ts" setup> +import { computed, defineAsyncComponent, ref, shallowRef } from 'vue'; +import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkPagination from '@/components/MkPagination.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import * as os from '@/os'; +import { i18n } from '@/i18n'; + +const emojisPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>(); + +const query = ref(null); +const selectMode = ref(false); +const selectedEmojis = ref<string[]>([]); + +const pagination = { + endpoint: 'admin/emoji/list' as const, + limit: 30, + params: computed(() => ({ + query: (query.value && query.value !== '') ? query.value : null, + })), +}; + +const selectAll = () => { + if (selectedEmojis.value.length > 0) { + selectedEmojis.value = []; + } else { + selectedEmojis.value = emojisPaginationComponent.value.items.map(item => item.id); + } +}; + +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 edit = (emoji) => { + os.popup(defineAsyncComponent(() => import('@/components/MkEmojiEditDialog.vue')), { + emoji: emoji, + isRequest: false, + }, { + done: result => { + if (result.updated) { + emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({ + ...oldEmoji, + ...result.updated, + })); + emojisPaginationComponent.value.reload(); + } else if (result.deleted) { + emojisPaginationComponent.value.removeItem((item) => item.id === emoji.id); + } + }, + }, 'closed'); +}; + +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 setLisenceBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'License', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/set-license-bulk', { + ids: selectedEmojis.value, + license: 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.ts.deleteConfirm, + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/delete-bulk', { + ids: selectedEmojis.value, + }); + emojisPaginationComponent.value.reload(); +}; +</script> + +<style lang="scss" scoped> +.ldhfsamy { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); + grid-gap: var(--margin); + + div > .emoji { + display: flex; + align-items: center; + padding: 11px; + text-align: left; + border: solid 1px var(--panel); + width: 100%; + + &:hover { + border-color: var(--inputBorderHover); + } + + &.selected { + border-color: var(--accent); + } + + > .img { + width: 42px; + height: 42px; + } + + > .body { + padding: 0 0 0 8px; + white-space: nowrap; + overflow: hidden; + + > .name { + text-overflow: ellipsis; + overflow: hidden; + } + + > .info { + opacity: 0.5; + text-overflow: ellipsis; + overflow: hidden; + } + } + } +} + +.emoji-draft { + --c: rgb(255 196 0 / 15%);; + background-image: linear-gradient(45deg,var(--c) 16.67%,transparent 16.67%,transparent 50%,var(--c) 50%,var(--c) 66.67%,transparent 66.67%,transparent 100%); + background-size: 16px 16px; +} +</style> diff --git a/packages/frontend/src/components/MkCustomEmojiEditRemote.vue b/packages/frontend/src/components/MkCustomEmojiEditRemote.vue new file mode 100644 index 0000000000..26c8dd66ac --- /dev/null +++ b/packages/frontend/src/components/MkCustomEmojiEditRemote.vue @@ -0,0 +1,110 @@ +<template> +<FormSplit> + <MkInput v-model="queryRemote" :debounce="true" type="search"> + <template #prefix><i class="ti ti-search"></i></template> + <template #label>{{ i18n.ts.search }}</template> + </MkInput> + <MkInput v-model="host" :debounce="true"> + <template #label>{{ i18n.ts.host }}</template> + </MkInput> +</FormSplit> +<MkPagination :pagination="remotePagination" :displayLimit="100"> + <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template> + <template #default="{items}"> + <div class="ldhfsamy"> + <div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji, $event)"> + <img :src="emoji.url" class="img" :alt="emoji.name"/> + <div class="body"> + <div class="name _monospace">{{ emoji.name }}</div> + <div class="info">{{ emoji.host }}</div> + </div> + </div> + </div> + </template> +</MkPagination> +</template> + +<script lang="ts" setup> +import { computed, ref } from 'vue'; +import MkInput from '@/components/MkInput.vue'; +import MkPagination from '@/components/MkPagination.vue'; +import FormSplit from '@/components/form/split.vue'; +import * as os from '@/os'; +import { i18n } from '@/i18n'; + +const queryRemote = ref(null); +const host = ref(null); + +const remotePagination = { + endpoint: 'admin/emoji/list-remote' as const, + limit: 30, + params: computed(() => ({ + query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null, + host: (host.value && host.value !== '') ? host.value : null, + })), +}; + +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.ts.import, + icon: 'ti ti-plus', + action: () => { im(emoji); }, + }], ev.currentTarget ?? ev.target); +}; +</script> + +<style lang="scss" scoped> +.empty { + margin: var(--margin); +} + +.ldhfsamy { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); + grid-gap: 12px; + margin: var(--margin) 0; + + > .emoji { + display: flex; + align-items: center; + padding: 12px; + text-align: left; + + &:hover { + color: var(--accent); + } + + > .img { + width: 32px; + height: 32px; + } + + > .body { + padding: 0 0 0 8px; + white-space: nowrap; + overflow: hidden; + + > .name { + text-overflow: ellipsis; + overflow: hidden; + } + + > .info { + opacity: 0.5; + font-size: 90%; + text-overflow: ellipsis; + overflow: hidden; + } + } + } +} +</style> diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/components/MkEmojiEditDialog.vue similarity index 100% rename from packages/frontend/src/pages/emoji-edit-dialog.vue rename to packages/frontend/src/components/MkEmojiEditDialog.vue diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index 805662065c..a49a70c88a 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -120,7 +120,7 @@ function toggleTag(tag) { } const edit = () => { - os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), { + os.popup(defineAsyncComponent(() => import('@/components/MkEmojiEditDialog.vue')), { isRequest: true, }, { done: result => { diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index 5dcd2009ed..6e55ffb945 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -10,106 +10,13 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSpacer :contentMax="900"> <div class="ogwlenmc"> <div v-if="tab === 'local'" class="local"> - <MkInput v-model="query" :debounce="true" type="search"> - <template #prefix><i class="ti ti-search"></i></template> - <template #label>{{ i18n.ts.search }}</template> - </MkInput> - <MkSwitch v-model="selectMode" style="margin: 8px 0;"> - <template #label>Select mode</template> - </MkSwitch> - <div v-if="selectMode" class="_buttons"> - <MkButton inline @click="selectAll">Select all</MkButton> - <MkButton inline @click="setCategoryBulk">Set category</MkButton> - <MkButton inline @click="setTagBulk">Set tag</MkButton> - <MkButton inline @click="addTagBulk">Add tag</MkButton> - <MkButton inline @click="removeTagBulk">Remove tag</MkButton> - <MkButton inline @click="setLicenseBulk">Set License</MkButton> - <MkButton inline danger @click="delBulk">Delete</MkButton> - </div> - <MkPagination ref="emojisPaginationComponent" :pagination="pagination"> - <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template> - <template #default="{items}"> - <div class="ldhfsamy"> - <div v-for="emoji in items" :key="emoji.id"> - <button v-if="emoji.draft" class="emoji _panel _button emoji-draft" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)"> - <img :src="`/emoji/${emoji.name}.webp`" class="img" :alt="emoji.name"/> - <div class="body"> - <div class="name _monospace">{{ emoji.name + ' (draft)' }}</div> - <div class="info">{{ emoji.category }}</div> - </div> - </button> - <button v-else 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"/> - <div class="body"> - <div class="name _monospace">{{ emoji.name }}</div> - <div class="info">{{ emoji.category }}</div> - </div> - </button> - </div> - </div> - </template> - </MkPagination> + <MkCustomEmojiEditLocal/> </div> - <div v-if="tab === 'draft'" class="draft"> - <MkPagination ref="emojisDraftPaginationComponent" :pagination="paginationDraft"> - <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template> - <template #default="{items}"> - <div class="ldhfsamy"> - <template v-for="emoji in items" :key="emoji.id"> - <div class="emoji _panel"> - <div class="img"> - <div class="imgLight"><img :src="emoji.url" :alt="emoji.name"/></div> - <div class="imgDark"><img :src="emoji.url" :alt="emoji.name"/></div> - </div> - <div class="info"> - <div class="name _monospace">{{ i18n.ts.name }}: {{ emoji.name }}</div> - <div class="category">{{ i18n.ts.category }}:{{ emoji.category }}</div> - <div class="aliases">{{ i18n.ts.tags }}:{{ emoji.aliases.join(' ') }}</div> - <div class="license">{{ i18n.ts.license }}:{{ emoji.license }}</div> - </div> - <div class="edit-button"> - <button class="edit _button" @click="editDraft(emoji)"> - {{ i18n.ts.edit }} - </button> - <button class="draft _button" @click="undrafted(emoji)"> - {{ i18n.ts.undrafted }} - </button> - <button class="delete _button" @click="deleteDraft(emoji)"> - {{ i18n.ts.delete }} - </button> - </div> - </div> - </template> - </div> - </template> - </MkPagination> + <MkCustomEmojiEditDraft/> </div> - <div v-else-if="tab === 'remote'" class="remote"> - <FormSplit> - <MkInput v-model="queryRemote" :debounce="true" type="search"> - <template #prefix><i class="ti ti-search"></i></template> - <template #label>{{ i18n.ts.search }}</template> - </MkInput> - <MkInput v-model="host" :debounce="true"> - <template #label>{{ i18n.ts.host }}</template> - </MkInput> - </FormSplit> - <MkPagination :pagination="remotePagination"> - <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template> - <template #default="{items}"> - <div class="ldhfsamy"> - <div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji, $event)"> - <img :src="emoji.url" class="img" :alt="emoji.name"/> - <div class="body"> - <div class="name _monospace">{{ emoji.name }}</div> - <div class="info">{{ emoji.host }}</div> - </div> - </div> - </div> - </template> - </MkPagination> + <MkCustomEmojiEditRemote/> </div> </div> </MkSpacer> @@ -118,171 +25,30 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, ref, shallowRef } from 'vue'; -import MkButton from '@/components/MkButton.vue'; -import MkInput from '@/components/MkInput.vue'; -import MkPagination from '@/components/MkPagination.vue'; -import MkSwitch from '@/components/MkSwitch.vue'; -import FormSplit from '@/components/form/split.vue'; -import { selectFile } from '@/scripts/select-file.js'; -import * as os from '@/os.js'; -import { i18n } from '@/i18n.js'; -import { definePageMetadata } from '@/scripts/page-metadata.js'; - -const emojisPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>(); -const emojisDraftPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>(); +import { computed, defineAsyncComponent, ref } from 'vue'; +import MkCustomEmojiEditDraft from '@/components/MkCustomEmojiEditDraft.vue'; +import MkCustomEmojiEditLocal from '@/components/MkCustomEmojiEditLocal.vue'; +import MkCustomEmojiEditRemote from '@/components/MkCustomEmojiEditRemote.vue'; +import { selectFile } from '@/scripts/select-file'; +import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { definePageMetadata } from '@/scripts/page-metadata'; const tab = ref('draft'); -const query = ref(null); -const queryRemote = ref(null); -const host = ref(null); -const selectMode = ref(false); -const selectedEmojis = ref<string[]>([]); - -const pagination = { - endpoint: 'admin/emoji/list' as const, - limit: 30, - params: computed(() => ({ - query: (query.value && query.value !== '') ? query.value : null, - })), -}; - -const paginationDraft = { - endpoint: 'admin/emoji/list' as const, - limit: 30, - params: computed(() => ({ - query: (query.value && query.value !== '') ? query.value : null, - draft: true, - })), -}; - -const remotePagination = { - endpoint: 'admin/emoji/list-remote' as const, - limit: 30, - params: computed(() => ({ - query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null, - host: (host.value && host.value !== '') ? host.value : null, - })), -}; - -const selectAll = () => { - if (selectedEmojis.value.length > 0) { - selectedEmojis.value = []; - } else { - selectedEmojis.value = Array.from(emojisPaginationComponent.value.items.values(), item => item.id); - } -}; - -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) => { - os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), { + os.popup(defineAsyncComponent(() => import('@/components/MkEmojiEditDialog.vue')), { }, { done: result => { - if (result.created) { - emojisPaginationComponent.value.prepend(result.created); - emojisPaginationComponent.value.reload(); - } + //TODO: emitにして追加を反映 + // if (result.created) { + // emojisPaginationComponent.value.prepend(result.created); + // emojisPaginationComponent.value.reload(); + // } }, }, 'closed'); }; -const edit = (emoji) => { - os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), { - emoji: emoji, - }, { - done: result => { - if (result.updated) { - emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({ - ...oldEmoji, - ...result.updated, - })); - emojisPaginationComponent.value.reload(); - } else if (result.deleted) { - emojisPaginationComponent.value.removeItem(emoji.id); - } - }, - }, 'closed'); -}; - -const editDraft = (emoji) => { - os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), { - emoji: emoji, - isRequest: false, - }, { - done: result => { - if (result.updated) { - emojisDraftPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({ - ...oldEmoji, - ...result.updated, - })); - emojisDraftPaginationComponent.value.reload(); - } else if (result.deleted) { - emojisDraftPaginationComponent.value.removeItem((item) => item.id === emoji.id); - } - }, - }, 'closed'); -}; - -async function undrafted(emoji) { - const { canceled } = await os.confirm({ - type: 'warning', - text: i18n.t('undraftAreYouSure', { x: emoji.name }), - }); - if (canceled) return; - - await os.api('admin/emoji/update', { - id: emoji.id, - name: emoji.name, - category: emoji.category, - aliases: emoji.aliases, - license: emoji.license, - draft: false, - isSensitive: emoji.isSensitive, - localOnly: emoji.localOnly, - roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction, - }); - - emojisDraftPaginationComponent.value.removeItem((item) => item.id === emoji.id); -} - -async function deleteDraft(emoji) { - const { canceled } = await os.confirm({ - type: 'warning', - text: i18n.t('removeAreYouSure', { x: emoji.name }), - }); - if (canceled) return; - - os.api('admin/emoji/delete', { - id: emoji.id, - }).then(() => { - emojisDraftPaginationComponent.value.removeItem((item) => item.id === emoji.id); - }); -} - -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.ts.import, - icon: 'ti ti-plus', - action: () => { im(emoji); }, - }], ev.currentTarget ?? ev.target); -}; - const menu = (ev: MouseEvent) => { os.popupMenu([{ icon: 'ti ti-download', @@ -325,78 +91,6 @@ const menu = (ev: MouseEvent) => { }], 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 setLicenseBulk = async () => { - const { canceled, result } = await os.inputText({ - title: 'License', - }); - if (canceled) return; - await os.apiWithDialog('admin/emoji/set-license-bulk', { - ids: selectedEmojis.value, - license: 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.ts.deleteConfirm, - }); - if (canceled) return; - await os.apiWithDialog('admin/emoji/delete-bulk', { - ids: selectedEmojis.value, - }); - emojisPaginationComponent.value.reload(); -}; - const headerActions = $computed(() => [{ asFullButton: true, icon: 'ti ti-plus', @@ -425,226 +119,4 @@ definePageMetadata(computed(() => ({ </script> <style lang="scss" scoped> -.ogwlenmc { - > .local { - .empty { - margin: var(--margin); - } - - .ldhfsamy { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); - grid-gap: 12px; - margin: var(--margin) 0; - - div > .emoji { - display: flex; - align-items: center; - padding: 11px; - text-align: left; - border: solid 1px var(--panel); - width: 100%; - - &:hover { - border-color: var(--inputBorderHover); - } - - &.selected { - border-color: var(--accent); - } - - > .img { - width: 42px; - height: 42px; - } - - > .body { - padding: 0 0 0 8px; - white-space: nowrap; - overflow: hidden; - - > .name { - text-overflow: ellipsis; - overflow: hidden; - } - - > .info { - opacity: 0.5; - text-overflow: ellipsis; - overflow: hidden; - } - } - } - } - } - > .draft { - .empty { - margin: var(--margin); - } - - .ldhfsamy { - > .emoji { - display: grid; - grid-template-rows: 40px 1fr; - grid-template-columns: 1fr 150px; - align-items: center; - padding: 11px; - text-align: left; - border: solid 1px var(--panel); - width: 100%; - margin: 10px; - - > .img { - display: grid; - grid-row: 1; - grid-column: 1/ span 2; - grid-template-columns: 50% 50%; - place-content: center; - place-items: center; - - > .imgLight { - display: grid; - grid-column: 1; - background-color: #fff; - - > img { - max-height: 30px; - max-width: 100%; - } - } - - > .imgDark { - display: grid; - grid-column: 2; - background-color: #000; - - > img { - max-height: 30px; - max-width: 100%; - } - } - } - - > .info { - display: grid; - grid-row: 2; - grid-template-rows: 30px 30px 30px; - - > .name { - grid-row: 1; - text-overflow: ellipsis; - overflow: hidden; - } - - > .category { - grid-row: 2; - text-overflow: ellipsis; - overflow: hidden; - } - - > .aliases { - grid-row: 3; - text-overflow: ellipsis; - overflow: hidden; - } - - > .license { - grid-row: 4; - text-overflow: ellipsis; - overflow: hidden; - } - } - - > .edit-button { - display: grid; - grid-row: 2; - grid-template-rows: 30px 30px 30px; - - > .edit { - grid-row: 1; - background-color: var(--buttonBg); - margin: 2px; - - &:hover { - color: var(--accent); - } - } - - > .draft { - grid-row: 2; - background-color: var(--buttonBg); - margin: 2px; - - &:hover { - color: var(--accent); - } - } - - > .delete { - background-color: var(--buttonBg); - grid-row: 3; - margin: 2px; - - &:hover { - color: var(--accent); - } - } - } - } - } - } - - > .remote { - .empty { - margin: var(--margin); - } - - .ldhfsamy { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); - grid-gap: 12px; - margin: var(--margin) 0; - - > .emoji { - display: flex; - align-items: center; - padding: 12px; - text-align: left; - - &:hover { - color: var(--accent); - } - - > .img { - width: 32px; - height: 32px; - } - - > .body { - padding: 0 0 0 8px; - white-space: nowrap; - overflow: hidden; - - > .name { - text-overflow: ellipsis; - overflow: hidden; - } - - > .info { - opacity: 0.5; - font-size: 90%; - text-overflow: ellipsis; - overflow: hidden; - } - } - } - } - } -} - -.emoji-draft { - --c: rgb(255 196 0 / 15%);; - background-image: linear-gradient(45deg,var(--c) 16.67%,transparent 16.67%,transparent 50%,var(--c) 50%,var(--c) 66.67%,transparent 66.67%,transparent 100%); - background-size: 16px 16px; -} </style>