mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 09:13:24 +00:00
ENH - expose Feather bookmarks on profiles
This commit is contained in:
@@ -129,6 +129,36 @@ describe('Feather store', () => {
|
||||
expect(store.connections).toEqual([profile])
|
||||
})
|
||||
|
||||
it('loads bookmarked posts for the owner profile', async () => {
|
||||
const bookmarkedPost = { ...post, is_bookmarked: true }
|
||||
vi.mocked(nuiCall).mockResolvedValue({
|
||||
success: true,
|
||||
data: { items: [bookmarkedPost] },
|
||||
})
|
||||
const store = useFeatherStore()
|
||||
|
||||
expect(await store.loadBookmarks()).toBe(true)
|
||||
expect(nuiCall).toHaveBeenCalledWith('feather:bookmarks')
|
||||
expect(store.bookmarkedPosts).toEqual([bookmarkedPost])
|
||||
expect(store.bookmarksLoading).toBe(false)
|
||||
})
|
||||
|
||||
it('removes an unbookmarked post from the saved profile tab', async () => {
|
||||
vi.mocked(nuiCall).mockResolvedValue({ success: true })
|
||||
const store = useFeatherStore()
|
||||
const bookmarkedPost = { ...post, is_bookmarked: true }
|
||||
store.bookmarkedPosts = [bookmarkedPost]
|
||||
|
||||
await store.react(bookmarkedPost, 'bookmark')
|
||||
|
||||
expect(store.bookmarkedPosts).toEqual([])
|
||||
expect(nuiCall).toHaveBeenCalledWith('feather:react', {
|
||||
active: false,
|
||||
id: bookmarkedPost.id,
|
||||
kind: 'bookmark',
|
||||
})
|
||||
})
|
||||
|
||||
it('removes an owned connection and updates the profile count', async () => {
|
||||
vi.mocked(nuiCall).mockResolvedValue({ success: true })
|
||||
const store = useFeatherStore()
|
||||
|
||||
@@ -17,6 +17,8 @@ export type FeatherConnectionMode = 'followers' | 'following'
|
||||
export const useFeatherStore = defineStore('feather', {
|
||||
state: () => ({
|
||||
activities: [] as FeatherActivity[],
|
||||
bookmarkedPosts: [] as FeatherPost[],
|
||||
bookmarksLoading: false,
|
||||
connectionLoading: false,
|
||||
connections: [] as FeatherProfile[],
|
||||
exploreLoading: false,
|
||||
@@ -135,7 +137,14 @@ export const useFeatherStore = defineStore('feather', {
|
||||
id: post.id,
|
||||
kind,
|
||||
})
|
||||
if (response.success) return
|
||||
if (response.success) {
|
||||
if (kind === 'bookmark') {
|
||||
this.bookmarkedPosts = next
|
||||
? [post, ...this.bookmarkedPosts.filter((item) => item.id !== post.id)]
|
||||
: this.bookmarkedPosts.filter((item) => item.id !== post.id)
|
||||
}
|
||||
return
|
||||
}
|
||||
post[key] = !next
|
||||
if (kind === 'like') post.like_count += next ? -1 : 1
|
||||
},
|
||||
@@ -163,6 +172,7 @@ export const useFeatherStore = defineStore('feather', {
|
||||
...this.feed,
|
||||
...this.explorePosts,
|
||||
...this.profilePosts,
|
||||
...this.bookmarkedPosts,
|
||||
]) {
|
||||
if (post.profile_id === profile.id) post.is_following = next
|
||||
}
|
||||
@@ -178,6 +188,7 @@ export const useFeatherStore = defineStore('feather', {
|
||||
...this.feed,
|
||||
...this.explorePosts,
|
||||
...this.profilePosts,
|
||||
...this.bookmarkedPosts,
|
||||
]) {
|
||||
if (item.profile_id === post.profile_id) item.is_following = next
|
||||
}
|
||||
@@ -222,6 +233,7 @@ export const useFeatherStore = defineStore('feather', {
|
||||
...this.feed,
|
||||
...this.explorePosts,
|
||||
...this.profilePosts,
|
||||
...this.bookmarkedPosts,
|
||||
]) {
|
||||
if (post.profile_id === target.id) post.is_following = false
|
||||
}
|
||||
@@ -237,6 +249,16 @@ export const useFeatherStore = defineStore('feather', {
|
||||
this.profilePosts = response.data.posts
|
||||
return true
|
||||
},
|
||||
async loadBookmarks(): Promise<boolean> {
|
||||
this.bookmarksLoading = true
|
||||
const response = await nuiCall<{ items: FeatherPost[] }>(
|
||||
'feather:bookmarks',
|
||||
)
|
||||
this.bookmarksLoading = false
|
||||
if (!response.success || !response.data) return false
|
||||
this.bookmarkedPosts = response.data.items
|
||||
return true
|
||||
},
|
||||
async loadThread(id: string): Promise<boolean> {
|
||||
const response = await nuiCall<FeatherThread>('feather:thread', { id })
|
||||
if (!response.success || !response.data) return false
|
||||
@@ -258,6 +280,9 @@ export const useFeatherStore = defineStore('feather', {
|
||||
this.feed = this.feed.filter((post) => post.id !== id)
|
||||
this.explorePosts = this.explorePosts.filter((post) => post.id !== id)
|
||||
this.profilePosts = this.profilePosts.filter((post) => post.id !== id)
|
||||
this.bookmarkedPosts = this.bookmarkedPosts.filter(
|
||||
(post) => post.id !== id,
|
||||
)
|
||||
return true
|
||||
},
|
||||
async blockProfile(profileId: number): Promise<boolean> {
|
||||
@@ -267,6 +292,9 @@ export const useFeatherStore = defineStore('feather', {
|
||||
this.explorePosts = this.explorePosts.filter(
|
||||
(post) => post.profile_id !== profileId,
|
||||
)
|
||||
this.bookmarkedPosts = this.bookmarkedPosts.filter(
|
||||
(post) => post.profile_id !== profileId,
|
||||
)
|
||||
this.networkResults = this.networkResults.filter(
|
||||
(profile) => profile.id !== profileId,
|
||||
)
|
||||
|
||||
@@ -548,6 +548,8 @@ const defaultLocales: LocaleTree = {
|
||||
noReplies: 'No replies yet. Start the conversation.',
|
||||
likes: 'Likes',
|
||||
bookmarks: 'Bookmarks',
|
||||
noBookmarks: 'No saved posts yet',
|
||||
noBookmarksBody: 'Posts you bookmark will appear here.',
|
||||
delete: 'Delete',
|
||||
report: 'Report',
|
||||
block: 'Block @{handle}',
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
AlignLeft,
|
||||
AtSign,
|
||||
Bell,
|
||||
Bookmark,
|
||||
Camera,
|
||||
CalendarDays,
|
||||
CheckCircle2,
|
||||
@@ -78,7 +79,7 @@ type Screen =
|
||||
| 'connections'
|
||||
type ExploreView = 'explore' | 'trending' | 'news'
|
||||
type ActivityView = 'all' | 'mentions'
|
||||
type ProfileView = 'posts' | 'replies' | 'media'
|
||||
type ProfileView = 'posts' | 'replies' | 'media' | 'bookmarks'
|
||||
type SelectedPhoto = { id: number; url: string }
|
||||
type ComposerContext = {
|
||||
body: string
|
||||
@@ -141,6 +142,7 @@ const displayedActivities = computed(() =>
|
||||
: feather.activities,
|
||||
)
|
||||
const displayedProfilePosts = computed(() => {
|
||||
if (profileView.value === 'bookmarks') return feather.bookmarkedPosts
|
||||
if (profileView.value === 'replies')
|
||||
return feather.profilePosts.filter((post) => Boolean(post.reply_to_id))
|
||||
if (profileView.value === 'media')
|
||||
@@ -354,10 +356,16 @@ async function selectTab(next: Tab): Promise<void> {
|
||||
await feather.markActivities()
|
||||
}
|
||||
if (next === 'profile' && feather.profile) {
|
||||
profileView.value = 'posts'
|
||||
await feather.loadProfile(feather.profile.id)
|
||||
}
|
||||
}
|
||||
|
||||
async function selectProfileView(next: ProfileView): Promise<void> {
|
||||
profileView.value = next
|
||||
if (next === 'bookmarks') await feather.loadBookmarks()
|
||||
}
|
||||
|
||||
async function setFeedMode(mode: 'for-you' | 'following'): Promise<void> {
|
||||
await feather.loadFeed(mode)
|
||||
}
|
||||
@@ -518,7 +526,10 @@ async function publishThreadReply(): Promise<void> {
|
||||
}
|
||||
|
||||
async function openProfile(profileId: number): Promise<void> {
|
||||
if (await feather.loadProfile(profileId)) screen.value = 'profile'
|
||||
if (await feather.loadProfile(profileId)) {
|
||||
profileView.value = 'posts'
|
||||
screen.value = 'profile'
|
||||
}
|
||||
}
|
||||
|
||||
async function followProfile(profile: FeatherProfile): Promise<void> {
|
||||
@@ -1391,7 +1402,7 @@ onMounted(async () => {
|
||||
type="button"
|
||||
:active="profileView === 'posts'"
|
||||
:class="{ 'is-active': profileView === 'posts' }"
|
||||
@click="profileView = 'posts'"
|
||||
@click="selectProfileView('posts')"
|
||||
>
|
||||
<AlignLeft :size="16" /> {{ t('posts') }}
|
||||
</kSegmentedButton>
|
||||
@@ -1399,7 +1410,7 @@ onMounted(async () => {
|
||||
type="button"
|
||||
:active="profileView === 'replies'"
|
||||
:class="{ 'is-active': profileView === 'replies' }"
|
||||
@click="profileView = 'replies'"
|
||||
@click="selectProfileView('replies')"
|
||||
>
|
||||
<AtSign :size="17" />
|
||||
</kSegmentedButton>
|
||||
@@ -1407,13 +1418,41 @@ onMounted(async () => {
|
||||
type="button"
|
||||
:active="profileView === 'media'"
|
||||
:class="{ 'is-active': profileView === 'media' }"
|
||||
@click="profileView = 'media'"
|
||||
@click="selectProfileView('media')"
|
||||
>
|
||||
<Video :size="17" />
|
||||
</kSegmentedButton>
|
||||
<kSegmentedButton
|
||||
v-if="activeProfile.is_owner"
|
||||
type="button"
|
||||
:active="profileView === 'bookmarks'"
|
||||
:class="{ 'is-active': profileView === 'bookmarks' }"
|
||||
:aria-label="t('bookmarks')"
|
||||
:title="t('bookmarks')"
|
||||
@click="selectProfileView('bookmarks')"
|
||||
>
|
||||
<Bookmark :size="17" />
|
||||
</kSegmentedButton>
|
||||
</kSegmented>
|
||||
<div
|
||||
v-if="profileView === 'bookmarks' && feather.bookmarksLoading"
|
||||
class="feather-network-loading"
|
||||
>
|
||||
<kPreloader />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
profileView === 'bookmarks' && !displayedProfilePosts.length
|
||||
"
|
||||
class="feather-empty"
|
||||
>
|
||||
<Bookmark :size="34" />
|
||||
<h2>{{ t('noBookmarks') }}</h2>
|
||||
<p>{{ t('noBookmarksBody') }}</p>
|
||||
</div>
|
||||
<FeatherPostCard
|
||||
v-for="post in displayedProfilePosts"
|
||||
v-show="profileView !== 'bookmarks' || !feather.bookmarksLoading"
|
||||
:key="post.id"
|
||||
:post="post"
|
||||
@follow="feather.followPost"
|
||||
|
||||
Reference in New Issue
Block a user