diff --git a/.gitignore b/.gitignore index 10e9324..4cb396f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ **/.DS_Store node_modules/ -dist/ +build/ .secret/ .svelte-kit/ diff --git a/README.md b/README.md index adab278..9ea7d71 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,14 @@ -# space social +# Campfire social media for the galaxy-wide-web! 🌌 -this is a neat experiment in building as much of a fediverse-compatible -software stack as i can (at least before the crippling weight of the full -activitypub spec finally cripples me) - -starting, of course, with a nice frontend! ✨ +this is a *very experimental* frontend for browsing the fediverse, built +from the ground up in svelte! should you choose to play around with this yourself, just know that *many -things are bound not to work!* notably, this has only been tested on iceshrimp -and mastodon API-compliant instances. anything beyond this will likely be -incompatible, and the web console will get very upset. +things are bound not to work!* notably, campfire is currently only being +battle-tested on mastodon API-compliant instances. anything beyond this +will likely be incompatible, and the web console will get very upset. ## features @@ -34,10 +31,17 @@ incompatible, and the web console will get very upset. - fast account switching - post editing/deletion - push notifications +- ...and potentially much more as development continues! ## try it out! - `git clone` this repo - `npm install` the dependencies - `npm run dev` to spin up the dev environment -- have fun! ✨ + +if you wish to run this in production, you need only `npm run build` and +place the static files somewhere accessible by a static webhost, such as +nginx or apache! **note:** your web server should attempt to reach +`/fallback.html` before erroring out. + +have fun! ✨ diff --git a/package.json b/package.json index cb454e0..873a26b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "spacesocial-client", - "version": "0.2.0_rev2", + "version": "0.2.0_rev3", "description": "social media for the galaxy-wide-web! 🌌", "private": true, "type": "module", diff --git a/res/campfire-favicon.afdesign b/res/campfire-favicon.afdesign new file mode 100644 index 0000000..8ff94a6 --- /dev/null +++ b/res/campfire-favicon.afdesign @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:846759440d8bdc603d08bec02c4a5e5e65e15aedd0de394b8d75c83be83857eb +size 58281 diff --git a/res/campfire-logo.afdesign b/res/campfire-logo.afdesign new file mode 100644 index 0000000..017152f --- /dev/null +++ b/res/campfire-logo.afdesign @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f670c0f8c1785a3bfc16f0d343175291a6dcd460760bac07ae959068be532854 +size 31593 diff --git a/res/spacesocial-logo.afdesign b/res/spacesocial-logo.afdesign deleted file mode 100644 index 7e42a6e..0000000 --- a/res/spacesocial-logo.afdesign +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a03c7e1af2cc54bbe621de3a68f41a75574dbcc498a9918e9fe387c5cb9d31c0 -size 41570 diff --git a/src/app.html b/src/app.html index bfa4811..ce47f4e 100644 --- a/src/app.html +++ b/src/app.html @@ -4,7 +4,23 @@ - space social + + Campfire + + + + + + + + + + + + + + + %sveltekit.head% diff --git a/src/img/CampfireLogo.svelte b/src/img/CampfireLogo.svelte new file mode 100644 index 0000000..2634a57 --- /dev/null +++ b/src/img/CampfireLogo.svelte @@ -0,0 +1,5 @@ + diff --git a/src/img/campfire-logo.svg b/src/img/campfire-logo.svg new file mode 100644 index 0000000..99365b6 --- /dev/null +++ b/src/img/campfire-logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/img/spacesocial-logo.svg b/src/img/spacesocial-logo.svg deleted file mode 100644 index b4f6ac0..0000000 --- a/src/img/spacesocial-logo.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - space social - - diff --git a/src/lib/app.css b/src/lib/app.css index c49cb44..b2fcd4c 100644 --- a/src/lib/app.css +++ b/src/lib/app.css @@ -20,6 +20,15 @@ --accent: #CDA1EC; --text: #E2DFE3; } + .light-only { + display: none + } +} + +@media screen and (prefers-color-scheme: light) { + .dark-only { + display: none + } } @supports (font-variation-settings: normal) { @@ -49,7 +58,7 @@ a:hover { text-decoration: underline; } -#spacesocial-app { +#app { margin: auto 0; padding: 0 16px; display: flex; diff --git a/src/lib/client/api.js b/src/lib/client/api.js index 403ea44..27f0e0b 100644 --- a/src/lib/client/api.js +++ b/src/lib/client/api.js @@ -7,10 +7,10 @@ import { get } from 'svelte/store'; export async function createApp(host) { let form = new FormData(); - form.append("client_name", "space social"); - form.append("redirect_uris", `${location.origin}`); + form.append("client_name", "Campfire"); + form.append("redirect_uris", `${location.origin}/callback`); form.append("scopes", "read write push"); - form.append("website", "https://spacesocial.arimelody.me"); + form.append("website", "https://campfire.bliss.town"); const res = await fetch(`https://${host}/api/v1/apps`, { method: "POST", @@ -35,7 +35,7 @@ export function getOAuthUrl() { return `https://${client.instance.host}/oauth/authorize` + `?client_id=${client.app.id}` + "&scope=read+write+push" + - `&redirect_uri=${location.origin}` + + `&redirect_uri=${location.origin}/callback` + "&response_type=code"; } @@ -44,7 +44,7 @@ export async function getToken(code) { let form = new FormData(); form.append("client_id", client.app.id); form.append("client_secret", client.app.secret); - form.append("redirect_uri", `${location.origin}`); + form.append("redirect_uri", `${location.origin}/callback`); form.append("grant_type", "authorization_code"); form.append("code", code); form.append("scope", "read write push"); @@ -212,9 +212,6 @@ export async function parsePost(data, ancestor_count, with_context) { let client = get(Client.get()); let post = new Post(); - // if (client.instance.capabilities.includes(capabilities.MARKDOWN_CONTENT)) - // post.text = data.text; - // else post.text = data.content; post.reply = null; @@ -223,10 +220,10 @@ export async function parsePost(data, ancestor_count, with_context) { ancestor_count !== 0 ) { const reply_data = data.reply || await getPost(data.in_reply_to_id, ancestor_count - 1); - post.reply = await parsePost(reply_data, ancestor_count - 1, false); // if the post returns false, we probably don't have permission to read it. // we'll respect the thread's privacy, and leave it alone :) - if (post.reply === false) return false; + 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; @@ -259,7 +256,7 @@ export async function parsePost(data, ancestor_count, with_context) { post.reply_count = data.replies_count; post.favourite_count = data.favourites_count; post.favourited = data.favourited; - post.boosted = data.boosted; + post.boosted = data.reblogged; post.mentions = data.mentions; post.files = data.media_attachments; post.url = data.url; @@ -305,7 +302,7 @@ export async function parseUser(data) { if (data.acct.includes('@')) user.host = data.acct.split('@')[1]; else - user.host = get(Client.get()).instance.host; + user.host = client.instance.host; user.emojis = []; data.emojis.forEach(emoji_data => { @@ -315,7 +312,7 @@ export async function parseUser(data) { user.emojis.push(parseEmoji(emoji_data)); }); - get(Client.get()).putCacheUser(user); + client.putCacheUser(user); return user; } diff --git a/src/lib/client/client.js b/src/lib/client/client.js index 5153c52..f4e3788 100644 --- a/src/lib/client/client.js +++ b/src/lib/client/client.js @@ -4,7 +4,7 @@ import { get, writable } from 'svelte/store'; let client = writable(false); -const save_name = "spacesocial"; +const save_name = "campfire"; export class Client { instance; @@ -15,6 +15,7 @@ export class Client { constructor() { this.instance = null; this.app = null; + this.user = null; this.cache = { users: {}, emojis: {}, @@ -22,10 +23,9 @@ export class Client { } static get() { - if (get(client)) return client; + let current = get(client); + if (current && current.app) return client; let new_client = new Client(); - if (typeof window !== typeof undefined) - window.peekie = new_client; new_client.load(); client.set(new_client); return client; @@ -44,13 +44,13 @@ export class Client { if (this.instance.type == server_types.UNSUPPORTED) { console.warn(`Server ${host} is unsupported - ${data.version}`); if (!confirm( - `This app does not officially support ${host}. ` + - `Things may break, or otherwise not work as epxected! ` + - `Are you sure you wish to continue?` - )) return false; + `This app does not officially support ${host}. ` + + `Things may break, or otherwise not work as epxected! ` + + `Are you sure you wish to continue?` + )) return false; } else { - console.log(`Server is "${this.instance.type}" (or compatible) with capabilities: [${this.instance.capabilities}].`); - } + console.log(`Server is "${this.instance.type}" (or compatible) with capabilities: [${this.instance.capabilities}].`); + } this.app = await api.createApp(host); @@ -85,11 +85,21 @@ export class Client { } async verifyCredentials() { + if (this.user) return this.user; + if (!this.app || !this.app.token) { + this.user = false; + return false; + } const data = await api.verifyCredentials(); - if (!data) return false; - this.user = await api.parseUser(data); - client.set(this); - return data; + 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; } async getTimeline(last_post_id) { diff --git a/src/lib/timeline.js b/src/lib/timeline.js index 667b4ee..5858199 100644 --- a/src/lib/timeline.js +++ b/src/lib/timeline.js @@ -4,13 +4,14 @@ import { parsePost } from '$lib/client/api.js'; export let posts = writable([]); -let client = get(Client.get()); let loading = false; 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); diff --git a/src/lib/ui/Navigation.svelte b/src/lib/ui/Navigation.svelte index c5c1a2e..7a01fd3 100644 --- a/src/lib/ui/Navigation.svelte +++ b/src/lib/ui/Navigation.svelte @@ -1,13 +1,15 @@
🗨️ - toggleBoost()} bind:active={post.boosted} bind:count={post.boost_count} sound="boost">🔁 - toggleFavourite()} bind:active={post.favourited} bind:count={post.favourite_count}>⭐ + 🔁 + 😃 🗣️ 🛠️ diff --git a/src/lib/ui/post/ReplyContext.svelte b/src/lib/ui/post/ReplyContext.svelte index b328d3b..dc7dd10 100644 --- a/src/lib/ui/post/ReplyContext.svelte +++ b/src/lib/ui/post/ReplyContext.svelte @@ -16,8 +16,8 @@ let aria_label = post.user.username + '; ' + post.text + '; ' + post.created_at; function gotoPost() { - if (focused) return; if (event.key && event.key !== "Enter") return; + console.log(`/post/${post.id}`); goto(`/post/${post.id}`); } @@ -108,8 +108,8 @@
🗨️ - toggleBoost()} bind:active={post.boosted} bind:count={post.boost_count} sound="boost">🔁 - toggleFavourite()} bind:active={post.favourited} bind:count={post.favourite_count}>⭐ + 🔁 + 😃 🗣️ 🛠️ diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..b55ec41 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,21 @@ + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
diff --git a/src/routes/+page.js b/src/routes/+page.js new file mode 100644 index 0000000..8a7c3ff --- /dev/null +++ b/src/routes/+page.js @@ -0,0 +1,15 @@ +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 + }; +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 197b7d9..ffa3414 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,42 +1,31 @@ -
- +{#if logged_in}
- +

Home

+
-
- {#if ready} - {#if logged_in} -
-

Home

- -
+ +{:else} +
+ +

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! +

- - {:else} -
- -

Space Social

-

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! -

- - - -
- {/if} - {:else} -
- just a moment... -
- {/if} -
- -
- -
- -
+ + +{/if} diff --git a/src/routes/callback/+page.js b/src/routes/callback/+page.js new file mode 100644 index 0000000..80122d2 --- /dev/null +++ b/src/routes/callback/+page.js @@ -0,0 +1,20 @@ +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" + }); +} diff --git a/src/routes/post/+page.js b/src/routes/post/+page.js new file mode 100644 index 0000000..c0ac9bd --- /dev/null +++ b/src/routes/post/+page.js @@ -0,0 +1,5 @@ +import { error } from '@sveltejs/kit'; + +export function load(event) { + error(404, 'Not Found'); +} diff --git a/src/routes/post/[id]/+layout.svelte b/src/routes/post/[id]/+layout.svelte deleted file mode 100644 index b4b556d..0000000 --- a/src/routes/post/[id]/+layout.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - -
- -
- -
- -
-
-

Home

- -
- - -
- -
- -
- -
- - diff --git a/src/routes/post/[id]/+page.js b/src/routes/post/[id]/+page.js index ea5ef2c..e9f391c 100644 --- a/src/routes/post/[id]/+page.js +++ b/src/routes/post/[id]/+page.js @@ -3,22 +3,11 @@ import { Client } from '$lib/client/client.js'; import { parsePost } from '$lib/client/api.js'; import { get } from 'svelte/store'; -export const prerender = true; export const ssr = false; export async function load({ params }) { let client = get(Client.get()); - if (client.app && client.app.token) { - // this triggers the client actually getting the authenticated user's data. - const res = await client.verifyCredentials() - if (res) { - console.log(`Logged in as @${client.user.username}@${client.user.host}`); - } else { - return null; - } - } else { - return null; - } + await client.verifyCredentials(); const post_id = params.id; diff --git a/src/routes/post/[id]/+page.svelte b/src/routes/post/[id]/+page.svelte index c583bd9..a5e9edc 100644 --- a/src/routes/post/[id]/+page.svelte +++ b/src/routes/post/[id]/+page.svelte @@ -1,27 +1,57 @@ +
+

Home

+ +
+
{#if data.posts.length <= 0}
just a moment...
{:else} + {#key data}
{#each replies as post} {/each} + {/key} {/if}