fix: iroiro
Signed-off-by: mattyatea <mattyacocacora0@gmail.com> カスタム絵文字一覧を一覧、新着、申請中に分離 (cherry picked from commit 5c464cd1bc177f0276397a686160c5c77e774d10) fix: 絵文字管理画面の表示不具合の修正 (cherry picked from commit 5300ff763bb008699b35dc6e219b50ac1c4fd7af) Signed-off-by: mattyatea <mattyacocacora0@gmail.com> Fix:draftが2重に宣言されてた Signed-off-by: mattyatea <mattyacocacora0@gmail.com>
This commit is contained in:
parent
08311ece41
commit
7149961f33
|
@ -824,6 +824,7 @@ export interface Locale {
|
|||
"high": string;
|
||||
"middle": string;
|
||||
"low": string;
|
||||
"list": string;
|
||||
"emailNotConfiguredWarning": string;
|
||||
"ratio": string;
|
||||
"previewNoteText": string;
|
||||
|
@ -1022,6 +1023,10 @@ export interface Locale {
|
|||
"notesSearchNotAvailable": string;
|
||||
"license": string;
|
||||
"draft": string;
|
||||
"newEmojis": string;
|
||||
"undrafted": string;
|
||||
"draftEmojis": string;
|
||||
"emojiNameValidation": string;
|
||||
"unfavoriteConfirm": string;
|
||||
"myClips": string;
|
||||
"drivecleaner": string;
|
||||
|
|
|
@ -821,6 +821,7 @@ priority: "優先度"
|
|||
high: "高"
|
||||
middle: "中"
|
||||
low: "低"
|
||||
list: "一覧"
|
||||
emailNotConfiguredWarning: "メールアドレスの設定がされていません。"
|
||||
ratio: "比率"
|
||||
previewNoteText: "本文をプレビュー"
|
||||
|
@ -1019,6 +1020,10 @@ sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キ
|
|||
notesSearchNotAvailable: "ノート検索は利用できません。"
|
||||
license: "ライセンス"
|
||||
draft: "ドラフト"
|
||||
newEmojis: "新着の絵文字"
|
||||
undrafted: "ドラフト解除"
|
||||
draftEmojis: "ドラフトされてる絵文字"
|
||||
emojiNameValidation: "名前には英数字と_が利用できます。"
|
||||
unfavoriteConfirm: "お気に入り解除しますか?"
|
||||
myClips: "自分のクリップ"
|
||||
drivecleaner: "ドライブクリーナー"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"version": "2023.10.1",
|
||||
"version": "2023.10.123",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -35,6 +35,8 @@ export const paramDef = {
|
|||
type: 'string',
|
||||
} },
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean', nullable: true },
|
||||
localOnly: { type: 'boolean', nullable: true },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['name', 'fileId'],
|
||||
|
@ -42,8 +44,8 @@ export const paramDef = {
|
|||
|
||||
// TODO: ロジックをサービスに切り出す
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
|
@ -64,12 +66,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
category: ps.category ?? null,
|
||||
aliases: ps.aliases ?? [],
|
||||
license: ps.license ?? null,
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
host: null,
|
||||
draft: true,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||
});
|
||||
|
||||
this.moderationLogService.insertModerationLog(me, 'addEmoji', {
|
||||
await this.moderationLogService.log(me, 'addCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
emoji: emoji,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
<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">{{ 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">
|
||||
<MkButton primary class="edit" @click="editDraft(emoji)">
|
||||
{{ i18n.ts.edit }}
|
||||
</MkButton>
|
||||
<MkButton class="draft" @click="undrafted(emoji)">
|
||||
{{ i18n.ts.undrafted }}
|
||||
</MkButton>
|
||||
<MkButton danger class="delete" @click="deleteDraft(emoji)">
|
||||
{{ i18n.ts.delete }}
|
||||
</MkButton>
|
||||
</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';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
|
||||
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 {
|
||||
align-items: center;
|
||||
padding: 11px;
|
||||
text-align: left;
|
||||
border: solid 1px var(--panel);
|
||||
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;
|
||||
margin-bottom: 12px;
|
||||
> img {
|
||||
max-height: 64px;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
> .imgDark {
|
||||
display: grid;
|
||||
grid-column: 2;
|
||||
background-color: #000;
|
||||
margin-bottom: 12px;
|
||||
> img {
|
||||
max-height: 64px;
|
||||
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-template-rows: 42px;
|
||||
margin-top: 6px;
|
||||
|
||||
> .edit {
|
||||
grid-row: 1;
|
||||
width: 100%;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
> .draft {
|
||||
grid-row: 2;
|
||||
width: 100%;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
> .delete {
|
||||
grid-row: 3;
|
||||
width: 100%;
|
||||
margin: 6px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -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>
|
|
@ -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>
|
|
@ -7,11 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkModalWindow
|
||||
ref="dialog"
|
||||
:width="400"
|
||||
:withOkButton="true"
|
||||
:withOkButton="false "
|
||||
@close="dialog.close()"
|
||||
@closed="$emit('closed')"
|
||||
>
|
||||
<template v-if="emoji" #header>:{{ emoji.name }}:</template>
|
||||
<template v-else-if="isRequest" #header>{{ i18n.ts.requestCustomEmojis }}</template>
|
||||
<template v-else #header>New emoji</template>
|
||||
|
||||
<div>
|
||||
|
@ -34,6 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkButton rounded style="margin: 0 auto;" @click="changeImage">{{ i18n.ts.selectFile }}</MkButton>
|
||||
<MkInput v-model="name" pattern="[a-z0-9_]">
|
||||
<template #label>{{ i18n.ts.name }}</template>
|
||||
<template #caption>{{ i18n.ts.emojiNameValidation }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="category" :datalist="customEmojiCategories">
|
||||
<template #label>{{ i18n.ts.category }}</template>
|
||||
|
@ -45,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkInput v-model="license">
|
||||
<template #label>{{ i18n.ts.license }}</template>
|
||||
</MkInput>
|
||||
<MkFolder>
|
||||
<MkFolder v-if="!isRequest">
|
||||
<template #label>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction }}</template>
|
||||
<template #suffix>{{ rolesThatCanBeUsedThisEmojiAsReaction.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisEmojiAsReaction.length }}</template>
|
||||
|
||||
|
@ -64,15 +66,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkFolder>
|
||||
<MkSwitch v-model="isSensitive">isSensitive</MkSwitch>
|
||||
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
|
||||
<MkButton v-if="emoji" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
<div :class="$style.footer">
|
||||
<MkButton primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-check"></i> {{ props.emoji ? i18n.ts.update : i18n.ts.create }}</MkButton>
|
||||
<MkSwitch v-if="!isRequest" v-model="draft" :disabled="isRequest">
|
||||
{{ i18n.ts.draft }}
|
||||
</MkSwitch>
|
||||
<MkButton v-if="!isRequest" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
<div :class="$style.footer">
|
||||
<div :class="$style.footerButtons">
|
||||
<MkButton v-if="!isRequest" danger rounded style="margin: 0 auto;" @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
<MkButton v-if="validation" primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-check"></i> {{ props.emoji ? i18n.ts.update : i18n.ts.create }}</MkButton>
|
||||
<MkButton v-else rounded style="margin: 0 auto;"><i class="ti ti-check"></i> {{ props.emoji ? i18n.ts.update : i18n.ts.create }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkModalWindow>
|
||||
|
@ -81,6 +85,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { computed, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { DriveFile } from 'misskey-js/built/entities.js';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
|
@ -109,17 +114,18 @@ let roleIdsThatCanBeUsedThisEmojiAsReaction = $ref(props.emoji ? props.emoji.rol
|
|||
let rolesThatCanBeUsedThisEmojiAsReaction = $ref([]);
|
||||
let file = $ref<Misskey.entities.DriveFile>();
|
||||
let chooseFile: DriveFile|null = $ref(null);
|
||||
let draft = $ref(props.emoji.draft);
|
||||
let draft = $ref(props.emoji ? props.emoji.draft : false);
|
||||
let isRequest = $ref(props.isRequest);
|
||||
let url;
|
||||
|
||||
watch($$(roleIdsThatCanBeUsedThisEmojiAsReaction), async () => {
|
||||
rolesThatCanBeUsedThisEmojiAsReaction = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
|
||||
}, { immediate: true });
|
||||
|
||||
const imgUrl = computed(() => file ? file.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
|
||||
let draft = $ref(props.emoji.draft);
|
||||
let isRequest = $ref(props.isRequest);
|
||||
|
||||
const validation = computed(() => {
|
||||
return name.match(/^[a-zA-Z0-9_]+$/) && imgUrl.value != null;
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
|
||||
(ev: 'closed'): void
|
||||
|
@ -208,8 +214,9 @@ async function done() {
|
|||
const params = {
|
||||
name,
|
||||
category: category === '' ? null : category,
|
||||
aliases: aliases.split(' ').filter(x => x !== ''),
|
||||
aliases: aliases.replace(' ', ' ').split(' ').filter(x => x !== ''),
|
||||
license: license === '' ? null : license,
|
||||
draft: draft,
|
||||
isSensitive,
|
||||
localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id),
|
||||
|
@ -218,7 +225,7 @@ async function done() {
|
|||
if (file) {
|
||||
params.fileId = file.id;
|
||||
}
|
||||
|
||||
console.log(props.emoji);
|
||||
if (props.emoji) {
|
||||
await os.apiWithDialog('admin/emoji/update', {
|
||||
id: props.emoji.id,
|
||||
|
@ -234,7 +241,9 @@ async function done() {
|
|||
|
||||
dialog.close();
|
||||
} else {
|
||||
const created = await os.apiWithDialog('admin/emoji/add', params);
|
||||
const created = isRequest
|
||||
? await os.apiWithDialog('admin/emoji/add-draft', params)
|
||||
: await os.apiWithDialog('admin/emoji/add', params);
|
||||
|
||||
emit('done', {
|
||||
created: created,
|
||||
|
@ -313,4 +322,11 @@ async function del() {
|
|||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
}
|
||||
|
||||
.footerButtons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
|
@ -4,11 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps">
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer v-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
|
||||
<MkButton v-if="$i && ($i.isModerator || $i.policies.canManageCustomEmojis)" primary link to="/custom-emojis-manager">{{ i18n.ts.manageCustomEmojis }}</MkButton>
|
||||
<MkButton v-if="$i && (!$i.isModerator && !$i.policies.canManageCustomEmojis && $i.policies.canRequestCustomEmojis)" primary @click="edit">{{ i18n.ts.requestCustomEmojis }}</MkButton>
|
||||
<MkButton v-if="$i && (!$i.isModerator && $i.policies.canRequestCustomEmojis)" primary @click="edit">{{ i18n.ts.requestCustomEmojis }}</MkButton>
|
||||
|
||||
<div class="query">
|
||||
<div class="query" style="margin-top: 10px;">
|
||||
<MkInput v-model="q" class="" :placeholder="i18n.ts.search">
|
||||
<template #prefix><i class="ti ti-search"></i></template>
|
||||
</MkInput>
|
||||
|
@ -30,28 +32,48 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category">
|
||||
<template #header>{{ category || i18n.ts.other }}</template>
|
||||
<div :class="$style.emojis">
|
||||
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" :emoji="emoji" :draft="emoji.draft"/>
|
||||
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category && !e.draft)" :key="emoji.name" :emoji="emoji" :draft="emoji.draft"/>
|
||||
</div>
|
||||
</MkFoldableSection>
|
||||
</MkSpacer>
|
||||
<MkSpacer v-if="tab === 'draft'" :contentMax="1000" :marginMin="20">
|
||||
<div :class="$style.emojis">
|
||||
<XEmoji v-for="emoji in draftEmojis" :key="emoji.name" :emoji="emoji" :draft="emoji.draft"/>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, defineAsyncComponent } from 'vue';
|
||||
import { watch, defineAsyncComponent, ref, computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import XEmoji from './emojis.emoji.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import { customEmojis, customEmojiCategories, getCustomEmojiTags } from '@/custom-emojis.js';
|
||||
import { customEmojis, customEmojiCategories } from '@/custom-emojis.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os';
|
||||
import { $i } from '@/account.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
let tab = $ref('emojis');
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => [{
|
||||
key: 'emojis',
|
||||
title: i18n.ts.list,
|
||||
}, {
|
||||
key: 'draft',
|
||||
title: i18n.ts.draftEmojis,
|
||||
}]);
|
||||
|
||||
definePageMetadata(ref({}));
|
||||
|
||||
const customEmojiTags = getCustomEmojiTags();
|
||||
let q = $ref('');
|
||||
let searchEmojis = $ref<Misskey.entities.CustomEmoji[]>(null);
|
||||
let selectedTags = $ref(new Set());
|
||||
const draftEmojis = customEmojis.value.filter(emoji => emoji.draft);
|
||||
|
||||
function search() {
|
||||
if ((q === '' || q == null) && selectedTags.size === 0) {
|
||||
|
@ -74,24 +96,8 @@ function search() {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleTag(tag) {
|
||||
if (selectedTags.has(tag)) {
|
||||
selectedTags.delete(tag);
|
||||
} else {
|
||||
selectedTags.add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
const edit = () => {
|
||||
os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), {
|
||||
emoji: {
|
||||
name: '',
|
||||
category: null,
|
||||
aliases: [],
|
||||
license: '',
|
||||
url: '',
|
||||
draft: true,
|
||||
},
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkEmojiEditDialog.vue')), {
|
||||
isRequest: true,
|
||||
}, {
|
||||
done: result => {
|
||||
|
@ -107,6 +113,11 @@ watch($$(q), () => {
|
|||
watch($$(selectedTags), () => {
|
||||
search();
|
||||
}, { deep: true });
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.customEmojis,
|
||||
icon: null,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
|
@ -89,9 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</FormSection>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
|
||||
<XEmojis/>
|
||||
</MkSpacer>
|
||||
<XEmojis v-else-if="tab === 'emojis'"/>
|
||||
<MkSpacer v-else-if="tab === 'federation'" :contentMax="1000" :marginMin="20">
|
||||
<XFederation/>
|
||||
</MkSpacer>
|
||||
|
|
|
@ -273,7 +273,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkSwitch v-model="role.policies.canRequestCustomEmojis.value" :disabled="role.policies.canRequestCustomEmojis.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange v-model="role.policies.canRequestCustomEmojis.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<MkRange v-model="role.policies.canRequestCustomEmojis.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
|
|
|
@ -10,71 +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>
|
||||
<MkCustomEmojiEditLocal/>
|
||||
</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 v-if="tab === 'draft'" class="draft">
|
||||
<MkCustomEmojiEditDraft/>
|
||||
</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>
|
||||
</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>
|
||||
|
@ -83,105 +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, selectFiles } from '@/scripts/select-file.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
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 emojisPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
||||
|
||||
const tab = ref('local');
|
||||
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 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 tab = ref('draft');
|
||||
|
||||
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);
|
||||
}
|
||||
//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,
|
||||
isRequest: false,
|
||||
}, {
|
||||
done: result => {
|
||||
if (result.updated) {
|
||||
emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({
|
||||
...oldEmoji,
|
||||
...result.updated,
|
||||
}));
|
||||
} else if (result.deleted) {
|
||||
emojisPaginationComponent.value.removeItem(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.ts.import,
|
||||
icon: 'ti ti-plus',
|
||||
action: () => { im(emoji); },
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
};
|
||||
|
||||
const menu = (ev: MouseEvent) => {
|
||||
os.popupMenu([{
|
||||
icon: 'ti ti-download',
|
||||
|
@ -224,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',
|
||||
|
@ -307,6 +102,9 @@ const headerActions = $computed(() => [{
|
|||
}]);
|
||||
|
||||
const headerTabs = $computed(() => [{
|
||||
key: 'draft',
|
||||
title: i18n.ts.draftEmojis,
|
||||
}, {
|
||||
key: 'local',
|
||||
title: i18n.ts.local,
|
||||
}, {
|
||||
|
@ -321,110 +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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .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>
|
||||
|
|
Loading…
Reference in New Issue