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">
|
<script lang="ts">
|
||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
|
import {remove} from "@welshman/lib"
|
||||||
import {NAMED_BOOKMARKS, toNostrURI, Address} from "@welshman/util"
|
import {NAMED_BOOKMARKS, toNostrURI, Address} from "@welshman/util"
|
||||||
import {slide} from "src/util/transition"
|
import {slide} from "src/util/transition"
|
||||||
import {boolCtrl} from "src/partials/utils"
|
import {boolCtrl} from "src/partials/utils"
|
||||||
@ -8,6 +9,7 @@
|
|||||||
import Chip from "src/partials/Chip.svelte"
|
import Chip from "src/partials/Chip.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import CopyValueSimple from "src/partials/CopyValueSimple.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 FeedSummary from "src/app/shared/FeedSummary.svelte"
|
||||||
import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte"
|
import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte"
|
||||||
import {readFeed, readList, displayFeed, mapListToFeed, getSingletonValues} from "src/domain"
|
import {readFeed, readList, displayFeed, mapListToFeed, getSingletonValues} from "src/domain"
|
||||||
@ -18,6 +20,7 @@
|
|||||||
addFeedFavorite,
|
addFeedFavorite,
|
||||||
removeFeedFavorite,
|
removeFeedFavorite,
|
||||||
userFeedFavorites,
|
userFeedFavorites,
|
||||||
|
feedFavoritesByAddress,
|
||||||
} from "src/engine"
|
} from "src/engine"
|
||||||
import {globalFeed} from "src/app/state"
|
import {globalFeed} from "src/app/state"
|
||||||
import {router} from "src/app/util"
|
import {router} from "src/app/util"
|
||||||
@ -40,6 +43,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: isFavorite = getSingletonValues("a", $userFeedFavorites).has(address)
|
$: isFavorite = getSingletonValues("a", $userFeedFavorites).has(address)
|
||||||
|
$: favoritedPubkeys = remove(
|
||||||
|
$pubkey,
|
||||||
|
($feedFavoritesByAddress.get(address) || []).map(s => s.event.pubkey),
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card class="flex gap-3">
|
<Card class="flex gap-3">
|
||||||
@ -70,6 +77,12 @@
|
|||||||
{#if feed.description}
|
{#if feed.description}
|
||||||
<p>{feed.description}</p>
|
<p>{feed.description}</p>
|
||||||
{/if}
|
{/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">
|
<div class="mt-2 flex items-start justify-between">
|
||||||
<FeedSummary feed={feed.definition} />
|
<FeedSummary feed={feed.definition} />
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {sortBy, uniqBy} from "@welshman/lib"
|
import {sortBy, flatten, batch, uniqBy} from "@welshman/lib"
|
||||||
import {getAddress} from "@welshman/util"
|
import {FEEDS, getAddress, getIdFilters} from "@welshman/util"
|
||||||
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {createScroller} from "src/util/misc"
|
import {createScroller} from "src/util/misc"
|
||||||
|
import {getAddressTagValues} from "src/util/nostr"
|
||||||
import {fly} from "src/util/transition"
|
import {fly} from "src/util/transition"
|
||||||
import FlexColumn from "src/partials/FlexColumn.svelte"
|
import FlexColumn from "src/partials/FlexColumn.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
@ -11,41 +13,64 @@
|
|||||||
import {router} from "src/app/util/router"
|
import {router} from "src/app/util/router"
|
||||||
import {displayFeed} from "src/domain"
|
import {displayFeed} from "src/domain"
|
||||||
import {
|
import {
|
||||||
|
load,
|
||||||
|
hints,
|
||||||
userFeeds,
|
userFeeds,
|
||||||
|
repository,
|
||||||
feedSearch,
|
feedSearch,
|
||||||
userListFeeds,
|
userListFeeds,
|
||||||
loadPubkeyFeeds,
|
loadPubkeyFeeds,
|
||||||
|
feedFavorites,
|
||||||
userFavoritedFeeds,
|
userFavoritedFeeds,
|
||||||
userFollows,
|
userFollows,
|
||||||
} from "src/engine"
|
} from "src/engine"
|
||||||
|
|
||||||
const favoritedFeeds = $userFavoritedFeeds
|
|
||||||
|
|
||||||
const createFeed = () => router.at("feeds/create").open()
|
const createFeed = () => router.at("feeds/create").open()
|
||||||
|
|
||||||
const editFeed = address => router.at("feeds").of(address).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 () => {
|
const loadMore = async () => {
|
||||||
limit += 20
|
limit += 20
|
||||||
}
|
}
|
||||||
|
|
||||||
let q = ""
|
let q = ""
|
||||||
let limit = 20
|
let limit = 20
|
||||||
|
let initialAddrs = new Set()
|
||||||
let element
|
let element
|
||||||
|
|
||||||
$: allUserFeeds = [...$userFeeds, ...$userListFeeds]
|
|
||||||
|
|
||||||
$: feeds = uniqBy(
|
$: feeds = uniqBy(
|
||||||
feed => getAddress(feed.event),
|
feed => getAddress(feed.event),
|
||||||
sortBy(displayFeed, [...allUserFeeds, ...favoritedFeeds]),
|
sortBy(displayFeed, [...$userFeeds, ...$userListFeeds, ...$userFavoritedFeeds]),
|
||||||
)
|
)
|
||||||
|
|
||||||
loadPubkeyFeeds(Array.from($userFollows))
|
loadPubkeyFeeds(Array.from($userFollows))
|
||||||
|
loadFeeds($feedFavorites.flatMap(s => getAddressTagValues(s.event.tags)))
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const scroller = createScroller(loadMore, {element})
|
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>
|
</script>
|
||||||
|
|
||||||
@ -96,7 +121,7 @@
|
|||||||
</Input>
|
</Input>
|
||||||
{#each $feedSearch
|
{#each $feedSearch
|
||||||
.searchValues(q)
|
.searchValues(q)
|
||||||
.filter(address => !feeds.find(feed => getAddress(feed.event) === address))
|
.filter(address => !initialAddrs.has(address))
|
||||||
.slice(0, limit) as address (address)}
|
.slice(0, limit) as address (address)}
|
||||||
<FeedCard {address} />
|
<FeedCard {address} />
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {fromPairs, randomId} from "@welshman/lib"
|
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 type {TrustedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
feedFromTags,
|
feedFromTags,
|
||||||
@ -10,7 +10,6 @@ import {
|
|||||||
isScopeFeed,
|
isScopeFeed,
|
||||||
} from "@welshman/feeds"
|
} from "@welshman/feeds"
|
||||||
import type {Feed as IFeed} from "@welshman/feeds"
|
import type {Feed as IFeed} from "@welshman/feeds"
|
||||||
import {SearchHelper} from "src/util/misc"
|
|
||||||
import {tryJson} from "src/util/misc"
|
import {tryJson} from "src/util/misc"
|
||||||
import type {PublishedList} from "./list"
|
import type {PublishedList} from "./list"
|
||||||
|
|
||||||
@ -81,12 +80,6 @@ export const editFeed = (feed: PublishedFeed) => ({
|
|||||||
|
|
||||||
export const displayFeed = (feed?: Feed) => feed?.title || "[no name]"
|
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 isTopicFeed = f => isTagFeed(f) && f[1] === "#t"
|
||||||
|
|
||||||
export const isMentionFeed = f => isTagFeed(f) && f[1] === "#p"
|
export const isMentionFeed = f => isTagFeed(f) && f[1] === "#p"
|
||||||
|
@ -50,7 +50,7 @@ const getFiltersForKey = (key: string, authors: string[]) => {
|
|||||||
case "pubkey/lists":
|
case "pubkey/lists":
|
||||||
return [{authors, kinds: LIST_KINDS}]
|
return [{authors, kinds: LIST_KINDS}]
|
||||||
case "pubkey/feeds":
|
case "pubkey/feeds":
|
||||||
return [{authors, kinds: [NAMED_BOOKMARKS, FEED]}]
|
return [{authors, kinds: [NAMED_BOOKMARKS, FEED, FEEDS]}]
|
||||||
case "pubkey/relays":
|
case "pubkey/relays":
|
||||||
return [{authors, kinds: [RELAYS, INBOX_RELAYS]}]
|
return [{authors, kinds: [RELAYS, INBOX_RELAYS]}]
|
||||||
case "pubkey/profile":
|
case "pubkey/profile":
|
||||||
|
@ -116,11 +116,11 @@ import type {
|
|||||||
} from "src/domain"
|
} from "src/domain"
|
||||||
import {
|
import {
|
||||||
RelayMode,
|
RelayMode,
|
||||||
|
displayFeed,
|
||||||
EDITABLE_LIST_KINDS,
|
EDITABLE_LIST_KINDS,
|
||||||
getSingletonValues,
|
getSingletonValues,
|
||||||
makeSingleton,
|
makeSingleton,
|
||||||
ListSearch,
|
ListSearch,
|
||||||
FeedSearch,
|
|
||||||
profileHasName,
|
profileHasName,
|
||||||
readFeed,
|
readFeed,
|
||||||
readList,
|
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(
|
export const userFeedFavorites = derived(
|
||||||
[feedFavorites, pubkey],
|
[feedFavorites, pubkey],
|
||||||
([$singletons, $pubkey]: [PublishedSingleton[], string]) =>
|
([$singletons, $pubkey]: [PublishedSingleton[], string]) =>
|
||||||
@ -1363,6 +1377,35 @@ export const userFavoritedFeeds = derived(userFeedFavorites, $singleton =>
|
|||||||
.map(readFeed),
|
.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 feedSearch = derived(feeds, $feeds => new FeedSearch($feeds))
|
||||||
|
|
||||||
export const listFeeds = deriveEventsMapped<PublishedListFeed>({
|
export const listFeeds = deriveEventsMapped<PublishedListFeed>({
|
||||||
|
Loading…
Reference in New Issue
Block a user