Overhaul relay settings page

This commit is contained in:
Jon Staab
2026-03-11 15:48:36 -07:00
parent cd54bc2880
commit 99966a976e
12 changed files with 470 additions and 212 deletions
+65 -13
View File
@@ -1,30 +1,70 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {SvelteSet} from "svelte/reactivity"
import type {Readable} from "svelte/store" import type {Readable} from "svelte/store"
import {tryCatch} from "@welshman/lib" import {tryCatch} from "@welshman/lib"
import {isShareableRelayUrl, normalizeRelayUrl} from "@welshman/util" import {isShareableRelayUrl, isIPAddress, normalizeRelayUrl} from "@welshman/util"
import {relaySearch} from "@welshman/app" import type {Thunk} from "@welshman/app"
import {waitForThunkError, relaySearch} from "@welshman/app"
import {createScroller} from "@lib/html" import {createScroller} from "@lib/html"
import {errorMessage} from "@lib/util"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl" import AddCircle from "@assets/icons/add-circle.svg?dataurl"
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"
import Modal from "@lib/components/Modal.svelte" import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte" import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import RelayItem from "@app/components/RelayItem.svelte" import RelayItem from "@app/components/RelayItem.svelte"
import {pushToast} from "@app/util/toast"
interface Props { interface Props {
relays: Readable<string[]> relays: Readable<string[]>
addRelay: (url: string) => void addRelay: (url: string) => Promise<Thunk>
matchRelay?: (url: string) => boolean
} }
const {relays, addRelay}: Props = $props() const {relays, addRelay, matchRelay}: Props = $props()
const back = () => history.back()
const customUrl = $derived(tryCatch(() => normalizeRelayUrl(term)))
const add = async (url: string) => {
loading.add(url)
try {
const error = await waitForThunkError(await addRelay(url))
if (error) {
pushToast({
theme: "error",
message: `Failed to add relay: ${errorMessage(error)}`,
})
}
} finally {
loading.delete(url)
}
}
let term = $state("") let term = $state("")
let limit = $state(20) let limit = $state(20)
let element: Element | undefined = $state() let element: Element | undefined = $state()
const customUrl = $derived(tryCatch(() => normalizeRelayUrl(term))) const loading = $state(new SvelteSet<string>())
const searchResults = $derived(
$relaySearch
.searchValues(term)
.filter(url => {
if (matchRelay?.(url) === false) return false
if ($relays.includes(url)) return false
if (isIPAddress(url)) return false
return true
})
.slice(0, limit),
)
onMount(() => { onMount(() => {
const scroller = createScroller({ const scroller = createScroller({
@@ -52,23 +92,35 @@
<RelayItem url={term}> <RelayItem url={term}>
<Button <Button
class="btn btn-outline btn-sm flex items-center" class="btn btn-outline btn-sm flex items-center"
onclick={() => addRelay(customUrl)}> disabled={loading.has(customUrl)}
<Icon icon={AddCircle} /> onclick={() => add(customUrl)}>
{#if loading.has(customUrl)}
<span class="loading loading-spinner loading-sm"></span>
{:else}
<Icon icon={AddCircle} />
{/if}
Add Relay Add Relay
</Button> </Button>
</RelayItem> </RelayItem>
{/if} {/if}
{#each $relaySearch {#each searchResults as url (url)}
.searchValues(term)
.filter(url => !$relays.includes(url))
.slice(0, limit) as url (url)}
<RelayItem {url}> <RelayItem {url}>
<Button class="btn btn-outline btn-sm flex items-center" onclick={() => addRelay(url)}> <Button
<Icon icon={AddCircle} /> class="btn btn-outline btn-sm flex items-center"
disabled={loading.has(url)}
onclick={() => add(url)}>
{#if loading.has(url)}
<span class="loading loading-spinner loading-sm"></span>
{:else}
<Icon icon={AddCircle} />
{/if}
Add Relay Add Relay
</Button> </Button>
</RelayItem> </RelayItem>
{/each} {/each}
</div> </div>
</ModalBody> </ModalBody>
<ModalFooter>
<Button class="btn btn-primary flex-grow" onclick={back}>Done</Button>
</ModalFooter>
</Modal> </Modal>
+85
View File
@@ -0,0 +1,85 @@
<script lang="ts">
import type {Readable} from "svelte/store"
import {SvelteSet} from "svelte/reactivity"
import {waitForThunkError} from "@welshman/app"
import type {Thunk} from "@welshman/app"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import {errorMessage} from "@lib/util"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import RelayAdd from "@app/components/RelayAdd.svelte"
import RelayItem from "@app/components/RelayItem.svelte"
import {pushModal} from "@app/util/modal"
import {pushToast} from "@app/util/toast"
interface Props {
title: string
subtitle: string
relays: Readable<string[]>
addRelay: (url: string) => Promise<Thunk>
removeRelay: (url: string) => Promise<Thunk>
matchRelay?: (url: string) => boolean
}
const {title, subtitle, relays, addRelay, removeRelay, matchRelay}: Props = $props()
const back = () => history.back()
const add = () => pushModal(RelayAdd, {relays, addRelay, matchRelay})
const remove = async (url: string) => {
loading.add(url)
try {
const error = await waitForThunkError(await removeRelay(url))
if (error) {
pushToast({
theme: "error",
message: `Failed to remove relay: ${errorMessage(error)}`,
})
}
} finally {
loading.delete(url)
}
}
const loading = $state(new SvelteSet<string>())
</script>
<Modal>
<ModalBody>
<h2 class="text-xl">{title}</h2>
<p class="text-sm">{subtitle}</p>
{#each $relays.toSorted() as url (url)}
<RelayItem {url}>
<Button
class="btn btn-sm btn-neutral"
disabled={loading.has(url)}
onclick={() => remove(url)}>
{#if loading.has(url)}
<span class="loading loading-spinner loading-sm"></span>
{:else}
<Icon icon={CloseCircle} />
{/if}
Remove
</Button>
</RelayItem>
{/each}
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
<Button class="btn btn-primary" onclick={add}>
Add Relays
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</Modal>
@@ -0,0 +1,27 @@
<script lang="ts" module>
export type ActionItem = {
title: string
subtitle: string
action: string
apply: () => unknown
}
</script>
<script lang="ts">
import Stars from "@assets/icons/stars.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
const {title, action, subtitle, apply}: ActionItem = $props()
</script>
<div class="card2 card2-sm bg-alt flex justify-between">
<div class="flex flex-col gap-1">
<strong>{title}</strong>
<p class="text-sm">{subtitle}</p>
</div>
<Button class="btn btn-neutral btn-sm" onclick={apply}>
<Icon icon={Stars} />
{action}
</Button>
</div>
@@ -0,0 +1,58 @@
<script lang="ts">
import type {Readable} from "svelte/store"
import Stars from "@assets/icons/stars.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import DangerTriangle from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import type {ActionItem} from "@app/components/RelaySettingsActionItem.svelte"
import RelaySettingsActionItem from "@app/components/RelaySettingsActionItem.svelte"
interface Props {
actionItems: Readable<ActionItem[]>
}
const {actionItems}: Props = $props()
const back = () => history.back()
const applyAll = () => {
for (const actionItem of $actionItems) {
actionItem.apply()
}
}
$effect(() => {
if ($actionItems.length === 0) {
back()
}
})
</script>
<Modal>
<ModalBody>
<div class="flex gap-2 items-center">
<Icon icon={DangerTriangle} />
<strong class="text-lg">Action Items</strong>
</div>
<p class="text-sm">
Below are recommendations for adjustments to your relay selections that you might consider.
</p>
{#each $actionItems as actionItem}
<RelaySettingsActionItem {...actionItem} />
{/each}
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go Back
</Button>
<Button class="btn btn-primary" onclick={applyAll}>
<Icon icon={Stars} />
Apply All Recommendations
</Button>
</ModalFooter>
</Modal>
@@ -0,0 +1,50 @@
<script lang="ts">
import type {Readable} from "svelte/store"
import Check from "@assets/icons/check.svg?dataurl"
import DangerTriangle from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import RelayList from "@app/components/RelayList.svelte"
import {pushModal} from "@app/util/modal"
interface Props {
icon: string
title: string
subtitle: string
relays: Readable<string[]>
addRelay: (url: string) => unknown
removeRelay: (url: string) => unknown
matchRelay?: (url: string) => boolean
}
const {icon, title, relays, subtitle, addRelay, removeRelay, matchRelay}: Props = $props()
const onclick = () =>
pushModal(RelayList, {title, subtitle, relays, addRelay, removeRelay, matchRelay})
</script>
<button
type="button"
class="btn font-normal flex h-[unset] w-full flex-nowrap py-4 text-left items-start justify-between"
{onclick}>
<div class="flex flex-grow flex-row items-start gap-4">
<div class="flex h-7 w-7 flex-shrink-0 items-center justify-center">
<Icon {icon} />
</div>
<div class="flex flex-col gap-1">
<p class="text-lg">
{title}
</p>
<p class="text-sm">
{subtitle}
</p>
</div>
</div>
<div class="flex items-center justify-end gap-1">
{#if $relays.length <= 1}
<Icon icon={DangerTriangle} />
{:else}
<Icon icon={Check} />
{/if}
{$relays.length}
</div>
</button>
+6 -1
View File
@@ -577,7 +577,7 @@ export const makeRoomId = (url: string, h: string) => `${url}'${h}`
export const splitRoomId = (id: string) => id.split("'") export const splitRoomId = (id: string) => id.split("'")
export const hasNip29 = (relay?: RelayProfile) => export const hasNip29 = (relay?: RelayProfile) =>
relay?.supported_nips?.map?.(String)?.includes?.("29") Boolean(relay?.supported_nips?.map?.(String)?.includes?.("29"))
export const roomMetaEventsByIdByUrl = deriveEventsByIdByUrl({ export const roomMetaEventsByIdByUrl = deriveEventsByIdByUrl({
tracker, tracker,
@@ -1224,3 +1224,8 @@ export const shouldNotify = (url: string, h?: string) => getShouldNotify(getSett
export const deriveShouldNotify = (url: string, h?: string) => export const deriveShouldNotify = (url: string, h?: string) =>
derived(userSettingsValues, $settings => getShouldNotify($settings, url, h)) derived(userSettingsValues, $settings => getShouldNotify($settings, url, h))
// Whatever who cares
export const hasNip50 = (relay?: RelayProfile) =>
Boolean(relay?.supported_nips?.map?.(String)?.includes?.("50"))
-36
View File
@@ -1,36 +0,0 @@
<script lang="ts">
import {slide} from "@lib/transition"
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
interface Props {
title?: import("svelte").Snippet
description?: import("svelte").Snippet
children?: import("svelte").Snippet
[key: string]: any
}
const {...props}: Props = $props()
const toggle = () => {
isOpen = !isOpen
}
let isOpen = $state(false)
</script>
<div class="relative flex flex-col gap-4 {props.class}">
<button
type="button"
class="absolute right-8 top-8 h-4 w-4 cursor-pointer transition-all"
class:rotate-90={!isOpen}
onclick={toggle}>
<Icon icon={AltArrowDown} />
</button>
{@render props.title?.()}
{@render props.description?.()}
{#if isOpen}
<div transition:slide>
{@render props.children?.()}
</div>
{/if}
</div>
+6 -1
View File
@@ -3,8 +3,10 @@
import {sleep} from "@welshman/lib" import {sleep} from "@welshman/lib"
import {Capacitor} from "@capacitor/core" import {Capacitor} from "@capacitor/core"
import {Badge} from "@capawesome/capacitor-badge" import {Badge} from "@capawesome/capacitor-badge"
import Bell from "@assets/icons/bell.svg?dataurl"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
import {Push, clearBadges} from "@app/util/notifications" import {Push, clearBadges} from "@app/util/notifications"
@@ -51,7 +53,10 @@
<form class="content column gap-4" {onsubmit}> <form class="content column gap-4" {onsubmit}>
<div class="card2 bg-alt col-4 shadow-md"> <div class="card2 bg-alt col-4 shadow-md">
<strong class="text-lg">Alert Settings</strong> <strong class="flex items-center gap-3 text-lg">
<Icon icon={Bell} />
Alert Settings
</strong>
{#await Badge.isSupported()} {#await Badge.isSupported()}
<!-- pass --> <!-- pass -->
{:then { isSupported }} {:then { isSupported }}
+6 -1
View File
@@ -10,10 +10,12 @@
} from "@welshman/util" } from "@welshman/util"
import {Router} from "@welshman/router" import {Router} from "@welshman/router"
import {userMuteList, tagPubkey, publishThunk, userBlossomServerList} from "@welshman/app" import {userMuteList, tagPubkey, publishThunk, userBlossomServerList} from "@welshman/app"
import NotesMinimalistic from "@assets/icons/notes-minimalistic.svg?dataurl"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import FieldInline from "@lib/components/FieldInline.svelte" import FieldInline from "@lib/components/FieldInline.svelte"
import InputList from "@lib/components/InputList.svelte" import InputList from "@lib/components/InputList.svelte"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte" import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
@@ -49,7 +51,10 @@
<form class="content column gap-4" {onsubmit}> <form class="content column gap-4" {onsubmit}>
<div class="card2 bg-alt col-4 shadow-md"> <div class="card2 bg-alt col-4 shadow-md">
<strong class="text-lg">Content Settings</strong> <strong class="flex items-center gap-3 text-lg">
<Icon icon={NotesMinimalistic} />
Content Settings
</strong>
<FieldInline> <FieldInline>
{#snippet label()} {#snippet label()}
<p>Hide sensitive content?</p> <p>Hide sensitive content?</p>
+6 -1
View File
@@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
import ShieldMinimalistic from "@assets/icons/shield-minimalistic.svg?dataurl"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
import {PLATFORM_NAME, RelayAuthMode, userSettingsValues} from "@app/core/state" import {PLATFORM_NAME, RelayAuthMode, userSettingsValues} from "@app/core/state"
@@ -24,7 +26,10 @@
<form class="content column gap-4" {onsubmit}> <form class="content column gap-4" {onsubmit}>
<div class="card2 bg-alt flex flex-col gap-4 shadow-md"> <div class="card2 bg-alt flex flex-col gap-4 shadow-md">
<strong class="text-lg">Privacy Settings</strong> <strong class="flex items-center gap-3 text-lg">
<Icon icon={ShieldMinimalistic} />
Privacy Settings
</strong>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<p>Authenticate with unknown relays?</p> <p>Authenticate with unknown relays?</p>
<input <input
+159 -157
View File
@@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {derived} from "svelte/store"
import {shuffle, partition, ifLet} from "@welshman/lib"
import { import {
pubkey, pubkey,
getRelayLists, getRelayLists,
@@ -10,182 +12,182 @@
removeBlockedRelay, removeBlockedRelay,
addMessagingRelay, addMessagingRelay,
removeMessagingRelay, removeMessagingRelay,
addSearchRelay,
removeSearchRelay,
getRelay,
setWriteRelays,
setReadRelays,
setSearchRelays,
setMessagingRelays,
} from "@welshman/app" } from "@welshman/app"
import {RelayMode} from "@welshman/util" import {RelayMode} from "@welshman/util"
import Plane from "@assets/icons/plane.svg?dataurl"
import Inbox from "@assets/icons/inbox.svg?dataurl"
import Server from "@assets/icons/server.svg?dataurl"
import Mailbox from "@assets/icons/mailbox.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import DangerTriangle from "@assets/icons/danger-triangle.svg?dataurl"
import ForbiddenCircle from "@assets/icons/forbidden-circle.svg?dataurl"
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"
import Collapse from "@lib/components/Collapse.svelte" import RelaySettingsItem from "@app/components/RelaySettingsItem.svelte"
import RelayItem from "@app/components/RelayItem.svelte" import type {ActionItem} from "@app/components/RelaySettingsActionItem.svelte"
import RelayAdd from "@app/components/RelayAdd.svelte" import RelaySettingsActionItems from "@app/components/RelaySettingsActionItems.svelte"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {hasNip50, DEFAULT_RELAYS, DEFAULT_MESSAGING_RELAYS} from "@app/core/state"
import {discoverRelays} from "@app/core/requests" import {discoverRelays} from "@app/core/requests"
import Globus from "@assets/icons/globus.svg?dataurl"
import Inbox from "@assets/icons/inbox.svg?dataurl"
import Mailbox from "@assets/icons/mailbox.svg?dataurl"
import ForbiddenCircle from "@assets/icons/forbidden-circle.svg?dataurl"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
const readRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Read) const readRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Read)
const writeRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Write) const writeRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Write)
const searchRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Search)
const blockedRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Blocked) const blockedRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Blocked)
const messagingRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Messaging) const messagingRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Messaging)
const addReadRelays = () => const addReadRelay = (url: string) => addRelay(url, RelayMode.Read)
pushModal(RelayAdd, {
relays: readRelayUrls,
addRelay: (url: string) => addRelay(url, RelayMode.Read),
})
const addWriteRelays = () =>
pushModal(RelayAdd, {
relays: writeRelayUrls,
addRelay: (url: string) => addRelay(url, RelayMode.Write),
})
const addBlockedRelays = () =>
pushModal(RelayAdd, {relays: blockedRelayUrls, addRelay: addBlockedRelay})
const addMessagingRelays = () =>
pushModal(RelayAdd, {relays: messagingRelayUrls, addRelay: addMessagingRelay})
const removeReadRelay = (url: string) => removeRelay(url, RelayMode.Read) const removeReadRelay = (url: string) => removeRelay(url, RelayMode.Read)
const addWriteRelay = (url: string) => addRelay(url, RelayMode.Write)
const removeWriteRelay = (url: string) => removeRelay(url, RelayMode.Write) const removeWriteRelay = (url: string) => removeRelay(url, RelayMode.Write)
const showActionItems = () => pushModal(RelaySettingsActionItems, {actionItems})
const actionItems = derived(
[readRelayUrls, writeRelayUrls, messagingRelayUrls, searchRelayUrls],
([$readRelayUrls, $writeRelayUrls, $messagingRelayUrls, $searchRelayUrls]) => {
const $actionItems: ActionItem[] = []
if ($readRelayUrls.length <= 1) {
$actionItems.push({
title: "Missing Inbox Relays",
subtitle: "Other people aren't currently able to reliably tag you in public notes.",
action: "Update",
apply: () => setReadRelays(DEFAULT_RELAYS),
})
}
if ($writeRelayUrls.length <= 1) {
$actionItems.push({
title: "Missing Outbox Relays",
subtitle: "Other people aren't currently able to reliably find your public notes.",
action: "Update",
apply: () => setWriteRelays(DEFAULT_RELAYS),
})
}
if ($messagingRelayUrls.length <= 1) {
$actionItems.push({
title: "Missing DM Relays",
subtitle: "You aren't currently able to reliably send or receive direct messages.",
action: "Update",
apply: () => setMessagingRelays(DEFAULT_MESSAGING_RELAYS),
})
}
if ($readRelayUrls.length > 8) {
$actionItems.push({
title: "Too Many Inbox Relays",
subtitle:
"You have more inbox relays than is really necessary, which can affect resource usage.",
action: "Prune Selections",
apply: () => setReadRelays(shuffle($readRelayUrls).slice(0, 5)),
})
}
if ($writeRelayUrls.length > 8) {
$actionItems.push({
title: "Too Many Outbox Relays",
subtitle:
"You have more outbox relays than is really necessary, which can affect resource usage.",
action: "Prune Selections",
apply: () => setWriteRelays(shuffle($writeRelayUrls).slice(0, 5)),
})
}
if ($messagingRelayUrls.length > 8) {
$actionItems.push({
title: "Too Many DM Relays",
subtitle:
"You have more DM relays than is really necessary, which can affect resource usage.",
action: "Prune Selections",
apply: () => setMessagingRelays(shuffle($messagingRelayUrls).slice(0, 5)),
})
}
const [okSearchRelays, badSearchRelays] = partition(
url => Boolean(ifLet(getRelay(url), hasNip50)),
$searchRelayUrls,
)
if (badSearchRelays.length > 0) {
$actionItems.push({
title: "Invalid Search Relays",
subtitle: `Some of your search relays don't support search.`,
action: "Remove Invalid",
apply: () => setSearchRelays(okSearchRelays),
})
}
return $actionItems
},
)
onMount(() => { onMount(() => {
discoverRelays(getRelayLists()) discoverRelays(getRelayLists())
}) })
</script> </script>
<div class="content column gap-4"> <div class="content">
<Collapse class="card2 bg-alt column gap-4 shadow-md"> <div class="card2 bg-alt flex flex-col gap-4 shadow-md">
{#snippet title()} <div class="flex justify-between">
<h2 class="flex items-center gap-3 text-xl"> <strong class="flex items-center gap-3 text-lg">
<Icon icon={Globus} /> <Icon icon={Server} />
Outbox Relays Your Relays
</h2> </strong>
{/snippet} {#if $actionItems.length > 0}
{#snippet description()} <Button class="btn btn-neutral btn-sm" onclick={showActionItems}>
<p class="text-sm"> <Icon icon={DangerTriangle} />
These relays will be advertised on your profile as places where you send your public notes. {$actionItems.length} Issue{$actionItems.length === 1 ? "" : "s"} Detected
Be sure to select relays that will accept your notes, and which will let people who follow </Button>
you read them. {/if}
</p>
{/snippet}
<div class="column gap-2">
{#each $writeRelayUrls.sort() as url (url)}
<RelayItem {url}>
<Button
class="tooltip flex items-center"
data-tip="Stop using this relay"
onclick={() => removeWriteRelay(url)}>
<Icon icon={CloseCircle} />
</Button>
</RelayItem>
{:else}
<p class="text-center text-sm">No relays found</p>
{/each}
<Button class="btn btn-primary mt-2" onclick={addWriteRelays}>
<Icon icon={AddCircle} />
Add Relay
</Button>
</div> </div>
</Collapse> <p class="text-sm mb-2">
<Collapse class="card2 bg-alt column gap-4 shadow-md"> Relays are servers which store your data, or allow you to find data from across the Nostr
{#snippet title()} network. We've set you up with some reasonable defaults, but if you're a power user, you can
<h2 class="flex items-center gap-3 text-xl"> customize your relay selections below.
<Icon icon={Inbox} /> </p>
Inbox Relays <RelaySettingsItem
</h2> icon={Inbox}
{/snippet} title="Inbox Relays"
{#snippet description()} subtitle="Where you send your public notes. Be sure to select relays that will accept your notes, and which will let people who follow you read them."
<p class="text-sm"> relays={readRelayUrls}
These relays will be advertised on your profile as places where other people should send addRelay={addReadRelay}
notes intended for you. Be sure to select relays that will accept notes that tag you. removeRelay={removeReadRelay} />
</p> <RelaySettingsItem
{/snippet} icon={Plane}
<div class="column gap-2"> title="Outbox Relays"
{#each $readRelayUrls.sort() as url (url)} subtitle="Where other people should send notes intended for you. Be sure to select relays that will accept notes that tag you."
<RelayItem {url}> relays={writeRelayUrls}
<Button addRelay={addWriteRelay}
class="tooltip flex items-center" removeRelay={removeWriteRelay} />
data-tip="Stop using this relay" <RelaySettingsItem
onclick={() => removeReadRelay(url)}> icon={Mailbox}
<Icon icon={CloseCircle} /> title="DM Relays"
</Button> subtitle="Where you send and receive direct messages. Be sure to select relays that will accept your messages and messages from people you'd like to be in contact with."
</RelayItem> relays={messagingRelayUrls}
{:else} addRelay={addMessagingRelay}
<p class="text-center text-sm">No relays found</p> removeRelay={removeMessagingRelay} />
{/each} <RelaySettingsItem
<Button class="btn btn-primary mt-2" onclick={addReadRelays}> icon={Magnifier}
<Icon icon={AddCircle} /> title="Search Relays"
Add Relay subtitle="Relays that support searching for profiles and public notes."
</Button> relays={searchRelayUrls}
</div> addRelay={addSearchRelay}
</Collapse> removeRelay={removeSearchRelay}
<Collapse class="card2 bg-alt column gap-4 shadow-md"> matchRelay={url => hasNip50(getRelay(url))} />
{#snippet title()} <RelaySettingsItem
<h2 class="flex items-center gap-3 text-xl"> icon={ForbiddenCircle}
<Icon icon={Mailbox} /> title="Blocked Relays"
Messaging Relays subtitle="These relays won't be used unless explicitly requested."
</h2> relays={blockedRelayUrls}
{/snippet} addRelay={addBlockedRelay}
{#snippet description()} removeRelay={removeBlockedRelay} />
<p class="text-sm"> </div>
These relays will be advertised on your profile as places you use to send and receive direct
messages. Be sure to select relays that will accept your messages and messages from people
you'd like to be in contact with.
</p>
{/snippet}
<div class="column gap-2">
{#each $messagingRelayUrls.sort() as url (url)}
<RelayItem {url}>
<Button
class="tooltip flex items-center"
data-tip="Stop using this relay"
onclick={() => removeMessagingRelay(url)}>
<Icon icon={CloseCircle} />
</Button>
</RelayItem>
{:else}
<p class="text-center text-sm">No relays found</p>
{/each}
<Button class="btn btn-primary mt-2" onclick={addMessagingRelays}>
<Icon icon={AddCircle} />
Add Relay
</Button>
</div>
</Collapse>
<Collapse class="card2 bg-alt column gap-4 shadow-md">
{#snippet title()}
<h2 class="flex items-center gap-3 text-xl">
<Icon icon={ForbiddenCircle} />
Blocked Relays
</h2>
{/snippet}
{#snippet description()}
<p class="text-sm">
These relays will never be connected to by clients supporting this policy.
</p>
{/snippet}
<div class="column gap-2">
{#each $blockedRelayUrls.sort() as url (url)}
<RelayItem {url}>
<Button
class="tooltip flex items-center"
data-tip="Stop using this relay"
onclick={() => removeBlockedRelay(url)}>
<Icon icon={CloseCircle} />
</Button>
</RelayItem>
{:else}
<p class="text-center text-sm">No relays found</p>
{/each}
<Button class="btn btn-primary mt-2" onclick={addBlockedRelays}>
<Icon icon={AddCircle} />
Add Relay
</Button>
</div>
</Collapse>
</div> </div>
+2 -2
View File
@@ -83,9 +83,9 @@
<div class="content column gap-4"> <div class="content column gap-4">
<div class="card2 bg-alt flex flex-col gap-6 shadow-md"> <div class="card2 bg-alt flex flex-col gap-6 shadow-md">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<strong class="flex items-center gap-3"> <strong class="flex items-center gap-3 text-lg">
<Icon icon={Wallet2} /> <Icon icon={Wallet2} />
Wallet Your Wallet
</strong> </strong>
{#if $session?.wallet} {#if $session?.wallet}
<div class={statusClass}> <div class={statusClass}>