wip bsky client for the web & android
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: show timeline & list feeds

willow ea04b76a 50dcb0cf

+97 -29
+3 -1
src/components/Feed/FeedList.vue
··· 82 82 ? 'app.bsky.feed.getFeed' 83 83 : 'app.bsky.feed.getListFeed' 84 84 85 + const idField = props.type === 'feed' ? 'feed' : props.type === 'list' ? 'list' : undefined 86 + 85 87 const data = ok( 86 88 await rpc.get(route, { 87 89 params: { 88 - feed: props.uri, 90 + [idField as string]: props.uri, 89 91 limit: 50, 90 92 cursor: cursor.value || undefined, 91 93 },
+94 -28
src/views/Root/HomeView.vue
··· 1 1 <script lang="ts" setup> 2 2 import { onMounted, ref } from 'vue' 3 - import { type AppBskyActorGetPreferences, AppBskyFeedDefs } from '@atcute/bluesky' 3 + import { type AppBskyActorGetPreferences, AppBskyFeedDefs, AppBskyGraphDefs } from '@atcute/bluesky' 4 4 import type { ResourceUri } from '@atcute/lexicons' 5 5 6 6 import PageLayout from '@/components/Navigation/PageLayout.vue' 7 7 import FeedList from '@/components/Feed/FeedList.vue' 8 - import { useAuthStore } from '@/stores/auth' 9 8 9 + import { useAuthStore } from '@/stores/auth' 10 10 import { useDraggableScroll } from '@/composables/useDraggableScroll' 11 11 12 12 import KEYS from '@/utils/keys' 13 13 14 + type PinnedFeedItem = 15 + | { 16 + type: 'feed' 17 + uri: ResourceUri 18 + displayName: string 19 + } 20 + | { 21 + type: 'list' 22 + uri: ResourceUri 23 + displayName: string 24 + } 25 + | { 26 + type: 'timeline' 27 + displayName: string 28 + } 29 + 14 30 const auth = useAuthStore() 15 - const pinnedFeeds = ref<AppBskyFeedDefs.GeneratorView[]>([]) 31 + const pinnedFeeds = ref<PinnedFeedItem[]>([]) 16 32 const feedList = ref<InstanceType<typeof FeedList> | null>(null) 17 33 const pageLayout = ref<InstanceType<typeof PageLayout> | null>(null) 18 34 ··· 33 49 34 50 if (!savedFeedsPref) return 35 51 36 - // TODO)) handle lists & the following feed too 37 - const feedGenerators = await rpc.get('app.bsky.feed.getFeedGenerators', { 38 - params: { 39 - feeds: savedFeedsPref.items 40 - .map((feed) => feed.value) 41 - .filter((value) => value !== 'following') as ResourceUri[], 42 - }, 43 - }) 52 + const feedUris: ResourceUri[] = [] 53 + const listUris: ResourceUri[] = [] 54 + const pinnedItems: PinnedFeedItem[] = [] 44 55 45 - if (!feedGenerators.ok) return 46 - pinnedFeeds.value = feedGenerators.data.feeds.filter((feed) => 47 - savedFeedsPref.items.some((item) => item.value === feed.uri), 48 - ) 56 + for (const item of savedFeedsPref.items) { 57 + if (item.type === 'timeline') { 58 + pinnedItems.push({ 59 + type: 'timeline', 60 + displayName: 'Following', 61 + }) 62 + } else if (item.type === 'feed') { 63 + feedUris.push(item.value as ResourceUri) 64 + } else if (item.type === 'list') { 65 + listUris.push(item.value as ResourceUri) 66 + } 67 + } 49 68 50 - const activeFeedUri = localStorage.getItem(KEYS.STATE.ACTIVE_FEED_URI) 51 - if (activeFeedUri) { 52 - const matchedFeed = pinnedFeeds.value.find((feed) => feed.uri === activeFeedUri) 53 - if (matchedFeed) { 54 - activeFeed.value = matchedFeed 69 + let feedGenerators: AppBskyFeedDefs.GeneratorView[] = [] 70 + if (feedUris.length > 0) { 71 + const feedResponse = await rpc.get('app.bsky.feed.getFeedGenerators', { 72 + params: { feeds: feedUris }, 73 + }) 74 + if (feedResponse.ok) { 75 + feedGenerators = feedResponse.data.feeds 76 + } 77 + } 78 + 79 + let lists: AppBskyGraphDefs.ListView[] = [] 80 + if (listUris.length > 0) { 81 + const listPromises = listUris.map((uri) => 82 + rpc.get('app.bsky.graph.getList', { params: { list: uri } }), 83 + ) 84 + const listResponses = await Promise.all(listPromises) 85 + lists = listResponses.filter((res) => res.ok).map((res) => res.data.list) 86 + } 87 + 88 + for (const item of savedFeedsPref.items) { 89 + if (item.type === 'feed') { 90 + const generator = feedGenerators.find((g) => g.uri === item.value) 91 + if (generator) { 92 + pinnedItems.push({ 93 + type: 'feed', 94 + uri: generator.uri, 95 + displayName: generator.displayName || 'Unnamed Feed', 96 + }) 97 + } 98 + } else if (item.type === 'list') { 99 + const list = lists.find((l) => l.uri === item.value) 100 + if (list) { 101 + pinnedItems.push({ 102 + type: 'list', 103 + uri: list.uri, 104 + displayName: list.name || 'Unnamed List', 105 + }) 106 + } 107 + } 108 + } 109 + 110 + pinnedFeeds.value = pinnedItems 111 + 112 + const activeFeedKey = localStorage.getItem(KEYS.STATE.ACTIVE_FEED_URI) 113 + if (activeFeedKey) { 114 + if (activeFeedKey === 'timeline') { 115 + activeFeed.value = pinnedFeeds.value.find((item) => item.type === 'timeline') || null 116 + } else { 117 + activeFeed.value = 118 + pinnedFeeds.value.find((item) => 'uri' in item && item.uri === activeFeedKey) || null 55 119 } 56 120 } else if (pinnedFeeds.value.length > 0) { 57 - activeFeed.value = pinnedFeeds.value[0] as AppBskyFeedDefs.GeneratorView 121 + activeFeed.value = pinnedFeeds.value[0] as PinnedFeedItem 58 122 } 59 123 }) 60 124 61 - const switchFeed = async (feed: AppBskyFeedDefs.GeneratorView) => { 125 + const switchFeed = async (feed: PinnedFeedItem) => { 62 126 if (activeFeed.value === feed) { 63 127 await feedList.value?.refresh() 64 128 pageLayout.value?.scrollToTop(true) ··· 66 130 } 67 131 68 132 activeFeed.value = feed 69 - localStorage.setItem(KEYS.STATE.ACTIVE_FEED_URI, feed ? feed.uri : '') 133 + const storageKey = feed.type === 'timeline' ? 'timeline' : feed.uri || '' 134 + localStorage.setItem(KEYS.STATE.ACTIVE_FEED_URI, storageKey) 70 135 pageLayout.value?.scrollToTop(false) 71 136 } 72 137 73 - const activeFeed = ref<AppBskyFeedDefs.GeneratorView | null>(null) 138 + const activeFeed = ref<PinnedFeedItem | null>(null) 74 139 </script> 75 140 76 141 <template> ··· 84 149 > 85 150 <button 86 151 v-for="feed in pinnedFeeds" 87 - :key="feed.uri" 88 - :class="['feed-button', { active: activeFeed && activeFeed.uri === feed.uri }]" 152 + :key="feed.type + ('uri' in feed ? feed.uri : '')" 153 + :class="['feed-button', { active: activeFeed === feed }]" 89 154 @click="switchFeed(feed)" 90 155 > 91 156 {{ feed.displayName || 'Unnamed Feed' }} ··· 93 158 </div> 94 159 </template> 95 160 <FeedList 96 - :type="activeFeed ? 'feed' : 'timeline'" 97 - :uri="activeFeed ? activeFeed.uri : null" 161 + v-if="activeFeed" 162 + :type="activeFeed?.type || 'timeline'" 163 + :uri="'uri' in activeFeed ? activeFeed.uri : null" 98 164 ref="feedList" 99 165 /> 100 166 </PageLayout>