feat: follow requests in sidebar

This commit is contained in:
mae taylor 2025-07-14 17:42:20 +01:00
parent 0dd903a4eb
commit 6f446fd871
Signed by: mae
GPG key ID: 3C80D76BA7A3B9BD
6 changed files with 117 additions and 3 deletions

View file

@ -23,6 +23,7 @@
"navigation": { "navigation": {
"timeline": "Timeline", "timeline": "Timeline",
"notifications": "Notifications", "notifications": "Notifications",
"follow_requests": "Follow requests",
"explore": "Explore", "explore": "Explore",
"lists": "Lists", "lists": "Lists",

View file

@ -176,6 +176,32 @@ export async function getNotifications(host, token, min_id, max_id, limit, types
return data; return data;
} }
/**
* GET /api/v1/follow_requests
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} min_id - If provided, only shows follow requests since this ID.
* @param {string} max_id - If provided, only shows follow requests before this ID.
* @param {string} limit - The maximum number of follow requests to retrieve (default 40, max 80).
*/
export async function getFollowRequests(host, token, since_id, max_id, limit) {
let url = `https://${host}/api/v1/follow_requests`;
let params = new URLSearchParams();
if (since_id) params.append("since_id", since_id);
if (max_id) params.append("max_id", max_id);
if (limit) params.append("limit", limit);
const params_string = params.toString();
if (params_string) url += '?' + params_string;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": "Bearer " + token }
}).then(res => res.json());
return data;
}
/** /**
* GET /api/v1/timelines/{timeline} * GET /api/v1/timelines/{timeline}
* @param {string} host - The domain of the target server. * @param {string} host - The domain of the target server.

24
src/lib/followRequests.js Normal file
View file

@ -0,0 +1,24 @@
import { server } from './client/server.js';
import { writable } from "svelte/store";
import * as api from "./api.js";
import { app } from './client/app.js';
import { get } from 'svelte/store';
// Cache for all requests
export let followRequests = writable();
/**
* Gets all follow requests
* @param {boolean} force
*/
export async function fetchFollowRequests(force) {
// if already cached, return for now
if(!get(followRequests) && !force) return;
let newReqs = await api.getFollowRequests(
get(server).host,
get(app).token
);
followRequests.set(newReqs);
}

View file

@ -6,8 +6,9 @@
import { playSound } from '$lib/sound.js'; import { playSound } from '$lib/sound.js';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import { unread_notif_count } from '$lib/notifications.js'; import { unread_notif_count } from '$lib/notifications.js';
import { fetchFollowRequests, followRequests } from '$lib/followRequests.js'
import Lang from '$lib/lang'; import Lang from '$lib/lang';
import Logo from '$lib/../img/campfire-logo.svg'; import Logo from '$lib/../img/campfire-logo.svg';
@ -24,6 +25,7 @@
import InfoIcon from '../../img/icons/info.svg'; import InfoIcon from '../../img/icons/info.svg';
import SettingsIcon from '../../img/icons/settings.svg'; import SettingsIcon from '../../img/icons/settings.svg';
import LogoutIcon from '../../img/icons/logout.svg'; import LogoutIcon from '../../img/icons/logout.svg';
import FollowersIcon from '../../img/icons/followers.svg';
const VERSION = APP_VERSION; const VERSION = APP_VERSION;
const lang = Lang('en_GB'); const lang = Lang('en_GB');
@ -40,7 +42,7 @@
goto(`/${$server.host}/${$account.username}`); goto(`/${$server.host}/${$account.username}`);
} }
async function log_out() { async function logOut() {
if (!confirm("This will log you out. Are you sure?")) return; if (!confirm("This will log you out. Are you sure?")) return;
const res = await api.revokeToken( const res = await api.revokeToken(
@ -59,6 +61,10 @@
goto("/"); goto("/");
} }
onMount(async () => {
await fetchFollowRequests(true)
})
</script> </script>
<div id="navigation"> <div id="navigation">
@ -91,6 +97,19 @@
</span> </span>
{/if} {/if}
</Button> </Button>
{#if $followRequests.length > 0}
<Button label="Follow requests"
href="/follow-requests"}
active={$page.url.pathname === "/follow-requests"}>
<svelte:fragment slot="icon">
<FollowersIcon/>
</svelte:fragment>
{lang.string('navigation.follow_requests')}
<span class="notification-count">
{$followRequests.length}
</span>
</Button>
{/if}
<Button label="Explore" disabled> <Button label="Explore" disabled>
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<ExploreIcon height="auto"/> <ExploreIcon height="auto"/>
@ -142,7 +161,7 @@
<SettingsIcon/> <SettingsIcon/>
</svelte:fragment> </svelte:fragment>
</Button> </Button>
<Button centered label="{lang.string('navigation.log_out')}" on:click={() => log_out()}> <Button centered label="{lang.string('navigation.log_out')}" on:click={() => logOut()}>
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<LogoutIcon/> <LogoutIcon/>
</svelte:fragment> </svelte:fragment>

View file

@ -0,0 +1,34 @@
<script>
export let title;
</script>
<header>
<h1>{title}</h1>
<slot name="icon" />
</header>
<style>
header {
width: 100%;
height: 64px;
margin: 16px 0;
padding: 0 8px;
display: flex;
flex-direction: row;
user-select: none;
box-sizing: border-box;
}
header h1 {
font-size: 1.5em;
}
nav {
margin-left: auto;
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
</style>

View file

@ -0,0 +1,10 @@
<script>
import { followRequests } from '$lib/followRequests.js';
</script>
<div>
</div>
<style></style>