mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Apply wot to feeds
This commit is contained in:
parent
1408f782f1
commit
2a32f31aba
@ -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">
|
||||
|
@ -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}
|
||||
|
@ -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"
|
||||
|
@ -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":
|
||||
|
@ -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>({
|
||||
|
Loading…
Reference in New Issue
Block a user