mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Compare commits
3 Commits
d87e8ca363
...
c15453b996
Author | SHA1 | Date | |
---|---|---|---|
|
c15453b996 | ||
|
a94fabdffb | ||
|
0d8d832e87 |
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {debounce} from "throttle-debounce"
|
import {debounce} from "throttle-debounce"
|
||||||
|
import {equals} from "ramda"
|
||||||
import {isSearchFeed, makeSearchFeed, makeScopeFeed, Scope, getFeedArgs} from "@welshman/feeds"
|
import {isSearchFeed, makeSearchFeed, makeScopeFeed, Scope, getFeedArgs} from "@welshman/feeds"
|
||||||
import {toSpliced} from "src/util/misc"
|
import {toSpliced} from "src/util/misc"
|
||||||
import {boolCtrl} from "src/partials/utils"
|
import {boolCtrl} from "src/partials/utils"
|
||||||
@ -11,6 +12,7 @@
|
|||||||
import MenuItem from "src/partials/MenuItem.svelte"
|
import MenuItem from "src/partials/MenuItem.svelte"
|
||||||
import FeedForm from "src/app/shared/FeedForm.svelte"
|
import FeedForm from "src/app/shared/FeedForm.svelte"
|
||||||
import {router} from "src/app/util"
|
import {router} from "src/app/util"
|
||||||
|
import {globalFeed} from "src/app/state"
|
||||||
import {normalizeFeedDefinition, displayList, readFeed, makeFeed, displayFeed} from "src/domain"
|
import {normalizeFeedDefinition, displayList, readFeed, makeFeed, displayFeed} from "src/domain"
|
||||||
import {userListFeeds, canSign, deleteEvent, userFeeds} from "src/engine"
|
import {userListFeeds, canSign, deleteEvent, userFeeds} from "src/engine"
|
||||||
|
|
||||||
@ -118,15 +120,27 @@
|
|||||||
<Anchor modal href="/feeds/create"><i class="fa fa-plus" /></Anchor>
|
<Anchor modal href="/feeds/create"><i class="fa fa-plus" /></Anchor>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<div class="max-h-80 overflow-auto">
|
<div class="max-h-80 overflow-auto">
|
||||||
<MenuItem on:click={() => setFeed(followsFeed)}>Follows</MenuItem>
|
<MenuItem
|
||||||
<MenuItem on:click={() => setFeed(networkFeed)}>Network</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}
|
{#each $userFeeds as feed}
|
||||||
<MenuItem on:click={() => setFeed(feed)}>
|
<MenuItem
|
||||||
|
active={equals(feed.definition, $globalFeed.definition)}
|
||||||
|
on:click={() => setFeed(feed)}>
|
||||||
{displayFeed(feed)}
|
{displayFeed(feed)}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/each}
|
{/each}
|
||||||
{#each $userListFeeds as feed}
|
{#each $userListFeeds as feed}
|
||||||
<MenuItem on:click={() => setFeed(feed)}>
|
<MenuItem
|
||||||
|
active={equals(feed.definition, $globalFeed.definition)}
|
||||||
|
on:click={() => setFeed(feed)}>
|
||||||
{displayList(feed.list)}
|
{displayList(feed.list)}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
<script>
|
<script>
|
||||||
import {ellipsize} from "hurdak"
|
import {ellipsize} from "hurdak"
|
||||||
|
import {derived} from "svelte/store"
|
||||||
|
import {remove} from "@welshman/lib"
|
||||||
import Chip from "src/partials/Chip.svelte"
|
import Chip from "src/partials/Chip.svelte"
|
||||||
import Card from "src/partials/Card.svelte"
|
import Card from "src/partials/Card.svelte"
|
||||||
import GroupCircle from "src/app/shared/GroupCircle.svelte"
|
import GroupCircle from "src/app/shared/GroupCircle.svelte"
|
||||||
import PersonCircles from "src/app/shared/PersonCircles.svelte"
|
import PersonCircles from "src/app/shared/PersonCircles.svelte"
|
||||||
import {router} from "src/app/util/router"
|
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 address
|
||||||
export let modal = false
|
export let modal = false
|
||||||
|
|
||||||
const group = deriveGroup(address)
|
const group = deriveGroup(address)
|
||||||
const members = $getWotCommunityMembers(address)
|
const members = derived(userFollowsByCommunity, $m => remove($pubkey, $m.get(address) || []))
|
||||||
|
|
||||||
const enter = () => {
|
const enter = () => {
|
||||||
const route = router.at("groups").of(address).at("notes")
|
const route = router.at("groups").of(address).at("notes")
|
||||||
@ -45,9 +47,10 @@
|
|||||||
{ellipsize($group.meta.about, 300)}
|
{ellipsize($group.meta.about, 300)}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#if members.length > 0}
|
{#if $members.length > 0}
|
||||||
<p class="mt-4 text-lg text-neutral-300">Members:</p>
|
<div class="pt-1">
|
||||||
<PersonCircles pubkeys={members.slice(0, 20)} />
|
<PersonCircles class="h-6 w-6" pubkeys={$members.slice(0, 20)} />
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -44,101 +44,103 @@
|
|||||||
{#await promise}
|
{#await promise}
|
||||||
<!-- pass -->
|
<!-- pass -->
|
||||||
{:then event}
|
{:then event}
|
||||||
<div in:fly|local={{y: 20}}>
|
{#if event}
|
||||||
<Card>
|
<div in:fly|local={{y: 20}}>
|
||||||
<FlexColumn>
|
<Card>
|
||||||
<div class="flex justify-between">
|
<FlexColumn>
|
||||||
<span>Kind {event.kind}, published {formatTimestamp(pub.created_at)}</span>
|
<div class="flex justify-between">
|
||||||
<Anchor underline modal class="text-sm" on:click={() => open(event)}>View Note</Anchor>
|
<span>Kind {event.kind}, published {formatTimestamp(pub.created_at)}</span>
|
||||||
</div>
|
<Anchor underline modal class="text-sm" on:click={() => open(event)}>View Note</Anchor>
|
||||||
<div class="flex justify-between text-sm">
|
</div>
|
||||||
<div class="hidden gap-4 sm:flex">
|
<div class="flex justify-between text-sm">
|
||||||
<span class="flex items-center gap-2">
|
<div class="hidden gap-4 sm:flex">
|
||||||
<i class="fa fa-check" />
|
|
||||||
{success.length} succeeded
|
|
||||||
</span>
|
|
||||||
{#if pending.length > 0}
|
|
||||||
<span class="flex items-center gap-2">
|
<span class="flex items-center gap-2">
|
||||||
<i class="fa fa-circle-notch fa-spin" />
|
<i class="fa fa-check" />
|
||||||
{pending.length} pending
|
{success.length} succeeded
|
||||||
</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>
|
</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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if expanded}
|
{#if expanded}
|
||||||
<Anchor class="flex items-center gap-2" on:click={collapse}>
|
<div transition:slide|local>
|
||||||
<i class="fa fa-caret-up" />
|
<FlexColumn>
|
||||||
<span class="text-underline">Hide Details</span>
|
{#if pending.length > 0}
|
||||||
</Anchor>
|
<p class="mt-4 text-lg">The following relays are still pending:</p>
|
||||||
{:else}
|
<div class="grid gap-2 sm:grid-cols-2">
|
||||||
<Anchor class="flex items-center gap-2" on:click={expand}>
|
{#each pending as url}
|
||||||
<i class="fa fa-caret-down" />
|
<RelayCard hideActions {url} />
|
||||||
<span class="text-underline">Show Details</span>
|
{/each}
|
||||||
</Anchor>
|
</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}
|
{/if}
|
||||||
</div>
|
</FlexColumn>
|
||||||
{#if expanded}
|
</Card>
|
||||||
<div transition:slide|local>
|
</div>
|
||||||
<FlexColumn>
|
{/if}
|
||||||
{#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>
|
|
||||||
{/await}
|
{/await}
|
||||||
|
1
src/app/shared/WotScore.svelte
Normal file
1
src/app/shared/WotScore.svelte
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
import {partition, prop, uniqBy} from "ramda"
|
import {partition, prop, uniqBy} from "ramda"
|
||||||
import {batch, tryFunc, seconds} from "hurdak"
|
import {batch, tryFunc, seconds} from "hurdak"
|
||||||
import {writable, derived} from "svelte/store"
|
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 type {TrustedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
Tags,
|
Tags,
|
||||||
@ -14,7 +14,7 @@ import {
|
|||||||
REACTION,
|
REACTION,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {Tracker} from "@welshman/net"
|
import {Tracker} from "@welshman/net"
|
||||||
import type {Feed} from "@welshman/feeds"
|
import type {Feed, RequestItem} from "@welshman/feeds"
|
||||||
import {walkFeed, FeedLoader as CoreFeedLoader} from "@welshman/feeds"
|
import {walkFeed, FeedLoader as CoreFeedLoader} from "@welshman/feeds"
|
||||||
import {noteKinds, isLike, reactionKinds, repostKinds} from "src/util/nostr"
|
import {noteKinds, isLike, reactionKinds, repostKinds} from "src/util/nostr"
|
||||||
import {withGetter} from "src/util/misc"
|
import {withGetter} from "src/util/misc"
|
||||||
@ -59,7 +59,7 @@ const prepFilters = (filters, opts: FeedOpts) => {
|
|||||||
return filters
|
return filters
|
||||||
}
|
}
|
||||||
|
|
||||||
function* getRequestItems({relays, filters}, opts: FeedOpts) {
|
function* getRequestItems({relays, filters}: RequestItem, opts: FeedOpts) {
|
||||||
filters = prepFilters(filters, opts)
|
filters = prepFilters(filters, opts)
|
||||||
|
|
||||||
// Use relays specified in feeds
|
// Use relays specified in feeds
|
||||||
@ -127,14 +127,15 @@ export const createFeed = (opts: FeedOpts) => {
|
|||||||
if (reqs && opts.shouldListen) {
|
if (reqs && opts.shouldListen) {
|
||||||
const tracker = new Tracker()
|
const tracker = new Tracker()
|
||||||
|
|
||||||
for (const {relays, filters} of reqs) {
|
for (const request of reqs) {
|
||||||
for (const request of Array.from(getRequestItems({relays, filters}, opts))) {
|
for (const {relays, filters} of Array.from(getRequestItems(request, opts))) {
|
||||||
subscribe({
|
subscribe({
|
||||||
...request,
|
relays,
|
||||||
tracker,
|
tracker,
|
||||||
skipCache: true,
|
skipCache: true,
|
||||||
onEvent: prependEvent,
|
onEvent: prependEvent,
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
|
filters: filters.map(assoc("since", now())),
|
||||||
forcePlatform: opts.forcePlatform && (relays?.length || 0) === 0,
|
forcePlatform: opts.forcePlatform && (relays?.length || 0) === 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -14,19 +14,15 @@
|
|||||||
import {
|
import {
|
||||||
load,
|
load,
|
||||||
hints,
|
hints,
|
||||||
Amber,
|
|
||||||
loadHandle,
|
loadHandle,
|
||||||
getExtension,
|
getExtension,
|
||||||
withExtension,
|
withExtension,
|
||||||
loginWithAmber,
|
|
||||||
loginWithExtension,
|
loginWithExtension,
|
||||||
loginWithNostrConnect,
|
loginWithNostrConnect,
|
||||||
} from "src/engine"
|
} from "src/engine"
|
||||||
import {router} from "src/app/util/router"
|
import {router} from "src/app/util/router"
|
||||||
import {boot} from "src/app/state"
|
import {boot} from "src/app/state"
|
||||||
|
|
||||||
const amber = Amber.get()
|
|
||||||
|
|
||||||
const signUp = () => router.at("signup").replaceModal()
|
const signUp = () => router.at("signup").replaceModal()
|
||||||
|
|
||||||
const useBunker = () => router.at("login/bunker").replaceModal()
|
const useBunker = () => router.at("login/bunker").replaceModal()
|
||||||
@ -41,15 +37,6 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const useAmber = async () => {
|
|
||||||
const pubkey = await tryCatch(amber.getPubkey, e => showWarning(e.toString()))
|
|
||||||
|
|
||||||
if (pubkey) {
|
|
||||||
loginWithAmber(pubkey)
|
|
||||||
boot()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const usePrivateKey = () => router.at("login/privkey").replaceModal()
|
const usePrivateKey = () => router.at("login/privkey").replaceModal()
|
||||||
|
|
||||||
const usePublicKey = () => router.at("login/pubkey").replaceModal()
|
const usePublicKey = () => router.at("login/pubkey").replaceModal()
|
||||||
@ -195,14 +182,6 @@
|
|||||||
<span>Extension</span>
|
<span>Extension</span>
|
||||||
</Tile>
|
</Tile>
|
||||||
{/if}
|
{/if}
|
||||||
{#if amber.isEnabled()}
|
|
||||||
<Tile class="cursor-pointer bg-tinted-800" on:click={useAmber}>
|
|
||||||
<div>
|
|
||||||
<i class="fa fa-gem fa-xl" />
|
|
||||||
</div>
|
|
||||||
<span>Amber</span>
|
|
||||||
</Tile>
|
|
||||||
{/if}
|
|
||||||
<Tile class="cursor-pointer bg-tinted-800" on:click={usePrivateKey}>
|
<Tile class="cursor-pointer bg-tinted-800" on:click={usePrivateKey}>
|
||||||
<div>
|
<div>
|
||||||
<i class="fa fa-key fa-xl" />
|
<i class="fa fa-key fa-xl" />
|
||||||
|
@ -919,8 +919,6 @@ export const loginWithPublicKey = pubkey => addSession({method: "pubkey", pubkey
|
|||||||
|
|
||||||
export const loginWithExtension = pubkey => addSession({method: "extension", pubkey})
|
export const loginWithExtension = pubkey => addSession({method: "extension", pubkey})
|
||||||
|
|
||||||
export const loginWithAmber = pubkey => addSession({method: "amber", pubkey})
|
|
||||||
|
|
||||||
export const loginWithNsecBunker = async (pubkey, connectToken, connectRelay) => {
|
export const loginWithNsecBunker = async (pubkey, connectToken, connectRelay) => {
|
||||||
const connectKey = generatePrivateKey()
|
const connectKey = generatePrivateKey()
|
||||||
const connectHandler = {relays: [connectRelay]}
|
const connectHandler = {relays: [connectRelay]}
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
MUTES,
|
MUTES,
|
||||||
FOLLOWS,
|
FOLLOWS,
|
||||||
RELAYS,
|
RELAYS,
|
||||||
|
COMMUNITIES,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {tryJson} from "src/util/misc"
|
import {tryJson} from "src/util/misc"
|
||||||
import {appDataKeys, giftWrapKinds, getPublicKey} from "src/util/nostr"
|
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)
|
let session = getSession(e.pubkey)
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
@ -38,6 +38,7 @@ import {
|
|||||||
sort,
|
sort,
|
||||||
groupBy,
|
groupBy,
|
||||||
indexBy,
|
indexBy,
|
||||||
|
pushToMapKey,
|
||||||
} from "@welshman/lib"
|
} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
WRAP,
|
WRAP,
|
||||||
@ -689,7 +690,7 @@ export const communityLists = withGetter(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const communityListsByPubkey = 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) =>
|
export const getCommunityList = (pk: string) =>
|
||||||
@ -703,16 +704,17 @@ export const getCommunities = (pk: string) => getSingletonValues("a", getCommuni
|
|||||||
export const deriveCommunities = (pk: string) =>
|
export const deriveCommunities = (pk: string) =>
|
||||||
derived(communityListsByPubkey, m => getSingletonValues("a", m.get(pk)))
|
derived(communityListsByPubkey, m => getSingletonValues("a", m.get(pk)))
|
||||||
|
|
||||||
export const getWotCommunityMembers = withGetter(
|
export const userFollowsByCommunity = derived(communityLists, $communityLists => {
|
||||||
derived(
|
const m = new Map<string, string[]>()
|
||||||
[userFollows, communityListsByPubkey],
|
|
||||||
([$userFollows, $communityListsByPubkey]) =>
|
for (const list of $communityLists) {
|
||||||
address =>
|
for (const a of getSingletonValues("a", list)) {
|
||||||
Array.from($userFollows).filter(pk =>
|
pushToMapKey(m, a, list.event.pubkey)
|
||||||
getSingletonValues("a", $communityListsByPubkey.get(pk)),
|
}
|
||||||
),
|
}
|
||||||
),
|
|
||||||
)
|
return m
|
||||||
|
})
|
||||||
|
|
||||||
// Groups
|
// Groups
|
||||||
|
|
||||||
@ -722,30 +724,33 @@ export const deriveGroup = address => {
|
|||||||
return groups.key(address).derived(defaultTo({id, pubkey, address}))
|
return groups.key(address).derived(defaultTo({id, pubkey, address}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const searchGroups = groups.throttle(300).derived($groups => {
|
export const searchGroups = derived(
|
||||||
const options = $groups
|
[groups.throttle(300), userFollowsByCommunity],
|
||||||
.filter(group => !repository.deletes.has(group.address))
|
([$groups, $userFollowsByCommunity]) => {
|
||||||
.map(group => ({group, score: getWotCommunityMembers.get()(group.address).length}))
|
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, {
|
const fuse = new Fuse(options, {
|
||||||
keys: [{name: "group.id", weight: 0.2}, "group.meta.name", "group.meta.about"],
|
keys: [{name: "group.id", weight: 0.2}, "group.meta.name", "group.meta.about"],
|
||||||
threshold: 0.3,
|
threshold: 0.3,
|
||||||
shouldSort: false,
|
shouldSort: false,
|
||||||
includeScore: true,
|
includeScore: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (term: string) => {
|
return (term: string) => {
|
||||||
if (!term) {
|
if (!term) {
|
||||||
return sortBy(item => -item.score, options).map(item => item.group)
|
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 => {
|
export const getRecipientKey = wrap => {
|
||||||
const pubkey = Tags.fromEvent(wrap).values("p").first()
|
const pubkey = Tags.fromEvent(wrap).values("p").first()
|
||||||
@ -1601,6 +1606,8 @@ export const publish = ({forcePlatform = true, ...request}: MyPublishRequest) =>
|
|||||||
// Add the event to projections
|
// Add the event to projections
|
||||||
if (canUnwrap(request.event)) {
|
if (canUnwrap(request.event)) {
|
||||||
ensureUnwrapped(request.event).then(projections.push)
|
ensureUnwrapped(request.event).then(projections.push)
|
||||||
|
} else {
|
||||||
|
projections.push(request.event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen to updates and update our publish queue
|
// Listen to updates and update our publish queue
|
||||||
|
@ -1,160 +0,0 @@
|
|||||||
import {defer} from "hurdak"
|
|
||||||
import {sleep, tryCatch, Worker} from "@welshman/lib"
|
|
||||||
import type {HashedEvent, SignedEvent} from "@welshman/util"
|
|
||||||
import {hasValidSignature} from "@welshman/util"
|
|
||||||
import {parsePubkey} from "src/util/nostr"
|
|
||||||
|
|
||||||
const createGetPublicKeyIntent = () =>
|
|
||||||
`intent:#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=get_public_key;end`
|
|
||||||
|
|
||||||
const createSignEventIntent = (draft: HashedEvent) =>
|
|
||||||
`intent:${encodeURIComponent(
|
|
||||||
JSON.stringify(draft),
|
|
||||||
)}#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=sign_event;end`
|
|
||||||
|
|
||||||
const createNip04EncryptIntent = (pubkey: string, plainText: string) =>
|
|
||||||
`intent:${encodeURIComponent(
|
|
||||||
plainText,
|
|
||||||
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip04_encrypt;end`
|
|
||||||
|
|
||||||
const createNip04DecryptIntent = (pubkey: string, data: string) =>
|
|
||||||
`intent:${encodeURIComponent(
|
|
||||||
data,
|
|
||||||
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip04_decrypt;end`
|
|
||||||
|
|
||||||
const createNip44EncryptIntent = (pubkey: string, plainText: string) =>
|
|
||||||
`intent:${encodeURIComponent(
|
|
||||||
plainText,
|
|
||||||
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip44_encrypt;end`
|
|
||||||
|
|
||||||
const createNip44DecryptIntent = (pubkey: string, data: string) =>
|
|
||||||
`intent:${encodeURIComponent(
|
|
||||||
data,
|
|
||||||
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip44_decrypt;end`
|
|
||||||
|
|
||||||
class Request {
|
|
||||||
result = defer<{result?: string; error?: string}>()
|
|
||||||
|
|
||||||
constructor(readonly intent: string) {}
|
|
||||||
|
|
||||||
fulfill = async () => {
|
|
||||||
// Clear out the clipboard if we can
|
|
||||||
await tryCatch(() => navigator.clipboard.writeText(""))
|
|
||||||
|
|
||||||
// Send the intent to amber
|
|
||||||
const other = window.open(this.intent, "_blank")
|
|
||||||
|
|
||||||
// Wait a moment to avoid the visibilitychange listener firing before navigation
|
|
||||||
await sleep(500)
|
|
||||||
|
|
||||||
const cleanup = () => {
|
|
||||||
document.removeEventListener("visibilitychange", onVisibilityChange)
|
|
||||||
|
|
||||||
clearTimeout(timeout)
|
|
||||||
other.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onResult = result => {
|
|
||||||
this.result.resolve({result})
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onError = error => {
|
|
||||||
this.result.resolve({error})
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => onError("No data received."), 15000)
|
|
||||||
|
|
||||||
const onVisibilityChange = async () => {
|
|
||||||
await sleep(500)
|
|
||||||
|
|
||||||
if (document.visibilityState !== "visible") return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await navigator.clipboard.readText()
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
onResult(result)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Pass, document isn't focused
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("visibilitychange", onVisibilityChange)
|
|
||||||
|
|
||||||
return this.result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let singleton: Amber
|
|
||||||
|
|
||||||
export class Amber {
|
|
||||||
worker = new Worker<Request>()
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.worker.addGlobalHandler(request => request.fulfill())
|
|
||||||
}
|
|
||||||
|
|
||||||
static get() {
|
|
||||||
if (!singleton) {
|
|
||||||
singleton = new Amber()
|
|
||||||
}
|
|
||||||
|
|
||||||
return singleton
|
|
||||||
}
|
|
||||||
|
|
||||||
_request = async (intent: string) => {
|
|
||||||
const request = new Request(intent)
|
|
||||||
|
|
||||||
this.worker.push(request)
|
|
||||||
|
|
||||||
return request.result.then(({result, error}) => {
|
|
||||||
if (error) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
isEnabled = () => navigator.userAgent.includes("Android") && navigator.clipboard?.readText
|
|
||||||
|
|
||||||
getPubkey = async () => {
|
|
||||||
const result = await this._request(createGetPublicKeyIntent())
|
|
||||||
const pubkey = await parsePubkey(result)
|
|
||||||
|
|
||||||
if (!pubkey) {
|
|
||||||
throw new Error("Expected clipboard to have pubkey")
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubkey
|
|
||||||
}
|
|
||||||
|
|
||||||
signEvent = async (draft: HashedEvent): Promise<SignedEvent> => {
|
|
||||||
const sig = await this._request(createSignEventIntent(draft))
|
|
||||||
|
|
||||||
if (!sig.match(/^[a-f0-9]+$/)) throw new Error("Expected hex signature")
|
|
||||||
|
|
||||||
const event: SignedEvent = {...draft, sig}
|
|
||||||
|
|
||||||
if (!hasValidSignature(event)) throw new Error("Invalid signature")
|
|
||||||
|
|
||||||
return event
|
|
||||||
}
|
|
||||||
|
|
||||||
nip04Encrypt = (pubkey: string, plaintext: string): Promise<string> =>
|
|
||||||
this._request(createNip04EncryptIntent(pubkey, plaintext))
|
|
||||||
|
|
||||||
nip04Decrypt = (pubkey: string, data: string): Promise<string> =>
|
|
||||||
this._request(createNip04DecryptIntent(pubkey, data))
|
|
||||||
|
|
||||||
nip44Encrypt = (pubkey: string, plaintext: string): Promise<string> =>
|
|
||||||
this._request(createNip44EncryptIntent(pubkey, plaintext))
|
|
||||||
|
|
||||||
nip44Decrypt = (pubkey: string, data: string): Promise<string> =>
|
|
||||||
this._request(createNip44DecryptIntent(pubkey, data))
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ import {Nip59} from "./nip59"
|
|||||||
import {Signer} from "./signer"
|
import {Signer} from "./signer"
|
||||||
import {Connect} from "./connect"
|
import {Connect} from "./connect"
|
||||||
|
|
||||||
export * from "./amber"
|
|
||||||
export * from "./nip04"
|
export * from "./nip04"
|
||||||
export * from "./nip07"
|
export * from "./nip07"
|
||||||
export * from "./nip44"
|
export * from "./nip44"
|
||||||
|
@ -3,7 +3,6 @@ import {switcherFn, tryFunc} from "hurdak"
|
|||||||
import type {Session} from "src/engine/model"
|
import type {Session} from "src/engine/model"
|
||||||
import type {Connect} from "./connect"
|
import type {Connect} from "./connect"
|
||||||
import {withExtension} from "./nip07"
|
import {withExtension} from "./nip07"
|
||||||
import {Amber} from "./amber"
|
|
||||||
|
|
||||||
export class Nip04 {
|
export class Nip04 {
|
||||||
constructor(
|
constructor(
|
||||||
@ -12,7 +11,7 @@ export class Nip04 {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
return ["amber", "privkey", "connect", "extension"].includes(this.session?.method)
|
return ["privkey", "connect", "extension"].includes(this.session?.method)
|
||||||
}
|
}
|
||||||
|
|
||||||
async encrypt(message: string, pk: string, sk: string) {
|
async encrypt(message: string, pk: string, sk: string) {
|
||||||
@ -27,7 +26,6 @@ export class Nip04 {
|
|||||||
const {method, privkey} = this.session
|
const {method, privkey} = this.session
|
||||||
|
|
||||||
return switcherFn(method, {
|
return switcherFn(method, {
|
||||||
amber: () => Amber.get().nip04Encrypt(pk, message),
|
|
||||||
privkey: () => this.encrypt(message, pk, privkey),
|
privkey: () => this.encrypt(message, pk, privkey),
|
||||||
extension: () => withExtension(ext => ext.nip04.encrypt(pk, message)),
|
extension: () => withExtension(ext => ext.nip04.encrypt(pk, message)),
|
||||||
connect: () => this.connect.broker.nip04Encrypt(pk, message),
|
connect: () => this.connect.broker.nip04Encrypt(pk, message),
|
||||||
@ -38,7 +36,6 @@ export class Nip04 {
|
|||||||
const {method, privkey} = this.session
|
const {method, privkey} = this.session
|
||||||
|
|
||||||
return switcherFn(method, {
|
return switcherFn(method, {
|
||||||
amber: () => Amber.get().nip04Decrypt(pk, message),
|
|
||||||
privkey: () => this.decrypt(message, pk, privkey),
|
privkey: () => this.decrypt(message, pk, privkey),
|
||||||
extension: () => withExtension(ext => ext.nip04.decrypt(pk, message)),
|
extension: () => withExtension(ext => ext.nip04.decrypt(pk, message)),
|
||||||
connect: () => this.connect.broker.nip04Decrypt(pk, message),
|
connect: () => this.connect.broker.nip04Decrypt(pk, message),
|
||||||
|
@ -6,7 +6,6 @@ import {fromHex} from "src/util/nostr"
|
|||||||
import type {Session} from "src/engine/model"
|
import type {Session} from "src/engine/model"
|
||||||
import type {Connect} from "./connect"
|
import type {Connect} from "./connect"
|
||||||
import {withExtension} from "./nip07"
|
import {withExtension} from "./nip07"
|
||||||
import {Amber} from "./amber"
|
|
||||||
|
|
||||||
// Deriving shared secret is an expensive computation, cache it
|
// Deriving shared secret is an expensive computation, cache it
|
||||||
export const getSharedSecret = cached({
|
export const getSharedSecret = cached({
|
||||||
@ -22,7 +21,7 @@ export class Nip44 {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
if (["amber", "privkey", "connect"].includes(this.session?.method)) {
|
if (["privkey", "connect"].includes(this.session?.method)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +44,6 @@ export class Nip44 {
|
|||||||
const {method, privkey} = this.session
|
const {method, privkey} = this.session
|
||||||
|
|
||||||
return switcherFn(method, {
|
return switcherFn(method, {
|
||||||
amber: () => Amber.get().nip44Encrypt(pk, message),
|
|
||||||
privkey: () => this.encrypt(message, pk, privkey),
|
privkey: () => this.encrypt(message, pk, privkey),
|
||||||
extension: () => withExtension(ext => ext.nip44.encrypt(pk, message)),
|
extension: () => withExtension(ext => ext.nip44.encrypt(pk, message)),
|
||||||
connect: () => this.connect.broker.nip44Encrypt(pk, message),
|
connect: () => this.connect.broker.nip44Encrypt(pk, message),
|
||||||
@ -56,7 +54,6 @@ export class Nip44 {
|
|||||||
const {method, privkey} = this.session
|
const {method, privkey} = this.session
|
||||||
|
|
||||||
return switcherFn(method, {
|
return switcherFn(method, {
|
||||||
amber: () => Amber.get().nip44Decrypt(pk, message),
|
|
||||||
privkey: () => this.decrypt(message, pk, privkey),
|
privkey: () => this.decrypt(message, pk, privkey),
|
||||||
extension: () => withExtension(ext => ext.nip44.decrypt(pk, message)),
|
extension: () => withExtension(ext => ext.nip44.decrypt(pk, message)),
|
||||||
connect: () => this.connect.broker.nip44Decrypt(pk, message),
|
connect: () => this.connect.broker.nip44Decrypt(pk, message),
|
||||||
|
@ -5,7 +5,6 @@ import {getPublicKey, getSignature} from "src/util/nostr"
|
|||||||
import type {Session} from "src/engine/model"
|
import type {Session} from "src/engine/model"
|
||||||
import type {Connect} from "./connect"
|
import type {Connect} from "./connect"
|
||||||
import {withExtension} from "./nip07"
|
import {withExtension} from "./nip07"
|
||||||
import {Amber} from "./amber"
|
|
||||||
|
|
||||||
export class Signer {
|
export class Signer {
|
||||||
constructor(
|
constructor(
|
||||||
@ -14,7 +13,7 @@ export class Signer {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
return ["amber", "connect", "privkey", "extension"].includes(this.session?.method)
|
return ["connect", "privkey", "extension"].includes(this.session?.method)
|
||||||
}
|
}
|
||||||
|
|
||||||
prepWithKey(event: EventTemplate, sk: string) {
|
prepWithKey(event: EventTemplate, sk: string) {
|
||||||
@ -48,7 +47,6 @@ export class Signer {
|
|||||||
const event = this.prepAsUser(template)
|
const event = this.prepAsUser(template)
|
||||||
|
|
||||||
return switcherFn(method, {
|
return switcherFn(method, {
|
||||||
amber: () => Amber.get().signEvent(event),
|
|
||||||
privkey: () => ({...event, sig: getSignature(event, privkey)}),
|
privkey: () => ({...event, sig: getSignature(event, privkey)}),
|
||||||
extension: () => withExtension(ext => ext.signEvent(event)),
|
extension: () => withExtension(ext => ext.signEvent(event)),
|
||||||
connect: () => this.connect.broker.signEvent(template),
|
connect: () => this.connect.broker.signEvent(template),
|
||||||
|
@ -3,12 +3,14 @@
|
|||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
|
|
||||||
export let inert = false
|
export let inert = false
|
||||||
|
export let active = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Anchor
|
<Anchor
|
||||||
{...$$props}
|
{...$$props}
|
||||||
class={cx($$props.class, "block p-3 px-4", {
|
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>
|
on:click>
|
||||||
<slot />
|
<slot />
|
||||||
|
Loading…
Reference in New Issue
Block a user