mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-19 11:43:35 +00:00
Wrap relays in user, improve first run experience
This commit is contained in:
parent
0b35ccc64d
commit
545f13e0b5
@ -7,6 +7,9 @@
|
||||
- [ ] Initial user load doesn't have any relays, cache user or wait for people db to be loaded
|
||||
- nip07.getRelays, nip05, relay.nostr.band
|
||||
- [ ] Fix bugs on bugsnag
|
||||
- [ ] Fix profile merging, put kind0 on its own property so we're not messing other people's profile data up.
|
||||
- [ ] Test publishing events with zero relays
|
||||
- [ ] Try lumping tables into a single key each to reduce load/save contention and time
|
||||
|
||||
# Snacks
|
||||
|
||||
@ -71,3 +74,5 @@
|
||||
- [ ] Ability to leave/mute DM conversation
|
||||
- [ ] Add petnames for channels
|
||||
- [ ] Add notifications for chat messages
|
||||
- [ ] Compress events
|
||||
- https://github.com/nostr-protocol/nips/issues/265#issuecomment-1434250263
|
||||
|
@ -19,9 +19,8 @@
|
||||
import network from 'src/agent/network'
|
||||
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 {user} from 'src/agent/user'
|
||||
import user from 'src/agent/user'
|
||||
import {loadAppData} from "src/app"
|
||||
import alerts from "src/app/alerts"
|
||||
import messages from "src/app/messages"
|
||||
@ -111,8 +110,8 @@
|
||||
})
|
||||
|
||||
database.onReady(() => {
|
||||
if ($user) {
|
||||
loadAppData($user.pubkey)
|
||||
if (user.getProfile()) {
|
||||
loadAppData(user.getPubkey())
|
||||
}
|
||||
|
||||
const interval = setInterval(
|
||||
|
@ -206,8 +206,8 @@ class Table {
|
||||
remove(k) {
|
||||
return this.bulkRemove([k])
|
||||
}
|
||||
clear() {
|
||||
return callLocalforage(this.name, 'clear')
|
||||
drop() {
|
||||
return callLocalforage(this.name, 'dropInstance')
|
||||
}
|
||||
dump() {
|
||||
return callLocalforage(this.name, 'dump')
|
||||
@ -316,7 +316,7 @@ const watch = (names, f) => {
|
||||
|
||||
const getPersonWithFallback = pubkey => people.get(pubkey) || {pubkey}
|
||||
|
||||
const clearAll = () => Promise.all(Object.values(registry).map(t => t.clear()))
|
||||
const dropAll = () => Promise.all(Object.values(registry).map(t => t.drop()))
|
||||
|
||||
const ready = derived(pluck('ready', Object.values(registry)), all(identity))
|
||||
|
||||
@ -330,6 +330,6 @@ const onReady = cb => {
|
||||
}
|
||||
|
||||
export default {
|
||||
watch, getPersonWithFallback, clearAll, people, rooms, messages,
|
||||
watch, getPersonWithFallback, dropAll, people, rooms, messages,
|
||||
alerts, relays, routes, ready, onReady,
|
||||
}
|
||||
|
@ -1,14 +1 @@
|
||||
/**
|
||||
* The dependency tree gets a little complex here:
|
||||
*
|
||||
* cmd
|
||||
* -> network
|
||||
* -> user, pool
|
||||
* -> keys
|
||||
* -> sync
|
||||
* -> database
|
||||
*
|
||||
* In other words, command/network depend on utility functions and the network to
|
||||
* do their job. The database sits at the bottom since it's shared between user
|
||||
* which query the database, and network which both queries and updates it.
|
||||
*/
|
||||
|
||||
|
@ -4,8 +4,8 @@ import {get} from 'svelte/store'
|
||||
import {error} from 'src/util/logger'
|
||||
import {synced} from 'src/util/misc'
|
||||
|
||||
const pubkey = synced('agent/user/pubkey')
|
||||
const privkey = synced('agent/user/privkey')
|
||||
const pubkey = synced('agent/keys/pubkey')
|
||||
const privkey = synced('agent/keys/privkey')
|
||||
const getExtension = () => (window as {nostr?: any}).nostr
|
||||
const canSign = () => Boolean(getExtension() || get(privkey))
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {uniq, uniqBy, prop, map, propEq, indexBy, pluck} from 'ramda'
|
||||
import {personKinds, findReplyId} from 'src/util/nostr'
|
||||
import {chunk} from 'hurdak/lib/hurdak'
|
||||
import {batch, shuffle, timedelta, now} from 'src/util/misc'
|
||||
import {batch, timedelta, now} from 'src/util/misc'
|
||||
import {
|
||||
getRelaysForEventParent, getAllPubkeyWriteRelays, aggregateScores,
|
||||
getUserReadRelays, getRelaysForEventChildren,
|
||||
@ -82,7 +82,7 @@ const listenUntilEose = (relays, filter, onEvents, {shouldProcess = true}: any =
|
||||
}) as Promise<void>
|
||||
}
|
||||
|
||||
const loadPeople = (pubkeys, {kinds = personKinds, force = false, ...opts} = {}) => {
|
||||
const loadPeople = async (pubkeys, {kinds = personKinds, force = false, ...opts} = {}) => {
|
||||
pubkeys = uniq(pubkeys)
|
||||
|
||||
// If we're not reloading, only get pubkeys we don't already know about
|
||||
@ -90,11 +90,14 @@ const loadPeople = (pubkeys, {kinds = personKinds, force = false, ...opts} = {})
|
||||
pubkeys = getStalePubkeys(pubkeys)
|
||||
}
|
||||
|
||||
return load(
|
||||
shuffle(getUserReadRelays().concat(getAllPubkeyWriteRelays(pubkeys))).slice(0, 3),
|
||||
{kinds, authors: pubkeys},
|
||||
opts
|
||||
)
|
||||
// Use the best relays we have, but fall back to user relays
|
||||
const relays = getAllPubkeyWriteRelays(pubkeys)
|
||||
.concat(getUserReadRelays())
|
||||
.slice(0, 3)
|
||||
|
||||
if (pubkeys.length > 0) {
|
||||
await load(relays, {kinds, authors: pubkeys}, opts)
|
||||
}
|
||||
}
|
||||
|
||||
const loadParents = notes => {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import type {Relay} from 'src/util/types'
|
||||
import {get} from 'svelte/store'
|
||||
import {pick, map, assoc, sortBy, uniqBy, prop} from 'ramda'
|
||||
import {first} from 'hurdak/lib/hurdak'
|
||||
import {Tags, findReplyId} from 'src/util/nostr'
|
||||
import {synced} from 'src/util/misc'
|
||||
import database from 'src/agent/database'
|
||||
import user from 'src/agent/user'
|
||||
import keys from 'src/agent/keys'
|
||||
|
||||
// From Mike Dilger:
|
||||
@ -19,8 +18,6 @@ import keys from 'src/agent/keys'
|
||||
// doesn't need to see.
|
||||
// 5) Advertise relays — write and read back your own relay list
|
||||
|
||||
export const relays = synced('agent/relays', [])
|
||||
|
||||
// Pubkey relays
|
||||
|
||||
export const getPubkeyRelays = (pubkey, mode = null) => {
|
||||
@ -46,9 +43,14 @@ export const getAllPubkeyWriteRelays = pubkeys =>
|
||||
|
||||
// Current user
|
||||
|
||||
export const getUserRelays = (): Array<Relay> => get(relays).map(assoc('score', 1))
|
||||
export const getUserReadRelays = () => getUserRelays().filter(prop('read'))
|
||||
export const getUserWriteRelays = () => getUserRelays().filter(prop('write'))
|
||||
export const getUserRelays = () =>
|
||||
user.getRelays().map(assoc('score', 1))
|
||||
|
||||
export const getUserReadRelays = () =>
|
||||
getUserRelays().filter(prop('read')).map(pick(['url', 'score']))
|
||||
|
||||
export const getUserWriteRelays = () =>
|
||||
getUserRelays().filter(prop('write')).map(pick(['url', 'score']))
|
||||
|
||||
// Event-related special cases
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
import {uniq} from 'ramda'
|
||||
import {get} from 'svelte/store'
|
||||
import {Tags} from 'src/util/nostr'
|
||||
import database from 'src/agent/database'
|
||||
import {follows} from 'src/agent/user'
|
||||
import user from 'src/agent/user'
|
||||
|
||||
export const getFollows = pubkey =>
|
||||
Tags.wrap(database.getPersonWithFallback(pubkey).petnames).type("p").values().all()
|
||||
@ -13,7 +12,8 @@ export const getNetwork = pubkey => {
|
||||
return uniq(follows.concat(follows.flatMap(getFollows)))
|
||||
}
|
||||
|
||||
export const getUserFollows = (): Array<string> => get(follows.pubkeys)
|
||||
export const getUserFollows = (): Array<string> =>
|
||||
Tags.wrap(user.getPetnames()).values().all()
|
||||
|
||||
export const getUserNetwork = () => {
|
||||
const follows = getUserFollows()
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {pick, identity, isEmpty} from 'ramda'
|
||||
import {uniq, pick, identity, isEmpty} from 'ramda'
|
||||
import {nip05} from 'nostr-tools'
|
||||
import {noop, createMap, ensurePlural, switcherFn} from 'hurdak/lib/hurdak'
|
||||
import {warn} from 'src/util/logger'
|
||||
import {warn, log} from 'src/util/logger'
|
||||
import {now, timedelta, shuffle, hash} from 'src/util/misc'
|
||||
import {Tags, roomAttrs, isRelay, normalizeRelayUrl} from 'src/util/nostr'
|
||||
import database from 'src/agent/database'
|
||||
@ -46,8 +46,66 @@ const processProfileEvents = async events => {
|
||||
}
|
||||
})
|
||||
},
|
||||
3: () => ({petnames: e.tags}),
|
||||
2: () => {
|
||||
if (e.created_at > (person.relays_updated_at || 0)) {
|
||||
const {relays = []} = database.getPersonWithFallback(e.pubkey)
|
||||
|
||||
return {
|
||||
relays: relays.concat({url: e.content}),
|
||||
relays_updated_at: e.created_at,
|
||||
}
|
||||
}
|
||||
},
|
||||
3: () => {
|
||||
const data = {petnames: e.tags}
|
||||
|
||||
if (e.created_at > (person.relays_updated_at || 0)) {
|
||||
tryJson(() => {
|
||||
Object.assign(data, {
|
||||
relays_updated_at: e.created_at,
|
||||
relays: Object.entries(JSON.parse(e.content))
|
||||
.map(([url, conditions]) => {
|
||||
const {write, read} = conditions as Record<string, boolean|string>
|
||||
|
||||
return {
|
||||
url,
|
||||
write: [false, '!'].includes(write) ? '!' : '',
|
||||
read: [false, '!'].includes(read) ? '!' : '',
|
||||
}
|
||||
})
|
||||
.filter(r => isRelay(r.url)),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return data
|
||||
},
|
||||
12165: () => ({muffle: e.tags}),
|
||||
// DEPRECATED
|
||||
10001: () => {
|
||||
if (e.created_at > (person.relays_updated_at || 0)) {
|
||||
return {
|
||||
relays_updated_at: e.created_at,
|
||||
relays: e.tags.map(([url, read, write]) => ({url, read, write})),
|
||||
}
|
||||
}
|
||||
},
|
||||
10002: () => {
|
||||
if (e.created_at > (person.relays_updated_at || 0)) {
|
||||
return {
|
||||
relays_updated_at: e.created_at,
|
||||
relays: e.tags.map(([_, url, mode]) => {
|
||||
const read = (mode || 'read') === 'read'
|
||||
const write = (mode || 'write') === 'write'
|
||||
|
||||
return {url, read, write}
|
||||
}),
|
||||
}
|
||||
}
|
||||
},
|
||||
default: () => {
|
||||
log(`Received unsupported event type ${e.kind}`)
|
||||
},
|
||||
}),
|
||||
updated_at: now(),
|
||||
}
|
||||
@ -129,7 +187,8 @@ const calculateRoute = (pubkey, rawUrl, type, mode, created_at) => {
|
||||
const url = normalizeRelayUrl(rawUrl)
|
||||
const id = hash([pubkey, url, mode].join('')).toString()
|
||||
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 defaults = {id, pubkey, url, mode, score: 0, count: 0, types: []}
|
||||
const route = database.routes.get(id) || defaults
|
||||
|
||||
const newTotalScore = route.score * route.count + score
|
||||
const newCount = route.count + 1
|
||||
@ -139,6 +198,7 @@ const calculateRoute = (pubkey, rawUrl, type, mode, created_at) => {
|
||||
...route,
|
||||
count: newCount,
|
||||
score: newTotalScore / newCount,
|
||||
types: uniq(route.types.concat(type)),
|
||||
last_seen: Math.max(created_at, route.last_seen || 0),
|
||||
}
|
||||
}
|
||||
@ -193,7 +253,7 @@ const processRoutes = async events => {
|
||||
},
|
||||
10002: () => {
|
||||
e.tags
|
||||
.forEach(([url, read, mode]) => {
|
||||
.forEach(([_, url, mode]) => {
|
||||
if (mode) {
|
||||
calculateRoute(e.pubkey, url, 'kind:10002', mode, e.created_at)
|
||||
} else {
|
||||
|
@ -1,56 +1,108 @@
|
||||
import type {Person} from 'src/util/types'
|
||||
import type {Readable} from 'svelte/store'
|
||||
import {pipe, concat, reject, nth, map} from 'ramda'
|
||||
import {pipe, assoc, whereEq, when, 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 {getUserWriteRelays} from 'src/agent/relays'
|
||||
import keys from 'src/agent/keys'
|
||||
import cmd from 'src/agent/cmd'
|
||||
|
||||
export const user = derived(
|
||||
// Create a special wrapper to manage profile data, follows, and relays in the same
|
||||
// way whether the user is logged in or not. This involves creating a store that we
|
||||
// allow an anonymous user to write to, then once the user logs in we use that until
|
||||
// we have actual event data for them, which we then prefer. For extra fun, we also
|
||||
// sync this stuff to regular private variables so we don't have to constantly call
|
||||
// `get` on our stores.
|
||||
|
||||
let profileCopy = null
|
||||
let petnamesCopy = []
|
||||
let relaysCopy = []
|
||||
|
||||
const anonPetnames = synced('agent/user/anonPetnames', [])
|
||||
const anonRelays = synced('agent/user/anonRelays', [])
|
||||
|
||||
const profile = derived(
|
||||
[keys.pubkey, database.people as Readable<any>],
|
||||
([pubkey, $people]) => {
|
||||
if (!pubkey) {
|
||||
return null
|
||||
}
|
||||
|
||||
return ($people[pubkey] || {pubkey})
|
||||
return profileCopy = ($people[pubkey] || {pubkey})
|
||||
}
|
||||
) as Readable<Person>
|
||||
|
||||
// Create a special wrapper to manage follows the same way whether
|
||||
// the user is logged in or not
|
||||
|
||||
export const follows = (() => {
|
||||
const anonPetnames = synced('agent/user/anonPetnames', [])
|
||||
|
||||
const petnames = derived(
|
||||
[user, anonPetnames],
|
||||
([$user, $anonPetnames]) =>
|
||||
$user?.petnames || $anonPetnames
|
||||
)
|
||||
|
||||
return {
|
||||
petnames,
|
||||
pubkeys: derived(petnames, map(nth(1))) as Readable<Array<string>>,
|
||||
update(f) {
|
||||
const $petnames = f(get(petnames))
|
||||
|
||||
anonPetnames.set($petnames)
|
||||
|
||||
if (get(user)) {
|
||||
cmd.setPetnames(getUserWriteRelays(), $petnames)
|
||||
}
|
||||
},
|
||||
addFollow(pubkey, url, name) {
|
||||
const tag = ["p", pubkey, url, name || ""]
|
||||
|
||||
this.update(pipe(reject(t => t[1] === pubkey), concat([tag])))
|
||||
},
|
||||
removeFollow(pubkey) {
|
||||
this.update(reject(t => t[1] === pubkey))
|
||||
},
|
||||
const petnames = derived(
|
||||
[profile, anonPetnames],
|
||||
([$profile, $anonPetnames]) => {
|
||||
return petnamesCopy = $profile?.petnames || $anonPetnames
|
||||
}
|
||||
})()
|
||||
)
|
||||
|
||||
const relays = derived(
|
||||
[profile, anonRelays],
|
||||
([$profile, $anonRelays]) => {
|
||||
return relaysCopy = $profile?.relays || $anonRelays
|
||||
}
|
||||
)
|
||||
|
||||
// Prime our copies
|
||||
get(profile)
|
||||
get(petnames)
|
||||
get(relays)
|
||||
|
||||
const user = {
|
||||
// Profile
|
||||
|
||||
profile,
|
||||
getProfile: () => profileCopy,
|
||||
getPubkey: () => profileCopy?.pubkey,
|
||||
|
||||
// Petnames
|
||||
|
||||
petnames,
|
||||
getPetnames: () => petnamesCopy,
|
||||
petnamePubkeys: derived(petnames, map(nth(1))) as Readable<Array<string>>,
|
||||
updatePetnames(f) {
|
||||
const $petnames = f(petnamesCopy)
|
||||
|
||||
anonPetnames.set($petnames)
|
||||
|
||||
if (profileCopy) {
|
||||
cmd.setPetnames(relaysCopy, $petnames)
|
||||
}
|
||||
},
|
||||
addPetname(pubkey, url, name) {
|
||||
const tag = ["p", pubkey, url, name || ""]
|
||||
|
||||
this.updatePetnames(pipe(reject(t => t[1] === pubkey), concat([tag])))
|
||||
},
|
||||
removePetname(pubkey) {
|
||||
this.updatePetnames(reject(t => t[1] === pubkey))
|
||||
},
|
||||
|
||||
// Relays
|
||||
|
||||
relays,
|
||||
getRelays: () => relaysCopy,
|
||||
updateRelays(f) {
|
||||
const $relays = f(relaysCopy)
|
||||
|
||||
anonRelays.set($relays)
|
||||
|
||||
if (profileCopy) {
|
||||
cmd.setRelays($relays, $relays)
|
||||
}
|
||||
},
|
||||
async addRelay(url) {
|
||||
this.updateRelays($relays => $relays.concat({url, write: false, read: true}))
|
||||
},
|
||||
async removeRelay(url) {
|
||||
this.updateRelays(reject(whereEq({url})))
|
||||
},
|
||||
async setRelayWriteCondition(url, write) {
|
||||
this.updateRelays(map(when(whereEq({url}), assoc('write', write))))
|
||||
},
|
||||
}
|
||||
|
||||
export default user
|
||||
|
@ -1,17 +1,14 @@
|
||||
import type {Person, DisplayEvent} from 'src/util/types'
|
||||
import {assoc, omit, sortBy, whereEq, identity, when, reject} from 'ramda'
|
||||
import type {DisplayEvent} from 'src/util/types'
|
||||
import {omit, sortBy, identity} from 'ramda'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import {createMap, ellipsize} from 'hurdak/lib/hurdak'
|
||||
import {get} from 'svelte/store'
|
||||
import {renderContent} from 'src/util/html'
|
||||
import {Tags, displayPerson, findReplyId} from 'src/util/nostr'
|
||||
import {user} from 'src/agent/user'
|
||||
import {getNetwork} from 'src/agent/social'
|
||||
import {relays, getUserReadRelays} from 'src/agent/relays'
|
||||
import {getUserReadRelays} from 'src/agent/relays'
|
||||
import database from 'src/agent/database'
|
||||
import network from 'src/agent/network'
|
||||
import keys from 'src/agent/keys'
|
||||
import cmd from 'src/agent/cmd'
|
||||
import alerts from 'src/app/alerts'
|
||||
import messages from 'src/app/messages'
|
||||
import {routes, modal} from 'src/app/ui'
|
||||
@ -52,54 +49,6 @@ export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: strin
|
||||
}
|
||||
}
|
||||
|
||||
export const addRelay = async url => {
|
||||
const $user = get(user) as Person
|
||||
|
||||
relays.update($relays => {
|
||||
$relays.push({url, write: false, read: true})
|
||||
|
||||
if ($user) {
|
||||
(async () => {
|
||||
// Publish to the new set of relays
|
||||
await cmd.setRelays($relays, $relays)
|
||||
|
||||
// Reload alerts, messages, etc
|
||||
await loadAppData($user.pubkey)
|
||||
})()
|
||||
}
|
||||
|
||||
return $relays
|
||||
})
|
||||
}
|
||||
|
||||
export const removeRelay = async url => {
|
||||
const $user = get(user) as Person
|
||||
|
||||
relays.update($relays => {
|
||||
$relays = reject(whereEq({url}), $relays)
|
||||
|
||||
if ($user && $relays.length > 0) {
|
||||
cmd.setRelays($relays, $relays)
|
||||
}
|
||||
|
||||
return $relays
|
||||
})
|
||||
}
|
||||
|
||||
export const setRelayWriteCondition = async (url, write) => {
|
||||
const $user = get(user) as Person
|
||||
|
||||
relays.update($relays => {
|
||||
$relays = $relays.map(when(whereEq({url}), assoc('write', write)))
|
||||
|
||||
if ($user && $relays.length > 0) {
|
||||
cmd.setRelays($relays, $relays)
|
||||
}
|
||||
|
||||
return $relays
|
||||
})
|
||||
}
|
||||
|
||||
export const renderNote = (note, {showEntire = false}) => {
|
||||
const shouldEllipsize = note.content.length > 500 && !showEntire
|
||||
const peopleByPubkey = createMap(
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {pluck, find, reject} from 'ramda'
|
||||
import {get, derived} from 'svelte/store'
|
||||
import {derived} from 'svelte/store'
|
||||
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 database from 'src/agent/database'
|
||||
import network from 'src/agent/network'
|
||||
@ -34,8 +34,6 @@ const listen = async pubkey => {
|
||||
[{kinds: [4], authors: [pubkey], since},
|
||||
{kinds: [4], '#p': [pubkey], since}],
|
||||
async events => {
|
||||
const $user = get(user)
|
||||
|
||||
// Reload annotated messages, don't alert about messages to self
|
||||
const messages = reject(e => e.pubkey === e.recipient, await database.messages.all())
|
||||
|
||||
@ -44,7 +42,7 @@ const listen = async pubkey => {
|
||||
|
||||
mostRecentByPubkey.update(o => {
|
||||
for (const {pubkey, created_at} of messages) {
|
||||
if (pubkey !== $user.pubkey) {
|
||||
if (pubkey !== user.getPubkey()) {
|
||||
o[pubkey] = Math.max(created_at, o[pubkey] || 0)
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
import Badge from 'src/partials/Badge.svelte'
|
||||
import Anchor from 'src/partials/Anchor.svelte'
|
||||
import Spinner from 'src/partials/Spinner.svelte'
|
||||
import {user} from 'src/agent/user'
|
||||
import user from 'src/agent/user'
|
||||
import database from 'src/agent/database'
|
||||
import {renderNote} from 'src/app'
|
||||
|
||||
@ -29,6 +29,8 @@
|
||||
let showNewMessages = false
|
||||
let cursor = new Cursor()
|
||||
|
||||
const {profile} = user
|
||||
|
||||
$: {
|
||||
// Group messages so we're only showing the person once per chunk
|
||||
annotatedMessages = reverse(sortBy(prop('created_at'), uniqBy(prop('id'), messages)).reduce(
|
||||
@ -62,7 +64,7 @@
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if (!$user) {
|
||||
if (!$profile) {
|
||||
return navigate('/login')
|
||||
}
|
||||
|
||||
@ -134,14 +136,14 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class={cx("flex overflow-hidden text-ellipsis", {
|
||||
'ml-12 justify-end': type === 'dm' && m.person.pubkey === $user.pubkey,
|
||||
'mr-12': type === 'dm' && m.person.pubkey !== $user.pubkey,
|
||||
'ml-12 justify-end': type === 'dm' && m.person.pubkey === $profile.pubkey,
|
||||
'mr-12': type === 'dm' && m.person.pubkey !== $profile.pubkey,
|
||||
})}>
|
||||
<div class={cx({
|
||||
'ml-6': type === 'chat',
|
||||
'rounded-2xl py-2 px-4 flex max-w-xl': type === 'dm',
|
||||
'bg-light text-black rounded-br-none': type === 'dm' && m.person.pubkey === $user.pubkey,
|
||||
'bg-dark rounded-bl-none': type === 'dm' && m.person.pubkey !== $user.pubkey,
|
||||
'bg-light text-black rounded-br-none': type === 'dm' && m.person.pubkey === $profile.pubkey,
|
||||
'bg-dark rounded-bl-none': type === 'dm' && m.person.pubkey !== $profile.pubkey,
|
||||
})}>
|
||||
{@html renderNote(m, {showEntire: true})}
|
||||
</div>
|
||||
|
@ -2,82 +2,96 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Modal from "src/partials/Modal.svelte"
|
||||
import RelayCard from "src/partials/RelayCard.svelte"
|
||||
import PersonInfo from "src/partials/PersonInfo.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'
|
||||
import database from 'src/agent/database'
|
||||
import user from 'src/agent/user'
|
||||
|
||||
export let enforceRelays = true
|
||||
export let enforcePeople = true
|
||||
|
||||
const {petnames} = follows
|
||||
const {petnamePubkeys, relays} = user
|
||||
const needsRelays = () => $relays.length === 0 && enforceRelays
|
||||
const needsPeople = () => $petnamePubkeys.length === 0 && enforcePeople
|
||||
|
||||
let modalIsOpen = true
|
||||
let modal = needsRelays() ? 'relays' : (needsPeople() ? 'people' : null)
|
||||
|
||||
const closeModal = () => {
|
||||
modalIsOpen = false
|
||||
modal = null
|
||||
}
|
||||
</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">
|
||||
{#if modal === 'relays'}
|
||||
<Modal onEscape={closeModal}>
|
||||
<Content>
|
||||
{#if $relays.length > 0}
|
||||
<h1 class="text-2xl">Your Relays</h1>
|
||||
{/if}
|
||||
{#each $relays as relay (relay.url)}
|
||||
<RelayCard showControls {relay} />
|
||||
{:else}
|
||||
<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>
|
||||
Click <Anchor href="/relays">here</Anchor> to find one to join.
|
||||
Search below to find one to join.
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<RelaySearch />
|
||||
</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">
|
||||
</Modal>
|
||||
{:else if needsRelays()}
|
||||
<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>
|
||||
{:else if modal === 'people'}
|
||||
<Modal onEscape={closeModal}>
|
||||
<Content>
|
||||
{#if $petnamePubkeys.length > 0}
|
||||
<h1 class="text-2xl">Your Follows</h1>
|
||||
{/if}
|
||||
{#each $petnamePubkeys as pubkey (pubkey)}
|
||||
<PersonInfo person={database.people.get(pubkey)} />
|
||||
{:else}
|
||||
<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>
|
||||
Click <Anchor href="/search/people">here</Anchor> to find some
|
||||
interesting people.
|
||||
Search below to find some interesting people.
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<SearchPeople hideFollowing />
|
||||
</Content>
|
||||
{/if}
|
||||
</Modal>
|
||||
{:else if needsPeople()}
|
||||
<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>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
@ -29,5 +29,12 @@
|
||||
<div class="bg-dark border-t border-solid border-medium h-full w-full overflow-auto pb-10">
|
||||
<slot />
|
||||
</div>
|
||||
{#if onEscape}
|
||||
<div class="absolute top-0 flex w-full justify-end pr-2 -mt-8">
|
||||
<div class="w-10 h-10 flex justify-center items-center bg-accent rounded-full cursor-pointer border border-solid border-medium border-b-0">
|
||||
<i class="fa fa-times fa-lg cursor-pointer" on:click={onEscape} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@
|
||||
import {formatTimestamp, stringToColor} from 'src/util/misc'
|
||||
import Compose from "src/partials/Compose.svelte"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
import {user} from 'src/agent/user'
|
||||
import user from 'src/agent/user'
|
||||
import {getEventPublishRelays, getRelaysForEventParent} from 'src/agent/relays'
|
||||
import database from 'src/agent/database'
|
||||
import cmd from 'src/agent/cmd'
|
||||
@ -30,12 +30,13 @@
|
||||
export let shouldDisplay = always(true)
|
||||
|
||||
const getDefaultReplyMentions = () =>
|
||||
without([$user?.pubkey], uniq(Tags.from(note).type("p").values().all().concat(note.pubkey)))
|
||||
without([$profile?.pubkey], uniq(Tags.from(note).type("p").values().all().concat(note.pubkey)))
|
||||
|
||||
let reply = null
|
||||
let replyMentions = getDefaultReplyMentions()
|
||||
let replyContainer = null
|
||||
|
||||
const {profile} = user
|
||||
const links = $settings.showLinkPreviews ? extractUrls(note.content) || [] : []
|
||||
const showEntire = anchorId === note.id
|
||||
const interactive = !anchorId || !showEntire
|
||||
@ -53,8 +54,8 @@
|
||||
flags = note.reactions.filter(whereEq({content: '-'}))
|
||||
}
|
||||
|
||||
$: like = find(whereEq({pubkey: $user?.pubkey}), likes)
|
||||
$: flag = find(whereEq({pubkey: $user?.pubkey}), flags)
|
||||
$: like = find(whereEq({pubkey: $profile?.pubkey}), likes)
|
||||
$: flag = find(whereEq({pubkey: $profile?.pubkey}), flags)
|
||||
|
||||
$: $likesCount = likes.length
|
||||
$: $flagsCount = flags.length
|
||||
@ -85,7 +86,7 @@
|
||||
}
|
||||
|
||||
const react = async content => {
|
||||
if (!$user) {
|
||||
if (!$profile) {
|
||||
return navigate('/login')
|
||||
}
|
||||
|
||||
@ -105,16 +106,16 @@
|
||||
cmd.deleteEvent(getEventPublishRelays(note), [e.id])
|
||||
|
||||
if (e.content === '+') {
|
||||
likes = reject(propEq('pubkey', $user.pubkey), likes)
|
||||
likes = reject(propEq('pubkey', $profile.pubkey), likes)
|
||||
}
|
||||
|
||||
if (e.content === '-') {
|
||||
flags = reject(propEq('pubkey', $user.pubkey), flags)
|
||||
flags = reject(propEq('pubkey', $profile.pubkey), flags)
|
||||
}
|
||||
}
|
||||
|
||||
const startReply = () => {
|
||||
if ($user) {
|
||||
if ($profile) {
|
||||
reply = reply || true
|
||||
} else {
|
||||
navigate('/login')
|
||||
|
@ -8,8 +8,9 @@
|
||||
import Spinner from 'src/partials/Spinner.svelte'
|
||||
import Content from 'src/partials/Content.svelte'
|
||||
import Note from "src/partials/Note.svelte"
|
||||
import {user} from 'src/agent/user'
|
||||
import user from 'src/agent/user'
|
||||
import network from 'src/agent/network'
|
||||
import {getUserReadRelays} from 'src/agent/relays'
|
||||
import {modal} from "src/app/ui"
|
||||
import {mergeParents} from "src/app"
|
||||
|
||||
@ -23,8 +24,9 @@
|
||||
const since = now()
|
||||
const maxNotes = 300
|
||||
const cursor = new Cursor()
|
||||
const {profile} = user
|
||||
const muffle = Tags
|
||||
.wrap(($user?.muffle || []).filter(t => Math.random() > parseFloat(last(t))))
|
||||
.wrap(($profile?.muffle || []).filter(t => Math.random() > parseFloat(last(t))))
|
||||
.values().all()
|
||||
|
||||
const processNewNotes = async newNotes => {
|
||||
@ -75,6 +77,9 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Add in our user relays in case they weren't specified above
|
||||
relays = relays.concat(getUserReadRelays()).slice(0, 3)
|
||||
|
||||
const sub = network.listen(relays, {...filter, since}, onChunk)
|
||||
|
||||
const scroller = createScroller(() => {
|
||||
|
@ -1,29 +1,55 @@
|
||||
<script lang="ts">
|
||||
import {last} from 'ramda'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {ellipsize} from 'hurdak/lib/hurdak'
|
||||
import {renderContent} from "src/util/html"
|
||||
import {renderContent, noEvent} from "src/util/html"
|
||||
import {displayPerson} from "src/util/nostr"
|
||||
import Anchor from 'src/partials/Anchor.svelte'
|
||||
import {getPubkeyWriteRelays} from 'src/agent/relays'
|
||||
import user from 'src/agent/user'
|
||||
import {routes} from "src/app/ui"
|
||||
|
||||
export let person
|
||||
|
||||
const {petnamePubkeys} = user
|
||||
|
||||
const addPetname = pubkey => {
|
||||
const [{url}] = getPubkeyWriteRelays(pubkey)
|
||||
|
||||
user.addPetname(pubkey, url, person.name)
|
||||
}
|
||||
</script>
|
||||
|
||||
<a
|
||||
in:fly={{y: 20}}
|
||||
href={routes.person(person.pubkey)}
|
||||
class="flex gap-4 border-l-2 border-solid border-dark hover:bg-black hover:border-accent transition-all py-3 px-6 overflow-hidden">
|
||||
<div
|
||||
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||
style="background-image: url({person.picture})" />
|
||||
<div class="flex-grow">
|
||||
<div class="flex gap-2 items-center justify-between">
|
||||
<h1 class="text-2xl">{displayPerson(person)}</h1>
|
||||
{#if person.verified_as}
|
||||
<div class="flex gap-1 text-sm">
|
||||
<i class="fa fa-user-check text-accent" />
|
||||
<span class="text-light">{last(person.verified_as.split('@'))}</span>
|
||||
<div class="flex-grow flex flex-col gap-4 min-w-0">
|
||||
<div class="flex gap-2 items-start justify-between">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="text-xl">{displayPerson(person)}</h1>
|
||||
{#if person.verified_as}
|
||||
<div class="flex gap-1 text-sm">
|
||||
<i class="fa fa-user-check text-accent" />
|
||||
<span class="text-light">{last(person.verified_as.split('@'))}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $petnamePubkeys.includes(person.pubkey)}
|
||||
<Anchor type="button-accent" on:click={noEvent(() => user.removePetname(person.pubkey))}>
|
||||
Following
|
||||
</Anchor>
|
||||
{:else}
|
||||
<Anchor type="button" on:click={noEvent(() => addPetname(person.pubkey))}>
|
||||
Follow
|
||||
</Anchor>
|
||||
{/if}
|
||||
</div>
|
||||
<p>{@html renderContent(ellipsize(person.about || '', 140))}</p>
|
||||
<p class="overflow-hidden text-ellipsis">
|
||||
{@html renderContent(ellipsize(person.about || '', 140))}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
@ -6,9 +6,8 @@
|
||||
import {between} from 'hurdak/lib/hurdak'
|
||||
import {fly} from 'svelte/transition'
|
||||
import Toggle from "src/partials/Toggle.svelte"
|
||||
import {relays} from "src/agent/relays"
|
||||
import pool from 'src/agent/pool'
|
||||
import {addRelay, removeRelay, setRelayWriteCondition} from "src/app"
|
||||
import user from "src/agent/user"
|
||||
|
||||
export let relay
|
||||
export let theme = 'dark'
|
||||
@ -19,6 +18,8 @@
|
||||
let showStatus = false
|
||||
let joined = false
|
||||
|
||||
const {relays} = user
|
||||
|
||||
$: joined = find(propEq('url', relay.url), $relays)
|
||||
|
||||
onMount(() => {
|
||||
@ -63,11 +64,15 @@
|
||||
</p>
|
||||
</div>
|
||||
{#if joined}
|
||||
<button class="flex gap-3 items-center text-light" on:click={() => removeRelay(relay.url)}>
|
||||
<button
|
||||
class="flex gap-3 items-center text-light"
|
||||
on:click={() => user.removeRelay(relay.url)}>
|
||||
<i class="fa fa-right-from-bracket" /> Leave
|
||||
</button>
|
||||
{:else}
|
||||
<button class="flex gap-3 items-center text-light" on:click={() => addRelay(relay.url)}>
|
||||
<button
|
||||
class="flex gap-3 items-center text-light"
|
||||
on:click={() => user.addRelay(relay.url)}>
|
||||
<i class="fa fa-right-to-bracket" /> Join
|
||||
</button>
|
||||
{/if}
|
||||
@ -81,7 +86,7 @@
|
||||
<span>Publish to this relay?</span>
|
||||
<Toggle
|
||||
value={relay.write}
|
||||
on:change={() => setRelayWriteCondition(relay.url, !relay.write)} />
|
||||
on:change={() => user.setRelayWriteCondition(relay.url, !relay.write)} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -5,12 +5,14 @@
|
||||
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"
|
||||
import user from "src/agent/user"
|
||||
|
||||
let q = ""
|
||||
let search
|
||||
let knownRelays = database.watch('relays', t => t.all())
|
||||
|
||||
const {relays} = user
|
||||
|
||||
$: {
|
||||
const joined = new Set(pluck('url', $relays))
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import {displayPerson} from 'src/util/nostr'
|
||||
import {user} from 'src/agent/user'
|
||||
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'
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
const {mostRecentAlert, lastCheckedAlerts} = alerts
|
||||
const {hasNewMessages} = messages
|
||||
const {profile} = user
|
||||
</script>
|
||||
|
||||
<ul
|
||||
@ -15,13 +16,13 @@
|
||||
border-r border-medium text-white overflow-hidden z-10 lg:ml-0"
|
||||
class:-ml-56={!$menuIsOpen}
|
||||
>
|
||||
{#if $user}
|
||||
{#if $profile}
|
||||
<li>
|
||||
<a href={routes.person($user.pubkey)} class="flex gap-2 px-4 py-2 pb-6 items-center">
|
||||
<a href={routes.person($profile.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>
|
||||
style="background-image: url({$profile.picture})" />
|
||||
<span class="text-lg font-bold">{displayPerson($profile)}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="cursor-pointer relative">
|
||||
@ -43,7 +44,7 @@
|
||||
<i class="fa-solid fa-tag mr-2" /> Notes
|
||||
</a>
|
||||
</li>
|
||||
{#if $user}
|
||||
{#if $profile}
|
||||
<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
|
||||
@ -62,7 +63,7 @@
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
{#if $user}
|
||||
{#if $profile}
|
||||
<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
|
||||
|
@ -1,11 +1,11 @@
|
||||
<script>
|
||||
import {fly} from 'svelte/transition'
|
||||
import {toast, modal} from "src/app/ui"
|
||||
import {addRelay} from 'src/app'
|
||||
import Input from 'src/partials/Input.svelte'
|
||||
import Content from 'src/partials/Content.svelte'
|
||||
import Heading from 'src/partials/Heading.svelte'
|
||||
import Button from 'src/partials/Button.svelte'
|
||||
import user from 'src/agent/user'
|
||||
import {toast, modal} from "src/app/ui"
|
||||
|
||||
let url = $modal.url
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
return toast.show("error", "That isn't a valid websocket url")
|
||||
}
|
||||
|
||||
addRelay(url)
|
||||
user.addRelay(url)
|
||||
modal.set(null)
|
||||
}
|
||||
</script>
|
||||
|
@ -4,7 +4,7 @@
|
||||
import {nip19} from 'nostr-tools'
|
||||
import {navigate} from "svelte-routing"
|
||||
import {fuzzy} from "src/util/misc"
|
||||
import {user} from 'src/agent/user'
|
||||
import user from 'src/agent/user'
|
||||
import network from 'src/agent/network'
|
||||
import database from 'src/agent/database'
|
||||
import {getUserReadRelays} from 'src/agent/relays'
|
||||
@ -24,7 +24,7 @@
|
||||
const rooms = database.watch(['rooms', 'messages'], async () => {
|
||||
const rooms = await database.rooms.all({joined: true})
|
||||
const messages = await database.messages.all()
|
||||
const pubkeys = without([$user.pubkey], uniq(messages.flatMap(m => [m.pubkey, m.recipient])))
|
||||
const pubkeys = without([user.getPubkey()], uniq(messages.flatMap(m => [m.pubkey, m.recipient])))
|
||||
|
||||
await network.loadPeople(pubkeys)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
import {nip19} from 'nostr-tools'
|
||||
import {now} from 'src/util/misc'
|
||||
import Channel from 'src/partials/Channel.svelte'
|
||||
import {user} from 'src/agent/user'
|
||||
import user from 'src/agent/user'
|
||||
import {getRelaysForEventChildren} from 'src/agent/relays'
|
||||
import database from 'src/agent/database'
|
||||
import network from 'src/agent/network'
|
||||
@ -57,7 +57,7 @@
|
||||
name={$room?.name}
|
||||
about={$room?.about}
|
||||
picture={$room?.picture}
|
||||
editRoom={$room?.pubkey === $user.pubkey && editRoom}
|
||||
editRoom={$room?.pubkey === user.getPubkey() && editRoom}
|
||||
{loadMessages}
|
||||
{listenForMessages}
|
||||
{sendMessage}
|
||||
|
@ -9,9 +9,9 @@
|
||||
const confirm = async () => {
|
||||
confirmed = true
|
||||
|
||||
localStorage.clear()
|
||||
await database.dropAll()
|
||||
|
||||
await database.clearAll()
|
||||
localStorage.clear()
|
||||
|
||||
// do a hard refresh so everything gets totally cleared.
|
||||
// Give them a moment to see the state transition. Dexie
|
||||
|
@ -4,7 +4,7 @@
|
||||
import {personKinds} from 'src/util/nostr'
|
||||
import {now} from 'src/util/misc'
|
||||
import Channel from 'src/partials/Channel.svelte'
|
||||
import {user} from 'src/agent/user'
|
||||
import user from 'src/agent/user'
|
||||
import {getAllPubkeyRelays} from 'src/agent/relays'
|
||||
import database from 'src/agent/database'
|
||||
import network from 'src/agent/network'
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
messages.lastCheckedByPubkey.update($obj => ({...$obj, [pubkey]: now()}))
|
||||
|
||||
const getRelays = () => getAllPubkeyRelays([pubkey, $user.pubkey]).slice(0, 3)
|
||||
const getRelays = () => getAllPubkeyRelays([pubkey, user.getPubkey()]).slice(0, 3)
|
||||
|
||||
const decryptMessages = async events => {
|
||||
// Gotta do it in serial because of extension limitations
|
||||
@ -37,8 +37,8 @@
|
||||
const listenForMessages = cb => network.listen(
|
||||
getRelays(),
|
||||
[{kinds: personKinds, authors: [pubkey]},
|
||||
{kinds: [4], authors: [$user.pubkey], '#p': [pubkey]},
|
||||
{kinds: [4], authors: [pubkey], '#p': [$user.pubkey]}],
|
||||
{kinds: [4], authors: [user.getPubkey()], '#p': [pubkey]},
|
||||
{kinds: [4], authors: [pubkey], '#p': [user.getPubkey()]}],
|
||||
async events => {
|
||||
// Reload from db since we annotate messages there
|
||||
const messageIds = pluck('id', events.filter(e => e.kind === 4))
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import user from 'src/agent/user'
|
||||
|
||||
onMount(() => navigate('/notes/network'))
|
||||
onMount(() => navigate(user.getProfile() ? '/notes/network' : '/login'))
|
||||
</script>
|
||||
|
@ -6,7 +6,7 @@
|
||||
import Tabs from "src/partials/Tabs.svelte"
|
||||
import Network from "src/views/notes/Network.svelte"
|
||||
import Popular from "src/views/notes/Popular.svelte"
|
||||
import {user} from 'src/agent/user'
|
||||
import user from 'src/agent/user'
|
||||
|
||||
export let activeTab
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
</script>
|
||||
|
||||
<Content>
|
||||
{#if !$user}
|
||||
{#if !user.getProfile()}
|
||||
<Content size="lg" class="text-center">
|
||||
<p class="text-xl">Don't have an account?</p>
|
||||
<p>Click <Anchor href="/login">here</Anchor> to join the nostr network.</p>
|
||||
|
@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import {last} from 'ramda'
|
||||
import {last, prop} from 'ramda'
|
||||
import {onMount} from 'svelte'
|
||||
import {tweened} from 'svelte/motion'
|
||||
import {nip19} from 'nostr-tools'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import {first} from 'hurdak/lib/hurdak'
|
||||
import {log} from 'src/util/logger'
|
||||
import {renderContent} from 'src/util/html'
|
||||
import {displayPerson, Tags} from 'src/util/nostr'
|
||||
@ -16,7 +17,7 @@
|
||||
import Notes from "src/views/person/Notes.svelte"
|
||||
import Likes from "src/views/person/Likes.svelte"
|
||||
import Relays from "src/views/person/Relays.svelte"
|
||||
import {user, follows} from "src/agent/user"
|
||||
import user from "src/agent/user"
|
||||
import {getUserReadRelays, getPubkeyWriteRelays} from "src/agent/relays"
|
||||
import network from "src/agent/network"
|
||||
import keys from "src/agent/keys"
|
||||
@ -28,7 +29,7 @@
|
||||
export let relays = []
|
||||
|
||||
const interpolate = (a, b) => t => a + Math.round((b - a) * t)
|
||||
const {pubkeys: userFollows} = follows
|
||||
const {petnamePubkeys} = user
|
||||
|
||||
let pubkey = nip19.decode(npub).data as string
|
||||
let following = false
|
||||
@ -37,13 +38,17 @@
|
||||
let person = database.getPersonWithFallback(pubkey)
|
||||
let loading = true
|
||||
|
||||
$: following = $userFollows.includes(pubkey)
|
||||
$: following = $petnamePubkeys.includes(pubkey)
|
||||
|
||||
onMount(async () => {
|
||||
log('Person', npub, person)
|
||||
|
||||
// Add all the relays we know the person uses
|
||||
relays = relays.concat(getPubkeyWriteRelays(pubkey))
|
||||
// Add all the relays we know the person uses, as well as our own
|
||||
// in case we don't have much information
|
||||
relays = relays
|
||||
.concat(getPubkeyWriteRelays(pubkey))
|
||||
.concat(getUserReadRelays())
|
||||
.slice(0, 3)
|
||||
|
||||
// Refresh our person if needed
|
||||
network.loadPeople([pubkey]).then(() => {
|
||||
@ -59,7 +64,7 @@
|
||||
}
|
||||
})
|
||||
|
||||
// Round it out
|
||||
// Round out our followers count
|
||||
await network.listenUntilEose(
|
||||
relays,
|
||||
[{kinds: [3], '#p': [pubkey]}],
|
||||
@ -87,13 +92,11 @@
|
||||
}
|
||||
|
||||
const follow = async () => {
|
||||
const [{url}] = relays.concat(getUserReadRelays())
|
||||
|
||||
follows.addFollow(pubkey, url, person.name)
|
||||
user.addPetname(pubkey, prop('url', first(relays)), person.name)
|
||||
}
|
||||
|
||||
const unfollow = async () => {
|
||||
follows.removeFollow(pubkey)
|
||||
user.removePetname(pubkey)
|
||||
}
|
||||
|
||||
const openAdvanced = () => {
|
||||
@ -132,9 +135,9 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="whitespace-nowrap flex gap-3 items-center flex-wrap">
|
||||
{#if $user?.pubkey === pubkey && keys.canSign()}
|
||||
{#if user.getPubkey() === pubkey && keys.canSign()}
|
||||
<Anchor href="/profile"><i class="fa-solid fa-edit" /> Edit profile</Anchor>
|
||||
{:else if $user && keys.canSign()}
|
||||
{:else if user.getProfile() && keys.canSign()}
|
||||
<Anchor type="button-circle" on:click={openAdvanced}>
|
||||
<i class="fa fa-sliders" />
|
||||
</Anchor>
|
||||
@ -146,7 +149,7 @@
|
||||
<Anchor type="button-circle" on:click={unfollow}>
|
||||
<i class="fa fa-user-minus" />
|
||||
</Anchor>
|
||||
{:else if $user?.pubkey !== pubkey}
|
||||
{:else if user.getPubkey() !== pubkey}
|
||||
<Anchor type="button-circle" on:click={follow}>
|
||||
<i class="fa fa-user-plus" />
|
||||
</Anchor>
|
||||
@ -158,7 +161,7 @@
|
||||
</div>
|
||||
<p>{@html renderContent(person.about || '')}</p>
|
||||
{#if person?.petnames}
|
||||
<div class="flex gap-8">
|
||||
<div class="flex gap-8" in:fly={{y: 20}}>
|
||||
<button on:click={showFollows}>
|
||||
<strong>{person.petnames.length}</strong> following
|
||||
</button>
|
||||
@ -173,7 +176,7 @@
|
||||
<Tabs tabs={['notes', 'likes', 'relays']} {activeTab} {setActiveTab} />
|
||||
|
||||
{#if activeTab === 'notes'}
|
||||
<Notes {pubkey} />
|
||||
<Notes {pubkey} {relays} />
|
||||
{:else if activeTab === 'likes'}
|
||||
<Likes {pubkey} />
|
||||
{:else if activeTab === 'relays'}
|
||||
|
@ -11,7 +11,7 @@
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import {user} from "src/agent/user"
|
||||
import user from "src/agent/user"
|
||||
import {getUserWriteRelays} from 'src/agent/relays'
|
||||
import cmd from "src/agent/cmd"
|
||||
import {routes, toast} from "src/app/ui"
|
||||
@ -22,11 +22,11 @@
|
||||
const pseudUrl = "https://www.coindesk.com/markets/2020/06/29/many-bitcoin-developers-are-choosing-to-use-pseudonyms-for-good-reason/"
|
||||
|
||||
onMount(async () => {
|
||||
if (!$user) {
|
||||
if (!user.getProfile()) {
|
||||
return navigate("/login")
|
||||
}
|
||||
|
||||
values = pick(Object.keys(values), $user)
|
||||
values = pick(Object.keys(values), user.getProfile())
|
||||
|
||||
document.querySelector('[name=picture]').addEventListener('change', async e => {
|
||||
const target = e.target as HTMLInputElement
|
||||
@ -48,7 +48,7 @@
|
||||
|
||||
cmd.updateUser(getUserWriteRelays(), values)
|
||||
|
||||
navigate(routes.person($user.pubkey, 'profile'))
|
||||
navigate(routes.person(user.getPubkey(), 'profile'))
|
||||
|
||||
toast.show("info", "Your profile has been updated!")
|
||||
}
|
||||
|
@ -4,8 +4,10 @@
|
||||
import RelaySearch from "src/partials/RelaySearch.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import RelayCard from "src/partials/RelayCard.svelte"
|
||||
import {relays} from "src/agent/relays"
|
||||
import user from "src/agent/user"
|
||||
import {modal} from "src/app/ui"
|
||||
|
||||
const {relays} = user
|
||||
</script>
|
||||
|
||||
<div in:fly={{y: 20}}>
|
||||
|
@ -7,13 +7,13 @@
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import Content from "src/partials/Content.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/ui"
|
||||
|
||||
let values = {...$settings}
|
||||
|
||||
onMount(async () => {
|
||||
if (!$user) {
|
||||
if (!user.getProfile()) {
|
||||
return navigate("/login")
|
||||
}
|
||||
})
|
||||
|
@ -61,6 +61,14 @@ export const killEvent = e => {
|
||||
e.stopImmediatePropagation()
|
||||
}
|
||||
|
||||
export const noEvent = f => e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
|
||||
f()
|
||||
}
|
||||
|
||||
export const fromParentOffset = (element, offset): [HTMLElement, number] => {
|
||||
for (const child of element.childNodes) {
|
||||
if (offset <= child.textContent.length) {
|
||||
|
@ -2,7 +2,7 @@ import {last, identity, objOf, prop, flatten, uniq} from 'ramda'
|
||||
import {nip19} from 'nostr-tools'
|
||||
import {ensurePlural, ellipsize, first} from 'hurdak/lib/hurdak'
|
||||
|
||||
export const personKinds = [0, 2, 3, 10002, 12165]
|
||||
export const personKinds = [0, 2, 3, 10001, 10002, 12165]
|
||||
|
||||
export class Tags {
|
||||
constructor(tags) {
|
||||
|
@ -4,7 +4,6 @@
|
||||
import {quantify} from 'hurdak/lib/hurdak'
|
||||
import {last, reject, pluck, propEq} from 'ramda'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {navigate} from "svelte-routing"
|
||||
import {fuzzy} from "src/util/misc"
|
||||
import {isRelay} from "src/util/nostr"
|
||||
import Button from "src/partials/Button.svelte"
|
||||
@ -14,7 +13,6 @@
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Modal from "src/partials/Modal.svelte"
|
||||
import Heading from 'src/partials/Heading.svelte'
|
||||
import {user} from "src/agent/user"
|
||||
import {getUserWriteRelays} from 'src/agent/relays'
|
||||
import database from 'src/agent/database'
|
||||
import cmd from "src/agent/cmd"
|
||||
@ -75,10 +73,6 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!$user) {
|
||||
navigate("/login")
|
||||
}
|
||||
|
||||
const person = database.people.get(pubkey)
|
||||
|
||||
if (person?.name) {
|
||||
|
@ -5,12 +5,12 @@
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import Content from 'src/partials/Content.svelte'
|
||||
import SelectButton from "src/partials/SelectButton.svelte"
|
||||
import {user} from 'src/agent/user'
|
||||
import user from 'src/agent/user'
|
||||
import {getUserWriteRelays} from 'src/agent/relays'
|
||||
import cmd from 'src/agent/cmd'
|
||||
import {modal} from 'src/app/ui'
|
||||
|
||||
const muffle = $user.muffle || []
|
||||
const muffle = user.getProfile().muffle || []
|
||||
const muffleOptions = ['Never', 'Sometimes', 'Often', 'Always']
|
||||
const muffleValue = parseFloat(first(muffle.filter(t => t[1] === $modal.person.pubkey).map(last)) || 1)
|
||||
|
||||
|
@ -1,17 +1,21 @@
|
||||
<script>
|
||||
import {fly} from 'svelte/transition'
|
||||
import {fuzzy} from "src/util/misc"
|
||||
import {personKinds} from "src/util/nostr"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import PersonInfo from 'src/partials/PersonInfo.svelte'
|
||||
import {user} from 'src/agent/user'
|
||||
import {getUserReadRelays} from 'src/agent/relays'
|
||||
import database from 'src/agent/database'
|
||||
import network from 'src/agent/network'
|
||||
import user from 'src/agent/user'
|
||||
|
||||
export let hideFollowing = false
|
||||
|
||||
let q
|
||||
let search
|
||||
|
||||
const {petnamePubkeys} = user
|
||||
|
||||
database.watch('people', people => {
|
||||
search = fuzzy(
|
||||
people.all({'name:!nil': null}),
|
||||
@ -28,9 +32,9 @@
|
||||
</Input>
|
||||
|
||||
{#each (search ? search(q) : []).slice(0, 30) as person (person.pubkey)}
|
||||
{#if person.pubkey !== $user?.pubkey}
|
||||
<div in:fly={{y: 20}}>
|
||||
<PersonInfo {person} />
|
||||
</div>
|
||||
{#if person.pubkey !== user.getPubkey() && !(hideFollowing && $petnamePubkeys.includes(person.pubkey))}
|
||||
<PersonInfo {person} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Spinner />
|
||||
{/each}
|
||||
|
Loading…
Reference in New Issue
Block a user