diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue index 0f59ca0b36..38cee91a51 100644 --- a/packages/frontend/src/pages/my-lists/index.vue +++ b/packages/frontend/src/pages/my-lists/index.vue @@ -14,7 +14,7 @@ <div v-if="items.length > 0" class="_gaps"> <MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`"> - <div style="margin-bottom: 4px;">{{ list.name }}</div> + <div style="margin-bottom: 4px;">{{ list.name }} <span :class="$style.nUsers">({{ i18n.t('nUsers', { n: `${list.userIds.length}/${$i?.policies['userEachUserListsLimit']}` }) }})</span></div> <MkAvatars :userIds="list.userIds" :limit="10"/> </MkA> </div> @@ -32,6 +32,7 @@ import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import { userListsCache } from '@/cache'; import { infoImageUrl } from '@/instance'; +import { $i } from '@/account'; const items = $computed(() => userListsCache.value.value ?? []); @@ -66,10 +67,6 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts.manageLists, icon: 'ti ti-list', - action: { - icon: 'ti ti-plus', - handler: create, - }, }); onActivated(() => { @@ -90,4 +87,9 @@ onActivated(() => { text-decoration: none; } } + +.nUsers { + font-size: .9em; + opacity: .7; +} </style> diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index dd431e8dc0..36a3a123c5 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -20,6 +20,7 @@ <MkFolder defaultOpen> <template #label>{{ i18n.ts.members }}</template> + <template #caption>{{ i18n.t('nUsers', { n: `${list.userIds.length}/${$i?.policies['userEachUserListsLimit']}` }) }}</template> <div class="_gaps_s"> <MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton> @@ -29,6 +30,10 @@ </MkA> <button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button> </div> + <MkButton v-if="!fetching && queueUserIds.length !== 0" v-appear="enableInfiniteScroll ? fetchMoreUsers : null" :class="$style.more" :style="{ cursor: 'pointer' }" primary rounded @click="fetchMoreUsers"> + {{ i18n.ts.loadMore }} + </MkButton> + <MkLoading v-if="fetching" class="loading"/> </div> </MkFolder> </div> @@ -49,34 +54,57 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkInput from '@/components/MkInput.vue'; import { userListsCache } from '@/cache'; +import { UserList, UserLite } from 'misskey-js/built/entities'; +import { $i } from '@/account'; +import { defaultStore } from '@/store'; +const { + enableInfiniteScroll, +} = defaultStore.reactiveState; const props = defineProps<{ listId: string; }>(); -let list = $ref(null); -let users = $ref([]); +const FETCH_USERS_LIMIT = 20; + +let list = $ref<UserList | null>(null); +let users = $ref<UserLite[]>([]); +let queueUserIds = $ref<string[]>([]); +let fetching = $ref(true); const isPublic = ref(false); const name = ref(''); function fetchList() { + fetching = true; os.api('users/lists/show', { listId: props.listId, }).then(_list => { list = _list; name.value = list.name; isPublic.value = list.isPublic; + queueUserIds = list.userIds; - os.api('users/show', { - userIds: list.userIds, - }).then(_users => { - users = _users; - }); + return fetchMoreUsers(); + }); +} + +function fetchMoreUsers() { + if (!list) return; + if (fetching && users.length !== 0) return; // fetchingがtrueならやめるが、usersが空なら続行 + fetching = true; + os.api('users/show', { + userIds: queueUserIds.slice(0, FETCH_USERS_LIMIT), + }).then(_users => { + users = users.concat(_users); + queueUserIds = queueUserIds.slice(FETCH_USERS_LIMIT); + }).finally(() => { + fetching = false; }); } function addUser() { os.selectUser().then(user => { + if (!list) return; os.apiWithDialog('users/lists/push', { listId: list.id, userId: user.id, @@ -92,6 +120,7 @@ async function removeUser(user, ev) { icon: 'ti ti-x', danger: true, action: async () => { + if (!list) return; os.api('users/lists/pull', { listId: list.id, userId: user.id, @@ -103,6 +132,7 @@ async function removeUser(user, ev) { } async function deleteList() { + if (!list) return; const { canceled } = await os.confirm({ type: 'warning', text: i18n.t('removeAreYouSure', { x: list.name }), @@ -117,6 +147,7 @@ async function deleteList() { } async function updateSettings() { + if (!list) return; await os.apiWithDialog('users/lists/update', { listId: list.id, name: name.value, @@ -166,6 +197,11 @@ definePageMetadata(computed(() => list ? { align-self: center; } +.more { + margin-left: auto; + margin-right: auto; +} + .footer { -webkit-backdrop-filter: var(--blur, blur(15px)); backdrop-filter: var(--blur, blur(15px));