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