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 { capabilities } from '../client/instance.js'; | ||||||
| import Post from '../post.js'; | import Post from '../post.js'; | ||||||
| import User from '../user/user.js'; | import User from '../user/user.js'; | ||||||
|  | @ -31,25 +31,23 @@ export async function createApp(host) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getOAuthUrl() { | export function getOAuthUrl() { | ||||||
|     let client = get(Client.get()); |     return `https://${get(client).instance.host}/oauth/authorize` + | ||||||
|     return `https://${client.instance.host}/oauth/authorize` + |         `?client_id=${get(client).app.id}` + | ||||||
|         `?client_id=${client.app.id}` + |  | ||||||
|         "&scope=read+write+push" + |         "&scope=read+write+push" + | ||||||
|         `&redirect_uri=${location.origin}/callback` + |         `&redirect_uri=${location.origin}/callback` + | ||||||
|         "&response_type=code"; |         "&response_type=code"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getToken(code) { | export async function getToken(code) { | ||||||
|     let client = get(Client.get()); |  | ||||||
|     let form = new FormData(); |     let form = new FormData(); | ||||||
|     form.append("client_id", client.app.id); |     form.append("client_id", get(client).app.id); | ||||||
|     form.append("client_secret", client.app.secret); |     form.append("client_secret", get(client).app.secret); | ||||||
|     form.append("redirect_uri", `${location.origin}/callback`); |     form.append("redirect_uri", `${location.origin}/callback`); | ||||||
|     form.append("grant_type", "authorization_code"); |     form.append("grant_type", "authorization_code"); | ||||||
|     form.append("code", code); |     form.append("code", code); | ||||||
|     form.append("scope", "read write push"); |     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", |         method: "POST", | ||||||
|         body: form, |         body: form, | ||||||
|     }) |     }) | ||||||
|  | @ -65,13 +63,12 @@ export async function getToken(code) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function revokeToken() { | export async function revokeToken() { | ||||||
|     let client = get(Client.get()); |  | ||||||
|     let form = new FormData(); |     let form = new FormData(); | ||||||
|     form.append("client_id", client.app.id); |     form.append("client_id", get(client).app.id); | ||||||
|     form.append("client_secret", client.app.secret); |     form.append("client_secret", get(client).app.secret); | ||||||
|     form.append("token", client.app.token); |     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", |         method: "POST", | ||||||
|         body: form, |         body: form, | ||||||
|     }) |     }) | ||||||
|  | @ -85,34 +82,32 @@ export async function revokeToken() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function verifyCredentials() { | export async function verifyCredentials() { | ||||||
|     let client = get(Client.get()); |     let url = `https://${get(client).instance.host}/api/v1/accounts/verify_credentials`; | ||||||
|     let url = `https://${client.instance.host}/api/v1/accounts/verify_credentials`; |  | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'GET', |         method: 'GET', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => res.json()); |     }).then(res => res.json()); | ||||||
| 
 | 
 | ||||||
|     return data; |     return data; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getTimeline(last_post_id) { | export async function getTimeline(last_post_id) { | ||||||
|     let client = get(Client.get()); |     if (!get(client).instance || !get(client).app) return false; | ||||||
|     let url = `https://${client.instance.host}/api/v1/timelines/home`; |     let url = `https://${get(client).instance.host}/api/v1/timelines/home`; | ||||||
|     if (last_post_id) url += "?max_id=" + last_post_id; |     if (last_post_id) url += "?max_id=" + last_post_id; | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'GET', |         method: 'GET', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => res.json()); |     }).then(res => res.json()); | ||||||
| 
 | 
 | ||||||
|     return data; |     return data; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getPost(post_id, ancestor_count) { | export async function getPost(post_id, ancestor_count) { | ||||||
|     let client = get(Client.get()); |     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}`; | ||||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}`; |  | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'GET', |         method: 'GET', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => { return res.ok ? res.json() : false }); |     }).then(res => { return res.ok ? res.json() : false }); | ||||||
| 
 | 
 | ||||||
