forked from Mirrors/elk
feat: more list support (#1479)
This commit is contained in:
parent
9d353fa07b
commit
e393049f04
8 changed files with 212 additions and 1 deletions
|
@ -117,6 +117,20 @@ const isNotifiedOnPost = $computed(() => !!relationship?.notifying)
|
|||
<span v-else i-ri-notification-4-line block text-current />
|
||||
</button>
|
||||
</CommonTooltip>
|
||||
<CommonTooltip :content="$t('list.modify_account')">
|
||||
<VDropdown v-if="!isSelf && relationship?.following">
|
||||
<button
|
||||
:aria-label="$t('list.modify_account')"
|
||||
rounded-full text-sm p2 border-1 transition-colors
|
||||
border-base hover:text-primary
|
||||
>
|
||||
<span i-ri:play-list-add-fill block text-current />
|
||||
</button>
|
||||
<template #popper>
|
||||
<ListLists :user-id="account.id" />
|
||||
</template>
|
||||
</VDropdown>
|
||||
</CommonTooltip>
|
||||
<AccountFollowButton :account="account" :command="command" />
|
||||
<!-- Edit profile -->
|
||||
<NuxtLink
|
||||
|
|
|
@ -11,6 +11,7 @@ const {
|
|||
virtualScroller = false,
|
||||
eventType = 'update',
|
||||
preprocess,
|
||||
noEndMessage = false,
|
||||
} = defineProps<{
|
||||
paginator: Paginator<T[], O>
|
||||
keyProp?: keyof T
|
||||
|
@ -18,6 +19,7 @@ const {
|
|||
stream?: Promise<WsEvents>
|
||||
eventType?: 'notification' | 'update'
|
||||
preprocess?: (items: (U | T)[]) => U[]
|
||||
noEndMessage?: boolean
|
||||
}>()
|
||||
|
||||
defineSlots<{
|
||||
|
@ -84,7 +86,7 @@ const { items, prevItems, update, state, endAnchor, error } = usePaginator(pagin
|
|||
<slot v-if="state === 'loading'" name="loading">
|
||||
<TimelineSkeleton />
|
||||
</slot>
|
||||
<slot v-else-if="state === 'done'" name="done">
|
||||
<slot v-else-if="state === 'done' && !noEndMessage" name="done">
|
||||
<div p5 text-secondary italic text-center>
|
||||
{{ t('common.end_of_list') }}
|
||||
</div>
|
||||
|
|
44
components/list/Account.vue
Normal file
44
components/list/Account.vue
Normal file
|
@ -0,0 +1,44 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const { account, list } = defineProps<{
|
||||
account: mastodon.v1.Account
|
||||
hoverCard?: boolean
|
||||
list: string
|
||||
}>()
|
||||
|
||||
cacheAccount(account)
|
||||
|
||||
const client = useMastoClient()
|
||||
|
||||
const isRemoved = ref(false)
|
||||
|
||||
async function edit() {
|
||||
try {
|
||||
isRemoved.value
|
||||
? await client.v1.lists.addAccount(list, { accountIds: [account.id] })
|
||||
: await client.v1.lists.removeAccount(list, { accountIds: [account.id] })
|
||||
isRemoved.value = !isRemoved.value
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div flex justify-between hover:bg-active transition-100 items-center>
|
||||
<AccountInfo
|
||||
:account="account" hover p1 as="router-link"
|
||||
:hover-card="hoverCard"
|
||||
shrink
|
||||
overflow-hidden
|
||||
:to="getAccountRoute(account)"
|
||||
/>
|
||||
<div>
|
||||
<CommonTooltip :content="isRemoved ? $t('list.add_account') : $t('list.remove_account')" :hover="isRemoved ? 'text-green' : 'text-red'">
|
||||
<button :class="isRemoved ? 'i-ri:user-add-line' : 'i-ri:user-unfollow-line'" text-xl @click="edit" />
|
||||
</CommonTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
49
components/list/Lists.vue
Normal file
49
components/list/Lists.vue
Normal file
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts" setup>
|
||||
const { userId } = defineProps<{
|
||||
userId: string
|
||||
}>()
|
||||
|
||||
const { client } = $(useMasto())
|
||||
const paginator = client.v1.lists.list()
|
||||
const listsWithUser = ref((await client.v1.accounts.listLists(userId)).map(list => list.id))
|
||||
|
||||
function indexOfUserInList(listId: string) {
|
||||
return listsWithUser.value.indexOf(listId)
|
||||
}
|
||||
|
||||
async function edit(listId: string) {
|
||||
try {
|
||||
const index = indexOfUserInList(listId)
|
||||
if (index === -1) {
|
||||
await client.v1.lists.addAccount(listId, { accountIds: [userId] })
|
||||
listsWithUser.value.push(listId)
|
||||
}
|
||||
else {
|
||||
await client.v1.lists.removeAccount(listId, { accountIds: [userId] })
|
||||
listsWithUser.value = listsWithUser.value.filter(id => id !== listId)
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CommonPaginator no-end-message :paginator="paginator">
|
||||
<template #default="{ item }">
|
||||
<div p4 hover:bg-active block w="100%" flex justify-between items-center gap-4>
|
||||
<p>{{ item.title }}</p>
|
||||
<CommonTooltip
|
||||
:content="indexOfUserInList(item.id) === -1 ? $t('list.add_account') : $t('list.remove_account')"
|
||||
:hover="indexOfUserInList(item.id) === -1 ? 'text-green' : 'text-red'"
|
||||
>
|
||||
<button
|
||||
:class="indexOfUserInList(item.id) === -1 ? 'i-ri:user-add-line' : 'i-ri:user-unfollow-line'"
|
||||
text-xl @click="() => edit(item.id)"
|
||||
/>
|
||||
</CommonTooltip>
|
||||
</div>
|
||||
</template>
|
||||
</CommonPaginator>
|
||||
</template>
|
|
@ -174,6 +174,11 @@
|
|||
"language": {
|
||||
"search": "Search"
|
||||
},
|
||||
"list": {
|
||||
"add_account": "Add account to list",
|
||||
"modify_account": "Modify lists with account",
|
||||
"remove_account": "Remove account from list"
|
||||
},
|
||||
"menu": {
|
||||
"block_account": "Block {0}",
|
||||
"block_domain": "Block domain {0}",
|
||||
|
@ -447,8 +452,10 @@
|
|||
"edited": "edited {0}"
|
||||
},
|
||||
"tab": {
|
||||
"accounts": "Accounts",
|
||||
"for_you": "For you",
|
||||
"hashtags": "Hashtags",
|
||||
"list": "List",
|
||||
"media": "Media",
|
||||
"news": "News",
|
||||
"notifications_all": "All",
|
||||
|
|
55
pages/[[server]]/list.vue
Normal file
55
pages/[[server]]/list.vue
Normal file
|
@ -0,0 +1,55 @@
|
|||
<script setup lang="ts">
|
||||
import type { CommonRouteTabOption } from '~/components/common/CommonRouteTabs.vue'
|
||||
|
||||
const list = $computed(() => useRoute().params.list as string)
|
||||
const server = $computed(() => useRoute().params.server as string)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const tabs = $computed<CommonRouteTabOption[]>(() => [
|
||||
{
|
||||
to: {
|
||||
name: 'list',
|
||||
params: { server, list },
|
||||
},
|
||||
display: t('tab.list'),
|
||||
icon: 'i-ri:list-unordered',
|
||||
},
|
||||
{
|
||||
to: {
|
||||
name: 'list-accounts',
|
||||
params: { server, list },
|
||||
},
|
||||
display: t('tab.accounts'),
|
||||
icon: 'i-ri:user-line',
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
const { client } = $(useMasto())
|
||||
const { data: listInfo, refresh } = $(await useAsyncData(() => client.v1.lists.fetch(list), { default: () => shallowRef() }))
|
||||
|
||||
if (listInfo) {
|
||||
useHeadFixed({
|
||||
title: () => `${listInfo.title}`,
|
||||
})
|
||||
}
|
||||
|
||||
onReactivated(() => {
|
||||
// Silently update data when reentering the page
|
||||
// The user will see the previous content first, and any changes will be updated to the UI when the request is completed
|
||||
refresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainContent back>
|
||||
<template #title>
|
||||
<span text-lg font-bold>{{ listInfo ? listInfo.title : t('nav.list') }}</span>
|
||||
</template>
|
||||
<template #header>
|
||||
<CommonRouteTabs replace :options="tabs" />
|
||||
</template>
|
||||
<NuxtPage v-if="isHydrated" />
|
||||
</MainContent>
|
||||
</template>
|
23
pages/[[server]]/list/[list]/accounts.vue
Normal file
23
pages/[[server]]/list/[list]/accounts.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
name: 'list-accounts',
|
||||
})
|
||||
|
||||
const params = useRoute().params
|
||||
const listId = $(computedEager(() => params.list as string))
|
||||
|
||||
const paginator = useMastoClient().v1.lists.listAccounts(listId)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CommonPaginator :paginator="paginator">
|
||||
<template #default="{ item }">
|
||||
<ListAccount
|
||||
:account="item"
|
||||
:list="listId"
|
||||
hover-card
|
||||
border="b base" py2 px4
|
||||
/>
|
||||
</template>
|
||||
</CommonPaginator>
|
||||
</template>
|
17
pages/[[server]]/list/[list]/index.vue
Normal file
17
pages/[[server]]/list/[list]/index.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
name: 'list',
|
||||
})
|
||||
|
||||
const params = useRoute().params
|
||||
const listId = $(computedEager(() => params.list as string))
|
||||
|
||||
const { client } = $(useMasto())
|
||||
|
||||
const paginator = client.v1.timelines.listList(listId)
|
||||
const stream = useStreaming(client => client.v1.stream.streamListTimeline(listId))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TimelinePaginator v-bind="{ paginator, stream }" :preprocess="reorderedTimeline" context="home" />
|
||||
</template>
|
Loading…
Reference in a new issue