forked from coracle/flotilla
Re-work space navigation #223
This commit is contained in:
@@ -4,31 +4,17 @@
|
||||
import {throttle} from "throttle-debounce"
|
||||
import {onMount} from "svelte"
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import {get, derived} from "svelte/store"
|
||||
import {get} from "svelte/store"
|
||||
import {App, type URLOpenListenerEvent} from "@capacitor/app"
|
||||
import {dev} from "$app/environment"
|
||||
import {goto} from "$app/navigation"
|
||||
import {sync, localStorageProvider} from "@welshman/store"
|
||||
import {
|
||||
ago,
|
||||
assoc,
|
||||
call,
|
||||
defer,
|
||||
dissoc,
|
||||
identity,
|
||||
memoize,
|
||||
on,
|
||||
sleep,
|
||||
spec,
|
||||
TaskQueue,
|
||||
WEEK,
|
||||
} from "@welshman/lib"
|
||||
import {assoc, call, defer, dissoc, on, sleep, spec, TaskQueue} from "@welshman/lib"
|
||||
import type {TrustedEvent, StampedEvent} from "@welshman/util"
|
||||
import {WRAP} from "@welshman/util"
|
||||
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
||||
import type {Socket, RelayMessage, ClientMessage} from "@welshman/net"
|
||||
import {
|
||||
request,
|
||||
defaultSocketPolicies,
|
||||
makeSocketPolicyAuth,
|
||||
SocketEvent,
|
||||
@@ -40,7 +26,6 @@
|
||||
isClientClose,
|
||||
} from "@welshman/net"
|
||||
import {
|
||||
loadRelay,
|
||||
repository,
|
||||
pubkey,
|
||||
session,
|
||||
@@ -64,20 +49,18 @@
|
||||
import {preferencesStorageProvider} from "@lib/storage"
|
||||
import AppContainer from "@app/components/AppContainer.svelte"
|
||||
import ModalContainer from "@app/components/ModalContainer.svelte"
|
||||
import {setupHistory} from "@app/util/history"
|
||||
import {setupTracking} from "@app/util/tracking"
|
||||
import {setupAnalytics} from "@app/util/analytics"
|
||||
import {
|
||||
INDEXER_RELAYS,
|
||||
userMembership,
|
||||
userSettingsValues,
|
||||
relaysPendingTrust,
|
||||
ensureUnwrapped,
|
||||
canDecrypt,
|
||||
getSetting,
|
||||
relaysMostlyRestricted,
|
||||
userInboxRelays,
|
||||
} from "@app/core/state"
|
||||
import {loadUserData, listenForNotifications} from "@app/core/requests"
|
||||
import {syncApplicationData} from "@app/core/sync"
|
||||
import {theme} from "@app/util/theme"
|
||||
import {toast, pushToast} from "@app/util/toast"
|
||||
import {initializePushNotifications} from "@app/util/push"
|
||||
@@ -192,8 +175,6 @@
|
||||
|
||||
// TODO: remove ack result
|
||||
if (pubkey && ["ack", connectSecret].includes(result)) {
|
||||
await loadUserData(pubkey)
|
||||
|
||||
loginWithNip46(pubkey, clientSecret, signerPubkey, relays)
|
||||
broker.cleanup()
|
||||
success = true
|
||||
@@ -224,6 +205,7 @@
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true
|
||||
setupHistory()
|
||||
setupTracking()
|
||||
setupAnalytics()
|
||||
|
||||
@@ -374,46 +356,8 @@
|
||||
},
|
||||
)
|
||||
|
||||
// Load relay info
|
||||
for (const url of INDEXER_RELAYS) {
|
||||
loadRelay(url)
|
||||
}
|
||||
|
||||
// Load user data
|
||||
if ($pubkey) {
|
||||
await loadUserData($pubkey)
|
||||
}
|
||||
|
||||
// Listen for space data, populate space-based notifications
|
||||
let unsubSpaces: any
|
||||
|
||||
userMembership.subscribe(
|
||||
memoize($membership => {
|
||||
unsubSpaces?.()
|
||||
unsubSpaces = listenForNotifications()
|
||||
}),
|
||||
)
|
||||
|
||||
// Listen for chats, populate chat-based notifications
|
||||
let controller: AbortController
|
||||
|
||||
derived([pubkey, canDecrypt, userInboxRelays], identity).subscribe(
|
||||
([$pubkey, $canDecrypt, $userInboxRelays]) => {
|
||||
controller?.abort()
|
||||
controller = new AbortController()
|
||||
|
||||
if ($pubkey && $canDecrypt) {
|
||||
request({
|
||||
signal: controller.signal,
|
||||
relays: $userInboxRelays,
|
||||
filters: [
|
||||
{kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK, 2)},
|
||||
{kinds: [WRAP], "#p": [$pubkey], limit: 100},
|
||||
],
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
// Load user data, listen for messages, etc
|
||||
syncApplicationData()
|
||||
|
||||
// subscribe to badge count for changes
|
||||
notifications.badgeCount.subscribe(notifications.handleBadgeCountChanges)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import Content from "@app/components/Content.svelte"
|
||||
import ContentMinimal from "@app/components/ContentMinimal.svelte"
|
||||
import ProfileEdit from "@app/components/ProfileEdit.svelte"
|
||||
import ProfileDelete from "@app/components/ProfileDelete.svelte"
|
||||
import SignerStatus from "@app/components/SignerStatus.svelte"
|
||||
@@ -66,7 +66,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
{#key $profile?.about}
|
||||
<Content event={{content: $profile?.about || "", tags: []}} hideMediaAtDepth={0} />
|
||||
<ContentMinimal event={{content: $profile?.about || "", tags: []}} />
|
||||
{/key}
|
||||
</div>
|
||||
{#if $session?.email}
|
||||
|
||||
@@ -1,125 +1,10 @@
|
||||
<script lang="ts">
|
||||
import {page} from "$app/stores"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import {deriveRelay} from "@welshman/app"
|
||||
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
|
||||
import Login2 from "@assets/icons/login-3.svg?dataurl"
|
||||
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
||||
import Ghost from "@assets/icons/ghost-smile.svg?dataurl"
|
||||
import BillList from "@assets/icons/bill-list.svg?dataurl"
|
||||
import ShieldUser from "@assets/icons/shield-user.svg?dataurl"
|
||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import ProfileLatest from "@app/components/ProfileLatest.svelte"
|
||||
import SpaceJoin from "@app/components/SpaceJoin.svelte"
|
||||
import RelayName from "@app/components/RelayName.svelte"
|
||||
import RelayDescription from "@app/components/RelayDescription.svelte"
|
||||
import SpaceQuickLinks from "@app/components/SpaceQuickLinks.svelte"
|
||||
import SpaceRecentActivity from "@app/components/SpaceRecentActivity.svelte"
|
||||
import SpaceRelayStatus from "@app/components/SpaceRelayStatus.svelte"
|
||||
import {decodeRelay, userRoomsByUrl} from "@app/core/state"
|
||||
import {makeChatPath} from "@app/util/routes"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {goto} from "$app/navigation"
|
||||
import {decodeRelay} from "@app/core/state"
|
||||
import {makeSpacePath} from "@app/util/routes"
|
||||
|
||||
const url = decodeRelay($page.params.relay!)
|
||||
const relay = deriveRelay(url)
|
||||
const joinSpace = () => pushModal(SpaceJoin, {url})
|
||||
|
||||
const owner = $derived($relay?.profile?.pubkey)
|
||||
goto(makeSpacePath(url, "recent"))
|
||||
</script>
|
||||
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div class="center">
|
||||
<Icon icon={HomeSmile} />
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<strong>Home</strong>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div class="row-2">
|
||||
{#if !$userRoomsByUrl.has(url)}
|
||||
<Button class="btn btn-primary btn-sm" onclick={joinSpace}>
|
||||
<Icon icon={Login2} />
|
||||
Join Space
|
||||
</Button>
|
||||
{:else if owner}
|
||||
<Link class="btn btn-primary btn-sm" href={makeChatPath([owner])}>
|
||||
<Icon icon={Letter} />
|
||||
Contact Owner
|
||||
</Link>
|
||||
{/if}
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
|
||||
<PageContent class="flex flex-col gap-2 p-2 pt-4">
|
||||
<div class="card2 bg-alt flex flex-col gap-4 text-left">
|
||||
<div class="relative flex gap-4">
|
||||
<div class="relative">
|
||||
<div class="avatar relative">
|
||||
<div
|
||||
class="center !flex h-16 w-16 min-w-16 rounded-full border-2 border-solid border-base-300 bg-base-300">
|
||||
{#if $relay?.profile?.icon}
|
||||
<img alt="" src={$relay.profile.icon} />
|
||||
{:else}
|
||||
<Icon icon={Ghost} size={6} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex min-w-0 flex-col gap-1">
|
||||
<h1 class="ellipsize whitespace-nowrap text-2xl font-bold">
|
||||
<RelayName {url} />
|
||||
</h1>
|
||||
<p class="ellipsize text-sm opacity-75">{displayRelayUrl(url)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<RelayDescription {url} />
|
||||
{#if $relay?.profile?.terms_of_service || $relay?.profile?.privacy_policy}
|
||||
<div class="flex gap-3">
|
||||
{#if $relay.profile.terms_of_service}
|
||||
<Link href={$relay.profile.terms_of_service} class="badge badge-neutral flex gap-2">
|
||||
<Icon icon={BillList} size={4} />
|
||||
Terms of Service
|
||||
</Link>
|
||||
{/if}
|
||||
{#if $relay.profile.privacy_policy}
|
||||
<Link href={$relay?.profile?.privacy_policy} class="badge badge-neutral flex gap-2">
|
||||
<Icon icon={ShieldUser} size={4} />
|
||||
Privacy Policy
|
||||
</Link>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<SpaceQuickLinks {url} />
|
||||
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<SpaceRecentActivity {url} />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<SpaceRelayStatus {url} />
|
||||
{#if owner}
|
||||
<div class="card2 bg-alt">
|
||||
<h3 class="mb-4 flex items-center gap-2 text-lg font-semibold">
|
||||
<Icon icon={UserRounded} />
|
||||
Latest Updates
|
||||
</h3>
|
||||
<ProfileLatest {url} pubkey={owner}>
|
||||
{#snippet fallback()}
|
||||
<p class="text-sm opacity-60">No recent posts from the relay admin</p>
|
||||
{/snippet}
|
||||
</ProfileLatest>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</PageContent>
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
makeRoomMeta,
|
||||
MESSAGE,
|
||||
DELETE,
|
||||
THREAD,
|
||||
EVENT_TIME,
|
||||
ZAP_GOAL,
|
||||
ROOM_ADD_USER,
|
||||
ROOM_REMOVE_USER,
|
||||
} from "@welshman/util"
|
||||
@@ -33,7 +36,7 @@
|
||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import ChannelName from "@app/components/ChannelName.svelte"
|
||||
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
||||
import ChannelItem from "@app/components/ChannelItem.svelte"
|
||||
import ChannelCompose from "@app/components/ChannelCompose.svelte"
|
||||
import ChannelComposeParent from "@app/components/ChannelComposeParent.svelte"
|
||||
import {
|
||||
@@ -65,7 +68,7 @@
|
||||
const lastChecked = $checked[$page.url.pathname]
|
||||
const url = decodeRelay(relay)
|
||||
const channel = deriveChannel(url, room)
|
||||
const filter = {kinds: [MESSAGE], "#h": [room]}
|
||||
const filter = {kinds: [MESSAGE, THREAD, EVENT_TIME, ZAP_GOAL], "#h": [room]}
|
||||
const isFavorite = $derived($userRoomsByUrl.get(url)?.has(room))
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
const membershipStatus = deriveUserMembershipStatus(url, room)
|
||||
@@ -429,7 +432,7 @@
|
||||
<Divider>{value}</Divider>
|
||||
{:else}
|
||||
<div in:slide class:-mt-1={!showPubkey}>
|
||||
<ChannelMessage
|
||||
<ChannelItem
|
||||
{url}
|
||||
{replyTo}
|
||||
event={$state.snapshot(value as TrustedEvent)}
|
||||
@@ -485,11 +488,12 @@
|
||||
</div>
|
||||
{#key eventToEdit}
|
||||
<ChannelCompose
|
||||
bind:this={compose}
|
||||
content={eventToEdit?.content}
|
||||
{onSubmit}
|
||||
{url}
|
||||
{onEditPrevious} />
|
||||
{room}
|
||||
{onSubmit}
|
||||
{onEditPrevious}
|
||||
content={eventToEdit?.content}
|
||||
bind:this={compose} />
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -46,17 +46,19 @@
|
||||
let haveISeenTheFuture = false
|
||||
let prevDateDisplay: string
|
||||
|
||||
return $events.map<Item>(event => {
|
||||
const newDateDisplay = formatTimestampAsDate(getStart(event))
|
||||
const dateDisplay = prevDateDisplay === newDateDisplay ? undefined : newDateDisplay
|
||||
const isFuture = todayDateDisplay === newDateDisplay || event.created_at > now()
|
||||
const isFirstFutureEvent = !haveISeenTheFuture && isFuture
|
||||
return $events
|
||||
.filter(event => !isNaN(getStart(event)))
|
||||
.map<Item>(event => {
|
||||
const newDateDisplay = formatTimestampAsDate(getStart(event))
|
||||
const dateDisplay = prevDateDisplay === newDateDisplay ? undefined : newDateDisplay
|
||||
const isFuture = todayDateDisplay === newDateDisplay || event.created_at > now()
|
||||
const isFirstFutureEvent = !haveISeenTheFuture && isFuture
|
||||
|
||||
prevDateDisplay = newDateDisplay
|
||||
haveISeenTheFuture = isFuture
|
||||
prevDateDisplay = newDateDisplay
|
||||
haveISeenTheFuture = isFuture
|
||||
|
||||
return {event, dateDisplay, isFirstFutureEvent}
|
||||
})
|
||||
return {event, dateDisplay, isFirstFutureEvent}
|
||||
})
|
||||
})
|
||||
|
||||
let previousScrollHeight = 0
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full flex-col justify-end sm:flex-row">
|
||||
<CalendarEventActions {url} event={$event} />
|
||||
<CalendarEventActions showRoom {url} event={$event} />
|
||||
</div>
|
||||
</div>
|
||||
{#if !showAll && $replies.length > 4}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import {readable} from "svelte/store"
|
||||
import {now, formatTimestampAsDate, MINUTE, ago} from "@welshman/lib"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {makeEvent, MESSAGE, DELETE} from "@welshman/util"
|
||||
import {makeEvent, MESSAGE, DELETE, THREAD, EVENT_TIME, ZAP_GOAL} from "@welshman/util"
|
||||
import {pubkey, publishThunk} from "@welshman/app"
|
||||
import {slide, fade, fly} from "@lib/transition"
|
||||
import ChatRound from "@assets/icons/chat-round.svg?dataurl"
|
||||
@@ -18,7 +18,7 @@
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
||||
import ChannelItem from "@app/components/ChannelItem.svelte"
|
||||
import ChannelCompose from "@app/components/ChannelCompose.svelte"
|
||||
import ChannelComposeParent from "@app/components/ChannelComposeParent.svelte"
|
||||
import {
|
||||
@@ -38,7 +38,7 @@
|
||||
const mounted = now()
|
||||
const lastChecked = $checked[$page.url.pathname]
|
||||
const url = decodeRelay($page.params.relay!)
|
||||
const filter = {kinds: [MESSAGE]}
|
||||
const filter = {kinds: [MESSAGE, THREAD, EVENT_TIME, ZAP_GOAL]}
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const replyTo = (event: TrustedEvent) => {
|
||||
@@ -282,11 +282,12 @@
|
||||
{:else if type === "date"}
|
||||
<Divider>{value}</Divider>
|
||||
{:else}
|
||||
{@const event = $state.snapshot(value as TrustedEvent)}
|
||||
<div in:slide class:-mt-1={!showPubkey}>
|
||||
<ChannelMessage
|
||||
<ChannelItem
|
||||
{url}
|
||||
{event}
|
||||
{replyTo}
|
||||
event={$state.snapshot(value as TrustedEvent)}
|
||||
{showPubkey}
|
||||
canEdit={canEditEvent}
|
||||
onEdit={onEditEvent} />
|
||||
@@ -316,11 +317,11 @@
|
||||
</div>
|
||||
{#key eventToEdit}
|
||||
<ChannelCompose
|
||||
bind:this={compose}
|
||||
content={eventToEdit?.content}
|
||||
{onSubmit}
|
||||
{url}
|
||||
{onEditPrevious} />
|
||||
{onSubmit}
|
||||
{onEditPrevious}
|
||||
content={eventToEdit?.content}
|
||||
bind:this={compose} />
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<div class="col-3 ml-12">
|
||||
<Content showEntire event={{...$event, content: summary}} {url} />
|
||||
<GoalSummary event={$event} {url} />
|
||||
<GoalActions event={$event} {url} />
|
||||
<GoalActions showRoom event={$event} {url} />
|
||||
</div>
|
||||
</NoteCard>
|
||||
{#if !showAll && $replies.length > 4}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {derived} from "svelte/store"
|
||||
import {page} from "$app/stores"
|
||||
import {groupBy, ago, MONTH, first, last, uniq, avg, overlappingPairs} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {MESSAGE, getTagValue} from "@welshman/util"
|
||||
import History from "@assets/icons/history.svg?dataurl"
|
||||
import {createScroller} from "@lib/html"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import ConversationCard from "@app/components/ConversationCard.svelte"
|
||||
import {decodeRelay, deriveEventsForUrl} from "@app/core/state"
|
||||
|
||||
const url = decodeRelay($page.params.relay!)
|
||||
const since = ago(MONTH)
|
||||
const messages = deriveEventsForUrl(url, [{kinds: [MESSAGE], since}])
|
||||
|
||||
const conversations = derived(messages, $messages => {
|
||||
const convs = []
|
||||
|
||||
for (const [room, messages] of groupBy(e => getTagValue("h", e.tags), $messages).entries()) {
|
||||
const avgTime = avg(overlappingPairs(messages).map(([a, b]) => a.created_at - b.created_at))
|
||||
const groups: TrustedEvent[][] = []
|
||||
const group: TrustedEvent[] = []
|
||||
|
||||
// Group conversations by time between messages
|
||||
let prevCreatedAt = messages[0].created_at
|
||||
for (const message of messages) {
|
||||
if (prevCreatedAt - message.created_at < avgTime) {
|
||||
group.push(message)
|
||||
} else {
|
||||
groups.push(group.splice(0))
|
||||
}
|
||||
|
||||
prevCreatedAt = message.created_at
|
||||
}
|
||||
|
||||
if (group.length > 0) {
|
||||
groups.push(group.splice(0))
|
||||
}
|
||||
|
||||
// Convert each group into a conversation
|
||||
for (const events of groups) {
|
||||
if (events.length < 2) {
|
||||
continue
|
||||
}
|
||||
|
||||
const latest = first(events)!
|
||||
const earliest = last(events)!
|
||||
const participants = uniq(events.map(msg => msg.pubkey))
|
||||
|
||||
convs.push({room, events, latest, earliest, participants})
|
||||
}
|
||||
}
|
||||
|
||||
return convs
|
||||
})
|
||||
|
||||
let limit = $state(3)
|
||||
let element: Element | undefined = $state()
|
||||
|
||||
onMount(() => {
|
||||
const scroller = createScroller({
|
||||
element: element!,
|
||||
onScroll: () => {
|
||||
limit += 3
|
||||
},
|
||||
})
|
||||
|
||||
return () => scroller.stop()
|
||||
})
|
||||
</script>
|
||||
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div class="center">
|
||||
<Icon icon={History} />
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<strong>Recent Activity</strong>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div class="row-2">
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
|
||||
<div bind:this={element}>
|
||||
<PageContent class="flex flex-col gap-2 p-2 pt-4">
|
||||
{#if $conversations.length === 0}
|
||||
{#if $messages.length > 0}
|
||||
{@const events = $messages.slice(0, 1)}
|
||||
{@const event = events[0]}
|
||||
{@const room = getTagValue("h", event.tags)}
|
||||
<ConversationCard
|
||||
{url}
|
||||
{room}
|
||||
{events}
|
||||
latest={event}
|
||||
earliest={event}
|
||||
participants={[event.pubkey]} />
|
||||
{:else}
|
||||
<div class="py-8 text-center opacity-70">
|
||||
<p>No recent conversations</p>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#each $conversations.slice(0, limit) as { room, events, latest, earliest, participants } (latest.id)}
|
||||
<ConversationCard {url} {room} {events} {latest} {earliest} {participants} />
|
||||
{/each}
|
||||
{/if}
|
||||
</PageContent>
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@
|
||||
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
|
||||
<div class="col-3 ml-12">
|
||||
<Content showEntire event={$event} {url} />
|
||||
<ThreadActions event={$event} {url} />
|
||||
<ThreadActions showRoom event={$event} {url} />
|
||||
</div>
|
||||
</NoteCard>
|
||||
{#if !showAll && $replies.length > 4}
|
||||
|
||||
Reference in New Issue
Block a user