From 176a8901b0207745f21df3e130cb84eacfcc830c Mon Sep 17 00:00:00 2001 From: ari melody Date: Sun, 30 Jun 2024 22:40:10 +0100 Subject: [PATCH 01/21] removed unnecessary debug logs --- src/lib/ui/post/Post.svelte | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/ui/post/Post.svelte b/src/lib/ui/post/Post.svelte index a1f914b..e9949b4 100644 --- a/src/lib/ui/post/Post.svelte +++ b/src/lib/ui/post/Post.svelte @@ -35,7 +35,6 @@ function gotoPost() { if (focused) return; if (event && event.key && event.key !== "Enter") return; - console.log(`/post/${post.id}`); goto(`/post/${post.id}`); } @@ -60,7 +59,7 @@ class={"post" + (focused ? " focused" : "")} aria-label={aria_label} bind:this={el} - 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}> From dc9b456409e6b9b444285d03db351e7516c3714d Mon Sep 17 00:00:00 2001 From: ari melody Date: Sun, 30 Jun 2024 23:13:55 +0100 Subject: [PATCH 02/21] update opengraph to use square icon --- src/app.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app.html b/src/app.html index ce47f4e..9f07942 100644 --- a/src/app.html +++ b/src/app.html @@ -12,14 +12,14 @@ - + - + %sveltekit.head% From 6953b49563ce6a71eb6fb790e578ce157417c304 Mon Sep 17 00:00:00 2001 From: ari melody Date: Mon, 1 Jul 2024 00:15:21 +0100 Subject: [PATCH 03/21] disable reaction bar for posts that don't support it --- src/lib/ui/post/Post.svelte | 4 +++- src/lib/ui/post/ReplyContext.svelte | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/ui/post/Post.svelte b/src/lib/ui/post/Post.svelte index e9949b4..db0044f 100644 --- a/src/lib/ui/post/Post.svelte +++ b/src/lib/ui/post/Post.svelte @@ -65,7 +65,9 @@
- + {#if post.reactions} + + {/if}
diff --git a/src/lib/ui/post/ReplyContext.svelte b/src/lib/ui/post/ReplyContext.svelte index ad4f620..833020c 100644 --- a/src/lib/ui/post/ReplyContext.svelte +++ b/src/lib/ui/post/ReplyContext.svelte @@ -43,7 +43,9 @@
- + {#if post.reactions} + + {/if}
From 40be54052774414ec6a047ad3ad017b8e3e16262 Mon Sep 17 00:00:00 2001 From: ari melody Date: Mon, 1 Jul 2024 03:41:02 +0100 Subject: [PATCH 04/21] i think i finally fixed the state management awfulness --- src/lib/client/api.js | 119 +++++++----------- src/lib/client/client.js | 37 +++--- src/lib/emoji.js | 6 +- src/lib/timeline.js | 8 +- src/lib/ui/Feed.svelte | 13 +- src/lib/ui/LoginForm.svelte | 162 ++++++++++++++++++++++++ src/lib/ui/Navigation.svelte | 37 ++---- src/lib/ui/post/ActionBar.svelte | 17 ++- src/lib/ui/post/Post.svelte | 4 +- src/lib/ui/post/ReplyContext.svelte | 9 +- src/lib/user/user.js | 4 +- src/routes/+layout.svelte | 34 +++++- src/routes/+page.js | 13 -- src/routes/+page.svelte | 183 +--------------------------- src/routes/callback/+page.js | 23 +--- src/routes/callback/+page.svelte | 35 ++++++ src/routes/post/[id]/+page.js | 36 +----- src/routes/post/[id]/+page.svelte | 79 +++++++++--- 18 files changed, 402 insertions(+), 417 deletions(-) create mode 100644 src/lib/ui/LoginForm.svelte create mode 100644 src/routes/callback/+page.svelte diff --git a/src/lib/client/api.js b/src/lib/client/api.js index 27f0e0b..c02517a 100644 --- a/src/lib/client/api.js +++ b/src/lib/client/api.js @@ -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); diff --git a/src/lib/client/client.js b/src/lib/client/client.js index f4e3788..6e96f19 100644 --- a/src/lib/client/client.js +++ b/src/lib/client/client.js @@ -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."); } } diff --git a/src/lib/emoji.js b/src/lib/emoji.js index 4fdd161..89df2d1 100644 --- a/src/lib/emoji.js +++ b/src/lib/emoji.js @@ -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; } diff --git a/src/lib/timeline.js b/src/lib/timeline.js index 5858199..a9fcdbf 100644 --- a/src/lib/timeline.js +++ b/src/lib/timeline.js @@ -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.`); diff --git a/src/lib/ui/Feed.svelte b/src/lib/ui/Feed.svelte index bd3cef4..d249165 100644 --- a/src/lib/ui/Feed.svelte +++ b/src/lib/ui/Feed.svelte @@ -1,10 +1,6 @@ +
+

Home

+ +
+
{#if posts.length <= 0}
diff --git a/src/lib/ui/LoginForm.svelte b/src/lib/ui/LoginForm.svelte new file mode 100644 index 0000000..8cf9b07 --- /dev/null +++ b/src/lib/ui/LoginForm.svelte @@ -0,0 +1,162 @@ + + +
+ +

Welcome, fediverse user!

+

Please enter your instance domain to log in.

+
+ + {#if instance_url_error} +

{instance_url_error}

+ {/if} +
+
+ +

+ Please note this is + extremely experimental software; + things are likely to break! +
+ If that's all cool with you, welcome aboard! +

+ + +
+ + diff --git a/src/lib/ui/Navigation.svelte b/src/lib/ui/Navigation.svelte index a513649..5ebbc5e 100644 --- a/src/lib/ui/Navigation.svelte +++ b/src/lib/ui/Navigation.svelte @@ -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("/"); }