Add per-url aliases

This commit is contained in:
Jon Staab
2025-04-15 11:03:27 -07:00
parent 91689e5b90
commit 374ca7f265
37 changed files with 321 additions and 162 deletions
+1 -1
View File
@@ -81,7 +81,7 @@
}
const content = initialValues?.content || ""
const editor = makeEditor({submit, uploading, content})
const editor = makeEditor({url, submit, uploading, content})
let title = $state(initialValues?.title || "")
let location = $state(initialValues?.location || "")
+1 -1
View File
@@ -18,7 +18,7 @@
<CalendarEventHeader {event} />
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
<span class="whitespace-nowrap py-1 text-sm opacity-75">
Posted by <ProfileLink pubkey={event.pubkey} />
Posted by <ProfileLink pubkey={event.pubkey} {url} />
</span>
<CalendarEventActions showActivity {url} {event} />
</div>
+3 -2
View File
@@ -6,16 +6,17 @@
type Props = {
event: TrustedEvent
url: string
}
const {event}: Props = $props()
const {event, url}: Props = $props()
const meta = $derived(fromPairs(event.tags) as Record<string, string>)
</script>
<div class="flex min-w-0 flex-col gap-1 text-sm opacity-75">
<span class="flex items-center gap-1">
<Icon icon="user-circle" size={4} />
Posted by <ProfileLink pubkey={event.pubkey} />
Posted by <ProfileLink pubkey={event.pubkey} {url} />
</span>
{#if meta.location}
<span class="flex items-start gap-1">
+6 -4
View File
@@ -1,16 +1,18 @@
<script lang="ts">
import {writable} from "svelte/store"
import type {EventContent} from "@welshman/util"
import {isMobile, preventDefault} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import EditorContent from "@app/editor/EditorContent.svelte"
import {makeEditor} from "@app/editor"
interface Props {
onSubmit: any
type Props = {
url?: string
onSubmit: (event: EventContent) => void
}
const {onSubmit}: Props = $props()
const {onSubmit, url}: Props = $props()
const autofocus = !isMobile
@@ -33,7 +35,7 @@
editor.chain().clearContent().run()
}
const editor = makeEditor({autofocus, submit, uploading, aggressive: true})
const editor = makeEditor({url, autofocus, submit, uploading, aggressive: true})
</script>
<form class="relative z-feature flex gap-2 p-2" onsubmit={preventDefault(submit)}>
+14 -13
View File
@@ -4,8 +4,6 @@
import {
thunks,
pubkey,
deriveProfile,
deriveProfileDisplay,
formatTimestampAsDate,
formatTimestampAsTime,
thunkIsComplete,
@@ -22,15 +20,15 @@
import ChannelMessageEmojiButton from "@app/components/ChannelMessageEmojiButton.svelte"
import ChannelMessageMenuButton from "@app/components/ChannelMessageMenuButton.svelte"
import ChannelMessageMenuMobile from "@app/components/ChannelMessageMenuMobile.svelte"
import {colors} from "@app/state"
import {colors, deriveAlias, deriveAliasDisplay} from "@app/state"
import {publishDelete, publishReaction} from "@app/commands"
import {pushModal} from "@app/modal"
interface Props {
url: any
room: any
url: string
room: string
event: TrustedEvent
replyTo?: any
replyTo?: (event: TrustedEvent) => void
showPubkey?: boolean
inert?: boolean
}
@@ -39,16 +37,16 @@
const thunk = $thunks[event.id]
const today = formatTimestampAsDate(now())
const profile = deriveProfile(event.pubkey)
const profileDisplay = deriveProfileDisplay(event.pubkey)
const alias = deriveAlias(event.pubkey, url)
const aliasDisplay = deriveAliasDisplay(event.pubkey, url)
const [_, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
const hideMenuButton = $derived($thunk && !thunkIsComplete($thunk))
const reply = () => replyTo(event)
const reply = () => replyTo!(event)
const onTap = () => pushModal(ChannelMessageMenuMobile, {url, event, reply})
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey})
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey, url})
const onReactionClick = (content: string, events: TrustedEvent[]) => {
const reaction = events.find(e => e.pubkey === $pubkey)
@@ -68,7 +66,10 @@
<div class="flex w-full gap-3 overflow-auto">
{#if showPubkey}
<Button onclick={openProfile} class="flex items-start">
<Avatar src={$profile?.picture} class="border border-solid border-base-content" size={8} />
<Avatar
src={$alias?.profile?.picture}
class="border border-solid border-base-content"
size={8} />
</Button>
{:else}
<div class="w-8 min-w-8 max-w-8"></div>
@@ -77,7 +78,7 @@
{#if showPubkey}
<div class="flex items-center gap-2">
<Button onclick={openProfile} class="text-sm font-bold" style="color: {colorValue}">
{$profileDisplay}
{$aliasDisplay}
</Button>
<span class="text-xs opacity-50">
{#if formatTimestampAsDate(event.created_at) === today}
@@ -90,7 +91,7 @@
</div>
{/if}
<div class="text-sm">
<Content {event} relays={[url]} />
<Content {event} {url} />
{#if thunk}
<ThunkStatus {thunk} class="mt-2" />
{/if}
+13 -10
View File
@@ -9,6 +9,7 @@
pubkey,
Router,
tagPubkey,
loadUsingOutbox,
formatTimestampAsDate,
inboxRelaySelectionsByPubkey,
} from "@welshman/app"
@@ -27,17 +28,16 @@
import ChatMessage from "@app/components/ChatMessage.svelte"
import ChatCompose from "@app/components/ChannelCompose.svelte"
import ChatComposeParent from "@app/components/ChannelComposeParent.svelte"
import {userSettingValues, deriveChat, splitChatId, PLATFORM_NAME} from "@app/state"
import {INDEXER_RELAYS, userSettingValues, deriveChat, splitChatId, PLATFORM_NAME} from "@app/state"
import {pushModal} from "@app/modal"
import {sendWrapped, prependParent} from "@app/commands"
const {
id,
info,
}: {
type Props = {
id: string
info?: Snippet
} = $props()
}
const {id, info}: Props = $props()
const chat = deriveChat(id)
const pubkeys = splitChatId(id)
@@ -107,10 +107,13 @@
onMount(() => {
// Don't use loadInboxRelaySelection because we want to force reload
load({
relays: Router.get().FromPubkeys(others).getUrls(),
filters: [{kinds: [INBOX_RELAYS], authors: others}],
})
for (const pubkey of others) {
loadUsingOutbox({
pubkey,
kind: INBOX_RELAYS,
relays: INDEXER_RELAYS,
})
}
const observer = new ResizeObserver(() => {
if (dynamicPadding && chatCompose) {
+1 -1
View File
@@ -40,7 +40,7 @@
<div class="flex items-center justify-between gap-2">
<div class="flex min-w-0 items-center gap-2">
{#if others.length === 0}
<ProfileCircle pubkey={$pubkey} size={5} />
<ProfileCircle pubkey={$pubkey!} size={5} />
Note to self
{:else if others.length === 1}
<ProfileCircle pubkey={others[0]} size={5} />
+6 -12
View File
@@ -2,13 +2,7 @@
import {type Instance} from "tippy.js"
import {hash} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {
thunks,
deriveProfile,
deriveProfileDisplay,
formatTimestampAsTime,
pubkey,
} from "@welshman/app"
import {thunks, formatTimestampAsTime, pubkey} from "@welshman/app"
import {isMobile} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
@@ -21,7 +15,7 @@
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
import {colors} from "@app/state"
import {colors, deriveAlias, deriveAliasDisplay} from "@app/state"
import {makeDelete, makeReaction, sendWrapped} from "@app/commands"
import {pushModal} from "@app/modal"
@@ -36,8 +30,8 @@
const thunk = $thunks[event.id]
const isOwn = event.pubkey === $pubkey
const profile = deriveProfile(event.pubkey)
const profileDisplay = deriveProfileDisplay(event.pubkey)
const alias = deriveAlias(event.pubkey)
const aliasDisplay = deriveAliasDisplay(event.pubkey)
const [_, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
const reply = () => replyTo(event)
@@ -107,12 +101,12 @@
{#if !isOwn}
<Button onclick={openProfile} class="flex items-center gap-1">
<Avatar
src={$profile?.picture}
src={$alias?.profile?.picture}
class="border border-solid border-base-content"
size={4} />
<div class="flex items-center gap-2">
<Button onclick={openProfile} class="text-sm font-bold" style="color: {colorValue}">
{$profileDisplay}
{$aliasDisplay}
</Button>
</div>
</Button>
+4 -4
View File
@@ -38,8 +38,8 @@
showEntire?: boolean
hideMediaAtDepth?: number
expandMode?: string
relays?: string[]
depth?: number
url?: string
}
let {
@@ -49,8 +49,8 @@
showEntire = $bindable(false),
hideMediaAtDepth = 1,
expandMode = "block",
relays = [],
depth = 0,
url,
}: Props = $props()
const fullContent = parse(event)
@@ -146,10 +146,10 @@
<ContentLinkInline value={parsed.value} />
{/if}
{:else if isProfile(parsed)}
<ContentMention value={parsed.value} />
<ContentMention value={parsed.value} {url} />
{:else if isEvent(parsed) || isAddress(parsed)}
{#if isBlock(i)}
<ContentQuote {depth} {relays} {hideMediaAtDepth} value={parsed.value} {event} />
<ContentQuote {depth} {url} {hideMediaAtDepth} value={parsed.value} {event} />
{:else}
<Link
external
+11 -4
View File
@@ -1,17 +1,24 @@
<script lang="ts">
import type {ProfilePointer} from "@welshman/content"
import {displayProfile} from "@welshman/util"
import {deriveProfile} from "@welshman/app"
import Button from "@lib/components/Button.svelte"
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import {pushModal} from "@app/modal"
import {deriveAlias} from "@app/state"
const {value} = $props()
type Props = {
value: ProfilePointer
url?: string
}
const profile = deriveProfile(value.pubkey)
const {value, url}: Props = $props()
const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey})
const alias = deriveAlias(value.pubkey, url)
const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey, url})
</script>
<Button onclick={openProfile} class="link-content">
@{displayProfile($profile)}
@{displayProfile($alias?.profile)}
</Button>
+19 -5
View File
@@ -3,6 +3,7 @@
import {goto} from "$app/navigation"
import {nthEq} from "@welshman/lib"
import {Router, tracker, repository} from "@welshman/app"
import type {TrustedEvent} from "@welshman/util"
import {Address, DIRECT_MESSAGE, MESSAGE, THREAD, EVENT_TIME} from "@welshman/util"
import {scrollToEvent} from "@lib/html"
import Button from "@lib/components/Button.svelte"
@@ -12,11 +13,24 @@
import {deriveEvent, entityLink, ROOM} from "@app/state"
import {makeThreadPath, makeCalendarPath, makeRoomPath} from "@app/routes"
const {value, event, depth, hideMediaAtDepth, relays = []} = $props()
type Props = {
value: any
hideMediaAtDepth: number
event: TrustedEvent
depth: number
url?: string
}
const {id, identifier, kind, pubkey, relays: relayHints = []} = value
const {value, event, depth, hideMediaAtDepth, url}: Props = $props()
const {id, identifier, kind, pubkey, relays = []} = value
const idOrAddress = id || new Address(kind, pubkey, identifier).toString()
const mergedRelays = [...relays, ...Router.get().Quote(event, idOrAddress, relayHints).getUrls()]
const mergedRelays = Router.get().Quote(event, idOrAddress, relays).getUrls()
if (url) {
mergedRelays.push(url)
}
const quote = deriveEvent(idOrAddress, mergedRelays)
const entity = id
? nip19.neventEncode({id, relays: mergedRelays})
@@ -80,8 +94,8 @@
<Button class="my-2 block max-w-full text-left" {onclick}>
{#if $quote}
<NoteCard event={$quote} class="bg-alt rounded-box p-4">
<NoteContent {hideMediaAtDepth} {relays} event={$quote} depth={depth + 1} />
<NoteCard event={$quote} {url} class="bg-alt rounded-box p-4">
<NoteContent {hideMediaAtDepth} {url} event={$quote} depth={depth + 1} />
</NoteCard>
{:else}
<div class="rounded-box p-4">
+1 -1
View File
@@ -32,7 +32,7 @@
onSubmit(publishComment({event, content, tags, relays: [url]}))
}
const editor = makeEditor({submit, uploading, autofocus: !isMobile})
const editor = makeEditor({url, submit, uploading, autofocus: !isMobile})
let form: HTMLElement
let spacer: HTMLElement
+1 -1
View File
@@ -42,7 +42,7 @@
<div class="column gap-2">
<div class="flex justify-between">
<div>
<Profile pubkey={report.pubkey} />
<Profile pubkey={report.pubkey} {url} />
<span>Reported this event as "{reason}"</span>
</div>
{#if report.pubkey === $pubkey}
+1 -1
View File
@@ -43,7 +43,7 @@
const showMembers = () =>
pushModal(
ProfileList,
{pubkeys: members, title: `Members of`, subtitle: displayRelayUrl(url)},
{url, pubkeys: members, title: `Members of`, subtitle: displayRelayUrl(url)},
{replaceState},
)
+4 -2
View File
@@ -17,12 +17,14 @@
children,
minimal = false,
hideProfile = false,
url,
...restProps
}: {
event: TrustedEvent
children: Snippet
minimal?: boolean
hideProfile?: boolean
url?: string
class?: string
} = $props()
@@ -49,9 +51,9 @@
<div class="flex justify-between gap-2">
{#if !hideProfile}
{#if minimal}
@<ProfileName pubkey={event.pubkey} />
@<ProfileName pubkey={event.pubkey} {url} />
{:else}
<Profile pubkey={event.pubkey} />
<Profile pubkey={event.pubkey} {url} />
{/if}
{/if}
<Link
+1 -1
View File
@@ -25,7 +25,7 @@
publishReaction({event, content: emoji.unicode, relays: [url]})
</script>
<NoteCard {event} class="card2 bg-alt">
<NoteCard {event} {url} class="card2 bg-alt">
<NoteContent {event} expandMode="inline" />
<div class="flex w-full justify-between gap-2">
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-right">
+8 -3
View File
@@ -10,7 +10,12 @@
import ProfileInfo from "@app/components/ProfileInfo.svelte"
import {makeChatPath} from "@app/routes"
const {pubkey} = $props()
type Props = {
pubkey: string
url?: string
}
const {pubkey, url}: Props = $props()
const filters: Filter[] = [{authors: [pubkey], limit: 1}]
const events = deriveEvents(repository, {filters})
@@ -29,13 +34,13 @@
<div class="card2 bg-alt col-2 shadow-xl">
<div class="flex justify-between">
<Profile {pubkey} />
<Profile {pubkey} {url} />
<Link class="btn btn-primary hidden sm:flex" href={makeChatPath([pubkey])}>
<Icon icon="letter" />
Start a Chat
</Link>
</div>
<ProfileInfo {pubkey} />
<ProfileInfo {pubkey} {url} />
{#if $events.length > 0}
<div class="bg-alt badge badge-neutral border-none">
Last active {formatTimestampRelative($events[0].created_at)}
+12 -8
View File
@@ -4,25 +4,29 @@
session,
userFollows,
deriveUserWotScore,
deriveProfile,
deriveHandleForPubkey,
displayHandle,
deriveProfileDisplay,
} from "@welshman/app"
import Button from "@lib/components/Button.svelte"
import Avatar from "@lib/components/Avatar.svelte"
import WotScore from "@lib/components/WotScore.svelte"
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import {pushModal} from "@app/modal"
import {deriveAlias, deriveAliasDisplay} from "@app/state"
const {pubkey} = $props()
type Props = {
pubkey: string
url?: string
}
const profile = deriveProfile(pubkey)
const profileDisplay = deriveProfileDisplay(pubkey)
const {pubkey, url}: Props = $props()
const alias = deriveAlias(pubkey, url)
const aliasDisplay = deriveAliasDisplay(pubkey, url)
const handle = deriveHandleForPubkey(pubkey)
const score = deriveUserWotScore(pubkey)
const openProfile = () => pushModal(ProfileDetail, {pubkey})
const openProfile = () => pushModal(ProfileDetail, {pubkey, url})
const following = $derived(
pubkey === $session!.pubkey || getPubkeyTagValues(getListTags($userFollows)).includes(pubkey),
@@ -31,12 +35,12 @@
<div class="flex max-w-full gap-3">
<Button onclick={openProfile} class="py-1">
<Avatar src={$profile?.picture} size={10} />
<Avatar src={$alias?.profile?.picture} size={10} />
</Button>
<div class="flex min-w-0 flex-col">
<div class="flex items-center gap-2">
<Button onclick={openProfile} class="text-bold overflow-hidden text-ellipsis">
{$profileDisplay}
{$aliasDisplay}
</Button>
<WotScore score={$score} active={following} />
</div>
+9 -4
View File
@@ -1,10 +1,15 @@
<script lang="ts">
import {deriveProfile} from "@welshman/app"
import Avatar from "@lib/components/Avatar.svelte"
import {deriveAlias} from "@app/state"
const {...props} = $props()
type Props = {
pubkey: string
url?: string
} & Record<string, any>
const profile = deriveProfile(props.pubkey)
const {pubkey, url, ...props}: Props = $props()
const alias = deriveAlias(pubkey, url)
</script>
<Avatar src={$profile?.picture} icon="user-circle" {...props} />
<Avatar src={$alias?.profile?.picture} icon="user-circle" {...props} />
+12 -9
View File
@@ -5,10 +5,8 @@
session,
userFollows,
deriveUserWotScore,
deriveProfile,
deriveHandleForPubkey,
displayHandle,
deriveProfileDisplay,
} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
@@ -18,14 +16,19 @@
import ModalFooter from "@lib/components/ModalFooter.svelte"
import ProfileInfo from "@app/components/ProfileInfo.svelte"
import ChatEnable from "@app/components/ChatEnable.svelte"
import {canDecrypt, pubkeyLink} from "@app/state"
import {canDecrypt, pubkeyLink, deriveAlias, deriveAliasDisplay} from "@app/state"
import {pushModal} from "@app/modal"
import {makeChatPath} from "@app/routes"
const {pubkey} = $props()
export type Props = {
pubkey: string
url?: string
}
const profile = deriveProfile(pubkey)
const profileDisplay = deriveProfileDisplay(pubkey)
const {pubkey, url}: Props = $props()
const alias = deriveAlias(pubkey, url)
const aliasDisplay = deriveAliasDisplay(pubkey, url)
const handle = deriveHandleForPubkey(pubkey)
const score = deriveUserWotScore(pubkey)
@@ -43,12 +46,12 @@
<div class="column gap-4">
<div class="flex max-w-full gap-3">
<span class="py-1">
<Avatar src={$profile?.picture} size={10} />
<Avatar src={$alias?.profile?.picture} size={10} />
</span>
<div class="flex min-w-0 flex-col">
<div class="flex items-center gap-2">
<span class="text-bold overflow-hidden text-ellipsis">
{$profileDisplay}
{$aliasDisplay}
</span>
<WotScore score={$score} active={following} />
</div>
@@ -57,7 +60,7 @@
</div>
</div>
</div>
<ProfileInfo {pubkey} />
<ProfileInfo {pubkey} {url} />
<ModalFooter>
<Button onclick={back} class="btn btn-link">
<Icon icon="alt-arrow-left" />
+4 -4
View File
@@ -23,12 +23,12 @@
const back = () => history.back()
const onsubmit = ({profile, shouldBroadcast}: {profile: Profile; shouldBroadcast: boolean}) => {
const relays = shouldBroadcast
? Router.get().FromUser().getUrls()
: getMembershipUrls($userMembership)
const template = isPublishedProfile(profile) ? editProfile(profile) : createProfile(profile)
const relays = [...getMembershipUrls($userMembership)]
if (!shouldBroadcast) {
if (shouldBroadcast) {
relays.push(...Router.get().FromUser().getUrls())
} else {
template.tags = uniqTags([...template.tags, PROTECTED])
}
+1 -1
View File
@@ -91,7 +91,7 @@
{/snippet}
{#snippet info()}
<p>
If enabled, your profile will be published to the broader nostr network, as well as to
If enabled, changes will be published to the broader nostr network in addition to
spaces you are a member of.
</p>
{/snippet}
+10 -5
View File
@@ -1,12 +1,17 @@
<script lang="ts">
import {deriveProfile} from "@welshman/app"
import Content from "@app/components/Content.svelte"
import {deriveAlias} from "@app/state"
const {pubkey} = $props()
export type Props = {
pubkey: string
url?: string
}
const profile = deriveProfile(pubkey)
const {pubkey, url}: Props = $props()
const alias = deriveAlias(pubkey, url)
</script>
{#if $profile}
<Content event={{content: $profile.about, tags: []}} />
{#if $alias?.profile}
<Content event={{content: $alias.profile.about, tags: []}} hideMediaAtDepth={0} />
{/if}
+8 -3
View File
@@ -5,11 +5,16 @@
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import {pushModal} from "@app/modal"
const {pubkey}: {pubkey: string} = $props()
type Props = {
pubkey: string
url?: string
}
const openProfile = () => pushModal(ProfileDetail, {pubkey})
const {pubkey, url}: Props = $props()
const openProfile = () => pushModal(ProfileDetail, {pubkey, url})
</script>
<Button onclick={preventDefault(openProfile)} class="link-content">
@<ProfileName {pubkey} />
@<ProfileName {pubkey} {url} />
</Button>
+4 -3
View File
@@ -5,11 +5,12 @@
interface Props {
title: any
subtitle?: string
pubkeys: any
subtitle?: string
url?: string
}
const {subtitle = "", pubkeys, ...restProps}: Props = $props()
const {subtitle = "", pubkeys, url, ...restProps}: Props = $props()
</script>
<div class="column gap-4">
@@ -23,7 +24,7 @@
</ModalHeader>
{#each pubkeys as pubkey (pubkey)}
<div class="card2 bg-alt">
<Profile {pubkey} />
<Profile {pubkey} {url} />
</div>
{/each}
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
+9 -4
View File
@@ -1,9 +1,14 @@
<script lang="ts">
import {deriveProfileDisplay} from "@welshman/app"
import {deriveAliasDisplay} from "@app/state"
const {pubkey} = $props()
type Props = {
pubkey: string
url?: string
}
const profileDisplay = deriveProfileDisplay(pubkey)
const {pubkey, url}: Props = $props()
const aliasDisplay = deriveAliasDisplay(pubkey, url)
</script>
{$profileDisplay}
{$aliasDisplay}
+1 -1
View File
@@ -53,7 +53,7 @@
history.back()
}
const editor = makeEditor({submit, uploading, placeholder: "What's on your mind?"})
const editor = makeEditor({url, submit, uploading, placeholder: "What's on your mind?"})
let title: string = $state("")
</script>
+6 -7
View File
@@ -8,13 +8,12 @@
import ThreadActions from "@app/components/ThreadActions.svelte"
import {makeThreadPath} from "@app/routes"
const {
url,
event,
}: {
type Props = {
url: string
event: TrustedEvent
} = $props()
}
const {url, event}: Props = $props()
const title = event.tags.find(nthEq(0, "title"))?.[1]
</script>
@@ -32,10 +31,10 @@
{formatTimestamp(event.created_at)}
</p>
{/if}
<Content {event} expandMode="inline" relays={[url]} />
<Content {event} {url} expandMode="inline" />
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
<span class="whitespace-nowrap py-1 text-sm opacity-75">
Posted by <ProfileLink pubkey={event.pubkey} />
Posted by <ProfileLink pubkey={event.pubkey} {url} />
</span>
<ThreadActions showActivity {url} {event} />
</div>