Convert to simple relay-based groups from nip29
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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",
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
@@ -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,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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user