Apply wot to feeds

This commit is contained in:
Jon Staab 2024-06-20 19:48:04 -07:00
parent 1408f782f1
commit 2a32f31aba
5 changed files with 93 additions and 19 deletions

View File

@ -1,5 +1,6 @@
<script lang="ts">
import cx from "classnames"
import {remove} from "@welshman/lib"
import {NAMED_BOOKMARKS, toNostrURI, Address} from "@welshman/util"
import {slide} from "src/util/transition"
import {boolCtrl} from "src/partials/utils"
@ -8,6 +9,7 @@
import Chip from "src/partials/Chip.svelte"
import Anchor from "src/partials/Anchor.svelte"
import CopyValueSimple from "src/partials/CopyValueSimple.svelte"
import PersonCircles from "src/app/shared/PersonCircles.svelte"
import FeedSummary from "src/app/shared/FeedSummary.svelte"
import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte"
import {readFeed, readList, displayFeed, mapListToFeed, getSingletonValues} from "src/domain"
@ -18,6 +20,7 @@
addFeedFavorite,
removeFeedFavorite,
userFeedFavorites,
feedFavoritesByAddress,
} from "src/engine"
import {globalFeed} from "src/app/state"
import {router} from "src/app/util"
@ -40,6 +43,10 @@
}
$: isFavorite = getSingletonValues("a", $userFeedFavorites).has(address)
$: favoritedPubkeys = remove(
$pubkey,
($feedFavoritesByAddress.get(address) || []).map(s => s.event.pubkey),
)
</script>
<Card class="flex gap-3">
@ -70,6 +77,12 @@
{#if feed.description}
<p>{feed.description}</p>
{/if}
{#if favoritedPubkeys.length > 0}
<div class="flex gap-2">
<span class="text-neutral-300">Bookmarked by</span>
<PersonCircles class="h-6 w-6" pubkeys={favoritedPubkeys.slice(0, 20)} />
</div>
{/if}
<div class="mt-2 flex items-start justify-between">
<FeedSummary feed={feed.definition} />
<div class="flex gap-1">

View File

@ -1,8 +1,10 @@
<script lang="ts">
import {sortBy, uniqBy} from "@welshman/lib"
import {getAddress} from "@welshman/util"
import {sortBy, flatten, batch, uniqBy} from "@welshman/lib"
import {FEEDS, getAddress, getIdFilters} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {onMount} from "svelte"
import {createScroller} from "src/util/misc"
import {getAddressTagValues} from "src/util/nostr"
import {fly} from "src/util/transition"
import FlexColumn from "src/partials/FlexColumn.svelte"
import Anchor from "src/partials/Anchor.svelte"
@ -11,41 +13,64 @@
import {router} from "src/app/util/router"
import {displayFeed} from "src/domain"
import {
load,
hints,
userFeeds,
repository,
feedSearch,
userListFeeds,
loadPubkeyFeeds,
feedFavorites,
userFavoritedFeeds,
userFollows,
} from "src/engine"
const favoritedFeeds = $userFavoritedFeeds
const createFeed = () => router.at("feeds/create").open()
const editFeed = address => router.at("feeds").of(address).open()
const loadFeeds = batch(300, (addresseses: string[][]) => {
const addresses = flatten(addresseses).filter(a => !repository.getEvent(a))
if (addresses.length > 0) {
load({
relays: hints.User().getUrls(),
filters: getIdFilters(addresses),
skipCache: true,
})
}
})
const onRepositoryUpdate = ({added}: {added: TrustedEvent[]}) =>
loadFeeds(added.filter(e => e.kind === FEEDS).flatMap(e => getAddressTagValues(e.tags)))
const loadMore = async () => {
limit += 20
}
let q = ""
let limit = 20
let initialAddrs = new Set()
let element
$: allUserFeeds = [...$userFeeds, ...$userListFeeds]
$: feeds = uniqBy(
feed => getAddress(feed.event),
sortBy(displayFeed, [...allUserFeeds, ...favoritedFeeds]),
sortBy(displayFeed, [...$userFeeds, ...$userListFeeds, ...$userFavoritedFeeds]),
)
loadPubkeyFeeds(Array.from($userFollows))
loadFeeds($feedFavorites.flatMap(s => getAddressTagValues(s.event.tags)))
onMount(() => {
const scroller = createScroller(loadMore, {element})
return () => scroller.stop()
initialAddrs = new Set(feeds.map(feed => getAddress(feed.event)))
repository.on("update", onRepositoryUpdate)
return () => {
scroller.stop()
repository.off("update", onRepositoryUpdate)
}
})
</script>
@ -96,7 +121,7 @@
</Input>
{#each $feedSearch
.searchValues(q)
.filter(address => !feeds.find(feed => getAddress(feed.event) === address))
.filter(address => !initialAddrs.has(address))
.slice(0, limit) as address (address)}
<FeedCard {address} />
{/each}

View File

@ -1,5 +1,5 @@
import {fromPairs, randomId} from "@welshman/lib"
import {FEED, Tags, getAddress} from "@welshman/util"
import {FEED, Tags} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {
feedFromTags,
@ -10,7 +10,6 @@ import {
isScopeFeed,
} from "@welshman/feeds"
import type {Feed as IFeed} from "@welshman/feeds"
import {SearchHelper} from "src/util/misc"
import {tryJson} from "src/util/misc"
import type {PublishedList} from "./list"
@ -81,12 +80,6 @@ export const editFeed = (feed: PublishedFeed) => ({
export const displayFeed = (feed?: Feed) => feed?.title || "[no name]"
export class FeedSearch extends SearchHelper<PublishedFeed, string> {
config = {keys: ["title", "description"]}
getValue = (option: PublishedFeed) => getAddress(option.event)
displayValue = (address: string) => displayFeed(this.getOption(address))
}
export const isTopicFeed = f => isTagFeed(f) && f[1] === "#t"
export const isMentionFeed = f => isTagFeed(f) && f[1] === "#p"

View File

@ -50,7 +50,7 @@ const getFiltersForKey = (key: string, authors: string[]) => {
case "pubkey/lists":
return [{authors, kinds: LIST_KINDS}]
case "pubkey/feeds":
return [{authors, kinds: [NAMED_BOOKMARKS, FEED]}]
return [{authors, kinds: [NAMED_BOOKMARKS, FEED, FEEDS]}]
case "pubkey/relays":
return [{authors, kinds: [RELAYS, INBOX_RELAYS]}]
case "pubkey/profile":

View File

@ -116,11 +116,11 @@ import type {
} from "src/domain"
import {
RelayMode,
displayFeed,
EDITABLE_LIST_KINDS,
getSingletonValues,
makeSingleton,
ListSearch,
FeedSearch,
profileHasName,
readFeed,
readList,
@ -1350,6 +1350,20 @@ export const feedFavorites = deriveEventsMapped<PublishedSingleton>({
),
})
export const feedFavoritesByAddress = withGetter(
derived(feedFavorites, $feedFavorites => {
const $feedFavoritesByAddress = new Map<string, PublishedSingleton[]>()
for (const singleton of $feedFavorites) {
for (const address of getSingletonValues("a", singleton)) {
pushToMapKey($feedFavoritesByAddress, address, singleton)
}
}
return $feedFavoritesByAddress
}),
)
export const userFeedFavorites = derived(
[feedFavorites, pubkey],
([$singletons, $pubkey]: [PublishedSingleton[], string]) =>
@ -1363,6 +1377,35 @@ export const userFavoritedFeeds = derived(userFeedFavorites, $singleton =>
.map(readFeed),
)
export class FeedSearch extends SearchHelper<PublishedFeed, string> {
getSearch = () => {
const $feedFavoritesByAddress = feedFavoritesByAddress.get()
const getScore = feed => $feedFavoritesByAddress.get(getAddress(feed.event))?.length || 0
const options = this.options.map(feed => ({feed, score: getScore(feed)}))
const fuse = new Fuse(options, {
keys: ["feed.title", "feed.description"],
shouldSort: false,
includeScore: true,
})
return (term: string) => {
if (!term) {
return sortBy(item => -item.score, options).map(item => item.feed)
}
return doPipe(fuse.search(term), [
results =>
sortBy((r: any) => r.score - Math.pow(Math.max(0, r.item.score), 1 / 100), results),
results => results.map((r: any) => r.item.feed),
])
}
}
getValue = (option: PublishedFeed) => getAddress(option.event)
displayValue = (address: string) => displayFeed(this.getOption(address))
}
export const feedSearch = derived(feeds, $feeds => new FeedSearch($feeds))
export const listFeeds = deriveEventsMapped<PublishedListFeed>({