Implement NIP-85 fallback WOT ranking
This commit is contained in:
@@ -14,6 +14,7 @@ VITE_PUSH_SERVER=https://nps.flotilla.social/
|
|||||||
VITE_PUSH_BRIDGE=wss://npb.coracle.social/
|
VITE_PUSH_BRIDGE=wss://npb.coracle.social/
|
||||||
VITE_BLOCKED_RELAYS=brb.io,relay.nostr.band,nostr.mutinywallet.com,feeds.nostr.band,nostr.zbd.gg,wot.utxo.one,blastr.f7z.xyz,relay.current.fyi
|
VITE_BLOCKED_RELAYS=brb.io,relay.nostr.band,nostr.mutinywallet.com,feeds.nostr.band,nostr.zbd.gg,wot.utxo.one,blastr.f7z.xyz,relay.current.fyi
|
||||||
VITE_INDEXER_RELAYS=purplepag.es,relay.damus.io,indexer.coracle.social
|
VITE_INDEXER_RELAYS=purplepag.es,relay.damus.io,indexer.coracle.social
|
||||||
|
VITE_ASSERTION_RELAYS=nip85.brainstorm.world,nip85.nosfabrica.com,nip85.uid.ovh
|
||||||
VITE_DEFAULT_RELAYS=relay.damus.io,relay.primal.net,nostr.mom
|
VITE_DEFAULT_RELAYS=relay.damus.io,relay.primal.net,nostr.mom
|
||||||
VITE_DEFAULT_SEARCH_RELAYS=relay.ditto.pub,antiprimal.net,relay.vertexlab.io
|
VITE_DEFAULT_SEARCH_RELAYS=relay.ditto.pub,antiprimal.net,relay.vertexlab.io
|
||||||
VITE_DEFAULT_MESSAGING_RELAYS=auth.nostr1.com,relay.keychat.io,relay.ditto.pub
|
VITE_DEFAULT_MESSAGING_RELAYS=auth.nostr1.com,relay.keychat.io,relay.ditto.pub
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
VITE_DEFAULT_PUBKEYS=06639a386c9c1014217622ccbcf40908c4f1a0c33e23f8d6d68f4abf655f8f71,266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5,391819e2f2f13b90cac7209419eb574ef7c0d1f4e81867fc24c47a3ce5e8a248,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24,55f04590674f3648f4cdc9dc8ce32da2a282074cd0b020596ee033d12d385185,58c741aa630c2da35a56a77c1d05381908bd10504fdd2d8b43f725efa6d23196,61066504617ee79387021e18c89fb79d1ddbc3e7bff19cf2298f40466f8715e9,6389be6491e7b693e9f368ece88fcd145f07c068d2c1bbae4247b9b5ef439d32,63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed,6e75f7972397ca3295e0f4ca0fbc6eb9cc79be85bafdd56bd378220ca8eee74e,76c71aae3a491f1d9eec47cba17e229cda4113a0bbb6e6ae1776d7643e29cafa,7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240,97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322,b676ded7c768d66a757aa3967b1243d90bf57afb09d1044d3219d8d424e4aea0,dace63b00c42e6e017d00dd190a9328386002ff597b841eb5ef91de4f1ce8491,eeb11961b25442b16389fe6c7ebea9adf0ac36dd596816ea7119e521b8821b9e,fe7f6bc6f7338b76bbf80db402ade65953e20b2f23e66e898204b63cc42539a3
|
||||||
|
VITE_DEFAULT_BLOSSOM_SERVERS=https://blossom.primal.net/
|
||||||
|
VITE_POMADE_SIGNERS=https://pomade.coracle.social,https://pomade.fiatjaf.com,https://pomade.nostrver.se,https://pomade.scuttle.works
|
||||||
|
VITE_PLATFORM_URL=https://app.flotilla.social
|
||||||
|
VITE_PLATFORM_TERMS=https://flotilla.social/terms
|
||||||
|
VITE_PLATFORM_PRIVACY=https://flotilla.social/privacy
|
||||||
|
VITE_PLATFORM_NAME=Flotilla
|
||||||
|
VITE_PLATFORM_LOGO=static/logo.png
|
||||||
|
VITE_PLATFORM_RELAYS=
|
||||||
|
VITE_PLATFORM_ACCENT="#7161FF"
|
||||||
|
VITE_PLATFORM_SECONDARY="#EB5E28"
|
||||||
|
VITE_PLATFORM_DESCRIPTION="Flotilla is nostr - for communities."
|
||||||
|
VITE_PUSH_SERVER=https://nps.flotilla.social/
|
||||||
|
VITE_PUSH_BRIDGE=wss://npb.coracle.social/
|
||||||
|
VITE_BLOCKED_RELAYS=brb.io,relay.nostr.band,nostr.mutinywallet.com,feeds.nostr.band,nostr.zbd.gg,wot.utxo.one,blastr.f7z.xyz,relay.current.fyi
|
||||||
|
VITE_INDEXER_RELAYS=purplepag.es,relay.damus.io,indexer.coracle.social
|
||||||
|
VITE_ASSERTION_RELAYS=nip85.brainstorm.world,nip85.nosfabrica.com,nip85.uid.ovh
|
||||||
|
VITE_DEFAULT_RELAYS=relay.damus.io,relay.primal.net,nostr.mom
|
||||||
|
VITE_DEFAULT_SEARCH_RELAYS=relay.ditto.pub,antiprimal.net,relay.vertexlab.io
|
||||||
|
VITE_DEFAULT_MESSAGING_RELAYS=auth.nostr1.com,relay.keychat.io,relay.ditto.pub
|
||||||
|
VITE_SIGNER_RELAYS=relay.nsec.app,ephemeral.snowflare.cc,bucket.coracle.social
|
||||||
|
VITE_VAPID_PUBLIC_KEY=BIt2D4BdgdbCowD_0d3Np6GbrIGHxd7aIEUeZNe3hQuRlHz02OhzvDaai0XSFoJYVzSzdMjdyW-QhvW9_yq8j4Y
|
||||||
|
VITE_GLITCHTIP_API_KEY=
|
||||||
|
GLITCHTIP_AUTH_TOKEN=
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
# Flotilla
|
# Flotilla
|
||||||
|
|
||||||
A discord-like nostr client based on the idea of "relays as groups".
|
A discord-like nostr client based on the idea of "relays as groups".
|
||||||
@@ -8,11 +9,11 @@ If you would like to be interoperable with Flotilla, please check out this guide
|
|||||||
|
|
||||||
You can also optionally create an `.env.local` file and populate it with the following environment variables (see `.env.template` for examples):
|
You can also optionally create an `.env.local` file and populate it with the following environment variables (see `.env.template` for examples):
|
||||||
|
|
||||||
- `VITE_DEFAULT_PUBKEYS` - A comma-separated list of hex pubkeys for bootstrapping web of trust
|
|
||||||
- `VITE_PLATFORM_URL` - The url where the app will be hosted
|
- `VITE_PLATFORM_URL` - The url where the app will be hosted
|
||||||
- `VITE_PLATFORM_NAME` - The name of the app
|
- `VITE_PLATFORM_NAME` - The name of the app
|
||||||
- `VITE_PLATFORM_LOGO` - A logo url for the app. Can be a local path or https link. Must be a PNG file.
|
- `VITE_PLATFORM_LOGO` - A logo url for the app. Can be a local path or https link. Must be a PNG file.
|
||||||
- `VITE_PLATFORM_RELAYS` - A list of comma-separated relay urls that will make flotilla operate in "platform mode". Disables all space browse/add/select functionality and makes the first platform relay the home page.
|
- `VITE_PLATFORM_RELAYS` - A list of comma-separated relay urls that will make flotilla operate in "platform mode". Disables all space browse/add/select functionality and makes the first platform relay the home page.
|
||||||
|
- `VITE_ASSERTION_RELAYS` - A list of comma-separated relays used to fetch NIP-85 assertion ranks (kind 30382) for fallback trust scores.
|
||||||
- `VITE_PLATFORM_ACCENT` - A hex color for the app's accent color
|
- `VITE_PLATFORM_ACCENT` - A hex color for the app's accent color
|
||||||
- `VITE_PLATFORM_DESCRIPTION` - A description of the app
|
- `VITE_PLATFORM_DESCRIPTION` - A description of the app
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,35 @@
|
|||||||
<style>
|
<style>
|
||||||
.wot-background {
|
.wot-background {
|
||||||
fill: transparent;
|
fill: transparent;
|
||||||
stroke: var(--base-content);
|
stroke: var(--base-300);
|
||||||
opacity: 30%;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wot-highlight {
|
.wot-highlight {
|
||||||
fill: transparent;
|
fill: transparent;
|
||||||
stroke-width: 1.5;
|
stroke-linecap: round;
|
||||||
stroke-dasharray: 100 100;
|
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
|
transition:
|
||||||
|
stroke-dashoffset 160ms ease,
|
||||||
|
stroke 160ms ease,
|
||||||
|
stroke-width 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wot-score {
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: 800;
|
||||||
|
fill: var(--base-content);
|
||||||
|
paint-order: stroke;
|
||||||
|
stroke: var(--base-100);
|
||||||
|
stroke-width: 1.5px;
|
||||||
|
letter-spacing: -0.04em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {onMount} from "svelte"
|
||||||
import {clamp} from "@welshman/lib"
|
import {clamp} from "@welshman/lib"
|
||||||
import {pubkey, getFollows, deriveUserWotScore} from "@welshman/app"
|
import {getPubkeyRank} from "@app/util/wot/getPubkeyRank"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
@@ -24,27 +38,69 @@
|
|||||||
const {pubkey: target}: Props = $props()
|
const {pubkey: target}: Props = $props()
|
||||||
|
|
||||||
const max = 100
|
const max = 100
|
||||||
const radius = 6
|
const size = 22
|
||||||
const center = radius + 1
|
const radius = 8.25
|
||||||
|
const center = size / 2
|
||||||
|
const circumference = 2 * Math.PI * radius
|
||||||
|
|
||||||
const score = deriveUserWotScore(target)
|
let score = $state(50)
|
||||||
const active = $derived(getFollows($pubkey!).includes(target))
|
|
||||||
const normalizedScore = $derived(clamp([0, max], $score) / max)
|
onMount(() => {
|
||||||
const dashOffset = $derived(100 - 44 * normalizedScore)
|
let cancelled = false
|
||||||
const style = $derived(`transform: rotate(${135 - normalizedScore * 180}deg)`)
|
|
||||||
const stroke = $derived(active ? "var(--primary)" : "var(--base-content)")
|
getPubkeyRank(target)
|
||||||
|
.then(rank => {
|
||||||
|
if (!cancelled) {
|
||||||
|
score = rank ?? 50
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!cancelled) {
|
||||||
|
score = 50
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const normalizedScore = $derived(clamp([0, max], score) / max)
|
||||||
|
const dashOffset = $derived(circumference * (1 - normalizedScore))
|
||||||
|
const style = $derived(`transform: rotate(-90deg)`)
|
||||||
|
const strokeWidth = $derived(2.2 + normalizedScore * 1.5)
|
||||||
|
const stroke = $derived(
|
||||||
|
score >= 90
|
||||||
|
? "var(--success)"
|
||||||
|
: score >= 75
|
||||||
|
? "var(--info)"
|
||||||
|
: score >= 50
|
||||||
|
? "var(--warning)"
|
||||||
|
: "var(--error)",
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative h-[14px] w-[14px]">
|
<div class="relative h-[22px] w-[22px] shrink-0">
|
||||||
<svg height="14" width="14" class="absolute">
|
<svg height={size} width={size} class="absolute">
|
||||||
<circle class="wot-background" cx={center} cy={center} r={radius} />
|
<circle class="wot-background" cx={center} cy={center} r={radius} />
|
||||||
<circle
|
<circle
|
||||||
cx={center}
|
cx={center}
|
||||||
cy={center}
|
cy={center}
|
||||||
r={radius}
|
r={radius}
|
||||||
class="wot-highlight"
|
class="wot-highlight"
|
||||||
|
fill="none"
|
||||||
|
stroke-width={strokeWidth}
|
||||||
|
stroke-dasharray={circumference}
|
||||||
stroke-dashoffset={dashOffset}
|
stroke-dashoffset={dashOffset}
|
||||||
{style}
|
{style}
|
||||||
{stroke} />
|
{stroke} />
|
||||||
|
<text
|
||||||
|
x={center}
|
||||||
|
y={center + 0.15}
|
||||||
|
text-anchor="middle"
|
||||||
|
dominant-baseline="middle"
|
||||||
|
class="wot-score">
|
||||||
|
{Math.round(score)}
|
||||||
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
uniq,
|
uniq,
|
||||||
indexBy,
|
indexBy,
|
||||||
partition,
|
partition,
|
||||||
shuffle,
|
|
||||||
parseJson,
|
parseJson,
|
||||||
memoize,
|
memoize,
|
||||||
addToMapKey,
|
addToMapKey,
|
||||||
@@ -139,7 +138,6 @@ import {
|
|||||||
tracker,
|
tracker,
|
||||||
createSearch,
|
createSearch,
|
||||||
userMuteList,
|
userMuteList,
|
||||||
userFollowList,
|
|
||||||
ensurePlaintext,
|
ensurePlaintext,
|
||||||
makeOutboxLoader,
|
makeOutboxLoader,
|
||||||
appContext,
|
appContext,
|
||||||
@@ -205,8 +203,6 @@ export const POMADE_SIGNERS = fromCsv(import.meta.env.VITE_POMADE_SIGNERS)
|
|||||||
|
|
||||||
export const DEFAULT_BLOSSOM_SERVERS = fromCsv(import.meta.env.VITE_DEFAULT_BLOSSOM_SERVERS)
|
export const DEFAULT_BLOSSOM_SERVERS = fromCsv(import.meta.env.VITE_DEFAULT_BLOSSOM_SERVERS)
|
||||||
|
|
||||||
export const DEFAULT_PUBKEYS = import.meta.env.VITE_DEFAULT_PUBKEYS
|
|
||||||
|
|
||||||
export const DUFFLEPUD_URL = "https://dufflepud.onrender.com"
|
export const DUFFLEPUD_URL = "https://dufflepud.onrender.com"
|
||||||
|
|
||||||
export const NIP46_PERMS =
|
export const NIP46_PERMS =
|
||||||
@@ -255,13 +251,6 @@ export const entityLink = (entity: string) => `https://coracle.social/${entity}`
|
|||||||
export const pubkeyLink = (pubkey: string, relays = Router.get().FromPubkeys([pubkey]).getUrls()) =>
|
export const pubkeyLink = (pubkey: string, relays = Router.get().FromPubkeys([pubkey]).getUrls()) =>
|
||||||
entityLink(nip19.nprofileEncode({pubkey, relays}))
|
entityLink(nip19.nprofileEncode({pubkey, relays}))
|
||||||
|
|
||||||
export const bootstrapPubkeys = derived(userFollowList, $userFollowList => {
|
|
||||||
const appPubkeys = DEFAULT_PUBKEYS.split(",")
|
|
||||||
const userPubkeys = shuffle(getPubkeyTagValues(getListTags($userFollowList)))
|
|
||||||
|
|
||||||
return userPubkeys.length > 5 ? userPubkeys : [...userPubkeys, ...appPubkeys]
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deriveEvent = makeDeriveEvent({
|
export const deriveEvent = makeDeriveEvent({
|
||||||
repository,
|
repository,
|
||||||
includeDeleted: true,
|
includeDeleted: true,
|
||||||
|
|||||||
+2
-22
@@ -1,7 +1,7 @@
|
|||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import type {Unsubscriber} from "svelte/store"
|
import type {Unsubscriber} from "svelte/store"
|
||||||
import {derived, get} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import {last, call, ifLet, assoc, chunk, sleep, identity, WEEK, ago} from "@welshman/lib"
|
import {last, call, ifLet, assoc, chunk, identity, WEEK, ago} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
getListTags,
|
getListTags,
|
||||||
getRelayTagValues,
|
getRelayTagValues,
|
||||||
@@ -25,7 +25,6 @@ import {request, requestOne, Difference, DifferenceEvent} from "@welshman/net"
|
|||||||
import {
|
import {
|
||||||
pubkey,
|
pubkey,
|
||||||
loadRelay,
|
loadRelay,
|
||||||
userFollowList,
|
|
||||||
userRelayList,
|
userRelayList,
|
||||||
userMessagingRelayList,
|
userMessagingRelayList,
|
||||||
loadRelayList,
|
loadRelayList,
|
||||||
@@ -48,7 +47,6 @@ import {
|
|||||||
loadGroupList,
|
loadGroupList,
|
||||||
userSpaceUrls,
|
userSpaceUrls,
|
||||||
userGroupList,
|
userGroupList,
|
||||||
bootstrapPubkeys,
|
|
||||||
decodeRelay,
|
decodeRelay,
|
||||||
getSpaceUrlsFromGroupList,
|
getSpaceUrlsFromGroupList,
|
||||||
getSpaceRoomsFromGroupList,
|
getSpaceRoomsFromGroupList,
|
||||||
@@ -240,28 +238,10 @@ const syncUserData = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const unsubscribeFollows = userFollowList.subscribe(async $userFollowList => {
|
|
||||||
for (const pubkeys of chunk(10, get(bootstrapPubkeys))) {
|
|
||||||
// This isn't urgent, avoid clogging other stuff up
|
|
||||||
await sleep(1000)
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
pubkeys.flatMap(pk => [
|
|
||||||
loadRelayList(pk),
|
|
||||||
loadGroupList(pk),
|
|
||||||
loadProfile(pk),
|
|
||||||
loadFollowList(pk),
|
|
||||||
loadMuteList(pk),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribersByKey.forEach(call)
|
unsubscribersByKey.forEach(call)
|
||||||
unsubscribeGroupList()
|
unsubscribeGroupList()
|
||||||
unsubscribeRelayList()
|
unsubscribeRelayList()
|
||||||
unsubscribeFollows()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import {request} from "@welshman/net"
|
||||||
|
import {clamp} from "@welshman/lib"
|
||||||
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
|
||||||
|
const DEFAULT_ASSERTION_RELAYS =
|
||||||
|
"wss://nip85.brainstorm.world,wss://nip85.nosfabrica.com,wss://nip85.uid.ovh"
|
||||||
|
|
||||||
|
const toAssertionRelayUrl = (url: string) => {
|
||||||
|
const trimmed = url.trim()
|
||||||
|
|
||||||
|
if (!trimmed) return ""
|
||||||
|
if (trimmed.startsWith("http://")) return trimmed.replace(/^http:/, "ws:")
|
||||||
|
if (trimmed.startsWith("https://")) return trimmed.replace(/^https:/, "wss:")
|
||||||
|
if (!/^[a-z]+:\/\//i.test(trimmed)) return `wss://${trimmed}`
|
||||||
|
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
const ASSERTION_RELAYS = String(import.meta.env.VITE_ASSERTION_RELAYS || DEFAULT_ASSERTION_RELAYS)
|
||||||
|
.split(",")
|
||||||
|
.map(toAssertionRelayUrl)
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
const rankCache = new Map<string, number | null>()
|
||||||
|
const pendingRankRequests = new Map<string, Promise<number | null>>()
|
||||||
|
|
||||||
|
const getMedian = (values: number[]) => {
|
||||||
|
const sorted = [...values].sort((a, b) => a - b)
|
||||||
|
const middle = Math.floor(sorted.length / 2)
|
||||||
|
|
||||||
|
if (sorted.length % 2 === 1) {
|
||||||
|
return sorted[middle]
|
||||||
|
}
|
||||||
|
|
||||||
|
return (sorted[middle - 1] + sorted[middle]) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRankFromEvent = (event: TrustedEvent) => {
|
||||||
|
const rankTag = event.tags?.find((tag: string[]) => tag[0] === "rank")
|
||||||
|
const rankValue = rankTag ? Number(rankTag[1]) : null
|
||||||
|
|
||||||
|
if (rankValue === null || !Number.isFinite(rankValue)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return clamp([0, 100], rankValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchPubkeyRank = async (pubkey: string): Promise<number | null> => {
|
||||||
|
if (ASSERTION_RELAYS.length === 0) {
|
||||||
|
return 50
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const events = await request({
|
||||||
|
relays: ASSERTION_RELAYS,
|
||||||
|
autoClose: true,
|
||||||
|
filters: [{kinds: [30382], "#d": [pubkey], limit: 8}],
|
||||||
|
})
|
||||||
|
|
||||||
|
const ranks = events.map(getRankFromEvent).filter((rank): rank is number => rank !== null)
|
||||||
|
|
||||||
|
if (ranks.length === 0) {
|
||||||
|
return 50
|
||||||
|
}
|
||||||
|
|
||||||
|
const median = getMedian(ranks)
|
||||||
|
|
||||||
|
return median
|
||||||
|
} catch {
|
||||||
|
return 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPubkeyRank = (pubkey: string): Promise<number | null> => {
|
||||||
|
if (rankCache.has(pubkey)) {
|
||||||
|
return Promise.resolve(rankCache.get(pubkey) ?? 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pending = pendingRankRequests.get(pubkey)
|
||||||
|
|
||||||
|
if (pending) {
|
||||||
|
return pending
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestPromise = fetchPubkeyRank(pubkey).then(rank => {
|
||||||
|
rankCache.set(pubkey, rank ?? 50)
|
||||||
|
pendingRankRequests.delete(pubkey)
|
||||||
|
|
||||||
|
return rank ?? 50
|
||||||
|
})
|
||||||
|
|
||||||
|
pendingRankRequests.set(pubkey, requestPromise)
|
||||||
|
|
||||||
|
return requestPromise
|
||||||
|
}
|
||||||
@@ -8,19 +8,36 @@
|
|||||||
import Page from "@lib/components/Page.svelte"
|
import Page from "@lib/components/Page.svelte"
|
||||||
import ContentSearch from "@lib/components/ContentSearch.svelte"
|
import ContentSearch from "@lib/components/ContentSearch.svelte"
|
||||||
import PeopleItem from "@app/components/PeopleItem.svelte"
|
import PeopleItem from "@app/components/PeopleItem.svelte"
|
||||||
import {bootstrapPubkeys} from "@app/core/state"
|
import {getPubkeyRank} from "@app/util/wot/getPubkeyRank"
|
||||||
|
|
||||||
|
const FALLBACK_RANK = 50
|
||||||
|
|
||||||
let term = $state("")
|
let term = $state("")
|
||||||
let limit = $state(10)
|
let limit = $state(10)
|
||||||
let pubkeys = $state($bootstrapPubkeys)
|
let pubkeys = $state<string[]>([])
|
||||||
let element: Element | undefined = $state()
|
let element: Element | undefined = $state()
|
||||||
|
let requestId = 0
|
||||||
|
|
||||||
|
const rankPubkeys = async (items: string[]) => {
|
||||||
|
const currentRequestId = ++requestId
|
||||||
|
|
||||||
|
pubkeys = items
|
||||||
|
|
||||||
|
const scoredPubkeys = await Promise.all(
|
||||||
|
items.map(async pubkey => ({pubkey, rank: (await getPubkeyRank(pubkey)) ?? FALLBACK_RANK})),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (currentRequestId !== requestId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkeys = scoredPubkeys.sort((a, b) => b.rank - a.rank).map(item => item.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
const search = debounce(200, (term: string) => {
|
const search = debounce(200, (term: string) => {
|
||||||
if (term) {
|
const searchTerm = term.trim()
|
||||||
pubkeys = $profileSearch.searchValues(term)
|
|
||||||
} else {
|
void rankPubkeys($profileSearch.searchValues(searchTerm))
|
||||||
pubkeys = $bootstrapPubkeys
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
$effect(() => search(term))
|
$effect(() => search(term))
|
||||||
|
|||||||
Reference in New Issue
Block a user