Rename user > person

This commit is contained in:
Jonathan Staab 2022-12-17 13:22:09 -08:00
parent 447c112d21
commit 1d4b4a73d8
20 changed files with 137 additions and 135 deletions

View File

@ -40,7 +40,6 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
- [ ] Check firefox - in dev it won't work, but it should in production
- [ ] Re-implement muffle
- [ ] Rename users/accounts to people
- [ ] Move relays to db
- [ ] Make user a livequery instead of a store
- [ ] Figure out if multiple relays congest response times because we wait for all eose

View File

@ -16,6 +16,7 @@
import relay from 'src/relay'
import Anchor from 'src/partials/Anchor.svelte'
import NoteDetail from "src/views/NoteDetail.svelte"
import PersonSettings from "src/views/PersonSettings.svelte"
import NotFound from "src/routes/NotFound.svelte"
import Search from "src/routes/Search.svelte"
import Alerts from "src/routes/Alerts.svelte"
@ -26,8 +27,7 @@
import Keys from "src/routes/Keys.svelte"
import RelayList from "src/routes/RelayList.svelte"
import AddRelay from "src/routes/AddRelay.svelte"
import UserDetail from "src/routes/UserDetail.svelte"
import UserAdvanced from "src/routes/UserAdvanced.svelte"
import Person from "src/routes/Person.svelte"
import NoteCreate from "src/routes/NoteCreate.svelte"
export let url = ""
@ -97,9 +97,9 @@
<Route path="/search/:type" component={Search} />
<Route path="/notes/:activeTab" component={Notes} />
<Route path="/notes/new" component={NoteCreate} />
<Route path="/users/:pubkey/:activeTab" let:params>
<Route path="/people/:pubkey/:activeTab" let:params>
{#key params.pubkey}
<UserDetail {...params} />
<Person {...params} />
{/key}
</Route>
<Route path="/keys" component={Keys} />
@ -117,7 +117,7 @@
>
{#if $user}
<li>
<a href={`/users/${$user.pubkey}/notes`} class="flex gap-2 px-4 py-2 pb-6 items-center">
<a href={`/people/${$user.pubkey}/notes`} class="flex gap-2 px-4 py-2 pb-6 items-center">
<div
class="overflow-hidden w-6 h-6 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
style="background-image: url({$user.picture})" />
@ -201,8 +201,8 @@
{/key}
{:else if $modal.form === 'relay'}
<AddRelay />
{:else if $modal.form === 'user/advanced'}
<UserAdvanced />
{:else if $modal.form === 'person/settings'}
<PersonSettings />
{/if}
</dialog>
</div>

10
src/partials/Badge.svelte Normal file
View File

@ -0,0 +1,10 @@
<script>
export let person
</script>
<a href={`/people/${person.pubkey}/notes`} class="flex gap-2 items-center relative z-10">
<div
class="overflow-hidden w-4 h-4 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
style="background-image: url({person.picture})" />
<span class="text-lg font-bold">{person.name || person.pubkey.slice(0, 8)}</span>
</a>

View File

@ -1,10 +0,0 @@
<script>
export let user
</script>
<a href={`/users/${user.pubkey}/notes`} class="flex gap-2 items-center relative z-10">
<div
class="overflow-hidden w-4 h-4 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
style="background-image: url({user.picture})" />
<span class="text-lg font-bold">{user.name || user.pubkey.slice(0, 8)}</span>
</a>

View File

@ -8,7 +8,7 @@ export const db = new Dexie('coracle/relay')
db.version(3).stores({
events: '++id, pubkey, created_at, kind, content, reply, root',
users: '++pubkey, name, about',
people: '++pubkey, name, about',
tags: '++key, event, value',
})
@ -18,7 +18,7 @@ window.db = db
db.events.process = async events => {
// Only persist ones we care about, the rest can be
// ephemeral and used to update users etc
// ephemeral and used to update people etc
const eventsByKind = groupBy(prop('kind'), ensurePlural(events))
const notesAndReactions = flatten(Object.values(pick([1, 7], eventsByKind)))
const profileUpdates = flatten(Object.values(pick([0, 3, 12165], eventsByKind)))
@ -56,16 +56,16 @@ db.events.process = async events => {
db.tags.where('event').anyOf(eventIds).delete()
}
// Update our users
// Update our people
for (const event of profileUpdates) {
const {pubkey, kind, content, tags} = event
const user = await db.users.where('pubkey').equals(pubkey).first()
const putUser = data => db.users.put({...user, ...data, pubkey, updated_at: now()})
const person = await db.people.where('pubkey').equals(pubkey).first()
const putPerson = data => db.people.put({...person, ...data, pubkey, updated_at: now()})
await switcherFn(kind, {
0: () => putUser(JSON.parse(content)),
3: () => putUser({petnames: tags}),
12165: () => putUser({muffle: tags}),
0: () => putPerson(JSON.parse(content)),
3: () => putPerson({petnames: tags}),
12165: () => putPerson({muffle: tags}),
default: () => {
console.log(`Received unsupported event type ${event.kind}`)
},

View File

@ -17,25 +17,35 @@ const lq = f => liveQuery(async () => {
})
const ensurePerson = async ({pubkey}) => {
const user = await db.users.where('pubkey').equals(pubkey).first()
const person = await db.people.where('pubkey').equals(pubkey).first()
// Throttle updates for users
if (!user || user.updated_at < now() - timedelta(1, 'hours')) {
await pool.syncUserInfo({pubkey, ...user})
// Throttle updates for people
if (!person || person.updated_at < now() - timedelta(1, 'hours')) {
await pool.syncPersonInfo({pubkey, ...person})
}
}
const ensureContext = async events => {
const ids = events.flatMap(e => filterTags({tag: "e"}, e).concat(e.id))
const promises = []
const people = uniq(pluck('pubkey', events)).map(objOf('pubkey'))
const ids = events.flatMap(e => filterTags({tag: "e"}, e).concat(e.id))
await Promise.all([
people.map(ensurePerson),
pool.fetchEvents([
{kinds: [1, 5, 7], '#e': ids},
{kinds: [1, 5], ids},
]),
])
if (people.length > 0) {
for (const p of people.map(ensurePerson)) {
promises.push(p)
}
}
if (ids.length > 0) {
promises.push(
pool.fetchEvents([
{kinds: [1, 5, 7], '#e': ids},
{kinds: [1, 5], ids},
])
)
}
await Promise.all(promises)
}
const prefilterEvents = filter => {
@ -111,15 +121,15 @@ const findNote = async (id, giveUp = false) => {
return findNote(id, true)
}
const [replies, reactions, user, html] = await Promise.all([
const [replies, reactions, person, html] = await Promise.all([
children.clone().filter(e => e.kind === 1).toArray(),
children.clone().filter(e => e.kind === 7).toArray(),
db.users.get(note.pubkey),
db.people.get(note.pubkey),
renderNote(note, {showEntire: false}),
])
return {
...note, reactions, user, html,
...note, reactions, person, html,
replies: await Promise.all(replies.map(r => findNote(r.id))),
}
}
@ -127,8 +137,8 @@ const findNote = async (id, giveUp = false) => {
const renderNote = async (note, {showEntire = false}) => {
const shouldEllipsize = note.content.length > 500 && !showEntire
const content = shouldEllipsize ? ellipsize(note.content, 500) : note.content
const accounts = await db.users.where('pubkey').anyOf(filterTags({tag: "p"}, note)).toArray()
const accountsByPubkey = createMap('pubkey', accounts)
const people = await db.people.where('pubkey').anyOf(filterTags({tag: "p"}, note)).toArray()
const peopleByPubkey = createMap('pubkey', people)
return escapeHtml(content)
.replace(/\n/g, '<br />')
@ -141,15 +151,15 @@ const renderNote = async (note, {showEntire = false}) => {
}
const pubkey = note.tags[parseInt(i)][1]
const user = accountsByPubkey[pubkey]
const name = user?.name || pubkey.slice(0, 8)
const person = peopleByPubkey[pubkey]
const name = person?.name || pubkey.slice(0, 8)
return `@<a href="/users/${pubkey}/notes" class="underline">${name}</a>`
return `@<a href="/people/${pubkey}/notes" class="underline">${name}</a>`
})
}
const filterAlerts = async (user, filter) => {
const tags = db.tags.where('value').equals(user.pubkey)
const filterAlerts = async (person, filter) => {
const tags = db.tags.where('value').equals(person.pubkey)
const ids = pluck('event', await tags.toArray())
const events = await filterEvents({...filter, kinds: [1, 7], ids})

View File

@ -118,21 +118,19 @@ const loadEvents = async filter => {
return events
}
const syncUserInfo = async user => {
const syncPersonInfo = async person => {
const [events] = await Promise.all([
// Get profile info events
req({kinds: [0, 3, 12165], authors: [user.pubkey]}),
req({kinds: [0, 3, 12165], authors: [person.pubkey]}),
// Make sure we have something in the database
db.users.put({muffle: [], petnames: [], updated_at: 0, ...user}),
db.people.put({muffle: [], petnames: [], updated_at: 0, ...person}),
])
// Process the events to flesh out the user
// Process the events to flesh out the person
await db.events.process(events)
// Return our user for convenience
const person = await db.users.where('pubkey').equals(user.pubkey).first()
return person
// Return our person for convenience
return await db.people.where('pubkey').equals(person.pubkey).first()
}
const fetchEvents = async filter => {
@ -142,15 +140,15 @@ const fetchEvents = async filter => {
let syncSub = null
let syncChan = new Channel('sync')
const sync = async user => {
const sync = async person => {
if (syncSub) {
(await syncSub).unsub()
}
if (!user) return
if (!person) return
// Get user info right away
const {petnames, pubkey} = await syncUserInfo(user)
// Get person info right away
const {petnames, pubkey} = await syncPersonInfo(person)
// Don't grab nothing, but don't grab everything either
const since = Math.max(
@ -163,7 +161,7 @@ const sync = async user => {
setLocalJson('pool/lastSync', now())
// Populate recent activity in network so the user has something to look at right away
// Populate recent activity in network so the person has something to look at right away
syncSub = syncChan.sub(
[{since, authors: getTagValues(petnames).concat(pubkey)},
{since, '#p': [pubkey]}],
@ -173,5 +171,5 @@ const sync = async user => {
export default {
getPubkey, addRelay, removeRelay, setPrivateKey, setPublicKey,
publishEvent, loadEvents, syncUserInfo, fetchEvents, sync,
publishEvent, loadEvents, syncPersonInfo, fetchEvents, sync,
}

View File

@ -5,7 +5,7 @@
import {ellipsize} from 'hurdak/src/core'
import relay from 'src/relay'
import {alerts, modal, user} from 'src/state/app'
import UserBadge from "src/partials/UserBadge.svelte"
import Badge from "src/partials/Badge.svelte"
import Note from 'src/views/Note.svelte'
const events = relay.lq(async () => {
@ -32,7 +32,7 @@
border border-solid border-black hover:border-medium hover:bg-dark"
on:click={() => modal.set({note: e.parent})}>
<div class="flex gap-2 items-center">
<UserBadge user={e.user} />
<Badge person={e.person} />
<span>liked your note.</span>
</div>
<div class="ml-6 text-light">

View File

@ -33,7 +33,7 @@
const logIn = async ({privkey, pubkey}) => {
console.log(1)
const person = await dispatch("account/init", pubkey)
const person = await dispatch("user/init", pubkey)
console.log(person)
user.set({...person, pubkey, privkey})

View File

@ -25,7 +25,7 @@
{#if activeTab === 'follows' && authors.length === 0}
<div class="flex w-full justify-center items-center py-16">
<div class="text-center max-w-md">
You haven't yet followed anyone. Visit a user's profile to follow them.
You haven't yet followed anyone. Visit a person's profile to follow them.
</div>
</div>
{:else if activeTab === 'follows'}

View File

@ -8,7 +8,7 @@
import Notes from "src/views/Notes.svelte"
import Likes from "src/views/Likes.svelte"
import {t, dispatch} from 'src/state/dispatch'
import {modal, user as currentUser} from "src/state/app"
import {modal, user} from "src/state/app"
import relay from 'src/relay'
export let pubkey
@ -16,32 +16,32 @@
relay.ensurePerson({pubkey})
const user = relay.lq(() => relay.db.users.get(pubkey))
const person = relay.lq(() => relay.db.people.get(pubkey))
let following = $currentUser && find(t => t[1] === pubkey, $currentUser.petnames)
let following = $user && find(t => t[1] === pubkey, $user.petnames)
const setActiveTab = tab => navigate(`/users/${pubkey}/${tab}`)
const setActiveTab = tab => navigate(`/people/${pubkey}/${tab}`)
const follow = () => {
const petnames = $currentUser.petnames
.concat([t("p", pubkey, $user?.name)])
const petnames = $user.petnames
.concat([t("p", pubkey, $person?.name)])
dispatch('account/petnames', petnames)
dispatch('user/petnames', petnames)
following = true
}
const unfollow = () => {
const petnames = $currentUser.petnames
const petnames = $user.petnames
.filter(([_, pubkey]) => pubkey !== pubkey)
dispatch('account/petnames', petnames)
dispatch('user/petnames', petnames)
following = false
}
const openAdvanced = () => {
modal.set({form: 'user/advanced', user: $user || {pubkey}})
modal.set({form: 'person/settings', person: $person || {pubkey}})
}
</script>
@ -50,18 +50,18 @@
<div class="flex gap-4">
<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({$user?.picture})" />
style="background-image: url({$person?.picture})" />
<div class="flex-grow">
<div class="flex items-center gap-2">
<h1 class="text-2xl">{$user?.name || pubkey.slice(0, 8)}</h1>
{#if $currentUser && $currentUser.pubkey !== pubkey}
<h1 class="text-2xl">{$person?.name || pubkey.slice(0, 8)}</h1>
{#if $user && $user.pubkey !== pubkey}
<i class="fa-solid fa-sliders cursor-pointer" on:click={openAdvanced} />
{/if}
</div>
<p>{$user?.about || ''}</p>
<p>{$person?.about || ''}</p>
</div>
<div class="whitespace-nowrap">
{#if $currentUser?.pubkey === pubkey}
{#if $user?.pubkey === pubkey}
<a href="/profile" class="cursor-pointer text-sm">
<i class="fa-solid fa-edit" /> Edit
</a>
@ -85,11 +85,11 @@
{:else if activeTab === 'likes'}
<Likes author={pubkey} />
{:else if activeTab === 'network'}
{#if $user}
<Notes shouldMuffle filter={{kinds: [1], authors: $user.petnames.map(t => t[1])}} />
{#if $person}
<Notes shouldMuffle filter={{kinds: [1], authors: $person.petnames.map(t => t[1])}} />
{:else}
<div class="py-16 max-w-xl m-auto flex justify-center">
Unable to show network for this user.
Unable to show network for this person.
</div>
{/if}
{/if}

View File

@ -38,9 +38,9 @@
const submit = async event => {
event.preventDefault()
await dispatch("account/update", values)
await dispatch("user/update", values)
navigate(`/users/${$user.pubkey}/profile`)
navigate(`/people/${$user.pubkey}/profile`)
toast.show("info", "Your profile has been updated!")
}

View File

@ -24,7 +24,7 @@ const userLq = relay.lq(() => {
const $user = get(user)
if ($user) {
return relay.db.users.where('pubkey').equals($user?.pubkey).first()
return relay.db.people.where('pubkey').equals($user?.pubkey).first()
}
})

View File

@ -11,19 +11,19 @@ import {relays} from 'src/state/app'
export const dispatch = defmulti("dispatch", identity)
dispatch.addMethod("account/init", (topic, pubkey) => {
dispatch.addMethod("user/init", (topic, pubkey) => {
return relay.pool.syncUserInfo({pubkey})
})
dispatch.addMethod("account/update", async (topic, updates) => {
dispatch.addMethod("user/update", async (topic, updates) => {
await relay.pool.publishEvent(makeEvent(0, JSON.stringify(updates)))
})
dispatch.addMethod("account/petnames", async (topic, petnames) => {
dispatch.addMethod("user/petnames", async (topic, petnames) => {
await relay.pool.publishEvent(makeEvent(3, '', petnames))
})
dispatch.addMethod("account/muffle", async (topic, muffle) => {
dispatch.addMethod("user/muffle", async (topic, muffle) => {
await relay.pool.publishEvent(makeEvent(12165, '', muffle))
})

View File

@ -60,7 +60,7 @@ export const createScroller = loadMore => {
const {scrollY, innerHeight} = window
const {scrollHeight} = document.body
if (scrollY + innerHeight + 600 > scrollHeight) {
if (scrollY + innerHeight + 2000 > scrollHeight) {
await loadMore()
}

View File

@ -47,12 +47,12 @@ export const filterMatches = (filter, e) => {
))
}
export const getMuffleValue = user => {
if (!user) {
export const getMuffleValue = person => {
if (!person) {
return 1
}
const tag = find(t => t[1] === user.pubkey, user.muffle)
const tag = find(t => t[1] === person.pubkey, person.muffle)
if (!tag) {
return 1

View File

@ -10,7 +10,7 @@
import {dispatch} from "src/state/dispatch"
import {settings, user, modal} from "src/state/app"
import {formatTimestamp} from 'src/util/misc'
import UserBadge from "src/partials/UserBadge.svelte"
import Badge from "src/partials/Badge.svelte"
import Card from "src/partials/Card.svelte"
export let note
@ -94,7 +94,7 @@
<Card on:click={onClick} {interactive} {invertColors}>
<div class="flex gap-4 items-center justify-between">
<UserBadge user={{...note.user, pubkey: note.pubkey}} />
<Badge person={{...note.person, pubkey: note.pubkey}} />
<p class="text-sm text-light">{formatTimestamp(note.created_at)}</p>
</div>
<div class="ml-6 flex flex-col gap-2">

View File

@ -16,49 +16,44 @@
let since = now() - delta, until = now(), notes
const done = createScroller(async () => {
const unsub = createScroller(async () => {
since -= delta
until -= delta
// Load our events, but don't wait for them because we probably have them in dexie
await relay.ensureContext(
await relay.pool.loadEvents({...filter, since, until})
)
createNotesObservable()
const notes = await relay.filterEvents({...filter, since}).reverse().sortBy('created_at')
const ancestorIds = concat(notes.map(findRoot), notes.map(findReply)).filter(identity)
const ancestors = await relay.filterEvents({kinds: [1], ids: ancestorIds}).toArray()
const allNotes = uniqBy(prop('id'), notes.concat(ancestors))
const notesById = createMap('id', allNotes)
const notesByRoot = groupBy(
n => {
const rootId = findRoot(n)
const parentId = findReply(n)
// Actually dereference the notes in case we weren't able to retrieve them
if (notesById[rootId]) {
return rootId
}
if (notesById[parentId]) {
return parentId
}
return n.id
},
allNotes
)
return await Promise.all(Object.keys(notesByRoot).map(relay.findNote))
})
const createNotesObservable = () => {
notes = relay.lq(async () => {
const notes = await relay.filterEvents({...filter, since}).reverse().sortBy('created_at')
const ancestorIds = concat(notes.map(findRoot), notes.map(findReply)).filter(identity)
const ancestors = await relay.filterEvents({kinds: [1], ids: ancestorIds}).toArray()
const allNotes = uniqBy(prop('id'), notes.concat(ancestors))
const notesById = createMap('id', allNotes)
const notesByRoot = groupBy(
n => {
const rootId = findRoot(n)
const parentId = findReply(n)
// Actually dereference the notes in case we weren't able to retrieve them
if (notesById[rootId]) {
return rootId
}
if (notesById[parentId]) {
return parentId
}
return n.id
},
allNotes
)
return await Promise.all(Object.keys(notesByRoot).map(relay.findNote))
})
}
onDestroy(done)
onDestroy(unsub)
</script>
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">

View File

@ -25,7 +25,7 @@
.concat([t("p", $modal.user.pubkey, muffleValue.toString())])
.filter(x => last(x) !== "1")
dispatch('account/muffle', muffle)
dispatch('user/muffle', muffle)
modal.set(null)
}
@ -35,7 +35,7 @@
<div class="flex flex-col gap-2">
<h1 class="text-3xl">Advanced Follow</h1>
<p>
Fine grained controls for interacting with other users.
Fine grained controls for interacting with other people.
</p>
</div>
<div class="flex flex-col gap-1">

View File

@ -9,7 +9,7 @@
let results = []
const search = relay.lq(async () => {
return fuzzy(await relay.db.users.toArray(), {keys: ["name", "about", "pubkey"]})
return fuzzy(await relay.db.people.toArray(), {keys: ["name", "about", "pubkey"]})
})
$: {
@ -27,7 +27,7 @@
{#each results as e (e.pubkey)}
{#if e.pubkey !== $user.pubkey}
<li in:fly={{y: 20}}>
<a href="/users/{e.pubkey}/notes" class="flex gap-4 my-4">
<a href="/people/{e.pubkey}/notes" class="flex gap-4 my-4">
<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({e.picture})" />