|     if (data === false) return false; |     if (data === false) return false; | ||||||
|  | @ -120,11 +115,10 @@ export async function getPost(post_id, ancestor_count) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getPostContext(post_id) { | export async function getPostContext(post_id) { | ||||||
|     let client = get(Client.get()); |     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/context`; | ||||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/context`; |  | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'GET', |         method: 'GET', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => { return res.ok ? res.json() : false }); |     }).then(res => { return res.ok ? res.json() : false }); | ||||||
| 
 | 
 | ||||||
|     if (data === false) return false; |     if (data === false) return false; | ||||||
|  | @ -132,11 +126,10 @@ export async function getPostContext(post_id) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function boostPost(post_id) { | export async function boostPost(post_id) { | ||||||
|     let client = get(Client.get()); |     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/reblog`; | ||||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/reblog`; |  | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'POST', |         method: 'POST', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => { return res.ok ? res.json() : false }); |     }).then(res => { return res.ok ? res.json() : false }); | ||||||
| 
 | 
 | ||||||
|     if (data === false) return false; |     if (data === false) return false; | ||||||
|  | @ -144,11 +137,10 @@ export async function boostPost(post_id) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function unboostPost(post_id) { | export async function unboostPost(post_id) { | ||||||
|     let client = get(Client.get()); |     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unreblog`; | ||||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unreblog`; |  | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'POST', |         method: 'POST', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => { return res.ok ? res.json() : false }); |     }).then(res => { return res.ok ? res.json() : false }); | ||||||
| 
 | 
 | ||||||
|     if (data === false) return false; |     if (data === false) return false; | ||||||
|  | @ -156,11 +148,10 @@ export async function unboostPost(post_id) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function favouritePost(post_id) { | export async function favouritePost(post_id) { | ||||||
|     let client = get(Client.get()); |     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/favourite`; | ||||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/favourite`; |  | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'POST', |         method: 'POST', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => { return res.ok ? res.json() : false }); |     }).then(res => { return res.ok ? res.json() : false }); | ||||||
| 
 | 
 | ||||||
|     if (data === false) return false; |     if (data === false) return false; | ||||||
|  | @ -168,11 +159,10 @@ export async function favouritePost(post_id) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function unfavouritePost(post_id) { | export async function unfavouritePost(post_id) { | ||||||
|     let client = get(Client.get()); |     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unfavourite`; | ||||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unfavourite`; |  | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'POST', |         method: 'POST', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => { return res.ok ? res.json() : false }); |     }).then(res => { return res.ok ? res.json() : false }); | ||||||
| 
 | 
 | ||||||
|     if (data === false) return false; |     if (data === false) return false; | ||||||
|  | @ -185,11 +175,10 @@ export async function reactPost(post_id, shortcode) { | ||||||
|     // to the default like emote.
 |     // to the default like emote.
 | ||||||
|     // identical api calls on chuckya instances do not display
 |     // identical api calls on chuckya instances do not display
 | ||||||
|     // this behaviour.
 |     // this behaviour.
 | ||||||
|     let client = get(Client.get()); |     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/react/${encodeURIComponent(shortcode)}`; | ||||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/react/${encodeURIComponent(shortcode)}`; |  | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'POST', |         method: 'POST', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => { return res.ok ? res.json() : false }); |     }).then(res => { return res.ok ? res.json() : false }); | ||||||
| 
 | 
 | ||||||
|     if (data === false) return false; |     if (data === false) return false; | ||||||
|  | @ -197,26 +186,23 @@ export async function reactPost(post_id, shortcode) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function unreactPost(post_id, shortcode) { | export async function unreactPost(post_id, shortcode) { | ||||||
|     let client = get(Client.get()); |     let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unreact/${encodeURIComponent(shortcode)}`; | ||||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unreact/${encodeURIComponent(shortcode)}`; |  | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'POST', |         method: 'POST', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => { return res.ok ? res.json() : false }); |     }).then(res => { return res.ok ? res.json() : false }); | ||||||
| 
 | 
 | ||||||
