···
import { type AtpClient } from '$lib/at/client';
···
import * as TID from '@atcute/tid';
import type { PostWithUri } from '$lib/at/fetch';
import { onMount } from 'svelte';
+
import { isActorIdentifier, type AtprotoDid } from '@atcute/lexicons/syntax';
import { derived } from 'svelte/store';
import Device from 'svelte-device-info';
import Dropdown from './Dropdown.svelte';
···
? Promise.resolve(ok(data))
: client.getRecord(AppBskyFeedPost.mainSchema, did, rkey);
+
let profile: AppBskyActorProfile.Main | null = $state(null);
+
const p = await client.getProfile(did);
+
console.log(profile.description);
// const replies = replyBacklinks
// ? Promise.resolve(ok(replyBacklinks))
// : client.getBacklinks(
···
actionsPos = { x: event.clientX, y: event.clientY };
+
event.stopPropagation();
let deleteState: 'waiting' | 'confirm' | 'deleted' = $state('waiting');
···
+
let profileOpen = $state(false);
+
let profilePopoutShowDid = $state(false);
{#snippet embedBadge(embed: AppBskyEmbeds)}
···
+
{#snippet profileInline()}
+
flex min-w-0 items-center gap-2 font-bold {isOnPostComposer ? 'contrast-200' : ''}
+
rounded-sm pr-1 transition-colors duration-100 ease-in-out hover:bg-white/10
+
style="color: {color};"
+
onclick={() => (profileOpen = !profileOpen)}
+
<ProfilePicture {client} {did} size={8} />
+
<span class="w-min max-w-sm min-w-0 overflow-hidden text-nowrap overflow-ellipsis"
+
>{profile.displayName}</span
+
><span class="shrink-0 text-sm text-nowrap opacity-70">(@{handle})</span>
+
<!-- eslint-disable svelte/no-navigation-without-resolve -->
+
{#snippet profilePopout()}
+
{@const profileDesc = profile?.description?.trim() ?? ''}
+
class="post-dropdown max-w-xl gap-2! p-2.5! backdrop-blur-3xl! backdrop-brightness-25!"
+
style="background: {color}36; border-color: {color}99;"
+
bind:isOpen={profileOpen}
+
trigger={profileInline}
+
<div class="flex items-center gap-2">
+
<ProfilePicture {client} {did} size={20} />
+
<div class="flex flex-col items-start overflow-hidden overflow-ellipsis">
+
<span class="mb-1.5 min-w-0 overflow-hidden text-2xl text-nowrap overflow-ellipsis">
+
{profile?.displayName ?? handle}
+
{#if profile?.pronouns}
+
<span class="shrink-0 text-sm text-nowrap opacity-60">({profile.pronouns})</span>
+
oncontextmenu={(e) => {
+
const node = e.target as Node;
+
const selection = window.getSelection() ?? new Selection();
+
const range = document.createRange();
+
range.selectNodeContents(node);
+
selection.removeAllRanges();
+
selection.addRange(range);
+
onclick={() => (profilePopoutShowDid = !profilePopoutShowDid)}
+
class="mb-0.5 text-nowrap opacity-85 select-text hover:underline"
+
{profilePopoutShowDid ? did : `@${handle}`}
+
rel="noopener noreferrer"
+
class="text-sm text-nowrap opacity-60">{profile.website}</a
+
{#if profileDesc.length > 0}
+
<p class="rounded-sm bg-black/25 p-1.5 text-wrap wrap-break-word">
+
{#each profileDesc.split(/(\s)/) as line, idx (idx)}
+
{:else if isActorIdentifier(line.replace(/^@/, ''))}
+
rel="noopener noreferrer"
+
class="text-(--nucleus-accent2)"
+
href={`${$settings.socialAppUrl}/profile/${line.replace(/^@/, '')}`}>{line}</a
+
{:else if line.startsWith('https://')}
+
rel="noopener noreferrer"
+
class="text-(--nucleus-accent2)"
+
href={line}>{line.replace(/https?:\/\//, '')}</a
<div class="text-sm opacity-60">
···
+
mb-3 flex w-fit max-w-full items-center gap-1 rounded-sm pr-1
style="background: {color}33;"
+
{@render profilePopout()}
+
title={new Date(record.createdAt).toLocaleString()}
+
class="pl-0.5 text-nowrap text-(--nucleus-fg)/67"
+
{getRelativeTime(new Date(record.createdAt))}
<p class="leading-normal text-wrap wrap-break-word">
···
embed: AppBskyEmbedImages.Main | AppBskyEmbedVideo.Main | AppBskyEmbedExternal.Main
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
+
<div oncontextmenu={(e) => e.stopPropagation()}>
+
{#if embed.$type === 'app.bsky.embed.images'}
+
<!-- todo: improve how images are displayed, and pop out on click -->
+
{#each embed.images as image (image.image)}
+
{#if isBlob(image.image)}
+
class="w-full rounded-sm"
+
src={img('feed_thumbnail', did, image.image.ref.$link)}
+
{:else if embed.$type === 'app.bsky.embed.video'}
+
{#if isBlob(embed.video)}
+
{#await didDoc then didDoc}
+
<!-- svelte-ignore a11y_media_has_caption -->
+
src={blob(didDoc.value.pds, did, embed.video.ref.$link)}
{#snippet embedPost(uri: ResourceUri)}
···
style="background: {color}36; border-color: {color}99;"
bind:isOpen={actionsOpen}
bind:position={actionsPos}
{@render dropdownItem('heroicons:link-20-solid', 'copy link to post', () =>
navigator.clipboard.writeText(`${$settings.socialAppUrl}/profile/${did}/post/${rkey}`)
···
<Icon class="h-6 w-6" {icon} />
+
@reference "../app.css";
+
:global(.post-dropdown) {
+
@apply flex min-w-54 flex-col gap-1 rounded-sm border-2 p-1 shadow-2xl backdrop-blur-xl backdrop-brightness-60;