Fix some group related things
This commit is contained in:
parent
d87e8ca363
commit
0d8d832e87
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import {debounce} from "throttle-debounce"
|
||||
import {equals} from "ramda"
|
||||
import {isSearchFeed, makeSearchFeed, makeScopeFeed, Scope, getFeedArgs} from "@welshman/feeds"
|
||||
import {toSpliced} from "src/util/misc"
|
||||
import {boolCtrl} from "src/partials/utils"
|
||||
|
@ -11,6 +12,7 @@
|
|||
import MenuItem from "src/partials/MenuItem.svelte"
|
||||
import FeedForm from "src/app/shared/FeedForm.svelte"
|
||||
import {router} from "src/app/util"
|
||||
import {globalFeed} from "src/app/state"
|
||||
import {normalizeFeedDefinition, displayList, readFeed, makeFeed, displayFeed} from "src/domain"
|
||||
import {userListFeeds, canSign, deleteEvent, userFeeds} from "src/engine"
|
||||
|
||||
|
@ -118,15 +120,27 @@
|
|||
<Anchor modal href="/feeds/create"><i class="fa fa-plus" /></Anchor>
|
||||
</MenuItem>
|
||||
<div class="max-h-80 overflow-auto">
|
||||
<MenuItem on:click={() => setFeed(followsFeed)}>Follows</MenuItem>
|
||||
<MenuItem on:click={() => setFeed(networkFeed)}>Network</MenuItem>
|
||||
<MenuItem
|
||||
active={equals(followsFeed.definition, $globalFeed.definition)}
|
||||
on:click={() => setFeed(followsFeed)}>
|
||||
Follows
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
active={equals(networkFeed.definition, $globalFeed.definition)}
|
||||
on:click={() => setFeed(networkFeed)}>
|
||||
Network
|
||||
</MenuItem>
|
||||
{#each $userFeeds as feed}
|
||||
<MenuItem on:click={() => setFeed(feed)}>
|
||||
<MenuItem
|
||||
active={equals(feed.definition, $globalFeed.definition)}
|
||||
on:click={() => setFeed(feed)}>
|
||||
{displayFeed(feed)}
|
||||
</MenuItem>
|
||||
{/each}
|
||||
{#each $userListFeeds as feed}
|
||||
<MenuItem on:click={() => setFeed(feed)}>
|
||||
<MenuItem
|
||||
active={equals(feed.definition, $globalFeed.definition)}
|
||||
on:click={() => setFeed(feed)}>
|
||||
{displayList(feed.list)}
|
||||
</MenuItem>
|
||||
{/each}
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
<script>
|
||||
import {ellipsize} from "hurdak"
|
||||
import {derived} from "svelte/store"
|
||||
import {remove} from "@welshman/lib"
|
||||
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/util/router"
|
||||
import {displayGroup, deriveGroup, getWotCommunityMembers} from "src/engine"
|
||||
import {displayGroup, deriveGroup, userFollowsByCommunity, pubkey} from "src/engine"
|
||||
|
||||
export let address
|
||||
export let modal = false
|
||||
|
||||
const group = deriveGroup(address)
|
||||
const members = $getWotCommunityMembers(address)
|
||||
const members = derived(userFollowsByCommunity, $m => remove($pubkey, $m.get(address) || []))
|
||||
|
||||
const enter = () => {
|
||||
const route = router.at("groups").of(address).at("notes")
|
||||
|
@ -45,9 +47,10 @@
|
|||
{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 $members.length > 0}
|
||||
<div class="pt-1">
|
||||
<PersonCircles class="h-6 w-6" pubkeys={$members.slice(0, 20)} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
@ -44,101 +44,103 @@
|
|||
{#await promise}
|
||||
<!-- pass -->
|
||||
{:then event}
|
||||
<div in:fly|local={{y: 20}}>
|
||||
<Card>
|
||||
<FlexColumn>
|
||||
<div class="flex justify-between">
|
||||
<span>Kind {event.kind}, published {formatTimestamp(pub.created_at)}</span>
|
||||
<Anchor underline modal class="text-sm" on:click={() => open(event)}>View Note</Anchor>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm">
|
||||
<div class="hidden gap-4 sm:flex">
|
||||
<span class="flex items-center gap-2">
|
||||
<i class="fa fa-check" />
|
||||
{success.length} succeeded
|
||||
</span>
|
||||
{#if pending.length > 0}
|
||||
{#if event}
|
||||
<div in:fly|local={{y: 20}}>
|
||||
<Card>
|
||||
<FlexColumn>
|
||||
<div class="flex justify-between">
|
||||
<span>Kind {event.kind}, published {formatTimestamp(pub.created_at)}</span>
|
||||
<Anchor underline modal class="text-sm" on:click={() => open(event)}>View Note</Anchor>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm">
|
||||
<div class="hidden gap-4 sm:flex">
|
||||
<span class="flex items-center gap-2">
|
||||
<i class="fa fa-circle-notch fa-spin" />
|
||||
{pending.length} pending
|
||||
</span>
|
||||
{/if}
|
||||
{#if failure.length > 0}
|
||||
<span class="flex items-center gap-2">
|
||||
<i class="fa fa-triangle-exclamation" />
|
||||
{failure.length} failed
|
||||
</span>
|
||||
{/if}
|
||||
{#if timeout.length > 0}
|
||||
<span class="flex items-center gap-2">
|
||||
<i class="fa fa-stopwatch" />
|
||||
{timeout.length} timed out
|
||||
<i class="fa fa-check" />
|
||||
{success.length} succeeded
|
||||
</span>
|
||||
{#if pending.length > 0}
|
||||
<span class="flex items-center gap-2">
|
||||
<i class="fa fa-circle-notch fa-spin" />
|
||||
{pending.length} pending
|
||||
</span>
|
||||
{/if}
|
||||
{#if failure.length > 0}
|
||||
<span class="flex items-center gap-2">
|
||||
<i class="fa fa-triangle-exclamation" />
|
||||
{failure.length} failed
|
||||
</span>
|
||||
{/if}
|
||||
{#if timeout.length > 0}
|
||||
<span class="flex items-center gap-2">
|
||||
<i class="fa fa-stopwatch" />
|
||||
{timeout.length} timed out
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if expanded}
|
||||
<Anchor class="flex items-center gap-2" on:click={collapse}>
|
||||
<i class="fa fa-caret-up" />
|
||||
<span class="text-underline">Hide Details</span>
|
||||
</Anchor>
|
||||
{:else}
|
||||
<Anchor class="flex items-center gap-2" on:click={expand}>
|
||||
<i class="fa fa-caret-down" />
|
||||
<span class="text-underline">Show Details</span>
|
||||
</Anchor>
|
||||
{/if}
|
||||
</div>
|
||||
{#if expanded}
|
||||
<Anchor class="flex items-center gap-2" on:click={collapse}>
|
||||
<i class="fa fa-caret-up" />
|
||||
<span class="text-underline">Hide Details</span>
|
||||
</Anchor>
|
||||
{:else}
|
||||
<Anchor class="flex items-center gap-2" on:click={expand}>
|
||||
<i class="fa fa-caret-down" />
|
||||
<span class="text-underline">Show Details</span>
|
||||
</Anchor>
|
||||
<div transition:slide|local>
|
||||
<FlexColumn>
|
||||
{#if pending.length > 0}
|
||||
<p class="mt-4 text-lg">The following relays are still pending:</p>
|
||||
<div class="grid gap-2 sm:grid-cols-2">
|
||||
{#each pending as url}
|
||||
<RelayCard hideActions {url} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if success.length > 0}
|
||||
<p class="mt-4 text-lg">The following relays accepted your note:</p>
|
||||
<div class="grid gap-2 sm:grid-cols-2">
|
||||
{#each success as url}
|
||||
<RelayCard hideActions {url} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if failure.length > 0}
|
||||
<p class="mt-4 text-lg">The following relays rejected your note:</p>
|
||||
{#each failure as url}
|
||||
<RelayCard {url}>
|
||||
<div slot="actions">
|
||||
<Anchor
|
||||
on:click={() => retry(url, event)}
|
||||
class="flex items-center gap-2 text-sm">
|
||||
<i class="fa fa-rotate" /> Retry
|
||||
</Anchor>
|
||||
</div>
|
||||
</RelayCard>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if timeout.length > 0}
|
||||
<p class="mt-4 text-lg">The following relays did not respond:</p>
|
||||
{#each timeout as url}
|
||||
<RelayCard {url}>
|
||||
<div slot="actions">
|
||||
<Anchor
|
||||
on:click={() => retry(url, event)}
|
||||
class="flex items-center gap-2 text-sm">
|
||||
<i class="fa fa-rotate" /> Retry
|
||||
</Anchor>
|
||||
</div>
|
||||
</RelayCard>
|
||||
{/each}
|
||||
{/if}
|
||||
</FlexColumn>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if expanded}
|
||||
<div transition:slide|local>
|
||||
<FlexColumn>
|
||||
{#if pending.length > 0}
|
||||
<p class="mt-4 text-lg">The following relays are still pending:</p>
|
||||
<div class="grid gap-2 sm:grid-cols-2">
|
||||
{#each pending as url}
|
||||
<RelayCard hideActions {url} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if success.length > 0}
|
||||
<p class="mt-4 text-lg">The following relays accepted your note:</p>
|
||||
<div class="grid gap-2 sm:grid-cols-2">
|
||||
{#each success as url}
|
||||
<RelayCard hideActions {url} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if failure.length > 0}
|
||||
<p class="mt-4 text-lg">The following relays rejected your note:</p>
|
||||
{#each failure as url}
|
||||
<RelayCard {url}>
|
||||
<div slot="actions">
|
||||
<Anchor
|
||||
on:click={() => retry(url, event)}
|
||||
class="flex items-center gap-2 text-sm">
|
||||
<i class="fa fa-rotate" /> Retry
|
||||
</Anchor>
|
||||
</div>
|
||||
</RelayCard>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if timeout.length > 0}
|
||||
<p class="mt-4 text-lg">The following relays did not respond:</p>
|
||||
{#each timeout as url}
|
||||
<RelayCard {url}>
|
||||
<div slot="actions">
|
||||
<Anchor
|
||||
on:click={() => retry(url, event)}
|
||||
class="flex items-center gap-2 text-sm">
|
||||
<i class="fa fa-rotate" /> Retry
|
||||
</Anchor>
|
||||
</div>
|
||||
</RelayCard>
|
||||
{/each}
|
||||
{/if}
|
||||
</FlexColumn>
|
||||
</div>
|
||||
{/if}
|
||||
</FlexColumn>
|
||||
</Card>
|
||||
</div>
|
||||
</FlexColumn>
|
||||
</Card>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {partition, prop, uniqBy} from "ramda"
|
||||
import {batch, tryFunc, seconds} from "hurdak"
|
||||
import {writable, derived} from "svelte/store"
|
||||
import {inc, pushToMapKey, sortBy, now} from "@welshman/lib"
|
||||
import {inc, assoc, pushToMapKey, sortBy, now} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {
|
||||
Tags,
|
||||
|
@ -127,14 +127,15 @@ export const createFeed = (opts: FeedOpts) => {
|
|||
if (reqs && opts.shouldListen) {
|
||||
const tracker = new Tracker()
|
||||
|
||||
for (const {relays, filters} of reqs) {
|
||||
for (const request of Array.from(getRequestItems({relays, filters}, opts))) {
|
||||
for (const request of reqs) {
|
||||
for (const {relays, filters} of Array.from(getRequestItems(request, opts))) {
|
||||
subscribe({
|
||||
...request,
|
||||
relays,
|
||||
tracker,
|
||||
skipCache: true,
|
||||
onEvent: prependEvent,
|
||||
signal: controller.signal,
|
||||
filters: filters.map(assoc('since', now())),
|
||||
forcePlatform: opts.forcePlatform && (relays?.length || 0) === 0,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import cx from "classnames"
|
||||
import {onMount} from "svelte"
|
||||
import {last, prop, objOf} from "ramda"
|
||||
import {tryCatch} from "@welshman/lib"
|
||||
import {HANDLER_INFORMATION, NOSTR_CONNECT} from "@welshman/util"
|
||||
import {tryJson} from "src/util/misc"
|
||||
import {showWarning} from "src/partials/Toast.svelte"
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
MUTES,
|
||||
FOLLOWS,
|
||||
RELAYS,
|
||||
COMMUNITIES,
|
||||
} from "@welshman/util"
|
||||
import {tryJson} from "src/util/misc"
|
||||
import {appDataKeys, giftWrapKinds, getPublicKey} from "src/util/nostr"
|
||||
|
@ -195,9 +196,9 @@ projections.addHandler(27, (e: TrustedEvent) => {
|
|||
}
|
||||
})
|
||||
|
||||
// Membership access/exit requests
|
||||
// Membership
|
||||
|
||||
projections.addHandler(10004, (e: TrustedEvent) => {
|
||||
projections.addHandler(COMMUNITIES, (e: TrustedEvent) => {
|
||||
let session = getSession(e.pubkey)
|
||||
|
||||
if (!session) {
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
sort,
|
||||
groupBy,
|
||||
indexBy,
|
||||
pushToMapKey,
|
||||
} from "@welshman/lib"
|
||||
import {
|
||||
WRAP,
|
||||
|
@ -689,7 +690,7 @@ export const communityLists = withGetter(
|
|||
)
|
||||
|
||||
export const communityListsByPubkey = withGetter(
|
||||
derived(muteLists, $ls => indexBy($l => $l.event.pubkey, $ls)),
|
||||
derived(communityLists, $ls => indexBy($l => $l.event.pubkey, $ls)),
|
||||
)
|
||||
|
||||
export const getCommunityList = (pk: string) =>
|
||||
|
@ -703,16 +704,17 @@ export const getCommunities = (pk: string) => getSingletonValues("a", getCommuni
|
|||
export const deriveCommunities = (pk: string) =>
|
||||
derived(communityListsByPubkey, m => getSingletonValues("a", m.get(pk)))
|
||||
|
||||
export const getWotCommunityMembers = withGetter(
|
||||
derived(
|
||||
[userFollows, communityListsByPubkey],
|
||||
([$userFollows, $communityListsByPubkey]) =>
|
||||
address =>
|
||||
Array.from($userFollows).filter(pk =>
|
||||
getSingletonValues("a", $communityListsByPubkey.get(pk)),
|
||||
),
|
||||
),
|
||||
)
|
||||
export const userFollowsByCommunity = derived(communityLists, $communityLists => {
|
||||
const m = new Map<string, string[]>()
|
||||
|
||||
for (const list of $communityLists) {
|
||||
for (const a of getSingletonValues("a", list)) {
|
||||
pushToMapKey(m, a, list.event.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
})
|
||||
|
||||
// Groups
|
||||
|
||||
|
@ -722,30 +724,33 @@ export const deriveGroup = address => {
|
|||
return groups.key(address).derived(defaultTo({id, pubkey, address}))
|
||||
}
|
||||
|
||||
export const searchGroups = groups.throttle(300).derived($groups => {
|
||||
const options = $groups
|
||||
.filter(group => !repository.deletes.has(group.address))
|
||||
.map(group => ({group, score: getWotCommunityMembers.get()(group.address).length}))
|
||||
export const searchGroups = derived(
|
||||
[groups.throttle(300), userFollowsByCommunity],
|
||||
([$groups, $userFollowsByCommunity]) => {
|
||||
const options = $groups
|
||||
.filter(group => !repository.deletes.has(group.address))
|
||||
.map(group => ({group, score: $userFollowsByCommunity.get(group.address)?.length || 0}))
|
||||
|
||||
const fuse = new Fuse(options, {
|
||||
keys: [{name: "group.id", weight: 0.2}, "group.meta.name", "group.meta.about"],
|
||||
threshold: 0.3,
|
||||
shouldSort: false,
|
||||
includeScore: true,
|
||||
})
|
||||
const fuse = new Fuse(options, {
|
||||
keys: [{name: "group.id", weight: 0.2}, "group.meta.name", "group.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 (term: string) => {
|
||||
if (!term) {
|
||||
return sortBy(item => -item.score, options).map(item => item.group)
|
||||
}
|
||||
|
||||
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.group),
|
||||
])
|
||||
}
|
||||
|
||||
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.group),
|
||||
])
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
export const getRecipientKey = wrap => {
|
||||
const pubkey = Tags.fromEvent(wrap).values("p").first()
|
||||
|
@ -1601,6 +1606,8 @@ export const publish = ({forcePlatform = true, ...request}: MyPublishRequest) =>
|
|||
// Add the event to projections
|
||||
if (canUnwrap(request.event)) {
|
||||
ensureUnwrapped(request.event).then(projections.push)
|
||||
} else {
|
||||
projections.push(request.event)
|
||||
}
|
||||
|
||||
// Listen to updates and update our publish queue
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
import Anchor from "src/partials/Anchor.svelte"
|
||||
|
||||
export let inert = false
|
||||
export let active = false
|
||||
</script>
|
||||
|
||||
<Anchor
|
||||
{...$$props}
|
||||
class={cx($$props.class, "block p-3 px-4", {
|
||||
"transition-all hover:bg-accent hover:text-white": !inert,
|
||||
"bg-accent text-neutral-100": active,
|
||||
"transition-all hover:bg-accent hover:text-neutral-100": !inert,
|
||||
})}
|
||||
on:click>
|
||||
<slot />
|
||||
|
|
Loading…
Reference in New Issue