|     if (data === false) return false; |     if (data === false) return false; | ||||||
|     return data; |     return data; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function parsePost(data, ancestor_count, with_context) { | export async function parsePost(data, ancestor_count) { | ||||||
|     let client = get(Client.get()); |  | ||||||
|     let post = new Post(); |     let post = new Post(); | ||||||
| 
 | 
 | ||||||
|     post.text = data.content; |     post.text = data.content; | ||||||
| 
 | 
 | ||||||
|     post.reply = null; |     post.reply = null; | ||||||
|     if (!with_context && // ancestor replies are handled in full later
 |     if ((data.in_reply_to_id || data.reply) && | ||||||
|         (data.in_reply_to_id || data.reply) && |  | ||||||
|         ancestor_count !== 0 |         ancestor_count !== 0 | ||||||
|     ) { |     ) { | ||||||
|         const reply_data = data.reply || await getPost(data.in_reply_to_id, ancestor_count - 1); |         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; |         if (!reply_data) return false; | ||||||
|         post.reply = await parsePost(reply_data, ancestor_count - 1, false); |         post.reply = await parsePost(reply_data, ancestor_count - 1, false); | ||||||
|     } |     } | ||||||
|     post.boost = data.reblog ? await parsePost(data.reblog, 1, false) : null; |  | ||||||
| 
 | 
 | ||||||
|     post.replies = []; |     post.boost = data.reblog ? await parsePost(data.reblog, 1, false) : null; | ||||||
|     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.id = data.id; |     post.id = data.id; | ||||||
|     post.created_at = new Date(data.created_at); |     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); |         post.reactions = parseReactions(data.reactions); | ||||||
|     } |     } | ||||||
|     return post; |     return post; | ||||||
|  | @ -286,8 +252,7 @@ export async function parseUser(data) { | ||||||
|         console.error("Attempted to parse user data but no data was provided"); |         console.error("Attempted to parse user data but no data was provided"); | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|     let client = get(Client.get()); |     let user = await get(client).getCacheUser(data.id); | ||||||
|     let user = await client.getCacheUser(data.id); |  | ||||||
| 
 | 
 | ||||||
|     if (user) return user; |     if (user) return user; | ||||||
|     // cache miss!
 |     // cache miss!
 | ||||||
|  | @ -302,7 +267,7 @@ export async function parseUser(data) { | ||||||
|     if (data.acct.includes('@')) |     if (data.acct.includes('@')) | ||||||
|         user.host = data.acct.split('@')[1]; |         user.host = data.acct.split('@')[1]; | ||||||
|     else |     else | ||||||
|         user.host = client.instance.host; |         user.host = get(client).instance.host; | ||||||
| 
 | 
 | ||||||
|     user.emojis = []; |     user.emojis = []; | ||||||
|     data.emojis.forEach(emoji_data => { |     data.emojis.forEach(emoji_data => { | ||||||
|  | @ -312,12 +277,11 @@ export async function parseUser(data) { | ||||||
|         user.emojis.push(parseEmoji(emoji_data)); |         user.emojis.push(parseEmoji(emoji_data)); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     client.putCacheUser(user); |     get(client).putCacheUser(user); | ||||||
|     return user; |     return user; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function parseReactions(data) { | export function parseReactions(data) { | ||||||
|     let client = get(Client.get()); |  | ||||||
|     let reactions = []; |     let reactions = []; | ||||||
|     data.forEach(reaction_data => { |     data.forEach(reaction_data => { | ||||||
|         let reaction = { |         let reaction = { | ||||||
|  | @ -338,16 +302,15 @@ export function parseEmoji(data) { | ||||||
|         data.host, |         data.host, | ||||||
|         data.url, |         data.url, | ||||||
|     ); |     ); | ||||||
|     get(Client.get()).putCacheEmoji(emoji); |     get(client).putCacheEmoji(emoji); | ||||||
|     return emoji; |     return emoji; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getUser(user_id) { | export async function getUser(user_id) { | ||||||
|     let client = get(Client.get()); |     let url = `https://${get(client).instance.host}/api/v1/accounts/${user_id}`; | ||||||
|     let url = `https://${client.instance.host}/api/v1/accounts/${user_id}`; |  | ||||||
|     const data = await fetch(url, { |     const data = await fetch(url, { | ||||||
|         method: 'GET', |         method: 'GET', | ||||||
|         headers: { "Authorization": "Bearer " + client.app.token } |         headers: { "Authorization": "Bearer " + get(client).app.token } | ||||||
|     }).then(res => res.json()); |     }).then(res => res.json()); | ||||||
| 
 | 
 | ||||||
|     const user = await parseUser(data); |     const user = await parseUser(data); | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { Instance, server_types } from './instance.js'; | ||||||
| import * as api from './api.js'; | import * as api from './api.js'; | ||||||
| import { get, writable } from 'svelte/store'; | import { get, writable } from 'svelte/store'; | ||||||
| 
 | 
 | ||||||
| let client = writable(false); | export const client = writable(false); | ||||||
| 
 | 
 | ||||||
| const save_name = "campfire"; | 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) { |     async init(host) { | ||||||
|         if (host.startsWith("https://")) host = host.substring(8); |         if (host.startsWith("https://")) host = host.substring(8); | ||||||
|         const url = `https://${host}/api/v1/instance`; |         const url = `https://${host}/api/v1/instance`; | ||||||
|  | @ -76,30 +67,30 @@ export class Client { | ||||||
|             console.error("Failed to obtain access token"); |             console.error("Failed to obtain access token"); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         this.app.token = token; |         return token; | ||||||
|         client.set(this); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async revokeToken() { |     async revokeToken() { | ||||||
|         return await api.revokeToken(); |         return await api.revokeToken(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async verifyCredentials() { |     async getUser() { | ||||||
|  |         // already known
 | ||||||
|         if (this.user) return this.user; |         if (this.user) return this.user; | ||||||
|  | 
 | ||||||
|  |         // cannot provide- not logged in
 | ||||||
|         if (!this.app || !this.app.token) { |         if (!this.app || !this.app.token) { | ||||||
|             this.user = false; |  | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         // logged in- attempt to retrieve using token
 | ||||||
|         const data = await api.verifyCredentials(); |         const data = await api.verifyCredentials(); | ||||||
|         if (!data) { |         if (!data) { | ||||||
|             this.user = false; |  | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         await client.update(async c => { |         const user = await api.parseUser(data); | ||||||
|             c.user = await api.parseUser(data); |         console.log(`Logged in as @${user.username}@${user.host}`); | ||||||
|             console.log(`Logged in as @${c.user.username}@${c.user.host}`); |         return user; | ||||||
|         }); |  | ||||||
|         return this.user; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async getTimeline(last_post_id) { |     async getTimeline(last_post_id) { | ||||||
|  | @ -110,6 +101,10 @@ export class Client { | ||||||
|         return await api.getPost(post_id, parent_replies, child_replies); |         return await api.getPost(post_id, parent_replies, child_replies); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     async getPostContext(post_id) { | ||||||
|  |         return await api.getPostContext(post_id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     async boostPost(post_id) { |     async boostPost(post_id) { | ||||||
|         return await api.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."); |             console.warn("Failed to log out correctly; ditching the old tokens anyways."); | ||||||
|         } |         } | ||||||
|         localStorage.removeItem(save_name); |         localStorage.removeItem(save_name); | ||||||
|         client.set(false); |         client.set(new Client()); | ||||||
|         console.log("Logged out successfully."); |         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'; | import { get } from 'svelte/store'; | ||||||
| 
 | 
 | ||||||
| export const EMOJI_REGEX = /:[\w\-.]{0,32}@[\w\-.]{0,32}:/g; | 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(':'); |     let length = text.substring(index + 1).search(':'); | ||||||
|     if (length <= 0) return text; |     if (length <= 0) return text; | ||||||
|     let emoji_name = text.substring(index + 1, index + length + 1); |     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) { |     if (emoji) { | ||||||
|         return text.substring(0, index) + emoji.html + |         return text.substring(0, index) + emoji.html + | ||||||
|  | @ -46,7 +46,7 @@ export function parseText(text, host) { | ||||||
| export function parseOne(emoji_id) { | export function parseOne(emoji_id) { | ||||||
|     if (emoji_id == '❤') return '❤️'; // stupid heart unicode
 |     if (emoji_id == '❤') return '❤️'; // stupid heart unicode
 | ||||||
|     if (EMOJI_REGEX.exec(':' + emoji_id + ':')) return emoji_id; |     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; |     if (!cached_emoji) return emoji_id; | ||||||
|     return cached_emoji.html; |     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 { get, writable } from 'svelte/store'; | ||||||
| import { parsePost } from '$lib/client/api.js'; | import { parsePost } from '$lib/client/api.js'; | ||||||
| 
 | 
 | ||||||
|  | @ -10,11 +10,9 @@ export async function getTimeline(clean) { | ||||||
|     if (loading) return; // no spamming!!
 |     if (loading) return; // no spamming!!
 | ||||||
|     loading = true; |     loading = true; | ||||||
| 
 | 
 | ||||||
|     let client = get(Client.get()); |  | ||||||
| 
 |  | ||||||
|     let timeline_data; |     let timeline_data; | ||||||
|     if (clean || get(posts).length === 0) timeline_data = await client.getTimeline() |     if (clean || get(posts).length === 0) timeline_data = await get(client).getTimeline() | ||||||
|     else timeline_data = await client.getTimeline(get(posts)[get(posts).length - 1].id); |     else timeline_data = await get(client).getTimeline(get(posts)[get(posts).length - 1].id); | ||||||
| 
 | 
 | ||||||
|     if (!timeline_data) { |     if (!timeline_data) { | ||||||
|         console.error(`Failed to retrieve timeline.`); |         console.error(`Failed to retrieve timeline.`); | ||||||
|  |  | ||||||
|  | @ -1,10 +1,6 @@ | ||||||
| <script> | <script> | ||||||
|     import Button from './Button.svelte'; |     import Button from './Button.svelte'; | ||||||
|     import Post from './post/Post.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'; |     import { posts, getTimeline } from '$lib/timeline.js'; | ||||||
| 
 | 
 | ||||||
|     getTimeline(); |     getTimeline(); | ||||||
|  | @ -15,6 +11,15 @@ | ||||||
|     }); |     }); | ||||||
| </script> | </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"> | <div id="feed" role="feed"> | ||||||
|     {#if posts.length <= 0} |     {#if posts.length <= 0} | ||||||
|         <div class="loading throb"> |         <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 Logo from '$lib/../img/campfire-logo.svg'; | ||||||
|     import Button from './Button.svelte'; |     import Button from './Button.svelte'; | ||||||
|     import Feed from './Feed.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 { play_sound } from '$lib/sound.js'; | ||||||
|     import { getTimeline } from '$lib/timeline.js'; |     import { getTimeline } from '$lib/timeline.js'; | ||||||
|     import { goto } from '$app/navigation'; |     import { goto } from '$app/navigation'; | ||||||
|  | @ -22,11 +22,6 @@ | ||||||
| 
 | 
 | ||||||
|     const VERSION = APP_VERSION; |     const VERSION = APP_VERSION; | ||||||
|      |      | ||||||
|     let client = false; |  | ||||||
|     Client.get().subscribe(c => { |  | ||||||
|         client = c; |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     let notification_count = 0; |     let notification_count = 0; | ||||||
|     if (notification_count > 99) notification_count = "99+"; |     if (notification_count > 99) notification_count = "99+"; | ||||||
| 
 | 
 | ||||||
|  | @ -44,26 +39,20 @@ | ||||||
| 
 | 
 | ||||||
|     async function log_out() { |     async function log_out() { | ||||||
|         if (!confirm("This will log you out. Are you sure?")) return; |         if (!confirm("This will log you out. Are you sure?")) return; | ||||||
|         await get(Client.get()).logout(); |         await get(client).logout(); | ||||||
|         goto("/"); |         goto("/"); | ||||||
|     } |     } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div id="navigation"> | <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"> |     <header class="instance-header"> | ||||||
|         <div class="app-logo"> |         <div class="app-logo"> | ||||||
|             <Logo /> |             <Logo /> | ||||||
|         </div> |         </div> | ||||||
|     </header> |     </header> | ||||||
|     {/if} |  | ||||||
| 
 | 
 | ||||||
|     <div id="nav-items"> |     <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"> |             <svelte:fragment slot="icon"> | ||||||
|                 <TimelineIcon/> |                 <TimelineIcon/> | ||||||
|             </svelte:fragment> |             </svelte:fragment> | ||||||
|  | @ -117,7 +106,7 @@ | ||||||
|             </Button> |             </Button> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     {#if (client.user)} |     {#if $client.user} | ||||||
|     <div id="account-items"> |     <div id="account-items"> | ||||||
|         <div class="flex-row"> |         <div class="flex-row"> | ||||||
|             <Button centered label="Profile information" disabled> |             <Button centered label="Profile information" disabled> | ||||||
|  | @ -138,11 +127,11 @@ | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <div id="account-button"> |         <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"> |             <div class="account-name" aria-hidden="true"> | ||||||
|                 <span class="nickname" title={client.user.nickname}>{client.user.nickname}</span> |                 <span class="nickname" title={$client.user.nickname}>{$client.user.nickname}</span> | ||||||
|                 <span class="username" title={`@${client.user.username}@${client.user.host}`}> |                 <span class="username" title={`@${$client.user.username}@${$client.user.host}`}> | ||||||
|                     {`@${client.user.username}@${client.user.host}`} |                     {`@${$client.user.username}@${$client.user.host}`} | ||||||
|                 </span> |                 </span> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <script> | <script> | ||||||
|     import { Client } from '../../client/client.js'; |     import { client } from '../../client/client.js'; | ||||||
|     import * as api from '../../client/api.js'; |     import * as api from '../../client/api.js'; | ||||||
|     import { get } from 'svelte/store'; |     import { get } from 'svelte/store'; | ||||||
| 
 | 
 | ||||||
|  | @ -16,12 +16,11 @@ | ||||||
|     export let post; |     export let post; | ||||||
| 
 | 
 | ||||||
|     async function toggleBoost() { |     async function toggleBoost() { | ||||||
|         let client = get(Client.get()); |  | ||||||
|         let data; |         let data; | ||||||
|         if (post.boosted) |         if (post.boosted) | ||||||
|             data = await client.unboostPost(post.id); |             data = await get(client).unboostPost(post.id); | ||||||
|         else |         else | ||||||
|             data = await client.boostPost(post.id); |             data = await get(client).boostPost(post.id); | ||||||
|         if (!data) { |         if (!data) { | ||||||
|             console.error(`Failed to boost post ${post.id}`); |             console.error(`Failed to boost post ${post.id}`); | ||||||
|             return; |             return; | ||||||
|  | @ -31,12 +30,11 @@ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async function toggleFavourite() { |     async function toggleFavourite() { | ||||||
|         let client = get(Client.get()); |  | ||||||
|         let data; |         let data; | ||||||
|         if (post.favourited) |         if (post.favourited) | ||||||
|             data = await client.unfavouritePost(post.id); |             data = await get(client).unfavouritePost(post.id); | ||||||
|         else |         else | ||||||
|             data = await client.favouritePost(post.id); |             data = await get(client).favouritePost(post.id); | ||||||
|         if (!data) { |         if (!data) { | ||||||
|             console.error(`Failed to favourite post ${post.id}`); |             console.error(`Failed to favourite post ${post.id}`); | ||||||
|             return; |             return; | ||||||
|  | @ -48,13 +46,12 @@ | ||||||
| 
 | 
 | ||||||
|     async function toggleReaction(reaction) { |     async function toggleReaction(reaction) { | ||||||
|         if (reaction.name.includes('@')) return; |         if (reaction.name.includes('@')) return; | ||||||
|         let client = get(Client.get()); |  | ||||||
| 
 | 
 | ||||||
|         let data; |         let data; | ||||||
|         if (reaction.me) |         if (reaction.me) | ||||||
|             data = await client.unreactPost(post.id, reaction.name); |             data = await get(client).unreactPost(post.id, reaction.name); | ||||||
|         else |         else | ||||||
|             data = await client.reactPost(post.id, reaction.name); |             data = await get(client).reactPost(post.id, reaction.name); | ||||||
|         if (!data) { |         if (!data) { | ||||||
|             console.error(`Failed to favourite post ${post.id}`); |             console.error(`Failed to favourite post ${post.id}`); | ||||||
|             return; |             return; | ||||||
|  |  | ||||||
|  | @ -50,7 +50,9 @@ | ||||||
| 
 | 
 | ||||||
| <div class="post-container"> | <div class="post-container"> | ||||||
|     {#if post.reply} |     {#if post.reply} | ||||||
|         <ReplyContext post={post.reply} /> |         {#await post.reply then reply} | ||||||
|  |             <ReplyContext post={reply} /> | ||||||
|  |         {/await} | ||||||
|     {/if} |     {/if} | ||||||
|     {#if is_boost && !post_context.text} |     {#if is_boost && !post_context.text} | ||||||
|         <BoostContext post={post_context} /> |         <BoostContext post={post_context} /> | ||||||
|  |  | ||||||
|  | @ -1,8 +1,6 @@ | ||||||
| <script> | <script> | ||||||
|     import { parseText as parseEmojis, parseOne as parseEmoji } from '../../emoji.js'; |     import { parseText as parseEmojis, parseOne as parseEmoji } from '../../emoji.js'; | ||||||
|     import { shorthand as short_time } from '../../time.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 * as api from '../../client/api.js'; | ||||||
|     import { goto } from '$app/navigation'; |     import { goto } from '$app/navigation'; | ||||||
| 
 | 
 | ||||||
|  | @ -20,19 +18,20 @@ | ||||||
| 
 | 
 | ||||||
|     function gotoPost() { |     function gotoPost() { | ||||||
|         if (event && event.key && event.key !== "Enter") return; |         if (event && event.key && event.key !== "Enter") return; | ||||||
|         console.log(`/post/${post.id}`); |  | ||||||
|         goto(`/post/${post.id}`); |         goto(`/post/${post.id}`); | ||||||
|     } |     } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if post.reply} | {#if post.reply} | ||||||
|     <svelte:self post={post.reply} /> |     {#await post.reply then reply} | ||||||
|  |         <svelte:self post={reply} /> | ||||||
|  |     {/await} | ||||||
| {/if} | {/if} | ||||||
| 
 | 
 | ||||||
| <article | <article | ||||||
|         class="post-reply" |         class="post-reply" | ||||||
|         aria-label={aria_label} |         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:mouseup={e => {if (e.pageX == mouse_pos.left && e.pageY == mouse_pos.top) gotoPost()}} | ||||||
|         on:keydown={gotoPost}> |         on:keydown={gotoPost}> | ||||||
|     <div class="line"></div> |     <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 { parseText as parseEmojis } from '../emoji.js'; | ||||||
| import { get } from 'svelte/store'; | import { get } from 'svelte/store'; | ||||||
| 
 | 
 | ||||||
|  | @ -17,7 +17,7 @@ export default class User { | ||||||
| 
 | 
 | ||||||
|     get mention() { |     get mention() { | ||||||
|         let res = "@" + this.username; |         let res = "@" + this.username; | ||||||
|         if (this.host != get(Client.get()).instance.host) |         if (this.host != get(client).instance.host) | ||||||
|             res += "@" + this.host; |             res += "@" + this.host; | ||||||
|         return res; |         return res; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -2,10 +2,26 @@ | ||||||
|     import '$lib/app.css'; |     import '$lib/app.css'; | ||||||
|     import Navigation from '$lib/ui/Navigation.svelte'; |     import Navigation from '$lib/ui/Navigation.svelte'; | ||||||
|     import Widgets from '$lib/ui/Widgets.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'; |     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> | </script> | ||||||
| 
 | 
 | ||||||
| <div id="app"> | <div id="app"> | ||||||
|  | @ -15,7 +31,7 @@ | ||||||
|     </header> |     </header> | ||||||
| 
 | 
 | ||||||
|     <main> |     <main> | ||||||
|         {#await client.verifyCredentials()} |         {#await ready} | ||||||
|             <div class="loading throb"> |             <div class="loading throb"> | ||||||
|                 <span>just a moment...</span> |                 <span>just a moment...</span> | ||||||
|             </div> |             </div> | ||||||
|  | @ -29,3 +45,15 @@ | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
| </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 prerender = true; | ||||||
| export const ssr = false; | export const ssr = false; | ||||||
| 
 |  | ||||||
| export async function load() { |  | ||||||
|     let client = get(Client.get()); |  | ||||||
|     await client.verifyCredentials(); |  | ||||||
|     return {  |  | ||||||
|         client: client |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1,92 +1,19 @@ | ||||||
| <script> | <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 Feed from '$lib/ui/Feed.svelte'; | ||||||
|     import { Client } from '$lib/client/client.js'; |  | ||||||
|     import User from '$lib/user/user.js'; |     import User from '$lib/user/user.js'; | ||||||
|     import Button from '$lib/ui/Button.svelte'; |     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> | </script> | ||||||
| 
 | 
 | ||||||
| {#if logged_in} | {#if $client.user} | ||||||
|     <header> |  | ||||||
|         <h1>Home</h1> |  | ||||||
|         <nav> |  | ||||||
|             <Button centered active>Home</Button> |  | ||||||
|             <Button centered disabled>Local</Button> |  | ||||||
|             <Button centered disabled>Federated</Button> |  | ||||||
|         </nav> |  | ||||||
|     </header> |  | ||||||
| 
 |  | ||||||
|     <Feed /> |     <Feed /> | ||||||
| {:else} | {:else} | ||||||
|     <form on:submit={log_in} id="login-form"> |     <LoginForm /> | ||||||
|         <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> |  | ||||||
| {/if} | {/if} | ||||||
| 
 | 
 | ||||||
| <style> | <style> | ||||||
|     form#login-form { |  | ||||||
|         height: 100vh; |  | ||||||
|         display: flex; |  | ||||||
|         flex-direction: column; |  | ||||||
|         justify-content: center; |  | ||||||
|         align-items: center; |  | ||||||
|         text-align: center; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     a { |     a { | ||||||
|         color: var(--accent); |         color: var(--accent); | ||||||
|         text-decoration: none; |         text-decoration: none; | ||||||
|  | @ -96,106 +23,6 @@ | ||||||
|         text-decoration: underline; |         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 { |     header { | ||||||
|         width: 100%; |         width: 100%; | ||||||
|         margin: 16px 0 8px 0; |         margin: 16px 0 8px 0; | ||||||
|  |  | ||||||
|  | @ -1,20 +1,5 @@ | ||||||
| import { Client } from '$lib/client/client.js'; | export async function load({ url }) { | ||||||
| import { goto } from '$app/navigation'; |     return { | ||||||
| import { error } from '@sveltejs/kit'; |         code: url.searchParams.get("code") || false | ||||||
| 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" |  | ||||||
|     }); |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										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 }) { | 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 { |     return { | ||||||
|         posts: posts |         post_id: params.id | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,37 +1,80 @@ | ||||||
| <script> | <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 Post from '$lib/ui/post/Post.svelte'; | ||||||
|     import Button from '$lib/ui/Button.svelte'; |     import Button from '$lib/ui/Button.svelte'; | ||||||
| 
 | 
 | ||||||
|     export let data; |     export let data; | ||||||
|     $: main_post = data.posts[0]; |     let error = false; | ||||||
|     $: replies = data.posts.slice(1); | 
 | ||||||
|  |     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> | </script> | ||||||
| 
 | 
 | ||||||
|  | {#if !error} | ||||||
| <header> | <header> | ||||||
|     <h1>Home</h1> |     {#await post then post} | ||||||
|  |         <h1>Post by {@html post.user.rich_name}</h1> | ||||||
|  |     {/await} | ||||||
|     <nav> |     <nav> | ||||||
|         <Button centered active>Home</Button> |         <Button centered>Back</Button> | ||||||
|         <Button centered disabled>Local</Button> |  | ||||||
|         <Button centered disabled>Federated</Button> |  | ||||||
|     </nav> |     </nav> | ||||||
| </header> | </header> | ||||||
| 
 | 
 | ||||||
| <div id="feed" role="feed"> | <div id="feed" role="feed"> | ||||||
|     {#if data.posts.length <= 0} |     {#await post} | ||||||
|         <div class="throb"> |         <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> | </div> | ||||||
| {:else} | {:else} | ||||||
|         {#key data} |     <p>{@html error}</p> | ||||||
|         <Post post_data={main_post} focused /> |  | ||||||
|         <br> |  | ||||||
|         {#each replies as post} |  | ||||||
|             <Post post_data={post} /> |  | ||||||
|         {/each} |  | ||||||
|         {/key} |  | ||||||
| {/if} | {/if} | ||||||
| </div> |  | ||||||
| 
 | 
 | ||||||
| <style> | <style> | ||||||
|     header { |     header { | ||||||
|  | @ -42,7 +85,11 @@ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     header h1 { |     header h1 { | ||||||
|  |         margin: auto auto auto 8px; | ||||||
|         font-size: 1.5em; |         font-size: 1.5em; | ||||||
|  |         text-overflow: ellipsis; | ||||||
|  |         overflow: hidden; | ||||||
|  |         white-space: nowrap; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     header nav { |     header nav { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue