Fix nip04 messages not having a members field, Improve person search

This commit is contained in:
Jon Staab 2024-01-29 12:26:56 -08:00
parent 6c902b7e8f
commit 04b3772231
9 changed files with 77 additions and 32 deletions

View File

@ -26,6 +26,8 @@
- [x] Fix initial message when opening conversation from a listing
- [x] Support frequency value in `price` tag for nip 99
- [x] Show all replies exands notes inline rather than opening the note
- [x] Fix nip04 messages not having a members field
- [x] Improve person search
# 0.4.0

View File

@ -73,7 +73,8 @@
let collapsed = depth === 0
let ctx = uniqBy(prop("id"), context)
let showEntire = anchor === getIdOrAddress(event)
let interactive = !anchor || !showEntire
const interactive = !anchor || !showEntire
const onClick = e => {
const target = (e.detail?.target || e.target) as HTMLElement

View File

@ -56,7 +56,7 @@
uniq(Tags.from(parent).type("p").values().all().concat(parent.pubkey)),
)
setTimeout(() => compose.write(draft))
setTimeout(() => compose.write(draft), 10)
}
const bypassNsecWarning = () => {

View File

@ -34,6 +34,9 @@ projections.addHandler(30078, async e => {
const channel = $channels.get(id)
$channels.set(id, {
relays: [],
members: [],
messages: [],
...channel,
last_checked: Math.max(ts, channel?.last_checked || 0),
})
@ -55,12 +58,7 @@ const handleMessage = async e => {
continue
}
const $channel =
channels.key(channelId).get() ||
({
id: channelId,
members: pubkeys,
} as Channel)
const $channel = channels.key(channelId).get()
const relays = $channel?.relays || []
const messages = $channel?.messages || []
@ -90,10 +88,12 @@ const handleMessage = async e => {
e = {...e, content: await nip04.decryptAsUser(e.content, other)}
}
const updates = {
const updates: Channel = {
...$channel,
id: channelId,
relays: uniq([...tags.relays().all(), ...relays]),
messages: uniqBy(prop("id"), [e, ...messages]),
members: pubkeys,
}
if (e.pubkey === pubkey) {

View File

@ -165,6 +165,7 @@ export class IndexedDBAdapter {
readonly max: number,
readonly sort?: (xs: any[]) => any[],
readonly filter?: (x: any) => boolean,
readonly migrate?: (xs: any[]) => any[],
) {}
getIndexedDBConfig() {
@ -177,10 +178,12 @@ export class IndexedDBAdapter {
}
async initialize(storage: Storage) {
const {key, store, filter} = this
const {key, store} = this
const data = await storage.db.getAll(key)
const filter = this.filter || identity
const migrate = this.migrate || identity
store.set(data.filter(filter || identity))
store.set(migrate(data.filter(filter)))
store.subscribe(
throttle(randomInt(3000, 5000), async <T>(rows: T) => {

View File

@ -1,4 +1,5 @@
import {prop, sortBy} from "ramda"
import {Tags} from 'paravel'
import {prop, uniq, sortBy} from "ramda"
import {Storage, LocalStorageAdapter, IndexedDBAdapter, sortByPubkeyWhitelist} from "./core"
import {_lists} from "./lists"
import {people} from "./people"
@ -34,6 +35,15 @@ const setAdapter = {
load: a => new Set(a || []),
}
// Nip 04 channels weren't getting members set
const migrateChannels = channels => {
return channels.map(c => {
c.members = uniq(c.members.concat(c.messages?.flatMap(m => Tags.from(m).pubkeys().all().concat(m.pubkey))))
return c
})
}
export const storage = new Storage(9, [
new LocalStorageAdapter("pubkey", pubkey),
new LocalStorageAdapter("sessions", sessions),
@ -51,7 +61,7 @@ export const storage = new Storage(9, [
),
new IndexedDBAdapter("people", people, 5000, sortByPubkeyWhitelist(prop("last_fetched"))),
new IndexedDBAdapter("relays", relays, 1000, sortBy(prop("count"))),
new IndexedDBAdapter("channels", channels, 1000, sortBy(prop("last_checked"))),
new IndexedDBAdapter("channels", channels, 1000, sortBy(prop("last_checked")), null, migrateChannels),
new IndexedDBAdapter("groups", groups, 1000, sortBy(prop("count"))),
new IndexedDBAdapter("groupAlerts", groupAlerts, 30, sortBy(prop("created_at"))),
new IndexedDBAdapter("groupRequests", groupRequests, 100, sortBy(prop("created_at"))),

View File

@ -1,12 +1,15 @@
import {defaultTo, filter} from "ramda"
import Fuse from "fuse.js"
import {doPipe} from "hurdak"
import {defaultTo, map, filter, sortBy} from "ramda"
import {derived} from "src/engine/core/utils"
import {pubkey} from "src/engine/session/state"
import {user} from "src/engine/session/derived"
import type {Person} from "./model"
import {people} from "./state"
import {personHasName, getPeopleSearch, getMutes, getFollows, getNetwork} from "./utils"
import {personHasName, getFollowsWhoFollow, primeWotCaches, getWotScore, getMutes, getFollows, getNetwork} from "./utils"
export const peopleWithName = people.derived(filter(personHasName))
export const searchPeople = peopleWithName.derived(getPeopleSearch)
export const derivePerson = pubkey => people.key(pubkey).derived(defaultTo({pubkey}))
export const mutes = user.derived(getMutes)
@ -18,3 +21,35 @@ export const network = user.derived(getNetwork)
export const deriveMuted = (value: string) => mutes.derived(s => s.has(value))
export const deriveFollowing = (pubkey: string) => follows.derived(s => s.has(pubkey))
export const searchPeople = derived(
[pubkey, peopleWithName.throttle(300)],
([$pubkey, $peopleWithName]: [string, Person[]]): ((term: string) => Person[]) => {
primeWotCaches($pubkey)
const options = $peopleWithName.map(p => ({person: p, score: getWotScore($pubkey, p.pubkey)}))
const fuse = new Fuse(options, {
keys: [
"person.profile.name",
"person.profile.display_name",
{name: "person.profile.nip05", weight: 0.5},
{name: "person.profile.about", weight: 0.1},
],
threshold: 0.3,
shouldSort: false,
includeScore: true,
})
return (term: string) => {
if (!term) {
return sortBy(item => -item.score, options).map(item => item.person)
}
return doPipe(fuse.search(term), [
sortBy((r: any) => (r.score - 1) * Math.sqrt(Math.max(0, r.item.score))),
map((r: any) => r.item.person),
])
}
},
)

View File

@ -1,8 +1,9 @@
import {nip19} from "nostr-tools"
import {throttle} from "throttle-debounce"
import {fromNostrURI, cached} from "paravel"
import {uniq, join, nth, last} from "ramda"
import {Fetch, tryFunc, createMapOf, ellipsize, switcherFn} from "hurdak"
import {fuzzy, createBatcher, pushToKey} from "src/util/misc"
import {createBatcher, pushToKey} from "src/util/misc"
import {dufflepud} from "src/engine/session/utils"
import {getPubkeyHints} from "src/engine/relays/utils"
import type {Person, Handle} from "./model"
@ -58,17 +59,6 @@ export const displayHandle = (handle: Handle) =>
export const displayPubkey = (pubkey: string) => displayPerson(getPersonWithDefault(pubkey))
export const getPeopleSearch = $people =>
fuzzy($people, {
keys: [
"profile.name",
"profile.display_name",
{name: "profile.nip05", weight: 0.5},
{name: "about", weight: 0.1},
],
threshold: 0.3,
})
export const getMutedPubkeys = $person => ($person?.mutes || []).map(nth(1)) as string[]
export const getMutes = $person => new Set(getMutedPubkeys($person))
@ -116,7 +106,7 @@ export const getFollowsWhoMute = cached({
getFollowedPubkeys(people.key(pk).get()).filter(pk => isMuting(people.key(pk).get(), tpk)),
})
export const primeWotCaches = pk => {
export const primeWotCaches = throttle(3000, pk => {
const mutes = {}
const follows = {}
@ -153,7 +143,7 @@ export const primeWotCaches = pk => {
getFollowsWhoFollow.cache.set(getFollowsWhoFollow.getKey([pk, person.pubkey]), [])
}
}
}
})
export const getWotScore = (pk, tpk) => {
if (!people.key(pk).exists()) {

View File

@ -19,11 +19,15 @@
let input, suggestions, popover
let focused = Boolean(autofocus)
$: suggestions?.setData(term ? search(term).slice(0, 10) : defaultOptions)
$: {
suggestions?.setData(term ? search(term).slice(0, 10) : defaultOptions)
}
$: {
if (!focused || !term) {
popover?.hide()
} else {
popover?.show()
}
if (popover) {