Rank groups by wot

This commit is contained in:
Jon Staab 2024-03-21 09:24:55 -07:00
parent b0028ce216
commit 5b33a1cdc3
10 changed files with 68 additions and 31 deletions

View File

@ -5,6 +5,7 @@
- [x] Accept npubs in people input
- [x] Skip notifying admin when the person joining/leaving groups is the admin
- [x] Remove group share modal, skip straight to create invite link
- [x] Rank groups by WoT
# 0.4.4

View File

@ -69,7 +69,7 @@
"normalize-url": "^8.0.0",
"nostr-tools": "^2.1.5",
"npm-run-all": "^4.1.5",
"paravel": "^0.5.4",
"paravel": "^0.5.5",
"qr-scanner": "^1.4.2",
"qrcode": "^1.5.3",
"ramda": "^0.29.1",

View File

@ -3,13 +3,15 @@
import Chip from "src/partials/Chip.svelte"
import Card from "src/partials/Card.svelte"
import GroupCircle from "src/app/shared/GroupCircle.svelte"
import PersonCircles from "src/app/shared/PersonCircles.svelte"
import {router} from "src/app/router"
import {displayGroup, deriveGroup} from "src/engine"
import {displayGroup, deriveGroup, getWotGroupMembers} from "src/engine"
export let address
export let modal = false
const group = deriveGroup(address)
const members = getWotGroupMembers(address)
const enter = () => {
const route = router.at("groups").of(address).at("notes")
@ -43,5 +45,9 @@
{ellipsize($group.meta.about, 300)}
</p>
{/if}
{#if members.length > 0}
<p class="mt-4 text-lg text-neutral-300">Members:</p>
<PersonCircles pubkeys={members.slice(0, 20)} />
{/if}
</div>
</Card>

View File

@ -1,4 +1,5 @@
<script lang="ts">
import cx from "classnames"
import PersonCircle from "src/app/shared/PersonCircle.svelte"
export let pubkeys
@ -6,10 +7,10 @@
const klass = $$props.class || "w-8 h-8"
</script>
<div class="flex">
<div class="flex pr-3">
{#each pubkeys as pubkey (pubkey)}
<div class="z-feature -mr-3 inline-block">
<PersonCircle class={klass} {pubkey} />
<PersonCircle class={cx(klass, "border border-solid border-neutral-800")} {pubkey} />
</div>
{/each}
</div>

View File

@ -7,7 +7,7 @@
import Popover from "src/partials/Popover.svelte"
import Modal from "src/partials/Modal.svelte"
import Anchor from "src/partials/Anchor.svelte"
import PersonCircle from "src/app/shared/PersonCircle.svelte"
import PersonCircles from "src/app/shared/PersonCircles.svelte"
import PersonAbout from "src/app/shared/PersonAbout.svelte"
import NoteContent from "src/app/shared/NoteContent.svelte"
import {router} from "src/app/router"
@ -81,13 +81,7 @@
<div slot="header" class="flex h-16 items-start gap-4 overflow-hidden p-1 px-4">
<div class="flex items-center gap-4 pt-1">
<Anchor class="fa fa-arrow-left cursor-pointer text-2xl" href="/channels" />
<div class="mr-3 flex pt-1">
{#each pubkeys as pubkey (pubkey)}
<div class="-mr-3 inline-block">
<PersonCircle class="h-10 w-10 border-2 border-solid border-neutral-900" {pubkey} />
</div>
{/each}
</div>
<PersonCircles {pubkeys} />
</div>
<div class="flex h-12 w-full flex-col overflow-hidden pt-1">
<div class="w-full">

View File

@ -1,9 +1,8 @@
<script>
import {onMount, onDestroy} from "svelte"
import {derived} from "svelte/store"
import {partition, assoc} from "ramda"
import {filter, assoc} from "ramda"
import {now} from "paravel"
import {fuzzy, createScroller} from "src/util/misc"
import {createScroller} from "src/util/misc"
import {giftWrapKinds} from "src/util/nostr"
import {getModal} from "src/partials/state"
import Anchor from "src/partials/Anchor.svelte"
@ -17,27 +16,25 @@
deriveIsGroupMember,
updateCurrentSession,
forcePlatformRelays,
session,
searchGroups,
} from "src/engine"
const loadMore = async () => {
limit += 50
limit += 20
}
const scroller = createScroller(loadMore, {element: getModal()})
const groupList = derived([groups, session], ([$groups, $session]) => {
const [joined, other] = partition(g => deriveIsGroupMember(g.address, true).get(), $groups)
const userIsMember = g => deriveIsGroupMember(g.address, true).get()
return {joined, other}
})
const userGroups = groups.derived(filter(userIsMember))
let q = ""
let limit = 50
let limit = 20
$: searchGroups = fuzzy($groupList.other, {
keys: [{name: "id", weight: 0.2}, "meta.name", "meta.description"],
})
$: otherGroups = $searchGroups(q)
.filter(g => !userIsMember(g))
.slice(0, limit)
document.title = "Groups"
@ -74,7 +71,7 @@
<i class="fa-solid fa-plus" /> Create
</Anchor>
</div>
{#each $groupList.joined as group (group.address)}
{#each $userGroups as group (group.address)}
<GroupListItem address={group.address} />
{:else}
<p class="text-center py-8">You haven't yet joined any groups.</p>
@ -83,6 +80,6 @@
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Search groups">
<i slot="before" class="fa-solid fa-search" />
</Input>
{#each searchGroups(q).slice(0, limit) as group (group.address)}
{#each otherGroups as group (group.address)}
<GroupListItem address={group.address} />
{/each}

View File

@ -1,11 +1,13 @@
import {identity, prop, uniqBy, defaultTo, sortBy, last, whereEq} from "ramda"
import {ellipsize, seconds} from "hurdak"
import Fuse from "fuse.js"
import {identity, prop, uniqBy, map, defaultTo, sortBy, last, whereEq} from "ramda"
import {ellipsize, doPipe, seconds} from "hurdak"
import {Tags, decodeAddress, addressToNaddr} from "paravel"
import {fuzzy} from "src/util/misc"
import type {GroupStatus, Session} from "src/engine/session/model"
import {pubkey} from "src/engine/session/state"
import {session} from "src/engine/session/derived"
import {forcePlatformRelays, hints} from "src/engine/relays/utils"
import {derivePerson, follows} from "src/engine/people/derived"
import {groups, groupSharedKeys, groupAdminKeys} from "./state"
import {GroupAccess} from "./model"
import type {Group} from "./model"
@ -27,7 +29,35 @@ export const deriveGroup = address => {
export const getGroupSearch = $groups => fuzzy($groups, {keys: ["meta.name", "meta.about"]})
export const searchGroups = groups.derived(getGroupSearch)
export const getWotGroupMembers = address =>
Array.from(follows.get()).filter(
pk =>
derivePerson(pk)
.get()
.communities?.some(t => t[1] === address),
)
export const searchGroups = groups.throttle(300).derived($groups => {
const options = $groups.map(group => ({group, score: getWotGroupMembers(group.address).length}))
const fuse = new Fuse(options, {
keys: [{name: "id", weight: 0.2}, "meta.name", "meta.about"],
threshold: 0.3,
shouldSort: false,
includeScore: true,
})
return (term: string) => {
if (!term) {
return sortBy(item => -item.score, options).map(item => item.group)
}
return doPipe(fuse.search(term), [
sortBy((r: any) => r.score - Math.pow(Math.max(0, r.item.score), 1 / 100)),
map((r: any) => r.item.group),
])
}
})
export const getRecipientKey = wrap => {
const pubkey = Tags.fromEvent(wrap).values("p").first()

View File

@ -30,6 +30,8 @@ export type Person = {
mutes?: string[][]
relays_updated_at?: number
relays?: RelayPolicy[]
communities_updated_at?: number
communities?: string[][]
handle_updated_at?: number
handle?: Handle
zapper_updated_at?: number

View File

@ -88,3 +88,9 @@ projections.addHandler(10000, e => {
.valueOf(),
})
})
projections.addHandler(10004, e => {
updateStore(people.key(e.pubkey), e.created_at, {
communities: Tags.fromEvent(e).whereKey("a").valueOf(),
})
})

View File

@ -25,7 +25,7 @@ export const isKeyValid = (key: string) => {
}
export const noteKinds = [1, 30023, 9802, 1808, 32123, 31923, 30402]
export const personKinds = [0, 2, 3, 10000, 10002]
export const personKinds = [0, 2, 3, 10000, 10002, 10004]
export const reactionKinds = [7, 9735]
export const repostKinds = [6, 16]
export const giftWrapKinds = [1059, 1060]