enhance: exploreで公開ロール一覧とそのメンバーを閲覧できるように
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
<template>
|
||||
<MkA v-adaptive-bg :to="`/admin/roles/${role.id}`" class="_panel" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }">
|
||||
<MkA v-adaptive-bg :to="forModeration ? `/admin/roles/${role.id}` : `/roles/${role.id}`" class="_panel" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }">
|
||||
<div :class="$style.title">
|
||||
<span :class="$style.icon">
|
||||
<i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--accent);"></i>
|
||||
<i v-else-if="role.isModerator" class="ti ti-shield" style="color: var(--accent);"></i>
|
||||
<i v-else class="ti ti-user" style="opacity: 0.7;"></i>
|
||||
<template v-if="role.iconUrl">
|
||||
<img :class="$style.badge" :src="role.iconUrl"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--accent);"></i>
|
||||
<i v-else-if="role.isModerator" class="ti ti-shield" style="color: var(--accent);"></i>
|
||||
<i v-else class="ti ti-user" style="opacity: 0.7;"></i>
|
||||
</template>
|
||||
</span>
|
||||
<span :class="$style.name">{{ role.name }}</span>
|
||||
<span v-if="role.target === 'manual'" :class="$style.users">{{ role.usersCount }} users</span>
|
||||
@@ -20,6 +25,7 @@ import { i18n } from '@/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
role: any;
|
||||
forModeration: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -38,6 +44,11 @@ const props = defineProps<{
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
height: 1.3em;
|
||||
vertical-align: -20%;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@@ -7,9 +7,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default="{ items: users }">
|
||||
<template #default="{ items }">
|
||||
<div class="efvhhmdq">
|
||||
<MkUserInfo v-for="user in users" :key="user.id" class="user" :user="user"/>
|
||||
<MkUserInfo v-for="item in items" :key="item.id" class="user" :user="extractor(item)"/>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
@@ -20,10 +20,13 @@ import MkUserInfo from '@/components/MkUserInfo.vue';
|
||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
pagination: Paging;
|
||||
noGap?: boolean;
|
||||
}>();
|
||||
extractor?: (item: any) => any;
|
||||
}>(), {
|
||||
extractor: (item) => item,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -16,16 +16,29 @@
|
||||
<MkFolder v-if="role.target === 'manual'" default-open>
|
||||
<template #icon><i class="ti ti-users"></i></template>
|
||||
<template #label>{{ i18n.ts.users }}</template>
|
||||
<template #suffix>{{ role.users.length }}</template>
|
||||
<template #suffix>{{ role.usersCount }}</template>
|
||||
<div class="_gaps">
|
||||
<MkButton primary rounded @click="assign"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton>
|
||||
|
||||
<div v-for="user in role.users" :key="user.id" :class="$style.userItem">
|
||||
<MkA :class="$style.user" :to="`/user-info/${user.id}`">
|
||||
<MkUserCardMini :user="user"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.unassign" @click="unassign(user, $event)"><i class="ti ti-x"></i></button>
|
||||
</div>
|
||||
<MkPagination :pagination="usersPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div v-for="item in items" :key="item.user.id" :class="$style.userItem">
|
||||
<MkA :class="$style.user" :to="`/user-info/${item.user.id}`">
|
||||
<MkUserCardMini :user="item.user"/>
|
||||
</MkA>
|
||||
<button class="_button" :class="$style.unassign" @click="unassign(item.user, $event)"><i class="ti ti-x"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<MkInfo v-else>{{ i18n.ts._role.isConditionalRole }}</MkInfo>
|
||||
@@ -47,6 +60,7 @@ import { useRouter } from '@/router';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -54,6 +68,14 @@ const props = defineProps<{
|
||||
id?: string;
|
||||
}>();
|
||||
|
||||
const usersPagination = {
|
||||
endpoint: 'admin/roles/users' as const,
|
||||
limit: 20,
|
||||
params: computed(() => ({
|
||||
roleId: props.id,
|
||||
})),
|
||||
};
|
||||
|
||||
const role = reactive(await os.api('admin/roles/show', {
|
||||
roleId: props.id,
|
||||
}));
|
||||
|
@@ -133,7 +133,7 @@
|
||||
</div>
|
||||
</MkFolder>
|
||||
<div class="_gaps_s">
|
||||
<MkRolePreview v-for="role in roles" :key="role.id" :role="role"/>
|
||||
<MkRolePreview v-for="role in roles" :key="role.id" :role="role" :for-moderation="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
|
22
packages/frontend/src/pages/explore.roles.vue
Normal file
22
packages/frontend/src/pages/explore.roles.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<MkSpacer :content-max="1200">
|
||||
<div class="_gaps_s">
|
||||
<MkRolePreview v-for="role in roles" :key="role.id" :role="role" :for-moderation="false"/>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
let roles = $ref();
|
||||
|
||||
os.api('roles/list', {
|
||||
limit: 30,
|
||||
}).then(res => {
|
||||
roles = res;
|
||||
});
|
||||
</script>
|
||||
|
@@ -8,19 +8,19 @@
|
||||
<template v-if="tag == null">
|
||||
<MkFoldableSection class="_margin" persist-key="explore-pinned-users">
|
||||
<template #header><i class="ti ti-bookmark ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedUsers }}</template>
|
||||
<XUserList :pagination="pinnedUsers"/>
|
||||
<MkUserList :pagination="pinnedUsers"/>
|
||||
</MkFoldableSection>
|
||||
<MkFoldableSection class="_margin" persist-key="explore-popular-users">
|
||||
<template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
|
||||
<XUserList :pagination="popularUsers"/>
|
||||
<MkUserList :pagination="popularUsers"/>
|
||||
</MkFoldableSection>
|
||||
<MkFoldableSection class="_margin" persist-key="explore-recently-updated-users">
|
||||
<template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
|
||||
<XUserList :pagination="recentlyUpdatedUsers"/>
|
||||
<MkUserList :pagination="recentlyUpdatedUsers"/>
|
||||
</MkFoldableSection>
|
||||
<MkFoldableSection class="_margin" persist-key="explore-recently-registered-users">
|
||||
<template #header><i class="ti ti-plus ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyRegisteredUsers }}</template>
|
||||
<XUserList :pagination="recentlyRegisteredUsers"/>
|
||||
<MkUserList :pagination="recentlyRegisteredUsers"/>
|
||||
</MkFoldableSection>
|
||||
</template>
|
||||
</div>
|
||||
@@ -36,21 +36,21 @@
|
||||
|
||||
<MkFoldableSection v-if="tag != null" :key="`${tag}`" class="_margin">
|
||||
<template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template>
|
||||
<XUserList :pagination="tagUsers"/>
|
||||
<MkUserList :pagination="tagUsers"/>
|
||||
</MkFoldableSection>
|
||||
|
||||
<template v-if="tag == null">
|
||||
<MkFoldableSection class="_margin">
|
||||
<template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
|
||||
<XUserList :pagination="popularUsersF"/>
|
||||
<MkUserList :pagination="popularUsersF"/>
|
||||
</MkFoldableSection>
|
||||
<MkFoldableSection class="_margin">
|
||||
<template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
|
||||
<XUserList :pagination="recentlyUpdatedUsersF"/>
|
||||
<MkUserList :pagination="recentlyUpdatedUsersF"/>
|
||||
</MkFoldableSection>
|
||||
<MkFoldableSection class="_margin">
|
||||
<template #header><i class="ti ti-rocket ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyDiscoveredUsers }}</template>
|
||||
<XUserList :pagination="recentlyRegisteredUsersF"/>
|
||||
<MkUserList :pagination="recentlyRegisteredUsersF"/>
|
||||
</MkFoldableSection>
|
||||
</template>
|
||||
</div>
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch } from 'vue';
|
||||
import XUserList from '@/components/MkUserList.vue';
|
||||
import MkUserList from '@/components/MkUserList.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import MkTab from '@/components/MkTab.vue';
|
||||
import * as os from '@/os';
|
||||
|
@@ -8,6 +8,9 @@
|
||||
<div v-else-if="tab === 'users'">
|
||||
<XUsers/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'roles'">
|
||||
<XRoles/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'search'">
|
||||
<MkSpacer :content-max="1200">
|
||||
<div>
|
||||
@@ -22,7 +25,7 @@
|
||||
</MkRadios>
|
||||
</div>
|
||||
|
||||
<XUserList v-if="searchQuery" ref="searchEl" class="_margin" :pagination="searchPagination"/>
|
||||
<MkUserList v-if="searchQuery" ref="searchEl" class="_margin" :pagination="searchPagination"/>
|
||||
</MkSpacer>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,12 +36,13 @@
|
||||
import { computed, watch } from 'vue';
|
||||
import XFeatured from './explore.featured.vue';
|
||||
import XUsers from './explore.users.vue';
|
||||
import XRoles from './explore.roles.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { i18n } from '@/i18n';
|
||||
import XUserList from '@/components/MkUserList.vue';
|
||||
import MkUserList from '@/components/MkUserList.vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
tag?: string;
|
||||
@@ -75,8 +79,13 @@ const headerTabs = $computed(() => [{
|
||||
key: 'users',
|
||||
icon: 'ti ti-users',
|
||||
title: i18n.ts.users,
|
||||
}, {
|
||||
key: 'roles',
|
||||
icon: 'ti ti-badges',
|
||||
title: i18n.ts.roles,
|
||||
}, {
|
||||
key: 'search',
|
||||
icon: 'ti ti-search',
|
||||
title: i18n.ts.search,
|
||||
}]);
|
||||
|
||||
|
47
packages/frontend/src/pages/role.vue
Normal file
47
packages/frontend/src/pages/role.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader/></template>
|
||||
|
||||
<MkSpacer :content-max="1200">
|
||||
<div class="_gaps_s">
|
||||
<div v-if="role">{{ role.description }}</div>
|
||||
<MkUserList :pagination="users" :extractor="(item) => item.user"/>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch } from 'vue';
|
||||
import * as os from '@/os';
|
||||
import MkUserList from '@/components/MkUserList.vue';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
const props = defineProps<{
|
||||
role: string;
|
||||
}>();
|
||||
|
||||
let role = $ref();
|
||||
|
||||
watch(() => props.role, () => {
|
||||
os.api('roles/show', {
|
||||
roleId: props.role,
|
||||
}).then(res => {
|
||||
role = res;
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
const users = $computed(() => ({
|
||||
endpoint: 'roles/users' as const,
|
||||
limit: 30,
|
||||
params: {
|
||||
roleId: props.role,
|
||||
},
|
||||
}));
|
||||
|
||||
definePageMetadata(computed(() => ({
|
||||
title: role?.name,
|
||||
icon: 'ti ti-badge',
|
||||
})));
|
||||
</script>
|
||||
|
@@ -112,7 +112,7 @@
|
||||
<MkButton v-if="user.host == null && iAmModerator" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton>
|
||||
|
||||
<div v-for="role in info.roles" :key="role.id" :class="$style.roleItem">
|
||||
<MkRolePreview :class="$style.role" :role="role"/>
|
||||
<MkRolePreview :class="$style.role" :role="role" :for-moderation="true"/>
|
||||
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button>
|
||||
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
||||
</div>
|
||||
|
@@ -197,6 +197,9 @@ export const routes = [{
|
||||
path: '/theme-editor',
|
||||
component: page(() => import('./pages/theme-editor.vue')),
|
||||
loginRequired: true,
|
||||
}, {
|
||||
path: '/roles/:role',
|
||||
component: page(() => import('./pages/role.vue')),
|
||||
}, {
|
||||
path: '/explore/tags/:tag',
|
||||
component: page(() => import('./pages/explore.vue')),
|
||||
|
Reference in New Issue
Block a user