forked from coracle/flotilla
Add non-nip29 chat, add leave room
This commit is contained in:
+5
-4
@@ -33,7 +33,7 @@ import {
|
|||||||
getRelaysFromList,
|
getRelaysFromList,
|
||||||
RelayMode,
|
RelayMode,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {Pool, PublishStatus, AuthStatus, SocketStatus} from "@welshman/net"
|
import {Pool, AuthStatus, SocketStatus} from "@welshman/net"
|
||||||
import {Router} from "@welshman/router"
|
import {Router} from "@welshman/router"
|
||||||
import {
|
import {
|
||||||
pubkey,
|
pubkey,
|
||||||
@@ -52,10 +52,8 @@ import {
|
|||||||
dropSession,
|
dropSession,
|
||||||
tagEventForComment,
|
tagEventForComment,
|
||||||
tagEventForQuote,
|
tagEventForQuote,
|
||||||
thunkIsComplete,
|
|
||||||
getThunkError,
|
getThunkError,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import type {Thunk} from "@welshman/app"
|
|
||||||
import {
|
import {
|
||||||
tagRoom,
|
tagRoom,
|
||||||
PROTECTED,
|
PROTECTED,
|
||||||
@@ -177,7 +175,10 @@ export const removeSpaceMembership = async (url: string) => {
|
|||||||
|
|
||||||
export const addRoomMembership = async (url: string, room: string) => {
|
export const addRoomMembership = async (url: string, room: string) => {
|
||||||
const list = get(userMembership) || makeList({kind: GROUPS})
|
const list = get(userMembership) || makeList({kind: GROUPS})
|
||||||
const newTags = [["r", url], ["group", room, url]]
|
const newTags = [
|
||||||
|
["r", url],
|
||||||
|
["group", room, url],
|
||||||
|
]
|
||||||
const event = await addToListPublicly(list, ...newTags).reconcile(nip44EncryptToSelf)
|
const event = await addToListPublicly(list, ...newTags).reconcile(nip44EncryptToSelf)
|
||||||
const relays = uniq([...Router.get().FromUser().getUrls(), ...getRelayTagValues(event.tags)])
|
const relays = uniq([...Router.get().FromUser().getUrls(), ...getRelayTagValues(event.tags)])
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,13 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: string
|
url: string
|
||||||
room: string
|
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
replyTo?: (event: TrustedEvent) => void
|
replyTo?: (event: TrustedEvent) => void
|
||||||
showPubkey?: boolean
|
showPubkey?: boolean
|
||||||
inert?: boolean
|
inert?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const {url, room, event, replyTo = undefined, showPubkey = false, inert = false}: Props = $props()
|
const {url, event, replyTo = undefined, showPubkey = false, inert = false}: Props = $props()
|
||||||
|
|
||||||
const thunk = $thunks[event.id]
|
const thunk = $thunks[event.id]
|
||||||
const today = formatTimestampAsDate(now())
|
const today = formatTimestampAsDate(now())
|
||||||
@@ -95,7 +94,7 @@
|
|||||||
<button
|
<button
|
||||||
class="join absolute right-1 top-1 border border-solid border-neutral text-xs opacity-0 transition-all"
|
class="join absolute right-1 top-1 border border-solid border-neutral text-xs opacity-0 transition-all"
|
||||||
class:group-hover:opacity-100={!isMobile}>
|
class:group-hover:opacity-100={!isMobile}>
|
||||||
<ChannelMessageEmojiButton {url} {room} {event} />
|
<ChannelMessageEmojiButton {url} {event} />
|
||||||
{#if replyTo}
|
{#if replyTo}
|
||||||
<Button class="btn join-item btn-xs" onclick={reply}>
|
<Button class="btn join-item btn-xs" onclick={reply}>
|
||||||
<Icon icon="reply" size={4} />
|
<Icon icon="reply" size={4} />
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {noop} from "@welshman/lib"
|
|
||||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import {publishReaction} from "@app/commands"
|
import {publishReaction} from "@app/commands"
|
||||||
|
|
||||||
const {url, room, event} = $props()
|
const {url, event} = $props()
|
||||||
|
|
||||||
// Tell svelte-check to shut up
|
|
||||||
noop(room)
|
|
||||||
|
|
||||||
const onEmoji = (emoji: NativeEmoji) =>
|
const onEmoji = (emoji: NativeEmoji) =>
|
||||||
publishReaction({event, relays: [url], content: emoji.unicode})
|
publishReaction({event, relays: [url], content: emoji.unicode})
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
const {url} = $props()
|
const {url} = $props()
|
||||||
|
|
||||||
const relay = deriveRelay(url)
|
const relay = deriveRelay(url)
|
||||||
|
const chatPath = makeSpacePath(url, "chat")
|
||||||
const threadsPath = makeSpacePath(url, "threads")
|
const threadsPath = makeSpacePath(url, "threads")
|
||||||
const calendarPath = makeSpacePath(url, "calendar")
|
const calendarPath = makeSpacePath(url, "calendar")
|
||||||
const userRooms = deriveUserRooms(url)
|
const userRooms = deriveUserRooms(url)
|
||||||
@@ -128,29 +129,38 @@
|
|||||||
notification={$notifications.has(calendarPath)}>
|
notification={$notifications.has(calendarPath)}>
|
||||||
<Icon icon="calendar-minimalistic" /> Calendar
|
<Icon icon="calendar-minimalistic" /> Calendar
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
<div class="h-2"></div>
|
|
||||||
<SecondaryNavHeader>Your Rooms</SecondaryNavHeader>
|
|
||||||
{#each $userRooms as room, i (room)}
|
|
||||||
<MenuSpaceRoomItem {replaceState} notify {url} {room} />
|
|
||||||
{/each}
|
|
||||||
{#if $otherRooms.length > 0}
|
|
||||||
<div class="h-2"></div>
|
|
||||||
<SecondaryNavHeader>
|
|
||||||
{#if $userRooms.length > 0}
|
|
||||||
Other Rooms
|
|
||||||
{:else}
|
|
||||||
Rooms
|
|
||||||
{/if}
|
|
||||||
</SecondaryNavHeader>
|
|
||||||
{/if}
|
|
||||||
{#each $otherRooms as room, i (room)}
|
|
||||||
<MenuSpaceRoomItem {replaceState} {url} {room} />
|
|
||||||
{/each}
|
|
||||||
{#if hasNip29($relay)}
|
{#if hasNip29($relay)}
|
||||||
|
{#if $userRooms.length > 0}
|
||||||
|
<div class="h-2"></div>
|
||||||
|
<SecondaryNavHeader>Your Rooms</SecondaryNavHeader>
|
||||||
|
{/if}
|
||||||
|
{#each $userRooms as room, i (room)}
|
||||||
|
<MenuSpaceRoomItem {replaceState} notify {url} {room} />
|
||||||
|
{/each}
|
||||||
|
{#if $otherRooms.length > 0}
|
||||||
|
<div class="h-2"></div>
|
||||||
|
<SecondaryNavHeader>
|
||||||
|
{#if $userRooms.length > 0}
|
||||||
|
Other Rooms
|
||||||
|
{:else}
|
||||||
|
Rooms
|
||||||
|
{/if}
|
||||||
|
</SecondaryNavHeader>
|
||||||
|
{/if}
|
||||||
|
{#each $otherRooms as room, i (room)}
|
||||||
|
<MenuSpaceRoomItem {replaceState} {url} {room} />
|
||||||
|
{/each}
|
||||||
<SecondaryNavItem {replaceState} onclick={addRoom}>
|
<SecondaryNavItem {replaceState} onclick={addRoom}>
|
||||||
<Icon icon="add-circle" />
|
<Icon icon="add-circle" />
|
||||||
Create room
|
Create room
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
|
{:else}
|
||||||
|
<SecondaryNavItem
|
||||||
|
{replaceState}
|
||||||
|
href={chatPath}
|
||||||
|
notification={$notifications.has(chatPath)}>
|
||||||
|
<Icon icon="chat-round" /> Chat
|
||||||
|
</SecondaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</SecondaryNavSection>
|
</SecondaryNavSection>
|
||||||
|
|||||||
+7
-2
@@ -636,7 +636,12 @@ export enum MembershipStatus {
|
|||||||
|
|
||||||
export const deriveUserMembershipStatus = (url: string, room: string) =>
|
export const deriveUserMembershipStatus = (url: string, room: string) =>
|
||||||
derived(
|
derived(
|
||||||
[pubkey, deriveEventsForUrl(url, [{kinds: [GROUP_JOIN, GROUP_ADD_USER, GROUP_REMOVE_USER], '#h': [room]}])],
|
[
|
||||||
|
pubkey,
|
||||||
|
deriveEventsForUrl(url, [
|
||||||
|
{kinds: [GROUP_JOIN, GROUP_ADD_USER, GROUP_REMOVE_USER], "#h": [room]},
|
||||||
|
]),
|
||||||
|
],
|
||||||
([$pubkey, $events]) => {
|
([$pubkey, $events]) => {
|
||||||
let status = MembershipStatus.Initial
|
let status = MembershipStatus.Initial
|
||||||
|
|
||||||
@@ -655,7 +660,7 @@ export const deriveUserMembershipStatus = (url: string, room: string) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return status
|
return status
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Other utils
|
// Other utils
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from 'classnames'
|
import cx from "classnames"
|
||||||
import {readable} from "svelte/store"
|
import {readable} from "svelte/store"
|
||||||
import {onMount, onDestroy} from "svelte"
|
import {onMount, onDestroy} from "svelte"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
@@ -7,8 +7,15 @@
|
|||||||
import {now, formatTimestampAsDate} from "@welshman/lib"
|
import {now, formatTimestampAsDate} from "@welshman/lib"
|
||||||
import {request} from "@welshman/net"
|
import {request} from "@welshman/net"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {createEvent, MESSAGE, DELETE, REACTION, GROUP_ADD_USER, GROUP_REMOVE_USER} from "@welshman/util"
|
import {
|
||||||
import {pubkey, publishThunk, deriveRelay, getThunkError, waitForThunkCompletion} from "@welshman/app"
|
createEvent,
|
||||||
|
MESSAGE,
|
||||||
|
DELETE,
|
||||||
|
REACTION,
|
||||||
|
GROUP_ADD_USER,
|
||||||
|
GROUP_REMOVE_USER,
|
||||||
|
} from "@welshman/util"
|
||||||
|
import {pubkey, publishThunk, getThunkError} from "@welshman/app"
|
||||||
import {slide, fade, fly} from "@lib/transition"
|
import {slide, fade, fly} from "@lib/transition"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
@@ -26,7 +33,6 @@
|
|||||||
userSettingValues,
|
userSettingValues,
|
||||||
decodeRelay,
|
decodeRelay,
|
||||||
tagRoom,
|
tagRoom,
|
||||||
displayChannel,
|
|
||||||
getEventsForUrl,
|
getEventsForUrl,
|
||||||
deriveUserMembershipStatus,
|
deriveUserMembershipStatus,
|
||||||
deriveChannel,
|
deriveChannel,
|
||||||
@@ -240,15 +246,17 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
|
|
||||||
const req = request({
|
request({
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
relays: [url],
|
relays: [url],
|
||||||
filters: [{
|
filters: [
|
||||||
kinds: [GROUP_ADD_USER, GROUP_REMOVE_USER],
|
{
|
||||||
'#p': [$pubkey!],
|
kinds: [GROUP_ADD_USER, GROUP_REMOVE_USER],
|
||||||
'#h': [room],
|
"#p": [$pubkey!],
|
||||||
limit: 10,
|
"#h": [room],
|
||||||
}],
|
limit: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
@@ -291,11 +299,20 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet action()}
|
{#snippet action()}
|
||||||
<div class="row-2">
|
<div class="row-2">
|
||||||
|
{#if $membershipStatus !== MembershipStatus.Initial}
|
||||||
|
<Button
|
||||||
|
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||||
|
data-tip="Request to be removed from member list"
|
||||||
|
disabled={leaving}
|
||||||
|
onclick={leave}>
|
||||||
|
<Icon size={4} icon="arrows-a-logout-2" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
<Button
|
<Button
|
||||||
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
class="btn btn-neutral btn-sm tooltip tooltip-left"
|
||||||
data-tip={isFavorite ? "Remove Favorite" : "Add Favorite"}
|
data-tip={isFavorite ? "Remove Favorite" : "Add Favorite"}
|
||||||
onclick={isFavorite ? removeFavorite : addFavorite}>
|
onclick={isFavorite ? removeFavorite : addFavorite}>
|
||||||
<Icon size={4} icon="bookmark" class={cx({'text-primary': isFavorite})} />
|
<Icon size={4} icon="bookmark" class={cx({"text-primary": isFavorite})} />
|
||||||
</Button>
|
</Button>
|
||||||
<MenuSpaceButton {url} />
|
<MenuSpaceButton {url} />
|
||||||
</div>
|
</div>
|
||||||
@@ -307,9 +324,7 @@
|
|||||||
{#if $channel?.private && $membershipStatus !== MembershipStatus.Granted}
|
{#if $channel?.private && $membershipStatus !== MembershipStatus.Granted}
|
||||||
<div class="py-20">
|
<div class="py-20">
|
||||||
<div class="card2 col-8 m-auto max-w-md items-center text-center">
|
<div class="card2 col-8 m-auto max-w-md items-center text-center">
|
||||||
<p class="row-2">
|
<p class="row-2">You aren't currently a member of this room.</p>
|
||||||
You aren't currently a member of this room.
|
|
||||||
</p>
|
|
||||||
{#if $membershipStatus === MembershipStatus.Pending}
|
{#if $membershipStatus === MembershipStatus.Pending}
|
||||||
<Button class="btn btn-neutral btn-sm" disabled={leaving} onclick={leave}>
|
<Button class="btn btn-neutral btn-sm" disabled={leaving} onclick={leave}>
|
||||||
<Icon icon="clock-circle" />
|
<Icon icon="clock-circle" />
|
||||||
@@ -344,7 +359,6 @@
|
|||||||
<div in:slide class:-mt-1={!showPubkey}>
|
<div in:slide class:-mt-1={!showPubkey}>
|
||||||
<ChannelMessage
|
<ChannelMessage
|
||||||
{url}
|
{url}
|
||||||
{room}
|
|
||||||
{replyTo}
|
{replyTo}
|
||||||
event={$state.snapshot(value as TrustedEvent)}
|
event={$state.snapshot(value as TrustedEvent)}
|
||||||
{showPubkey} />
|
{showPubkey} />
|
||||||
@@ -365,7 +379,7 @@
|
|||||||
{#if $channel?.private && $membershipStatus !== MembershipStatus.Granted}
|
{#if $channel?.private && $membershipStatus !== MembershipStatus.Granted}
|
||||||
<!-- pass -->
|
<!-- pass -->
|
||||||
{:else if $channel?.closed && $membershipStatus !== MembershipStatus.Granted}
|
{:else if $channel?.closed && $membershipStatus !== MembershipStatus.Granted}
|
||||||
<div class="flex flex-row items-center justify-between m-4 px-4 py-3 card bg-alt">
|
<div class="bg-alt card m-4 flex flex-row items-center justify-between px-4 py-3">
|
||||||
<p>Only members are allowed to post to this room.</p>
|
<p>Only members are allowed to post to this room.</p>
|
||||||
{#if $membershipStatus === MembershipStatus.Pending}
|
{#if $membershipStatus === MembershipStatus.Pending}
|
||||||
<Button class="btn btn-neutral btn-sm" disabled={leaving} onclick={leave}>
|
<Button class="btn btn-neutral btn-sm" disabled={leaving} onclick={leave}>
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {readable} from "svelte/store"
|
||||||
|
import {onMount, onDestroy} from "svelte"
|
||||||
|
import {page} from "$app/stores"
|
||||||
|
import type {Readable} from "svelte/store"
|
||||||
|
import {now, formatTimestampAsDate} from "@welshman/lib"
|
||||||
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
|
import {createEvent, MESSAGE, DELETE, REACTION} from "@welshman/util"
|
||||||
|
import {pubkey, publishThunk} from "@welshman/app"
|
||||||
|
import {slide, fade, fly} from "@lib/transition"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
|
import PageBar from "@lib/components/PageBar.svelte"
|
||||||
|
import PageContent from "@lib/components/PageContent.svelte"
|
||||||
|
import Divider from "@lib/components/Divider.svelte"
|
||||||
|
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||||
|
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
||||||
|
import ChannelCompose from "@app/components/ChannelCompose.svelte"
|
||||||
|
import ChannelComposeParent from "@app/components/ChannelComposeParent.svelte"
|
||||||
|
import {userSettingValues, decodeRelay, getEventsForUrl} from "@app/state"
|
||||||
|
import {setChecked, checked} from "@app/notifications"
|
||||||
|
import {prependParent} from "@app/commands"
|
||||||
|
import {PROTECTED} from "@app/state"
|
||||||
|
import {makeFeed} from "@app/requests"
|
||||||
|
import {popKey} from "@app/implicit"
|
||||||
|
|
||||||
|
const mounted = now()
|
||||||
|
const lastChecked = $checked[$page.url.pathname]
|
||||||
|
const url = decodeRelay($page.params.relay)
|
||||||
|
const filter = {kinds: [MESSAGE]}
|
||||||
|
|
||||||
|
const replyTo = (event: TrustedEvent) => {
|
||||||
|
parent = event
|
||||||
|
compose?.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearParent = () => {
|
||||||
|
parent = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearShare = () => {
|
||||||
|
share = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = ({content, tags}: EventContent) => {
|
||||||
|
tags.push(PROTECTED)
|
||||||
|
|
||||||
|
let template = {content, tags}
|
||||||
|
|
||||||
|
if (share) {
|
||||||
|
template = prependParent(share, template)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
template = prependParent(parent, template)
|
||||||
|
}
|
||||||
|
|
||||||
|
publishThunk({
|
||||||
|
relays: [url],
|
||||||
|
event: createEvent(MESSAGE, template),
|
||||||
|
delay: $userSettingValues.send_delay,
|
||||||
|
})
|
||||||
|
|
||||||
|
clearParent()
|
||||||
|
clearShare()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onScroll = () => {
|
||||||
|
showScrollButton = Math.abs(element?.scrollTop || 0) > 1500
|
||||||
|
|
||||||
|
if (!newMessages || newMessagesSeen) {
|
||||||
|
showFixedNewMessages = false
|
||||||
|
} else {
|
||||||
|
const {y} = newMessages.getBoundingClientRect()
|
||||||
|
|
||||||
|
if (y > 300) {
|
||||||
|
newMessagesSeen = true
|
||||||
|
} else {
|
||||||
|
showFixedNewMessages = y < 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToNewMessages = () =>
|
||||||
|
newMessages?.scrollIntoView({behavior: "smooth", block: "center"})
|
||||||
|
|
||||||
|
const scrollToBottom = () => element?.scrollTo({top: 0, behavior: "smooth"})
|
||||||
|
|
||||||
|
let loadingEvents = $state(true)
|
||||||
|
let share = $state(popKey<TrustedEvent | undefined>("share"))
|
||||||
|
let parent: TrustedEvent | undefined = $state()
|
||||||
|
let element: HTMLElement | undefined = $state()
|
||||||
|
let newMessages: HTMLElement | undefined = $state()
|
||||||
|
let chatCompose: HTMLElement | undefined = $state()
|
||||||
|
let dynamicPadding: HTMLElement | undefined = $state()
|
||||||
|
let newMessagesSeen = false
|
||||||
|
let showFixedNewMessages = $state(false)
|
||||||
|
let showScrollButton = $state(false)
|
||||||
|
let cleanup: () => void
|
||||||
|
let events: Readable<TrustedEvent[]> = $state(readable([]))
|
||||||
|
let compose: ChannelCompose | undefined = $state()
|
||||||
|
|
||||||
|
const elements = $derived.by(() => {
|
||||||
|
const elements = []
|
||||||
|
const seen = new Set()
|
||||||
|
|
||||||
|
let previousDate
|
||||||
|
let previousPubkey
|
||||||
|
let newMessagesSeen = false
|
||||||
|
|
||||||
|
if (events) {
|
||||||
|
const lastUserEvent = $events.find(e => e.pubkey === $pubkey)
|
||||||
|
|
||||||
|
// Adjust last checked to account for messages that came from a different device
|
||||||
|
const adjustedLastChecked =
|
||||||
|
lastChecked && lastUserEvent ? Math.max(lastUserEvent.created_at, lastChecked) : lastChecked
|
||||||
|
|
||||||
|
for (const event of $events.toReversed()) {
|
||||||
|
if (seen.has(event.id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = formatTimestampAsDate(event.created_at)
|
||||||
|
|
||||||
|
if (
|
||||||
|
!newMessagesSeen &&
|
||||||
|
adjustedLastChecked &&
|
||||||
|
event.pubkey !== $pubkey &&
|
||||||
|
event.created_at > adjustedLastChecked &&
|
||||||
|
event.created_at < mounted
|
||||||
|
) {
|
||||||
|
elements.push({type: "new-messages", id: "new-messages"})
|
||||||
|
newMessagesSeen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date !== previousDate) {
|
||||||
|
elements.push({type: "date", value: date, id: date, showPubkey: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.push({
|
||||||
|
id: event.id,
|
||||||
|
type: "note",
|
||||||
|
value: event,
|
||||||
|
showPubkey: date !== previousDate || previousPubkey !== event.pubkey,
|
||||||
|
})
|
||||||
|
|
||||||
|
previousDate = date
|
||||||
|
previousPubkey = event.pubkey
|
||||||
|
seen.add(event.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.reverse()
|
||||||
|
|
||||||
|
setTimeout(onScroll, 100)
|
||||||
|
|
||||||
|
return elements
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const controller = new AbortController()
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(() => {
|
||||||
|
if (dynamicPadding && chatCompose) {
|
||||||
|
dynamicPadding!.style.minHeight = `${chatCompose!.offsetHeight}px`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
observer.observe(chatCompose!)
|
||||||
|
observer.observe(dynamicPadding!)
|
||||||
|
|
||||||
|
const feed = makeFeed({
|
||||||
|
element: element!,
|
||||||
|
relays: [url],
|
||||||
|
feedFilters: [filter],
|
||||||
|
subscriptionFilters: [{kinds: [DELETE, REACTION, MESSAGE], since: now()}],
|
||||||
|
initialEvents: getEventsForUrl(url, [{...filter, limit: 20}]),
|
||||||
|
onExhausted: () => {
|
||||||
|
loadingEvents = false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
events = feed.events
|
||||||
|
cleanup = feed.cleanup
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort()
|
||||||
|
observer.unobserve(chatCompose!)
|
||||||
|
observer.unobserve(dynamicPadding!)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
// Sveltekit calls onDestroy at the beginning of the page load for some reason
|
||||||
|
setTimeout(() => {
|
||||||
|
setChecked($page.url.pathname)
|
||||||
|
}, 800)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<PageBar>
|
||||||
|
{#snippet icon()}
|
||||||
|
<div class="center">
|
||||||
|
<Icon icon="chat-round" />
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet title()}
|
||||||
|
<strong>Chat</strong>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet action()}
|
||||||
|
<MenuSpaceButton {url} />
|
||||||
|
{/snippet}
|
||||||
|
</PageBar>
|
||||||
|
|
||||||
|
<PageContent bind:element onscroll={onScroll} class="flex flex-col-reverse pt-4">
|
||||||
|
<div bind:this={dynamicPadding}></div>
|
||||||
|
{#each elements as { type, id, value, showPubkey } (id)}
|
||||||
|
{#if type === "new-messages"}
|
||||||
|
<div
|
||||||
|
bind:this={newMessages}
|
||||||
|
class="flex items-center py-2 text-xs transition-colors"
|
||||||
|
class:opacity-0={showFixedNewMessages}>
|
||||||
|
<div class="h-px flex-grow bg-primary"></div>
|
||||||
|
<p class="rounded-full bg-primary px-2 py-1 text-primary-content">New Messages</p>
|
||||||
|
<div class="h-px flex-grow bg-primary"></div>
|
||||||
|
</div>
|
||||||
|
{:else if type === "date"}
|
||||||
|
<Divider>{value}</Divider>
|
||||||
|
{:else}
|
||||||
|
<div in:slide class:-mt-1={!showPubkey}>
|
||||||
|
<ChannelMessage
|
||||||
|
{url}
|
||||||
|
{replyTo}
|
||||||
|
event={$state.snapshot(value as TrustedEvent)}
|
||||||
|
{showPubkey} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
<p class="flex h-10 items-center justify-center py-20">
|
||||||
|
{#if loadingEvents}
|
||||||
|
<Spinner loading={loadingEvents}>Looking for messages...</Spinner>
|
||||||
|
{:else}
|
||||||
|
<Spinner>End of message history</Spinner>
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
</PageContent>
|
||||||
|
|
||||||
|
<div class="chat__compose bg-base-200" bind:this={chatCompose}>
|
||||||
|
<div>
|
||||||
|
{#if parent}
|
||||||
|
<ChannelComposeParent event={parent} clear={clearParent} verb="Replying to" />
|
||||||
|
{/if}
|
||||||
|
{#if share}
|
||||||
|
<ChannelComposeParent event={share} clear={clearShare} verb="Sharing" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<ChannelCompose bind:this={compose} {onSubmit} {url} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if showScrollButton}
|
||||||
|
<div in:fade class="chat__scroll-down">
|
||||||
|
<Button class="btn btn-circle btn-neutral" onclick={scrollToBottom}>
|
||||||
|
<Icon icon="alt-arrow-down" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if showFixedNewMessages}
|
||||||
|
<div class="relative z-feature flex justify-center">
|
||||||
|
<div transition:fly={{duration: 200}} class="fixed top-12">
|
||||||
|
<Button class="btn btn-primary btn-xs rounded-full" onclick={scrollToNewMessages}>
|
||||||
|
New Messages
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
Reference in New Issue
Block a user