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)
|
(Based on https://github.com/Otaku-Social/maniakey/pull/14)
|
||||||
- Enhance: AiScriptの拡張API関数において引数の型チェックをより厳格に
|
- Enhance: AiScriptの拡張API関数において引数の型チェックをより厳格に
|
||||||
- Enhance: クエリパラメータでuiを一時的に変更できるように #15240
|
- Enhance: クエリパラメータでuiを一時的に変更できるように #15240
|
||||||
|
- Enhance: リモート絵文字のインポート時に詳細を確認できるように #15336
|
||||||
- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正
|
- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正
|
||||||
- Fix: サーバー情報メニューに区切り線が不足していたのを修正
|
- 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;
|
"logNothing": string;
|
||||||
};
|
};
|
||||||
"_remote": {
|
"_remote": {
|
||||||
|
/**
|
||||||
|
* 選択行の詳細
|
||||||
|
*/
|
||||||
|
"selectionRowDetail": string;
|
||||||
/**
|
/**
|
||||||
* 選択行をインポート
|
* 選択行をインポート
|
||||||
*/
|
*/
|
||||||
|
@ -2837,6 +2837,7 @@ _customEmojisManager:
|
|||||||
failureLogNothing: "失敗ログはありません。"
|
failureLogNothing: "失敗ログはありません。"
|
||||||
logNothing: "ログはありません。"
|
logNothing: "ログはありません。"
|
||||||
_remote:
|
_remote:
|
||||||
|
selectionRowDetail: "選択行の詳細"
|
||||||
importSelectionRows: "選択行をインポート"
|
importSelectionRows: "選択行をインポート"
|
||||||
importSelectionRangesRows: "選択範囲の行をインポート"
|
importSelectionRangesRows: "選択範囲の行をインポート"
|
||||||
importEmojisButton: "チェックされた絵文字をインポート"
|
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>
|
<template #label>host</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
<MkInput
|
||||||
|
v-model="queryLicense"
|
||||||
|
type="search"
|
||||||
|
autocapitalize="off"
|
||||||
|
:class="[$style.col3, $style.row1]"
|
||||||
|
@enter="onSearchRequest"
|
||||||
|
>
|
||||||
|
<template #label>license</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
<MkInput
|
<MkInput
|
||||||
v-model="queryUri"
|
v-model="queryUri"
|
||||||
type="search"
|
type="search"
|
||||||
@ -115,6 +125,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, useCssModule } from 'vue';
|
import { computed, onMounted, ref, useCssModule } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import MkRemoteEmojiEditDialog from '@/components/MkRemoteEmojiEditDialog.vue';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
@ -135,7 +146,7 @@ import { deviceKind } from '@/scripts/device-kind.js';
|
|||||||
import MkPagingButtons from '@/components/MkPagingButtons.vue';
|
import MkPagingButtons from '@/components/MkPagingButtons.vue';
|
||||||
import MkSortOrderEditor from '@/components/MkSortOrderEditor.vue';
|
import MkSortOrderEditor from '@/components/MkSortOrderEditor.vue';
|
||||||
import { SortOrder } from '@/components/MkSortOrderEditor.define.js';
|
import { SortOrder } from '@/components/MkSortOrderEditor.define.js';
|
||||||
import { useLoading } from "@/components/hook/useLoading.js";
|
import { useLoading } from '@/components/hook/useLoading.js';
|
||||||
|
|
||||||
type GridItem = {
|
type GridItem = {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
@ -178,12 +189,37 @@ function setupGrid(): GridSetting {
|
|||||||
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto' },
|
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto' },
|
||||||
{ bindTo: 'name', title: 'name', type: 'text', 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: '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: 'uri', title: 'uri', type: 'text', editable: false, width: 'auto' },
|
||||||
{ bindTo: 'publicUrl', title: 'publicUrl', type: 'text', editable: false, width: 'auto' },
|
{ bindTo: 'publicUrl', title: 'publicUrl', type: 'text', editable: false, width: 'auto' },
|
||||||
],
|
],
|
||||||
cells: {
|
cells: {
|
||||||
contextMenuFactory: (col, row, value, context) => {
|
contextMenuFactory: (col, row, value, context) => {
|
||||||
return [
|
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',
|
type: 'button',
|
||||||
text: i18n.ts._customEmojisManager._remote.importSelectionRangesRows,
|
text: i18n.ts._customEmojisManager._remote.importSelectionRangesRows,
|
||||||
@ -207,6 +243,7 @@ const currentPage = ref<number>(0);
|
|||||||
|
|
||||||
const queryName = ref<string | null>(null);
|
const queryName = ref<string | null>(null);
|
||||||
const queryHost = ref<string | null>(null);
|
const queryHost = ref<string | null>(null);
|
||||||
|
const queryLicense = ref<string | null>(null);
|
||||||
const queryUri = ref<string | null>(null);
|
const queryUri = ref<string | null>(null);
|
||||||
const queryPublicUrl = ref<string | null>(null);
|
const queryPublicUrl = ref<string | null>(null);
|
||||||
const previousQuery = ref<string | undefined>(undefined);
|
const previousQuery = ref<string | undefined>(undefined);
|
||||||
@ -229,6 +266,7 @@ async function onSearchRequest() {
|
|||||||
function onQueryResetButtonClicked() {
|
function onQueryResetButtonClicked() {
|
||||||
queryName.value = null;
|
queryName.value = null;
|
||||||
queryHost.value = null;
|
queryHost.value = null;
|
||||||
|
queryLicense.value = null;
|
||||||
queryUri.value = null;
|
queryUri.value = null;
|
||||||
queryPublicUrl.value = null;
|
queryPublicUrl.value = null;
|
||||||
}
|
}
|
||||||
@ -306,6 +344,7 @@ async function refreshCustomEmojis() {
|
|||||||
const query: Misskey.entities.V2AdminEmojiListRequest['query'] = {
|
const query: Misskey.entities.V2AdminEmojiListRequest['query'] = {
|
||||||
name: emptyStrToUndefined(queryName.value),
|
name: emptyStrToUndefined(queryName.value),
|
||||||
host: emptyStrToUndefined(queryHost.value),
|
host: emptyStrToUndefined(queryHost.value),
|
||||||
|
license: emptyStrToUndefined(queryLicense.value),
|
||||||
uri: emptyStrToUndefined(queryUri.value),
|
uri: emptyStrToUndefined(queryUri.value),
|
||||||
publicUrl: emptyStrToUndefined(queryPublicUrl.value),
|
publicUrl: emptyStrToUndefined(queryPublicUrl.value),
|
||||||
hostType: 'remote',
|
hostType: 'remote',
|
||||||
@ -330,6 +369,7 @@ async function refreshCustomEmojis() {
|
|||||||
id: it.id,
|
id: it.id,
|
||||||
url: it.publicUrl,
|
url: it.publicUrl,
|
||||||
name: it.name,
|
name: it.name,
|
||||||
|
license: it.license,
|
||||||
host: it.host!,
|
host: it.host!,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -356,6 +396,10 @@ onMounted(async () => {
|
|||||||
grid-column: 2 / 3;
|
grid-column: 2 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.col3 {
|
||||||
|
grid-column: 3 / 4;
|
||||||
|
}
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
@ -366,7 +410,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.searchArea {
|
.searchArea {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,7 @@ import { computed, defineAsyncComponent, ref, shallowRef } from 'vue';
|
|||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import MkRemoteEmojiEditDialog from '@/components/MkRemoteEmojiEditDialog.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import FormSplit from '@/components/form/split.vue';
|
import FormSplit from '@/components/form/split.vue';
|
||||||
import { selectFile } from '@/scripts/select-file.js';
|
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) => {
|
const importEmoji = (emoji) => {
|
||||||
os.apiWithDialog('admin/emoji/copy', {
|
os.apiWithDialog('admin/emoji/copy', {
|
||||||
emojiId: emoji.id,
|
emojiId: emoji.id,
|
||||||
@ -169,6 +183,10 @@ const remoteMenu = (emoji, ev: MouseEvent) => {
|
|||||||
os.popupMenu([{
|
os.popupMenu([{
|
||||||
type: 'label',
|
type: 'label',
|
||||||
text: ':' + emoji.name + ':',
|
text: ':' + emoji.name + ':',
|
||||||
|
}, {
|
||||||
|
text: i18n.ts.details,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
action: () => { detailRemoteEmoji(emoji); },
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.import,
|
text: i18n.ts.import,
|
||||||
icon: 'ti ti-plus',
|
icon: 'ti ti-plus',
|
||||||
|
Loading…
Reference in New Issue
Block a user