even more frontend stuff

This commit is contained in:
sam 2024-11-25 17:35:24 +01:00
parent 8bba5f6137
commit c0bb76580d
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
33 changed files with 796 additions and 178 deletions

View file

@ -1,22 +1,23 @@
<script lang="ts">
import { DEFAULT_AVATAR } from "$lib";
type Props = { url: string | null; alt: string; lazyLoad?: boolean };
let { url, alt, lazyLoad }: Props = $props();
type Props = { url: string | null; alt: string; lazyLoad?: boolean; size?: number };
let { url, alt, lazyLoad, size }: Props = $props();
let width = $derived(size || 200);
</script>
<img
class="rounded-circle img-fluid"
style="height: {width}px; width: {width}px"
src={url || DEFAULT_AVATAR}
{alt}
width={200}
{width}
loading={lazyLoad ? "lazy" : "eager"}
/>
<style>
img {
object-fit: cover;
height: 200px;
width: 200px;
}
</style>

View file

@ -0,0 +1,35 @@
<script lang="ts">
import { Pagination, PaginationItem, PaginationLink } from "@sveltestrap/sveltestrap";
type Props = { currentPage: number; pageCount: number; href: string; center?: boolean };
let { currentPage, pageCount, href, center }: Props = $props();
let prevPage = $derived(currentPage > 0 ? currentPage - 1 : 0);
let prevLink = $derived(prevPage !== 0 ? `${href}?page=${prevPage}` : href);
let nextPage = $derived(currentPage < pageCount - 1 ? currentPage + 1 : pageCount - 1);
</script>
{#if pageCount > 1}
<div>
<Pagination listClassName={center ? "justify-content-center" : undefined}>
<PaginationItem>
<PaginationLink first {href} />
</PaginationItem>
<PaginationItem>
<PaginationLink previous href={prevLink} />
</PaginationItem>
{#each new Array(pageCount) as _, page}
<PaginationItem active={page === currentPage}>
<PaginationLink href="{href}?page={page}">{page + 1}</PaginationLink>
</PaginationItem>
{/each}
<PaginationItem>
<PaginationLink next href="{href}?page={nextPage}" />
</PaginationItem>
<PaginationItem>
<PaginationLink last href="{href}?page={pageCount - 1}" />
</PaginationItem>
</Pagination>
</div>
{/if}

View file

@ -4,14 +4,15 @@
import { Icon, InputGroup } from "@sveltestrap/sveltestrap";
import { encode } from "base64-arraybuffer";
import prettyBytes from "pretty-bytes";
import ShortNoscriptWarning from "./ShortNoscriptWarning.svelte";
type Props = {
current: string | null;
alt: string;
onclick: (avatar: string) => Promise<void>;
update: (avatar: string) => Promise<void>;
updated: boolean;
};
let { current, alt, onclick, updated }: Props = $props();
let { current, alt, update: onclick, updated }: Props = $props();
const MAX_AVATAR_BYTES = 1_000_000;
@ -59,6 +60,8 @@
</button>
</InputGroup>
<ShortNoscriptWarning />
{#if updated}
<p class="text-success-emphasis">
<Icon name="check-circle-fill" />

View file

@ -0,0 +1,26 @@
<script lang="ts">
import { t } from "$lib/i18n";
import { renderMarkdown } from "$lib/markdown";
type Props = { value: string; maxLength: number };
let { value = $bindable(), maxLength }: Props = $props();
</script>
<textarea name="bio" class="form-control" style="height: 200px;" bind:value></textarea>
<button disabled={value.length > maxLength} type="submit" class="btn btn-primary mt-2 my-1">
{$t("save-changes")}
</button>
<p class="text-muted mt-1">
{$t("edit-profile.bio-length-hint", {
length: value.length,
maxLength,
})}
</p>
{#if value !== ""}
<div class="card">
<div class="card-header">{$t("edit-profile.preview")}</div>
<div class="card-body">{@html renderMarkdown(value)}</div>
</div>
{/if}

View file

@ -0,0 +1,12 @@
<script lang="ts">
import { t } from "$lib/i18n";
</script>
<noscript>
<div class="alert alert-secondary">
<h4>{$t("error.noscript-title")}</h4>
<p>
{$t("error.noscript-info")}
</p>
</div>
</noscript>

View file

@ -0,0 +1,11 @@
<script lang="ts">
import { t } from "$lib/i18n";
import { Icon } from "@sveltestrap/sveltestrap";
</script>
<noscript>
<p class="text-danger-emphasis">
<Icon name="exclamation-circle-fill" aria-hidden />
{$t("error.noscript-short")}
</p>
</noscript>

View file

@ -0,0 +1,30 @@
<script lang="ts">
import { PUBLIC_SHORT_URL } from "$env/static/public";
import { t } from "$lib/i18n";
import { ButtonGroup, Button, Icon } from "@sveltestrap/sveltestrap";
import ShortNoscriptWarning from "./ShortNoscriptWarning.svelte";
type Props = { sid: string; rerollSid: () => Promise<void>; canRerollSid: boolean };
let { sid, rerollSid, canRerollSid }: Props = $props();
const copySid = async () => {
const url = `${PUBLIC_SHORT_URL}/${sid}`;
await navigator.clipboard.writeText(url);
};
</script>
{$t("edit-profile.sid-current")} <code>{sid}</code>
<ButtonGroup class="mb-1">
<Button color="secondary" onclick={() => rerollSid()} disabled={!canRerollSid}>
{$t("edit-profile.sid-reroll")}
</Button>
<Button color="secondary" onclick={() => copySid()}>
<Icon name="link-45deg" aria-hidden />
<span class="visually-hidden">{$t("edit-profile.sid-copy")}</span>
</Button>
</ButtonGroup>
<ShortNoscriptWarning />
<p class="text-muted">
<Icon name="info-circle-fill" aria-hidden />
{$t("edit-profile.sid-hint")}
</p>

View file

@ -35,11 +35,15 @@
<div>
<a href="/@{username}/{member.name}">
<Avatar url={member.avatar_url} lazyLoad alt={$t("avatar-tooltip", { name: member.name })} />
<Avatar
url={member.avatar_url}
lazyLoad
alt={$t("avatar-tooltip", { name: member.display_name })}
/>
</a>
<p class="m-2">
<a class="text-reset fs-5 text-break" href="/@{username}/{member.name}">
{member.name}
{member.display_name}
</a>
{#if pronouns}
<br />