show posts on profile page
This commit is contained in:
		
							parent
							
								
									d8efaccb30
								
							
						
					
					
						commit
						455679a525
					
				
					 7 changed files with 158 additions and 10 deletions
				
			
		|  | @ -60,6 +60,7 @@ | |||
| 
 | ||||
|     "post": { | ||||
|         "loading": "loading post...", | ||||
|         "pinned": "📌 Pinned post", | ||||
|         "by": "Post by %1", | ||||
|         "time": "%1 ago", | ||||
|         "boosted": "%1 boosted this post.", | ||||
|  |  | |||
|  | @ -448,3 +448,51 @@ export async function lookupUser(host, token, handle) { | |||
| 
 | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * GET /api/v1/accounts/{user_id}/statuses | ||||
|  * @param {string} host - The domain of the target server. | ||||
|  * @param {string} token - The application token. | ||||
|  * @param {string} user_id - The ID of the user to fetch. | ||||
|  * @param {string} max_id - If provided, only shows notifications before this ID. | ||||
|  * @param {boolean} replies - If replies should be fetched. | ||||
|  * @param {boolean} boosts - If boosts should be fetched. | ||||
|  * @param {boolean} only_media - If only media should be fetched. | ||||
|  */ | ||||
| export async function getUserPosts(host, token, user_id, max_id, show_replies, show_boosts, only_media) { | ||||
|     let url = new URL(`https://${host}/api/v1/accounts/${user_id}/statuses`); | ||||
|     let query = []; | ||||
|     if (!show_replies) | ||||
|         query.push('exclude_replies=true'); | ||||
|     if (!show_boosts) | ||||
|         query.push('exclude_boosts=true'); | ||||
|     if (only_media) | ||||
|         query.push('only_media=true'); | ||||
|     if (max_id) | ||||
|         query.push(`max_id=${max_id}`); | ||||
|     url.search = query.join('&'); | ||||
| 
 | ||||
|     const data = await fetch(url, { | ||||
|         method: 'GET', | ||||
|         headers: { "Authorization": token ? `Bearer ${token}` : null } | ||||
|     }).then(res => res.json()); | ||||
| 
 | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * GET /api/v1/accounts/{user_id}/statuses?pinned=true | ||||
|  * @param {string} host - The domain of the target server. | ||||
|  * @param {string} token - The application token. | ||||
|  * @param {string} user_id - The ID of the user to fetch. | ||||
|  */ | ||||
| export async function getUserPinnedPosts(host, token, user_id) { | ||||
|     let url = `https://${host}/api/v1/accounts/${user_id}/statuses?pinned=true`; | ||||
| 
 | ||||
|     const data = await fetch(url, { | ||||
|         method: 'GET', | ||||
|         headers: { "Authorization": token ? `Bearer ${token}` : null } | ||||
|     }).then(res => res.json()); | ||||
| 
 | ||||
|     return data; | ||||
| } | ||||
|  |  | |||
|  | @ -81,6 +81,10 @@ img.emoji { | |||
|     margin: -.2em 0; | ||||
| } | ||||
| 
 | ||||
| hr { | ||||
|     border-color: color-mix(in srgb, transparent, var(--accent) 50%); | ||||
| } | ||||
| 
 | ||||
| .throb { | ||||
|     animation: .25s throb alternate infinite ease-in; | ||||
| } | ||||
|  |  | |||
|  | @ -4,12 +4,10 @@ | |||
|     import { server } from '$lib/client/server.js'; | ||||
|     import { app } from '$lib/client/app.js'; | ||||
|     import { playSound } from '$lib/sound.js'; | ||||
|     import { getTimeline } from '$lib/timeline.js'; | ||||
|     import { getNotifications } from '$lib/notifications.js'; | ||||
|     import { goto } from '$app/navigation'; | ||||
|     import { page } from '$app/stores'; | ||||
|     import { createEventDispatcher } from 'svelte'; | ||||
|     import { notifications, unread_notif_count } from '$lib/notifications.js'; | ||||
|     import { unread_notif_count } from '$lib/notifications.js'; | ||||
|     import Lang from '$lib/lang'; | ||||
| 
 | ||||
|     import Logo from '$lib/../img/campfire-logo.svg'; | ||||
|  | @ -81,7 +79,7 @@ | |||
|             {lang.string('navigation.timeline')} | ||||
|         </Button> | ||||
|         <Button label="Notifications" | ||||
|                 href="notifications"} | ||||
|                 href="/notifications"} | ||||
|                 active={$page.url.pathname === "/notifications"}> | ||||
|             <svelte:fragment slot="icon"> | ||||
|                 <NotificationsIcon/> | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ | |||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     let mention = (accounts) => { | ||||
|         let res = `<a href=${account.url}>${account.rich_name}</a>`; | ||||
|         let res = `<a href="/${$server.host}/${account.fqn}">${account.rich_name}</a>`; | ||||
|         if (accounts.length > 1) res += ' ' + lang.string('notification.and_others').replaceAll('%1', accounts.length - 1); | ||||
|         return res; | ||||
|     }; | ||||
|  | @ -90,7 +90,7 @@ | |||
|         </span> | ||||
|         <span class="notif-avatars"> | ||||
|             {#if data.accounts.length == 1} | ||||
|                 <a href={data.accounts[0].url} class="notif-avatar"> | ||||
|                 <a href="/{$server.host}/{data.accounts[0].fqn}" class="notif-avatar"> | ||||
|                     <img src={data.accounts[0].avatar_url} alt="" width="28" height="28" /> | ||||
|                 </a> | ||||
|             {:else} | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
|     import { onMount } from 'svelte'; | ||||
|     import { goto } from '$app/navigation'; | ||||
|     import { server } from '$lib/client/server'; | ||||
|     import Lang from '$lib/lang'; | ||||
| 
 | ||||
|     import BoostContext from './BoostContext.svelte'; | ||||
|     import ReplyContext from './ReplyContext.svelte'; | ||||
|  | @ -12,6 +13,9 @@ | |||
| 
 | ||||
|     export let post_data; | ||||
|     export let focused = false; | ||||
|     export let pinned = false; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     let post_context = undefined; | ||||
|     let post = post_data; | ||||
|  | @ -41,8 +45,6 @@ | |||
|             window.scrollTo(0, el.scrollHeight); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     let aria_label = post.account.username + '; ' + post.text + '; ' + post.created_at; | ||||
| </script> | ||||
| 
 | ||||
| <div class="post-container"> | ||||
|  | @ -51,12 +53,15 @@ | |||
|             <ReplyContext post={reply} /> | ||||
|         {/await} | ||||
|     {/if} | ||||
|     {#if pinned} | ||||
|         <p class="pinned">{lang.string('post.pinned')}</p> | ||||
|     {/if} | ||||
|     {#if is_boost && !post_context.text} | ||||
|         <BoostContext post={post_context} /> | ||||
|     {/if} | ||||
|     <article | ||||
|             class={"post" + (focused ? " focused" : "")} | ||||
|             aria-label={aria_label} | ||||
|             aria-label={post.account.username + '; ' + post.text + '; ' + post.created_at} | ||||
|             bind:this={el} | ||||
|             on:mousedown={e => {mouse_pos.left = e.pageX; mouse_pos.top = e.pageY}} | ||||
|             on:mouseup={e => {if (e.pageX == mouse_pos.left && e.pageY == mouse_pos.top) gotoPost(e)}} | ||||
|  | @ -83,6 +88,12 @@ | |||
|         border-top: none; | ||||
|     } | ||||
| 
 | ||||
|     .pinned { | ||||
|         margin: 1em 1.2em -.2em 1.2em; | ||||
|         font-size: .8em; | ||||
|         color: var(--accent); | ||||
|     } | ||||
| 
 | ||||
|     .post { | ||||
|         padding: 16px; | ||||
|         transition: background-color .1s; | ||||
|  |  | |||
|  | @ -9,26 +9,60 @@ | |||
|     import { server, createServer } from '$lib/client/server.js'; | ||||
|     import { app } from '$lib/client/app.js'; | ||||
|     import { parseAccount } from '$lib/account.js'; | ||||
|     import { parsePost } from '$lib/post.js'; | ||||
|     import { account } from '$lib/stores/account'; | ||||
|     import { goto, afterNavigate } from '$app/navigation'; | ||||
|     import { base } from '$app/paths'; | ||||
|     import Post from '../../../lib/ui/post/Post.svelte'; | ||||
|     import { writable } from 'svelte/store'; | ||||
| 
 | ||||
|     export let data; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     let profile_pinned_posts = writable([]); | ||||
|     let profile_posts_max_id = null; | ||||
|     let profile_posts = writable([]); | ||||
|     let profile = fetchProfile(data.account_handle); | ||||
|     let error = false; | ||||
|     let previous_page = base; | ||||
| 
 | ||||
|     let post_replies = false; | ||||
|     let post_boosts = true; | ||||
|     let post_media = false; | ||||
| 
 | ||||
|     afterNavigate(({from}) => { | ||||
|         previous_page = from?.url.pathname || previous_page; | ||||
|         profile = fetchProfile(data.account_handle); | ||||
|     }) | ||||
| 
 | ||||
|     async function getPosts(profile, max_id) { | ||||
|         const posts = await api.getUserPosts( | ||||
|             $server.host, | ||||
|             $app.token, | ||||
|             profile.id, | ||||
|             max_id, | ||||
|             post_replies, | ||||
|             post_boosts, | ||||
|             post_media | ||||
|         ); | ||||
|         let parsed_posts = []; | ||||
|         for (let post of posts) { | ||||
|             parsed_posts.push(await parsePost(post, 1)); | ||||
|         } | ||||
|         profile_posts.update(posts => { | ||||
|             posts.push(...parsed_posts); | ||||
|             return posts; | ||||
|         }); | ||||
|         return parsed_posts.length > 0 ? parsed_posts[parsed_posts.length - 1].id : null; | ||||
|     } | ||||
| 
 | ||||
|     async function fetchProfile(handle) { | ||||
|         let token = $app ? $app.token : null; | ||||
| 
 | ||||
|         profile_posts.set([]); | ||||
|         profile_pinned_posts.set([]); | ||||
| 
 | ||||
|         if (!$server || $server.host !== data.server_host) { | ||||
|             server.set(await createServer(data.server_host)); | ||||
|             if (!$server) { | ||||
|  | @ -40,7 +74,6 @@ | |||
|         let profile_data; | ||||
|         try { | ||||
|             profile_data = await api.lookupUser($server.host, token, handle); | ||||
|             console.debug(profile_data); | ||||
|         } catch (error) { | ||||
|             throw error; | ||||
|         } | ||||
|  | @ -51,6 +84,45 @@ | |||
|         } | ||||
|         let profile = await parseAccount(profile_data, 0); | ||||
| 
 | ||||
|         api.getUserPinnedPosts( | ||||
|             $server.host, | ||||
|             token, | ||||
|             profile.id, | ||||
|         ).then(async posts => { | ||||
|             for (let post of posts) { | ||||
|                 const parsedPost = await parsePost(post, 1); | ||||
|                 profile_pinned_posts.update(posts => { | ||||
|                     posts.push(parsedPost); | ||||
|                     return posts; | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         let post_lock = false; // `true` == "locked" | ||||
|         getPosts(profile, null).then(last_id => { | ||||
|             profile_posts_max_id = last_id; | ||||
|             post_lock = false; | ||||
|         }); | ||||
| 
 | ||||
|         document.addEventListener("scroll", () => { | ||||
|             if (window.innerHeight + window.scrollY < document.body.offsetHeight - 2048) | ||||
|                 return; | ||||
|             if ($profile_posts.length == 0) | ||||
|                 return; | ||||
|             if (profile_posts_max_id == null) | ||||
|                 return; | ||||
|             if (profile_posts_max_id != $profile_posts[$profile_posts.length - 1].id) | ||||
|                 return; | ||||
| 
 | ||||
|             if (post_lock) return; | ||||
|             post_lock = true; | ||||
| 
 | ||||
|             getPosts(profile, profile_posts_max_id).then(last_id => { | ||||
|                 profile_posts_max_id = last_id; | ||||
|                 post_lock = false; | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         return profile; | ||||
|     } | ||||
| </script> | ||||
|  | @ -118,6 +190,20 @@ | |||
|             {lang.string('profile.media')} | ||||
|         </Button> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="profile-pinned-posts"> | ||||
|         {#if profile_pinned_posts} | ||||
|             {#each $profile_pinned_posts as post} | ||||
|                 <Post post_data={post} pinned /> | ||||
|             {/each} | ||||
|             <br/><hr/><br/> | ||||
|         {/if} | ||||
|     </div> | ||||
|     <div class="profile-posts"> | ||||
|         {#each $profile_posts as post} | ||||
|             <Post post_data={post} /> | ||||
|         {/each} | ||||
|     </div> | ||||
| {:catch error} | ||||
|     <p class="error">{error}</p> | ||||
| {/await} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue