mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-19 11:43:35 +00:00
Make users with no relays or follows comply
This commit is contained in:
parent
e2baa5c0c9
commit
7eeeb63e82
@ -27,6 +27,22 @@
|
|||||||
import messages from "src/app/messages"
|
import messages from "src/app/messages"
|
||||||
import {modal, toast, settings, routes, menuIsOpen, logUsage} from "src/app/ui"
|
import {modal, toast, settings, routes, menuIsOpen, logUsage} from "src/app/ui"
|
||||||
import RelayCard from "src/partials/RelayCard.svelte"
|
import RelayCard from "src/partials/RelayCard.svelte"
|
||||||
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
|
import Content from 'src/partials/Content.svelte'
|
||||||
|
import EnsureData from 'src/partials/EnsureData.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'
|
||||||
|
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 AddRelay from "src/routes/AddRelay.svelte"
|
import AddRelay from "src/routes/AddRelay.svelte"
|
||||||
import Alerts from "src/routes/Alerts.svelte"
|
import Alerts from "src/routes/Alerts.svelte"
|
||||||
import Bech32Entity from "src/routes/Bech32Entity.svelte"
|
import Bech32Entity from "src/routes/Bech32Entity.svelte"
|
||||||
@ -44,21 +60,6 @@
|
|||||||
import RelayList from "src/routes/RelayList.svelte"
|
import RelayList from "src/routes/RelayList.svelte"
|
||||||
import Search from "src/routes/Search.svelte"
|
import Search from "src/routes/Search.svelte"
|
||||||
import Settings from "src/routes/Settings.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})
|
||||||
|
|
||||||
@ -167,8 +168,16 @@
|
|||||||
<div use:links class="h-full">
|
<div use:links class="h-full">
|
||||||
<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" let:params>
|
||||||
<Route path="/notes/:activeTab" component={Notes} />
|
<EnsureData enforcePeople={false}>
|
||||||
|
<Search activeTab={params.activeTab} />
|
||||||
|
</EnsureData>
|
||||||
|
</Route>
|
||||||
|
<Route path="/notes/:activeTab" let:params>
|
||||||
|
<EnsureData>
|
||||||
|
<Notes activeTab={params.activeTab} />
|
||||||
|
</EnsureData>
|
||||||
|
</Route>
|
||||||
<Route path="/people/:npub/:activeTab" let:params>
|
<Route path="/people/:npub/:activeTab" let:params>
|
||||||
{#key params.npub}
|
{#key params.npub}
|
||||||
<Person npub={params.npub} activeTab={params.activeTab} />
|
<Person npub={params.npub} activeTab={params.activeTab} />
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import {uniq, uniqBy, prop, map, propEq, indexBy, pluck} from 'ramda'
|
import {uniq, uniqBy, prop, map, propEq, indexBy, pluck} from 'ramda'
|
||||||
import {personKinds, findReplyId} from 'src/util/nostr'
|
import {personKinds, findReplyId} from 'src/util/nostr'
|
||||||
import {chunk} from 'hurdak/lib/hurdak'
|
import {chunk} from 'hurdak/lib/hurdak'
|
||||||
import {batch, timedelta, now} from 'src/util/misc'
|
import {batch, shuffle, timedelta, now} from 'src/util/misc'
|
||||||
import {
|
import {
|
||||||
getRelaysForEventParent, getAllPubkeyWriteRelays, aggregateScores,
|
getRelaysForEventParent, getAllPubkeyWriteRelays, aggregateScores,
|
||||||
getUserNetworkWriteRelays, getUserReadRelays,
|
getUserReadRelays,
|
||||||
} from 'src/agent/relays'
|
} from 'src/agent/relays'
|
||||||
|
import {getUserNetwork} from 'src/agent/social'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import pool from 'src/agent/pool'
|
import pool from 'src/agent/pool'
|
||||||
import keys from 'src/agent/keys'
|
import keys from 'src/agent/keys'
|
||||||
@ -32,6 +33,10 @@ const publish = async (relays, event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const load = async (relays, filter, opts?): Promise<Record<string, unknown>[]> => {
|
const load = async (relays, filter, opts?): Promise<Record<string, unknown>[]> => {
|
||||||
|
if (relays.length === 0) {
|
||||||
|
relays = getUserReadRelays()
|
||||||
|
}
|
||||||
|
|
||||||
const events = await pool.request(relays, filter, opts)
|
const events = await pool.request(relays, filter, opts)
|
||||||
|
|
||||||
await sync.processEvents(events)
|
await sync.processEvents(events)
|
||||||
@ -40,6 +45,10 @@ const load = async (relays, filter, opts?): Promise<Record<string, unknown>[]> =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listen = (relays, filter, onEvents, {shouldProcess = true}: any = {}) => {
|
const listen = (relays, filter, onEvents, {shouldProcess = true}: any = {}) => {
|
||||||
|
if (relays.length === 0) {
|
||||||
|
relays = getUserReadRelays()
|
||||||
|
}
|
||||||
|
|
||||||
return pool.subscribe(relays, filter, {
|
return pool.subscribe(relays, filter, {
|
||||||
onEvent: batch(300, events => {
|
onEvent: batch(300, events => {
|
||||||
if (shouldProcess) {
|
if (shouldProcess) {
|
||||||
@ -54,6 +63,10 @@ const listen = (relays, filter, onEvents, {shouldProcess = true}: any = {}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listenUntilEose = (relays, filter, onEvents, {shouldProcess = true}: any = {}) => {
|
const listenUntilEose = (relays, filter, onEvents, {shouldProcess = true}: any = {}) => {
|
||||||
|
if (relays.length === 0) {
|
||||||
|
relays = getUserReadRelays()
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
pool.subscribeUntilEose(relays, filter, {
|
pool.subscribeUntilEose(relays, filter, {
|
||||||
onClose: () => resolve(),
|
onClose: () => resolve(),
|
||||||
@ -79,7 +92,7 @@ const loadPeople = (pubkeys, {kinds = personKinds, force = false, ...opts} = {})
|
|||||||
}
|
}
|
||||||
|
|
||||||
return load(
|
return load(
|
||||||
getUserReadRelays().concat(getAllPubkeyWriteRelays(pubkeys)).slice(0, 10),
|
shuffle(getUserReadRelays().concat(getAllPubkeyWriteRelays(pubkeys))).slice(0, 3),
|
||||||
{kinds, authors: pubkeys},
|
{kinds, authors: pubkeys},
|
||||||
opts
|
opts
|
||||||
)
|
)
|
||||||
@ -97,15 +110,16 @@ const streamContext = ({notes, updateNotes, depth = 0}) => {
|
|||||||
// but it's also more likely to include spam. Checking our user's social graph
|
// but it's also more likely to include spam. Checking our user's social graph
|
||||||
// avoids this problem. TODO: review this, maybe add note authors's graphs to this
|
// avoids this problem. TODO: review this, maybe add note authors's graphs to this
|
||||||
// as well.
|
// as well.
|
||||||
const relays = getUserNetworkWriteRelays()
|
const relays = getAllPubkeyWriteRelays(getUserNetwork()).slice(0, 3)
|
||||||
|
|
||||||
// Some relays reject very large filters, send multiple
|
// Some relays reject very large filters, send multiple
|
||||||
chunk(256, notes).forEach(chunk => {
|
chunk(256, notes).forEach(chunk => {
|
||||||
const authors = getStalePubkeys(pluck('pubkey', chunk))
|
const authors = getStalePubkeys(pluck('pubkey', chunk))
|
||||||
const filter = [
|
const filter = [{kinds: [1, 7], '#e': pluck('id', chunk)}] as Array<object>
|
||||||
{kinds: [1, 7], '#e': pluck('id', chunk)},
|
|
||||||
{kinds: personKinds, authors},
|
if (authors.length > 0) {
|
||||||
]
|
filter.push({kinds: personKinds, authors})
|
||||||
|
}
|
||||||
|
|
||||||
// Load authors and reactions in one subscription
|
// Load authors and reactions in one subscription
|
||||||
listenUntilEose(relays, filter, events => {
|
listenUntilEose(relays, filter, events => {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import type {Relay} from 'src/util/types'
|
import type {Relay} from 'src/util/types'
|
||||||
import {get} from 'svelte/store'
|
import {get} from 'svelte/store'
|
||||||
import {pick, map, assoc, sortBy, uniq, uniqBy, prop} from 'ramda'
|
import {pick, map, assoc, sortBy, 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 {synced} from 'src/util/misc'
|
||||||
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'
|
||||||
|
|
||||||
@ -51,19 +50,6 @@ export const getUserRelays = (): Array<Relay> => get(relays).map(assoc('score',
|
|||||||
export const getUserReadRelays = () => getUserRelays().filter(prop('read'))
|
export const getUserReadRelays = () => getUserRelays().filter(prop('read'))
|
||||||
export const getUserWriteRelays = () => getUserRelays().filter(prop('write'))
|
export const getUserWriteRelays = () => getUserRelays().filter(prop('write'))
|
||||||
|
|
||||||
// Network relays
|
|
||||||
|
|
||||||
export const getNetworkWriteRelays = pubkey => {
|
|
||||||
const follows = Tags.wrap(getFollows(pubkey)).values().all()
|
|
||||||
const others = Tags.wrap(follows.flatMap(getFollows)).values().all()
|
|
||||||
|
|
||||||
return getAllPubkeyWriteRelays(uniq(follows.concat(others)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// User's network relays
|
|
||||||
|
|
||||||
export const getUserNetworkWriteRelays = () => getNetworkWriteRelays(get(keys.pubkey))
|
|
||||||
|
|
||||||
// Event-related special cases
|
// Event-related special cases
|
||||||
|
|
||||||
// If we're looking for an event's parent, tags are the most reliable hint,
|
// If we're looking for an event's parent, tags are the most reliable hint,
|
||||||
@ -74,7 +60,7 @@ export const getRelaysForEventParent = event => {
|
|||||||
const pubkeys = tags.type("p").values().all()
|
const pubkeys = tags.type("p").values().all()
|
||||||
const pubkeyRelays = pubkeys.flatMap(getPubkeyWriteRelays)
|
const pubkeyRelays = pubkeys.flatMap(getPubkeyWriteRelays)
|
||||||
|
|
||||||
return uniqByUrl(relays.concat(pubkeyRelays).concat(event.seen_on))
|
return uniqByUrl(relays.concat(pubkeyRelays).concat({url: event.seen_on}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're looking for an event's children, the read relays the author has
|
// If we're looking for an event's children, the read relays the author has
|
||||||
@ -82,10 +68,10 @@ export const getRelaysForEventParent = event => {
|
|||||||
// will write replies there. However, this may include spam, so we may want
|
// will write replies there. However, this may include spam, so we may want
|
||||||
// to read from the current user's network's read relays instead.
|
// to read from the current user's network's read relays instead.
|
||||||
export const getRelaysForEventChildren = event => {
|
export const getRelaysForEventChildren = event => {
|
||||||
return uniqByUrl(getPubkeyReadRelays(event.pubkey).concat(event.seen_on))
|
return uniqByUrl(getPubkeyReadRelays(event.pubkey).concat({url: event.seen_on}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getRelayForEventHint = event => event.seen_on
|
export const getRelayForEventHint = event => ({url: event.seen_on})
|
||||||
|
|
||||||
export const getRelayForPersonHint = (pubkey, event) =>
|
export const getRelayForPersonHint = (pubkey, event) =>
|
||||||
first(getPubkeyWriteRelays(pubkey)) || getRelayForEventHint(event)
|
first(getPubkeyWriteRelays(pubkey)) || getRelayForEventHint(event)
|
||||||
|
@ -1,9 +1,22 @@
|
|||||||
import {uniq} from 'ramda'
|
import {uniq} from 'ramda'
|
||||||
|
import {get} from 'svelte/store'
|
||||||
import {Tags} from 'src/util/nostr'
|
import {Tags} from 'src/util/nostr'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
|
import {follows} from 'src/agent/user'
|
||||||
|
|
||||||
export const getFollows = pubkey =>
|
export const getFollows = pubkey =>
|
||||||
Tags.wrap(database.getPersonWithFallback(pubkey).petnames).type("p").values().all()
|
Tags.wrap(database.getPersonWithFallback(pubkey).petnames).type("p").values().all()
|
||||||
|
|
||||||
export const getNetwork = pubkey =>
|
export const getNetwork = pubkey => {
|
||||||
uniq(getFollows(pubkey).flatMap(getFollows))
|
const follows = getFollows(pubkey)
|
||||||
|
|
||||||
|
return uniq(follows.concat(follows.flatMap(getFollows)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getUserFollows = (): Array<string> => get(follows.pubkeys)
|
||||||
|
|
||||||
|
export const getUserNetwork = () => {
|
||||||
|
const follows = getUserFollows()
|
||||||
|
|
||||||
|
return uniq(follows.concat(follows.flatMap(getFollows)))
|
||||||
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import {pick, objOf, identity, isEmpty} from 'ramda'
|
import {pick, 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 {warn} from 'src/util/logger'
|
||||||
import {now, timedelta, shuffle, hash} from 'src/util/misc'
|
import {now, timedelta, shuffle, hash} from 'src/util/misc'
|
||||||
import {personKinds, Tags, roomAttrs, isRelay} from 'src/util/nostr'
|
import {Tags, roomAttrs, isRelay, normalizeRelayUrl} from 'src/util/nostr'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
|
|
||||||
const processEvents = async events => {
|
const processEvents = async events => {
|
||||||
@ -17,7 +17,7 @@ const processEvents = async events => {
|
|||||||
|
|
||||||
const processProfileEvents = async events => {
|
const processProfileEvents = async events => {
|
||||||
const profileEvents = ensurePlural(events)
|
const profileEvents = ensurePlural(events)
|
||||||
.filter(e => personKinds.includes(e.kind))
|
.filter(e => [0, 3, 12165].includes(e.kind))
|
||||||
|
|
||||||
const updates = {}
|
const updates = {}
|
||||||
for (const e of profileEvents) {
|
for (const e of profileEvents) {
|
||||||
@ -43,9 +43,6 @@ const processProfileEvents = async events => {
|
|||||||
},
|
},
|
||||||
3: () => ({petnames: e.tags}),
|
3: () => ({petnames: e.tags}),
|
||||||
12165: () => ({muffle: e.tags}),
|
12165: () => ({muffle: e.tags}),
|
||||||
default: () => {
|
|
||||||
log(`Received unsupported event type ${e.kind}`)
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
updated_at: now(),
|
updated_at: now(),
|
||||||
}
|
}
|
||||||
@ -119,11 +116,12 @@ const getWeight = type => {
|
|||||||
if (type === 'tag') return 0.1
|
if (type === 'tag') return 0.1
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculateRoute = (pubkey, url, type, mode, created_at) => {
|
const calculateRoute = (pubkey, rawUrl, type, mode, created_at) => {
|
||||||
if (!isRelay(url)) {
|
if (!isRelay(rawUrl)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const url = normalizeRelayUrl(rawUrl)
|
||||||
const id = hash([pubkey, url, mode].join('')).toString()
|
const id = hash([pubkey, url, mode].join('')).toString()
|
||||||
const score = getWeight(type) * (1 - (now() - created_at) / timedelta(30, 'days'))
|
const score = getWeight(type) * (1 - (now() - created_at) / timedelta(30, 'days'))
|
||||||
const route = database.routes.get(id) || {id, pubkey, url, mode, score: 0, count: 0}
|
const route = database.routes.get(id) || {id, pubkey, url, mode, score: 0, count: 0}
|
||||||
@ -229,7 +227,9 @@ const verifyNip05 = (pubkey, as) =>
|
|||||||
if (result.relays?.length > 0) {
|
if (result.relays?.length > 0) {
|
||||||
const urls = result.relays.filter(isRelay)
|
const urls = result.relays.filter(isRelay)
|
||||||
|
|
||||||
database.relays.bulkPatch(createMap('url', urls.map(objOf('url'))))
|
database.relays.bulkPatch(
|
||||||
|
createMap('url', urls.map(url => ({url: normalizeRelayUrl(url)})))
|
||||||
|
)
|
||||||
|
|
||||||
database.routes.bulkPut(
|
database.routes.bulkPut(
|
||||||
createMap('id', urls.flatMap(url =>[
|
createMap('id', urls.flatMap(url =>[
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import {pipe, concat, reject, nth, map} from 'ramda'
|
|
||||||
import type {Person} from 'src/util/types'
|
import type {Person} from 'src/util/types'
|
||||||
import type {Readable} from 'svelte/store'
|
import type {Readable} from 'svelte/store'
|
||||||
import {derived, get, writable} from 'svelte/store'
|
import {pipe, concat, reject, nth, map} from 'ramda'
|
||||||
|
import {synced} from 'src/util/misc'
|
||||||
|
import {derived, get} from 'svelte/store'
|
||||||
import database from 'src/agent/database'
|
import database from 'src/agent/database'
|
||||||
import {getUserWriteRelays} from 'src/agent/relays'
|
import {getUserWriteRelays} from 'src/agent/relays'
|
||||||
import keys from 'src/agent/keys'
|
import keys from 'src/agent/keys'
|
||||||
@ -22,7 +23,7 @@ export const user = derived(
|
|||||||
// the user is logged in or not
|
// the user is logged in or not
|
||||||
|
|
||||||
export const follows = (() => {
|
export const follows = (() => {
|
||||||
const anonPetnames = writable([])
|
const anonPetnames = synced('agent/user/anonPetnames', [])
|
||||||
|
|
||||||
const petnames = derived(
|
const petnames = derived(
|
||||||
[user, anonPetnames],
|
[user, anonPetnames],
|
||||||
@ -32,7 +33,7 @@ export const follows = (() => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
petnames,
|
petnames,
|
||||||
pubkeys: derived(petnames, pipe(nth(0), map(nth(1)))),
|
pubkeys: derived(petnames, map(nth(1))) as Readable<Array<string>>,
|
||||||
update(f) {
|
update(f) {
|
||||||
const $petnames = f(get(petnames))
|
const $petnames = f(get(petnames))
|
||||||
|
|
||||||
|
83
src/partials/EnsureData.svelte
Normal file
83
src/partials/EnsureData.svelte
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<script>
|
||||||
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
|
import Content from "src/partials/Content.svelte"
|
||||||
|
import Modal from "src/partials/Modal.svelte"
|
||||||
|
import RelaySearch from "src/partials/RelaySearch.svelte"
|
||||||
|
import SearchPeople from "src/views/SearchPeople.svelte"
|
||||||
|
import {relays} from 'src/agent/relays'
|
||||||
|
import {follows} from 'src/agent/user'
|
||||||
|
|
||||||
|
export let enforceRelays = true
|
||||||
|
export let enforcePeople = true
|
||||||
|
|
||||||
|
const {petnames} = follows
|
||||||
|
|
||||||
|
let modalIsOpen = true
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
modalIsOpen = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $relays.length === 0 && enforceRelays}
|
||||||
|
{#if modalIsOpen}
|
||||||
|
<Modal onEscape={closeModal}>
|
||||||
|
<Content>
|
||||||
|
<div class="flex flex-col items-center gap-4 my-8">
|
||||||
|
<div class="text-xl flex gap-2 items-center">
|
||||||
|
<i class="fa fa-triangle-exclamation fa-light" />
|
||||||
|
You aren't yet connected to any relays.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Search below to find one to join.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<RelaySearch />
|
||||||
|
</Content>
|
||||||
|
</Modal>
|
||||||
|
{:else}
|
||||||
|
<Content size="lg">
|
||||||
|
<div class="flex flex-col items-center gap-4 mt-12">
|
||||||
|
<div class="text-xl flex gap-2 items-center">
|
||||||
|
<i class="fa fa-triangle-exclamation fa-light" />
|
||||||
|
You aren't yet connected to any relays.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Click <Anchor href="/relays">here</Anchor> to find one to join.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
|
{/if}
|
||||||
|
{:else if $petnames.length === 0 && enforcePeople}
|
||||||
|
{#if modalIsOpen}
|
||||||
|
<Modal onEscape={closeModal}>
|
||||||
|
<Content>
|
||||||
|
<div class="flex flex-col items-center gap-4 my-8">
|
||||||
|
<div class="text-xl flex gap-2 items-center">
|
||||||
|
<i class="fa fa-triangle-exclamation fa-light" />
|
||||||
|
You aren't yet following anyone.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Search below to find some interesting people.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SearchPeople />
|
||||||
|
</Content>
|
||||||
|
</Modal>
|
||||||
|
{:else}
|
||||||
|
<Content size="lg">
|
||||||
|
<div class="flex flex-col items-center gap-4 mt-12">
|
||||||
|
<div class="text-xl flex gap-2 items-center">
|
||||||
|
<i class="fa fa-triangle-exclamation fa-light" />
|
||||||
|
You aren't yet following anyone.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Click <Anchor href="/search/people">here</Anchor> to find some
|
||||||
|
interesting people.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<slot />
|
||||||
|
{/if}
|
@ -26,7 +26,7 @@
|
|||||||
class="absolute inset-0 mt-20 sm:mt-28 modal-content"
|
class="absolute inset-0 mt-20 sm:mt-28 modal-content"
|
||||||
transition:fly={{y: 1000, opacity: 1}}
|
transition:fly={{y: 1000, opacity: 1}}
|
||||||
style={nested && `padding-top: 1rem`}>
|
style={nested && `padding-top: 1rem`}>
|
||||||
<div class="bg-dark border-t border-solid border-medium h-full w-full overflow-auto">
|
<div class="bg-dark border-t border-solid border-medium h-full w-full overflow-auto pb-10">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -145,7 +145,7 @@
|
|||||||
text: 'View',
|
text: 'View',
|
||||||
href: "/" + nip19.neventEncode({
|
href: "/" + nip19.neventEncode({
|
||||||
id: event.id,
|
id: event.id,
|
||||||
relays: pluck('url', relays.slice(0, 5)),
|
relays: pluck('url', relays.slice(0, 3)),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
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} from "src/app/ui"
|
import {modal} from "src/app/ui"
|
||||||
import {mergeParents} from "src/app"
|
import {mergeParents} from "src/app"
|
||||||
@ -76,10 +75,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
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(() => {
|
||||||
@ -89,6 +84,8 @@
|
|||||||
|
|
||||||
const {limit, until} = cursor
|
const {limit, until} = cursor
|
||||||
|
|
||||||
|
console.log('here')
|
||||||
|
|
||||||
return network.listenUntilEose(relays, {...filter, until, limit}, onChunk)
|
return network.listenUntilEose(relays, {...filter, until, limit}, onChunk)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
33
src/partials/RelaySearch.svelte
Normal file
33
src/partials/RelaySearch.svelte
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script>
|
||||||
|
import {pluck} from 'ramda'
|
||||||
|
import {fuzzy} from "src/util/misc"
|
||||||
|
import {isRelay} from "src/util/nostr"
|
||||||
|
import Input from "src/partials/Input.svelte"
|
||||||
|
import RelayCard from "src/partials/RelayCard.svelte"
|
||||||
|
import database from 'src/agent/database'
|
||||||
|
import {relays} from "src/agent/relays"
|
||||||
|
|
||||||
|
let q = ""
|
||||||
|
let search
|
||||||
|
let knownRelays = database.watch('relays', t => t.all())
|
||||||
|
|
||||||
|
$: {
|
||||||
|
const joined = new Set(pluck('url', $relays))
|
||||||
|
|
||||||
|
search = fuzzy(
|
||||||
|
$knownRelays.filter(r => isRelay(r.url) && !joined.has(r.url)),
|
||||||
|
{keys: ["name", "description", "url"]}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
|
||||||
|
<i slot="before" class="fa-solid fa-search" />
|
||||||
|
</Input>
|
||||||
|
{#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>
|
@ -4,33 +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 {relays} from 'src/agent/relays'
|
import {user} from 'src/agent/user'
|
||||||
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>
|
||||||
{#if !$user}
|
{#if !$user}
|
||||||
<Content size="lg" class="text-center">
|
<Content size="lg" class="text-center">
|
||||||
<p>
|
<p class="text-xl">Don't have an account?</p>
|
||||||
Don't have an account? Click <Anchor href="/login">here</Anchor> to join the nostr network.
|
<p>Click <Anchor href="/login">here</Anchor> to join the nostr network.</p>
|
||||||
</p>
|
|
||||||
</Content>
|
</Content>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Tabs tabs={['network', 'popular']} {activeTab} {setActiveTab} />
|
<Tabs tabs={['network', 'popular']} {activeTab} {setActiveTab} />
|
||||||
{#if activeTab === 'network'}
|
{#if activeTab === 'network'}
|
||||||
@ -40,11 +29,4 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
<NewNoteButton />
|
<NewNoteButton />
|
||||||
|
|
||||||
{#if $relays.length === 0}
|
|
||||||
<Modal>
|
|
||||||
<RelayList />
|
|
||||||
</Modal>
|
|
||||||
{/if}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {last, find} from 'ramda'
|
import {last} from 'ramda'
|
||||||
import {onMount} from 'svelte'
|
import {onMount} from 'svelte'
|
||||||
import {tweened} from 'svelte/motion'
|
import {tweened} from 'svelte/motion'
|
||||||
import {nip19} from 'nostr-tools'
|
import {nip19} from 'nostr-tools'
|
||||||
@ -17,7 +17,7 @@
|
|||||||
import Likes from "src/views/person/Likes.svelte"
|
import Likes from "src/views/person/Likes.svelte"
|
||||||
import Relays from "src/views/person/Relays.svelte"
|
import Relays from "src/views/person/Relays.svelte"
|
||||||
import {user, follows} from "src/agent/user"
|
import {user, follows} from "src/agent/user"
|
||||||
import {getPubkeyWriteRelays} from "src/agent/relays"
|
import {getUserReadRelays, getPubkeyWriteRelays} from "src/agent/relays"
|
||||||
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"
|
||||||
@ -28,6 +28,7 @@
|
|||||||
export let relays = []
|
export let relays = []
|
||||||
|
|
||||||
const interpolate = (a, b) => t => a + Math.round((b - a) * t)
|
const interpolate = (a, b) => t => a + Math.round((b - a) * t)
|
||||||
|
const {pubkeys: userFollows} = follows
|
||||||
|
|
||||||
let pubkey = nip19.decode(npub).data as string
|
let pubkey = nip19.decode(npub).data as string
|
||||||
let following = false
|
let following = false
|
||||||
@ -36,7 +37,7 @@
|
|||||||
let person = database.getPersonWithFallback(pubkey)
|
let person = database.getPersonWithFallback(pubkey)
|
||||||
let loading = true
|
let loading = true
|
||||||
|
|
||||||
$: following = find(t => t[1] === pubkey, $user?.petnames || [])
|
$: following = $userFollows.includes(pubkey)
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
log('Person', npub, person)
|
log('Person', npub, person)
|
||||||
@ -86,7 +87,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const follow = async () => {
|
const follow = async () => {
|
||||||
follows.addFollow(pubkey, relays[0].url, person.name)
|
const [{url}] = relays.concat(getUserReadRelays())
|
||||||
|
|
||||||
|
follows.addFollow(pubkey, url, person.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
const unfollow = async () => {
|
const unfollow = async () => {
|
||||||
@ -138,15 +141,15 @@
|
|||||||
<Anchor type="button-circle" href={`/messages/${npub}`}>
|
<Anchor type="button-circle" href={`/messages/${npub}`}>
|
||||||
<i class="fa fa-envelope" />
|
<i class="fa fa-envelope" />
|
||||||
</Anchor>
|
</Anchor>
|
||||||
{#if following}
|
{/if}
|
||||||
<Anchor type="button-circle" on:click={unfollow}>
|
{#if following}
|
||||||
<i class="fa fa-user-minus" />
|
<Anchor type="button-circle" on:click={unfollow}>
|
||||||
</Anchor>
|
<i class="fa fa-user-minus" />
|
||||||
{:else if $user}
|
</Anchor>
|
||||||
<Anchor type="button-circle" on:click={follow}>
|
{:else}
|
||||||
<i class="fa fa-user-plus" />
|
<Anchor type="button-circle" on:click={follow}>
|
||||||
</Anchor>
|
<i class="fa fa-user-plus" />
|
||||||
{/if}
|
</Anchor>
|
||||||
{/if}
|
{/if}
|
||||||
<Anchor type="button-circle" on:click={share}>
|
<Anchor type="button-circle" on:click={share}>
|
||||||
<i class="fa fa-share-nodes" />
|
<i class="fa fa-share-nodes" />
|
||||||
|
@ -1,28 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import {pluck} from 'ramda'
|
|
||||||
import {fly} from 'svelte/transition'
|
import {fly} from 'svelte/transition'
|
||||||
import {fuzzy} from "src/util/misc"
|
|
||||||
import {isRelay} from "src/util/nostr"
|
|
||||||
import Input from "src/partials/Input.svelte"
|
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
|
import RelaySearch from "src/partials/RelaySearch.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 {modal} from "src/app/ui"
|
import {modal} from "src/app/ui"
|
||||||
|
|
||||||
let q = ""
|
|
||||||
let search
|
|
||||||
let knownRelays = database.watch('relays', t => t.all())
|
|
||||||
|
|
||||||
$: {
|
|
||||||
const joined = new Set(pluck('url', $relays))
|
|
||||||
|
|
||||||
search = fuzzy(
|
|
||||||
$knownRelays.filter(r => isRelay(r.url) && !joined.has(r.url)),
|
|
||||||
{keys: ["name", "description", "url"]}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div in:fly={{y: 20}}>
|
<div in:fly={{y: 20}}>
|
||||||
@ -32,7 +15,7 @@
|
|||||||
<i class="fa fa-server fa-lg" />
|
<i class="fa fa-server fa-lg" />
|
||||||
<h2 class="staatliches text-2xl">Your relays</h2>
|
<h2 class="staatliches text-2xl">Your relays</h2>
|
||||||
</div>
|
</div>
|
||||||
<Anchor type="button" on:click={() => modal.set({type: 'relay/add', url: q})}>
|
<Anchor type="button" on:click={() => modal.set({type: 'relay/add'})}>
|
||||||
<i class="fa-solid fa-plus" /> Add Relay
|
<i class="fa-solid fa-plus" /> Add Relay
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</div>
|
</div>
|
||||||
@ -52,7 +35,6 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-6" in:fly={{y: 20, delay: 1000}}>
|
<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="pt-2 mb-2 border-b border-solid border-medium" />
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex gap-2 items-center">
|
||||||
<i class="fa fa-earth-asia fa-lg" />
|
<i class="fa fa-earth-asia fa-lg" />
|
||||||
@ -62,17 +44,7 @@
|
|||||||
Coracle automatically discovers relays as you browse the network. Adding more relays
|
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.
|
will generally make things quicker to load, at the expense of higher data usage.
|
||||||
</p>
|
</p>
|
||||||
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
|
<RelaySearch />
|
||||||
<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>
|
</div>
|
||||||
</Content>
|
</Content>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,11 +2,8 @@
|
|||||||
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
|
||||||
|
|
||||||
@ -21,9 +18,3 @@
|
|||||||
<Scan />
|
<Scan />
|
||||||
{/if}
|
{/if}
|
||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
{#if $relays.length === 0}
|
|
||||||
<Modal>
|
|
||||||
<RelayList />
|
|
||||||
</Modal>
|
|
||||||
{/if}
|
|
||||||
|
@ -96,6 +96,8 @@ export const poll = (t, cb) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const createScroller = (loadMore, {reverse = false} = {}) => {
|
export const createScroller = (loadMore, {reverse = false} = {}) => {
|
||||||
|
const THRESHOLD = 1200
|
||||||
|
|
||||||
// NOTE TO FUTURE SELF
|
// NOTE TO FUTURE SELF
|
||||||
// If the scroller is saturating request channels on a slow relay, the
|
// If the scroller is saturating request channels on a slow relay, the
|
||||||
// loadMore function is not properly awaiting all the work necessary.
|
// loadMore function is not properly awaiting all the work necessary.
|
||||||
@ -106,8 +108,8 @@ export const createScroller = (loadMore, {reverse = false} = {}) => {
|
|||||||
const {scrollY, innerHeight} = window
|
const {scrollY, innerHeight} = window
|
||||||
const {scrollHeight} = document.body
|
const {scrollHeight} = document.body
|
||||||
const shouldLoad = reverse
|
const shouldLoad = reverse
|
||||||
? scrollY < 800
|
? scrollY < THRESHOLD
|
||||||
: scrollY + innerHeight + 800 > scrollHeight
|
: scrollY + innerHeight + THRESHOLD > scrollHeight
|
||||||
|
|
||||||
// Only trigger loading the first time we reach the threshold
|
// Only trigger loading the first time we reach the threshold
|
||||||
if (shouldLoad) {
|
if (shouldLoad) {
|
||||||
|
@ -81,4 +81,6 @@ export const isAlert = (e, pubkey) => {
|
|||||||
|
|
||||||
export const isRelay = url => typeof url === 'string' && url.match(/^wss?:\/\/.+/)
|
export const isRelay = url => typeof url === 'string' && url.match(/^wss?:\/\/.+/)
|
||||||
|
|
||||||
|
export const normalizeRelayUrl = url => url.replace(/\/+$/, '')
|
||||||
|
|
||||||
export const roomAttrs = ['name', 'about', 'picture']
|
export const roomAttrs = ['name', 'about', 'picture']
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
text: 'View',
|
text: 'View',
|
||||||
href: "/" + nip19.neventEncode({
|
href: "/" + nip19.neventEncode({
|
||||||
id: event.id,
|
id: event.id,
|
||||||
relays: pluck('url', relays.slice(0, 5)),
|
relays: pluck('url', relays.slice(0, 3)),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import {uniq} from 'ramda'
|
import {shuffle} from 'src/util/misc'
|
||||||
import Notes from "src/partials/Notes.svelte"
|
import Notes from "src/partials/Notes.svelte"
|
||||||
import {user} from 'src/agent/user'
|
import {getUserNetwork} from 'src/agent/social'
|
||||||
import {getFollows, getNetwork} from 'src/agent/social'
|
|
||||||
import {getAllPubkeyWriteRelays} from 'src/agent/relays'
|
import {getAllPubkeyWriteRelays} from 'src/agent/relays'
|
||||||
|
|
||||||
// Get first- and second-order follows. shuffle and slice network so we're not
|
// Get first- and second-order follows. shuffle and slice network so we're not
|
||||||
// sending too many pubkeys. This will also result in some variety.
|
// sending too many pubkeys. This will also result in some variety.
|
||||||
const follows = getFollows($user?.pubkey)
|
const authors = shuffle(getUserNetwork()).slice(0, 100)
|
||||||
const network = getNetwork($user?.pubkey)
|
const relays = getAllPubkeyWriteRelays(authors).slice(0, 3)
|
||||||
const authors = uniq(follows.concat(network)).slice(0, 100)
|
|
||||||
const relays = getAllPubkeyWriteRelays(authors)
|
|
||||||
const filter = {kinds: [1, 7], authors}
|
const filter = {kinds: [1, 7], authors}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user