feat(frontend): リモート絵文字のインポート時に詳細を確認できるように (#15344)
* feat(frontend): リモート絵文字のインポート時に詳細を確認できるように * 追加対応 * MkInput -> MkKeyValue
This commit is contained in:
parent
b53800bd84
commit
33bd08a3d1
@ -23,6 +23,7 @@
|
||||
(Based on https://github.com/Otaku-Social/maniakey/pull/14)
|
||||
- Enhance: AiScriptの拡張API関数において引数の型チェックをより厳格に
|
||||
- Enhance: クエリパラメータでuiを一時的に変更できるように #15240
|
||||
- Enhance: リモート絵文字のインポート時に詳細を確認できるように #15336
|
||||
- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正
|
||||
- Fix: サーバー情報メニューに区切り線が不足していたのを修正
|
||||
- Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正
|
||||
|
4
locales/index.d.ts
vendored
4
locales/index.d.ts
vendored
@ -10635,6 +10635,10 @@ export interface Locale extends ILocale {
|
||||
"logNothing": string;
|
||||
};
|
||||
"_remote": {
|
||||
/**
|
||||
* 選択行の詳細
|
||||
*/
|
||||
"selectionRowDetail": string;
|
||||
/**
|
||||
* 選択行をインポート
|
||||
*/
|
||||
|
@ -2837,6 +2837,7 @@ _customEmojisManager:
|
||||
failureLogNothing: "失敗ログはありません。"
|
||||
logNothing: "ログはありません。"
|
||||
_remote:
|
||||
selectionRowDetail: "選択行の詳細"
|
||||
importSelectionRows: "選択行をインポート"
|
||||
importSelectionRangesRows: "選択範囲の行をインポート"
|
||||
importEmojisButton: "チェックされた絵文字をインポート"
|
||||
|
132
packages/frontend/src/components/MkRemoteEmojiEditDialog.vue
Normal file
132
packages/frontend/src/components/MkRemoteEmojiEditDialog.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkWindow
|
||||
ref="windowEl"
|
||||
:initialWidth="400"
|
||||
:initialHeight="500"
|
||||
:canResize="true"
|
||||
@close="windowEl?.close()"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
<template #header>:{{ name }}:</template>
|
||||
|
||||
<div style="display: flex; flex-direction: column; min-height: 100%;">
|
||||
<MkSpacer :marginMin="20" :marginMax="28" style="flex-grow: 1;">
|
||||
<div class="_gaps_m">
|
||||
<div v-if="imgUrl != null" :class="$style.imgs">
|
||||
<div style="background: #000;" :class="$style.imgContainer">
|
||||
<img :src="imgUrl" :class="$style.img" :alt="name"/>
|
||||
</div>
|
||||
<div style="background: #222;" :class="$style.imgContainer">
|
||||
<img :src="imgUrl" :class="$style.img" :alt="name"/>
|
||||
</div>
|
||||
<div style="background: #ddd;" :class="$style.imgContainer">
|
||||
<img :src="imgUrl" :class="$style.img" :alt="name"/>
|
||||
</div>
|
||||
<div style="background: #fff;" :class="$style.imgContainer">
|
||||
<img :src="imgUrl" :class="$style.img" :alt="name"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.id }}</template>
|
||||
<template #value>{{ name }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.host }}</template>
|
||||
<template #value>{{ host }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.license }}</template>
|
||||
<template #value>{{ license }}</template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
<div :class="$style.footer">
|
||||
<MkButton primary rounded style="margin: 0 auto;" @click="done">
|
||||
<i class="ti ti-plus"></i> {{ i18n.ts.import }}
|
||||
</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</MkWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkWindow from '@/components/MkWindow.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
const props = defineProps<{
|
||||
emoji: {
|
||||
id: string,
|
||||
name: string,
|
||||
host: string,
|
||||
license: string | null,
|
||||
url: string
|
||||
},
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
// 必要なら戻り値を増やす
|
||||
(ev: 'done'): void,
|
||||
(ev: 'closed'): void
|
||||
}>();
|
||||
|
||||
const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
|
||||
|
||||
const name = computed(() => props.emoji.name);
|
||||
const host = computed(() => props.emoji.host);
|
||||
const license = computed(() => props.emoji.license);
|
||||
const imgUrl = computed(() => props.emoji.url);
|
||||
|
||||
async function done() {
|
||||
await os.apiWithDialog('admin/emoji/copy', {
|
||||
emojiId: props.emoji.id,
|
||||
});
|
||||
|
||||
emit('done');
|
||||
windowEl.value?.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.imgs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.imgContainer {
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.img {
|
||||
display: block;
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: sticky;
|
||||
z-index: 10000;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 12px;
|
||||
border-top: solid 0.5px var(--MI_THEME-divider);
|
||||
background: var(--MI_THEME-acrylicBg);
|
||||
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||
backdrop-filter: var(--MI-blur, blur(15px));
|
||||
}
|
||||
</style>
|
@ -34,6 +34,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
>
|
||||
<template #label>host</template>
|
||||
</MkInput>
|
||||
<MkInput
|
||||
v-model="queryLicense"
|
||||
type="search"
|
||||
autocapitalize="off"
|
||||
:class="[$style.col3, $style.row1]"
|
||||
@enter="onSearchRequest"
|
||||
>
|
||||
<template #label>license</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput
|
||||
v-model="queryUri"
|
||||
type="search"
|
||||
@ -115,6 +125,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, useCssModule } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkRemoteEmojiEditDialog from '@/components/MkRemoteEmojiEditDialog.vue';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
@ -135,7 +146,7 @@ import { deviceKind } from '@/scripts/device-kind.js';
|
||||
import MkPagingButtons from '@/components/MkPagingButtons.vue';
|
||||
import MkSortOrderEditor from '@/components/MkSortOrderEditor.vue';
|
||||
import { SortOrder } from '@/components/MkSortOrderEditor.define.js';
|
||||
import { useLoading } from "@/components/hook/useLoading.js";
|
||||
import { useLoading } from '@/components/hook/useLoading.js';
|
||||
|
||||
type GridItem = {
|
||||
checked: boolean;
|
||||
@ -178,12 +189,37 @@ function setupGrid(): GridSetting {
|
||||
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto' },
|
||||
{ bindTo: 'name', title: 'name', type: 'text', editable: false, width: 'auto' },
|
||||
{ bindTo: 'host', title: 'host', type: 'text', editable: false, width: 'auto' },
|
||||
{ bindTo: 'license', title: 'license', type: 'text', editable: false, width: 200 },
|
||||
{ bindTo: 'uri', title: 'uri', type: 'text', editable: false, width: 'auto' },
|
||||
{ bindTo: 'publicUrl', title: 'publicUrl', type: 'text', editable: false, width: 'auto' },
|
||||
],
|
||||
cells: {
|
||||
contextMenuFactory: (col, row, value, context) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
text: i18n.ts._customEmojisManager._remote.selectionRowDetail,
|
||||
icon: 'ti ti-info-circle',
|
||||
action: async () => {
|
||||
const target = customEmojis.value[row.index];
|
||||
const { dispose } = os.popup(MkRemoteEmojiEditDialog, {
|
||||
emoji: {
|
||||
id: target.id,
|
||||
name: target.name,
|
||||
host: target.host!,
|
||||
license: target.license,
|
||||
url: target.publicUrl,
|
||||
},
|
||||
}, {
|
||||
done: () => {
|
||||
dispose();
|
||||
},
|
||||
closed: () => {
|
||||
dispose();
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: i18n.ts._customEmojisManager._remote.importSelectionRangesRows,
|
||||
@ -207,6 +243,7 @@ const currentPage = ref<number>(0);
|
||||
|
||||
const queryName = ref<string | null>(null);
|
||||
const queryHost = ref<string | null>(null);
|
||||
const queryLicense = ref<string | null>(null);
|
||||
const queryUri = ref<string | null>(null);
|
||||
const queryPublicUrl = ref<string | null>(null);
|
||||
const previousQuery = ref<string | undefined>(undefined);
|
||||
@ -229,6 +266,7 @@ async function onSearchRequest() {
|
||||
function onQueryResetButtonClicked() {
|
||||
queryName.value = null;
|
||||
queryHost.value = null;
|
||||
queryLicense.value = null;
|
||||
queryUri.value = null;
|
||||
queryPublicUrl.value = null;
|
||||
}
|
||||
@ -306,6 +344,7 @@ async function refreshCustomEmojis() {
|
||||
const query: Misskey.entities.V2AdminEmojiListRequest['query'] = {
|
||||
name: emptyStrToUndefined(queryName.value),
|
||||
host: emptyStrToUndefined(queryHost.value),
|
||||
license: emptyStrToUndefined(queryLicense.value),
|
||||
uri: emptyStrToUndefined(queryUri.value),
|
||||
publicUrl: emptyStrToUndefined(queryPublicUrl.value),
|
||||
hostType: 'remote',
|
||||
@ -330,6 +369,7 @@ async function refreshCustomEmojis() {
|
||||
id: it.id,
|
||||
url: it.publicUrl,
|
||||
name: it.name,
|
||||
license: it.license,
|
||||
host: it.host!,
|
||||
}));
|
||||
}
|
||||
@ -356,6 +396,10 @@ onMounted(async () => {
|
||||
grid-column: 2 / 3;
|
||||
}
|
||||
|
||||
.col3 {
|
||||
grid-column: 3 / 4;
|
||||
}
|
||||
|
||||
.root {
|
||||
padding: 16px;
|
||||
}
|
||||
@ -366,7 +410,7 @@ onMounted(async () => {
|
||||
|
||||
.searchArea {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
|
@ -78,6 +78,7 @@ 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 MkRemoteEmojiEditDialog from '@/components/MkRemoteEmojiEditDialog.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import { selectFile } from '@/scripts/select-file.js';
|
||||
@ -159,6 +160,19 @@ const edit = (emoji) => {
|
||||
});
|
||||
};
|
||||
|
||||
const detailRemoteEmoji = (emoji) => {
|
||||
const { dispose } = os.popup(MkRemoteEmojiEditDialog, {
|
||||
emoji: emoji,
|
||||
}, {
|
||||
done: () => {
|
||||
dispose();
|
||||
},
|
||||
closed: () => {
|
||||
dispose();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const importEmoji = (emoji) => {
|
||||
os.apiWithDialog('admin/emoji/copy', {
|
||||
emojiId: emoji.id,
|
||||
@ -169,6 +183,10 @@ const remoteMenu = (emoji, ev: MouseEvent) => {
|
||||
os.popupMenu([{
|
||||
type: 'label',
|
||||
text: ':' + emoji.name + ':',
|
||||
}, {
|
||||
text: i18n.ts.details,
|
||||
icon: 'ti ti-info-circle',
|
||||
action: () => { detailRemoteEmoji(emoji); },
|
||||
}, {
|
||||
text: i18n.ts.import,
|
||||
icon: 'ti ti-plus',
|
||||
|
Loading…
Reference in New Issue
Block a user