Compare commits

..

3 commits

Author SHA1 Message Date
2260afd5f1 add /explore/ 2026-03-11 13:36:07 +01:00
db37df4151 improve loading ux 2025-12-25 16:32:20 +01:00
edc38bb6a5 add /about/ 2025-12-20 20:52:51 +01:00
12 changed files with 159 additions and 10 deletions

View file

@ -1,7 +1,7 @@
{
"name": "@yusurko/vigil",
"private": true,
"version": "0.1.0-dev49",
"version": "0.1.0-dev62",
"type": "module",
"scripts": {
"dev": "vite dev",

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { RiHome2Line, RiSettings2Line, RiShieldStarLine, RiUserLine } from 'svelte-remixicon';
import { RiCompassLine, RiHome2Line, RiSettings2Line, RiShieldStarLine, RiUserLine } from 'svelte-remixicon';
import MenuLink from './MenuLink.svelte';
let { user = null } = $props();
@ -7,6 +7,7 @@
<ul class="column">
<MenuLink href="/" icon={RiHome2Line} label="Home" />
<MenuLink href="/explore" icon={RiCompassLine} label="Explore" />
<MenuLink href="/settings" icon={RiSettings2Line} label="Settings" />
{#if user}
<MenuLink href="/@{user.username}" icon={RiUserLine} label="My profile" />
@ -14,4 +15,6 @@
{#if user.badges && user.badges.indexOf('administrator') >= 0}
<MenuLink href="/admin/" icon={RiShieldStarLine} label="Administration" />
{/if}
<!-- TODO add /v1/user/@me/guilds (?) -->
</ul>

View file

@ -4,14 +4,16 @@ import { RiListOrdered } from "svelte-remixicon";
let health : {
app_name: string, version: string, post_count: number,
user_count: number, me: null | UserEntry, color_theme: number
user_count: number, me: null | UserEntry, color_theme: number,
loaded: boolean
} = $state({
app_name: 'app_name',
app_name: 'Freak',
version: "?.?",
post_count: NaN,
user_count: NaN,
me: null,
color_theme: 0
color_theme: 0,
loaded: false
});
@ -21,6 +23,7 @@ export function setHealth ({ name, version, post_count, user_count, color_theme
health.post_count = post_count;
health.user_count = user_count;
health.color_theme = color_theme;
health.loaded = true;
}
export function setMe (me: UserEntry | null) {
@ -60,4 +63,8 @@ export function randomChoice<T>(list: T[]): T {
export function getColorThemeCode (): number {
return health.color_theme;
}
export function isLoaded(): boolean {
return health.loaded;
}

View file

@ -27,7 +27,7 @@ let colorThemeCls = $derived(`color-scheme-${colorScheme} color-theme-${colorThe
</script>
<svelte:head>
<meta name="og:site_name" content="app_name" />
<meta name="og:site_name" content="Freak" />
{#if data}
<meta name="generator" content={`Freak (frontend ${version} + Svelte 5, backend ${bkVersion()}); https://nekode.yusur.moe/yusur/freak`} />
{/if}

View file

@ -3,7 +3,7 @@
import type { GuildEntry, PostEntry } from "$lib/backend";
import Centered from "$lib/Centered.svelte";
import Feed from "$lib/Feed.svelte";
import { activePostCount, activeUserCount, appName, getMe } from "$lib/globals.svelte";
import { activePostCount, activeUserCount, appName, getMe, isLoaded } from "$lib/globals.svelte";
import HomeMenu from "$lib/HomeMenu.svelte";
import SLayout from "$lib/SLayout.svelte";
@ -12,6 +12,7 @@
let { feed } : { feed: PostEntry[] } = $derived(data);
let { top_guilds }: { top_guilds: GuildEntry[] } = $derived(data);
// TODO infinite scrolling
// $effect(() => {
// if (me && data?.feed) {
// feed.push(...data.feed.slice(feedIndex));
@ -46,6 +47,10 @@
{/if}
{/snippet}
</SLayout>
{:else if !isLoaded()}
<Centered>
<p>{appName()} is now loading…</p>
</Centered>
{:else}
<Centered>
<p>{appName()} is a social media platform made by people like you.<br />

View file

@ -42,7 +42,7 @@ async function loadTopGuilds (event: LoadEvent): Promise<GuildEntry[] | null> {
export async function load(event): Promise<{feed: PostEntry[] | null, top_guilds?: GuildEntry[] | null}> {
// delay loading after layout
if (!isFirstLoad) {
await sleep(2000);
await sleep(500);
isFirstLoad = true;
}

View file

@ -4,7 +4,6 @@
import SLayout from "$lib/SLayout.svelte";
import UserAbout from "$lib/UserAbout.svelte";
import UserMenu from "$lib/UserMenu.svelte";
import { RiUserLine } from "svelte-remixicon";
let { data } : { data: { user: UserEntry, feed: PostEntry[] } } = $props();

View file

@ -8,7 +8,7 @@ export async function load(event) {
const resp = await backend.withEvent(event).fetch('user/@' + encodeURIComponent(name) + '/feed');
if(resp.status === 404) {
if (resp.status === 404) {
error(404);
}

View file

@ -0,0 +1,13 @@
<script lang="ts">
import { SvelteShowdown } from "svelte-showdown";
let { data }: { data: {content: string} } = $props();
let { content } = data;
</script>
<article class="card">
<p class="faint">About</p>
<SvelteShowdown {content} />
</article>

19
src/routes/about/+page.ts Normal file
View file

@ -0,0 +1,19 @@
import { backend } from '$lib/backend.js';
import { error } from '@sveltejs/kit';
export async function load(event) {
const resp = await backend.withEvent(event).fetch('about/about');
if(resp.status === 404) {
error(404);
}
const respJ = await resp.json();
const { content } = respJ;
return { content };
}

View file

@ -0,0 +1,50 @@
<script lang="ts">
import AsideCard from "$lib/AsideCard.svelte";
import type { GuildEntry, PostEntry } from "$lib/backend";
import Centered from "$lib/Centered.svelte";
import Feed from "$lib/Feed.svelte";
import { getMe } from "$lib/globals.svelte";
import HomeMenu from "$lib/HomeMenu.svelte";
import SLayout from "$lib/SLayout.svelte";
let me = getMe();
let { data } : { data: { feed: PostEntry[], top_guilds: GuildEntry[] } } = $props();
let { feed } : { feed: PostEntry[] } = $derived(data);
let { top_guilds }: { top_guilds: GuildEntry[] } = $derived(data);
// TODO infinite scrolling
// $effect(() => {
// if (me && data?.feed) {
// feed.push(...data.feed.slice(feedIndex));
// feedIndex += feed.length;
// } else if (me) {
// console.log('data.feed is', data?.feed)
// }
// });
</script>
<SLayout title="Explore">
<Feed posts={feed} />
{#snippet left()}
<HomeMenu user={me} />
{/snippet}
{#snippet right()}
{#if top_guilds}
<AsideCard title="Top Communities">
<ul>
{#each top_guilds as gu}
<li>
<a href="/+{gu.name}">+{gu.name}</a> -
<strong>{gu.post_count ?? 0}</strong> posts -
<strong>{gu.subscriber_count ?? 0}</strong> subscribers
</li>
{/each}
</ul>
</AsideCard>
{/if}
{/snippet}
</SLayout>

View file

@ -0,0 +1,53 @@
import { backend, type GuildEntry, type PostEntry } from '$lib/backend.js';
import { getMe, sleep } from '$lib/globals.svelte.js';
import type { LoadEvent } from '@sveltejs/kit';
async function loadFeed (event: LoadEvent): Promise<PostEntry[] | null> {
const resp = await backend.withEvent(event).fetch('explore/feed');
if ([200].indexOf(resp.status) < 0) {
console.error(`fetch feed returned status ${resp.status}`);
return null;
}
try {
const respJ = await resp.json();
const { feed } : { feed: PostEntry[] } = respJ;
return feed;
} catch (e) {
return null;
}
}
async function loadTopGuilds (event: LoadEvent): Promise<GuildEntry[] | null> {
const resp = await backend.withEvent(event).fetch('top/guilds');
if ([200].indexOf(resp.status) < 0) {
console.error(`fetch top_guilds returned status ${resp.status}`);
return null;
}
try {
const respJ = await resp.json();
const { has: top_guilds } : { has: GuildEntry[] } = respJ;
return top_guilds;
} catch (e) {
return null;
}
}
export async function load(event): Promise<{feed: PostEntry[] | null, top_guilds?: GuildEntry[] | null}> {
let feed = null;
let me = getMe();
let top_guilds = null;
if (me) {
feed = await loadFeed(event);
top_guilds = await loadTopGuilds(event);
}
return { feed, top_guilds };
}