mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-18 19:23:40 +00:00
Nest profile data under kind0 to avoid nuking stuff we don't support
This commit is contained in:
parent
545f13e0b5
commit
d4aaf98985
14
ROADMAP.md
14
ROADMAP.md
@ -1,18 +1,17 @@
|
||||
# Current
|
||||
|
||||
- [ ] Fix anon/new user experience
|
||||
- [ ] Clicking stuff that would publish kicks you to the login page, we should open a modal instead.
|
||||
- [ ] Separate user info and relays so we can still select/figure out relays for anons
|
||||
- [ ] Separate petnames out as well so anons can follow people
|
||||
- [ ] 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.
|
||||
- [ ] Fix anon/new user experience
|
||||
- [ ] When logging in rather than generating a new keypair, ask for a relay to bootstrap from
|
||||
- [ ] Preload/wait for our big list of relays so we can offer suggestions. Search in the background and let them know if we found their profile.
|
||||
- [ ] Clicking stuff that would publish kicks you to the login page, we should open a modal instead.
|
||||
- [ ] Test publishing events with zero relays
|
||||
- [ ] Try lumping tables into a single key each to reduce load/save contention and time
|
||||
- [ ] Fix turning off link previews, or make sure it applies to images/videos too
|
||||
|
||||
# Snacks
|
||||
|
||||
- [ ] open web+nostr links like snort
|
||||
- [ ] DM/chat read status in encrypted note
|
||||
- [ ] Relay recommendations based on follows/followers
|
||||
- [ ] Pinned posts ala snort
|
||||
@ -76,3 +75,4 @@
|
||||
- [ ] Add notifications for chat messages
|
||||
- [ ] Compress events
|
||||
- https://github.com/nostr-protocol/nips/issues/265#issuecomment-1434250263
|
||||
- [ ] If you hide something, but the event doesn't get retrived, it gets un-hidden
|
||||
|
@ -60,7 +60,7 @@
|
||||
import Search from "src/routes/Search.svelte"
|
||||
import Settings from "src/routes/Settings.svelte"
|
||||
|
||||
Object.assign(window, {cmd, database, keys, network, pool, sync})
|
||||
Object.assign(window, {cmd, database, user, keys, network, pool, sync})
|
||||
|
||||
export let url = ""
|
||||
|
||||
@ -214,7 +214,7 @@
|
||||
<TopNav />
|
||||
|
||||
{#if $modal}
|
||||
<Modal onEscape={closeModal}>
|
||||
<Modal onEscape={$modal.noEscape ? null : closeModal}>
|
||||
{#if $modal.type === 'note/detail'}
|
||||
{#key $modal.note.id}
|
||||
<NoteDetail {...$modal} />
|
||||
@ -256,12 +256,11 @@
|
||||
|
||||
{#if $toast}
|
||||
<div
|
||||
class="fixed top-0 left-0 right-0 z-10"
|
||||
transition:fly={{y: -50, duration: 300}}
|
||||
>
|
||||
class="fixed top-0 left-0 right-0 z-10 click-events-none"
|
||||
transition:fly={{y: -50, duration: 300}}>
|
||||
<div
|
||||
class="rounded bg-accent shadow-xl mx-24 sm:mx-32 mt-2 p-3 text-white text-center border border-dark"
|
||||
>
|
||||
class="rounded bg-accent shadow-xl mx-24 sm:mx-32 mt-2 p-3 text-white text-center
|
||||
border border-dark click-events-all">
|
||||
{#if is(String, $toast.message)}
|
||||
{$toast.message}
|
||||
{:else}
|
||||
|
@ -316,7 +316,13 @@ const watch = (names, f) => {
|
||||
|
||||
const getPersonWithFallback = pubkey => people.get(pubkey) || {pubkey}
|
||||
|
||||
const dropAll = () => Promise.all(Object.values(registry).map(t => t.drop()))
|
||||
const dropAll = async () => {
|
||||
for (const table of Object.values(registry)) {
|
||||
await table.drop()
|
||||
|
||||
log(`Successfully dropped table ${table.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
const ready = derived(pluck('ready', Object.values(registry)), all(identity))
|
||||
|
||||
|
@ -82,7 +82,7 @@ const listenUntilEose = (relays, filter, onEvents, {shouldProcess = true}: any =
|
||||
}) as Promise<void>
|
||||
}
|
||||
|
||||
const loadPeople = async (pubkeys, {kinds = personKinds, force = false, ...opts} = {}) => {
|
||||
const loadPeople = async (pubkeys, {relays = null, kinds = personKinds, force = false, ...opts} = {}) => {
|
||||
pubkeys = uniq(pubkeys)
|
||||
|
||||
// If we're not reloading, only get pubkeys we don't already know about
|
||||
@ -90,19 +90,23 @@ const loadPeople = async (pubkeys, {kinds = personKinds, force = false, ...opts}
|
||||
pubkeys = getStalePubkeys(pubkeys)
|
||||
}
|
||||
|
||||
// Use the best relays we have, but fall back to user relays
|
||||
const relays = getAllPubkeyWriteRelays(pubkeys)
|
||||
.concat(getUserReadRelays())
|
||||
.slice(0, 3)
|
||||
await Promise.all(
|
||||
chunk(256, pubkeys).map(async chunk => {
|
||||
// Use the best relays we have, but fall back to user relays
|
||||
const chunkRelays = relays || (
|
||||
getAllPubkeyWriteRelays(chunk)
|
||||
.concat(getUserReadRelays())
|
||||
.slice(0, 3)
|
||||
)
|
||||
|
||||
if (pubkeys.length > 0) {
|
||||
await load(relays, {kinds, authors: pubkeys}, opts)
|
||||
}
|
||||
await load(chunkRelays, {kinds, authors: chunk}, opts)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const loadParents = notes => {
|
||||
const notesWithParent = notes.filter(findReplyId)
|
||||
const relays = aggregateScores(notesWithParent.map(getRelaysForEventParent))
|
||||
const relays = aggregateScores(notesWithParent.map(getRelaysForEventParent)).slice(0, 3)
|
||||
|
||||
return load(relays, {kinds: [1], ids: notesWithParent.map(findReplyId)})
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import {get} from 'svelte/store'
|
||||
import type {Relay} from 'src/util/types'
|
||||
import {pick, map, assoc, sortBy, uniqBy, prop} from 'ramda'
|
||||
import {first} from 'hurdak/lib/hurdak'
|
||||
import {Tags, findReplyId} from 'src/util/nostr'
|
||||
import database from 'src/agent/database'
|
||||
import user from 'src/agent/user'
|
||||
import keys from 'src/agent/keys'
|
||||
|
||||
// From Mike Dilger:
|
||||
// 1) Other people's write relays — pull events from people you follow,
|
||||
@ -49,7 +48,7 @@ export const getUserRelays = () =>
|
||||
export const getUserReadRelays = () =>
|
||||
getUserRelays().filter(prop('read')).map(pick(['url', 'score']))
|
||||
|
||||
export const getUserWriteRelays = () =>
|
||||
export const getUserWriteRelays = (): Array<Relay> =>
|
||||
getUserRelays().filter(prop('write')).map(pick(['url', 'score']))
|
||||
|
||||
// Event-related special cases
|
||||
@ -85,14 +84,9 @@ export const getRelayForPersonHint = (pubkey, event) =>
|
||||
// our followers, so write to our write relays as well.
|
||||
export const getEventPublishRelays = event => {
|
||||
const tags = Tags.from(event)
|
||||
const pubkeys = tags.type("p").values().all()
|
||||
const pubkeys = tags.type("p").values().all().concat(event.pubkey)
|
||||
|
||||
return uniqByUrl(
|
||||
pubkeys
|
||||
.concat(event.pubkey)
|
||||
.concat(get(keys.pubkey))
|
||||
.flatMap(getPubkeyReadRelays)
|
||||
)
|
||||
return getAllPubkeyReadRelays(pubkeys).concat(getUserWriteRelays())
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ import {nip05} from 'nostr-tools'
|
||||
import {noop, createMap, ensurePlural, switcherFn} from 'hurdak/lib/hurdak'
|
||||
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 {Tags, personKinds, roomAttrs, isRelay, normalizeRelayUrl} from 'src/util/nostr'
|
||||
import database from 'src/agent/database'
|
||||
|
||||
const processEvents = async events => {
|
||||
@ -17,7 +17,7 @@ const processEvents = async events => {
|
||||
|
||||
const processProfileEvents = async events => {
|
||||
const profileEvents = ensurePlural(events)
|
||||
.filter(e => [0, 3, 12165].includes(e.kind))
|
||||
.filter(e => personKinds.includes(e.kind))
|
||||
|
||||
const updates = {}
|
||||
for (const e of profileEvents) {
|
||||
@ -29,19 +29,22 @@ const processProfileEvents = async events => {
|
||||
...switcherFn(e.kind, {
|
||||
0: () => {
|
||||
return tryJson(() => {
|
||||
const content = JSON.parse(e.content)
|
||||
const kind0 = JSON.parse(e.content)
|
||||
|
||||
// Fire off a nip05 verification
|
||||
if (content.nip05 && e.created_at > (person.nip05_updated_at || 0)) {
|
||||
verifyNip05(e.pubkey, content.nip05)
|
||||
if (e.created_at > (person.kind0_updated_at || 0)) {
|
||||
if (kind0.nip05) {
|
||||
verifyNip05(e.pubkey, kind0.nip05)
|
||||
|
||||
content.nip05_updated_at = e.created_at
|
||||
}
|
||||
kind0.nip05_updated_at = e.created_at
|
||||
}
|
||||
|
||||
if (e.created_at > (person.profile_updated_at || 0)) {
|
||||
return {
|
||||
...content,
|
||||
profile_updated_at: e.created_at,
|
||||
kind0: {
|
||||
...person?.kind0,
|
||||
...updates[e.pubkey]?.kind0,
|
||||
...kind0,
|
||||
},
|
||||
kind0_updated_at: e.created_at,
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -271,6 +274,10 @@ const processRoutes = async events => {
|
||||
updates.push(
|
||||
calculateRoute(pubkey, url, 'tag', 'write', e.created_at)
|
||||
)
|
||||
|
||||
updates.push(
|
||||
calculateRoute(pubkey, url, 'tag', 'read', e.created_at)
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -279,7 +286,7 @@ const processRoutes = async events => {
|
||||
|
||||
if (!isEmpty(updates)) {
|
||||
await database.relays.bulkPatch(createMap('url', updates.map(pick(['url']))))
|
||||
await database.routes.bulkPut(createMap('id', updates.filter(identity)))
|
||||
await database.routes.bulkPut(createMap('id', updates))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import type {Person} from 'src/util/types'
|
||||
import type {Readable} from 'svelte/store'
|
||||
import {pipe, assoc, whereEq, when, concat, reject, nth, map} from 'ramda'
|
||||
import {synced} from 'src/util/misc'
|
||||
import {derived, get} from 'svelte/store'
|
||||
import {derived} from 'svelte/store'
|
||||
import database from 'src/agent/database'
|
||||
import keys from 'src/agent/keys'
|
||||
import cmd from 'src/agent/cmd'
|
||||
@ -28,28 +28,35 @@ const profile = derived(
|
||||
return null
|
||||
}
|
||||
|
||||
return profileCopy = ($people[pubkey] || {pubkey})
|
||||
return $people[pubkey] || {pubkey}
|
||||
}
|
||||
) as Readable<Person>
|
||||
|
||||
const petnames = derived(
|
||||
[profile, anonPetnames],
|
||||
([$profile, $anonPetnames]) => {
|
||||
return petnamesCopy = $profile?.petnames || $anonPetnames
|
||||
}
|
||||
([$profile, $anonPetnames]) =>
|
||||
$profile?.petnames || $anonPetnames
|
||||
)
|
||||
|
||||
const relays = derived(
|
||||
[profile, anonRelays],
|
||||
([$profile, $anonRelays]) => {
|
||||
return relaysCopy = $profile?.relays || $anonRelays
|
||||
}
|
||||
([$profile, $anonRelays]) =>
|
||||
$profile?.relays || $anonRelays
|
||||
)
|
||||
|
||||
// Prime our copies
|
||||
get(profile)
|
||||
get(petnames)
|
||||
get(relays)
|
||||
// Keep our copies up to date
|
||||
|
||||
profile.subscribe($profile => {
|
||||
profileCopy = $profile
|
||||
})
|
||||
|
||||
petnames.subscribe($petnames => {
|
||||
petnamesCopy = $petnames
|
||||
})
|
||||
|
||||
relays.subscribe($relays => {
|
||||
relaysCopy = $relays
|
||||
})
|
||||
|
||||
const user = {
|
||||
// Profile
|
||||
|
@ -1,7 +1,7 @@
|
||||
import lf from 'localforage'
|
||||
import memoryStorageDriver from 'localforage-memoryStorageDriver'
|
||||
import {switcherFn} from 'hurdak/lib/hurdak'
|
||||
import {error} from 'src/util/logger'
|
||||
import {error, warn} from 'src/util/logger'
|
||||
import {where} from 'src/util/misc'
|
||||
|
||||
// Firefox private mode doesn't have access to any storage options
|
||||
@ -15,6 +15,12 @@ const getStore = storeName => {
|
||||
stores[storeName] = lf.createInstance({name: 'coracle', storeName})
|
||||
}
|
||||
|
||||
if (stores[storeName].dropped) {
|
||||
warn(`Dropped instance ${storeName} was requested`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return stores[storeName]
|
||||
}
|
||||
|
||||
@ -26,43 +32,52 @@ addEventListener('message', async ({data: {topic, payload, channel}}) => {
|
||||
const {storeName, method, args} = payload
|
||||
const instance = getStore(storeName)
|
||||
|
||||
const result = await switcherFn(method, {
|
||||
dump: () => new Promise(resolve => {
|
||||
const result = {}
|
||||
if (instance) {
|
||||
const result = await switcherFn(method, {
|
||||
dump: () => new Promise(resolve => {
|
||||
const result = {}
|
||||
|
||||
instance.iterate(
|
||||
(v, k, i) => { result[k] = v },
|
||||
() => resolve(result),
|
||||
)
|
||||
}),
|
||||
setItems: () => {
|
||||
for (const [k, v] of Object.entries(args[0])) {
|
||||
instance.setItem(k, v)
|
||||
}
|
||||
},
|
||||
removeItems: () => {
|
||||
for (const k of args[0]) {
|
||||
instance.removeItem(k)
|
||||
}
|
||||
},
|
||||
default: () => instance[method](...args),
|
||||
})
|
||||
instance.iterate(
|
||||
(v, k, i) => { result[k] = v },
|
||||
() => resolve(result),
|
||||
)
|
||||
}),
|
||||
setItems: async () => {
|
||||
for (const [k, v] of Object.entries(args[0])) {
|
||||
await instance.setItem(k, v)
|
||||
}
|
||||
},
|
||||
removeItems: async () => {
|
||||
for (const k of args[0]) {
|
||||
await instance.removeItem(k)
|
||||
}
|
||||
},
|
||||
drop: async () => {
|
||||
instance.dropped = true
|
||||
await instance.drop()
|
||||
},
|
||||
default: () => instance[method](...args),
|
||||
})
|
||||
|
||||
reply('localforage.return', result)
|
||||
reply('localforage.return', result)
|
||||
}
|
||||
},
|
||||
'localforage.iterate': async () => {
|
||||
const matchesFilter = where(payload.where)
|
||||
const instance = getStore(payload.storeName)
|
||||
|
||||
getStore(payload.storeName).iterate(
|
||||
(v, k, i) => {
|
||||
if (matchesFilter(v)) {
|
||||
reply('localforage.item', {v, k, i})
|
||||
}
|
||||
},
|
||||
() => {
|
||||
reply('localforage.iterationComplete')
|
||||
},
|
||||
)
|
||||
if (instance) {
|
||||
instance.iterate(
|
||||
(v, k, i) => {
|
||||
if (matchesFilter(v)) {
|
||||
reply('localforage.item', {v, k, i})
|
||||
}
|
||||
},
|
||||
() => {
|
||||
reply('localforage.iterationComplete')
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
default: () => {
|
||||
throw new Error(`invalid topic: ${topic}`)
|
||||
|
@ -1,8 +1,10 @@
|
||||
import type {DisplayEvent} from 'src/util/types'
|
||||
import {omit, sortBy, identity} from 'ramda'
|
||||
import {objOf, omit, sortBy, identity} from 'ramda'
|
||||
import {get} from 'svelte/store'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import {createMap, ellipsize} from 'hurdak/lib/hurdak'
|
||||
import {renderContent} from 'src/util/html'
|
||||
import {shuffle} from 'src/util/misc'
|
||||
import {Tags, displayPerson, findReplyId} from 'src/util/nostr'
|
||||
import {getNetwork} from 'src/agent/social'
|
||||
import {getUserReadRelays} from 'src/agent/relays'
|
||||
@ -11,7 +13,7 @@ import network from 'src/agent/network'
|
||||
import keys from 'src/agent/keys'
|
||||
import alerts from 'src/app/alerts'
|
||||
import messages from 'src/app/messages'
|
||||
import {routes, modal} from 'src/app/ui'
|
||||
import {routes, settings, modal} from 'src/app/ui'
|
||||
|
||||
export const loadAppData = async pubkey => {
|
||||
if (getUserReadRelays().length > 0) {
|
||||
@ -31,14 +33,32 @@ export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: strin
|
||||
keys.setPublicKey(pubkey)
|
||||
}
|
||||
|
||||
modal.set({type: 'message', message: "Loading your profile data...", spinner: true})
|
||||
modal.set({
|
||||
type: 'message',
|
||||
message: "Loading your profile data...",
|
||||
spinner: true,
|
||||
noEscape: true,
|
||||
})
|
||||
|
||||
// Get a reasonably sized sample of relays and ask them all for relay information
|
||||
// for our user so we can bootstrap. This could be improved.
|
||||
let relays = []
|
||||
try {
|
||||
relays = (
|
||||
await fetch(get(settings).dufflepudUrl + '/relay').then(r => r.json())
|
||||
).relays.map(objOf('url'))
|
||||
} catch (e) {
|
||||
relays = database.relays.all()
|
||||
}
|
||||
|
||||
// Load our user so we can populate network and show profile info
|
||||
await network.loadPeople([pubkey], {
|
||||
relays: shuffle(relays).slice(0, 50),
|
||||
})
|
||||
|
||||
if (getUserReadRelays().length === 0) {
|
||||
navigate('/relays')
|
||||
} else {
|
||||
// Load our user so we can populate network and show profile info
|
||||
await network.loadPeople([pubkey])
|
||||
|
||||
// Load network and start listening, but don't wait for it
|
||||
loadAppData(pubkey)
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
{#if inert}
|
||||
<span class="flex gap-2 items-center relative z-10">
|
||||
<ImageCircle src={person.picture} />
|
||||
<ImageCircle src={person.kind0?.picture} />
|
||||
<span class="text-lg font-bold">{displayPerson(person)}</span>
|
||||
</span>
|
||||
{:else}
|
||||
@ -19,7 +19,7 @@
|
||||
to={routes.person(person.pubkey)}
|
||||
class="flex gap-2 items-center relative z-10"
|
||||
on:click={killEvent}>
|
||||
<ImageCircle src={person.picture} />
|
||||
<ImageCircle src={person.kind0?.picture} />
|
||||
<span class="text-lg font-bold">{displayPerson(person)}</span>
|
||||
</Link>
|
||||
{/if}
|
||||
|
@ -17,7 +17,7 @@
|
||||
let input = null
|
||||
let prevContent = ''
|
||||
|
||||
const search = fuzzy(database.people.all({'name:!nil': null}), {keys: ["name", "pubkey"]})
|
||||
const search = fuzzy(database.people.all(), {keys: ["kind0.name", "pubkey"]})
|
||||
|
||||
const getText = () => {
|
||||
const selection = document.getSelection()
|
||||
|
@ -30,9 +30,12 @@
|
||||
<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 class="absolute top-0 flex w-full justify-end pr-2 -mt-8 pointer-events-none">
|
||||
<div
|
||||
class="pointer-events-auto w-10 h-10 flex justify-center items-center bg-accent
|
||||
rounded-full cursor-pointer border border-solid border-medium border-b-0"
|
||||
on:click={onEscape}>
|
||||
<i class="fa fa-times fa-lg" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -103,7 +103,8 @@
|
||||
}
|
||||
|
||||
const deleteReaction = e => {
|
||||
cmd.deleteEvent(getEventPublishRelays(note), [e.id])
|
||||
const relays = getEventPublishRelays(note)
|
||||
cmd.deleteEvent(relays, [e.id])
|
||||
|
||||
if (e.content === '+') {
|
||||
likes = reject(propEq('pubkey', $profile.pubkey), likes)
|
||||
@ -208,7 +209,7 @@
|
||||
<div class="absolute h-px w-3 bg-light z-10" style="left: 0px; top: 27px;" />
|
||||
{/if}
|
||||
<Anchor class="text-lg font-bold" href={routes.person($person.pubkey)}>
|
||||
<ImageCircle size={10} src={$person.picture} />
|
||||
<ImageCircle size={10} src={$person.kind0?.picture} />
|
||||
</Anchor>
|
||||
<div class="flex flex-col gap-2 flex-grow min-w-0">
|
||||
<div class="flex items-center justify-between">
|
||||
|
@ -16,7 +16,7 @@
|
||||
const addPetname = pubkey => {
|
||||
const [{url}] = getPubkeyWriteRelays(pubkey)
|
||||
|
||||
user.addPetname(pubkey, url, person.name)
|
||||
user.addPetname(pubkey, url, displayPerson(person))
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
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})" />
|
||||
style="background-image: url({person.kind0?.picture})" />
|
||||
<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">
|
||||
@ -49,7 +49,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
<p class="overflow-hidden text-ellipsis">
|
||||
{@html renderContent(ellipsize(person.about || '', 140))}
|
||||
{@html renderContent(ellipsize(person.kind0?.about || '', 140))}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
@ -1,7 +1,6 @@
|
||||
<script>
|
||||
import {pluck} from 'ramda'
|
||||
import {fuzzy} from "src/util/misc"
|
||||
import {isRelay} from "src/util/nostr"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
import RelayCard from "src/partials/RelayCard.svelte"
|
||||
import database from 'src/agent/database'
|
||||
@ -17,7 +16,7 @@
|
||||
const joined = new Set(pluck('url', $relays))
|
||||
|
||||
search = fuzzy(
|
||||
$knownRelays.filter(r => isRelay(r.url) && !joined.has(r.url)),
|
||||
$knownRelays.filter(r => !joined.has(r.url)),
|
||||
{keys: ["name", "description", "url"]}
|
||||
)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
<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({$profile.picture})" />
|
||||
style="background-image: url({$profile.kind0?.picture})" />
|
||||
<span class="text-lg font-bold">{displayPerson($profile)}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -24,11 +24,9 @@
|
||||
[{kinds: [40, 41], ids: [roomId]},
|
||||
{kinds: [42], '#e': [roomId], since: now()}],
|
||||
events => {
|
||||
const newMessages = events.filter(e => e.kind === 42)
|
||||
network.loadPeople(pluck('pubkey', events))
|
||||
|
||||
network.loadPeople(relays, pluck('pubkey', events))
|
||||
|
||||
cb(newMessages)
|
||||
cb(events.filter(e => e.kind === 42))
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -37,9 +35,7 @@
|
||||
const relays = getRelaysForEventChildren($room)
|
||||
const events = await network.load(relays, {kinds: [42], '#e': [roomId], until, limit})
|
||||
|
||||
if (events.length) {
|
||||
await network.loadPeople(relays, pluck('pubkey', events))
|
||||
}
|
||||
network.loadPeople(pluck('pubkey', events))
|
||||
|
||||
return events
|
||||
}
|
||||
|
@ -3,13 +3,17 @@
|
||||
import Anchor from 'src/partials/Anchor.svelte'
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import database from 'src/agent/database'
|
||||
import pool from 'src/agent/pool'
|
||||
|
||||
let confirmed = false
|
||||
|
||||
const confirm = async () => {
|
||||
confirmed = true
|
||||
|
||||
await database.dropAll()
|
||||
await Promise.all([
|
||||
...pool.getConnections().map(c => c.disconnect()),
|
||||
database.dropAll(),
|
||||
])
|
||||
|
||||
localStorage.clear()
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {nip19} from 'nostr-tools'
|
||||
import {sortBy, pluck} from 'ramda'
|
||||
import {personKinds} from 'src/util/nostr'
|
||||
import {personKinds, displayPerson} from 'src/util/nostr'
|
||||
import {now} from 'src/util/misc'
|
||||
import Channel from 'src/partials/Channel.svelte'
|
||||
import user from 'src/agent/user'
|
||||
@ -68,9 +68,9 @@
|
||||
|
||||
<Channel
|
||||
type="dm"
|
||||
name={$person?.name}
|
||||
about={$person?.about}
|
||||
picture={$person?.picture}
|
||||
name={displayPerson($person)}
|
||||
about={$person?.kind0?.about}
|
||||
picture={$person?.kind0?.picture}
|
||||
link={$person ? routes.person($person.pubkey) : null}
|
||||
{loadMessages}
|
||||
{listenForMessages}
|
||||
|
@ -92,7 +92,7 @@
|
||||
}
|
||||
|
||||
const follow = async () => {
|
||||
user.addPetname(pubkey, prop('url', first(relays)), person.name)
|
||||
user.addPetname(pubkey, prop('url', first(relays)), displayPerson(person))
|
||||
}
|
||||
|
||||
const unfollow = async () => {
|
||||
@ -114,13 +114,13 @@
|
||||
background-size: cover;
|
||||
background-image:
|
||||
linear-gradient(to bottom, rgba(0, 0, 0, 0.3), #0f0f0e),
|
||||
url('{person.banner}')" />
|
||||
url('{person.kind0?.banner}')" />
|
||||
|
||||
<Content>
|
||||
<div class="flex gap-4" in:fly={{y: 20}}>
|
||||
<div
|
||||
class="overflow-hidden w-16 h-16 sm:w-32 sm:h-32 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||
style="background-image: url({person.picture})" />
|
||||
style="background-image: url({person.kind0?.picture})" />
|
||||
<div class="flex flex-col gap-4 flex-grow">
|
||||
<div class="flex justify-between items-center gap-4">
|
||||
<div class="flex-grow flex flex-col gap-2">
|
||||
@ -159,7 +159,7 @@
|
||||
</Anchor>
|
||||
</div>
|
||||
</div>
|
||||
<p>{@html renderContent(person.about || '')}</p>
|
||||
<p>{@html renderContent(person?.kind0?.about || '')}</p>
|
||||
{#if person?.petnames}
|
||||
<div class="flex gap-8" in:fly={{y: 20}}>
|
||||
<button on:click={showFollows}>
|
||||
@ -176,7 +176,7 @@
|
||||
<Tabs tabs={['notes', 'likes', 'relays']} {activeTab} {setActiveTab} />
|
||||
|
||||
{#if activeTab === 'notes'}
|
||||
<Notes {pubkey} {relays} />
|
||||
<Notes {pubkey} />
|
||||
{:else if activeTab === 'likes'}
|
||||
<Likes {pubkey} />
|
||||
{:else if activeTab === 'relays'}
|
||||
|
@ -2,9 +2,7 @@
|
||||
import {onMount} from "svelte"
|
||||
import {fly} from 'svelte/transition'
|
||||
import {navigate} from "svelte-routing"
|
||||
import pick from "ramda/src/pick"
|
||||
import {error} from "src/util/logger"
|
||||
import {stripExifData} from "src/util/html"
|
||||
// import {stripExifData} from "src/util/html"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
import Textarea from "src/partials/Textarea.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
@ -16,7 +14,7 @@
|
||||
import cmd from "src/agent/cmd"
|
||||
import {routes, toast} from "src/app/ui"
|
||||
|
||||
let values = {picture: null, about: null, name: null, nip05: null}
|
||||
let values = user.getProfile().kind0 || {}
|
||||
|
||||
const nip05Url = "https://github.com/nostr-protocol/nips/blob/master/05.md"
|
||||
const pseudUrl = "https://www.coindesk.com/markets/2020/06/29/many-bitcoin-developers-are-choosing-to-use-pseudonyms-for-good-reason/"
|
||||
@ -26,8 +24,7 @@
|
||||
return navigate("/login")
|
||||
}
|
||||
|
||||
values = pick(Object.keys(values), user.getProfile())
|
||||
|
||||
/*
|
||||
document.querySelector('[name=picture]').addEventListener('change', async e => {
|
||||
const target = e.target as HTMLInputElement
|
||||
const [file] = target.files
|
||||
@ -41,6 +38,7 @@
|
||||
values.picture = null
|
||||
}
|
||||
})
|
||||
*/
|
||||
})
|
||||
|
||||
const submit = async event => {
|
||||
@ -93,10 +91,26 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Profile Image</strong>
|
||||
<input type="file" name="picture" />
|
||||
<strong>Profile Picture</strong>
|
||||
<!-- <input type="file" name="picture" />
|
||||
<p class="text-sm text-light">
|
||||
Your profile image will have all metadata removed before being published.
|
||||
</p> -->
|
||||
<Input type="text" name="picture" wrapperClass="flex-grow" bind:value={values.picture}>
|
||||
<i slot="before" class="fa fa-image-portrait" />
|
||||
</Input>
|
||||
<p class="text-sm text-light">
|
||||
Enter a url to your profile image - please be mindful of others and
|
||||
only use small images.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Profile Banner</strong>
|
||||
<Input type="text" name="banner" wrapperClass="flex-grow" bind:value={values.banner}>
|
||||
<i slot="before" class="fa fa-panorama" />
|
||||
</Input>
|
||||
<p class="text-sm text-light">
|
||||
Enter a url to your profile's banner image.
|
||||
</p>
|
||||
</div>
|
||||
<Button type="submit" class="text-center">Done</Button>
|
||||
|
@ -55,15 +55,15 @@ export const findRoot = e =>
|
||||
export const findRootId = e => Tags.wrap([findRoot(e)]).values().first()
|
||||
|
||||
export const displayPerson = p => {
|
||||
if (p.display_name) {
|
||||
return ellipsize(p.display_name, 60)
|
||||
if (p.kind0?.display_name) {
|
||||
return ellipsize(p.kind0?.display_name, 60)
|
||||
}
|
||||
|
||||
if (p.name) {
|
||||
return ellipsize(p.name, 60)
|
||||
if (p.kind0?.name) {
|
||||
return ellipsize(p.kind0?.name, 60)
|
||||
}
|
||||
|
||||
return nip19.npubEncode(p.pubkey).slice(4, 12)
|
||||
return nip19.npubEncode(p.pubkey).slice(-8)
|
||||
}
|
||||
|
||||
export const isLike = content => ['', '+', '🤙', '👍', '❤️'].includes(content)
|
||||
@ -82,7 +82,17 @@ export const isAlert = (e, pubkey) => {
|
||||
return true
|
||||
}
|
||||
|
||||
export const isRelay = url => typeof url === 'string' && url.match(/^wss?:\/\/.+/)
|
||||
export const isRelay = url => (
|
||||
typeof url === 'string'
|
||||
// It should have the protocol included
|
||||
&& url.match(/^wss?:\/\/.+/)
|
||||
// Don't match stuff with a port number
|
||||
&& !url.slice(6).match(/:\d+/)
|
||||
// Don't match raw ip addresses
|
||||
&& !url.slice(6).match(/\d+\.\d+\.\d+\.\d+/)
|
||||
// Skip nostr.wine's virtual relays
|
||||
&& !url.slice(6).match(/\/npub/)
|
||||
)
|
||||
|
||||
export const normalizeRelayUrl = url => url.replace(/\/+$/, '')
|
||||
|
||||
|
@ -9,10 +9,15 @@ export type Relay = {
|
||||
|
||||
export type Person = {
|
||||
pubkey: string
|
||||
picture?: string
|
||||
relays?: Array<Relay>
|
||||
muffle?: Array<Array<string>>
|
||||
petnames?: Array<Array<string>>
|
||||
kind0?: {
|
||||
name?: string
|
||||
about?: string
|
||||
nip05?: string
|
||||
picture?: string
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
import {last, reject, pluck, propEq} from 'ramda'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {fuzzy} from "src/util/misc"
|
||||
import {isRelay} from "src/util/nostr"
|
||||
import {isRelay, displayPerson} from "src/util/nostr"
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import Compose from "src/partials/Compose.svelte"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
@ -75,10 +75,8 @@
|
||||
onMount(() => {
|
||||
const person = database.people.get(pubkey)
|
||||
|
||||
if (person?.name) {
|
||||
input.type('@' + person.name)
|
||||
input.trigger({key: 'Enter'})
|
||||
}
|
||||
input.type('@' + displayPerson(person))
|
||||
input.trigger({key: 'Enter'})
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -1,16 +1,14 @@
|
||||
<script type="ts">
|
||||
import Content from 'src/partials/Content.svelte'
|
||||
import PersonInfo from 'src/partials/PersonInfo.svelte'
|
||||
import {getAllPubkeyWriteRelays} from 'src/agent/relays'
|
||||
import database from 'src/agent/database'
|
||||
import network from 'src/agent/network'
|
||||
|
||||
export let pubkeys
|
||||
|
||||
const relays = getAllPubkeyWriteRelays(pubkeys)
|
||||
const people = database.watch('people', people => people.all({pubkey: pubkeys}))
|
||||
|
||||
network.loadPeople(relays, pubkeys)
|
||||
network.loadPeople(pubkeys)
|
||||
</script>
|
||||
|
||||
<Content gap={2}>
|
||||
|
Loading…
Reference in New Issue
Block a user