Convert to simple relay-based groups from nip29

This commit is contained in:
Jon Staab
2024-09-11 10:48:34 -07:00
parent ed365f7e38
commit 4ad67921a0
20 changed files with 363 additions and 525 deletions
+13 -15
View File
@@ -3,17 +3,17 @@
import type {Readable} from "svelte/store"
import {writable} from "svelte/store"
import {createEditor, type Editor, EditorContent} from "svelte-tiptap"
import {NProfileExtension, TagExtension as TopicExtension, ImageExtension} from "nostr-editor"
import {NProfileExtension, ImageExtension} from "nostr-editor"
import {createEvent, CHAT_MESSAGE} from "@welshman/util"
import {publishThunk, makeThunk} from "@welshman/app"
import {findNodes} from "@lib/tiptap"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import {userRelayUrlsByNom} from "@app/state"
import {makeMention, makeIMeta} from "@app/commands"
import {getChatEditorOptions, addFile} from "@app/editor"
export let nom
export let url
export let topic = ""
const uploading = writable(false)
@@ -21,22 +21,20 @@
const sendMessage = () => {
const json = $editor.getJSON()
const relays = $userRelayUrlsByNom.get(nom)
const topicTag = topic ? ["t", topic] : []
const mentionTags = findNodes(NProfileExtension.name, json).map(m =>
makeMention(m.attrs!.pubkey, m.attrs!.relays),
)
const imetaTags = findNodes(ImageExtension.name, json).map(({attrs: {src, sha256: x}}: any) =>
makeIMeta(src, {x, ox: x}),
)
const event = createEvent(CHAT_MESSAGE, {
content: $editor.getText(),
tags: [
["h", nom],
...findNodes(TopicExtension.name, json).map(t => ["t", t.attrs!.name.toLowerCase()]),
...findNodes(NProfileExtension.name, json).map(m =>
makeMention(m.attrs!.pubkey, m.attrs!.relays),
),
...findNodes(ImageExtension.name, json).map(({attrs: {src, sha256: x}}: any) =>
makeIMeta(src, {x, ox: x}),
),
],
tags: [topicTag, ...mentionTags, ...imetaTags],
})
publishThunk(makeThunk({event, relays}))
publishThunk(makeThunk({event, relays: [url]}))
$editor.chain().clearContent().run()
}
+1 -7
View File
@@ -27,7 +27,7 @@
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Avatar from "@lib/components/Avatar.svelte"
import {deriveEvent} from "@app/state"
import {deriveEvent, displayReaction} from "@app/state"
import {getChatViewOptions} from "@app/editor"
export let event: TrustedEvent
@@ -66,12 +66,6 @@
const [colorName, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
const ps = derived(publishStatusData, $m => Object.values($m[event.id] || {}))
const displayReaction = (content: string) => {
if (content === "+") return "❤️"
if (content === "-") return "👎"
return content
}
const findStatus = ($ps: PublishStatusData[], statuses: PublishStatus[]) =>
$ps.find(({status}) => statuses.includes(status))
@@ -3,7 +3,6 @@
import Button from "@lib/components/Button.svelte"
import Link from "@lib/components/Link.svelte"
import Icon from "@lib/components/Icon.svelte"
import {DEFAULT_RELAYS} from "@app/state"
import {clip} from "@app/toast"
</script>
@@ -17,22 +16,8 @@
This means that anyone can host their own data, making the web more decentralized and resilient.
</p>
<p>
Only some relays support spaces. You can find a list of suggested relays below, or you can <Link
external
href="https://coracle.tools">host your own</Link
>. If you do decide to join someone else's, make sure to follow their directions for registering
as a user.
Different relays have different policies for access control and content retention. Be sure to
double check that you have access to the relays you try to use by visiting their website.
</p>
{#each DEFAULT_RELAYS as url}
<div class="card2 card2-alt card2-sm flex items-center justify-between">
<div class="flex items-center gap-2">
<Icon icon="remote-controller-minimalistic" />
{displayRelayUrl(url)}
</div>
<Button on:click={() => clip(url)}>
<Icon icon="copy" />
</Button>
</div>
{/each}
<Button class="btn btn-primary" on:click={() => history.back()}>Got it</Button>
</div>
+1 -1
View File
@@ -60,7 +60,7 @@
const pubkey = await getNip07()?.getPublicKey()
if (pubkey) {
await onSuccess({method: "extension", pubkey})
await onSuccess({method: "nip07", pubkey})
} else {
pushToast({
theme: "error",
+7 -11
View File
@@ -9,13 +9,15 @@
import {page} from "$app/stores"
import {tweened} from "svelte/motion"
import {quintOut} from "svelte/easing"
import {displayRelayUrl} from "@welshman/util"
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 {userProfile, displayGroup, userGroupsByNom} from "@app/state"
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
import {userProfile, userMembership} from "@app/state"
import {pushModal} from "@app/modal"
import {getPrimaryNavItemIndex} from "@app/routes"
import {makeSpacePath, getPrimaryNavItemIndex} from "@app/routes"
const activeOffset = tweened(-44, {
duration: 300,
@@ -49,15 +51,9 @@
class="!h-10 !w-10 border border-solid border-base-300"
size={7} />
</PrimaryNavItem>
{#each $userGroupsByNom.entries() as [nom, qualifiedGroups] (nom)}
{@const qualifiedGroup = qualifiedGroups[0]}
<PrimaryNavItem title={displayGroup(qualifiedGroup?.group)} href="/spaces/{nom}">
<Avatar
icon="ghost"
class="!h-10 !w-10 border border-solid border-base-300"
alt={displayGroup(qualifiedGroup?.group)}
src={qualifiedGroup?.group.picture}
size={7} />
{#each $userMembership?.topicsByUrl.keys() || [] as url (url)}
<PrimaryNavItem title={displayRelayUrl(url)} href={makeSpacePath(url)}>
<SpaceAvatar {url} />
</PrimaryNavItem>
{/each}
<PrimaryNavItem title="Add Space" on:click={addSpace}>
+65
View File
@@ -0,0 +1,65 @@
<script lang="ts">
import {goto} from "$app/navigation"
import {append, remove} from "@welshman/lib"
import {displayRelayUrl} from "@welshman/util"
import {deriveRelay} from "@welshman/app"
import Field from "@lib/components/Field.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Icon from "@lib/components/Icon.svelte"
import {pushModal} from "@app/modal"
import {pushToast} from "@app/toast"
import {addRoomMembership} from "@app/commands"
import {makeSpacePath} from '@app/routes'
export let url
const relay = deriveRelay(url)
const back = () => history.back()
const tryCreate = async () => {
await addRoomMembership(url, topic)
goto(makeSpacePath(url, topic))
}
const create = async () => {
loading = true
try {
await tryCreate()
} finally {
loading = false
}
}
let topic = ""
let loading = false
</script>
<form class="column gap-4" on:submit|preventDefault={create}>
<h1 class="heading">
Create a Room
</h1>
<p class="text-center">
On <span class="text-primary">{displayRelayUrl(url)}</span>
</p>
<Field>
<p slot="label">Room Name</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="hashtag" />
<input bind:value={topic} class="grow" type="text" />
</label>
</Field>
<div class="flex flex-row items-center justify-between gap-4">
<Button class="btn btn-link" on:click={back}>
<Icon icon="alt-arrow-left" />
Go back
</Button>
<Button type="submit" class="btn btn-primary" disabled={!topic || loading}>
<Spinner {loading}>Create Room</Spinner>
<Icon icon="alt-arrow-right" />
</Button>
</div>
</form>
+16
View File
@@ -0,0 +1,16 @@
<script lang="ts">
import {displayRelayUrl} from "@welshman/util"
import Avatar from "@lib/components/Avatar.svelte"
import {deriveRelay} from "@welshman/app"
export let url
const relay = deriveRelay(url)
</script>
<Avatar
icon="ghost"
class="!h-10 !w-10 border border-solid border-base-300"
alt={displayRelayUrl(url)}
src={$relay?.profile?.icon}
size={7} />
+3 -3
View File
@@ -3,7 +3,7 @@
import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte"
import Icon from "@lib/components/Icon.svelte"
import InfoNip29 from "@app/components/InfoNip29.svelte"
import InfoRelay from "@app/components/InfoRelay.svelte"
import SpaceCreateFinish from "@app/components/SpaceCreateFinish.svelte"
import {pushModal} from "@app/modal"
@@ -38,8 +38,8 @@
<input bind:value={relay} class="grow" type="text" />
</label>
<p slot="info">
This should be a NIP-29 compatible nostr relay where you'd like to host your space.
<Button class="link" on:click={() => pushModal(InfoNip29)}>What is a relay?</Button>
This can be any nostr relay where you'd like to host your space.
<Button class="link" on:click={() => pushModal(InfoRelay)}>What is a relay?</Button>
</p>
</Field>
<div class="flex flex-row items-center justify-between gap-4">
+5 -7
View File
@@ -1,14 +1,12 @@
<script lang="ts">
import {goto} from "$app/navigation"
import {displayRelayUrl} from "@welshman/util"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Icon from "@lib/components/Icon.svelte"
import {deriveGroup} from "@app/state"
import {removeGroupMemberships} from "@app/commands"
import {removeSpaceMembership} from "@app/commands"
export let nom
const group = deriveGroup(nom)
export let url
const back = () => history.back()
@@ -16,7 +14,7 @@
loading = true
try {
await removeGroupMemberships([nom])
await removeSpaceMembership(url)
} finally {
loading = false
}
@@ -29,7 +27,7 @@
<form class="column gap-4" on:submit|preventDefault={exit}>
<h1 class="heading">
You are leaving <span class="text-primary">{$group?.name || "[no name]"}</span>
You are leaving <span class="text-primary">{displayRelayUrl(url)}</span>
</h1>
<p class="text-center">Are you sure you want to leave?</p>
<div class="flex flex-row items-center justify-between gap-4">
+14 -27
View File
@@ -1,5 +1,7 @@
<script lang="ts">
import {goto} from "$app/navigation"
import {tryCatch} from "@welshman/lib"
import {isRelayUrl, normalizeRelayUrl} from "@welshman/util"
import {loadRelay} from "@welshman/app"
import CardButton from "@lib/components/CardButton.svelte"
import Spinner from "@lib/components/Spinner.svelte"
@@ -7,43 +9,28 @@
import Field from "@lib/components/Field.svelte"
import Icon from "@lib/components/Icon.svelte"
import {pushToast} from "@app/toast"
import {splitGroupId, loadGroup} from "@app/state"
import {addGroupMemberships} from "@app/commands"
import {addSpaceMembership} from "@app/commands"
import {makeSpacePath} from "@app/routes"
const back = () => history.back()
const browse = () => goto("/discover")
const joinQualifiedGroup = async (id: string) => {
const [url, nom] = splitGroupId(id)
const joinRelay = async (url: string) => {
url = normalizeRelayUrl(url)
const relay = await loadRelay(url)
if (!relay) {
if (!relay?.profile) {
return pushToast({
theme: "error",
message: "Sorry, we weren't able to find that relay.",
})
}
if (!relay.profile?.supported_nips?.includes(29)) {
return pushToast({
theme: "error",
message: "Sorry, it looks like that relay doesn't support nostr spaces.",
})
}
await addSpaceMembership(url)
const group = await loadGroup(nom, [url])
if (!group) {
return pushToast({
theme: "error",
message: "Sorry, we weren't able to find that space.",
})
}
await addGroupMemberships([["group", nom, url]])
goto(`/spaces/${nom}`)
goto(makeSpacePath(url))
pushToast({
message: "Welcome to the space!",
})
@@ -53,16 +40,16 @@
loading = true
try {
await joinQualifiedGroup(id)
await joinRelay(url)
} finally {
loading = false
}
}
let id = ""
let url = ""
let loading = false
$: linkIsValid = Boolean(id.match(/.+\..+'.+/))
$: linkIsValid = Boolean(tryCatch(() => isRelayUrl(normalizeRelayUrl(url))))
</script>
<form class="column gap-4" on:submit|preventDefault={join}>
@@ -74,7 +61,7 @@
<p slot="label">Invite Link*</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="link-round" />
<input bind:value={id} class="grow" type="text" />
<input bind:value={url} class="grow" type="text" />
</label>
</Field>
<CardButton icon="compass" title="Don't have an invite?" on:click={browse}>
+8 -44
View File
@@ -1,37 +1,22 @@
<script lang="ts">
import {append, remove} from "@welshman/lib"
import {displayRelayUrl} from "@welshman/util"
import {deriveRelay} from "@welshman/app"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Icon from "@lib/components/Icon.svelte"
import InfoNip29 from "@app/components/InfoNip29.svelte"
import {pushModal, clearModal} from "@app/modal"
import {pushToast} from "@app/toast"
import {deriveGroup, displayGroup, relayUrlsByNom} from "@app/state"
import {sendJoinRequest, addGroupMemberships} from "@app/commands"
import {addSpaceMembership} from "@app/commands"
export let nom
export let url
const group = deriveGroup(nom)
const relay = deriveRelay(url)
const back = () => history.back()
const onUrlChange = (e: any) => {
urls = urls.includes(e.target.value)
? remove(e.target.value, urls)
: append(e.target.value, urls)
}
const tryJoin = async () => {
for (const url of urls) {
const [ok, message] = await sendJoinRequest(nom, url)
if (!ok) {
return pushToast({theme: "error", message})
}
}
await addGroupMemberships(urls.map(url => ["group", nom, url]))
await addSpaceMembership(url)
clearModal()
}
@@ -47,40 +32,19 @@
}
let loading = false
let urls: string[] = $relayUrlsByNom.get(nom) || []
$: hasUrls = urls.length > 0
$: urlOptions = $relayUrlsByNom.get(nom)?.toSorted() || []
</script>
<form class="column gap-4" on:submit|preventDefault={join}>
<h1 class="heading">
Joining <span class="text-primary">{displayGroup($group)}</span>
Joining <span class="text-primary">{displayRelayUrl(url)}</span>
</h1>
<p class="text-center">
Please select which relays you'd like to use for this group.
<Button class="link" on:click={() => pushModal(InfoNip29)}>What is a relay?</Button>
</p>
{#each urlOptions as url}
<div class="alert !flex items-center justify-between">
<div class="flex items-center gap-2">
<Icon icon="remote-controller-minimalistic" />
{displayRelayUrl(url)}
</div>
<input
type="checkbox"
value={url}
class="toggle toggle-primary"
checked={urls.includes(url)}
on:change={onUrlChange} />
</div>
{/each}
<p class="text-center">Are you sure you'd like to join this space?</p>
<div class="flex flex-row items-center justify-between gap-4">
<Button class="btn btn-link" on:click={back}>
<Icon icon="alt-arrow-left" />
Go back
</Button>
<Button type="submit" class="btn btn-primary" disabled={!hasUrls || loading}>
<Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Join Space</Spinner>
<Icon icon="alt-arrow-right" />
</Button>