Prettier Linting
Some checks failed
Run Tests on Code / test (push) Has been cancelled
Build Docker Image on Pull Request / test (pull_request) Failing after 3m47s

This commit is contained in:
Neshura 2024-01-06 14:02:30 +01:00
parent 7f3eb2888a
commit 4254015cd8
Signed by: Neshura
GPG key ID: B6983AAA6B9A7A6C
11 changed files with 243 additions and 201 deletions

View file

@ -1,173 +1,193 @@
<svelte:options runes={true} />
<script lang="ts">
import {onMount} from "svelte";
import sanitizeHtml from "sanitize-html";
import {Skeleton} from "$lib/components/ui/skeleton/index.js";
import {DoubleArrowUp} from "radix-icons-svelte";
import { onMount } from 'svelte';
import sanitizeHtml from 'sanitize-html';
import { Skeleton } from '$lib/components/ui/skeleton/index.js';
import { DoubleArrowUp } from 'radix-icons-svelte';
let { account, maxToots, accountId, excludeReplies }: { account: string, maxToots?: number, accountId?: string, excludeReplies: boolean } = $props();
let {
account,
maxToots,
accountId,
excludeReplies
}: { account: string; maxToots?: number; accountId?: string; excludeReplies: boolean } = $props();
let toots: Toot[] = $state([]);
let loading = $state(false);
let toots: Toot[] = $state([]);
let loading = $state(false);
onMount(() => {
loading = true;
loadToots(account, accountId, maxToots, excludeReplies);
})
onMount(() => {
loading = true;
loadToots(account, accountId, maxToots, excludeReplies);
});
interface Toot {
created_at: string;
in_reply_to_id: string | null;
content: string;
url: string;
account: {
username: string;
display_name: string;
avatar: string;
url: string;
};
reblog?: Toot;
media_attachments: {
type: "unknown" | "image" | "gifv" | "video" | "audio";
url: string;
preview_url: string;
description: string;
blurhash: string;
}[];
}
interface Toot {
created_at: string;
in_reply_to_id: string | null;
content: string;
url: string;
account: {
username: string;
display_name: string;
avatar: string;
url: string;
};
reblog?: Toot;
media_attachments: {
type: 'unknown' | 'image' | 'gifv' | 'video' | 'audio';
url: string;
preview_url: string;
description: string;
blurhash: string;
}[];
}
export async function getToots(
userURL: string,
limit: number,
excludeReplies: boolean,
accountId?: string,
): Promise<Toot[]> {
const url = new URL(userURL);
export async function getToots(
userURL: string,
limit: number,
excludeReplies: boolean,
accountId?: string
): Promise<Toot[]> {
const url = new URL(userURL);
// Either use the account id specified or look it up based on the username
// in the link.
const userId: string =
accountId ??
(await (async () => {
// Extract username from URL.
const parts = /@(\w+)$/.exec(url.pathname);
if (!parts) {
throw "not a Mastodon user URL";
}
const username = parts[1];
// Either use the account id specified or look it up based on the username
// in the link.
const userId: string =
accountId ??
(await (async () => {
// Extract username from URL.
const parts = /@(\w+)$/.exec(url.pathname);
if (!parts) {
throw 'not a Mastodon user URL';
}
const username = parts[1];
// Look up user ID from username.
const lookupURL = Object.assign(new URL(url), {
pathname: "/api/v1/accounts/lookup",
search: `?acct=${username}`,
});
return (await (await fetch(lookupURL)).json())["id"];
})());
// Look up user ID from username.
const lookupURL = Object.assign(new URL(url), {
pathname: '/api/v1/accounts/lookup',
search: `?acct=${username}`
});
return (await (await fetch(lookupURL)).json())['id'];
})());
// Fetch toots.
const tootURL = Object.assign(new URL(url), {
pathname: `/api/v1/accounts/${userId}/statuses`,
search: `?limit=${limit ?? 5}&exclude_replies=${!!excludeReplies}`,
});
// Fetch toots.
const tootURL = Object.assign(new URL(url), {
pathname: `/api/v1/accounts/${userId}/statuses`,
search: `?limit=${limit ?? 5}&exclude_replies=${!!excludeReplies}`
});
return await (await fetch(tootURL)).json();
}
return await (await fetch(tootURL)).json();
}
function loadToots() {
getToots(
account,
maxToots ?? 5,
excludeReplies === true,
accountId,
).then((data) => {
toots = data;
loading = false;
});
}
function loadToots() {
getToots(account, maxToots ?? 5, excludeReplies === true, accountId).then((data) => {
toots = data;
loading = false;
});
}
</script>
{#snippet avatar(toot)}
<a class="flex flex-row gap-2" href={toot.account.url}>
<img class="rounded-md" width="48px" height="48px" src={toot.account.avatar} alt="{toot.account.username} avatar"/>
<div class="flex flex-col items-start">
<span class="hover:underline font-bold h-6">{toot.account.display_name}</span>
<span class="h-4 text-sm text-muted">@{toot.account.username}</span>
</div>
</a>
<a class="flex flex-row gap-2" href={toot.account.url}>
<img
class="rounded-md"
width="48px"
height="48px"
src={toot.account.avatar}
alt="{toot.account.username} avatar"
/>
<div class="flex flex-col items-start">
<span class="h-6 font-bold hover:underline">{toot.account.display_name}</span>
<span class="h-4 text-sm text-muted">@{toot.account.username}</span>
</div>
</a>
{/snippet}
{#snippet body(toot)}
<div>
<div class="[&>p>span>a]:hover:underline">
{@html sanitizeHtml(toot.content)}
</div>
{#each toot.media_attachments.filter((att) => att.type === "image") as image}
<a class="block rounded-md aspect-16/9 w-full overflow-hidden" href={image.url} target="_blank" rel="noopener noreferrer">
<img class="h-full w-full object-cover" src={image.preview_url} alt={image.description}/>
</a>
{/each}
</div>
<div>
<div class="[&>p>span>a]:hover:underline">
{@html sanitizeHtml(toot.content)}
</div>
{#each toot.media_attachments.filter((att) => att.type === 'image') as image}
<a
class="block aspect-16/9 w-full overflow-hidden rounded-md"
href={image.url}
target="_blank"
rel="noopener noreferrer"
>
<img class="h-full w-full object-cover" src={image.preview_url} alt={image.description} />
</a>
{/each}
</div>
{/snippet}
<ol class="w-full">
{#if loading}
{#each Array(maxToots ?? 5) as placeholder}
<li class="flex flex-col gap-3 px-4 py-3">
<div class="flex flex-row justify-between">
<div class="flex flex-row gap-2">
<Skeleton class="h-12 w-12 rounded-md"/>
<div class="flex flex-col items-start gap-1">
<Skeleton class="h-6 w-24"></Skeleton>
<Skeleton class="h-4 w-20"></Skeleton>
</div>
</div>
<Skeleton class="h-10 w-16" />
</div>
<Skeleton class="h-36 w-full"></Skeleton>
</li>
{/each}
{:else}
{#each toots as toot}
<li class="flex flex-col gap-3 px-4 py-3">
{#if toot.reblog}
<div class="flex flex-row justify-between">
<div class="flex flex-col gap-1">
<a class="items-center flex flex-row gap-1" href={toot.account.url}>
<DoubleArrowUp />
<img class="rounded-md" width="23px" height="23px" src={toot.account.avatar} alt="{toot.account.username} avatar"/>
<span class="hover:underline font-bold h-6">{toot.account.display_name}</span>
</a>
{@render avatar(toot.reblog)}
</div>
<a class="flex flex-col items-center hover:underline text-sm text-muted" href={toot.url}>
<time datetime={toot.created_at}>
{new Date(toot.created_at).toLocaleDateString()}
</time>
<time datetime={toot.created_at}>
{new Date(toot.created_at).toLocaleTimeString()}
</time>
</a>
</div>
{@render body(toot.reblog)}
{:else}
<div class="flex flex-row justify-between">
{@render avatar(toot)}
<a class="flex flex-col items-center hover:underline text-sm text-muted" href={toot.url}>
<time datetime={toot.created_at}>
{new Date(toot.created_at).toLocaleDateString()}
</time>
<time datetime={toot.created_at}>
{new Date(toot.created_at).toLocaleTimeString()}
</time>
</a>
</div>
{@render body(toot)}
{/if}
</li>
{/each}
{/if}
{#if loading}
{#each Array(maxToots ?? 5) as placeholder}
<li class="flex flex-col gap-3 px-4 py-3">
<div class="flex flex-row justify-between">
<div class="flex flex-row gap-2">
<Skeleton class="h-12 w-12 rounded-md" />
<div class="flex flex-col items-start gap-1">
<Skeleton class="h-6 w-24"></Skeleton>
<Skeleton class="h-4 w-20"></Skeleton>
</div>
</div>
<Skeleton class="h-10 w-16" />
</div>
<Skeleton class="h-36 w-full"></Skeleton>
</li>
{/each}
{:else}
{#each toots as toot}
<li class="flex flex-col gap-3 px-4 py-3">
{#if toot.reblog}
<div class="flex flex-row justify-between">
<div class="flex flex-col gap-1">
<a class="flex flex-row items-center gap-1" href={toot.account.url}>
<DoubleArrowUp />
<img
class="rounded-md"
width="23px"
height="23px"
src={toot.account.avatar}
alt="{toot.account.username} avatar"
/>
<span class="h-6 font-bold hover:underline">{toot.account.display_name}</span>
</a>
{@render avatar(toot.reblog)}
</div>
<a
class="flex flex-col items-center text-sm text-muted hover:underline"
href={toot.url}
>
<time datetime={toot.created_at}>
{new Date(toot.created_at).toLocaleDateString()}
</time>
<time datetime={toot.created_at}>
{new Date(toot.created_at).toLocaleTimeString()}
</time>
</a>
</div>
{@render body(toot.reblog)}
{:else}
<div class="flex flex-row justify-between">
{@render avatar(toot)}
<a
class="flex flex-col items-center text-sm text-muted hover:underline"
href={toot.url}
>
<time datetime={toot.created_at}>
{new Date(toot.created_at).toLocaleDateString()}
</time>
<time datetime={toot.created_at}>
{new Date(toot.created_at).toLocaleTimeString()}
</time>
</a>
</div>
{@render body(toot)}
{/if}
</li>
{/each}
{/if}
</ol>

View file

@ -1,4 +1,4 @@
import Root from "./separator.svelte";
import Root from './separator.svelte';
export {
Root,

View file

@ -1,19 +1,19 @@
<script lang="ts">
import { Separator as SeparatorPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
import { Separator as SeparatorPrimitive } from 'bits-ui';
import { cn } from '$lib/utils';
type $$Props = SeparatorPrimitive.Props;
let className: $$Props["class"] = undefined;
export let orientation: $$Props["orientation"] = "horizontal";
export let decorative: $$Props["decorative"] = undefined;
let className: $$Props['class'] = undefined;
export let orientation: $$Props['orientation'] = 'horizontal';
export let decorative: $$Props['decorative'] = undefined;
export { className as class };
</script>
<SeparatorPrimitive.Root
class={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
'shrink-0 bg-border',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className
)}
{orientation}

View file

@ -1,4 +1,4 @@
import { writable } from 'svelte/store';
import {io} from "socket.io-client";
import { io } from 'socket.io-client';
export let socketStore = writable(io('https://status.neshweb.net/'));

View file

@ -1,4 +1,4 @@
import {type Writable, writable} from 'svelte/store';
import type {Heartbeat} from "$lib/types/uptime-kuma-types";
import { type Writable, writable } from 'svelte/store';
import type { Heartbeat } from '$lib/types/uptime-kuma-types';
export let uptimeStore: Writable<Map<number, Heartbeat>> = writable(new Map());

View file

@ -3,18 +3,20 @@
<script>
import '../app.pcss';
import Header from './Header.svelte';
import { socketStore } from "$lib/stores/socketStore";
import {beforeNavigate} from "$app/navigation";
import { socketStore } from '$lib/stores/socketStore';
import { beforeNavigate } from '$app/navigation';
$effect(() => {
beforeNavigate((navigation) => {
const servers = navigation.to.url.pathname === "/servers" || navigation.from.url.pathname === "/servers"
const services = navigation.to.url.pathname === "/services" || navigation.from.url.pathname === "/services"
if ( ! (servers && services) ) {
$socketStore.close()
const servers =
navigation.to.url.pathname === '/servers' || navigation.from.url.pathname === '/servers';
const services =
navigation.to.url.pathname === '/services' || navigation.from.url.pathname === '/services';
if (!(servers && services)) {
$socketStore.close();
}
})
})
});
});
</script>
<Header />

View file

@ -1,7 +1,7 @@
<script lang="ts">
import {Separator} from "$lib/components/ui/separator";
import {OpenInNewWindow} from "radix-icons-svelte";
import Emfed from "$lib/components/Emfed.svelte";
import { Separator } from '$lib/components/ui/separator';
import { OpenInNewWindow } from 'radix-icons-svelte';
import Emfed from '$lib/components/Emfed.svelte';
</script>
<svelte:head>
@ -9,32 +9,55 @@
<meta name="description" content="Landing Page for neshweb.net" />
</svelte:head>
<div class="grid max-h-full grid-cols-3 gap-4 justify-center overflow-auto p-8">
<div class="flex flex-col items-center col-span-2">
<div class="flex flex-col border rounded-md p-4 bg-black/55 backdrop-blur-sm gap-y-2">
<h1 class="text-2xl text-center" >Home Page</h1>
<p>I'm not sure what to put here quite yet, maybe I'll think of something eventually. In the meantime I've linked some of my accounts in the sidebar to the right</p>
<div class="grid max-h-full grid-cols-3 justify-center gap-4 overflow-auto p-8">
<div class="col-span-2 flex flex-col items-center">
<div class="flex flex-col gap-y-2 rounded-md border bg-black/55 p-4 backdrop-blur-sm">
<h1 class="text-center text-2xl">Home Page</h1>
<p>
I'm not sure what to put here quite yet, maybe I'll think of something eventually. In the
meantime I've linked some of my accounts in the sidebar to the right
</p>
</div>
</div>
<div class="flex flex-col gap-y-1 items-center border rounded-md bg-black/55 backdrop-blur-sm py-1">
<div
class="flex flex-col items-center gap-y-1 rounded-md border bg-black/55 py-1 backdrop-blur-sm"
>
<p class="font-bold">Fediverse Accounts</p>
<Separator class="max-w-80" />
<a rel="me" href="https://mastodon.neshweb.net/@neshura" target="_blank" class="flex flex-row gap-1 items-center hover:text-secondary">
<a
rel="me"
href="https://mastodon.neshweb.net/@neshura"
target="_blank"
class="flex flex-row items-center gap-1 hover:text-secondary"
>
Mastodon
<OpenInNewWindow />
</a>
<a rel="noopener noreferrer" href="https://bookwormstory.social/u/Neshura" target="_blank" class="flex flex-row gap-1 items-center hover:text-secondary">
<a
rel="noopener noreferrer"
href="https://bookwormstory.social/u/Neshura"
target="_blank"
class="flex flex-row items-center gap-1 hover:text-secondary"
>
Lemmy
<OpenInNewWindow />
</a>
<a rel="noopener noreferrer" href="https://neshweb.tv/c/neshura_ch/videos" target="_blank" class="flex flex-row gap-1 items-center hover:text-secondary">
<a
rel="noopener noreferrer"
href="https://neshweb.tv/c/neshura_ch/videos"
target="_blank"
class="flex flex-row items-center gap-1 hover:text-secondary"
>
PeerTube
<OpenInNewWindow />
</a>
<Separator class="max-w-80" />
<p class="font-bold">Mastodon Feed</p>
<Separator class="max-w-80" />
<Emfed account="https://mastodon.neshweb.net/@neshura" maxToots={4} accountId="109199738141333007"/>
<Emfed
account="https://mastodon.neshweb.net/@neshura"
maxToots={4}
accountId="109199738141333007"
/>
</div>
</div>

View file

@ -16,4 +16,3 @@
{version}
</a>
</p>

View file

@ -1,6 +1,6 @@
<svelte:head>
<title>CSS Test</title>
<meta name="description" content="CSS playground" />
<title>CSS Test</title>
<meta name="description" content="CSS playground" />
</svelte:head>
<p class="text-background">Background</p>

View file

@ -6,7 +6,7 @@
import type { Heartbeat } from '$lib/types/uptime-kuma-types';
import ServerCard from '$lib/components/ServerCard.svelte';
import { socketStore } from '$lib/stores/socketStore';
import {uptimeStore} from "$lib/stores/uptimeStore";
import { uptimeStore } from '$lib/stores/uptimeStore';
let { data }: { data: { promise: Promise<string> } } = $props();
@ -30,12 +30,11 @@
if (token) {
if (!socket.connected) {
socket.connect();
}
else {
console.log("already connected");
} else {
console.log('already connected');
}
socket.on('connect', () => {
console.log("logging in")
console.log('logging in');
socket.emit('loginByToken', token, () => {});
});

View file

@ -5,9 +5,9 @@
import type { Service } from '$lib/types/data-types';
import { io } from 'socket.io-client';
import type { Heartbeat } from '$lib/types/uptime-kuma-types';
import {socketStore} from "$lib/stores/socketStore";
import {onDestroy} from "svelte";
import {uptimeStore} from "$lib/stores/uptimeStore";
import { socketStore } from '$lib/stores/socketStore';
import { onDestroy } from 'svelte';
import { uptimeStore } from '$lib/stores/uptimeStore';
let { data }: { data: { promise: Promise<string> } } = $props();
@ -29,12 +29,11 @@
if (token) {
if (!socket.connected) {
socket.connect();
}
else {
console.log("connected")
} else {
console.log('connected');
}
socket.on('connect', () => {
console.log("login")
console.log('login');
socket.emit('loginByToken', token, () => {});
});