From 437cfa7bc4d67d3a981c6fcf97d0a32ab214f63b Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Thu, 15 Aug 2024 14:30:55 -0700 Subject: [PATCH] Get chat view started --- src/app.css | 4 ++ src/app/components/GroupNote.svelte | 48 +++++++++++++++++ src/app/components/PrimaryNav.svelte | 9 +--- src/app/state.ts | 23 ++++---- src/assets/icons/Arrow Right.svg | 3 ++ src/lib/components/Avatar.svelte | 17 ++++++ src/lib/components/Icon.svelte | 2 + src/lib/util.ts | 30 +++++++++++ src/routes/spaces/[nom]/+page.svelte | 78 ++++++++++++++++++++++++++-- src/routes/spaces/[nom]/+page.ts | 1 + 10 files changed, 195 insertions(+), 20 deletions(-) create mode 100644 src/app/components/GroupNote.svelte create mode 100644 src/assets/icons/Arrow Right.svg create mode 100644 src/lib/components/Avatar.svelte create mode 100644 src/routes/spaces/[nom]/+page.ts diff --git a/src/app.css b/src/app.css index 25e6d2e..386b8cf 100644 --- a/src/app.css +++ b/src/app.css @@ -79,3 +79,7 @@ .input input::placeholder { opacity: 0.5; } + +.shadow-top-xl { + @apply shadow-[0_20px_25px_-5px_rgb(0,0,0,0.1)_0_8px_10px_-6px_rgb(0,0,0,0.1)]; +} diff --git a/src/app/components/GroupNote.svelte b/src/app/components/GroupNote.svelte new file mode 100644 index 0000000..37b5430 --- /dev/null +++ b/src/app/components/GroupNote.svelte @@ -0,0 +1,48 @@ + + +
+ {#if event.kind === GROUP_REPLY} +
+
+ + +

{displayProfile($parentProfile, displayPubkey($parentEvent.pubkey))}

+

{$parentEvent.content}

+
+
+ {/if} +
+ {#if showPubkey} + + {:else} +
+ {/if} +
+ {#if showPubkey} + {displayProfile($profile, displayPubkey(event.pubkey))} + {/if} +

{event.content}

+
+
+
diff --git a/src/app/components/PrimaryNav.svelte b/src/app/components/PrimaryNav.svelte index 94439d1..db9a339 100644 --- a/src/app/components/PrimaryNav.svelte +++ b/src/app/components/PrimaryNav.svelte @@ -13,6 +13,7 @@ import {quintOut} from 'svelte/easing' import {identity, nth} from '@welshman/lib' import Icon from "@lib/components/Icon.svelte" + import Avatar from "@lib/components/Avatar.svelte" import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte" import SpaceAdd from '@app/components/SpaceAdd.svelte' import {session} from "@app/base" @@ -53,13 +54,7 @@
-
- {#if $userProfile?.picture} - - {:else} - - {/if} -
+
{#each $userGroupsByNom.entries() as [nom, qualifiedGroups] (nom)} {@const qualifiedGroup = qualifiedGroups[0]} diff --git a/src/app/state.ts b/src/app/state.ts index d3f0946..db1989f 100644 --- a/src/app/state.ts +++ b/src/app/state.ts @@ -2,8 +2,8 @@ import type {Readable} from "svelte/store" import type {FuseResult} from 'fuse.js' import {get, writable, readable, derived} from "svelte/store" import type {Maybe} from "@welshman/lib" -import {uniq, uniqBy, groupBy, pushToMapKey, nthEq, batcher, postJson, stripProtocol, assoc, indexBy, now} from "@welshman/lib" -import {getIdentifier, getRelayTags, getRelayTagValues, normalizeRelayUrl, getPubkeyTagValues, GROUP_META, PROFILE, RELAYS, FOLLOWS, MUTES, GROUPS, getGroupTags, readProfile, readList, asDecryptedEvent, editList, makeList, createList} from "@welshman/util" +import {max, uniq, between, uniqBy, groupBy, pushToMapKey, nthEq, batcher, postJson, stripProtocol, assoc, indexBy, now} from "@welshman/lib" +import {getIdentifier, getRelayTags, getRelayTagValues, normalizeRelayUrl, getPubkeyTagValues, GROUP_META, PROFILE, RELAYS, FOLLOWS, MUTES, GROUPS, getGroupTags, readProfile, readList, asDecryptedEvent, editList, makeList, createList, GROUP_JOIN, GROUP_ADD_USER} from "@welshman/util" import type {Filter, SignedEvent, CustomEvent, PublishedProfile, PublishedList} from '@welshman/util' import type {SubscribeRequest, PublishRequest} from '@welshman/net' import {publish as basePublish, subscribe} from '@welshman/net' @@ -75,16 +75,16 @@ export const publish = (request: PublishRequest) => { } export const load = (request: SubscribeRequest) => - new Promise>(resolve => { + new Promise(resolve => { const sub = subscribe({closeOnEose: true, timeout: 3000, delay: 50, ...request}) + const events: CustomEvent[] = [] sub.emitter.on('event', (url: string, e: SignedEvent) => { repository.publish(e) - sub.close() - resolve(e) + events.push(e) }) - sub.emitter.on('complete', () => resolve(undefined)) + sub.emitter.on('complete', () => resolve(events)) }) // Freshness @@ -448,7 +448,7 @@ export const groupMemberships = deriveEventsMapped(rep export const { indexStore: groupMembershipByPubkey, - getIndex: getGroupMembersipByPubkey, + getIndex: getGroupMembershipsByPubkey, deriveItem: deriveGroupMembership, loadItem: loadGroupMembership, } = createCollection({ @@ -473,7 +473,7 @@ export type GroupMessage = { export const readGroupMessage = (event: CustomEvent): Maybe => { const nom = event.tags.find(nthEq(0, 'h'))?.[1] - if (!nom) { + if (!nom || between(GROUP_ADD_USER - 1, GROUP_JOIN + 1, event.kind)) { return undefined } @@ -501,7 +501,7 @@ export const groupConversations = derived(groupMessages, $groupMessages => { export const { indexStore: groupConversationByNom, - getIndex: getGroupMembersipByNom, + getIndex: getGroupConversationsByNom, deriveItem: deriveGroupConversation, loadItem: loadGroupConversation, } = createCollection({ @@ -510,12 +510,15 @@ export const { getKey: groupConversation => groupConversation.nom, load: (nom: string, hints = [], request: Partial = {}) => { const relays = [...hints, ...get(relayUrlsByNom).get(nom) || []] + const conversation = get(groupConversations).find(c => c.nom === nom) + const timestamps = conversation?.messages.map(m => m.event.created_at) || [] + const since = Math.min(0, max(timestamps) - 3600) if (relays.length === 0) { console.warn(`Attempted to load conversation for ${nom} with no qualified groups`) } - return load({...request, relays, filters: [{'#h': [nom]}]}) + return load({...request, relays, filters: [{'#h': [nom], since}]}) }, }) diff --git a/src/assets/icons/Arrow Right.svg b/src/assets/icons/Arrow Right.svg new file mode 100644 index 0000000..938becc --- /dev/null +++ b/src/assets/icons/Arrow Right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/lib/components/Avatar.svelte b/src/lib/components/Avatar.svelte new file mode 100644 index 0000000..29fbf0f --- /dev/null +++ b/src/lib/components/Avatar.svelte @@ -0,0 +1,17 @@ + + +
+ {#if src} + + {:else} + + {/if} +
diff --git a/src/lib/components/Icon.svelte b/src/lib/components/Icon.svelte index 0ec4d59..e54566b 100644 --- a/src/lib/components/Icon.svelte +++ b/src/lib/components/Icon.svelte @@ -12,6 +12,7 @@ import AddCircle from "@assets/icons/Add Circle.svg?dataurl" import AltArrowRight from "@assets/icons/Alt Arrow Right.svg?dataurl" import AltArrowLeft from "@assets/icons/Alt Arrow Left.svg?dataurl" + import ArrowRight from "@assets/icons/Arrow Right.svg?dataurl" import CheckCircle from "@assets/icons/Check Circle.svg?dataurl" import ClipboardText from "@assets/icons/Clipboard Text.svg?dataurl" import CloseCircle from "@assets/icons/Close Circle.svg?dataurl" @@ -48,6 +49,7 @@ "add-circle": AddCircle, "alt-arrow-right": AltArrowRight, "alt-arrow-left": AltArrowLeft, + "arrow-right": ArrowRight, "check-circle": CheckCircle, "clipboard-text": ClipboardText, "close-circle": CloseCircle, diff --git a/src/lib/util.ts b/src/lib/util.ts index faf5d7a..08f5d16 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -60,3 +60,33 @@ export const createSearch = (data: T[], opts: SearchOptions) => { searchValues: (term: string) => search(term).map(opts.getValue), } } + + +export const secondsToDate = (ts: number) => new Date(ts * 1000) + +export const dateToSeconds = (date: Date) => Math.round(date.valueOf() / 1000) + +export const getTimeZone = () => new Date().toString().match(/GMT[^\s]+/) + +export const createLocalDate = (dateString: any) => new Date(`${dateString} ${getTimeZone()}`) + +export const getLocale = () => new Intl.DateTimeFormat().resolvedOptions().locale + +export const formatTimestamp = (ts: number) => { + const formatter = new Intl.DateTimeFormat(getLocale(), { + dateStyle: "short", + timeStyle: "short", + }) + + return formatter.format(secondsToDate(ts)) +} + +export const formatTimestampAsDate = (ts: number) => { + const formatter = new Intl.DateTimeFormat(getLocale(), { + year: "numeric", + month: "long", + day: "numeric", + }) + + return formatter.format(secondsToDate(ts)) +} diff --git a/src/routes/spaces/[nom]/+page.svelte b/src/routes/spaces/[nom]/+page.svelte index 2774523..13f7b38 100644 --- a/src/routes/spaces/[nom]/+page.svelte +++ b/src/routes/spaces/[nom]/+page.svelte @@ -1,16 +1,88 @@
-
+
-
+
+ {#each elements as {type, id, value, showPubkey} (id)} + {#if type === "date"} +
+
+

{value}

+
+
+ {:else} + + {/if} + {/each} +

+ + {#if loading} + Looking for messages... + {:else} + End of message history + {/if} + +

-
+
+ diff --git a/src/routes/spaces/[nom]/+page.ts b/src/routes/spaces/[nom]/+page.ts new file mode 100644 index 0000000..9786e09 --- /dev/null +++ b/src/routes/spaces/[nom]/+page.ts @@ -0,0 +1 @@ +export const prerender = false