Add Mastodon Feed to Home Page + Mastodon Embed component + Adjust Muted color
This commit is contained in:
parent
d054e2ec41
commit
47a2afafc1
4 changed files with 184 additions and 7 deletions
|
@ -71,7 +71,7 @@
|
||||||
--background: 0 0% 0%; /* #000000 */
|
--background: 0 0% 0%; /* #000000 */
|
||||||
--foreground: 183 100% 96%; /* #14b8a6 */
|
--foreground: 183 100% 96%; /* #14b8a6 */
|
||||||
|
|
||||||
--muted: 183 100% 96%; /* #ecfeff */
|
--muted: 180 10% 66%; /* #ecfeff */
|
||||||
--muted-foreground: 176 61% 19%; /* #134e4a */
|
--muted-foreground: 176 61% 19%; /* #134e4a */
|
||||||
|
|
||||||
--popover: 0 0% 0%; /* #000000 */
|
--popover: 0 0% 0%; /* #000000 */
|
||||||
|
|
173
src/lib/components/Emfed.svelte
Normal file
173
src/lib/components/Emfed.svelte
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
<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";
|
||||||
|
|
||||||
|
let { account, maxToots, accountId, excludeReplies }: { account: string, maxToots?: number, accountId?: string, excludeReplies: boolean } = $props();
|
||||||
|
|
||||||
|
let toots: Toot[] = $state([]);
|
||||||
|
let loading = $state(false);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
// 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}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await (await fetch(tootURL)).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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>
|
||||||
|
{/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>
|
||||||
|
{/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}
|
||||||
|
</ol>
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import {Separator} from "$lib/components/ui/separator";
|
import {Separator} from "$lib/components/ui/separator";
|
||||||
import {OpenInNewWindow} from "radix-icons-svelte";
|
import {OpenInNewWindow} from "radix-icons-svelte";
|
||||||
|
import Emfed from "$lib/components/Emfed.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -32,8 +33,8 @@ import {OpenInNewWindow} from "radix-icons-svelte";
|
||||||
<OpenInNewWindow />
|
<OpenInNewWindow />
|
||||||
</a>
|
</a>
|
||||||
<Separator class="max-w-80" />
|
<Separator class="max-w-80" />
|
||||||
<p class="font-bold">Subsection</p>
|
<p class="font-bold">Mastodon Feed</p>
|
||||||
<Separator class="max-w-80" />
|
<Separator class="max-w-80" />
|
||||||
<p>Link 4</p>
|
<Emfed account="https://mastodon.neshweb.net/@neshura" maxToots={4} accountId="109199738141333007"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { fontFamily } from "tailwindcss/defaultTheme";
|
||||||
const config = {
|
const config = {
|
||||||
darkMode: ["class"],
|
darkMode: ["class"],
|
||||||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||||
safelist: ["dark"],
|
safelist: ["dark", "nordlys"],
|
||||||
theme: {
|
theme: {
|
||||||
container: {
|
container: {
|
||||||
center: true,
|
center: true,
|
||||||
|
@ -68,6 +68,9 @@ const config = {
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: [...fontFamily.sans]
|
sans: [...fontFamily.sans]
|
||||||
|
},
|
||||||
|
aspectRatio: {
|
||||||
|
'16/9': '16 / 9',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue