you know what let's just change frontend framework again
This commit is contained in:
parent
c8cd483d20
commit
0d47f1fb01
115 changed files with 4407 additions and 10824 deletions
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { t } from "$lib/i18n";
|
||||
|
||||
type Props = { memberName?: string; editLink: string };
|
||||
let { memberName, editLink }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="alert alert-secondary">
|
||||
{#if memberName}
|
||||
{$t("profile.edit-member-profile-notice", { memberName })}
|
||||
{:else}
|
||||
{$t("profile.edit-user-profile-notice")}
|
||||
{/if}
|
||||
<br />
|
||||
<a href={editLink}>{$t("profile.edit-profile-link")}</a>
|
||||
</div>
|
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts">
|
||||
import type { CustomPreference, Member, User } from "$api/models";
|
||||
import ProfileField from "./field/ProfileField.svelte";
|
||||
import { t } from "$lib/i18n";
|
||||
|
||||
type Props = { profile: User | Member; allPreferences: Record<string, CustomPreference> };
|
||||
let { profile, allPreferences }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3">
|
||||
{#if profile.names.length > 0}
|
||||
<ProfileField name={$t("profile.names-header")} entries={profile.names} {allPreferences} />
|
||||
{/if}
|
||||
{#if profile.pronouns.length > 0}
|
||||
<ProfileField
|
||||
name={$t("profile.pronouns-header")}
|
||||
entries={profile.pronouns}
|
||||
{allPreferences}
|
||||
/>
|
||||
{/if}
|
||||
{#each profile.fields as field}
|
||||
{#if field.entries.length > 0}
|
||||
<ProfileField name={field.name} entries={field.entries} {allPreferences} />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts">
|
||||
import type { PrideFlag } from "$api/models/user";
|
||||
import { Tooltip } from "@sveltestrap/sveltestrap";
|
||||
|
||||
type Props = { flag: PrideFlag };
|
||||
let { flag }: Props = $props();
|
||||
|
||||
// svelte-ignore non_reactive_update
|
||||
let elem: HTMLImageElement;
|
||||
</script>
|
||||
|
||||
<span class="mx-2 my-1">
|
||||
<Tooltip target={elem} aria-hidden placement="top">{flag.description ?? flag.name}</Tooltip>
|
||||
<img bind:this={elem} class="flag" src={flag.image_url} alt={flag.description ?? flag.name} />
|
||||
{flag.name}
|
||||
</span>
|
||||
|
||||
<style>
|
||||
.flag {
|
||||
height: 1.5rem;
|
||||
max-width: 200px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,69 @@
|
|||
<script lang="ts">
|
||||
import type { User, Member } from "$api/models";
|
||||
import { t } from "$lib/i18n";
|
||||
import { renderMarkdown } from "$lib/markdown";
|
||||
import ProfileLink from "./ProfileLink.svelte";
|
||||
import ProfileFlag from "./ProfileFlag.svelte";
|
||||
import Avatar from "$components/Avatar.svelte";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
profile: User | Member;
|
||||
lazyLoadAvatar?: boolean;
|
||||
};
|
||||
|
||||
let { name, profile, lazyLoadAvatar }: Props = $props();
|
||||
|
||||
// renderMarkdown sanitizes the output HTML for us
|
||||
let bio = $derived(renderMarkdown(profile.bio));
|
||||
</script>
|
||||
|
||||
<div class="grid row-gap-3">
|
||||
<div class="row">
|
||||
<div class="col-md-4 text-center">
|
||||
<Avatar
|
||||
url={profile.avatar_url}
|
||||
alt={$t("avatar-tooltip", { name })}
|
||||
lazyLoad={lazyLoadAvatar}
|
||||
/>
|
||||
<!-- Flags show up below the avatar if the profile has a bio, otherwise they show up below the row entirely -->
|
||||
{#if profile.flags && profile.bio}
|
||||
<div class="d-flex flex-wrap m-4">
|
||||
{#each profile.flags as flag}
|
||||
<ProfileFlag {flag} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="col-md">
|
||||
{#if profile.display_name}
|
||||
<div>
|
||||
<h2>{profile.display_name}</h2>
|
||||
<p class="fs-5 text-body-secondary">{name}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<h2>{name}</h2>
|
||||
{/if}
|
||||
{#if bio}
|
||||
<hr />
|
||||
<p>{@html bio}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if profile.links.length > 0}
|
||||
<div class="col-md d-flex align-items-center">
|
||||
<ul class="list-unstyled">
|
||||
{#each profile.links as link}
|
||||
<ProfileLink {link} />
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if profile.flags && !profile.bio}
|
||||
<div class="d-flex flex-wrap m-4">
|
||||
{#each profile.flags as flag}
|
||||
<ProfileFlag {flag} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
|
@ -0,0 +1,33 @@
|
|||
<script lang="ts">
|
||||
import { Icon } from "@sveltestrap/sveltestrap";
|
||||
|
||||
type Props = { link: string };
|
||||
let { link }: Props = $props();
|
||||
|
||||
const prettifyLink = (raw: string) => {
|
||||
let out = raw;
|
||||
if (raw.startsWith("https://")) out = raw.substring("https://".length);
|
||||
else if (raw.startsWith("http://")) out = raw.substring("http://".length);
|
||||
|
||||
if (raw.endsWith("/")) out = raw.substring(0, raw.length - 1);
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
let isLink = $derived(link.startsWith("http://") || link.startsWith("https://"));
|
||||
let displayLink = $derived(prettifyLink(link));
|
||||
</script>
|
||||
|
||||
{#if isLink}
|
||||
<a href={link} class="text-decoration-none" rel="me nofollow noreferrer" target="_blank">
|
||||
<li class="py-2 py-lg-0">
|
||||
<Icon name="globe" aria-hidden class="text-body" />
|
||||
<span class="text-decoration-underline">{displayLink}</span>
|
||||
</li>
|
||||
</a>
|
||||
{:else}
|
||||
<li class="py-2 py-lg-0">
|
||||
<Icon name="globe" aria-hidden />
|
||||
<span>{displayLink}</span>
|
||||
</li>
|
||||
{/if}
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts">
|
||||
import type { CustomPreference, FieldEntry, Pronoun } from "$api/models";
|
||||
import ProfileFieldEntry from "./ProfileFieldEntry.svelte";
|
||||
import PronounLink from "./PronounLink.svelte";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
entries: Array<FieldEntry | Pronoun>;
|
||||
allPreferences: Record<string, CustomPreference>;
|
||||
isCol?: boolean;
|
||||
};
|
||||
let { name, entries, allPreferences, isCol }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class:col={isCol === false}>
|
||||
<h3>{name}</h3>
|
||||
<ul class="list-unstyled fs-5">
|
||||
{#each entries as entry}
|
||||
<li>
|
||||
<ProfileFieldEntry status={entry.status} {allPreferences}>
|
||||
{#if "display_text" in entry}
|
||||
<PronounLink pronouns={entry} />
|
||||
{:else}
|
||||
{entry.value}
|
||||
{/if}
|
||||
</ProfileFieldEntry>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts">
|
||||
import { defaultPreferences, PreferenceSize, type CustomPreference } from "$api/models";
|
||||
import StatusIcon from "$components/StatusIcon.svelte";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
status: string;
|
||||
allPreferences: Record<string, CustomPreference>;
|
||||
children: Snippet;
|
||||
};
|
||||
let { status, allPreferences, children }: Props = $props();
|
||||
|
||||
let preference = $derived(
|
||||
status in allPreferences ? allPreferences[status] : defaultPreferences.missing,
|
||||
);
|
||||
|
||||
let elemType = $derived(preference.size === PreferenceSize.Large ? "strong" : "span");
|
||||
</script>
|
||||
|
||||
<svelte:element
|
||||
this={elemType}
|
||||
class:text-muted={preference.muted}
|
||||
class:fs-5={preference.size === PreferenceSize.Large}
|
||||
class:fs-6={preference.size === PreferenceSize.Small}
|
||||
>
|
||||
<StatusIcon {preference} />
|
||||
{@render children?.()}
|
||||
</svelte:element>
|
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts">
|
||||
import type { Pronoun } from "$api/models/user";
|
||||
|
||||
type Props = { pronouns: Pronoun };
|
||||
let { pronouns }: Props = $props();
|
||||
|
||||
// TODO: this entire component is only made with English pronouns in mind.
|
||||
// It's gonna need a major rework to work with other languages.
|
||||
const updatePronouns = (pronouns: Pronoun) => {
|
||||
if (pronouns.display_text) {
|
||||
return pronouns.display_text;
|
||||
} else {
|
||||
const split = pronouns.value.split("/");
|
||||
if (split.length === 5) return split.splice(0, 2).join("/");
|
||||
return pronouns.value;
|
||||
}
|
||||
};
|
||||
|
||||
const linkPronouns = (pronouns: Pronoun) => {
|
||||
const linkBase = pronouns.value
|
||||
.split("/")
|
||||
.map((snippet) => encodeURIComponent(snippet))
|
||||
.join("/");
|
||||
|
||||
if (pronouns.display_text) {
|
||||
return `${linkBase},${encodeURIComponent(pronouns.display_text)}`;
|
||||
}
|
||||
return linkBase;
|
||||
};
|
||||
|
||||
let pronounText = $derived(updatePronouns(pronouns));
|
||||
|
||||
let link = $derived(linkPronouns(pronouns));
|
||||
let shouldLink = $derived(pronouns.value.split("/").length === 5);
|
||||
</script>
|
||||
|
||||
{#if shouldLink}
|
||||
<a class="text-reset" href="/pronouns/{link}">{pronounText}</a>
|
||||
{:else}
|
||||
{pronounText}
|
||||
{/if}
|
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts">
|
||||
import type { CustomPreference, PartialMember } from "$api/models";
|
||||
import Avatar from "$components/Avatar.svelte";
|
||||
import { t } from "$lib/i18n";
|
||||
|
||||
type Props = {
|
||||
username: string;
|
||||
member: PartialMember;
|
||||
allPreferences: Record<string, CustomPreference>;
|
||||
};
|
||||
let { username, member, allPreferences }: Props = $props();
|
||||
|
||||
const getPronouns = (member: PartialMember) => {
|
||||
const filteredPronouns = member.pronouns.filter(
|
||||
(entry) => (allPreferences[entry.status] || { favourite: false }).favourite,
|
||||
);
|
||||
if (filteredPronouns.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return filteredPronouns
|
||||
.map((pronouns) => {
|
||||
if (pronouns.display_text) {
|
||||
return pronouns.display_text;
|
||||
} else {
|
||||
const split = pronouns.value.split("/");
|
||||
if (split.length === 5) return split.splice(0, 2).join("/");
|
||||
return pronouns.value;
|
||||
}
|
||||
})
|
||||
.join(", ");
|
||||
};
|
||||
|
||||
let pronouns = $derived(getPronouns(member));
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<a href="/@{username}/{member.name}">
|
||||
<Avatar url={member.avatar_url} lazyLoad alt={$t("avatar-tooltip", { name: member.name })} />
|
||||
</a>
|
||||
<p class="m-2">
|
||||
<a class="text-reset fs-5 text-break" href="/@{username}/{member.name}">
|
||||
{member.name}
|
||||
</a>
|
||||
{#if pronouns}
|
||||
<br />
|
||||
{pronouns}
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
Loading…
Add table
Add a link
Reference in a new issue