init
This commit is contained in:
commit
d761a10bf7
102 changed files with 4761 additions and 0 deletions
49
lib/components/bio/UserPanel.svelte
Normal file
49
lib/components/bio/UserPanel.svelte
Normal file
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts">
|
||||
import Description from './profile/Description.svelte';
|
||||
import Footer from './profile/Footer.svelte';
|
||||
import Name from './profile/Name.svelte';
|
||||
import Panel from './elements/Panel.svelte';
|
||||
import Socials from './profile/Socials.svelte';
|
||||
import Avatar from './profile/Avatar.svelte';
|
||||
import Extra from './profile/Extra.svelte';
|
||||
|
||||
export let displayName: string;
|
||||
export let username: string;
|
||||
export let badges: BioSiteBadge[];
|
||||
export let description: string | null;
|
||||
export let socials: BioSiteSocials | undefined = undefined;
|
||||
export let uid: number;
|
||||
export let uniqueId: string;
|
||||
export let views: number | null;
|
||||
export let align: BioSiteAlign;
|
||||
export let location: string | undefined = undefined;
|
||||
export let school: string | undefined = undefined;
|
||||
export let workplace: string | undefined = undefined;
|
||||
|
||||
export let preview: boolean = false;
|
||||
export let placeholder: boolean = false;
|
||||
|
||||
export let loadTime: number | undefined = undefined;
|
||||
export let previewUid: boolean | undefined = undefined;
|
||||
export let storelessSocials: boolean = false;
|
||||
|
||||
$: uidLocal = uid === -1 ? undefined : uid;
|
||||
</script>
|
||||
|
||||
<Panel {preview}>
|
||||
<div class="flex flex-col gap-4">
|
||||
<Avatar {uniqueId} {align} {loadTime} />
|
||||
|
||||
<Name identifier={displayName} secondaryIdentifier={`@${username}`} {badges} {align} {placeholder} />
|
||||
|
||||
<Description {description} />
|
||||
|
||||
<Extra {location} {school} {workplace} />
|
||||
|
||||
<Socials {socials} {storelessSocials} />
|
||||
|
||||
<slot />
|
||||
|
||||
<Footer uid={previewUid === undefined ? uid : previewUid === true ? uidLocal ?? -2 : undefined} {views} />
|
||||
</div>
|
||||
</Panel>
|
12
lib/components/bio/elements/Banner.svelte
Normal file
12
lib/components/bio/elements/Banner.svelte
Normal file
|
@ -0,0 +1,12 @@
|
|||
<script lang="ts">
|
||||
export let banner: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<div class="w-full h-80 bg-center bg-cover md:rounded-[32px] shadow" style="--banner: url({banner ?? ''})" />
|
||||
|
||||
<style lang="postcss">
|
||||
div {
|
||||
@apply bg-accent;
|
||||
background-image: var(--banner);
|
||||
}
|
||||
</style>
|
40
lib/components/bio/elements/BannerContainer.svelte
Normal file
40
lib/components/bio/elements/BannerContainer.svelte
Normal file
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts">
|
||||
import Banner from './Banner.svelte';
|
||||
import { getBioBanner } from '$lib/config';
|
||||
import { editorStore } from '$lib/stores/editor';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
export let loadTime: number | undefined = undefined;
|
||||
export let bioId: number | undefined = undefined;
|
||||
export let simple: boolean = false;
|
||||
|
||||
$: noBanner =
|
||||
($page.data.bio?.hasBanner !== true && $editorStore.banner == null) ||
|
||||
$editorStore.edits.edits.deleteBanner != null;
|
||||
|
||||
$: isEditor = $page.url.pathname.startsWith('/dashboard');
|
||||
$: banner = noBanner
|
||||
? undefined
|
||||
: $editorStore.banner
|
||||
? $editorStore.banner
|
||||
: bioId
|
||||
? getBioBanner(bioId, isEditor ? loadTime : undefined)
|
||||
: undefined;
|
||||
</script>
|
||||
|
||||
<div class="w-full grid">
|
||||
{#if !simple}
|
||||
<div class="w-full blur-[70px] opacity-40 dark:opacity-80 transition-opacity">
|
||||
<Banner {banner} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="w-full z-[1]">
|
||||
<Banner {banner} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
div > div {
|
||||
@apply col-start-1 row-start-1;
|
||||
}
|
||||
</style>
|
40
lib/components/bio/elements/InteractivePanel.svelte
Normal file
40
lib/components/bio/elements/InteractivePanel.svelte
Normal file
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts">
|
||||
import { editorStore } from '$lib/stores/editor';
|
||||
import { faExternalLink } from '@fortawesome/free-solid-svg-icons';
|
||||
import Fa from 'svelte-fa';
|
||||
import Panel from './Panel.svelte';
|
||||
|
||||
export let preview: boolean = false;
|
||||
export let handle: boolean = false;
|
||||
export let url: string | undefined = undefined;
|
||||
export let title: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<a
|
||||
class="panel cursor-pointer rounded-3xl hover:bg-dark-hover-ui-element-bg/50 dark:hover:bg-light-hover-ui-element-bg/50 active:bg-dark-active-ui-element-bg/50 dark:active:bg-light-active-ui-element-bg/50 transition-colors box-content"
|
||||
href={!$editorStore.editMode ? url : undefined}
|
||||
target="_blank">
|
||||
<Panel {handle} {preview}>
|
||||
<div class="flex gap-1" class:title>
|
||||
<div class="flex-grow" class:min-w-0={!title}>
|
||||
<slot />
|
||||
</div>
|
||||
<div class="contents text-text-clickable text-xs">
|
||||
{#if title}
|
||||
<p class="whitespace-nowrap leading-[.8] text-base font-normal">{title}</p>
|
||||
{/if}
|
||||
<Fa icon={faExternalLink} />
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</a>
|
||||
|
||||
<style lang="postcss">
|
||||
div.title {
|
||||
@apply flex-col-reverse gap-0;
|
||||
}
|
||||
|
||||
div.title > div:last-of-type {
|
||||
@apply flex w-full justify-between items-center mb-5;
|
||||
}
|
||||
</style>
|
36
lib/components/bio/elements/Panel.svelte
Normal file
36
lib/components/bio/elements/Panel.svelte
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script lang="ts">
|
||||
import Handle from '$lib/components/dashboard/editor/Handle.svelte';
|
||||
import { editorStore } from '$lib/stores/editor';
|
||||
|
||||
export let preview: boolean = false;
|
||||
export let handle: boolean = false;
|
||||
export let fit: boolean = false;
|
||||
export let full: boolean = false;
|
||||
export let invisible: boolean = false;
|
||||
|
||||
$: hasHandle = handle ? $editorStore.editMode : false;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="panel flex-shrink-0 flex"
|
||||
class:w-fit={fit}
|
||||
class:w-full={full}
|
||||
class:visible={!invisible}
|
||||
class:widget-preview={preview}>
|
||||
{#if hasHandle}
|
||||
<Handle pad={true} />
|
||||
{/if}
|
||||
<div class="panel-content w-full h-fit min-w-0" class:!pl-0={hasHandle} class:-ml-6={hasHandle && invisible}>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.panel.visible {
|
||||
@apply bg-widget-fill rounded-3xl border border-widget-stroke backdrop-blur-xl transition-colors text-text-header transform-gpu;
|
||||
}
|
||||
|
||||
.panel.visible .panel-content {
|
||||
@apply p-6;
|
||||
}
|
||||
</style>
|
13
lib/components/bio/elements/WidgetRenderContainer.svelte
Normal file
13
lib/components/bio/elements/WidgetRenderContainer.svelte
Normal file
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts">
|
||||
import Name from '../profile/Name.svelte';
|
||||
|
||||
export let hasPreview: boolean = true;
|
||||
</script>
|
||||
|
||||
{#if hasPreview}
|
||||
<slot />
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
<Name identifier={'Loading widget...'} secondaryIdentifier={'Loading...'} placeholder={true} />
|
||||
</div>
|
||||
{/if}
|
52
lib/components/bio/profile/Avatar.svelte
Normal file
52
lib/components/bio/profile/Avatar.svelte
Normal file
|
@ -0,0 +1,52 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { getUserAvatar } from '$lib/config';
|
||||
import { editorStore } from '$lib/stores/editor';
|
||||
|
||||
export let loadTime: number | undefined = undefined;
|
||||
export let uniqueId: string | undefined = undefined;
|
||||
export let align: BioSiteAlign = 'LEFT';
|
||||
export let small: boolean = false;
|
||||
export let tiny: boolean = false;
|
||||
export let veryTiny: boolean = false;
|
||||
|
||||
$: noAvatar =
|
||||
($page.data.bio?.hasAvatar !== true && $editorStore.avatar == null) ||
|
||||
$editorStore.edits.edits.deleteAvatar != null;
|
||||
|
||||
$: isEditor = $page.url.pathname.startsWith('/dashboard');
|
||||
$: avatar = noAvatar
|
||||
? undefined
|
||||
: $editorStore.avatar
|
||||
? $editorStore.avatar
|
||||
: uniqueId
|
||||
? getUserAvatar(uniqueId, isEditor ? loadTime : undefined)
|
||||
: undefined;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="w-24 h-24 rounded-3xl avatar bg-center bg-cover"
|
||||
class:self-center={align === 'CENTER'}
|
||||
class:self-end={align === 'RIGHT'}
|
||||
class:small
|
||||
class:tiny
|
||||
class:very-tiny={veryTiny}
|
||||
style="--avatar: url({avatar ?? '/assets/default/avatar.svg'})" />
|
||||
|
||||
<style lang="postcss">
|
||||
div.avatar {
|
||||
background-image: var(--avatar);
|
||||
}
|
||||
|
||||
div.small {
|
||||
@apply w-16 h-16 rounded-2xl;
|
||||
}
|
||||
|
||||
div.tiny {
|
||||
@apply w-9 h-9 rounded-full;
|
||||
}
|
||||
|
||||
div.very-tiny {
|
||||
@apply w-6 h-6 rounded-full;
|
||||
}
|
||||
</style>
|
25
lib/components/bio/profile/Badge.svelte
Normal file
25
lib/components/bio/profile/Badge.svelte
Normal file
|
@ -0,0 +1,25 @@
|
|||
<script lang="ts">
|
||||
import badges from '$lib/badges';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
export let badge: BioSiteBadge;
|
||||
|
||||
const badgeObject = badges.find((x) => x.id == badge);
|
||||
</script>
|
||||
|
||||
{#if badgeObject}
|
||||
<a class="relative inline-block" href={badgeObject.link} aria-label={badgeObject.name}>
|
||||
<div class="w-5 h-5 bg-cover bg-center peer" style="--icon: url({badgeObject.icon})" />
|
||||
<div
|
||||
class="relative opacity-0 peer-hover:opacity-100 -translate-y-5 peer-hover:-translate-y-8 transition-all"
|
||||
aria-hidden="true">
|
||||
<Tooltip>{badgeObject.name}</Tooltip>
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
div {
|
||||
background-image: var(--icon);
|
||||
}
|
||||
</style>
|
11
lib/components/bio/profile/Badges.svelte
Normal file
11
lib/components/bio/profile/Badges.svelte
Normal file
|
@ -0,0 +1,11 @@
|
|||
<script lang="ts">
|
||||
import Badge from './Badge.svelte';
|
||||
|
||||
export let badges: BioSiteBadge[];
|
||||
</script>
|
||||
|
||||
<div class="flex gap-0.5 items-center flex-wrap">
|
||||
{#each badges as badge}
|
||||
<Badge {badge} />
|
||||
{/each}
|
||||
</div>
|
7
lib/components/bio/profile/Description.svelte
Normal file
7
lib/components/bio/profile/Description.svelte
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script lang="ts">
|
||||
export let description: string | null | undefined = undefined;
|
||||
</script>
|
||||
|
||||
{#if description}
|
||||
<p class="text-text-primary break-words whitespace-pre-wrap">{description.trim()}</p>
|
||||
{/if}
|
22
lib/components/bio/profile/Extra.svelte
Normal file
22
lib/components/bio/profile/Extra.svelte
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { faBriefcase, faGraduationCap, faLocationPin } from '@fortawesome/free-solid-svg-icons';
|
||||
import ExtraItem from './ExtraItem.svelte';
|
||||
|
||||
export let location: string | undefined = undefined;
|
||||
export let school: string | undefined = undefined;
|
||||
export let workplace: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
{#if location || school || workplace}
|
||||
<div class="flex flex-wrap gap-x-3 gap-y-2">
|
||||
{#if location}
|
||||
<ExtraItem icon={faLocationPin} text={location.trim()} />
|
||||
{/if}
|
||||
{#if school}
|
||||
<ExtraItem icon={faGraduationCap} text={school.trim()} />
|
||||
{/if}
|
||||
{#if workplace}
|
||||
<ExtraItem icon={faBriefcase} text={workplace.trim()} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
12
lib/components/bio/profile/ExtraItem.svelte
Normal file
12
lib/components/bio/profile/ExtraItem.svelte
Normal file
|
@ -0,0 +1,12 @@
|
|||
<script lang="ts">
|
||||
import type { IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
||||
import Fa from 'svelte-fa';
|
||||
|
||||
export let icon: IconDefinition;
|
||||
export let text: string;
|
||||
</script>
|
||||
|
||||
<div class="flex gap-1 text-text-secondary items-center">
|
||||
<Fa {icon} />
|
||||
<p class="break-words line-clamp-1">{text}</p>
|
||||
</div>
|
11
lib/components/bio/profile/Footer.svelte
Normal file
11
lib/components/bio/profile/Footer.svelte
Normal file
|
@ -0,0 +1,11 @@
|
|||
<script lang="ts">
|
||||
export let uid: number | null | undefined = undefined;
|
||||
export let views: number | null | undefined = undefined;
|
||||
</script>
|
||||
|
||||
{#if (uid || views) && uid !== -1}
|
||||
<div class="border-t border-text-secondary pt-1 flex justify-between text-text-secondary text-xs">
|
||||
{#if uid}<p>UID: {uid === -2 ? '??' : uid}</p>{/if}
|
||||
{#if views}<p>VIEWS: {views}</p>{/if}
|
||||
</div>
|
||||
{/if}
|
72
lib/components/bio/profile/Name.svelte
Normal file
72
lib/components/bio/profile/Name.svelte
Normal file
|
@ -0,0 +1,72 @@
|
|||
<script lang="ts">
|
||||
import BadgeContainer from './Badges.svelte';
|
||||
|
||||
export let identifier: string | null | undefined;
|
||||
export let secondaryIdentifier: string | null | undefined;
|
||||
export let badges: BioSiteBadge[] = [];
|
||||
|
||||
export let align: BioSiteAlign = 'LEFT';
|
||||
export let placeholder: boolean = false;
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col" class:placeholder>
|
||||
<div
|
||||
class="primary-id text-xl font-semibold text-text-header flex gap-2 items-center"
|
||||
class:justify-center={align === 'CENTER'}
|
||||
class:justify-end={align === 'RIGHT'}>
|
||||
<p class="break-words line-clamp-1">{(identifier ?? secondaryIdentifier ?? 'Unknown').trim()}</p>
|
||||
{#if placeholder}
|
||||
<div class="placeholder-container" />
|
||||
{/if}
|
||||
{#if badges.length > 0 && !placeholder}
|
||||
<BadgeContainer {badges} />
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class="secondary-id text-text-secondary"
|
||||
class:text-center={align === 'CENTER'}
|
||||
class:text-right={align === 'RIGHT'}>
|
||||
<p class="break-words line-clamp-1">{(!identifier ? '' : secondaryIdentifier || '').trim()}</p>
|
||||
{#if placeholder}
|
||||
<div class="placeholder-container" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
div.placeholder > * {
|
||||
@apply w-fit px-2 overflow-hidden select-none rounded-md relative opacity-50;
|
||||
}
|
||||
|
||||
div.placeholder > * {
|
||||
@apply bg-text-teritary !text-text-teritary;
|
||||
}
|
||||
|
||||
div.placeholder > div.primary-id {
|
||||
@apply mb-[1px];
|
||||
}
|
||||
|
||||
div.placeholder-container {
|
||||
@apply absolute top-0 left-0 bottom-0 right-0;
|
||||
}
|
||||
|
||||
div.placeholder div.placeholder-container {
|
||||
animation: slide 1s infinite;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0.8) 50%,
|
||||
rgba(128, 186, 232, 0) 99%,
|
||||
rgba(125, 185, 232, 0) 100%
|
||||
);
|
||||
}
|
||||
|
||||
@keyframes slide {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
</style>
|
31
lib/components/bio/profile/SocialLink.svelte
Normal file
31
lib/components/bio/profile/SocialLink.svelte
Normal file
|
@ -0,0 +1,31 @@
|
|||
<script lang="ts">
|
||||
import type { Site } from '$lib/models/socials';
|
||||
|
||||
export let hoverInvert: boolean = false;
|
||||
export let minimal: boolean = false;
|
||||
|
||||
export let site: Site | undefined;
|
||||
export let social: BioSiteSocialLink;
|
||||
</script>
|
||||
|
||||
{#if site}
|
||||
<a
|
||||
class="flex-grow"
|
||||
style="--color: {site.color}"
|
||||
href={`${site.type == 'EMAIL' ? 'mailto:' : site.baseUrl}${social.value}`}
|
||||
target="_blank"
|
||||
title={site.name}>
|
||||
<div
|
||||
class="w-full min-w-[95px] h-10 hover:opacity-80 transition-opacity rounded-lg flex justify-center items-center">
|
||||
<img class="w-5 h-5" alt="Icon" src={site.icon} />
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<!-- -->
|
||||
|
||||
<style lang="postcss">
|
||||
div {
|
||||
background: var(--color);
|
||||
}
|
||||
</style>
|
15
lib/components/bio/profile/SocialText.svelte
Normal file
15
lib/components/bio/profile/SocialText.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import Button from '$lib/components/dashboard/elements/Button.svelte';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
|
||||
export let social: BioSiteSocialText;
|
||||
</script>
|
||||
|
||||
<Button
|
||||
title={social.value}
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(social.value);
|
||||
toast.push('Copied to clipboard.');
|
||||
}}>
|
||||
{social.title}
|
||||
</Button>
|
92
lib/components/bio/profile/Socials.svelte
Normal file
92
lib/components/bio/profile/Socials.svelte
Normal file
|
@ -0,0 +1,92 @@
|
|||
<script lang="ts">
|
||||
import { getLinkType } from '$lib/models/socials';
|
||||
import { editorStore } from '$lib/stores/editor';
|
||||
import SocialLink from './SocialLink.svelte';
|
||||
import SocialText from './SocialText.svelte';
|
||||
import { SocialLink as SocialLinkType } from '$lib/models/socials';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let socials: BioSiteSocials | undefined = undefined;
|
||||
export let storelessSocials: boolean = false;
|
||||
|
||||
$: socialLinks = (
|
||||
[
|
||||
...(socials?.links ?? [])
|
||||
.map((x) => {
|
||||
const remove = $editorStore.edits.edits.deleteSocialLinks.map((x) => x.id).findIndex((y) => y === x.id);
|
||||
if (remove !== -1) return null;
|
||||
|
||||
return x;
|
||||
})
|
||||
.filter((x) => x),
|
||||
...$editorStore.edits.edits.createSocialLinks
|
||||
.filter(() => !storelessSocials)
|
||||
.map((x) => ({ ...x, type: Object.values(SocialLinkType)[x.type] } as BioSiteSocialLink)),
|
||||
].filter((x) => x) as BioSiteSocialLink[]
|
||||
)
|
||||
.map((x) => {
|
||||
const index = $editorStore.edits.edits.indexSocialLinks.findIndex((y) => x.id == y.socialId);
|
||||
if (index === -1) return x;
|
||||
return { ...x, index: $editorStore.edits.edits.indexSocialLinks[index].index ?? -1 };
|
||||
})
|
||||
.sort((a, b) => a.index - b.index);
|
||||
|
||||
$: socialTexts = (
|
||||
[
|
||||
...(socials?.texts ?? [])
|
||||
.map((x) => {
|
||||
const remove = $editorStore.edits.edits.deleteSocialTexts.map((x) => x.id).findIndex((y) => y === x.id);
|
||||
if (remove !== -1) return null;
|
||||
|
||||
return x;
|
||||
})
|
||||
.filter((x) => x),
|
||||
...$editorStore.edits.edits.createSocialTexts.filter(() => !storelessSocials),
|
||||
].filter((x) => x) as BioSiteSocialText[]
|
||||
)
|
||||
.map((x) => {
|
||||
const index = $editorStore.edits.edits.indexSocialTexts.findIndex((y) => x.id == y.socialId);
|
||||
if (index === -1) return x;
|
||||
return { ...x, index: $editorStore.edits.edits.indexSocialTexts[index].index ?? -1 };
|
||||
})
|
||||
.sort((a, b) => a.index - b.index);
|
||||
</script>
|
||||
|
||||
{#key socials}
|
||||
{#if socials}
|
||||
{#key socialLinks}
|
||||
{#if socialLinks.length > 0}
|
||||
<div class="flex flex-wrap gap-1.5" class:edit={$editorStore.editMode}>
|
||||
{#each socialLinks as link}
|
||||
<SocialLink
|
||||
site={getLinkType(link.type)}
|
||||
social={link}
|
||||
hoverInvert={socials.invert}
|
||||
minimal={socials.minimal} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/key}
|
||||
{#if browser}
|
||||
{#key socialTexts}
|
||||
{#if socialTexts.length > 0}
|
||||
<div class="flex flex-col gap-1.5" class:edit={$editorStore.editMode}>
|
||||
{#each socialTexts as text}
|
||||
<SocialText social={text} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/key}
|
||||
{/if}
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
<style lang="postcss">
|
||||
div {
|
||||
@apply select-none;
|
||||
}
|
||||
|
||||
div.edit {
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
</style>
|
19
lib/components/bio/widgets/Discord/Status.svelte
Normal file
19
lib/components/bio/widgets/Discord/Status.svelte
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
import statuses from '$lib/discord-statuses';
|
||||
|
||||
export let status: string;
|
||||
export let activity: string | null | undefined = undefined;
|
||||
|
||||
const statusObject = statuses.find((x) => x.id == status);
|
||||
</script>
|
||||
|
||||
{#if statusObject}
|
||||
<span
|
||||
class="font-medium text-[var(--color)] before:inline-block before:content-[''] before:w-3 before:h-3 before:min-w-[0.75rem] before:min-h-[0.75rem] before:bg-[var(--color)] before:rounded-full before:mr-1"
|
||||
style="--color: {statusObject.color}">
|
||||
{statusObject.text}
|
||||
</span>
|
||||
{#if activity}
|
||||
and
|
||||
{/if}
|
||||
{/if}
|
40
lib/components/bio/widgets/DiscordUser.svelte
Normal file
40
lib/components/bio/widgets/DiscordUser.svelte
Normal file
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts">
|
||||
import DiscordStatus from './Discord/Status.svelte';
|
||||
import Panel from '../elements/Panel.svelte';
|
||||
|
||||
//@ts-ignore
|
||||
export let data: WidgetDiscord;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
||||
|
||||
<Panel preview={isPreview} {handle}>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex gap-4 items-center">
|
||||
<div
|
||||
class="avatar w-10 h-10 rounded-full bg-center bg-cover"
|
||||
style={data.avatar ? `--avatar: url(${data.avatar})` : undefined} />
|
||||
<p class="font-medium">@{data.username}</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- TODO: Discord badges -->
|
||||
</div>
|
||||
</div>
|
||||
{#if data.activity || data.status}
|
||||
<p>
|
||||
Currently <DiscordStatus status={data.status} activity={data.activity} />
|
||||
{#if data.activity}
|
||||
playing <span class="contents font-medium">{data.activity}</span>.
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<style lang="postcss">
|
||||
div.avatar {
|
||||
@apply bg-accent;
|
||||
background-image: var(--avatar);
|
||||
}
|
||||
</style>
|
15
lib/components/bio/widgets/ExternalSite.svelte
Normal file
15
lib/components/bio/widgets/ExternalSite.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import InteractivePanel from '../elements/InteractivePanel.svelte';
|
||||
|
||||
export let data: WidgetExternalSite;
|
||||
export let nonInteractive: boolean = false;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
||||
|
||||
<InteractivePanel preview={isPreview} url={!nonInteractive ? data.url : undefined} {handle}>
|
||||
<div class="flex flex-col gap-1 justify-center">
|
||||
<p class="text-lg line-clamp-1 break-all">{data.title || data.url}</p>
|
||||
{#if data.title}<p class="text-secondary line-clamp-1 break-all">{data.url}</p>{/if}
|
||||
</div>
|
||||
</InteractivePanel>
|
17
lib/components/bio/widgets/InstagramPost.svelte
Normal file
17
lib/components/bio/widgets/InstagramPost.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import GenericSite from './types/GenericSite.svelte';
|
||||
import InteractivePanel from '../elements/InteractivePanel.svelte';
|
||||
import WidgetRenderContainer from '../elements/WidgetRenderContainer.svelte';
|
||||
|
||||
export let data: WidgetInstagramPost;
|
||||
export let preview: WidgetPreviewInstagramPost | undefined = undefined;
|
||||
export let nonInteractive: boolean = false;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
||||
|
||||
<InteractivePanel preview={isPreview} title="Instagram Post" url={!nonInteractive ? data.url : undefined} {handle}>
|
||||
<WidgetRenderContainer hasPreview={preview != null}>
|
||||
<GenericSite thumbnail={preview?.thumbnail} title={preview?.author} description={preview?.description} />
|
||||
</WidgetRenderContainer>
|
||||
</InteractivePanel>
|
23
lib/components/bio/widgets/Markdown.svelte
Normal file
23
lib/components/bio/widgets/Markdown.svelte
Normal file
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import Panel from '../elements/Panel.svelte';
|
||||
|
||||
export let data: WidgetMarkdown;
|
||||
export let preview: WidgetRenderedMarkdown | undefined = undefined;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
||||
|
||||
<Panel preview={isPreview} {handle}>
|
||||
<div class="contents markdown">
|
||||
{#key data}
|
||||
{#key preview}
|
||||
{#if preview}
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html preview.html}
|
||||
{:else}
|
||||
<p class="break-words whitespace-pre-line">{data.content.trim()}</p>
|
||||
{/if}
|
||||
{/key}
|
||||
{/key}
|
||||
</div>
|
||||
</Panel>
|
17
lib/components/bio/widgets/PinterestPin.svelte
Normal file
17
lib/components/bio/widgets/PinterestPin.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import GenericSite from './types/GenericSite.svelte';
|
||||
import InteractivePanel from '../elements/InteractivePanel.svelte';
|
||||
import WidgetRenderContainer from '../elements/WidgetRenderContainer.svelte';
|
||||
|
||||
export let data: WidgetPinterestPin;
|
||||
export let preview: WidgetPreviewPinterestPin | undefined = undefined;
|
||||
export let nonInteractive: boolean = false;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
||||
|
||||
<InteractivePanel preview={isPreview} title="Pinterest Pin" url={!nonInteractive ? data.url : undefined} {handle}>
|
||||
<WidgetRenderContainer hasPreview={preview != null}>
|
||||
<GenericSite thumbnail={preview?.thumbnail} title={preview?.title} description={preview?.description} />
|
||||
</WidgetRenderContainer>
|
||||
</InteractivePanel>
|
17
lib/components/bio/widgets/SoundCloudTrack.svelte
Normal file
17
lib/components/bio/widgets/SoundCloudTrack.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import Track from './types/Track.svelte';
|
||||
import InteractivePanel from '../elements/InteractivePanel.svelte';
|
||||
import WidgetRenderContainer from '../elements/WidgetRenderContainer.svelte';
|
||||
|
||||
export let data: WidgetSoundCloudTrack;
|
||||
export let preview: WidgetPreviewSoundCloudTrack | undefined = undefined;
|
||||
export let nonInteractive: boolean = false;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
||||
|
||||
<InteractivePanel preview={isPreview} title="SoundCloud Track" url={!nonInteractive ? data.url : undefined} {handle}>
|
||||
<WidgetRenderContainer hasPreview={preview != null}>
|
||||
<Track thumbnail={preview?.thumbnail} title={preview?.title} artist={preview?.authorName} />
|
||||
</WidgetRenderContainer>
|
||||
</InteractivePanel>
|
5
lib/components/bio/widgets/SpotifyNowPlaying.svelte
Normal file
5
lib/components/bio/widgets/SpotifyNowPlaying.svelte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script lang="ts">
|
||||
export let data: WidgetSpotifyNowPlaying;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
13
lib/components/bio/widgets/Title.svelte
Normal file
13
lib/components/bio/widgets/Title.svelte
Normal file
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts">
|
||||
import Panel from '../elements/Panel.svelte';
|
||||
|
||||
export let data: WidgetMarkdown;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
||||
|
||||
<Panel preview={isPreview} invisible={true} {handle}>
|
||||
<div class="px-6 py-3">
|
||||
<p class="line-clamp-2 break-words font-semibold text-xl -mb-1 min-h-[28px]">{data.content}</p>
|
||||
</div>
|
||||
</Panel>
|
17
lib/components/bio/widgets/TwitchLive.svelte
Normal file
17
lib/components/bio/widgets/TwitchLive.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import GenericSite from './types/GenericSite.svelte';
|
||||
import InteractivePanel from '../elements/InteractivePanel.svelte';
|
||||
import WidgetRenderContainer from '../elements/WidgetRenderContainer.svelte';
|
||||
|
||||
export let data: WidgetTwitchLive;
|
||||
export let preview: WidgetPreviewTwitchLive | undefined = undefined;
|
||||
export let nonInteractive: boolean = false;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
||||
|
||||
<InteractivePanel preview={isPreview} title="Twitch Live" url={!nonInteractive ? data.url : undefined} {handle}>
|
||||
<WidgetRenderContainer hasPreview={preview != null}>
|
||||
<GenericSite thumbnail={preview?.thumbnail} title={preview?.channel} description={preview?.description} />
|
||||
</WidgetRenderContainer>
|
||||
</InteractivePanel>
|
17
lib/components/bio/widgets/TwitterPost.svelte
Normal file
17
lib/components/bio/widgets/TwitterPost.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import GenericSite from './types/GenericSite.svelte';
|
||||
import InteractivePanel from '../elements/InteractivePanel.svelte';
|
||||
import WidgetRenderContainer from '../elements/WidgetRenderContainer.svelte';
|
||||
|
||||
export let data: WidgetTwitterPost;
|
||||
export let preview: WidgetPreviewTwitterPost | undefined = undefined;
|
||||
export let nonInteractive: boolean = false;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
||||
|
||||
<InteractivePanel preview={isPreview} title="Twitter Post" url={!nonInteractive ? data.url : undefined} {handle}>
|
||||
<WidgetRenderContainer hasPreview={preview != null}>
|
||||
<GenericSite thumbnail={preview?.thumbnail} title={preview?.description} description={preview?.tag} />
|
||||
</WidgetRenderContainer>
|
||||
</InteractivePanel>
|
17
lib/components/bio/widgets/YouTubeVideo.svelte
Normal file
17
lib/components/bio/widgets/YouTubeVideo.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import GenericSite from './types/GenericSite.svelte';
|
||||
import InteractivePanel from '../elements/InteractivePanel.svelte';
|
||||
import WidgetRenderContainer from '../elements/WidgetRenderContainer.svelte';
|
||||
|
||||
export let data: WidgetYouTubeVideo;
|
||||
export let preview: WidgetPreviewYouTubeVideo | undefined = undefined;
|
||||
export let nonInteractive: boolean = false;
|
||||
export let handle: boolean = false;
|
||||
export let isPreview: boolean = false;
|
||||
</script>
|
||||
|
||||
<InteractivePanel preview={isPreview} title="YouTube Video" url={!nonInteractive ? data.url : undefined} {handle}>
|
||||
<WidgetRenderContainer hasPreview={preview != null}>
|
||||
<GenericSite thumbnail={preview?.thumbnail} title={preview?.title} description={preview?.description} />
|
||||
</WidgetRenderContainer>
|
||||
</InteractivePanel>
|
15
lib/components/bio/widgets/types/GenericSite.svelte
Normal file
15
lib/components/bio/widgets/types/GenericSite.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
export let thumbnail: string | undefined = undefined;
|
||||
export let title: string | undefined = undefined;
|
||||
export let description: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
{#if thumbnail}
|
||||
<img class="widget-thumbnail rounded-xl" src={thumbnail} alt="Thumbnail" />
|
||||
{/if}
|
||||
<div class="flex flex-col gap-3">
|
||||
<p class="break-words text-text-header line-clamp-2 font-bold">{title}</p>
|
||||
<p class="break-words text-text-primary line-clamp-4">{description}</p>
|
||||
</div>
|
||||
</div>
|
15
lib/components/bio/widgets/types/Track.svelte
Normal file
15
lib/components/bio/widgets/types/Track.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
export let thumbnail: string | undefined = undefined;
|
||||
export let title: string | undefined = undefined;
|
||||
export let artist: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<div class="flex gap-2 items-center">
|
||||
{#if thumbnail}
|
||||
<img class="widget-thumbnail h-14 rounded-xl" src={thumbnail} alt="Thumbnail" />
|
||||
{/if}
|
||||
<div class="flex flex-col justify-center w-full">
|
||||
<p class="break-words text-text-header line-clamp-1 font-bold">{title}</p>
|
||||
<p class="break-words text-text-primary line-clamp-1">{artist}</p>
|
||||
</div>
|
||||
</div>
|
Loading…
Add table
Add a link
Reference in a new issue