feat: more list support (#1479)
parent
9d353fa07b
commit
e393049f04
|
@ -117,6 +117,20 @@ const isNotifiedOnPost = $computed(() => !!relationship?.notifying)
|
||||||
<span v-else i-ri-notification-4-line block text-current />
|
<span v-else i-ri-notification-4-line block text-current />
|
||||||
</button>
|
</button>
|
||||||
</CommonTooltip>
|
</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" />
|
<AccountFollowButton :account="account" :command="command" />
|
||||||
<!-- Edit profile -->
|
<!-- Edit profile -->
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
|
|
|
@ -11,6 +11,7 @@ const {
|
||||||
virtualScroller = false,
|
virtualScroller = false,
|
||||||
eventType = 'update',
|
eventType = 'update',
|
||||||
preprocess,
|
preprocess,
|
||||||
|
noEndMessage = false,
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
paginator: Paginator<T[], O>
|
paginator: Paginator<T[], O>
|
||||||
keyProp?: keyof T
|
keyProp?: keyof T
|
||||||
|
@ -18,6 +19,7 @@ const {
|
||||||
stream?: Promise<WsEvents>
|
stream?: Promise<WsEvents>
|
||||||
eventType?: 'notification' | 'update'
|
eventType?: 'notification' | 'update'
|
||||||
preprocess?: (items: (U | T)[]) => U[]
|
preprocess?: (items: (U | T)[]) => U[]
|
||||||
|
noEndMessage?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineSlots<{
|
defineSlots<{
|
||||||
|
@ -84,7 +86,7 @@ const { items, prevItems, update, state, endAnchor, error } = usePaginator(pagin
|
||||||
<slot v-if="state === 'loading'" name="loading">
|
<slot v-if="state === 'loading'" name="loading">
|
||||||
<TimelineSkeleton />
|
<TimelineSkeleton />
|
||||||
</slot>
|
</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>
|
<div p5 text-secondary italic text-center>
|
||||||
{{ t('common.end_of_list') }}
|
{{ t('common.end_of_list') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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>
|
|
@ -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": {
|
"language": {
|
||||||
"search": "Search"
|
"search": "Search"
|
||||||
},
|
},
|
||||||
|
"list": {
|
||||||
|
"add_account": "Add account to list",
|
||||||
|
"modify_account": "Modify lists with account",
|
||||||
|
"remove_account": "Remove account from list"
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"block_account": "Block {0}",
|
"block_account": "Block {0}",
|
||||||
"block_domain": "Block domain {0}",
|
"block_domain": "Block domain {0}",
|
||||||
|
@ -447,8 +452,10 @@
|
||||||
"edited": "edited {0}"
|
"edited": "edited {0}"
|
||||||
},
|
},
|
||||||
"tab": {
|
"tab": {
|
||||||
|
"accounts": "Accounts",
|
||||||
"for_you": "For you",
|
"for_you": "For you",
|
||||||
"hashtags": "Hashtags",
|
"hashtags": "Hashtags",
|
||||||
|
"list": "List",
|
||||||
"media": "Media",
|
"media": "Media",
|
||||||
"news": "News",
|
"news": "News",
|
||||||
"notifications_all": "All",
|
"notifications_all": "All",
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 New Issue