i think i finally fixed the state management awfulness
This commit is contained in:
		
							parent
							
								
									6953b49563
								
							
						
					
					
						commit
						40be540527
					
				
					 18 changed files with 402 additions and 417 deletions
				
			
		|  | @ -1,4 +1,4 @@ | |||
| import { Client } from '../client/client.js'; | ||||
| import { client } from '../client/client.js'; | ||||
| import { capabilities } from '../client/instance.js'; | ||||
| import Post from '../post.js'; | ||||
| import User from '../user/user.js'; | ||||
|  | @ -31,25 +31,23 @@ export async function createApp(host) { | |||
| } | ||||
| 
 | ||||
| export function getOAuthUrl() { | ||||
|     let client = get(Client.get()); | ||||
|     return `https://${client.instance.host}/oauth/authorize` + | ||||
|         `?client_id=${client.app.id}` + | ||||
|     return `https://${get(client).instance.host}/oauth/authorize` + | ||||
|         `?client_id=${get(client).app.id}` + | ||||
|         "&scope=read+write+push" + | ||||
|         `&redirect_uri=${location.origin}/callback` + | ||||
|         "&response_type=code"; | ||||
| } | ||||
| 
 | ||||
| export async function getToken(code) { | ||||
|     let client = get(Client.get()); | ||||
|     let form = new FormData(); | ||||
|     form.append("client_id", client.app.id); | ||||
|     form.append("client_secret", client.app.secret); | ||||
|     form.append("client_id", get(client).app.id); | ||||
|     form.append("client_secret", get(client).app.secret); | ||||
|     form.append("redirect_uri", `${location.origin}/callback`); | ||||
|     form.append("grant_type", "authorization_code"); | ||||
|     form.append("code", code); | ||||
|     form.append("scope", "read write push"); | ||||
| 
 | ||||
|     const res = await fetch(`https://${client.instance.host}/oauth/token`, { | ||||
|     const res = await fetch(`https://${get(client).instance.host}/oauth/token`, { | ||||
|         method: "POST", | ||||
|         body: form, | ||||
|     }) | ||||
|  | @ -65,13 +63,12 @@ export async function getToken(code) { | |||
| } | ||||
| 
 | ||||
| export async function revokeToken() { | ||||
|     let client = get(Client.get()); | ||||
|     let form = new FormData(); | ||||
|     form.append("client_id", client.app.id); | ||||
|     form.append("client_secret", client.app.secret); | ||||
|     form.append("token", client.app.token); | ||||
|     form.append("client_id", get(client).app.id); | ||||
|     form.append("client_secret", get(client).app.secret); | ||||
|     form.append("token", get(client).app.token); | ||||
| 
 | ||||
|     const res = await fetch(`https://${client.instance.host}/oauth/revoke`, { | ||||
|     const res = await fetch(`https://${get(client).instance.host}/oauth/revoke`, { | ||||
|         method: "POST", | ||||
|         body: form, | ||||
|     }) | ||||
|  | @ -85,34 +82,32 @@ export async function revokeToken() { | |||
| } | ||||
| 
 | ||||
| export async function verifyCredentials() { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/accounts/verify_credentials`; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/accounts/verify_credentials`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'GET', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => res.json()); | ||||
| 
 | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| export async function getTimeline(last_post_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/timelines/home`; | ||||
|     if (!get(client).instance || !get(client).app) return false; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/timelines/home`; | ||||
|     if (last_post_id) url += "?max_id=" + last_post_id; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'GET', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => res.json()); | ||||
| 
 | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| export async function getPost(post_id, ancestor_count) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}`; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'GET', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|  | @ -120,11 +115,10 @@ export async function getPost(post_id, ancestor_count) { | |||
| } | ||||
| 
 | ||||
| export async function getPostContext(post_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/context`; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/context`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'GET', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|  | @ -132,11 +126,10 @@ export async function getPostContext(post_id) { | |||
| } | ||||
| 
 | ||||
| export async function boostPost(post_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/reblog`; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/reblog`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|  | @ -144,11 +137,10 @@ export async function boostPost(post_id) { | |||
| } | ||||
| 
 | ||||
| export async function unboostPost(post_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unreblog`; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unreblog`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|  | @ -156,11 +148,10 @@ export async function unboostPost(post_id) { | |||
| } | ||||
| 
 | ||||
| export async function favouritePost(post_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/favourite`; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/favourite`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|  | @ -168,11 +159,10 @@ export async function favouritePost(post_id) { | |||
| } | ||||
| 
 | ||||
| export async function unfavouritePost(post_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unfavourite`; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unfavourite`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|  | @ -185,11 +175,10 @@ export async function reactPost(post_id, shortcode) { | |||
|     // to the default like emote.
 | ||||
|     // identical api calls on chuckya instances do not display
 | ||||
|     // this behaviour.
 | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/react/${encodeURIComponent(shortcode)}`; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/react/${encodeURIComponent(shortcode)}`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|  | @ -197,26 +186,23 @@ export async function reactPost(post_id, shortcode) { | |||
| } | ||||
| 
 | ||||
| export async function unreactPost(post_id, shortcode) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unreact/${encodeURIComponent(shortcode)}`; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unreact/${encodeURIComponent(shortcode)}`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| export async function parsePost(data, ancestor_count, with_context) { | ||||
|     let client = get(Client.get()); | ||||
| export async function parsePost(data, ancestor_count) { | ||||
|     let post = new Post(); | ||||
| 
 | ||||
|     post.text = data.content; | ||||
| 
 | ||||
|     post.reply = null; | ||||
|     if (!with_context && // ancestor replies are handled in full later
 | ||||
|         (data.in_reply_to_id || data.reply) && | ||||
|     if ((data.in_reply_to_id || data.reply) && | ||||
|         ancestor_count !== 0 | ||||
|     ) { | ||||
|         const reply_data = data.reply || await getPost(data.in_reply_to_id, ancestor_count - 1); | ||||
|  | @ -225,28 +211,8 @@ export async function parsePost(data, ancestor_count, with_context) { | |||
|         if (!reply_data) return false; | ||||
|         post.reply = await parsePost(reply_data, ancestor_count - 1, false); | ||||
|     } | ||||
|     post.boost = data.reblog ? await parsePost(data.reblog, 1, false) : null; | ||||
| 
 | ||||
|     post.replies = []; | ||||
|     if (with_context) { | ||||
|         const replies_data = await getPostContext(data.id); | ||||
|         if (replies_data) { | ||||
|             // posts this is replying to
 | ||||
|             if (replies_data.ancestors) { | ||||
|                 let head = post; | ||||
|                 while (replies_data.ancestors.length > 0) { | ||||
|                     head.reply = await parsePost(replies_data.ancestors.pop(), 0, false); | ||||
|                     head = head.reply; | ||||
|                 } | ||||
|             } | ||||
|             // posts in reply to this
 | ||||
|             if (replies_data.descendants) { | ||||
|                 for (let i in replies_data.descendants) { | ||||
|                     post.replies.push(await parsePost(replies_data.descendants[i], 0, false)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     post.boost = data.reblog ? await parsePost(data.reblog, 1, false) : null; | ||||
| 
 | ||||
|     post.id = data.id; | ||||
|     post.created_at = new Date(data.created_at); | ||||
|  | @ -275,7 +241,7 @@ export async function parsePost(data, ancestor_count, with_context) { | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     if (data.reactions && client.instance.capabilities.includes(capabilities.REACTIONS)) { | ||||
|     if (data.reactions && get(client).instance.capabilities.includes(capabilities.REACTIONS)) { | ||||
|         post.reactions = parseReactions(data.reactions); | ||||
|     } | ||||
|     return post; | ||||
|  | @ -286,8 +252,7 @@ export async function parseUser(data) { | |||
|         console.error("Attempted to parse user data but no data was provided"); | ||||
|         return null; | ||||
|     } | ||||
|     let client = get(Client.get()); | ||||
|     let user = await client.getCacheUser(data.id); | ||||
|     let user = await get(client).getCacheUser(data.id); | ||||
| 
 | ||||
|     if (user) return user; | ||||
|     // cache miss!
 | ||||
|  | @ -302,7 +267,7 @@ export async function parseUser(data) { | |||
|     if (data.acct.includes('@')) | ||||
|         user.host = data.acct.split('@')[1]; | ||||
|     else | ||||
|         user.host = client.instance.host; | ||||
|         user.host = get(client).instance.host; | ||||
| 
 | ||||
|     user.emojis = []; | ||||
|     data.emojis.forEach(emoji_data => { | ||||
|  | @ -312,12 +277,11 @@ export async function parseUser(data) { | |||
|         user.emojis.push(parseEmoji(emoji_data)); | ||||
|     }); | ||||
| 
 | ||||
|     client.putCacheUser(user); | ||||
|     get(client).putCacheUser(user); | ||||
|     return user; | ||||
| } | ||||
| 
 | ||||
| export function parseReactions(data) { | ||||
|     let client = get(Client.get()); | ||||
|     let reactions = []; | ||||
|     data.forEach(reaction_data => { | ||||
|         let reaction = { | ||||
|  | @ -338,16 +302,15 @@ export function parseEmoji(data) { | |||
|         data.host, | ||||
|         data.url, | ||||
|     ); | ||||
|     get(Client.get()).putCacheEmoji(emoji); | ||||
|     get(client).putCacheEmoji(emoji); | ||||
|     return emoji; | ||||
| } | ||||
| 
 | ||||
| export async function getUser(user_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/accounts/${user_id}`; | ||||
|     let url = `https://${get(client).instance.host}/api/v1/accounts/${user_id}`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'GET', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||
|     }).then(res => res.json()); | ||||
| 
 | ||||
|     const user = await parseUser(data); | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import { Instance, server_types } from './instance.js'; | |||
| import * as api from './api.js'; | ||||
| import { get, writable } from 'svelte/store'; | ||||
| 
 | ||||
| let client = writable(false); | ||||
| export const client = writable(false); | ||||
| 
 | ||||
| const save_name = "campfire"; | ||||
| 
 | ||||
|  | @ -22,15 +22,6 @@ export class Client { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     static get() { | ||||
|         let current = get(client); | ||||
|         if (current && current.app) return client; | ||||
|         let new_client = new Client(); | ||||
|         new_client.load(); | ||||
|         client.set(new_client); | ||||
|         return client; | ||||
|     } | ||||
| 
 | ||||
|     async init(host) { | ||||
|         if (host.startsWith("https://")) host = host.substring(8); | ||||
|         const url = `https://${host}/api/v1/instance`; | ||||
|  | @ -76,30 +67,30 @@ export class Client { | |||
|             console.error("Failed to obtain access token"); | ||||
|             return false; | ||||
|         } | ||||
|         this.app.token = token; | ||||
|         client.set(this); | ||||
|         return token; | ||||
|     } | ||||
| 
 | ||||
|     async revokeToken() { | ||||
|         return await api.revokeToken(); | ||||
|     } | ||||
| 
 | ||||
|     async verifyCredentials() { | ||||
|     async getUser() { | ||||
|         // already known
 | ||||
|         if (this.user) return this.user; | ||||
| 
 | ||||
|         // cannot provide- not logged in
 | ||||
|         if (!this.app || !this.app.token) { | ||||
|             this.user = false; | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // logged in- attempt to retrieve using token
 | ||||
|         const data = await api.verifyCredentials(); | ||||
|         if (!data) { | ||||
|             this.user = false; | ||||
|             return false; | ||||
|         } | ||||
|         await client.update(async c => { | ||||
|             c.user = await api.parseUser(data); | ||||
|             console.log(`Logged in as @${c.user.username}@${c.user.host}`); | ||||
|         }); | ||||
|         return this.user; | ||||
|         const user = await api.parseUser(data); | ||||
|         console.log(`Logged in as @${user.username}@${user.host}`); | ||||
|         return user; | ||||
|     } | ||||
| 
 | ||||
|     async getTimeline(last_post_id) { | ||||
|  | @ -110,6 +101,10 @@ export class Client { | |||
|         return await api.getPost(post_id, parent_replies, child_replies); | ||||
|     } | ||||
| 
 | ||||
|     async getPostContext(post_id) { | ||||
|         return await api.getPostContext(post_id); | ||||
|     } | ||||
| 
 | ||||
|     async boostPost(post_id) { | ||||
|         return await api.boostPost(post_id); | ||||
|     } | ||||
|  | @ -199,7 +194,7 @@ export class Client { | |||
|             console.warn("Failed to log out correctly; ditching the old tokens anyways."); | ||||
|         } | ||||
|         localStorage.removeItem(save_name); | ||||
|         client.set(false); | ||||
|         client.set(new Client()); | ||||
|         console.log("Logged out successfully."); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { Client } from './client/client.js'; | ||||
| import { client } from './client/client.js'; | ||||
| import { get } from 'svelte/store'; | ||||
| 
 | ||||
| export const EMOJI_REGEX = /:[\w\-.]{0,32}@[\w\-.]{0,32}:/g; | ||||
|  | @ -33,7 +33,7 @@ export function parseText(text, host) { | |||
|     let length = text.substring(index + 1).search(':'); | ||||
|     if (length <= 0) return text; | ||||
|     let emoji_name = text.substring(index + 1, index + length + 1); | ||||
|     let emoji = get(Client.get()).getEmoji(emoji_name + '@' + host); | ||||
|     let emoji = get(client).getEmoji(emoji_name + '@' + host); | ||||
| 
 | ||||
|     if (emoji) { | ||||
|         return text.substring(0, index) + emoji.html + | ||||
|  | @ -46,7 +46,7 @@ export function parseText(text, host) { | |||
| export function parseOne(emoji_id) { | ||||
|     if (emoji_id == '❤') return '❤️'; // stupid heart unicode
 | ||||
|     if (EMOJI_REGEX.exec(':' + emoji_id + ':')) return emoji_id; | ||||
|     let cached_emoji = get(Client.get()).getEmoji(emoji_id); | ||||
|     let cached_emoji = get(client).getEmoji(emoji_id); | ||||
|     if (!cached_emoji) return emoji_id; | ||||
|     return cached_emoji.html; | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { Client } from '$lib/client/client.js'; | ||||
| import { client } from '$lib/client/client.js'; | ||||
| import { get, writable } from 'svelte/store'; | ||||
| import { parsePost } from '$lib/client/api.js'; | ||||
| 
 | ||||
|  | @ -10,11 +10,9 @@ export async function getTimeline(clean) { | |||
|     if (loading) return; // no spamming!!
 | ||||
|     loading = true; | ||||
| 
 | ||||
|     let client = get(Client.get()); | ||||
| 
 | ||||
|     let timeline_data; | ||||
|     if (clean || get(posts).length === 0) timeline_data = await client.getTimeline() | ||||
|     else timeline_data = await client.getTimeline(get(posts)[get(posts).length - 1].id); | ||||
|     if (clean || get(posts).length === 0) timeline_data = await get(client).getTimeline() | ||||
|     else timeline_data = await get(client).getTimeline(get(posts)[get(posts).length - 1].id); | ||||
| 
 | ||||
|     if (!timeline_data) { | ||||
|         console.error(`Failed to retrieve timeline.`); | ||||
|  |  | |||
|  | @ -1,10 +1,6 @@ | |||
| <script> | ||||
|     import Button from './Button.svelte'; | ||||
|     import Post from './post/Post.svelte'; | ||||
|     import Error from './Error.svelte'; | ||||
|     import { Client } from '$lib/client/client.js'; | ||||
|     import { parsePost } from '$lib/client/api.js'; | ||||
|     import { get } from 'svelte/store'; | ||||
|     import { posts, getTimeline } from '$lib/timeline.js'; | ||||
| 
 | ||||
|     getTimeline(); | ||||
|  | @ -15,6 +11,15 @@ | |||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| <header> | ||||
|     <h1>Home</h1> | ||||
|     <nav> | ||||
|         <Button centered active>Home</Button> | ||||
|         <Button centered disabled>Local</Button> | ||||
|         <Button centered disabled>Federated</Button> | ||||
|     </nav> | ||||
| </header> | ||||
| 
 | ||||
| <div id="feed" role="feed"> | ||||
|     {#if posts.length <= 0} | ||||
|         <div class="loading throb"> | ||||
|  |  | |||
							
								
								
									
										162
									
								
								src/lib/ui/LoginForm.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								src/lib/ui/LoginForm.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,162 @@ | |||
| <script> | ||||
|     import { client } from '$lib/client/client.js'; | ||||
|     import { get } from 'svelte/store'; | ||||
| 
 | ||||
|     import Logo from '$lib/../img/campfire-logo.svg'; | ||||
| 
 | ||||
|     let instance_url_error = false; | ||||
|     let logging_in = false; | ||||
| 
 | ||||
|     function log_in(event) { | ||||
|         event.preventDefault(); | ||||
|         instance_url_error = false; | ||||
| 
 | ||||
|         logging_in = true; | ||||
|         const host = event.target.host.value; | ||||
| 
 | ||||
|         if (!host || host === "") { | ||||
|             instance_url_error = "Please enter an instance domain."; | ||||
|             logging_in = false; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         console.log(client); | ||||
| 
 | ||||
|         get(client).init(host).then(res => { | ||||
|             logging_in = false; | ||||
|             if (!res) return; | ||||
|             if (res.constructor === String) { | ||||
|                 instance_url_error = res; | ||||
|                 return; | ||||
|             }; | ||||
|             let oauth_url = get(client).getOAuthUrl(); | ||||
|             location = oauth_url; | ||||
|         }); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <form on:submit={log_in} id="login-form"> | ||||
|     <div class="app-logo"> | ||||
|         <Logo /> | ||||
|     </div> | ||||
|     <p>Welcome, fediverse user!</p> | ||||
|     <p>Please enter your instance domain to log in.</p> | ||||
|     <div class="input-wrapper"> | ||||
|         <input type="text" id="host" aria-label="instance domain" class={logging_in ? "throb" : ""}> | ||||
|         {#if instance_url_error} | ||||
|             <p class="error">{instance_url_error}</p> | ||||
|         {/if} | ||||
|     </div> | ||||
|     <br> | ||||
|     <button type="submit" id="login" class={logging_in ? "disabled" : ""}>Log in</button> | ||||
|     <p><small> | ||||
|         Please note this is | ||||
|         <strong><em>extremely experimental software</em></strong>; | ||||
|         things are likely to break! | ||||
|         <br> | ||||
|         If that's all cool with you, welcome aboard! | ||||
|     </small></p> | ||||
| 
 | ||||
|     <p class="form-footer">made with ❤ by <a href="https://bliss.town">bliss town</a>, 2024</p> | ||||
| </form> | ||||
| 
 | ||||
| <style> | ||||
|     form#login-form { | ||||
|         height: 100vh; | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         justify-content: center; | ||||
|         align-items: center; | ||||
|         text-align: center; | ||||
|     } | ||||
| 
 | ||||
|     .input-wrapper { | ||||
|         width: 360px; | ||||
|         margin: 0 auto; | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         align-items: center; | ||||
|     } | ||||
| 
 | ||||
|     input[type=text] { | ||||
|         width: 100%; | ||||
|         padding: 12px; | ||||
|         display: block; | ||||
|         border-radius: 8px; | ||||
|         border: 1px solid var(--accent); | ||||
|         background-color: var(--bg-800); | ||||
| 
 | ||||
|         font-family: inherit; | ||||
|         font-weight: bold; | ||||
|         font-size: inherit; | ||||
|         color: var(--text); | ||||
| 
 | ||||
|         transition: box-shadow .2s; | ||||
|     } | ||||
| 
 | ||||
|     input[type=text]::placeholder { | ||||
|         opacity: .8; | ||||
|     } | ||||
| 
 | ||||
|     input[type=text]:focus { | ||||
|         outline: none; | ||||
|         box-shadow: 0 0 16px color-mix(in srgb, transparent, var(--accent) 25%); | ||||
|     } | ||||
| 
 | ||||
|     .error { | ||||
|         margin: 6px; | ||||
|         font-style: italic; | ||||
|         font-size: .9em; | ||||
|         color: red; | ||||
|         opacity: .7; | ||||
|     } | ||||
| 
 | ||||
|     button#login { | ||||
|         margin: 8px auto; | ||||
|         padding: 12px 24px; | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         justify-content: space-between; | ||||
|         align-items: center; | ||||
| 
 | ||||
|         font-family: inherit; | ||||
|         font-size: 1rem; | ||||
|         font-weight: 600; | ||||
|         text-align: left; | ||||
| 
 | ||||
|         border-radius: 8px; | ||||
|         border-width: 2px; | ||||
|         border-style: solid; | ||||
| 
 | ||||
|         background-color: var(--bg-700); | ||||
|         color: var(--text); | ||||
|         border-color: transparent; | ||||
| 
 | ||||
|         transition-property: border-color, background-color, color; | ||||
|         transition-timing-function: ease-out; | ||||
|         transition-duration: .1s; | ||||
| 
 | ||||
|         cursor: pointer; | ||||
|         text-align: center; | ||||
|         justify-content: center; | ||||
|     } | ||||
| 
 | ||||
|     button#login:hover { | ||||
|         background-color: color-mix(in srgb, var(--bg-700), var(--accent) 10%); | ||||
|         border-color: color-mix(in srgb, var(--bg-700), var(--accent) 20%); | ||||
|     } | ||||
| 
 | ||||
|     button#login:active { | ||||
|         background-color: color-mix(in srgb, var(--bg-700), var(--bg-800) 50%); | ||||
|         border-color: color-mix(in srgb, var(--bg-700), var(--bg-800) 10%); | ||||
|     } | ||||
| 
 | ||||
|     button#login.disabled { | ||||
|         opacity: .5; | ||||
|         cursor: initial; | ||||
|     } | ||||
| 
 | ||||
|     .form-footer { | ||||
|         opacity: .7; | ||||
|     } | ||||
| </style> | ||||
|  | @ -2,7 +2,7 @@ | |||
|     import Logo from '$lib/../img/campfire-logo.svg'; | ||||
|     import Button from './Button.svelte'; | ||||
|     import Feed from './Feed.svelte'; | ||||
|     import { Client } from '$lib/client/client.js'; | ||||
|     import { client } from '$lib/client/client.js'; | ||||
|     import { play_sound } from '$lib/sound.js'; | ||||
|     import { getTimeline } from '$lib/timeline.js'; | ||||
|     import { goto } from '$app/navigation'; | ||||
|  | @ -22,11 +22,6 @@ | |||
| 
 | ||||
|     const VERSION = APP_VERSION; | ||||
|      | ||||
|     let client = false; | ||||
|     Client.get().subscribe(c => { | ||||
|         client = c; | ||||
|     }); | ||||
| 
 | ||||
|     let notification_count = 0; | ||||
|     if (notification_count > 99) notification_count = "99+"; | ||||
| 
 | ||||
|  | @ -44,26 +39,20 @@ | |||
| 
 | ||||
|     async function log_out() { | ||||
|         if (!confirm("This will log you out. Are you sure?")) return; | ||||
|         await get(Client.get()).logout(); | ||||
|         await get(client).logout(); | ||||
|         goto("/"); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <div id="navigation"> | ||||
|     {#if client.instance && client.instance.icon_url && client.instance.banner_url} | ||||
|         <header class="instance-header" style="background-image: url({client.instance.banner_url})"> | ||||
|             <img src={client.instance.icon_url} class="instance-icon" height="92px" aria-hidden="true"> | ||||
|         </header> | ||||
|     {:else} | ||||
|     <header class="instance-header"> | ||||
|         <div class="app-logo"> | ||||
|             <Logo /> | ||||
|         </div> | ||||
|     </header> | ||||
|     {/if} | ||||
| 
 | ||||
|     <div id="nav-items"> | ||||
|         <Button label="Timeline" on:click={() => goTimeline()} active={client.user}> | ||||
|         <Button label="Timeline" on:click={() => goTimeline()} active={!!$client.user}> | ||||
|             <svelte:fragment slot="icon"> | ||||
|                 <TimelineIcon/> | ||||
|             </svelte:fragment> | ||||
|  | @ -117,7 +106,7 @@ | |||
|             </Button> | ||||
|     </div> | ||||
| 
 | ||||
|     {#if (client.user)} | ||||
|     {#if $client.user} | ||||
|     <div id="account-items"> | ||||
|         <div class="flex-row"> | ||||
|             <Button centered label="Profile information" disabled> | ||||
|  | @ -138,11 +127,11 @@ | |||
|         </div> | ||||
| 
 | ||||
|         <div id="account-button"> | ||||
|             <img src={client.user.avatar_url} class="account-avatar" height="64px" alt="" aria-hidden="true" on:click={() => play_sound()}> | ||||
|             <img src={$client.user.avatar_url} class="account-avatar" height="64px" alt="" aria-hidden="true" on:click={() => play_sound()}> | ||||
|             <div class="account-name" aria-hidden="true"> | ||||
|                 <span class="nickname" title={client.user.nickname}>{client.user.nickname}</span> | ||||
|                 <span class="username" title={`@${client.user.username}@${client.user.host}`}> | ||||
|                     {`@${client.user.username}@${client.user.host}`} | ||||
|                 <span class="nickname" title={$client.user.nickname}>{$client.user.nickname}</span> | ||||
|                 <span class="username" title={`@${$client.user.username}@${$client.user.host}`}> | ||||
|                     {`@${$client.user.username}@${$client.user.host}`} | ||||
|                 </span> | ||||
|             </div> | ||||
|         </div> | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <script> | ||||
|     import { Client } from '../../client/client.js'; | ||||
|     import { client } from '../../client/client.js'; | ||||
|     import * as api from '../../client/api.js'; | ||||
|     import { get } from 'svelte/store'; | ||||
| 
 | ||||
|  | @ -16,12 +16,11 @@ | |||
|     export let post; | ||||
| 
 | ||||
|     async function toggleBoost() { | ||||
|         let client = get(Client.get()); | ||||
|         let data; | ||||
|         if (post.boosted) | ||||
|             data = await client.unboostPost(post.id); | ||||
|             data = await get(client).unboostPost(post.id); | ||||
|         else | ||||
|             data = await client.boostPost(post.id); | ||||
|             data = await get(client).boostPost(post.id); | ||||
|         if (!data) { | ||||
|             console.error(`Failed to boost post ${post.id}`); | ||||
|             return; | ||||
|  | @ -31,12 +30,11 @@ | |||
|     } | ||||
| 
 | ||||
|     async function toggleFavourite() { | ||||
|         let client = get(Client.get()); | ||||
|         let data; | ||||
|         if (post.favourited) | ||||
|             data = await client.unfavouritePost(post.id); | ||||
|             data = await get(client).unfavouritePost(post.id); | ||||
|         else | ||||
|             data = await client.favouritePost(post.id); | ||||
|             data = await get(client).favouritePost(post.id); | ||||
|         if (!data) { | ||||
|             console.error(`Failed to favourite post ${post.id}`); | ||||
|             return; | ||||
|  | @ -48,13 +46,12 @@ | |||
| 
 | ||||
|     async function toggleReaction(reaction) { | ||||
|         if (reaction.name.includes('@')) return; | ||||
|         let client = get(Client.get()); | ||||
| 
 | ||||
|         let data; | ||||
|         if (reaction.me) | ||||
|             data = await client.unreactPost(post.id, reaction.name); | ||||
|             data = await get(client).unreactPost(post.id, reaction.name); | ||||
|         else | ||||
|             data = await client.reactPost(post.id, reaction.name); | ||||
|             data = await get(client).reactPost(post.id, reaction.name); | ||||
|         if (!data) { | ||||
|             console.error(`Failed to favourite post ${post.id}`); | ||||
|             return; | ||||
|  |  | |||
|  | @ -50,7 +50,9 @@ | |||
| 
 | ||||
| <div class="post-container"> | ||||
|     {#if post.reply} | ||||
|         <ReplyContext post={post.reply} /> | ||||
|         {#await post.reply then reply} | ||||
|             <ReplyContext post={reply} /> | ||||
|         {/await} | ||||
|     {/if} | ||||
|     {#if is_boost && !post_context.text} | ||||
|         <BoostContext post={post_context} /> | ||||
|  |  | |||
|  | @ -1,8 +1,6 @@ | |||
| <script> | ||||
|     import { parseText as parseEmojis, parseOne as parseEmoji } from '../../emoji.js'; | ||||
|     import { shorthand as short_time } from '../../time.js'; | ||||
|     import { get } from 'svelte/store'; | ||||
|     import { Client } from '../../client/client.js'; | ||||
|     import * as api from '../../client/api.js'; | ||||
|     import { goto } from '$app/navigation'; | ||||
| 
 | ||||
|  | @ -20,19 +18,20 @@ | |||
| 
 | ||||
|     function gotoPost() { | ||||
|         if (event && event.key && event.key !== "Enter") return; | ||||
|         console.log(`/post/${post.id}`); | ||||
|         goto(`/post/${post.id}`); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| {#if post.reply} | ||||
|     <svelte:self post={post.reply} /> | ||||
|     {#await post.reply then reply} | ||||
|         <svelte:self post={reply} /> | ||||
|     {/await} | ||||
| {/if} | ||||
| 
 | ||||
| <article | ||||
|         class="post-reply" | ||||
|         aria-label={aria_label} | ||||
|         on:mousedown={e => {mouse_pos.left = e.pageX; mouse_pos.top = e.pageY; console.log(mouse_pos)}} | ||||
|         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()}} | ||||
|         on:keydown={gotoPost}> | ||||
|     <div class="line"></div> | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { Client } from '../client/client.js'; | ||||
| import { client } from '../client/client.js'; | ||||
| import { parseText as parseEmojis } from '../emoji.js'; | ||||
| import { get } from 'svelte/store'; | ||||
| 
 | ||||
|  | @ -17,7 +17,7 @@ export default class User { | |||
| 
 | ||||
|     get mention() { | ||||
|         let res = "@" + this.username; | ||||
|         if (this.host != get(Client.get()).instance.host) | ||||
|         if (this.host != get(client).instance.host) | ||||
|             res += "@" + this.host; | ||||
|         return res; | ||||
|     } | ||||
|  |  | |||
|  | @ -2,10 +2,26 @@ | |||
|     import '$lib/app.css'; | ||||
|     import Navigation from '$lib/ui/Navigation.svelte'; | ||||
|     import Widgets from '$lib/ui/Widgets.svelte'; | ||||
|     import { Client } from '$lib/client/client.js'; | ||||
|     import { client, Client } from '$lib/client/client.js'; | ||||
|     import { get } from 'svelte/store'; | ||||
| 
 | ||||
|     let client = get(Client.get()); | ||||
|     let ready = new Promise(resolve => { | ||||
|         if (get(client)) { | ||||
|             return resolve(); | ||||
|         } | ||||
|         let new_client = new Client(); | ||||
|         new_client.load(); | ||||
| 
 | ||||
|         return new_client.getUser().then(user => { | ||||
|             if (!user) { | ||||
|                 client.set(new_client); | ||||
|                 return resolve(); | ||||
|             } | ||||
|             new_client.user = user; | ||||
|             client.set(new_client); | ||||
|             return resolve(); | ||||
|         }); | ||||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| <div id="app"> | ||||
|  | @ -15,7 +31,7 @@ | |||
|     </header> | ||||
| 
 | ||||
|     <main> | ||||
|         {#await client.verifyCredentials()} | ||||
|         {#await ready} | ||||
|             <div class="loading throb"> | ||||
|                 <span>just a moment...</span> | ||||
|             </div> | ||||
|  | @ -29,3 +45,15 @@ | |||
|     </div> | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| <style> | ||||
|     .loading { | ||||
|         width: 100%; | ||||
|         height: 100vh; | ||||
|         display: flex; | ||||
|         justify-content: center; | ||||
|         align-items: center; | ||||
|         font-size: 2em; | ||||
|         font-weight: bold; | ||||
|     } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,15 +1,2 @@ | |||
| import Feed from '$lib/ui/Feed.svelte'; | ||||
| import { Client } from '$lib/client/client.js'; | ||||
| import Button from '$lib/ui/Button.svelte'; | ||||
| import { get } from 'svelte/store'; | ||||
| 
 | ||||
| export const prerender = true; | ||||
| export const ssr = false; | ||||
| 
 | ||||
| export async function load() { | ||||
|     let client = get(Client.get()); | ||||
|     await client.verifyCredentials(); | ||||
|     return {  | ||||
|         client: client | ||||
|     }; | ||||
| } | ||||
|  |  | |||
|  | @ -1,92 +1,19 @@ | |||
| <script> | ||||
|     import Logo from '$lib/../img/campfire-logo.svg'; | ||||
|     import { client } from '$lib/client/client.js'; | ||||
| 
 | ||||
|     import LoginForm from '$lib/ui/LoginForm.svelte'; | ||||
|     import Feed from '$lib/ui/Feed.svelte'; | ||||
|     import { Client } from '$lib/client/client.js'; | ||||
|     import User from '$lib/user/user.js'; | ||||
|     import Button from '$lib/ui/Button.svelte'; | ||||
|     import { get } from 'svelte/store'; | ||||
| 
 | ||||
|     export let data; | ||||
| 
 | ||||
|     let client = data.client; | ||||
|     let logged_in = client.user && client.user.constructor === User; | ||||
|     let instance_url_error = false; | ||||
|     let logging_in = false; | ||||
| 
 | ||||
|     function log_in(event) { | ||||
|         event.preventDefault(); | ||||
|         instance_url_error = false; | ||||
| 
 | ||||
|         logging_in = true; | ||||
|         const host = event.target.host.value; | ||||
| 
 | ||||
|         if (!host || host === "") { | ||||
|             instance_url_error = "Please enter an instance domain."; | ||||
|             logging_in = false; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         client.init(host).then(res => { | ||||
|             logging_in = false; | ||||
|             if (!res) return; | ||||
|             if (res.constructor === String) { | ||||
|                 instance_url_error = res; | ||||
|                 return; | ||||
|             }; | ||||
|             let oauth_url = client.getOAuthUrl(); | ||||
|             location = oauth_url; | ||||
|         }); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| {#if logged_in} | ||||
|     <header> | ||||
|         <h1>Home</h1> | ||||
|         <nav> | ||||
|             <Button centered active>Home</Button> | ||||
|             <Button centered disabled>Local</Button> | ||||
|             <Button centered disabled>Federated</Button> | ||||
|         </nav> | ||||
|     </header> | ||||
| 
 | ||||
| {#if $client.user} | ||||
|     <Feed /> | ||||
| {:else} | ||||
|     <form on:submit={log_in} id="login-form"> | ||||
|         <div class="app-logo"> | ||||
|             <Logo /> | ||||
|         </div> | ||||
|         <p>Welcome, fediverse user!</p> | ||||
|         <p>Please enter your instance domain to log in.</p> | ||||
|         <div class="input-wrapper"> | ||||
|             <input type="text" id="host" aria-label="instance domain" class={logging_in ? "throb" : ""}> | ||||
|                          {#if instance_url_error} | ||||
|                              <p class="error">{instance_url_error}</p> | ||||
|                          {/if} | ||||
|         </div> | ||||
|         <br> | ||||
|         <button type="submit" id="login" class={logging_in ? "disabled" : ""}>Log in</button> | ||||
|         <p><small> | ||||
|                               Please note this is | ||||
|                               <strong><em>extremely experimental software</em></strong>; | ||||
|                               things are likely to break! | ||||
|                               <br> | ||||
|                               If that's all cool with you, welcome aboard! | ||||
|         </small></p> | ||||
| 
 | ||||
|         <p class="form-footer">made with ❤ by <a href="https://bliss.town">bliss town</a>, 2024</p> | ||||
|     </form> | ||||
|     <LoginForm /> | ||||
| {/if} | ||||
| 
 | ||||
| <style> | ||||
|     form#login-form { | ||||
|         height: 100vh; | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         justify-content: center; | ||||
|         align-items: center; | ||||
|         text-align: center; | ||||
|     } | ||||
| 
 | ||||
|     a { | ||||
|         color: var(--accent); | ||||
|         text-decoration: none; | ||||
|  | @ -96,106 +23,6 @@ | |||
|         text-decoration: underline; | ||||
|     } | ||||
| 
 | ||||
|     .input-wrapper { | ||||
|         width: 360px; | ||||
|         margin: 0 auto; | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         align-items: center; | ||||
|     } | ||||
| 
 | ||||
|     input[type=text] { | ||||
|         width: 100%; | ||||
|         padding: 12px; | ||||
|         display: block; | ||||
|         border-radius: 8px; | ||||
|         border: 1px solid var(--accent); | ||||
|         background-color: var(--bg-800); | ||||
| 
 | ||||
|         font-family: inherit; | ||||
|         font-weight: bold; | ||||
|         font-size: inherit; | ||||
|         color: var(--text); | ||||
| 
 | ||||
|         transition: box-shadow .2s; | ||||
|     } | ||||
| 
 | ||||
|     input[type=text]::placeholder { | ||||
|         opacity: .8; | ||||
|     } | ||||
| 
 | ||||
|     input[type=text]:focus { | ||||
|         outline: none; | ||||
|         box-shadow: 0 0 16px color-mix(in srgb, transparent, var(--accent) 25%); | ||||
|     } | ||||
| 
 | ||||
|     .error { | ||||
|         margin: 6px; | ||||
|         font-style: italic; | ||||
|         font-size: .9em; | ||||
|         color: red; | ||||
|         opacity: .7; | ||||
|     } | ||||
| 
 | ||||
|     button#login { | ||||
|         margin: 8px auto; | ||||
|         padding: 12px 24px; | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         justify-content: space-between; | ||||
|         align-items: center; | ||||
| 
 | ||||
|         font-family: inherit; | ||||
|         font-size: 1rem; | ||||
|         font-weight: 600; | ||||
|         text-align: left; | ||||
| 
 | ||||
|         border-radius: 8px; | ||||
|         border-width: 2px; | ||||
|         border-style: solid; | ||||
| 
 | ||||
|         background-color: var(--bg-700); | ||||
|         color: var(--text); | ||||
|         border-color: transparent; | ||||
| 
 | ||||
|         transition-property: border-color, background-color, color; | ||||
|         transition-timing-function: ease-out; | ||||
|         transition-duration: .1s; | ||||
| 
 | ||||
|         cursor: pointer; | ||||
|         text-align: center; | ||||
|         justify-content: center; | ||||
|     } | ||||
| 
 | ||||
|     button#login:hover { | ||||
|         background-color: color-mix(in srgb, var(--bg-700), var(--accent) 10%); | ||||
|         border-color: color-mix(in srgb, var(--bg-700), var(--accent) 20%); | ||||
|     } | ||||
| 
 | ||||
|     button#login:active { | ||||
|         background-color: color-mix(in srgb, var(--bg-700), var(--bg-800) 50%); | ||||
|         border-color: color-mix(in srgb, var(--bg-700), var(--bg-800) 10%); | ||||
|     } | ||||
| 
 | ||||
|     button#login.disabled { | ||||
|         opacity: .5; | ||||
|         cursor: initial; | ||||
|     } | ||||
| 
 | ||||
|     .form-footer { | ||||
|         opacity: .7; | ||||
|     } | ||||
| 
 | ||||
|     .loading { | ||||
|         width: 100%; | ||||
|         height: 100vh; | ||||
|         display: flex; | ||||
|         justify-content: center; | ||||
|         align-items: center; | ||||
|         font-size: 2em; | ||||
|         font-weight: bold; | ||||
|     } | ||||
| 
 | ||||
|     header { | ||||
|         width: 100%; | ||||
|         margin: 16px 0 8px 0; | ||||
|  |  | |||
|  | @ -1,20 +1,5 @@ | |||
| import { Client } from '$lib/client/client.js'; | ||||
| import { goto } from '$app/navigation'; | ||||
| import { error } from '@sveltejs/kit'; | ||||
| import { get } from 'svelte/store'; | ||||
| 
 | ||||
| export const ssr = false; | ||||
| 
 | ||||
| export async function load({ params, url }) { | ||||
|     const client = get(Client.get()); | ||||
|     let auth_code = url.searchParams.get("code"); | ||||
|     if (auth_code) { | ||||
|         client.getToken(auth_code).then(() => { | ||||
|             client.save(); | ||||
|             goto("/"); | ||||
|         }); | ||||
|     } | ||||
|     error(400, { | ||||
|         message: "Bad request" | ||||
|     }); | ||||
| export async function load({ url }) { | ||||
|     return { | ||||
|         code: url.searchParams.get("code") || false | ||||
|     }; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										35
									
								
								src/routes/callback/+page.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/routes/callback/+page.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| <script> | ||||
|     import { client } from '$lib/client/client.js'; | ||||
|     import { goto } from '$app/navigation'; | ||||
|     import { error } from '@sveltejs/kit'; | ||||
|     import { get } from 'svelte/store'; | ||||
| 
 | ||||
|     export let data; | ||||
| 
 | ||||
|     let auth_code = data.code; | ||||
| 
 | ||||
|     if (!auth_code) { | ||||
|         error(400, { message: "Bad request" }); | ||||
|     } else { | ||||
|         get(client).getToken(auth_code).then(token => { | ||||
|             if (!token) { | ||||
|                 error(400, { message: "Invalid auth code provided" }); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             client.update(c => { | ||||
|                 c.app.token = token; | ||||
|                 c.save(); | ||||
|                 return c; | ||||
|             }); | ||||
| 
 | ||||
|             get(client).getUser().then(user => { | ||||
|                 if (user) client.update(client => { | ||||
|                     client.user = user | ||||
|                     return client; | ||||
|                 }); | ||||
|                 goto("/"); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| </script> | ||||
|  | @ -1,39 +1,5 @@ | |||
| import Post from '$lib/ui/post/Post.svelte'; | ||||
| import { Client } from '$lib/client/client.js'; | ||||
| import { parsePost } from '$lib/client/api.js'; | ||||
| import { get } from 'svelte/store'; | ||||
| import { goto } from '$app/navigation'; | ||||
| 
 | ||||
| export const ssr = false; | ||||
| 
 | ||||
| export async function load({ params }) { | ||||
|     let client = get(Client.get()); | ||||
| 
 | ||||
|     if (!client.instance || !client.user) { | ||||
|         goto("/"); | ||||
|     } | ||||
| 
 | ||||
|     const post_id = params.id; | ||||
| 
 | ||||
|     const post_data = await client.getPost(post_id); | ||||
|     if (!post_data) { | ||||
|         console.error(`Failed to retrieve post ${post_id}.`); | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     const post = await parsePost(post_data, 10, true); | ||||
|     let posts = [post]; | ||||
|     for (let i in post.replies) { | ||||
|         const reply = post.replies[i]; | ||||
|         // if (i > 1 && reply.reply_id === post.replies[i - 1].id) {
 | ||||
|         //     let reply_head = posts.pop();
 | ||||
|         //     reply.reply = reply_head;
 | ||||
|         // }
 | ||||
|         posts.push(reply); | ||||
|         // console.log(reply);
 | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|         posts: posts | ||||
|         post_id: params.id | ||||
|     }; | ||||
| } | ||||
|  |  | |||
|  | @ -1,37 +1,80 @@ | |||
| <script> | ||||
|     import '$lib/app.css'; | ||||
|     import { client } from '$lib/client/client.js'; | ||||
|     import * as api from '$lib/client/api.js'; | ||||
|     import { get } from 'svelte/store'; | ||||
| 
 | ||||
|     import Post from '$lib/ui/post/Post.svelte'; | ||||
|     import Button from '$lib/ui/Button.svelte'; | ||||
| 
 | ||||
|     export let data; | ||||
|     $: main_post = data.posts[0]; | ||||
|     $: replies = data.posts.slice(1); | ||||
|     let error = false; | ||||
| 
 | ||||
|     if (!get(client).instance || !get(client).user) { | ||||
|         goto("/"); | ||||
|     } | ||||
| 
 | ||||
|     $: post = (async resolve => { | ||||
|         const post_data = await get(client).getPost(data.post_id, 0, false); | ||||
|         if (!post_data) { | ||||
|             error = `Failed to retrieve post <code>${data.post_id}</code>.`; | ||||
|             console.error(`Failed to retrieve post ${data.post_id}.`); | ||||
|             return; | ||||
|         } | ||||
|         let post = await api.parsePost(post_data, 0, false); | ||||
| 
 | ||||
|         const post_context = await get(client).getPostContext(data.post_id); | ||||
| 
 | ||||
|         if (!post_context || !post_context.ancestors || !post_context.descendants) | ||||
|             return post; | ||||
| 
 | ||||
|         // handle ancestors (above post) | ||||
|         let thread_top = post; | ||||
|         while (post_context.ancestors.length > 0) { | ||||
|             thread_top.reply = await api.parsePost(post_context.ancestors.pop(), 0, false); | ||||
|             thread_top = thread_top.reply; | ||||
|         } | ||||
| 
 | ||||
|         // handle descendants (below post) | ||||
|         post.replies = []; | ||||
|         for (let i in post_context.descendants) { | ||||
|             post.replies.push( | ||||
|                 api.parsePost(post_context.descendants[i], 0, false) | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         console.log(post); | ||||
|         return post; | ||||
|     })(); | ||||
| </script> | ||||
| 
 | ||||
| {#if !error} | ||||
| <header> | ||||
|     <h1>Home</h1> | ||||
|     {#await post then post} | ||||
|         <h1>Post by {@html post.user.rich_name}</h1> | ||||
|     {/await} | ||||
|     <nav> | ||||
|         <Button centered active>Home</Button> | ||||
|         <Button centered disabled>Local</Button> | ||||
|         <Button centered disabled>Federated</Button> | ||||
|         <Button centered>Back</Button> | ||||
|     </nav> | ||||
| </header> | ||||
| 
 | ||||
| <div id="feed" role="feed"> | ||||
|     {#if data.posts.length <= 0} | ||||
|     {#await post} | ||||
|         <div class="throb"> | ||||
|             <span>just a moment...</span> | ||||
|             <span>loading post...</span> | ||||
|         </div> | ||||
|     {:then post} | ||||
|         <Post post_data={post} focused /> | ||||
|         <br> | ||||
|         {#each post.replies as reply} | ||||
|             {#await reply then reply} | ||||
|                 <Post post_data={reply} /> | ||||
|             {/await} | ||||
|         {/each} | ||||
|     {/await} | ||||
| </div> | ||||
| {:else} | ||||
|         {#key data} | ||||
|         <Post post_data={main_post} focused /> | ||||
|         <br> | ||||
|         {#each replies as post} | ||||
|             <Post post_data={post} /> | ||||
|         {/each} | ||||
|         {/key} | ||||
|     <p>{@html error}</p> | ||||
| {/if} | ||||
| </div> | ||||
| 
 | ||||
| <style> | ||||
|     header { | ||||
|  | @ -42,7 +85,11 @@ | |||
|     } | ||||
| 
 | ||||
|     header h1 { | ||||
|         margin: auto auto auto 8px; | ||||
|         font-size: 1.5em; | ||||
|         text-overflow: ellipsis; | ||||
|         overflow: hidden; | ||||
|         white-space: nowrap; | ||||
|     } | ||||
| 
 | ||||
|     header nav { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue