diff --git a/src/app.css b/src/app.css
index 7bec8383..789fe8e5 100644
--- a/src/app.css
+++ b/src/app.css
@@ -96,7 +96,7 @@
/* tiptap */
-.input-editor, .chat-editor {
+.input-editor, .chat-editor, .note-editor {
@apply p-1 -m-1 min-h-12;
}
@@ -113,6 +113,10 @@
@apply input input-bordered p-[.65rem] h-auto;
}
+.note-editor .tiptap[contenteditable="true"] {
+ @apply input input-bordered p-[.65rem] h-auto min-h-32 pb-6;
+}
+
.tiptap pre code {
@apply link-content block w-full;
}
diff --git a/src/app/components/ThreadCard.svelte b/src/app/components/ThreadCard.svelte
new file mode 100644
index 00000000..0d112f33
--- /dev/null
+++ b/src/app/components/ThreadCard.svelte
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
{$profileDisplay}
+
{displayPubkey(root.pubkey)}
+
+
+
{formatTimestamp(root.created_at)}
+
+
+
+
+
+ {#if replies.length > 0}
+ Show {replies.length} {replies.length === 1 ? 'reply' : 'replies'}
+ {/if}
+
diff --git a/src/app/components/ThreadCreate.svelte b/src/app/components/ThreadCreate.svelte
new file mode 100644
index 00000000..7129c9ac
--- /dev/null
+++ b/src/app/components/ThreadCreate.svelte
@@ -0,0 +1,86 @@
+
+
+
+
diff --git a/src/app/editor.ts b/src/app/editor.ts
index 1dd98ed6..212951fb 100644
--- a/src/app/editor.ts
+++ b/src/app/editor.ts
@@ -206,3 +206,4 @@ export const getNoteEditorOptions = ({uploading, sendMessage}: EditorOptions) =>
}),
],
})
+
diff --git a/src/app/state.ts b/src/app/state.ts
index cd6646f6..178603f3 100644
--- a/src/app/state.ts
+++ b/src/app/state.ts
@@ -1,9 +1,10 @@
import {nip19} from "nostr-tools"
import {get, derived} from "svelte/store"
import type {Maybe} from "@welshman/lib"
-import {setContext, nth, max, pushToMapKey, nthEq} from "@welshman/lib"
+import {setContext, partition, nth, max, pushToMapKey, nthEq} from "@welshman/lib"
import {
getIdFilters,
+ NOTE,
RELAYS,
REACTION,
ZAP_RESPONSE,
@@ -11,6 +12,8 @@ import {
EVENT_TIME,
getRelayTagValues,
isShareableRelayUrl,
+ getAncestorTags,
+ getAncestorTagValues,
} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {
@@ -93,7 +96,7 @@ export const readMembership = (event: TrustedEvent): PublishedMembership => {
roomsByUrl.set(tag[1], [])
}
- for (const tag of event.tags.filter(nthEq(0, "t"))) {
+ for (const tag of event.tags.filter(nthEq(0, "~"))) {
pushToMapKey(roomsByUrl, tag[2], tag[1])
}
@@ -191,7 +194,7 @@ export const {
},
})
-// Calendar vents
+// Calendar events
export const events = deriveEvents(repository, {filters: [{kinds: [EVENT_DATE, EVENT_TIME]}]})
@@ -207,6 +210,43 @@ export const eventsByUrl = derived([trackerStore, events], ([$tracker, $events])
return eventsByUrl
})
+// Threads
+
+export type Thread = {
+ root: TrustedEvent
+ replies: TrustedEvent[]
+}
+
+export const notes = deriveEvents(repository, {filters: [{kinds: [NOTE]}]})
+
+export const threadsByUrl = derived([trackerStore, notes], ([$tracker, $notes]) => {
+ const threadsByUrl = new Map()
+ const [parents, children] = partition(e => getAncestorTags(e.tags).replies.length === 0, $notes)
+
+ for (const event of parents) {
+ for (const url of $tracker.getRelays(event.id)) {
+ pushToMapKey(threadsByUrl, url, {root: event, replies: []})
+ }
+ }
+
+ for (const event of children) {
+ const [id] = getAncestorTagValues(event.tags).replies
+
+ for (const url of $tracker.getRelays(event.id)) {
+ const threads = threadsByUrl.get(url) || []
+ const thread = threads.find(thread => thread.root.id === id)
+
+ if (!thread) {
+ continue
+ }
+
+ thread.replies.push(event)
+ }
+ }
+
+ return threadsByUrl
+})
+
// Rooms
export const roomsByUrl = derived(chats, $chats => {
diff --git a/src/assets/icons/Calendar Add.svg b/src/assets/icons/Calendar Add.svg
new file mode 100644
index 00000000..c8be2a14
--- /dev/null
+++ b/src/assets/icons/Calendar Add.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/lib/components/Icon.svelte b/src/lib/components/Icon.svelte
index 8e9d2977..d2bf9960 100644
--- a/src/lib/components/Icon.svelte
+++ b/src/lib/components/Icon.svelte
@@ -22,6 +22,7 @@
import ArrowRight from "@assets/icons/Arrow Right.svg?dataurl"
import Bag from "@assets/icons/Bag.svg?dataurl"
import Bolt from "@assets/icons/Bolt.svg?dataurl"
+ import CalendarAdd from "@assets/icons/Calendar Add.svg?dataurl"
import CalendarMinimalistic from "@assets/icons/Calendar Minimalistic.svg?dataurl"
import ChatRound from "@assets/icons/Chat Round.svg?dataurl"
import CheckCircle from "@assets/icons/Check Circle.svg?dataurl"
@@ -87,6 +88,7 @@
"arrow-right": ArrowRight,
bag: Bag,
bolt: Bolt,
+ "calendar-add": CalendarAdd,
"calendar-minimalistic": CalendarMinimalistic,
"chat-round": ChatRound,
"check-circle": CheckCircle,
diff --git a/src/routes/spaces/[nrelay]/+layout.svelte b/src/routes/spaces/[nrelay]/+layout.svelte
index b7e81dc6..cf86e443 100644
--- a/src/routes/spaces/[nrelay]/+layout.svelte
+++ b/src/routes/spaces/[nrelay]/+layout.svelte
@@ -2,7 +2,7 @@
import {onMount} from "svelte"
import {page} from "$app/stores"
import {sort, now} from "@welshman/lib"
- import {displayRelayUrl, EVENT_DATE, EVENT_TIME, CLASSIFIED} from "@welshman/util"
+ import {displayRelayUrl, NOTE, EVENT_DATE, EVENT_TIME, CLASSIFIED} from "@welshman/util"
import {subscribe} from "@welshman/app"
import {fly, slide} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte"
@@ -16,7 +16,7 @@
import SpaceExit from "@app/components/SpaceExit.svelte"
import SpaceJoin from "@app/components/SpaceJoin.svelte"
import RoomCreate from "@app/components/RoomCreate.svelte"
- import {userMembership, roomsByUrl, decodeNRelay, GENERAL, MESSAGE, REPLY} from "@app/state"
+ import {userMembership, roomsByUrl, decodeNRelay, GENERAL, MESSAGE} from "@app/state"
import {pushModal} from "@app/modal"
import {makeSpacePath} from "@app/routes"
@@ -52,7 +52,7 @@
$: otherRooms = ($roomsByUrl.get(url) || []).filter(room => !rooms.concat(GENERAL).includes(room))
onMount(() => {
- const kinds = [MESSAGE, REPLY, EVENT_DATE, EVENT_TIME, CLASSIFIED]
+ const kinds = [NOTE, MESSAGE, EVENT_DATE, EVENT_TIME, CLASSIFIED]
const sub = subscribe({filters: [{kinds, since: now() - 30}], relays: [url]})
return () => sub.close()
diff --git a/src/routes/spaces/[nrelay]/[[room]]/+page.svelte b/src/routes/spaces/[nrelay]/[[room]]/+page.svelte
index a6b728d5..4ae538b3 100644
--- a/src/routes/spaces/[nrelay]/[[room]]/+page.svelte
+++ b/src/routes/spaces/[nrelay]/[[room]]/+page.svelte
@@ -71,9 +71,9 @@
class="flex min-h-12 items-center justify-between gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
- {room || "General"}
+ {room}
- {#if room}
+ {#if room !== GENERAL}
{#if membership.includes(room)}