mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-18 19:23:40 +00:00
Re-organize app to handle anonymous users
This commit is contained in:
parent
65f184ba48
commit
e2baa5c0c9
@ -13,7 +13,7 @@
|
|||||||
- [x] Make chat header overlap main header to save space
|
- [x] Make chat header overlap main header to save space
|
||||||
- [x] Strip formatting when pasting into Compose
|
- [x] Strip formatting when pasting into Compose
|
||||||
- [x] Upgraded nostr-tools to 1.4.1
|
- [x] Upgraded nostr-tools to 1.4.1
|
||||||
|
)
|
||||||
## 0.2.11
|
## 0.2.11
|
||||||
|
|
||||||
- [x] Converted threshold to percentage
|
- [x] Converted threshold to percentage
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# Current
|
# Current
|
||||||
|
|
||||||
- [ ] Review 10002 usage https://github.com/nostr-protocol/nips/blob/master/65.md
|
|
||||||
- [ ] Remove relays from people, pull from routes only
|
|
||||||
- [ ] Fix anon/new user experience
|
- [ ] Fix anon/new user experience
|
||||||
- [ ] Clicking stuff that would publish kicks you to the login page, we should open a modal instead.
|
- [ ] Clicking stuff that would publish kicks you to the login page, we should open a modal instead.
|
||||||
- [ ] Separate user info and relays so we can still select/figure out relays for anons
|
- [ ] Separate user info and relays so we can still select/figure out relays for anons
|
||||||
|
1
public/fonts/Montserrat
Normal file
1
public/fonts/Montserrat
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
361
src/App.svelte
361
src/App.svelte
@ -2,182 +2,78 @@
|
|||||||
import "@fortawesome/fontawesome-free/css/fontawesome.css"
|
import "@fortawesome/fontawesome-free/css/fontawesome.css"
|
||||||
import "@fortawesome/fontawesome-free/css/solid.css"
|
import "@fortawesome/fontawesome-free/css/solid.css"
|
||||||
|
|
||||||
import {find, is, identity, nthArg, pluck} from 'ramda'
|
import {onMount} from 'svelte'
|
||||||
import {createMap, first} from 'hurdak/lib/hurdak'
|
|
||||||
import {writable, get} from "svelte/store"
|
|
||||||
import {fly, fade} from "svelte/transition"
|
|
||||||
import {cubicInOut} from "svelte/easing"
|
|
||||||
import {Router, Route, links, navigate} from "svelte-routing"
|
import {Router, Route, links, navigate} from "svelte-routing"
|
||||||
import {globalHistory} from "svelte-routing/src/history"
|
import {globalHistory} from "svelte-routing/src/history"
|
||||||
|
import {cubicInOut} from "svelte/easing"
|
||||||
|
import {writable, get} from "svelte/store"
|
||||||
|
import {fly, fade} from "svelte/transition"
|
||||||
|
import {createMap, first} from 'hurdak/lib/hurdak'
|
||||||
|
import {find, is, identity, nthArg, pluck} from 'ramda'
|
||||||
import {log, warn} from 'src/util/logger'
|
import {log, warn} from 'src/util/logger'
|
||||||
import {displayPerson, isLike} from 'src/util/nostr'
|
|
||||||
import {timedelta, shuffle, now, sleep} from 'src/util/misc'
|
import {timedelta, shuffle, now, sleep} from 'src/util/misc'
|
||||||
|
import {displayPerson, isLike} from 'src/util/nostr'
|
||||||
import cmd from 'src/agent/cmd'
|
import cmd from 'src/agent/cmd'
|
||||||
import {user} from 'src/agent/user'
|
|
||||||
import {getUserRelays} from 'src/agent/relays'
|
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import keys from 'src/agent/keys'
|
import keys from 'src/agent/keys'
|
||||||
import network from 'src/agent/network'
|
import network from 'src/agent/network'
|
||||||
import pool from 'src/agent/pool'
|
import pool from 'src/agent/pool'
|
||||||
|
import {getUserRelays} from 'src/agent/relays'
|
||||||
|
import {relays} from 'src/agent/relays'
|
||||||
import sync from 'src/agent/sync'
|
import sync from 'src/agent/sync'
|
||||||
import {modal, toast, settings, logUsage, alerts, messages, loadAppData} from "src/app"
|
import {user} from 'src/agent/user'
|
||||||
import {routes} from "src/app/ui"
|
import {loadAppData} from "src/app"
|
||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import alerts from "src/app/alerts"
|
||||||
import Content from 'src/partials/Content.svelte'
|
import messages from "src/app/messages"
|
||||||
import Spinner from 'src/partials/Spinner.svelte'
|
import {modal, toast, settings, routes, menuIsOpen, logUsage} from "src/app/ui"
|
||||||
import Modal from 'src/partials/Modal.svelte'
|
|
||||||
import RelayCard from "src/partials/RelayCard.svelte"
|
import RelayCard from "src/partials/RelayCard.svelte"
|
||||||
import SignUp from "src/views/SignUp.svelte"
|
|
||||||
import PersonList from "src/views/PersonList.svelte"
|
|
||||||
import PrivKeyLogin from "src/views/PrivKeyLogin.svelte"
|
|
||||||
import PubKeyLogin from "src/views/PubKeyLogin.svelte"
|
|
||||||
import NoteDetail from "src/views/NoteDetail.svelte"
|
|
||||||
import PersonSettings from "src/views/PersonSettings.svelte"
|
|
||||||
import PersonShare from "src/views/PersonShare.svelte"
|
|
||||||
import NoteCreate from "src/views/NoteCreate.svelte"
|
|
||||||
import ChatEdit from "src/views/ChatEdit.svelte"
|
|
||||||
import NotFound from "src/routes/NotFound.svelte"
|
|
||||||
import Search from "src/routes/Search.svelte"
|
|
||||||
import Alerts from "src/routes/Alerts.svelte"
|
|
||||||
import Notes from "src/routes/Notes.svelte"
|
|
||||||
import Debug from "src/routes/Debug.svelte"
|
|
||||||
import Login from "src/routes/Login.svelte"
|
|
||||||
import Logout from "src/routes/Logout.svelte"
|
|
||||||
import Profile from "src/routes/Profile.svelte"
|
|
||||||
import Settings from "src/routes/Settings.svelte"
|
|
||||||
import Keys from "src/routes/Keys.svelte"
|
|
||||||
import RelayList from "src/routes/RelayList.svelte"
|
|
||||||
import AddRelay from "src/routes/AddRelay.svelte"
|
import AddRelay from "src/routes/AddRelay.svelte"
|
||||||
import Person from "src/routes/Person.svelte"
|
import Alerts from "src/routes/Alerts.svelte"
|
||||||
import Bech32Entity from "src/routes/Bech32Entity.svelte"
|
import Bech32Entity from "src/routes/Bech32Entity.svelte"
|
||||||
import Chat from "src/routes/Chat.svelte"
|
import Chat from "src/routes/Chat.svelte"
|
||||||
import ChatRoom from "src/routes/ChatRoom.svelte"
|
import ChatRoom from "src/routes/ChatRoom.svelte"
|
||||||
|
import Debug from "src/routes/Debug.svelte"
|
||||||
|
import Keys from "src/routes/Keys.svelte"
|
||||||
|
import Login from "src/routes/Login.svelte"
|
||||||
|
import Logout from "src/routes/Logout.svelte"
|
||||||
import Messages from "src/routes/Messages.svelte"
|
import Messages from "src/routes/Messages.svelte"
|
||||||
|
import NotFound from "src/routes/NotFound.svelte"
|
||||||
|
import Notes from "src/routes/Notes.svelte"
|
||||||
|
import Person from "src/routes/Person.svelte"
|
||||||
|
import Profile from "src/routes/Profile.svelte"
|
||||||
|
import RelayList from "src/routes/RelayList.svelte"
|
||||||
|
import Search from "src/routes/Search.svelte"
|
||||||
|
import Settings from "src/routes/Settings.svelte"
|
||||||
|
import ChatEdit from "src/views/ChatEdit.svelte"
|
||||||
|
import NoteCreate from "src/views/NoteCreate.svelte"
|
||||||
|
import NoteDetail from "src/views/NoteDetail.svelte"
|
||||||
|
import PersonList from "src/views/PersonList.svelte"
|
||||||
|
import PersonSettings from "src/views/PersonSettings.svelte"
|
||||||
|
import PersonShare from "src/views/PersonShare.svelte"
|
||||||
|
import PrivKeyLogin from "src/views/PrivKeyLogin.svelte"
|
||||||
|
import PubKeyLogin from "src/views/PubKeyLogin.svelte"
|
||||||
|
import SignUp from "src/views/SignUp.svelte"
|
||||||
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
|
import Content from 'src/partials/Content.svelte'
|
||||||
|
import Modal from 'src/partials/Modal.svelte'
|
||||||
|
import SideNav from 'src/partials/SideNav.svelte'
|
||||||
|
import Spinner from 'src/partials/Spinner.svelte'
|
||||||
|
import TopNav from 'src/partials/TopNav.svelte'
|
||||||
|
|
||||||
Object.assign(window, {cmd, database, keys, network, pool, sync})
|
Object.assign(window, {cmd, database, keys, network, pool, sync})
|
||||||
|
|
||||||
export let url = ""
|
export let url = ""
|
||||||
|
|
||||||
const menuIsOpen = writable(false)
|
let scrollY
|
||||||
const toggleMenu = () => menuIsOpen.update(x => !x)
|
|
||||||
|
|
||||||
const searchIsOpen = writable(false)
|
const {ready} = database
|
||||||
const toggleSearch = () => searchIsOpen.update(x => !x)
|
|
||||||
|
|
||||||
const closeModal = async () => {
|
const closeModal = async () => {
|
||||||
modal.clear()
|
modal.clear()
|
||||||
menuIsOpen.set(false)
|
menuIsOpen.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {ready} = database
|
onMount(() => {
|
||||||
const {lastCheckedAlerts, mostRecentAlert} = alerts
|
|
||||||
const {lastCheckedByPubkey, mostRecentByPubkey} = messages
|
|
||||||
|
|
||||||
let menuIcon
|
|
||||||
let scrollY
|
|
||||||
let suspendedSubs = []
|
|
||||||
let slowConnections = []
|
|
||||||
let hasNewMessages = false
|
|
||||||
|
|
||||||
$: {
|
|
||||||
hasNewMessages = Boolean(find(
|
|
||||||
([k, t]) => {
|
|
||||||
return t > now() - timedelta(7, 'days') && ($lastCheckedByPubkey[k] || 0) < t
|
|
||||||
},
|
|
||||||
Object.entries($mostRecentByPubkey)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
database.onReady(() => {
|
|
||||||
if ($user) {
|
|
||||||
loadAppData($user.pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background work
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
alertSlowConnections()
|
|
||||||
retrieveRelayMeta()
|
|
||||||
}, 30_000)
|
|
||||||
|
|
||||||
const alertSlowConnections = () => {
|
|
||||||
// Only notify about relays the user is actually subscribed to
|
|
||||||
const relayUrls = pluck('url', getUserRelays())
|
|
||||||
|
|
||||||
// Prune connections we haven't used in a while
|
|
||||||
pool.getConnections()
|
|
||||||
.filter(conn => conn.lastRequest < Date.now() - 60_000)
|
|
||||||
.forEach(conn => conn.disconnect())
|
|
||||||
|
|
||||||
// Log stats for debugging purposes
|
|
||||||
log(
|
|
||||||
'Connection stats',
|
|
||||||
pool.getConnections()
|
|
||||||
.map(c => `${c.nostr.url} ${c.getQuality().join(' ')}`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Alert the user to any heinously slow connections
|
|
||||||
slowConnections = pool.getConnections()
|
|
||||||
.filter(c => relayUrls.includes(c.nostr.url) && first(c.getQuality()) < 0.3)
|
|
||||||
}
|
|
||||||
|
|
||||||
const retrieveRelayMeta = async () => {
|
|
||||||
const {dufflepudUrl} = $settings
|
|
||||||
|
|
||||||
if (!dufflepudUrl) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find relays with old/missing metadata and refresh them. Only pick a
|
|
||||||
// few so we're not sending too many concurrent http requests
|
|
||||||
const staleRelays = shuffle(
|
|
||||||
await database.relays.all({
|
|
||||||
'refreshed_at:lt': now() - timedelta(7, 'days'),
|
|
||||||
})
|
|
||||||
).slice(0, 10)
|
|
||||||
|
|
||||||
const freshRelays = await Promise.all(
|
|
||||||
staleRelays.map(async ({url}) => {
|
|
||||||
try {
|
|
||||||
const res = await fetch(dufflepudUrl + '/relay/info', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({url}),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return {...await res.json(), url, refreshed_at: now()}
|
|
||||||
} catch (e) {
|
|
||||||
if (!e.toString().includes('Failed to fetch')) {
|
|
||||||
warn(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {url, refreshed_at: now()}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
database.relays.bulkPatch(createMap('url', freshRelays.filter(identity)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close menu on click outside
|
|
||||||
document.querySelector("html").addEventListener("click", e => {
|
|
||||||
if (e.target !== menuIcon) {
|
|
||||||
menuIsOpen.set(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Log usage on navigate
|
|
||||||
const unsubHistory = globalHistory.listen(({location}) => {
|
|
||||||
if (!location.hash) {
|
|
||||||
// Remove identifying information, e.g. pubkeys, event ids, etc
|
|
||||||
const name = location.pathname.slice(1)
|
|
||||||
.replace(/(npub|nprofile|note|nevent)[^\/]+/g, (_, m) => `<${m}>`)
|
|
||||||
|
|
||||||
logUsage(btoa(['page', name].join(':')))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Keep scroll position on body, but don't allow scrolling
|
// Keep scroll position on body, but don't allow scrolling
|
||||||
const unsubModal = modal.subscribe($modal => {
|
const unsubModal = modal.subscribe($modal => {
|
||||||
if ($modal) {
|
if ($modal) {
|
||||||
@ -196,17 +92,79 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Log usage on navigate
|
||||||
|
const unsubHistory = globalHistory.listen(({location}) => {
|
||||||
|
if (!location.hash) {
|
||||||
|
// Remove identifying information, e.g. pubkeys, event ids, etc
|
||||||
|
const name = location.pathname.slice(1)
|
||||||
|
.replace(/(npub|nprofile|note|nevent)[^\/]+/g, (_, m) => `<${m}>`)
|
||||||
|
|
||||||
|
logUsage(btoa(['page', name].join(':')))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval)
|
|
||||||
unsubHistory()
|
unsubHistory()
|
||||||
unsubModal()
|
unsubModal()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
database.onReady(() => {
|
||||||
|
if ($user) {
|
||||||
|
loadAppData($user.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
const interval = setInterval(
|
||||||
|
async () => {
|
||||||
|
const {dufflepudUrl} = $settings
|
||||||
|
|
||||||
|
if (!dufflepudUrl) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find relays with old/missing metadata and refresh them. Only pick a
|
||||||
|
// few so we're not sending too many concurrent http requests
|
||||||
|
const staleRelays = shuffle(
|
||||||
|
await database.relays.all({
|
||||||
|
'refreshed_at:lt': now() - timedelta(7, 'days'),
|
||||||
|
})
|
||||||
|
).slice(0, 10)
|
||||||
|
|
||||||
|
const freshRelays = await Promise.all(
|
||||||
|
staleRelays.map(async ({url}) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(dufflepudUrl + '/relay/info', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({url}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return {...await res.json(), url, refreshed_at: now()}
|
||||||
|
} catch (e) {
|
||||||
|
if (!e.toString().includes('Failed to fetch')) {
|
||||||
|
warn(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {url, refreshed_at: now()}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
database.relays.bulkPatch(createMap('url', freshRelays.filter(identity)))
|
||||||
|
},
|
||||||
|
30_000
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Router {url}>
|
<Router {url}>
|
||||||
<div use:links class="h-full">
|
<div use:links class="h-full">
|
||||||
{#if $ready}
|
|
||||||
<div class="pt-16 text-white h-full lg:ml-56">
|
<div class="pt-16 text-white h-full lg:ml-56">
|
||||||
<Route path="/alerts" component={Alerts} />
|
<Route path="/alerts" component={Alerts} />
|
||||||
<Route path="/search/:activeTab" component={Search} />
|
<Route path="/search/:activeTab" component={Search} />
|
||||||
@ -241,107 +199,9 @@
|
|||||||
</Route>
|
</Route>
|
||||||
<Route path="*" component={NotFound} />
|
<Route path="*" component={NotFound} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<ul
|
<SideNav />
|
||||||
class="py-20 w-56 bg-dark fixed top-0 bottom-0 left-0 transition-all shadow-xl
|
<TopNav />
|
||||||
border-r border-medium text-white overflow-hidden z-10 lg:ml-0"
|
|
||||||
class:-ml-56={!$menuIsOpen}
|
|
||||||
>
|
|
||||||
{#if $user}
|
|
||||||
<li>
|
|
||||||
<a href={routes.person($user.pubkey)} class="flex gap-2 px-4 py-2 pb-6 items-center">
|
|
||||||
<div
|
|
||||||
class="overflow-hidden w-6 h-6 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
|
||||||
style="background-image: url({$user.picture})" />
|
|
||||||
<span class="text-lg font-bold">{displayPerson($user)}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="cursor-pointer relative">
|
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/alerts">
|
|
||||||
<i class="fa-solid fa-bell mr-2" /> Alerts
|
|
||||||
{#if $mostRecentAlert > $lastCheckedAlerts}
|
|
||||||
<div class="w-2 h-2 rounded bg-accent absolute top-3 left-6" />
|
|
||||||
{/if}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
<li class="cursor-pointer">
|
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/search/people">
|
|
||||||
<i class="fa-solid fa-search mr-2" /> Search
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="cursor-pointer">
|
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/notes/network">
|
|
||||||
<i class="fa-solid fa-tag mr-2" /> Notes
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{#if $user}
|
|
||||||
<li class="cursor-pointer relative">
|
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/chat">
|
|
||||||
<i class="fa-solid fa-message mr-2" /> Chat
|
|
||||||
{#if hasNewMessages}
|
|
||||||
<div class="w-2 h-2 rounded bg-accent absolute top-2 left-7" />
|
|
||||||
{/if}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
<li class="h-px mx-3 my-4 bg-medium" />
|
|
||||||
<li class="cursor-pointer relative">
|
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/relays">
|
|
||||||
<i class="fa-solid fa-server mr-2" /> Relays
|
|
||||||
{#if slowConnections.length > 0}
|
|
||||||
<div class="w-2 h-2 rounded bg-accent absolute top-2 left-8" />
|
|
||||||
{/if}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{#if $user}
|
|
||||||
<li class="cursor-pointer">
|
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/keys">
|
|
||||||
<i class="fa-solid fa-key mr-2" /> Keys
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="cursor-pointer">
|
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/settings">
|
|
||||||
<i class="fa-solid fa-gear mr-2" /> Settings
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="cursor-pointer">
|
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/logout">
|
|
||||||
<i class="fa-solid fa-right-from-bracket mr-2" /> Logout
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{:else}
|
|
||||||
<li class="cursor-pointer">
|
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/login">
|
|
||||||
<i class="fa-solid fa-right-to-bracket mr-2" /> Login
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{#if import.meta.env.VITE_SHOW_DEBUG_ROUTE === 'true'}
|
|
||||||
<li class="cursor-pointer">
|
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/debug">
|
|
||||||
<i class="fa-solid fa-bug mr-2" /> Debug
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="fixed top-0 bg-dark flex justify-between items-center text-white w-full p-4
|
|
||||||
border-b border-medium z-10"
|
|
||||||
>
|
|
||||||
<div class="lg:hidden">
|
|
||||||
<button class="fa-solid fa-bars fa-2xl cursor-pointer" bind:this={menuIcon} on:click={toggleMenu} />
|
|
||||||
</div>
|
|
||||||
<Anchor external type="unstyled" href="https://github.com/staab/coracle" class="flex items-center gap-2">
|
|
||||||
<img alt="Coracle Logo" src="/images/favicon.png" class="w-8" />
|
|
||||||
<h1 class="staatliches text-3xl">Coracle</h1>
|
|
||||||
</Anchor>
|
|
||||||
{#if $mostRecentAlert > $lastCheckedAlerts || hasNewMessages}
|
|
||||||
<div class="w-2 h-2 rounded bg-accent absolute top-4 left-12 lg:hidden" />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if $modal}
|
{#if $modal}
|
||||||
<Modal onEscape={closeModal}>
|
<Modal onEscape={closeModal}>
|
||||||
@ -409,3 +269,4 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
onboarding
|
|
@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Onboarding from 'src/Onboarding.svelte'
|
|
||||||
import App from 'src/App.svelte'
|
|
||||||
import {relays} from 'src/agent/relays'
|
|
||||||
import {showOnboarding} from 'src/app/ui'
|
|
||||||
|
|
||||||
$: {
|
|
||||||
if ($relays.length === 0) {
|
|
||||||
showOnboarding.set(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $showOnboarding}
|
|
||||||
<Onboarding />
|
|
||||||
{:else}
|
|
||||||
<App />
|
|
||||||
{/if}
|
|
@ -242,9 +242,9 @@ const relays = new Table('relays', 'url', {
|
|||||||
{url: 'wss://nostr.zebedee.cloud'},
|
{url: 'wss://nostr.zebedee.cloud'},
|
||||||
{url: 'wss://nostr-pub.wellorder.net'},
|
{url: 'wss://nostr-pub.wellorder.net'},
|
||||||
{url: 'wss://relay.nostr.band'},
|
{url: 'wss://relay.nostr.band'},
|
||||||
{url: 'nostr.pleb.network'},
|
{url: 'wss://nostr.pleb.network'},
|
||||||
{url: 'relay.nostrich.de'},
|
{url: 'wss://relay.nostrich.de'},
|
||||||
{url: 'relay.damus.io'},
|
{url: 'wss://relay.damus.io'},
|
||||||
])
|
])
|
||||||
|
|
||||||
return Object.assign(data, defaults)
|
return Object.assign(data, defaults)
|
||||||
|
@ -4,7 +4,7 @@ import {chunk} from 'hurdak/lib/hurdak'
|
|||||||
import {batch, timedelta, now} from 'src/util/misc'
|
import {batch, timedelta, now} from 'src/util/misc'
|
||||||
import {
|
import {
|
||||||
getRelaysForEventParent, getAllPubkeyWriteRelays, aggregateScores,
|
getRelaysForEventParent, getAllPubkeyWriteRelays, aggregateScores,
|
||||||
getUserNetworkWriteRelays,
|
getUserNetworkWriteRelays, getUserReadRelays,
|
||||||
} from 'src/agent/relays'
|
} from 'src/agent/relays'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import pool from 'src/agent/pool'
|
import pool from 'src/agent/pool'
|
||||||
@ -79,7 +79,7 @@ const loadPeople = (pubkeys, {kinds = personKinds, force = false, ...opts} = {})
|
|||||||
}
|
}
|
||||||
|
|
||||||
return load(
|
return load(
|
||||||
getAllPubkeyWriteRelays(pubkeys).slice(0, 10),
|
getUserReadRelays().concat(getAllPubkeyWriteRelays(pubkeys)).slice(0, 10),
|
||||||
{kinds, authors: pubkeys},
|
{kinds, authors: pubkeys},
|
||||||
opts
|
opts
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import type {Relay} from 'src/util/types'
|
import type {Relay} from 'src/util/types'
|
||||||
import {writable, get} from 'svelte/store'
|
import {get} from 'svelte/store'
|
||||||
import {pick, map, assoc, sortBy, uniq, uniqBy, prop} from 'ramda'
|
import {pick, map, assoc, sortBy, uniq, uniqBy, prop} from 'ramda'
|
||||||
import {first} from 'hurdak/lib/hurdak'
|
import {first} from 'hurdak/lib/hurdak'
|
||||||
import {Tags} from 'src/util/nostr'
|
import {Tags} from 'src/util/nostr'
|
||||||
|
import {synced} from 'src/util/misc'
|
||||||
import {getFollows} from 'src/agent/social'
|
import {getFollows} from 'src/agent/social'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import keys from 'src/agent/keys'
|
import keys from 'src/agent/keys'
|
||||||
@ -19,7 +20,7 @@ import keys from 'src/agent/keys'
|
|||||||
// doesn't need to see.
|
// doesn't need to see.
|
||||||
// 5) Advertise relays — write and read back your own relay list
|
// 5) Advertise relays — write and read back your own relay list
|
||||||
|
|
||||||
export const relays = writable([])
|
export const relays = synced('agent/relays', [])
|
||||||
|
|
||||||
// Pubkey relays
|
// Pubkey relays
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {pick, identity, isEmpty} from 'ramda'
|
import {pick, objOf, identity, isEmpty} from 'ramda'
|
||||||
import {nip05} from 'nostr-tools'
|
import {nip05} from 'nostr-tools'
|
||||||
import {noop, createMap, ensurePlural, switcherFn} from 'hurdak/lib/hurdak'
|
import {noop, createMap, ensurePlural, switcherFn} from 'hurdak/lib/hurdak'
|
||||||
import {log, warn} from 'src/util/logger'
|
import {log, warn} from 'src/util/logger'
|
||||||
@ -142,7 +142,7 @@ const calculateRoute = (pubkey, url, type, mode, created_at) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const processRoutes = async events => {
|
const processRoutes = async events => {
|
||||||
const updates = []
|
let updates = []
|
||||||
|
|
||||||
// Sample events so we're not burning too many resources
|
// Sample events so we're not burning too many resources
|
||||||
for (const e of ensurePlural(shuffle(events)).slice(0, 10)) {
|
for (const e of ensurePlural(shuffle(events)).slice(0, 10)) {
|
||||||
@ -199,7 +199,10 @@ const processRoutes = async events => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updates = updates.filter(identity)
|
||||||
|
|
||||||
if (!isEmpty(updates)) {
|
if (!isEmpty(updates)) {
|
||||||
|
await database.relays.bulkPatch(createMap('url', updates.map(pick(['url']))))
|
||||||
await database.routes.bulkPut(createMap('id', updates.filter(identity)))
|
await database.routes.bulkPut(createMap('id', updates.filter(identity)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,8 +227,12 @@ const verifyNip05 = (pubkey, as) =>
|
|||||||
database.people.patch({...person, verified_as: as})
|
database.people.patch({...person, verified_as: as})
|
||||||
|
|
||||||
if (result.relays?.length > 0) {
|
if (result.relays?.length > 0) {
|
||||||
|
const urls = result.relays.filter(isRelay)
|
||||||
|
|
||||||
|
database.relays.bulkPatch(createMap('url', urls.map(objOf('url'))))
|
||||||
|
|
||||||
database.routes.bulkPut(
|
database.routes.bulkPut(
|
||||||
createMap('id', result.relays.filter(isRelay).flatMap(url =>[
|
createMap('id', urls.flatMap(url =>[
|
||||||
calculateRoute(pubkey, url, 'nip05', 'write', now()),
|
calculateRoute(pubkey, url, 'nip05', 'write', now()),
|
||||||
calculateRoute(pubkey, url, 'nip05', 'read', now()),
|
calculateRoute(pubkey, url, 'nip05', 'read', now()),
|
||||||
]))
|
]))
|
||||||
|
31
src/app/connection.js
Normal file
31
src/app/connection.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import {pluck} from 'ramda'
|
||||||
|
import {first} from 'hurdak/lib/hurdak'
|
||||||
|
import {log} from 'src/util/logger'
|
||||||
|
import {writable} from 'svelte/store'
|
||||||
|
import pool from 'src/agent/pool'
|
||||||
|
import {getUserRelays} from 'src/agent/relays'
|
||||||
|
|
||||||
|
export const slowConnections = writable([])
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
// Only notify about relays the user is actually subscribed to
|
||||||
|
const relayUrls = pluck('url', getUserRelays())
|
||||||
|
|
||||||
|
// Prune connections we haven't used in a while
|
||||||
|
pool.getConnections()
|
||||||
|
.filter(conn => conn.lastRequest < Date.now() - 60_000)
|
||||||
|
.forEach(conn => conn.disconnect())
|
||||||
|
|
||||||
|
// Log stats for debugging purposes
|
||||||
|
log(
|
||||||
|
'Connection stats',
|
||||||
|
pool.getConnections()
|
||||||
|
.map(c => `${c.nostr.url} ${c.getQuality().join(' ')}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Alert the user to any heinously slow connections
|
||||||
|
slowConnections.set(
|
||||||
|
pool.getConnections()
|
||||||
|
.filter(c => relayUrls.includes(c.nostr.url) && first(c.getQuality()) < 0.3)
|
||||||
|
)
|
||||||
|
}, 30_000)
|
@ -7,24 +7,25 @@ import {renderContent} from 'src/util/html'
|
|||||||
import {Tags, displayPerson, findReplyId} from 'src/util/nostr'
|
import {Tags, displayPerson, findReplyId} from 'src/util/nostr'
|
||||||
import {user} from 'src/agent/user'
|
import {user} from 'src/agent/user'
|
||||||
import {getNetwork} from 'src/agent/social'
|
import {getNetwork} from 'src/agent/social'
|
||||||
import {relays} from 'src/agent/relays'
|
import {relays, getUserReadRelays} from 'src/agent/relays'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import network from 'src/agent/network'
|
import network from 'src/agent/network'
|
||||||
import keys from 'src/agent/keys'
|
import keys from 'src/agent/keys'
|
||||||
import cmd from 'src/agent/cmd'
|
import cmd from 'src/agent/cmd'
|
||||||
import alerts from 'src/app/alerts'
|
import alerts from 'src/app/alerts'
|
||||||
import messages from 'src/app/messages'
|
import messages from 'src/app/messages'
|
||||||
import {toast, routes, modal, settings, logUsage} from 'src/app/ui'
|
import {routes, modal} from 'src/app/ui'
|
||||||
|
|
||||||
export {toast, modal, settings, alerts, messages, logUsage}
|
export const loadAppData = async pubkey => {
|
||||||
|
if (getUserReadRelays().length > 0) {
|
||||||
export const loadAppData = pubkey =>
|
await Promise.all([
|
||||||
Promise.all([
|
alerts.load(pubkey),
|
||||||
alerts.load(pubkey),
|
alerts.listen(pubkey),
|
||||||
alerts.listen(pubkey),
|
messages.listen(pubkey),
|
||||||
messages.listen(pubkey),
|
network.loadPeople(getNetwork(pubkey)),
|
||||||
network.loadPeople(getNetwork(pubkey)),
|
])
|
||||||
])
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: string}) => {
|
export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: string}) => {
|
||||||
if (privkey) {
|
if (privkey) {
|
||||||
@ -35,31 +36,35 @@ export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: strin
|
|||||||
|
|
||||||
modal.set({type: 'message', message: "Loading your profile data...", spinner: true})
|
modal.set({type: 'message', message: "Loading your profile data...", spinner: true})
|
||||||
|
|
||||||
// Load our user so we can populate network and show profile info
|
if (getUserReadRelays().length === 0) {
|
||||||
await network.loadPeople([pubkey])
|
navigate('/relays')
|
||||||
|
} else {
|
||||||
|
// Load our user so we can populate network and show profile info
|
||||||
|
await network.loadPeople([pubkey])
|
||||||
|
|
||||||
// Load network and start listening, but don't wait for it
|
// Load network and start listening, but don't wait for it
|
||||||
loadAppData(pubkey)
|
loadAppData(pubkey)
|
||||||
|
|
||||||
// Not ideal, but the network tab depends on the user's social network being
|
// Not ideal, but the network tab depends on the user's social network being
|
||||||
// loaded, so put them on global when they first log in so we're not slowing
|
// loaded, so put them on global when they first log in so we're not slowing
|
||||||
// down users' first run experience too much
|
// down users' first run experience too much
|
||||||
navigate('/notes/network')
|
navigate('/notes/network')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addRelay = async url => {
|
export const addRelay = async url => {
|
||||||
const person = get(user) as Person
|
const $user = get(user) as Person
|
||||||
|
|
||||||
relays.update($relays => {
|
relays.update($relays => {
|
||||||
$relays.push({url, write: false, read: true})
|
$relays.push({url, write: false, read: true})
|
||||||
|
|
||||||
if (person) {
|
if ($user) {
|
||||||
(async () => {
|
(async () => {
|
||||||
// Publish to the new set of relays
|
// Publish to the new set of relays
|
||||||
await cmd.setRelays($relays, $relays)
|
await cmd.setRelays($relays, $relays)
|
||||||
|
|
||||||
// Reload alerts, messages, etc
|
// Reload alerts, messages, etc
|
||||||
await loadAppData(person.pubkey)
|
await loadAppData($user.pubkey)
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,12 +73,12 @@ export const addRelay = async url => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const removeRelay = async url => {
|
export const removeRelay = async url => {
|
||||||
const person = get(user) as Person
|
const $user = get(user) as Person
|
||||||
|
|
||||||
relays.update($relays => {
|
relays.update($relays => {
|
||||||
$relays = reject(whereEq({url}), $relays)
|
$relays = reject(whereEq({url}), $relays)
|
||||||
|
|
||||||
if (person && $relays.length > 0) {
|
if ($user && $relays.length > 0) {
|
||||||
cmd.setRelays($relays, $relays)
|
cmd.setRelays($relays, $relays)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,12 +87,12 @@ export const removeRelay = async url => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const setRelayWriteCondition = async (url, write) => {
|
export const setRelayWriteCondition = async (url, write) => {
|
||||||
const person = get(user) as Person
|
const $user = get(user) as Person
|
||||||
|
|
||||||
relays.update($relays => {
|
relays.update($relays => {
|
||||||
$relays = $relays.map(when(whereEq({url}), assoc('write', write)))
|
$relays = $relays.map(when(whereEq({url}), assoc('write', write)))
|
||||||
|
|
||||||
if (person && $relays.length > 0) {
|
if ($user && $relays.length > 0) {
|
||||||
cmd.setRelays($relays, $relays)
|
cmd.setRelays($relays, $relays)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {pluck, reject} from 'ramda'
|
import {pluck, find, reject} from 'ramda'
|
||||||
import {get} from 'svelte/store'
|
import {get, derived} from 'svelte/store'
|
||||||
import {synced, now, timedelta} from 'src/util/misc'
|
import {synced, now, timedelta} from 'src/util/misc'
|
||||||
import {user} from 'src/agent/user'
|
import {user} from 'src/agent/user'
|
||||||
import {getUserReadRelays} from 'src/agent/relays'
|
import {getUserReadRelays} from 'src/agent/relays'
|
||||||
@ -12,6 +12,18 @@ const since = now() - timedelta(30, 'days')
|
|||||||
const mostRecentByPubkey = synced('app/messages/mostRecentByPubkey', {})
|
const mostRecentByPubkey = synced('app/messages/mostRecentByPubkey', {})
|
||||||
const lastCheckedByPubkey = synced('app/messages/lastCheckedByPubkey', {})
|
const lastCheckedByPubkey = synced('app/messages/lastCheckedByPubkey', {})
|
||||||
|
|
||||||
|
const hasNewMessages = derived(
|
||||||
|
[lastCheckedByPubkey, mostRecentByPubkey],
|
||||||
|
([$lastCheckedByPubkey, $mostRecentByPubkey]) => {
|
||||||
|
return Boolean(find(
|
||||||
|
([k, t]) => {
|
||||||
|
return t > now() - timedelta(7, 'days') && ($lastCheckedByPubkey[k] || 0) < t
|
||||||
|
},
|
||||||
|
Object.entries($mostRecentByPubkey)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const listen = async pubkey => {
|
const listen = async pubkey => {
|
||||||
if (listener) {
|
if (listener) {
|
||||||
listener.unsub()
|
listener.unsub()
|
||||||
@ -44,4 +56,4 @@ const listen = async pubkey => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {listen, mostRecentByPubkey, lastCheckedByPubkey}
|
export default {listen, mostRecentByPubkey, lastCheckedByPubkey, hasNewMessages}
|
||||||
|
@ -35,6 +35,10 @@ toast.show = (type, message, timeout = 5) => {
|
|||||||
}, timeout * 1000)
|
}, timeout * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Menu
|
||||||
|
|
||||||
|
export const menuIsOpen = writable(false)
|
||||||
|
|
||||||
// Modals
|
// Modals
|
||||||
|
|
||||||
export const modal = {
|
export const modal = {
|
||||||
@ -110,5 +114,3 @@ export const logUsage = async name => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showOnboarding = writable(false)
|
|
||||||
|
@ -7,9 +7,9 @@ Bugsnag.start({
|
|||||||
collectUserIp: false,
|
collectUserIp: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
import Shell from 'src/Shell.svelte'
|
import App from 'src/App.svelte'
|
||||||
|
|
||||||
const app = new Shell({
|
const app = new App({
|
||||||
target: document.getElementById('app')
|
target: document.getElementById('app')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import {formatTimestamp} from 'src/util/misc'
|
import {formatTimestamp} from 'src/util/misc'
|
||||||
import {killEvent} from 'src/util/html'
|
import {killEvent} from 'src/util/html'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import {modal} from 'src/app'
|
import {modal} from 'src/app/ui'
|
||||||
|
|
||||||
export let note
|
export let note
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import {fly, fade} from "svelte/transition"
|
import {fly, fade} from "svelte/transition"
|
||||||
|
|
||||||
export let onEscape
|
export let onEscape = null
|
||||||
export let nested = false
|
export let nested = false
|
||||||
|
|
||||||
let root
|
let root
|
||||||
@ -14,9 +14,10 @@
|
|||||||
}
|
}
|
||||||
}} />
|
}} />
|
||||||
|
|
||||||
<div class="fixed inset-0 z-10 modal" bind:this={root}>
|
<div class="fixed inset-0 z-20 modal" bind:this={root}>
|
||||||
<button
|
<div
|
||||||
class="absolute inset-0 bg-black cursor-pointer"
|
class="absolute inset-0 bg-black"
|
||||||
|
class:cursor-pointer={onEscape}
|
||||||
class:opacity-75={!nested}
|
class:opacity-75={!nested}
|
||||||
class:opacity-25={nested}
|
class:opacity-25={nested}
|
||||||
transition:fade
|
transition:fade
|
||||||
|
14
src/partials/NavItem.svelte
Normal file
14
src/partials/NavItem.svelte
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import cx from 'classnames'
|
||||||
|
|
||||||
|
export let href
|
||||||
|
export let icon
|
||||||
|
export let inert = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li class={cx($$props.class, {'transition-all hover:bg-accent': !inert}, "cursor-pointer")}>
|
||||||
|
<a {href} class="px-4 py-2 flex gap-2 items-center">
|
||||||
|
<i class={`fa fa-${icon}`} />
|
||||||
|
<slot />
|
||||||
|
</a>
|
||||||
|
</li>
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import keys from 'src/agent/keys'
|
import keys from 'src/agent/keys'
|
||||||
import {modal} from "src/app"
|
import {modal} from "src/app/ui"
|
||||||
|
|
||||||
export let pubkey = null
|
export let pubkey = null
|
||||||
</script>
|
</script>
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
import ImageCircle from 'src/partials/ImageCircle.svelte'
|
import ImageCircle from 'src/partials/ImageCircle.svelte'
|
||||||
import Preview from 'src/partials/Preview.svelte'
|
import Preview from 'src/partials/Preview.svelte'
|
||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
import {toast, settings, modal, renderNote} from "src/app"
|
import {toast, settings, modal} from "src/app/ui"
|
||||||
|
import {renderNote} from "src/app"
|
||||||
import {formatTimestamp, stringToColor} from 'src/util/misc'
|
import {formatTimestamp, stringToColor} from 'src/util/misc'
|
||||||
import Compose from "src/partials/Compose.svelte"
|
import Compose from "src/partials/Compose.svelte"
|
||||||
import Card from "src/partials/Card.svelte"
|
import Card from "src/partials/Card.svelte"
|
||||||
|
@ -9,11 +9,13 @@
|
|||||||
import Content from 'src/partials/Content.svelte'
|
import Content from 'src/partials/Content.svelte'
|
||||||
import Note from "src/partials/Note.svelte"
|
import Note from "src/partials/Note.svelte"
|
||||||
import {user} from 'src/agent/user'
|
import {user} from 'src/agent/user'
|
||||||
|
import {getUserReadRelays} from 'src/agent/relays'
|
||||||
import network from 'src/agent/network'
|
import network from 'src/agent/network'
|
||||||
import {modal, mergeParents} from "src/app"
|
import {modal} from "src/app/ui"
|
||||||
|
import {mergeParents} from "src/app"
|
||||||
|
|
||||||
export let relays
|
|
||||||
export let filter
|
export let filter
|
||||||
|
export let relays = []
|
||||||
export let shouldDisplay = always(true)
|
export let shouldDisplay = always(true)
|
||||||
|
|
||||||
let notes = []
|
let notes = []
|
||||||
@ -74,6 +76,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
if (relays.length === 0) {
|
||||||
|
relays = getUserReadRelays()
|
||||||
|
}
|
||||||
|
|
||||||
const sub = network.listen(relays, {...filter, since}, onChunk)
|
const sub = network.listen(relays, {...filter, since}, onChunk)
|
||||||
|
|
||||||
const scroller = createScroller(() => {
|
const scroller = createScroller(() => {
|
||||||
|
@ -50,7 +50,8 @@
|
|||||||
on:mouseout={() => {showStatus = false}}
|
on:mouseout={() => {showStatus = false}}
|
||||||
on:mouseover={() => {showStatus = true}}
|
on:mouseover={() => {showStatus = true}}
|
||||||
class="w-2 h-2 rounded-full bg-medium cursor-pointer"
|
class="w-2 h-2 rounded-full bg-medium cursor-pointer"
|
||||||
class:bg-danger={quality <= 0.3}
|
class:bg-medium={message === 'Not connected'}
|
||||||
|
class:bg-danger={quality <= 0.3 && message !== 'Not connected'}
|
||||||
class:bg-warning={between(0.3, 0.7, quality)}
|
class:bg-warning={between(0.3, 0.7, quality)}
|
||||||
class:bg-success={quality > 0.7}>
|
class:bg-success={quality > 0.7}>
|
||||||
</span>
|
</span>
|
||||||
@ -79,8 +80,8 @@
|
|||||||
<div class="flex justify-between gap-2">
|
<div class="flex justify-between gap-2">
|
||||||
<span>Publish to this relay?</span>
|
<span>Publish to this relay?</span>
|
||||||
<Toggle
|
<Toggle
|
||||||
value={relay.write !== "!"}
|
value={relay.write}
|
||||||
on:change={() => setRelayWriteCondition(relay.url, relay.write === "!" ? "" : "!")} />
|
on:change={() => setRelayWriteCondition(relay.url, !relay.write)} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
import {displayPerson} from 'src/util/nostr'
|
import {displayPerson} from 'src/util/nostr'
|
||||||
import {now, timedelta} from 'src/util/misc'
|
import {now, timedelta} from 'src/util/misc'
|
||||||
import {messages} from 'src/app'
|
import messages from 'src/app/messages'
|
||||||
|
|
||||||
export let joined = false
|
export let joined = false
|
||||||
export let room
|
export let room
|
||||||
|
96
src/partials/SideNav.svelte
Normal file
96
src/partials/SideNav.svelte
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {displayPerson} from 'src/util/nostr'
|
||||||
|
import {user} from 'src/agent/user'
|
||||||
|
import {menuIsOpen, routes} from 'src/app/ui'
|
||||||
|
import alerts from 'src/app/alerts'
|
||||||
|
import messages from 'src/app/messages'
|
||||||
|
import {slowConnections} from 'src/app/connection'
|
||||||
|
|
||||||
|
const {mostRecentAlert, lastCheckedAlerts} = alerts
|
||||||
|
const {hasNewMessages} = messages
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
class="py-20 w-56 bg-dark fixed top-0 bottom-0 left-0 transition-all shadow-xl
|
||||||
|
border-r border-medium text-white overflow-hidden z-10 lg:ml-0"
|
||||||
|
class:-ml-56={!$menuIsOpen}
|
||||||
|
>
|
||||||
|
{#if $user}
|
||||||
|
<li>
|
||||||
|
<a href={routes.person($user.pubkey)} class="flex gap-2 px-4 py-2 pb-6 items-center">
|
||||||
|
<div
|
||||||
|
class="overflow-hidden w-6 h-6 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||||
|
style="background-image: url({$user.picture})" />
|
||||||
|
<span class="text-lg font-bold">{displayPerson($user)}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="cursor-pointer relative">
|
||||||
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/alerts">
|
||||||
|
<i class="fa-solid fa-bell mr-2" /> Alerts
|
||||||
|
{#if $mostRecentAlert > $lastCheckedAlerts}
|
||||||
|
<div class="w-2 h-2 rounded bg-accent absolute top-3 left-6" />
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
<li class="cursor-pointer">
|
||||||
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/search/people">
|
||||||
|
<i class="fa-solid fa-search mr-2" /> Search
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="cursor-pointer">
|
||||||
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/notes/network">
|
||||||
|
<i class="fa-solid fa-tag mr-2" /> Notes
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{#if $user}
|
||||||
|
<li class="cursor-pointer relative">
|
||||||
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/chat">
|
||||||
|
<i class="fa-solid fa-message mr-2" /> Chat
|
||||||
|
{#if $hasNewMessages}
|
||||||
|
<div class="w-2 h-2 rounded bg-accent absolute top-2 left-7" />
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
<li class="h-px mx-3 my-4 bg-medium" />
|
||||||
|
<li class="cursor-pointer relative">
|
||||||
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/relays">
|
||||||
|
<i class="fa-solid fa-server mr-2" /> Relays
|
||||||
|
{#if $slowConnections.length > 0}
|
||||||
|
<div class="w-2 h-2 rounded bg-accent absolute top-2 left-8" />
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{#if $user}
|
||||||
|
<li class="cursor-pointer">
|
||||||
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/keys">
|
||||||
|
<i class="fa-solid fa-key mr-2" /> Keys
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="cursor-pointer">
|
||||||
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/settings">
|
||||||
|
<i class="fa-solid fa-gear mr-2" /> Settings
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="cursor-pointer">
|
||||||
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/logout">
|
||||||
|
<i class="fa-solid fa-right-from-bracket mr-2" /> Logout
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{:else}
|
||||||
|
<li class="cursor-pointer">
|
||||||
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/login">
|
||||||
|
<i class="fa-solid fa-right-to-bracket mr-2" /> Login
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
{#if import.meta.env.VITE_SHOW_DEBUG_ROUTE === 'true'}
|
||||||
|
<li class="cursor-pointer">
|
||||||
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/debug">
|
||||||
|
<i class="fa-solid fa-bug mr-2" /> Debug
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
</ul>
|
||||||
|
|
34
src/partials/TopNav.svelte
Normal file
34
src/partials/TopNav.svelte
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {onMount} from 'svelte'
|
||||||
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
|
import {menuIsOpen} from 'src/app/ui'
|
||||||
|
import alerts from "src/app/alerts"
|
||||||
|
import messages from "src/app/messages"
|
||||||
|
|
||||||
|
const {lastCheckedAlerts, mostRecentAlert} = alerts
|
||||||
|
const {hasNewMessages} = messages
|
||||||
|
|
||||||
|
const toggleMenu = () => menuIsOpen.update(x => !x)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
document.querySelector("html").addEventListener("click", e => {
|
||||||
|
if (!(e.target as any).matches('.fa-bars')) {
|
||||||
|
menuIsOpen.set(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="fixed top-0 bg-dark flex justify-between items-center text-white w-full p-4
|
||||||
|
border-b border-medium z-10"
|
||||||
|
>
|
||||||
|
<button class="lg:hidden fa fa-bars fa-2xl cursor-pointer" on:click={toggleMenu} />
|
||||||
|
<Anchor external type="unstyled" href="https://github.com/staab/coracle" class="flex items-center gap-2">
|
||||||
|
<img alt="Coracle Logo" src="/images/favicon.png" class="w-8" />
|
||||||
|
<h1 class="staatliches text-3xl">Coracle</h1>
|
||||||
|
</Anchor>
|
||||||
|
{#if $mostRecentAlert > $lastCheckedAlerts || $hasNewMessages}
|
||||||
|
<div class="w-2 h-2 rounded bg-accent absolute top-4 left-12 lg:hidden" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import {fly} from 'svelte/transition'
|
import {fly} from 'svelte/transition'
|
||||||
import {toast, modal, addRelay} from "src/app"
|
import {toast, modal} from "src/app/ui"
|
||||||
|
import {addRelay} from 'src/app'
|
||||||
import Input from 'src/partials/Input.svelte'
|
import Input from 'src/partials/Input.svelte'
|
||||||
import Content from 'src/partials/Content.svelte'
|
import Content from 'src/partials/Content.svelte'
|
||||||
import Heading from 'src/partials/Heading.svelte'
|
import Heading from 'src/partials/Heading.svelte'
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
import Content from 'src/partials/Content.svelte'
|
import Content from 'src/partials/Content.svelte'
|
||||||
import Like from 'src/partials/Like.svelte'
|
import Like from 'src/partials/Like.svelte'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import {alerts, asDisplayEvent} from 'src/app'
|
import alerts from 'src/app/alerts'
|
||||||
|
import {asDisplayEvent} from 'src/app'
|
||||||
|
|
||||||
let limit = 0
|
let limit = 0
|
||||||
let notes = null
|
let notes = null
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
import network from 'src/agent/network'
|
import network from 'src/agent/network'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import {getUserReadRelays} from 'src/agent/relays'
|
import {getUserReadRelays} from 'src/agent/relays'
|
||||||
import {modal, messages} from 'src/app'
|
import {modal} from 'src/app/ui'
|
||||||
|
import messages from 'src/app/messages'
|
||||||
import Room from "src/partials/Room.svelte"
|
import Room from "src/partials/Room.svelte"
|
||||||
import Input from "src/partials/Input.svelte"
|
import Input from "src/partials/Input.svelte"
|
||||||
import Content from "src/partials/Content.svelte"
|
import Content from "src/partials/Content.svelte"
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
import {getRelaysForEventChildren} from 'src/agent/relays'
|
import {getRelaysForEventChildren} from 'src/agent/relays'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import network from 'src/agent/network'
|
import network from 'src/agent/network'
|
||||||
import {modal} from 'src/app'
|
import {modal} from 'src/app/ui'
|
||||||
import cmd from 'src/agent/cmd'
|
import cmd from 'src/agent/cmd'
|
||||||
|
|
||||||
export let entity
|
export let entity
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import Content from "src/partials/Content.svelte"
|
import Content from "src/partials/Content.svelte"
|
||||||
import Heading from 'src/partials/Heading.svelte'
|
import Heading from 'src/partials/Heading.svelte'
|
||||||
import keys from "src/agent/keys"
|
import keys from "src/agent/keys"
|
||||||
import {toast} from "src/app"
|
import {toast} from "src/app/ui"
|
||||||
|
|
||||||
const {pubkey, privkey} = keys
|
const {pubkey, privkey} = keys
|
||||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import Content from "src/partials/Content.svelte"
|
import Content from "src/partials/Content.svelte"
|
||||||
import Heading from 'src/partials/Heading.svelte'
|
import Heading from 'src/partials/Heading.svelte'
|
||||||
import {modal, login} from "src/app"
|
import {modal} from "src/app/ui"
|
||||||
|
import {login} from "src/app"
|
||||||
|
|
||||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
import keys from 'src/agent/keys'
|
import keys from 'src/agent/keys'
|
||||||
import cmd from 'src/agent/cmd'
|
import cmd from 'src/agent/cmd'
|
||||||
import {routes} from 'src/app/ui'
|
import {routes} from 'src/app/ui'
|
||||||
import {messages} from 'src/app'
|
import messages from 'src/app/messages'
|
||||||
|
|
||||||
export let entity
|
export let entity
|
||||||
|
|
||||||
|
@ -4,13 +4,22 @@
|
|||||||
import Content from "src/partials/Content.svelte"
|
import Content from "src/partials/Content.svelte"
|
||||||
import NewNoteButton from "src/partials/NewNoteButton.svelte"
|
import NewNoteButton from "src/partials/NewNoteButton.svelte"
|
||||||
import Tabs from "src/partials/Tabs.svelte"
|
import Tabs from "src/partials/Tabs.svelte"
|
||||||
|
import Modal from 'src/partials/Modal.svelte'
|
||||||
|
import RelayList from "src/routes/RelayList.svelte"
|
||||||
import Network from "src/views/notes/Network.svelte"
|
import Network from "src/views/notes/Network.svelte"
|
||||||
import Popular from "src/views/notes/Popular.svelte"
|
import Popular from "src/views/notes/Popular.svelte"
|
||||||
import {user} from 'src/agent/user'
|
import {relays} from 'src/agent/relays'
|
||||||
|
import {user, follows} from 'src/agent/user'
|
||||||
|
|
||||||
export let activeTab
|
export let activeTab
|
||||||
|
|
||||||
|
const {petnames} = follows
|
||||||
const setActiveTab = tab => navigate(`/notes/${tab}`)
|
const setActiveTab = tab => navigate(`/notes/${tab}`)
|
||||||
|
|
||||||
|
// If they're not following anyone, skip network tab
|
||||||
|
if ($petnames.length === 0) {
|
||||||
|
setActiveTab('popular')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Content>
|
<Content>
|
||||||
@ -33,3 +42,9 @@
|
|||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
<NewNoteButton />
|
<NewNoteButton />
|
||||||
|
|
||||||
|
{#if $relays.length === 0}
|
||||||
|
<Modal>
|
||||||
|
<RelayList />
|
||||||
|
</Modal>
|
||||||
|
{/if}
|
||||||
|
@ -21,8 +21,7 @@
|
|||||||
import network from "src/agent/network"
|
import network from "src/agent/network"
|
||||||
import keys from "src/agent/keys"
|
import keys from "src/agent/keys"
|
||||||
import database from "src/agent/database"
|
import database from "src/agent/database"
|
||||||
import {routes} from "src/app/ui"
|
import {routes, modal} from "src/app/ui"
|
||||||
import {modal} from "src/app"
|
|
||||||
|
|
||||||
export let npub
|
export let npub
|
||||||
export let activeTab
|
export let activeTab
|
||||||
|
@ -14,8 +14,7 @@
|
|||||||
import {user} from "src/agent/user"
|
import {user} from "src/agent/user"
|
||||||
import {getUserWriteRelays} from 'src/agent/relays'
|
import {getUserWriteRelays} from 'src/agent/relays'
|
||||||
import cmd from "src/agent/cmd"
|
import cmd from "src/agent/cmd"
|
||||||
import {toast} from "src/app"
|
import {routes, toast} from "src/app/ui"
|
||||||
import {routes} from "src/app/ui"
|
|
||||||
|
|
||||||
let values = {picture: null, about: null, name: null, nip05: null}
|
let values = {picture: null, about: null, name: null, nip05: null}
|
||||||
|
|
||||||
|
@ -2,13 +2,14 @@
|
|||||||
import {pluck} from 'ramda'
|
import {pluck} from 'ramda'
|
||||||
import {fly} from 'svelte/transition'
|
import {fly} from 'svelte/transition'
|
||||||
import {fuzzy} from "src/util/misc"
|
import {fuzzy} from "src/util/misc"
|
||||||
|
import {isRelay} from "src/util/nostr"
|
||||||
import Input from "src/partials/Input.svelte"
|
import Input from "src/partials/Input.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import Content from "src/partials/Content.svelte"
|
import Content from "src/partials/Content.svelte"
|
||||||
import RelayCard from "src/partials/RelayCard.svelte"
|
import RelayCard from "src/partials/RelayCard.svelte"
|
||||||
import {relays} from "src/agent/relays"
|
import {relays} from "src/agent/relays"
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import {modal} from "src/app"
|
import {modal} from "src/app/ui"
|
||||||
|
|
||||||
let q = ""
|
let q = ""
|
||||||
let search
|
let search
|
||||||
@ -18,55 +19,60 @@
|
|||||||
const joined = new Set(pluck('url', $relays))
|
const joined = new Set(pluck('url', $relays))
|
||||||
|
|
||||||
search = fuzzy(
|
search = fuzzy(
|
||||||
$knownRelays.filter(r => !joined.has(r.url)),
|
$knownRelays.filter(r => isRelay(r.url) && !joined.has(r.url)),
|
||||||
{keys: ["name", "description", "url"]}
|
{keys: ["name", "description", "url"]}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Content>
|
<div in:fly={{y: 20}}>
|
||||||
<div class="flex justify-between">
|
<Content>
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex justify-between mt-10">
|
||||||
<i class="fa fa-server fa-lg" />
|
<div class="flex gap-2 items-center">
|
||||||
<h2 class="staatliches text-2xl">Your relays</h2>
|
<i class="fa fa-server fa-lg" />
|
||||||
</div>
|
<h2 class="staatliches text-2xl">Your relays</h2>
|
||||||
<Anchor type="button" on:click={() => modal.set({type: 'relay/add', url: q})}>
|
</div>
|
||||||
<i class="fa-solid fa-plus" /> Add Relay
|
<Anchor type="button" on:click={() => modal.set({type: 'relay/add', url: q})}>
|
||||||
</Anchor>
|
<i class="fa-solid fa-plus" /> Add Relay
|
||||||
</div>
|
</Anchor>
|
||||||
<p>
|
|
||||||
Relays are hubs for your content and connections. At least one is required to
|
|
||||||
interact with the network, but you can join as many as you like.
|
|
||||||
</p>
|
|
||||||
{#if $relays.length === 0}
|
|
||||||
<div class="text-center">No relays connected</div>
|
|
||||||
{/if}
|
|
||||||
<div class="grid grid-cols-1 gap-4">
|
|
||||||
{#each $relays as relay (relay.url)}
|
|
||||||
<RelayCard showControls {relay} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-6" in:fly={{y: 20, delay: 1000}}>
|
|
||||||
{#if ($knownRelays || []).length > 0}
|
|
||||||
<div class="pt-2 mb-2 border-b border-solid border-medium" />
|
|
||||||
<div class="flex gap-2 items-center">
|
|
||||||
<i class="fa fa-globe fa-lg" />
|
|
||||||
<h2 class="staatliches text-2xl">Other relays</h2>
|
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
Coracle automatically discovers relays as you browse the network. Adding more relays
|
Relays are hubs for your content and connections. At least one is required to
|
||||||
will generally make things quicker to load, at the expense of higher data usage.
|
interact with the network, but you can join as many as you like.
|
||||||
</p>
|
</p>
|
||||||
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
|
{#if $relays.length === 0}
|
||||||
<i slot="before" class="fa-solid fa-search" />
|
<div class="text-center mt-10 flex gap-2 justify-center items-center">
|
||||||
</Input>
|
<i class="fa fa-triangle-exclamation" />
|
||||||
|
No relays connected
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each (search(q) || []).slice(0, 50) as relay (relay.url)}
|
<div class="grid grid-cols-1 gap-4">
|
||||||
<RelayCard {relay} />
|
{#each $relays as relay (relay.url)}
|
||||||
{/each}
|
<RelayCard showControls {relay} />
|
||||||
<small class="text-center">
|
{/each}
|
||||||
Showing {Math.min(($knownRelays || []).length - $relays.length, 50)}
|
</div>
|
||||||
of {($knownRelays || []).length - $relays.length} known relays
|
<div class="flex flex-col gap-6" in:fly={{y: 20, delay: 1000}}>
|
||||||
</small>
|
{#if ($knownRelays || []).length > 0}
|
||||||
</div>
|
<div class="pt-2 mb-2 border-b border-solid border-medium" />
|
||||||
</Content>
|
<div class="flex gap-2 items-center">
|
||||||
|
<i class="fa fa-earth-asia fa-lg" />
|
||||||
|
<h2 class="staatliches text-2xl">Other relays</h2>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Coracle automatically discovers relays as you browse the network. Adding more relays
|
||||||
|
will generally make things quicker to load, at the expense of higher data usage.
|
||||||
|
</p>
|
||||||
|
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
|
||||||
|
<i slot="before" class="fa-solid fa-search" />
|
||||||
|
</Input>
|
||||||
|
{/if}
|
||||||
|
{#each (search(q) || []).slice(0, 50) as relay (relay.url)}
|
||||||
|
<RelayCard {relay} />
|
||||||
|
{/each}
|
||||||
|
<small class="text-center">
|
||||||
|
Showing {Math.min(($knownRelays || []).length - $relays.length, 50)}
|
||||||
|
of {($knownRelays || []).length - $relays.length} known relays
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
|
</div>
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
import {navigate} from 'svelte-routing'
|
import {navigate} from 'svelte-routing'
|
||||||
import Content from 'src/partials/Content.svelte'
|
import Content from 'src/partials/Content.svelte'
|
||||||
import Tabs from 'src/partials/Tabs.svelte'
|
import Tabs from 'src/partials/Tabs.svelte'
|
||||||
|
import Modal from 'src/partials/Modal.svelte'
|
||||||
import SearchPeople from 'src/views/SearchPeople.svelte'
|
import SearchPeople from 'src/views/SearchPeople.svelte'
|
||||||
|
import RelayList from "src/routes/RelayList.svelte"
|
||||||
import Scan from 'src/views/Scan.svelte'
|
import Scan from 'src/views/Scan.svelte'
|
||||||
|
import {relays} from 'src/agent/relays'
|
||||||
|
|
||||||
export let activeTab
|
export let activeTab
|
||||||
|
|
||||||
@ -18,3 +21,9 @@
|
|||||||
<Scan />
|
<Scan />
|
||||||
{/if}
|
{/if}
|
||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
|
{#if $relays.length === 0}
|
||||||
|
<Modal>
|
||||||
|
<RelayList />
|
||||||
|
</Modal>
|
||||||
|
{/if}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
import Content from "src/partials/Content.svelte"
|
import Content from "src/partials/Content.svelte"
|
||||||
import Heading from "src/partials/Heading.svelte"
|
import Heading from "src/partials/Heading.svelte"
|
||||||
import {user} from 'src/agent/user'
|
import {user} from 'src/agent/user'
|
||||||
import {toast, settings} from "src/app"
|
import {toast, settings} from "src/app/ui"
|
||||||
|
|
||||||
let values = {...$settings}
|
let values = {...$settings}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import {getUserWriteRelays} from 'src/agent/relays'
|
import {getUserWriteRelays} from 'src/agent/relays'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import cmd from "src/agent/cmd"
|
import cmd from "src/agent/cmd"
|
||||||
import {toast, modal} from "src/app"
|
import {toast, modal} from "src/app/ui"
|
||||||
|
|
||||||
export let room = {name: null, id: null, about: null, picture: null}
|
export let room = {name: null, id: null, about: null, picture: null}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import {fly} from 'svelte/transition'
|
import {fly} from 'svelte/transition'
|
||||||
import {navigate} from "svelte-routing"
|
import {navigate} from "svelte-routing"
|
||||||
import {fuzzy} from "src/util/misc"
|
import {fuzzy} from "src/util/misc"
|
||||||
|
import {isRelay} from "src/util/nostr"
|
||||||
import Button from "src/partials/Button.svelte"
|
import Button from "src/partials/Button.svelte"
|
||||||
import Compose from "src/partials/Compose.svelte"
|
import Compose from "src/partials/Compose.svelte"
|
||||||
import Input from "src/partials/Input.svelte"
|
import Input from "src/partials/Input.svelte"
|
||||||
@ -17,7 +18,7 @@
|
|||||||
import {getUserWriteRelays} from 'src/agent/relays'
|
import {getUserWriteRelays} from 'src/agent/relays'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import cmd from "src/agent/cmd"
|
import cmd from "src/agent/cmd"
|
||||||
import {toast, modal} from "src/app"
|
import {toast, modal} from "src/app/ui"
|
||||||
|
|
||||||
export let pubkey = null
|
export let pubkey = null
|
||||||
|
|
||||||
@ -33,7 +34,7 @@
|
|||||||
const joined = new Set(pluck('url', relays))
|
const joined = new Set(pluck('url', relays))
|
||||||
|
|
||||||
search = fuzzy(
|
search = fuzzy(
|
||||||
$knownRelays.filter(r => !joined.has(r.url)),
|
$knownRelays.filter(r => isRelay(r.url) && !joined.has(r.url)),
|
||||||
{keys: ["name", "description", "url"]}
|
{keys: ["name", "description", "url"]}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
import {user} from 'src/agent/user'
|
import {user} from 'src/agent/user'
|
||||||
import {getUserWriteRelays} from 'src/agent/relays'
|
import {getUserWriteRelays} from 'src/agent/relays'
|
||||||
import cmd from 'src/agent/cmd'
|
import cmd from 'src/agent/cmd'
|
||||||
import {modal} from 'src/app'
|
import {modal} from 'src/app/ui'
|
||||||
|
|
||||||
const muffle = $user.muffle || []
|
const muffle = $user.muffle || []
|
||||||
const muffleOptions = ['Never', 'Sometimes', 'Often', 'Always']
|
const muffleOptions = ['Never', 'Sometimes', 'Often', 'Always']
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
import Content from 'src/partials/Content.svelte'
|
import Content from 'src/partials/Content.svelte'
|
||||||
import Input from 'src/partials/Input.svelte'
|
import Input from 'src/partials/Input.svelte'
|
||||||
import {getPubkeyWriteRelays} from 'src/agent/relays'
|
import {getPubkeyWriteRelays} from 'src/agent/relays'
|
||||||
import {modal, toast} from 'src/app'
|
import {modal, toast} from 'src/app/ui'
|
||||||
|
|
||||||
const {pubkey} = $modal.person
|
const {pubkey} = $modal.person
|
||||||
const relays = [prop('url', getPubkeyWriteRelays(pubkey))]
|
const relays = [prop('url', getPubkeyWriteRelays(pubkey))]
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
import Content from 'src/partials/Content.svelte'
|
import Content from 'src/partials/Content.svelte'
|
||||||
import Heading from 'src/partials/Heading.svelte'
|
import Heading from 'src/partials/Heading.svelte'
|
||||||
import {toast, login} from "src/app"
|
import {toast} from "src/app/ui"
|
||||||
|
import {login} from "src/app"
|
||||||
|
|
||||||
let nsec = ''
|
let nsec = ''
|
||||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
import Content from 'src/partials/Content.svelte'
|
import Content from 'src/partials/Content.svelte'
|
||||||
import Heading from 'src/partials/Heading.svelte'
|
import Heading from 'src/partials/Heading.svelte'
|
||||||
import {toast, login} from "src/app"
|
import {toast} from "src/app/ui"
|
||||||
|
import {login} from "src/app"
|
||||||
|
|
||||||
let npub = ''
|
let npub = ''
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import {fly} from 'svelte/transition'
|
||||||
import {fuzzy} from "src/util/misc"
|
import {fuzzy} from "src/util/misc"
|
||||||
import {personKinds} from "src/util/nostr"
|
import {personKinds} from "src/util/nostr"
|
||||||
import Input from "src/partials/Input.svelte"
|
import Input from "src/partials/Input.svelte"
|
||||||
@ -28,6 +29,8 @@
|
|||||||
|
|
||||||
{#each (search ? search(q) : []).slice(0, 30) as person (person.pubkey)}
|
{#each (search ? search(q) : []).slice(0, 30) as person (person.pubkey)}
|
||||||
{#if person.pubkey !== $user?.pubkey}
|
{#if person.pubkey !== $user?.pubkey}
|
||||||
<PersonInfo {person} />
|
<div in:fly={{y: 20}}>
|
||||||
|
<PersonInfo {person} />
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
import Content from 'src/partials/Content.svelte'
|
import Content from 'src/partials/Content.svelte'
|
||||||
import Heading from 'src/partials/Heading.svelte'
|
import Heading from 'src/partials/Heading.svelte'
|
||||||
import {toast, login} from "src/app"
|
import {toast} from "src/app/ui"
|
||||||
|
import {login} from "src/app"
|
||||||
|
|
||||||
const nsec = nip19.nsecEncode(generatePrivateKey())
|
const nsec = nip19.nsecEncode(generatePrivateKey())
|
||||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||||
|
@ -1,18 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import {uniq} from 'ramda'
|
|
||||||
import Notes from "src/partials/Notes.svelte"
|
import Notes from "src/partials/Notes.svelte"
|
||||||
import {isLike} from 'src/util/nostr'
|
import {isLike} from 'src/util/nostr'
|
||||||
import {user} from 'src/agent/user'
|
|
||||||
import {getFollows, getNetwork} from 'src/agent/social'
|
|
||||||
import {getAllPubkeyWriteRelays} from 'src/agent/relays'
|
|
||||||
|
|
||||||
// Get first- and second-order follows. shuffle and slice network so we're not
|
const filter = {kinds: [1, 7]}
|
||||||
// sending too many pubkeys. This will also result in some variety.
|
|
||||||
const follows = getFollows($user?.pubkey)
|
|
||||||
const network = getNetwork($user?.pubkey)
|
|
||||||
const authors = uniq(follows.concat(network)).slice(0, 100)
|
|
||||||
const relays = getAllPubkeyWriteRelays(authors)
|
|
||||||
const filter = {kinds: [1, 7], authors}
|
|
||||||
|
|
||||||
const shouldDisplay = note => {
|
const shouldDisplay = note => {
|
||||||
return (
|
return (
|
||||||
@ -22,4 +12,4 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Notes {relays} {filter} {shouldDisplay} />
|
<Notes {filter} {shouldDisplay} />
|
||||||
|
1
src/views/notes/Top.svelte
Normal file
1
src/views/notes/Top.svelte
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
Loading…
Reference in New Issue
Block a user