fix(frontend): 一部のグリッドのページネーションが正しく表示されない問題を修正 (#16147)
* fix(frontend): 一部のグリッドのページネーションが正しく表示されない問題を修正 * update changelog * fix
This commit is contained in:
parent
5537558fa7
commit
810a609df4
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Fix: ドライブファイルの選択が不安定な問題を修正
|
- Fix: ドライブファイルの選択が不安定な問題を修正
|
||||||
|
- Fix: コントロールパネルのファイル欄などのデザインが崩れている問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
-
|
-
|
||||||
|
|
|
@ -5,33 +5,35 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<MkPagination v-slot="{items}" :pagination="pagination" class="urempief" :class="{ grid: viewMode === 'grid' }">
|
<MkPagination v-slot="{ items }" :pagination="pagination">
|
||||||
<MkA
|
<div :class="[$style.fileList, { [$style.grid]: viewMode === 'grid', [$style.list]: viewMode === 'list', '_gaps_s': viewMode === 'list' }]">
|
||||||
v-for="file in (items as Misskey.entities.DriveFile[])"
|
<MkA
|
||||||
:key="file.id"
|
v-for="file in items"
|
||||||
v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${dateString(file.createdAt)}\nby ${file.user ? '@' + Misskey.acct.toString(file.user) : 'system'}`"
|
:key="file.id"
|
||||||
:to="`/admin/file/${file.id}`"
|
v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${dateString(file.createdAt)}\nby ${file.user ? '@' + Misskey.acct.toString(file.user) : 'system'}`"
|
||||||
class="file _button"
|
:to="`/admin/file/${file.id}`"
|
||||||
>
|
:class="[$style.file, '_button']"
|
||||||
<div v-if="file.isSensitive" class="sensitive-label">{{ i18n.ts.sensitive }}</div>
|
>
|
||||||
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain" :highlightWhenSensitive="true"/>
|
<div v-if="file.isSensitive" :class="$style.sensitiveLabel">{{ i18n.ts.sensitive }}</div>
|
||||||
<div v-if="viewMode === 'list'" class="body">
|
<MkDriveFileThumbnail :class="$style.thumbnail" :file="file" fit="contain" :highlightWhenSensitive="true"/>
|
||||||
<div>
|
<div v-if="viewMode === 'list'" :class="$style.body">
|
||||||
<small style="opacity: 0.7;">{{ file.name }}</small>
|
<div>
|
||||||
|
<small style="opacity: 0.7;">{{ file.name }}</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<MkAcct v-if="file.user" :user="file.user"/>
|
||||||
|
<div v-else>{{ i18n.ts.system }}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span style="margin-right: 1em;">{{ file.type }}</span>
|
||||||
|
<span>{{ bytes(file.size) }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ i18n.ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</MkA>
|
||||||
<MkAcct v-if="file.user" :user="file.user"/>
|
</div>
|
||||||
<div v-else>{{ i18n.ts.system }}</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span style="margin-right: 1em;">{{ file.type }}</span>
|
|
||||||
<span>{{ bytes(file.size) }}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>{{ i18n.ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</MkA>
|
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -43,76 +45,76 @@ import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
||||||
import bytes from '@/filters/bytes.js';
|
import bytes from '@/filters/bytes.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { dateString } from '@/filters/date.js';
|
import { dateString } from '@/filters/date.js';
|
||||||
|
import type { PagingCtx } from '@/composables/use-pagination.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
pagination: any;
|
pagination: PagingCtx<'admin/drive/files'>;
|
||||||
viewMode: 'grid' | 'list';
|
viewMode: 'grid' | 'list';
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" module>
|
||||||
@keyframes sensitive-blink {
|
@keyframes sensitive-blink {
|
||||||
0% { opacity: 1; }
|
0% { opacity: 1; }
|
||||||
50% { opacity: 0; }
|
50% { opacity: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.urempief {
|
.list {
|
||||||
&.list {
|
> .file {
|
||||||
> .file {
|
display: flex;
|
||||||
display: flex;
|
width: 100%;
|
||||||
width: 100%;
|
height: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--MI_THEME-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .thumbnail {
|
|
||||||
width: 128px;
|
|
||||||
height: 128px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .body {
|
|
||||||
margin-left: 0.3em;
|
|
||||||
padding: 8px;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.grid {
|
> .file:hover {
|
||||||
display: grid;
|
color: var(--MI_THEME-accent);
|
||||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
}
|
||||||
grid-gap: 12px;
|
|
||||||
|
|
||||||
> .file {
|
> .file > .thumbnail {
|
||||||
position: relative;
|
width: 128px;
|
||||||
aspect-ratio: 1;
|
height: 128px;
|
||||||
|
}
|
||||||
|
|
||||||
> .thumbnail {
|
> .file > .body {
|
||||||
width: 100%;
|
margin-left: 0.3em;
|
||||||
height: 100%;
|
padding: 8px;
|
||||||
}
|
flex: 1;
|
||||||
|
|
||||||
> .sensitive-label {
|
@media (max-width: 500px) {
|
||||||
position: absolute;
|
font-size: 14px;
|
||||||
z-index: 10;
|
|
||||||
top: 8px;
|
|
||||||
left: 8px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
background: #ff0000bf;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 85%;
|
|
||||||
animation: sensitive-blink 1s infinite;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
||||||
|
grid-gap: 12px;
|
||||||
|
|
||||||
|
> .file {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sensitiveLabel {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
background: #ff0000bf;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 85%;
|
||||||
|
animation: sensitive-blink 1s infinite;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -34,6 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
|
import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
|
||||||
|
@ -41,12 +42,13 @@ import * as os from '@/os.js';
|
||||||
import { lookupFile } from '@/utility/admin-lookup.js';
|
import { lookupFile } from '@/utility/admin-lookup.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
import type { PagingCtx } from '@/composables/use-pagination.js';
|
||||||
|
|
||||||
const origin = ref('local');
|
const origin = ref<Misskey.entities.AdminDriveFilesRequest['origin']>('local');
|
||||||
const type = ref<string | null>(null);
|
const type = ref<string | null>(null);
|
||||||
const searchHost = ref('');
|
const searchHost = ref('');
|
||||||
const userId = ref('');
|
const userId = ref('');
|
||||||
const viewMode = ref('grid');
|
const viewMode = ref<'grid' | 'list'>('grid');
|
||||||
const pagination = {
|
const pagination = {
|
||||||
endpoint: 'admin/drive/files' as const,
|
endpoint: 'admin/drive/files' as const,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
@ -56,7 +58,7 @@ const pagination = {
|
||||||
origin: origin.value,
|
origin: origin.value,
|
||||||
hostname: (searchHost.value && searchHost.value !== '') ? searchHost.value : null,
|
hostname: (searchHost.value && searchHost.value !== '') ? searchHost.value : null,
|
||||||
})),
|
})),
|
||||||
};
|
} satisfies PagingCtx<'admin/drive/files'>;
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
os.confirm({
|
os.confirm({
|
||||||
|
|
|
@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true">
|
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true">
|
||||||
<div v-if="instance" class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
<div v-if="instance" class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||||
<div v-if="tab === 'overview'" class="_gaps_m">
|
<div v-if="tab === 'overview'" class="_gaps_m">
|
||||||
<div class="fnfelxur">
|
<div :class="$style.faviconAndName">
|
||||||
<img :src="faviconUrl" alt="" class="icon"/>
|
<img :src="faviconUrl" alt="" :class="$style.icon"/>
|
||||||
<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
|
<span :class="$style.name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; flex-direction: column; gap: 1em;">
|
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||||
<MkKeyValue :copy="host" oneline>
|
<MkKeyValue :copy="host" oneline>
|
||||||
|
@ -90,8 +90,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</FormSection>
|
</FormSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'chart'" class="_gaps_m">
|
<div v-else-if="tab === 'chart'" class="_gaps_m">
|
||||||
<div class="cmhjzshl">
|
<div>
|
||||||
<div class="selects">
|
<div :class="$style.selects">
|
||||||
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
|
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
|
||||||
<option value="instance-requests">{{ i18n.ts._instanceCharts.requests }}</option>
|
<option value="instance-requests">{{ i18n.ts._instanceCharts.requests }}</option>
|
||||||
<option value="instance-users">{{ i18n.ts._instanceCharts.users }}</option>
|
<option value="instance-users">{{ i18n.ts._instanceCharts.users }}</option>
|
||||||
|
@ -106,19 +106,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="instance-drive-files-total">{{ i18n.ts._instanceCharts.filesTotal }}</option>
|
<option value="instance-drive-files-total">{{ i18n.ts._instanceCharts.filesTotal }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
</div>
|
</div>
|
||||||
<div class="charts">
|
<div>
|
||||||
<div class="label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
|
<div :class="$style.label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
|
||||||
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
<MkChart :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
||||||
<div class="label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
|
<div :class="$style.label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
|
||||||
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
<MkChart :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'users'" class="_gaps_m">
|
<div v-else-if="tab === 'users'" class="_gaps_m">
|
||||||
<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;">
|
<MkPagination v-slot="{ items }" :pagination="usersPagination">
|
||||||
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`">
|
<div :class="$style.users">
|
||||||
<MkUserCardMini :user="user"/>
|
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${user.updatedAt ? dateString(user.updatedAt) : 'unknown'}`" :to="`/admin/user/${user.id}`">
|
||||||
</MkA>
|
<MkUserCardMini :user="user"/>
|
||||||
|
</MkA>
|
||||||
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
||||||
|
@ -180,7 +182,7 @@ const usersPagination = {
|
||||||
hostname: props.host,
|
hostname: props.host,
|
||||||
},
|
},
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
} satisfies PagingCtx;
|
} satisfies PagingCtx<'admin/show-users' | 'users'>;
|
||||||
|
|
||||||
if (iAmModerator) {
|
if (iAmModerator) {
|
||||||
watch(moderationNote, async () => {
|
watch(moderationNote, async () => {
|
||||||
|
@ -281,7 +283,7 @@ const headerTabs = computed(() => [{
|
||||||
key: 'overview',
|
key: 'overview',
|
||||||
title: i18n.ts.overview,
|
title: i18n.ts.overview,
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
}, {
|
}, ...(iAmModerator ? [{
|
||||||
key: 'chart',
|
key: 'chart',
|
||||||
title: i18n.ts.charts,
|
title: i18n.ts.charts,
|
||||||
icon: 'ti ti-chart-line',
|
icon: 'ti ti-chart-line',
|
||||||
|
@ -289,7 +291,7 @@ const headerTabs = computed(() => [{
|
||||||
key: 'users',
|
key: 'users',
|
||||||
title: i18n.ts.users,
|
title: i18n.ts.users,
|
||||||
icon: 'ti ti-users',
|
icon: 'ti ti-users',
|
||||||
}, {
|
}] : []), {
|
||||||
key: 'raw',
|
key: 'raw',
|
||||||
title: 'Raw',
|
title: 'Raw',
|
||||||
icon: 'ti ti-code',
|
icon: 'ti ti-code',
|
||||||
|
@ -301,34 +303,31 @@ definePage(() => ({
|
||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" module>
|
||||||
.fnfelxur {
|
.faviconAndName {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
> .icon {
|
|
||||||
display: block;
|
|
||||||
margin: 0 16px 0 0;
|
|
||||||
height: 64px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .name {
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.icon {
|
||||||
.cmhjzshl {
|
display: block;
|
||||||
> .selects {
|
margin: 0 16px 0 0;
|
||||||
display: flex;
|
height: 64px;
|
||||||
margin: 0 0 16px 0;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
.name {
|
||||||
> .charts {
|
word-break: break-all;
|
||||||
> .label {
|
}
|
||||||
margin-bottom: 12px;
|
.selects {
|
||||||
font-weight: bold;
|
display: flex;
|
||||||
}
|
margin: 0 0 16px 0;
|
||||||
}
|
}
|
||||||
|
.label {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.users {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill,minmax(270px,1fr));
|
||||||
|
grid-gap: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue