mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Compare commits
7 Commits
2a32f31aba
...
abd306ccac
Author | SHA1 | Date | |
---|---|---|---|
|
abd306ccac | ||
|
b7f6d4b8be | ||
|
d7e808d977 | ||
|
5cbdda2438 | ||
|
01e6c663fb | ||
|
4708dc3013 | ||
|
2e3df2838d |
BIN
package-lock.json
generated
BIN
package-lock.json
generated
Binary file not shown.
@ -59,7 +59,7 @@
|
|||||||
"@welshman/feeds": "^0.0.12",
|
"@welshman/feeds": "^0.0.12",
|
||||||
"@welshman/lib": "^0.0.10",
|
"@welshman/lib": "^0.0.10",
|
||||||
"@welshman/net": "^0.0.14",
|
"@welshman/net": "^0.0.14",
|
||||||
"@welshman/util": "^0.0.15",
|
"@welshman/util": "^0.0.16",
|
||||||
"bowser": "^2.11.0",
|
"bowser": "^2.11.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"compressorjs": "^1.2.1",
|
"compressorjs": "^1.2.1",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {writable, hash} from "@welshman/lib"
|
import {seconds} from "hurdak"
|
||||||
|
import {writable, now, hash} from "@welshman/lib"
|
||||||
import {createScroller, synced} from "src/util/misc"
|
import {createScroller, synced} from "src/util/misc"
|
||||||
import {fly, fade} from "src/util/transition"
|
import {fly, fade} from "src/util/transition"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
@ -28,6 +29,8 @@
|
|||||||
|
|
||||||
const splits = [["zap", $env.PLATFORM_PUBKEY, "", "1"]]
|
const splits = [["zap", $env.PLATFORM_PUBKEY, "", "1"]]
|
||||||
|
|
||||||
|
const promptDismissed = synced("feed/promptDismissed", 0)
|
||||||
|
|
||||||
const shouldHideReplies = showControls ? synced("Feed.shouldHideReplies", false) : writable(false)
|
const shouldHideReplies = showControls ? synced("Feed.shouldHideReplies", false) : writable(false)
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
@ -103,12 +106,19 @@
|
|||||||
{anchor}
|
{anchor}
|
||||||
{note} />
|
{note} />
|
||||||
</div>
|
</div>
|
||||||
{#if i > 20 && parseInt(hash(note.id)) % 100 === 0}
|
{#if i > 20 && parseInt(hash(note.id)) % 100 === 0 && $promptDismissed < now() - seconds(7, "day")}
|
||||||
<Card class="flex items-center justify-between">
|
<Card class="group flex items-center justify-between">
|
||||||
<p class="text-xl">Enjoying Coracle?</p>
|
<p class="text-xl">Enjoying Coracle?</p>
|
||||||
<Anchor modal button accent href={router.at("zap").qp({splits}).toString()}>
|
<div class="flex gap-2">
|
||||||
Zap the developer
|
<Anchor
|
||||||
</Anchor>
|
class="text-neutral-400 opacity-0 transition-all group-hover:opacity-100"
|
||||||
|
on:click={() => promptDismissed.set(now())}>
|
||||||
|
Dismiss
|
||||||
|
</Anchor>
|
||||||
|
<Anchor modal button accent href={router.at("zap").qp({splits}).toString()}>
|
||||||
|
Zap the developer
|
||||||
|
</Anchor>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -8,8 +8,10 @@
|
|||||||
const meta = deriveGroupMeta(address)
|
const meta = deriveGroupMeta(address)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NoteContentKind1
|
{#key $meta?.about}
|
||||||
note={{content: $meta?.about || ""}}
|
<NoteContentKind1
|
||||||
minLength={100}
|
note={{content: $meta?.about || ""}}
|
||||||
maxLength={140}
|
minLength={100}
|
||||||
showEntire={!truncate} />
|
maxLength={140}
|
||||||
|
showEntire={!truncate} />
|
||||||
|
{/key}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
deriveGroup,
|
deriveGroup,
|
||||||
deriveGroupStatus,
|
deriveGroupStatus,
|
||||||
deriveAdminKeyForGroup,
|
deriveAdminKeyForGroup,
|
||||||
deriveUserCommunities,
|
getUserCommunities,
|
||||||
} from "src/engine"
|
} from "src/engine"
|
||||||
|
|
||||||
export let address
|
export let address
|
||||||
@ -51,9 +51,9 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const join = () => publishCommunitiesList(deriveUserCommunities().get().concat(address))
|
const join = () => publishCommunitiesList(getUserCommunities(session.get()).concat(address))
|
||||||
|
|
||||||
const leave = () => publishCommunitiesList(without([address], deriveUserCommunities().get()))
|
const leave = () => publishCommunitiesList(without([address], getUserCommunities(session.get())))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center gap-3" on:click|stopPropagation>
|
<div class="flex items-center gap-3" on:click|stopPropagation>
|
||||||
|
@ -114,7 +114,7 @@
|
|||||||
$: reply = tags.parent()
|
$: reply = tags.parent()
|
||||||
$: root = tags.root()
|
$: root = tags.root()
|
||||||
|
|
||||||
$: muted = !showMuted && $isEventMuted(event, true)
|
$: muted = !showMuted && $isEventMuted(event)
|
||||||
|
|
||||||
// Find children in our context
|
// Find children in our context
|
||||||
$: children = context.filter(e => isChildOf(e, event))
|
$: children = context.filter(e => isChildOf(e, event))
|
||||||
@ -130,7 +130,7 @@
|
|||||||
visibleReplies = []
|
visibleReplies = []
|
||||||
|
|
||||||
for (const e of replies) {
|
for (const e of replies) {
|
||||||
if ($isEventMuted(e)) {
|
if ($isEventMuted(e, true)) {
|
||||||
mutedReplies.push(e)
|
mutedReplies.push(e)
|
||||||
} else if (collapsed) {
|
} else if (collapsed) {
|
||||||
hiddenReplies.push(e)
|
hiddenReplies.push(e)
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
unmuteNote,
|
unmuteNote,
|
||||||
muteNote,
|
muteNote,
|
||||||
deriveHandlersForKind,
|
deriveHandlersForKind,
|
||||||
deriveIsGroupMember,
|
userIsGroupMember,
|
||||||
publishToZeroOrMoreGroups,
|
publishToZeroOrMoreGroups,
|
||||||
deleteEvent,
|
deleteEvent,
|
||||||
getSetting,
|
getSetting,
|
||||||
@ -192,12 +192,12 @@
|
|||||||
window.open(templateTag[1].replace("<bech32>", entity))
|
window.open(templateTag[1].replace("<bech32>", entity))
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupOptions = session.derived($session => {
|
const groupOptions = derived(session, $session => {
|
||||||
const options = []
|
const options = []
|
||||||
|
|
||||||
for (const addr of Object.keys($session?.groups || {})) {
|
for (const addr of Object.keys($session?.groups || {})) {
|
||||||
const group = groups.key(addr).get()
|
const group = groups.key(addr).get()
|
||||||
const isMember = deriveIsGroupMember(addr).get()
|
const isMember = $userIsGroupMember(addr)
|
||||||
|
|
||||||
if (group && isMember && addr !== address) {
|
if (group && isMember && addr !== address) {
|
||||||
options.push(group)
|
options.push(group)
|
||||||
@ -212,9 +212,7 @@
|
|||||||
let handlersShown = false
|
let handlersShown = false
|
||||||
|
|
||||||
$: disableActions =
|
$: disableActions =
|
||||||
!$canSign ||
|
!$canSign || ($muted && !showMuted) || (note.wrap && address && !$userIsGroupMember(address))
|
||||||
($muted && !showMuted) ||
|
|
||||||
(note.wrap && address && !deriveIsGroupMember(address).get())
|
|
||||||
$: like = likes.find(e => e.pubkey === $session?.pubkey)
|
$: like = likes.find(e => e.pubkey === $session?.pubkey)
|
||||||
$: $likesCount = likes.length
|
$: $likesCount = likes.length
|
||||||
$: zap = zaps.find(e => e.request.pubkey === $session?.pubkey)
|
$: zap = zaps.find(e => e.request.pubkey === $session?.pubkey)
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
{#if muted}
|
{#if muted}
|
||||||
<p class="mb-1 py-24 text-center text-neutral-600">
|
<p class="mb-1 py-24 text-center text-neutral-600">
|
||||||
You have hidden this note.
|
You have hidden this note.
|
||||||
<Anchor class="underline" on:click={unmute}>Show</Anchor>
|
<Anchor class="underline" stopPropagation on:click={unmute}>Show</Anchor>
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
{#if !isGroup}
|
{#if !isGroup}
|
||||||
|
@ -37,9 +37,9 @@
|
|||||||
|
|
||||||
const getContent = e => (e.kind === 4 ? ensureMessagePlaintext(e) : e.content) || ""
|
const getContent = e => (e.kind === 4 ? ensureMessagePlaintext(e) : e.content) || ""
|
||||||
|
|
||||||
const send = async (content, useNip44) => {
|
const send = async (content, useNip17) => {
|
||||||
// If we don't have nip44 support, just send a legacy message
|
// If we don't have nip44 support, just send a legacy message
|
||||||
if (!$nip44.isEnabled() || !useNip44) {
|
if (!$nip44.isEnabled() || !useNip17) {
|
||||||
return sendLegacyMessage(channelId, content)
|
return sendLegacyMessage(channelId, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
import {
|
import {
|
||||||
env,
|
env,
|
||||||
pubkey,
|
pubkey,
|
||||||
|
session,
|
||||||
initGroup,
|
initGroup,
|
||||||
publishGroupMeta,
|
publishGroupMeta,
|
||||||
publishGroupInvites,
|
publishGroupInvites,
|
||||||
@ -16,7 +17,7 @@
|
|||||||
publishCommunityMeta,
|
publishCommunityMeta,
|
||||||
publishCommunitiesList,
|
publishCommunitiesList,
|
||||||
publishGroupMembers,
|
publishGroupMembers,
|
||||||
deriveUserCommunities,
|
getUserCommunities,
|
||||||
} from "src/engine"
|
} from "src/engine"
|
||||||
import {router} from "src/app/util/router"
|
import {router} from "src/app/util/router"
|
||||||
|
|
||||||
@ -50,7 +51,7 @@
|
|||||||
|
|
||||||
if (kind === COMMUNITY) {
|
if (kind === COMMUNITY) {
|
||||||
await publishCommunityMeta(address, identifier, meta)
|
await publishCommunityMeta(address, identifier, meta)
|
||||||
await publishCommunitiesList(deriveUserCommunities().get().concat(address))
|
await publishCommunitiesList(getUserCommunities(session.get()).concat(address))
|
||||||
} else {
|
} else {
|
||||||
await publishGroupMeta(address, identifier, meta, listing_is_public)
|
await publishGroupMeta(address, identifier, meta, listing_is_public)
|
||||||
await publishGroupMembers(address, "set", members)
|
await publishGroupMembers(address, "set", members)
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
deriveGroupMeta,
|
deriveGroupMeta,
|
||||||
deriveAdminKeyForGroup,
|
deriveAdminKeyForGroup,
|
||||||
deriveSharedKeyForGroup,
|
deriveSharedKeyForGroup,
|
||||||
deriveIsGroupMember,
|
userIsGroupMember,
|
||||||
deriveGroupStatus,
|
deriveGroupStatus,
|
||||||
loadGroups,
|
loadGroups,
|
||||||
loadGroupMessages,
|
loadGroupMessages,
|
||||||
@ -38,7 +38,6 @@
|
|||||||
const group = deriveGroup(address)
|
const group = deriveGroup(address)
|
||||||
const meta = deriveGroupMeta(address)
|
const meta = deriveGroupMeta(address)
|
||||||
const status = deriveGroupStatus(address)
|
const status = deriveGroupStatus(address)
|
||||||
const isGroupMember = deriveIsGroupMember(address)
|
|
||||||
const sharedKey = deriveSharedKeyForGroup(address)
|
const sharedKey = deriveSharedKeyForGroup(address)
|
||||||
const adminKey = deriveAdminKeyForGroup(address)
|
const adminKey = deriveAdminKeyForGroup(address)
|
||||||
const requests = groupRequests.derived(requests =>
|
const requests = groupRequests.derived(requests =>
|
||||||
@ -56,7 +55,7 @@
|
|||||||
|
|
||||||
let tabs
|
let tabs
|
||||||
|
|
||||||
$: key = $group && $isGroupMember
|
$: key = $group && $userIsGroupMember(address)
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (key) {
|
if (key) {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
groups,
|
groups,
|
||||||
loadGiftWraps,
|
loadGiftWraps,
|
||||||
loadGroupMessages,
|
loadGroupMessages,
|
||||||
deriveIsGroupMember,
|
userIsGroupMember,
|
||||||
updateCurrentSession,
|
updateCurrentSession,
|
||||||
communityListsByAddress,
|
communityListsByAddress,
|
||||||
searchGroupMeta,
|
searchGroupMeta,
|
||||||
@ -26,7 +26,7 @@
|
|||||||
limit += 20
|
limit += 20
|
||||||
}
|
}
|
||||||
|
|
||||||
const userIsMember = meta => deriveIsGroupMember(getAddress(meta.event), true).get()
|
const userIsMember = meta => $userIsGroupMember(getAddress(meta.event), true)
|
||||||
|
|
||||||
const userGroupMeta = derived(groupMeta, filter(userIsMember))
|
const userGroupMeta = derived(groupMeta, filter(userIsMember))
|
||||||
|
|
||||||
|
@ -6,8 +6,7 @@
|
|||||||
|
|
||||||
export let topic
|
export let topic
|
||||||
|
|
||||||
const topics = ["web-of-trust", "nip-44-dms"]
|
const topics = ["web-of-trust", "nip-17-dms"]
|
||||||
const nip44Url = "https://github.com/nostr-protocol/nips/blob/master/44.md"
|
|
||||||
const nip17Url = "https://github.com/nostr-protocol/nips/blob/master/17.md"
|
const nip17Url = "https://github.com/nostr-protocol/nips/blob/master/17.md"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -29,11 +28,10 @@
|
|||||||
You can set a minimum web of trust score on your content settings page, which will
|
You can set a minimum web of trust score on your content settings page, which will
|
||||||
automatically mute anyone with a lower score than your threshold.
|
automatically mute anyone with a lower score than your threshold.
|
||||||
</p>
|
</p>
|
||||||
{:else if topic === "nip-44-dms"}
|
{:else if topic === "nip-17-dms"}
|
||||||
<p>
|
<p>
|
||||||
<Anchor underline external href={nip44Url}>NIP 44</Anchor> is a new encryption standard for nostr,
|
<Anchor underline external href={nip17Url}>NIP 17</Anchor> improves upon the old NIP 04 direct
|
||||||
which along with <Anchor underline external href={nip17Url}>NIP 17</Anchor> improves upon the old
|
messages standard by adding support for group chats and better metadata hiding.
|
||||||
NIP 04 direct messages standard by adding support for group chats and better metadata hiding.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
In the past, a significant amount of information about private messages was public, event
|
In the past, a significant amount of information about private messages was public, event
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
import PersonSelect from "src/app/shared/PersonSelect.svelte"
|
import PersonSelect from "src/app/shared/PersonSelect.svelte"
|
||||||
import {
|
import {
|
||||||
mention,
|
mention,
|
||||||
getSettings,
|
settings,
|
||||||
publishSettings,
|
publishSettings,
|
||||||
searchTopics,
|
searchTopics,
|
||||||
userMutes,
|
userMutes,
|
||||||
@ -23,12 +23,12 @@
|
|||||||
updateSingleton,
|
updateSingleton,
|
||||||
} from "src/engine"
|
} from "src/engine"
|
||||||
|
|
||||||
const settings = getSettings()
|
const values = {...$settings}
|
||||||
|
|
||||||
const searchWords = q => pluck("name", $searchTopics(q))
|
const searchWords = q => pluck("name", $searchTopics(q))
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
publishSettings(settings)
|
publishSettings(values)
|
||||||
updateSingleton(MUTES, () => mutedPubkeys.map(mention))
|
updateSingleton(MUTES, () => mutedPubkeys.map(mention))
|
||||||
|
|
||||||
showInfo("Your preferences have been saved!")
|
showInfo("Your preferences have been saved!")
|
||||||
@ -50,20 +50,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex w-full flex-col gap-8">
|
<div class="flex w-full flex-col gap-8">
|
||||||
<FieldInline label="Show likes on notes">
|
<FieldInline label="Show likes on notes">
|
||||||
<Toggle bind:value={settings.enable_reactions} />
|
<Toggle bind:value={values.enable_reactions} />
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
Show how many likes and reactions a note received. Disabling this can reduce how much data {appName}
|
Show how many likes and reactions a note received. Disabling this can reduce how much data {appName}
|
||||||
uses.
|
uses.
|
||||||
</p>
|
</p>
|
||||||
</FieldInline>
|
</FieldInline>
|
||||||
<FieldInline label="Show images and link previews">
|
<FieldInline label="Show images and link previews">
|
||||||
<Toggle bind:value={settings.show_media} />
|
<Toggle bind:value={values.show_media} />
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
If enabled, {appName} will automatically show images and previews for embedded links.
|
If enabled, {appName} will automatically show images and previews for embedded links.
|
||||||
</p>
|
</p>
|
||||||
</FieldInline>
|
</FieldInline>
|
||||||
<FieldInline label="Hide sensitive content">
|
<FieldInline label="Hide sensitive content">
|
||||||
<Toggle bind:value={settings.hide_sensitive} />
|
<Toggle bind:value={values.hide_sensitive} />
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
If enabled, content flagged by the author as potentially sensitive will be hidden.
|
If enabled, content flagged by the author as potentially sensitive will be hidden.
|
||||||
</p>
|
</p>
|
||||||
@ -71,9 +71,9 @@
|
|||||||
<Field>
|
<Field>
|
||||||
<div slot="label" class="flex justify-between">
|
<div slot="label" class="flex justify-between">
|
||||||
<strong>Minimum WoT score</strong>
|
<strong>Minimum WoT score</strong>
|
||||||
<div>{settings.min_wot_score}</div>
|
<div>{values.min_wot_score}</div>
|
||||||
</div>
|
</div>
|
||||||
<Input type="range" bind:value={settings.min_wot_score} min={-10} max={10} />
|
<Input type="range" bind:value={values.min_wot_score} min={-10} max={10} />
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
Select a minimum <Anchor underline modal href="/help/web-of-trust">web-of-trust</Anchor>
|
Select a minimum <Anchor underline modal href="/help/web-of-trust">web-of-trust</Anchor>
|
||||||
score. Notes from accounts with a lower score will be automatically hidden.
|
score. Notes from accounts with a lower score will be automatically hidden.
|
||||||
@ -86,7 +86,7 @@
|
|||||||
<Field label="Muted words and topics">
|
<Field label="Muted words and topics">
|
||||||
<SearchSelect
|
<SearchSelect
|
||||||
multiple
|
multiple
|
||||||
bind:value={settings.muted_words}
|
bind:value={values.muted_words}
|
||||||
search={searchWords}
|
search={searchWords}
|
||||||
termToItem={identity} />
|
termToItem={identity} />
|
||||||
<p slot="info">Notes containing these words will be hidden by default.</p>
|
<p slot="info">Notes containing these words will be hidden by default.</p>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
hints,
|
hints,
|
||||||
groupSharedKeys,
|
groupSharedKeys,
|
||||||
relaySearch,
|
relaySearch,
|
||||||
deriveIsGroupMember,
|
userIsGroupMember,
|
||||||
groupAdminKeys,
|
groupAdminKeys,
|
||||||
subscribe,
|
subscribe,
|
||||||
LOAD_OPTS,
|
LOAD_OPTS,
|
||||||
@ -104,7 +104,7 @@
|
|||||||
prop("group"),
|
prop("group"),
|
||||||
sortBy(
|
sortBy(
|
||||||
k => -k.created_at,
|
k => -k.created_at,
|
||||||
$groupSharedKeys.filter(k => deriveIsGroupMember(k.group).get()),
|
$groupSharedKeys.filter(k => $userIsGroupMember(k.group)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -9,14 +9,14 @@
|
|||||||
import Input from "src/partials/Input.svelte"
|
import Input from "src/partials/Input.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import Heading from "src/partials/Heading.svelte"
|
import Heading from "src/partials/Heading.svelte"
|
||||||
import {env, getSettings, publishSettings} from "src/engine"
|
import {env, settings, publishSettings} from "src/engine"
|
||||||
import SearchSelect from "src/partials/SearchSelect.svelte"
|
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||||
import {fuzzy} from "src/util/misc"
|
import {fuzzy} from "src/util/misc"
|
||||||
|
|
||||||
const settings = getSettings()
|
const values = {...$settings}
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
publishSettings(settings)
|
publishSettings(values)
|
||||||
|
|
||||||
showInfo("Your settings have been saved!")
|
showInfo("Your settings have been saved!")
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@
|
|||||||
const formatPercent = d => String(Math.round(d * 100))
|
const formatPercent = d => String(Math.round(d * 100))
|
||||||
const parsePercent = p => parseInt(p) / 100
|
const parsePercent = p => parseInt(p) / 100
|
||||||
|
|
||||||
$: settings.relay_redundancy = Math.round(Math.log10(settings.relay_limit) * 4)
|
$: values.relay_redundancy = Math.round(Math.log10(values.relay_limit) * 4)
|
||||||
|
|
||||||
document.title = "Settings"
|
document.title = "Settings"
|
||||||
</script>
|
</script>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex w-full flex-col gap-8">
|
<div class="flex w-full flex-col gap-8">
|
||||||
<Field label="Default zap amount">
|
<Field label="Default zap amount">
|
||||||
<Input bind:value={settings.default_zap}>
|
<Input bind:value={values.default_zap}>
|
||||||
<i slot="before" class="fa fa-bolt" />
|
<i slot="before" class="fa fa-bolt" />
|
||||||
</Input>
|
</Input>
|
||||||
<p slot="info">The default amount of sats to use when sending a lightning tip.</p>
|
<p slot="info">The default amount of sats to use when sending a lightning tip.</p>
|
||||||
@ -46,7 +46,7 @@
|
|||||||
<Field label="Platform zap split">
|
<Field label="Platform zap split">
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
bind:value={settings.platform_zap_split}
|
bind:value={values.platform_zap_split}
|
||||||
format={formatPercent}
|
format={formatPercent}
|
||||||
parse={parsePercent}>
|
parse={parsePercent}>
|
||||||
<i slot="before" class="fa fa-percent" />
|
<i slot="before" class="fa fa-percent" />
|
||||||
@ -58,9 +58,9 @@
|
|||||||
<Field>
|
<Field>
|
||||||
<div slot="label" class="flex justify-between">
|
<div slot="label" class="flex justify-between">
|
||||||
<strong>Max relays per request</strong>
|
<strong>Max relays per request</strong>
|
||||||
<div>{settings.relay_limit} relays</div>
|
<div>{values.relay_limit} relays</div>
|
||||||
</div>
|
</div>
|
||||||
<Input type="range" bind:value={settings.relay_limit} min={1} max={30} parse={parseInt} />
|
<Input type="range" bind:value={values.relay_limit} min={1} max={30} parse={parseInt} />
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
This controls how many relays to max out at when loading feeds and event context. More is
|
This controls how many relays to max out at when loading feeds and event context. More is
|
||||||
faster, but will require more bandwidth and processing power.
|
faster, but will require more bandwidth and processing power.
|
||||||
@ -68,7 +68,7 @@
|
|||||||
</Field>
|
</Field>
|
||||||
{#if !$env.FORCE_GROUP && $env.PLATFORM_RELAYS.length === 0}
|
{#if !$env.FORCE_GROUP && $env.PLATFORM_RELAYS.length === 0}
|
||||||
<FieldInline label="Authenticate with relays">
|
<FieldInline label="Authenticate with relays">
|
||||||
<Toggle bind:value={settings.auto_authenticate} />
|
<Toggle bind:value={values.auto_authenticate} />
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
Allows {appName} to authenticate with relays that have access controls automatically.
|
Allows {appName} to authenticate with relays that have access controls automatically.
|
||||||
</p>
|
</p>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<SearchSelect
|
<SearchSelect
|
||||||
multiple
|
multiple
|
||||||
search={searchUploadProviders}
|
search={searchUploadProviders}
|
||||||
bind:value={settings.nip96_urls}
|
bind:value={values.nip96_urls}
|
||||||
termToItem={identity}>
|
termToItem={identity}>
|
||||||
<div slot="item" let:item>
|
<div slot="item" let:item>
|
||||||
<strong>{item}</strong>
|
<strong>{item}</strong>
|
||||||
@ -92,7 +92,7 @@
|
|||||||
</SearchSelect>
|
</SearchSelect>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Dufflepud URL">
|
<Field label="Dufflepud URL">
|
||||||
<Input bind:value={settings.dufflepud_url}>
|
<Input bind:value={values.dufflepud_url}>
|
||||||
<i slot="before" class="fa-solid fa-server" />
|
<i slot="before" class="fa-solid fa-server" />
|
||||||
</Input>
|
</Input>
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
@ -104,7 +104,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Imgproxy URL">
|
<Field label="Imgproxy URL">
|
||||||
<Input bind:value={settings.imgproxy_url}>
|
<Input bind:value={values.imgproxy_url}>
|
||||||
<i slot="before" class="fa-solid fa-image" />
|
<i slot="before" class="fa-solid fa-image" />
|
||||||
</Input>
|
</Input>
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
@ -115,7 +115,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Multiplextr URL">
|
<Field label="Multiplextr URL">
|
||||||
<Input bind:value={settings.multiplextr_url}>
|
<Input bind:value={values.multiplextr_url}>
|
||||||
<i slot="before" class="fa-solid fa-code-merge" />
|
<i slot="before" class="fa-solid fa-code-merge" />
|
||||||
</Input>
|
</Input>
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
@ -128,14 +128,14 @@
|
|||||||
</p>
|
</p>
|
||||||
</Field>
|
</Field>
|
||||||
<FieldInline label="Report errors and analytics">
|
<FieldInline label="Report errors and analytics">
|
||||||
<Toggle bind:value={settings.report_analytics} />
|
<Toggle bind:value={values.report_analytics} />
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
Keep this enabled if you would like developers to be able to know what features are used,
|
Keep this enabled if you would like developers to be able to know what features are used,
|
||||||
and to diagnose and fix bugs.
|
and to diagnose and fix bugs.
|
||||||
</p>
|
</p>
|
||||||
</FieldInline>
|
</FieldInline>
|
||||||
<FieldInline label="Enable client fingerprinting">
|
<FieldInline label="Enable client fingerprinting">
|
||||||
<Toggle bind:value={settings.enable_client_tag} />
|
<Toggle bind:value={values.enable_client_tag} />
|
||||||
<p slot="info">
|
<p slot="info">
|
||||||
If this is turned on, public notes you create will have a "client" tag added. This helps
|
If this is turned on, public notes you create will have a "client" tag added. This helps
|
||||||
with troubleshooting, and allows other people to find out about {appName}.
|
with troubleshooting, and allows other people to find out about {appName}.
|
||||||
|
@ -30,10 +30,10 @@ export const readGroupMeta = (event: TrustedEvent) => {
|
|||||||
relays: event.tags.filter(nthEq(0, 'relay')),
|
relays: event.tags.filter(nthEq(0, 'relay')),
|
||||||
moderators: event.tags.filter(nthEq(0, 'p')),
|
moderators: event.tags.filter(nthEq(0, 'p')),
|
||||||
identifier: meta.d,
|
identifier: meta.d,
|
||||||
name: meta.name,
|
name: meta.name || "",
|
||||||
about: meta.about,
|
about: meta.about || "",
|
||||||
banner: meta.banner,
|
banner: meta.banner || "",
|
||||||
image: meta.image || meta.picture,
|
image: meta.image || meta.picture || "",
|
||||||
listing_is_public: isSignedEvent(event),
|
listing_is_public: isSignedEvent(event),
|
||||||
} as PublishedGroupMeta
|
} as PublishedGroupMeta
|
||||||
}
|
}
|
||||||
|
@ -54,10 +54,9 @@ import {
|
|||||||
loadOne,
|
loadOne,
|
||||||
createAndPublish,
|
createAndPublish,
|
||||||
deriveAdminKeyForGroup,
|
deriveAdminKeyForGroup,
|
||||||
deriveIsGroupMember,
|
userIsGroupMember,
|
||||||
deriveSharedKeyForGroup,
|
deriveSharedKeyForGroup,
|
||||||
displayProfileByPubkey,
|
displayProfileByPubkey,
|
||||||
getSettings,
|
|
||||||
env,
|
env,
|
||||||
getClientTags,
|
getClientTags,
|
||||||
groupAdminKeys,
|
groupAdminKeys,
|
||||||
@ -388,6 +387,8 @@ export const publishToGroupsPublicly = async (addresses, template, {anonymous =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const publishToGroupsPrivately = async (addresses, template, {anonymous = false} = {}) => {
|
export const publishToGroupsPrivately = async (addresses, template, {anonymous = false} = {}) => {
|
||||||
|
const $userIsGroupMember = userIsGroupMember.get()
|
||||||
|
|
||||||
const events = []
|
const events = []
|
||||||
const pubs = []
|
const pubs = []
|
||||||
for (const address of addresses) {
|
for (const address of addresses) {
|
||||||
@ -399,7 +400,7 @@ export const publishToGroupsPrivately = async (addresses, template, {anonymous =
|
|||||||
throw new Error("Attempted to publish privately to an invalid address", address)
|
throw new Error("Attempted to publish privately to an invalid address", address)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deriveIsGroupMember(address).get()) {
|
if (!$userIsGroupMember(address)) {
|
||||||
throw new Error("Attempted to publish privately to a group the user is not a member of")
|
throw new Error("Attempted to publish privately to a group the user is not a member of")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,6 +546,7 @@ export const publishCommunityMeta = (address, identifier, meta) => {
|
|||||||
tags: [
|
tags: [
|
||||||
["d", identifier],
|
["d", identifier],
|
||||||
["name", meta.name],
|
["name", meta.name],
|
||||||
|
["about", meta.about],
|
||||||
["description", meta.about],
|
["description", meta.about],
|
||||||
["banner", meta.banner],
|
["banner", meta.banner],
|
||||||
["picture", meta.image],
|
["picture", meta.image],
|
||||||
@ -565,6 +567,7 @@ export const publishGroupMeta = (address, identifier, meta, listPublicly) => {
|
|||||||
["d", identifier],
|
["d", identifier],
|
||||||
["name", meta.name],
|
["name", meta.name],
|
||||||
["about", meta.about],
|
["about", meta.about],
|
||||||
|
["description", meta.description],
|
||||||
["banner", meta.banner],
|
["banner", meta.banner],
|
||||||
["picture", meta.image],
|
["picture", meta.image],
|
||||||
["image", meta.image],
|
["image", meta.image],
|
||||||
@ -690,9 +693,7 @@ export const updateSingleton = async (kind: number, modifyTags: ModifyTags) => {
|
|||||||
|
|
||||||
// If we don't have a recent version loaded, re-fetch to avoid dropping updates
|
// If we don't have a recent version loaded, re-fetch to avoid dropping updates
|
||||||
if ((event?.created_at || 0) < now() - seconds(5, "minute")) {
|
if ((event?.created_at || 0) < now() - seconds(5, "minute")) {
|
||||||
console.log("loading")
|
|
||||||
const loadedEvent = await loadOne({relays: hints.User().getUrls(), filters})
|
const loadedEvent = await loadOne({relays: hints.User().getUrls(), filters})
|
||||||
console.log("loaded", loadedEvent)
|
|
||||||
|
|
||||||
if ((loadedEvent?.created_at || 0) > (event?.created_at || 0)) {
|
if ((loadedEvent?.created_at || 0) > (event?.created_at || 0)) {
|
||||||
event = loadedEvent
|
event = loadedEvent
|
||||||
@ -714,15 +715,12 @@ export const updateSingleton = async (kind: number, modifyTags: ModifyTags) => {
|
|||||||
} else {
|
} else {
|
||||||
const singleton = makeSingleton({kind})
|
const singleton = makeSingleton({kind})
|
||||||
const publicTags = modifyTags(singleton.publicTags)
|
const publicTags = modifyTags(singleton.publicTags)
|
||||||
|
|
||||||
encryptable = createSingleton({...singleton, publicTags})
|
encryptable = createSingleton({...singleton, publicTags})
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(1)
|
|
||||||
|
|
||||||
const template = await encryptable.reconcile(encrypt)
|
const template = await encryptable.reconcile(encrypt)
|
||||||
|
|
||||||
console.log(2, template)
|
|
||||||
|
|
||||||
await createAndPublish({...template, content, relays})
|
await createAndPublish({...template, content, relays})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1037,8 +1035,8 @@ export const setAppData = async (d: string, data: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const publishSettings = (updates: Record<string, any>) =>
|
export const publishSettings = ($settings: Record<string, any>) =>
|
||||||
setAppData(appDataKeys.USER_SETTINGS, {...getSettings(), ...updates})
|
setAppData(appDataKeys.USER_SETTINGS, $settings)
|
||||||
|
|
||||||
export const setSession = (k, data) => sessions.update($s => ($s[k] ? {...$s, [k]: data} : $s))
|
export const setSession = (k, data) => sessions.update($s => ($s[k] ? {...$s, [k]: data} : $s))
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
topics,
|
topics,
|
||||||
relays,
|
relays,
|
||||||
deriveAdminKeyForGroup,
|
deriveAdminKeyForGroup,
|
||||||
deriveGroupStatus,
|
getGroupStatus,
|
||||||
getChannelId,
|
getChannelId,
|
||||||
getSession,
|
getSession,
|
||||||
groupAdminKeys,
|
groupAdminKeys,
|
||||||
@ -71,7 +71,7 @@ projections.addHandler(24, (e: TrustedEvent) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const status = deriveGroupStatus(address).get()
|
const status = getGroupStatus(getSession(recipient), address)
|
||||||
|
|
||||||
if (privkey) {
|
if (privkey) {
|
||||||
const pubkey = getPublicKey(privkey)
|
const pubkey = getPublicKey(privkey)
|
||||||
|
@ -36,7 +36,7 @@ import {LIST_KINDS} from "src/domain"
|
|||||||
import type {Zapper} from "src/engine/model"
|
import type {Zapper} from "src/engine/model"
|
||||||
import {repository} from "src/engine/repository"
|
import {repository} from "src/engine/repository"
|
||||||
import {
|
import {
|
||||||
deriveUserCircles,
|
getUserCircles,
|
||||||
getGroupReqInfo,
|
getGroupReqInfo,
|
||||||
getCommunityReqInfo,
|
getCommunityReqInfo,
|
||||||
dvmRequest,
|
dvmRequest,
|
||||||
@ -175,7 +175,7 @@ export const loadGroups = async (rawAddrs: string[], explicitRelays: string[] =
|
|||||||
|
|
||||||
export const loadGroupMessages = (addresses?: string[]) => {
|
export const loadGroupMessages = (addresses?: string[]) => {
|
||||||
const promises = []
|
const promises = []
|
||||||
const addrs = addresses || deriveUserCircles().get()
|
const addrs = addresses || getUserCircles(session.get())
|
||||||
const [groupAddrs, communityAddrs] = partition(isGroupAddress, addrs)
|
const [groupAddrs, communityAddrs] = partition(isGroupAddress, addrs)
|
||||||
|
|
||||||
for (const address of groupAddrs) {
|
for (const address of groupAddrs) {
|
||||||
|
@ -75,6 +75,8 @@ import {
|
|||||||
getFilterResultCardinality,
|
getFilterResultCardinality,
|
||||||
isShareableRelayUrl,
|
isShareableRelayUrl,
|
||||||
isReplaceable,
|
isReplaceable,
|
||||||
|
isGroupAddress,
|
||||||
|
isCommunityAddress,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {Filter, RouterScenario, TrustedEvent, SignedEvent} from "@welshman/util"
|
import type {Filter, RouterScenario, TrustedEvent, SignedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
@ -215,13 +217,22 @@ export const getFreshness = (key: string, value: any) =>
|
|||||||
export const setFreshness = (key: string, value: any, ts: number) =>
|
export const setFreshness = (key: string, value: any, ts: number) =>
|
||||||
freshness.set(getFreshnessKey(key, value), ts)
|
freshness.set(getFreshnessKey(key, value), ts)
|
||||||
|
|
||||||
// Session and settings
|
// Session, signing, encryption
|
||||||
|
|
||||||
export const getSession = pubkey => sessions.get()[pubkey]
|
export const getSession = pubkey => sessions.get()[pubkey]
|
||||||
|
|
||||||
export const getCurrentSession = () => sessions.get()[pubkey.get()]
|
export const session = withGetter(derived([pubkey, sessions], ([$pk, $sessions]) => $sessions[$pk]))
|
||||||
|
|
||||||
export const getDefaultSettings = () => ({
|
export const connect = withGetter(derived(session, getConnect))
|
||||||
|
export const signer = withGetter(derived(session, getSigner))
|
||||||
|
export const nip04 = withGetter(derived(session, getNip04))
|
||||||
|
export const nip44 = withGetter(derived(session, getNip44))
|
||||||
|
export const nip59 = withGetter(derived(session, getNip59))
|
||||||
|
export const canSign = withGetter(derived(signer, $signer => $signer.isEnabled()))
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
|
||||||
|
export const defaultSettings = {
|
||||||
relay_limit: 10,
|
relay_limit: 10,
|
||||||
relay_redundancy: 3,
|
relay_redundancy: 3,
|
||||||
default_zap: 21,
|
default_zap: 21,
|
||||||
@ -238,11 +249,13 @@ export const getDefaultSettings = () => ({
|
|||||||
dufflepud_url: env.get().DUFFLEPUD_URL,
|
dufflepud_url: env.get().DUFFLEPUD_URL,
|
||||||
multiplextr_url: env.get().MULTIPLEXTR_URL,
|
multiplextr_url: env.get().MULTIPLEXTR_URL,
|
||||||
platform_zap_split: env.get().PLATFORM_ZAP_SPLIT,
|
platform_zap_split: env.get().PLATFORM_ZAP_SPLIT,
|
||||||
})
|
}
|
||||||
|
|
||||||
export const getSettings = () => ({...getDefaultSettings(), ...getSession(pubkey.get())?.settings})
|
export const settings = withGetter(
|
||||||
|
derived(session, $session => ({...defaultSettings, ...$session.settings})),
|
||||||
|
)
|
||||||
|
|
||||||
export const getSetting = k => prop(k, getSettings())
|
export const getSetting = k => prop(k, settings.get())
|
||||||
|
|
||||||
export const imgproxy = (url: string, {w = 640, h = 1024} = {}) => {
|
export const imgproxy = (url: string, {w = 640, h = 1024} = {}) => {
|
||||||
const base = getSetting("imgproxy_url")
|
const base = getSetting("imgproxy_url")
|
||||||
@ -270,19 +283,6 @@ export const dufflepud = (path: string) => {
|
|||||||
return `${base}/${path}`
|
return `${base}/${path}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const session = new Derived(
|
|
||||||
[pubkey, sessions],
|
|
||||||
([$pk, $sessions]: [string, Record<string, Session>]) => ($pk ? $sessions[$pk] : null),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const connect = session.derived(getConnect)
|
|
||||||
export const signer = session.derived(getSigner)
|
|
||||||
export const nip04 = session.derived(getNip04)
|
|
||||||
export const nip44 = session.derived(getNip44)
|
|
||||||
export const nip59 = session.derived(getNip59)
|
|
||||||
export const canSign = signer.derived($signer => $signer.isEnabled())
|
|
||||||
export const settings = derived(pubkey, getSettings)
|
|
||||||
|
|
||||||
// Plaintext
|
// Plaintext
|
||||||
|
|
||||||
export const getPlaintext = (e: TrustedEvent) => plaintext.get()[e.id]
|
export const getPlaintext = (e: TrustedEvent) => plaintext.get()[e.id]
|
||||||
@ -617,65 +617,6 @@ export const userMuteList = derived([muteListsByPubkey, pubkey], ([$m, $pk]) =>
|
|||||||
|
|
||||||
export const userMutes = derived(userMuteList, l => getSingletonValues("p", l))
|
export const userMutes = derived(userMuteList, l => getSingletonValues("p", l))
|
||||||
|
|
||||||
// Events
|
|
||||||
|
|
||||||
export const isEventMuted = withGetter(
|
|
||||||
derived(
|
|
||||||
[userMutes, userFollows, settings, pubkey],
|
|
||||||
([$userMutes, $userFollows, $settings, $pubkey]) => {
|
|
||||||
const words = $settings.muted_words
|
|
||||||
const minWot = $settings.min_wot_score
|
|
||||||
const regex =
|
|
||||||
words.length > 0 ? new RegExp(`\\b(${words.map(w => w.toLowerCase()).join("|")})\\b`) : null
|
|
||||||
|
|
||||||
return (e: Partial<TrustedEvent>, strict = false) => {
|
|
||||||
if (!$pubkey || e.pubkey === $pubkey) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const tags = Tags.wrap(e.tags || [])
|
|
||||||
const {roots, replies} = tags.ancestors()
|
|
||||||
|
|
||||||
if (
|
|
||||||
find(
|
|
||||||
t => $userMutes.has(t),
|
|
||||||
[e.id, e.pubkey, ...roots.values().valueOf(), ...replies.values().valueOf()],
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (regex && e.content?.toLowerCase().match(regex)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strict) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const isGroupMember = tags
|
|
||||||
.groups()
|
|
||||||
.values()
|
|
||||||
.some(a => deriveIsGroupMember(a).get())
|
|
||||||
const isCommunityMember = tags
|
|
||||||
.communities()
|
|
||||||
.values()
|
|
||||||
.some(a => false)
|
|
||||||
const wotAdjustment = isCommunityMember || isGroupMember ? 1 : 0
|
|
||||||
|
|
||||||
if (
|
|
||||||
!$userFollows.has(e.pubkey) &&
|
|
||||||
getWotScore($pubkey, e.pubkey) < minWot - wotAdjustment
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Channels
|
// Channels
|
||||||
|
|
||||||
export const sortChannels = $channels =>
|
export const sortChannels = $channels =>
|
||||||
@ -853,37 +794,36 @@ export const getGroupStatus = (session, address) =>
|
|||||||
(session?.groups?.[address] || {}) as GroupStatus
|
(session?.groups?.[address] || {}) as GroupStatus
|
||||||
|
|
||||||
export const deriveGroupStatus = address =>
|
export const deriveGroupStatus = address =>
|
||||||
session.derived($session => getGroupStatus($session, address))
|
derived(session, $session => getGroupStatus($session, address))
|
||||||
|
|
||||||
export const getIsGroupMember = (session, address, includeRequests = false) => {
|
export const userIsGroupMember = withGetter(
|
||||||
const status = getGroupStatus(session, address)
|
derived(session, $session => (address, includeRequests = false) => {
|
||||||
|
const status = getGroupStatus($session, address)
|
||||||
|
|
||||||
if (address.startsWith("34550:")) {
|
if (isCommunityAddress(address)) {
|
||||||
return status.joined
|
return status.joined
|
||||||
}
|
|
||||||
|
|
||||||
if (address.startsWith("35834:")) {
|
|
||||||
if (includeRequests && status.access === GroupAccess.Requested) {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status.access === GroupAccess.Granted
|
if (isGroupAddress(address)) {
|
||||||
}
|
if (includeRequests && status.access === GroupAccess.Requested) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return status.access === GroupAccess.Granted
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveIsGroupMember = (address, includeRequests = false) =>
|
return false
|
||||||
session.derived($session => getIsGroupMember($session, address, includeRequests))
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
export const deriveGroupOptions = (defaultGroups = []) =>
|
export const deriveGroupOptions = (defaultGroups = []) =>
|
||||||
session.derived($session => {
|
derived([session, userIsGroupMember], ([$session, $userIsGroupMember]) => {
|
||||||
const options = []
|
const options = []
|
||||||
|
|
||||||
for (const address of Object.keys($session?.groups || {})) {
|
for (const address of Object.keys($session?.groups || {})) {
|
||||||
const group = groups.key(address).get()
|
const group = groups.key(address).get()
|
||||||
|
|
||||||
if (group && deriveIsGroupMember(address).get()) {
|
if (group && $userIsGroupMember(address)) {
|
||||||
options.push(group)
|
options.push(group)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -895,22 +835,73 @@ export const deriveGroupOptions = (defaultGroups = []) =>
|
|||||||
return uniqBy(prop("address"), options)
|
return uniqBy(prop("address"), options)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getUserCircles = (session: Session) =>
|
export const getUserCircles = (session: Session) => {
|
||||||
Object.entries(session?.groups || {})
|
const $userIsGroupMember = userIsGroupMember.get()
|
||||||
.filter(([a, s]) => deriveIsGroupMember(a).get())
|
|
||||||
|
return Object.entries(session?.groups || {})
|
||||||
|
.filter(([a, s]) => $userIsGroupMember(a))
|
||||||
.map(([a, s]) => a)
|
.map(([a, s]) => a)
|
||||||
|
}
|
||||||
|
|
||||||
export const deriveUserCircles = () => session.derived(getUserCircles)
|
export const getUserGroups = (session: Session) => getUserCircles(session).filter(isGroupAddress)
|
||||||
|
|
||||||
export const getUserGroups = (session: Session) =>
|
|
||||||
getUserCircles(session).filter(a => a.startsWith("35834:"))
|
|
||||||
|
|
||||||
export const deriveUserGroups = () => session.derived(getUserGroups)
|
|
||||||
|
|
||||||
export const getUserCommunities = (session: Session) =>
|
export const getUserCommunities = (session: Session) =>
|
||||||
getUserCircles(session).filter(a => a.startsWith("34550:"))
|
getUserCircles(session).filter(isCommunityAddress)
|
||||||
|
|
||||||
export const deriveUserCommunities = () => session.derived(getUserCommunities)
|
// Events
|
||||||
|
|
||||||
|
export const isEventMuted = withGetter(
|
||||||
|
derived(
|
||||||
|
[userMutes, userFollows, settings, pubkey, userIsGroupMember],
|
||||||
|
([$userMutes, $userFollows, $settings, $pubkey, $userIsGroupMember]) => {
|
||||||
|
const words = $settings.muted_words
|
||||||
|
const minWot = $settings.min_wot_score
|
||||||
|
const regex =
|
||||||
|
words.length > 0 ? new RegExp(`\\b(${words.map(w => w.toLowerCase()).join("|")})\\b`) : null
|
||||||
|
|
||||||
|
return (e: Partial<TrustedEvent>, strict = false) => {
|
||||||
|
if (!$pubkey || e.pubkey === $pubkey) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = Tags.wrap(e.tags || [])
|
||||||
|
const {roots, replies} = tags.ancestors()
|
||||||
|
|
||||||
|
if (
|
||||||
|
find(
|
||||||
|
t => $userMutes.has(t),
|
||||||
|
[e.id, e.pubkey, ...roots.values().valueOf(), ...replies.values().valueOf()],
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regex && e.content?.toLowerCase().match(regex)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strict) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const isInGroup = tags.groups().values().some($userIsGroupMember)
|
||||||
|
const isInCommunity = tags
|
||||||
|
.communities()
|
||||||
|
.values()
|
||||||
|
.some(a => false)
|
||||||
|
const wotAdjustment = isInCommunity || isInGroup ? 1 : 0
|
||||||
|
|
||||||
|
if (
|
||||||
|
!$userFollows.has(e.pubkey) &&
|
||||||
|
getWotScore($pubkey, e.pubkey) < minWot - wotAdjustment
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
// Read receipts
|
// Read receipts
|
||||||
|
|
||||||
@ -935,19 +926,20 @@ export const isSeen = derived(allReadReceipts, $m => e => $m.has(e.id))
|
|||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
|
|
||||||
export const notifications = derived(events, $events => {
|
export const notifications = derived(
|
||||||
const $pubkey = pubkey.get()
|
[pubkey, events, isEventMuted],
|
||||||
const $isEventMuted = isEventMuted.get()
|
([$pubkey, $events, $isEventMuted]) => {
|
||||||
const kinds = [...noteKinds, ...reactionKinds]
|
const kinds = [...noteKinds, ...reactionKinds]
|
||||||
|
|
||||||
return Array.from(repository.query([{"#p": [$pubkey]}])).filter(
|
return Array.from(repository.query([{"#p": [$pubkey]}])).filter(
|
||||||
e =>
|
e =>
|
||||||
kinds.includes(e.kind) &&
|
kinds.includes(e.kind) &&
|
||||||
e.pubkey !== $pubkey &&
|
e.pubkey !== $pubkey &&
|
||||||
!$isEventMuted(e) &&
|
!$isEventMuted(e) &&
|
||||||
(e.kind !== 7 || isLike(e)),
|
(e.kind !== 7 || isLike(e)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
export const unreadNotifications = derived([isSeen, notifications], ([$isSeen, $notifications]) => {
|
export const unreadNotifications = derived([isSeen, notifications], ([$isSeen, $notifications]) => {
|
||||||
const since = now() - seconds(30, "day")
|
const since = now() - seconds(30, "day")
|
||||||
@ -958,14 +950,13 @@ export const unreadNotifications = derived([isSeen, notifications], ([$isSeen, $
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const groupNotifications = new Derived(
|
export const groupNotifications = new Derived(
|
||||||
[session, events, groupRequests, groupAlerts, groupAdminKeys],
|
[session, events, groupRequests, groupAlerts, groupAdminKeys, isEventMuted],
|
||||||
x => x,
|
x => x,
|
||||||
)
|
)
|
||||||
.throttle(3000)
|
.throttle(3000)
|
||||||
.derived(([$session, $events, $requests, $alerts, $adminKeys, $addresses]) => {
|
.derived(([$session, $events, $requests, $alerts, $adminKeys, $addresses, $isEventMuted]) => {
|
||||||
const addresses = new Set(getUserCircles($session))
|
const addresses = new Set(getUserCircles($session))
|
||||||
const adminPubkeys = new Set($adminKeys.map(k => k.pubkey))
|
const adminPubkeys = new Set($adminKeys.map(k => k.pubkey))
|
||||||
const $isEventMuted = isEventMuted.get()
|
|
||||||
|
|
||||||
const shouldSkip = e => {
|
const shouldSkip = e => {
|
||||||
const context = e.tags.filter(t => t[0] === "a")
|
const context = e.tags.filter(t => t[0] === "a")
|
||||||
@ -1718,7 +1709,7 @@ export const subscribePersistent = (request: MySubscribeRequest) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LOAD_OPTS = {timeout: 3000, closeOnEose: true}
|
export const LOAD_OPTS = {timeout: 5000, closeOnEose: true}
|
||||||
|
|
||||||
export const load = (request: MySubscribeRequest) =>
|
export const load = (request: MySubscribeRequest) =>
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
@ -1848,6 +1839,7 @@ export const mention = (pubkey: string, ...args: unknown[]) => [
|
|||||||
|
|
||||||
export const mentionGroup = (address: string, ...args: unknown[]) => [
|
export const mentionGroup = (address: string, ...args: unknown[]) => [
|
||||||
"a",
|
"a",
|
||||||
|
address,
|
||||||
hints.WithinContext(address).getUrl(),
|
hints.WithinContext(address).getUrl(),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
let limit = 10
|
let limit = 10
|
||||||
let showNewMessages = false
|
let showNewMessages = false
|
||||||
let groupedMessages = []
|
let groupedMessages = []
|
||||||
let useNip44 =
|
let useNip17 =
|
||||||
pubkeys.length > 2 ||
|
pubkeys.length > 2 ||
|
||||||
($nip44.isEnabled() &&
|
($nip44.isEnabled() &&
|
||||||
repository.query([{kinds: [INBOX_RELAYS], authors: pubkeys}]).length === pubkeys.length)
|
repository.query([{kinds: [INBOX_RELAYS], authors: pubkeys}]).length === pubkeys.length)
|
||||||
@ -78,7 +78,7 @@
|
|||||||
if (content) {
|
if (content) {
|
||||||
textarea.value = ""
|
textarea.value = ""
|
||||||
|
|
||||||
await sendMessage(content, useNip44)
|
await sendMessage(content, useNip17)
|
||||||
|
|
||||||
stickToBottom()
|
stickToBottom()
|
||||||
}
|
}
|
||||||
@ -161,18 +161,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if $nip44.isEnabled()}
|
{#if $nip44.isEnabled()}
|
||||||
<div class="fixed bottom-0 right-12 flex items-center justify-end gap-2 p-2">
|
<div class="fixed bottom-0 right-12 flex items-center justify-end gap-2 p-2">
|
||||||
<Toggle scale={0.7} bind:value={useNip44} />
|
<Toggle scale={0.7} bind:value={useNip17} />
|
||||||
<small>
|
<small>
|
||||||
Send messages using
|
Send messages using
|
||||||
<Popover class="inline">
|
<Popover class="inline">
|
||||||
<span slot="trigger" class="cursor-pointer underline">NIP 44</span>
|
<span slot="trigger" class="cursor-pointer underline">NIP 17</span>
|
||||||
<div slot="tooltip" class="flex flex-col gap-2">
|
<div slot="tooltip" class="flex flex-col gap-2">
|
||||||
<p>
|
<p>
|
||||||
When enabled, Coracle will use nostr's new group chat specification, which solves
|
When enabled, Coracle will use nostr's new group chat specification, which solves
|
||||||
several problems with legacy DMs. Read more <Anchor
|
several problems with legacy DMs. Read more <Anchor
|
||||||
underline
|
underline
|
||||||
modal
|
modal
|
||||||
href="/help/nip-44-dms">here</Anchor
|
href="/help/nip-17-dms">here</Anchor
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
Loading…
Reference in New Issue
Block a user