rewrite URLs to represent instance (#2)

This commit is contained in:
ari melody 2024-07-07 14:33:28 +01:00
parent 41143cdddf
commit a3fdd0007c
Signed by: ari
GPG key ID: CF99829C92678188
15 changed files with 199 additions and 168 deletions

View file

@ -18,7 +18,10 @@ app.subscribe(app => {
*/ */
function saveApp(app) { function saveApp(app) {
if (!browser) return; if (!browser) return;
if (!app) localStorage.removeItem(app_name + "_app"); if (!app) {
localStorage.removeItem(app_name + "_app");
return;
}
localStorage.setItem(app_name + "_app", JSON.stringify(app)); localStorage.setItem(app_name + "_app", JSON.stringify(app));
} }

View file

@ -73,7 +73,10 @@ export async function createServer(host) {
*/ */
function saveServer(server) { function saveServer(server) {
if (!browser) return; if (!browser) return;
if (!server) localStorage.removeItem(app_name + "_server"); if (!server) {
localStorage.removeItem(app_name + "_server");
return;
}
localStorage.setItem(app_name + "_server", JSON.stringify(server)); localStorage.setItem(app_name + "_server", JSON.stringify(server));
} }

View file

@ -88,7 +88,7 @@
</div> </div>
</header> </header>
{#if $logged_in} {#if $account}
<div id="nav-items"> <div id="nav-items">
<Button label="Timeline" <Button label="Timeline"
on:click={() => handle_btn("timeline")} on:click={() => handle_btn("timeline")}
@ -180,6 +180,7 @@
</div> </div>
</div> </div>
{/if} {/if}
<span class="version"> <span class="version">
campfire v{VERSION} campfire v{VERSION}
<br> <br>

View file

@ -1,6 +1,5 @@
<script> <script>
import * as api from '$lib/api'; import * as api from '$lib/api';
import { get } from 'svelte/store';
import { server } from '$lib/client/server'; import { server } from '$lib/client/server';
import { app } from '$lib/client/app'; import { app } from '$lib/client/app';
import { account } from '@cf/store/account'; import { account } from '@cf/store/account';
@ -20,11 +19,13 @@
export let post; export let post;
async function toggleBoost() { async function toggleBoost() {
if (!$app || !$app.token) return;
let data; let data;
if (post.boosted) if (post.boosted)
data = await api.unboostPost(get(server).host, get(app).token, post.id); data = await api.unboostPost($server.host, $app.token, post.id);
else else
data = await api.boostPost(get(server).host, get(app).token, post.id); data = await api.boostPost($server.host, $app.token, post.id);
if (!data) { if (!data) {
console.error(`Failed to boost post ${post.id}`); console.error(`Failed to boost post ${post.id}`);
return; return;
@ -34,11 +35,13 @@
} }
async function toggleFavourite() { async function toggleFavourite() {
if (!$app || !$app.token) return;
let data; let data;
if (post.favourited) if (post.favourited)
data = await api.unfavouritePost(get(server).host, get(app).token, post.id); data = await api.unfavouritePost($server.host, $app.token, post.id);
else else
data = await api.favouritePost(get(server).host, get(app).token, post.id); data = await api.favouritePost($server.host, $app.token, post.id);
if (!data) { if (!data) {
console.error(`Failed to favourite post ${post.id}`); console.error(`Failed to favourite post ${post.id}`);
return; return;
@ -69,13 +72,13 @@
<ActionButton type="reply" label="Reply" bind:count={post.reply_count} sound="post" disabled> <ActionButton type="reply" label="Reply" bind:count={post.reply_count} sound="post" disabled>
<ReplyIcon/> <ReplyIcon/>
</ActionButton> </ActionButton>
<ActionButton type="boost" label="Boost" on:click={toggleBoost} bind:active={post.boosted} bind:count={post.boost_count} sound="boost"> <ActionButton type="boost" label="Boost" on:click={toggleBoost} bind:active={post.boosted} bind:count={post.boost_count} sound="boost" disabled={!$account}>
<RepostIcon/> <RepostIcon/>
<svelte:fragment slot="activeIcon"> <svelte:fragment slot="activeIcon">
<RepostIcon/> <RepostIcon/>
</svelte:fragment> </svelte:fragment>
</ActionButton> </ActionButton>
<ActionButton type="favourite" label="Favourite" on:click={toggleFavourite} bind:active={post.favourited} bind:count={post.favourite_count}> <ActionButton type="favourite" label="Favourite" on:click={toggleFavourite} bind:active={post.favourited} bind:count={post.favourite_count} disabled={!$account}>
<FavouriteIcon/> <FavouriteIcon/>
<svelte:fragment slot="activeIcon"> <svelte:fragment slot="activeIcon">
<FavouriteIconFill/> <FavouriteIconFill/>

View file

@ -1,6 +1,7 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { server } from '$lib/client/server';
import BoostContext from './BoostContext.svelte'; import BoostContext from './BoostContext.svelte';
import ReplyContext from './ReplyContext.svelte'; import ReplyContext from './ReplyContext.svelte';
@ -31,7 +32,7 @@
event.ctrlKey)) return; event.ctrlKey)) return;
if (event.key && event.key !== "Enter") return; if (event.key && event.key !== "Enter") return;
} }
goto(`/post/${post.id}`); goto(`/${$server.host}/${post.account.mention}/${post.id}`);
} }
let el; let el;

View file

@ -2,7 +2,7 @@
import * as api from '$lib/api.js'; import * as api from '$lib/api.js';
import { server, capabilities } from '$lib/client/server.js'; import { server, capabilities } from '$lib/client/server.js';
import { app } from '$lib/client/app.js'; import { app } from '$lib/client/app.js';
import { get } from 'svelte/store'; import { account } from '@cf/store/account';
import { parseReactions } from '$lib/post.js'; import { parseReactions } from '$lib/post.js';
import ReactionButton from './ReactionButton.svelte'; import ReactionButton from './ReactionButton.svelte';
@ -11,6 +11,8 @@
export let post; export let post;
async function toggleReaction(reaction) { async function toggleReaction(reaction) {
if (!$app || !$app.token) return;
if ( if (
reaction.name.includes('@') && reaction.name.includes('@') &&
!$server.capabilities.includes(capabilities.FOREIGN_REACTIONS) !$server.capabilities.includes(capabilities.FOREIGN_REACTIONS)
@ -18,9 +20,9 @@
let data; let data;
if (reaction.me) if (reaction.me)
data = await api.unreactPost(get(server).host, get(app).token, post.id, reaction.name); data = await api.unreactPost($server.host, $app.token, post.id, reaction.name);
else else
data = await api.reactPost(get(server).host, get(app).token, post.id, reaction.name); data = await api.reactPost($server.host, $app.token, 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;
@ -39,7 +41,7 @@
on:click={() => toggleReaction(reaction)} on:click={() => toggleReaction(reaction)}
bind:active={reaction.me} bind:active={reaction.me}
bind:count={reaction.count} bind:count={reaction.count}
disabled={reaction.name.includes('@') && !$server.capabilities.includes(capabilities.FOREIGN_REACTIONS)} disabled={!$account || (reaction.name.includes('@') && !$server.capabilities.includes(capabilities.FOREIGN_REACTIONS))}
title={reaction.name} title={reaction.name}
label=""> label="">
{#if reaction.url} {#if reaction.url}

View file

@ -6,7 +6,6 @@
import { account, logged_in } from '$lib/stores/account.js'; import { account, logged_in } from '$lib/stores/account.js';
import { parseAccount } from '$lib/account.js'; import { parseAccount } from '$lib/account.js';
import { unread_notif_count, last_read_notif_id } from '$lib/notifications.js'; import { unread_notif_count, last_read_notif_id } from '$lib/notifications.js';
import { get } from 'svelte/store';
import Navigation from '$lib/ui/Navigation.svelte'; import Navigation from '$lib/ui/Navigation.svelte';
import Modal from '@cf/ui/Modal.svelte'; import Modal from '@cf/ui/Modal.svelte';
@ -16,25 +15,25 @@
let show_composer = false; let show_composer = false;
async function init() { async function init() {
if (!get(app) || !get(app).token) { if (!$app || !$app.token) {
account.set(false); account.set(false);
logged_in.set(false); logged_in.set(false);
return; return;
} }
// logged in- attempt to retrieve using token // logged in- attempt to retrieve using token
const data = await api.verifyCredentials(get(server).host, get(app).token); const data = await api.verifyCredentials($server.host, $app.token);
if (!data) return; if (!data) return;
account.set(parseAccount(data)); account.set(parseAccount(data));
logged_in.set(true); logged_in.set(true);
console.log(`Logged in as @${get(account).username}@${get(account).host}`); console.log(`Logged in as @${$account.username}@${$account.host}`);
// spin up async task to fetch notifications // spin up async task to fetch notifications
const notif_data = await api.getNotifications( const notif_data = await api.getNotifications(
get(server).host, $server.host,
get(app).token, $app.token,
get(last_read_notif_id) $last_read_notif_id
); );
if (!notif_data) return; if (!notif_data) return;

View file

@ -1,15 +1,15 @@
<script> <script>
import { page } from '$app/stores'; import { page } from '$app/stores';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { logged_in } from '$lib/stores/account.js'; import { account } from '$lib/stores/account.js';
import { timeline, getTimeline } from '$lib/timeline.js'; import { timeline, getTimeline } from '$lib/timeline.js';
import LoginForm from '$lib/ui/LoginForm.svelte'; import LoginForm from '$lib/ui/LoginForm.svelte';
import Button from '$lib/ui/Button.svelte'; import Button from '$lib/ui/Button.svelte';
import Post from '$lib/ui/post/Post.svelte'; import Post from '$lib/ui/post/Post.svelte';
logged_in.subscribe(logged_in => { account.subscribe(account => {
if (logged_in) getTimeline(); if (account) getTimeline();
}); });
document.addEventListener("scroll", () => { document.addEventListener("scroll", () => {
@ -20,7 +20,7 @@
}); });
</script> </script>
{#if $logged_in} {#if $account}
<header> <header>
<h1>Home</h1> <h1>Home</h1>
<nav> <nav>

View file

@ -1,5 +1,5 @@
export async function load({ params }) { export async function load({ params }) {
return { return {
post_id: params.id server_domain: params.server
}; };
} }

View file

@ -0,0 +1,8 @@
import { error } from '@sveltejs/kit';
export async function load({ params }) {
return error(404, 'Not Found');
// return {
// account_name: params.account
// };
}

View file

@ -0,0 +1,7 @@
export async function load({ params }) {
return {
server_host: params.server,
account_handle: params.account,
post_id: params.post
};
}

View file

@ -0,0 +1,143 @@
<script>
import * as api from '$lib/api.js';
import { server, createServer } from '$lib/client/server.js';
import { app } from '$lib/client/app.js';
import { parsePost } from '$lib/post.js';
import { goto, afterNavigate } from '$app/navigation';
import { base as previous_page } from '$app/paths'
import Post from '$lib/ui/post/Post.svelte';
import Button from '$lib/ui/Button.svelte';
export let data;
let post;
let error = false;
if (($server && $server.host === data.server_host) && $app) {
post = fetchPost(data.post_id, $app.token);
} else {
post = createServer(data.server_host).then(new_server => {
server.set(new_server);
if (!$server) {
error = `Failed to connect to <code>${data.server_host}</code>.`;
console.error(`Failed to connect to ${data.server_host}.`);
return;
}
return post = fetchPost(data.post_id, null);
});
}
afterNavigate(({from}) => {
previous_page = from?.url.pathname || previous_page
})
async function fetchPost(post_id, token) {
const post_data = await api.getPost($server.host, token, post_id);
if (!post_data || post_data.error) {
error = `Failed to retrieve post <code>${post_id}</code>.`;
console.error(`Failed to retrieve post ${post_id}.`);
return;
}
let post = await parsePost(post_data, 0);
const post_context = await api.getPostContext($server.host, token, 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 parsePost(post_context.ancestors.pop(), 0);
thread_top = thread_top.reply;
}
// handle descendants (below post)
post.replies = [];
for (let i in post_context.descendants) {
post.replies.push(parsePost(post_context.descendants[i], 0));
}
return post;
}
</script>
{#await post}
<div class="loading throb">
<span>loading post...</span>
</div>
{:then post}
{#if error}
<p>{@html error}</p>
{:else}
<header>
{#if previous_page}
<nav>
<Button centered on:click={() => {goto(previous_page)}}>Back</Button>
</nav>
{/if}
<img src={post.account.avatar_url} type={post.account.avatar_type || "image/png"} alt="" width="40" height="40" class="header-avatar" loading="lazy" decoding="async">
<h1>
Post by {@html post.account.rich_name}
</h1>
</header>
<div id="feed" role="feed">
<Post post_data={post} focused />
<br>
{#each post.replies as reply}
{#await reply then reply}
<Post post_data={reply} />
{/await}
{/each}
</div>
{/if}
{/await}
<style>
header {
width: 100%;
height: 64px;
margin: 16px 0 8px 0;
display: flex;
flex-direction: row;
}
header .header-avatar {
width: 40px;
height: 40px;
margin: auto 0;
border-radius: 4px;
}
header h1 {
margin: auto auto auto 8px;
font-size: 1.5em;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
header nav {
margin-right: 8px;
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
#feed {
margin-bottom: 20vh;
}
.loading {
width: 100%;
height: 80vh;
display: flex;
justify-content: center;
align-items: center;
font-size: 2em;
font-weight: bold;
}
</style>

View file

@ -19,7 +19,6 @@
api.getToken(get(server).host, get(app).id, get(app).secret, auth_code).then(token => { api.getToken(get(server).host, get(app).id, get(app).secret, auth_code).then(token => {
if (!token) { if (!token) {
error(400, { message: "Invalid auth code provided" }); error(400, { message: "Invalid auth code provided" });
return;
} }
app.update(app => { app.update(app => {

View file

@ -1,5 +0,0 @@
import { error } from '@sveltejs/kit';
export function load(event) {
error(404, 'Not Found');
}

View file

@ -1,133 +0,0 @@
<script>
import * as api from '$lib/api.js';
import { logged_in } from '$lib/stores/account.js';
import { server } from '$lib/client/server.js';
import { app } from '$lib/client/app.js';
import { parsePost } from '$lib/post.js';
import { get } from 'svelte/store';
import { goto, afterNavigate } from '$app/navigation';
import { base } from '$app/paths'
import Post from '$lib/ui/post/Post.svelte';
import Button from '$lib/ui/Button.svelte';
export let data;
let error = false;
if (!get(logged_in)) goto("/");
let previous_page = base;
afterNavigate(({from}) => {
previous_page = from?.url.pathname || previous_page
})
$: post = (async resolve => {
const post_data = await api.getPost(get(server).host, get(app).token, data.post_id);
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 parsePost(post_data, 0);
const post_context = await api.getPostContext(get(server).host, get(app).token, 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 parsePost(post_context.ancestors.pop(), 0);
thread_top = thread_top.reply;
}
// handle descendants (below post)
post.replies = [];
for (let i in post_context.descendants) {
post.replies.push(parsePost(post_context.descendants[i], 0));
}
return post;
})();
</script>
{#if !error}
<header>
{#await post then post}
<nav>
<Button centered on:click={() => {goto(previous_page)}}>Back</Button>
</nav>
<img src={post.account.avatar_url} type={post.account.avatar_type} alt="" width="40" height="40" class="header-avatar" loading="lazy" decoding="async">
<h1>
Post by {@html post.account.rich_name}
</h1>
{/await}
</header>
<div id="feed" role="feed">
{#await post}
<div class="loading throb">
<span>loading post...</span>
</div>
{:then post}
<Post post_data={post} focused />
<br>
{#each post.replies as reply}
{#await reply then reply}
<Post post_data={reply} />
{/await}
{/each}
{/await}
</div>
{:else}
<p>{@html error}</p>
{/if}
<style>
header {
width: 100%;
height: 64px;
margin: 16px 0 8px 0;
display: flex;
flex-direction: row;
}
header .header-avatar {
width: 40px;
height: 40px;
margin: auto 0;
border-radius: 4px;
}
header h1 {
margin: auto auto auto 8px;
font-size: 1.5em;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
header nav {
margin-right: 8px;
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
#feed {
margin-bottom: 20vh;
}
.loading {
width: 100%;
height: 80vh;
display: flex;
justify-content: center;
align-items: center;
font-size: 2em;
font-weight: bold;
}
</style>