fix some community/calendar bugs

This commit is contained in:
Jon Staab 2024-06-17 15:42:17 -07:00
parent c15453b996
commit 1dccb3e95c
13 changed files with 106 additions and 111 deletions

View File

@ -125,7 +125,7 @@
class="staatliches px-8 text-tinted-400 hover:text-tinted-100"
on:click={() => setSubMenu("settings")}>Settings</Anchor>
<div class="staatliches block flex h-8 gap-2 px-8 text-tinted-500">
<Anchor external class="hover:text-tinted-100" href="/about">About</Anchor> /
<Anchor class="hover:text-tinted-100" href="/about">About</Anchor> /
<Anchor external class="hover:text-tinted-100" href="/terms.html">Terms</Anchor> /
<Anchor external class="hover:text-tinted-100" href="/privacy.html">Privacy</Anchor>
</div>

View File

@ -1,31 +1,41 @@
<script lang="ts">
import {fromPairs} from "ramda"
import {batch} from "hurdak"
import {onMount, onDestroy} from "svelte"
import {writable} from "@welshman/lib"
import {onMount} from "svelte"
import {fromPairs} from "@welshman/lib"
import {getAddress, getReplyFilters} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {feedFromFilter} from "@welshman/feeds"
import Calendar from "@event-calendar/core"
import DayGrid from "@event-calendar/day-grid"
import Interaction from "@event-calendar/interaction"
import {secondsToDate} from "src/util/misc"
import {themeColors} from "src/partials/state"
import Anchor from "src/partials/Anchor.svelte"
import {
hints,
load,
pubkey,
canSign,
repository,
subscribe,
feedLoader,
getFilterSelections,
} from "src/engine"
import {hints, load, pubkey, canSign, loadAll, deriveEventsMapped} from "src/engine"
import {router} from "src/app/util/router"
export let feed
export let filter
export let group = null
const calendarEvents = deriveEventsMapped({
filters: [filter],
itemToEvent: (item: any) => item.event,
eventToItem: (event: TrustedEvent) => {
const meta = fromPairs(event.tags)
const isOwn = event.pubkey === $pubkey
return {
event,
editable: isOwn,
id: getAddress(event),
title: meta.title || meta.name, // Backwards compat with a bug
start: secondsToDate(meta.start),
end: secondsToDate(meta.end),
backgroundColor: $themeColors[isOwn ? "accent" : "neutral-100"],
}
},
})
const createEvent = () => router.at("notes/create").qp({type: "calendar_event", group}).open()
const getEventContent = ({event}) => event.title
@ -45,55 +55,17 @@
const onEventClick = ({event: calendarEvent}) => router.at("events").of(calendarEvent.id).open()
const events = writable(new Map())
const onEvent = batch(300, (chunk: TrustedEvent[]) => {
events.update($events => {
for (const e of chunk) {
const addr = getAddress(e)
const dup = $events.get(addr)
// Make sure we have the latest version of every event
$events.set(addr, dup?.created_at > e.created_at ? dup : e)
}
return $events
})
// Load deletes for these events
load({
relays: hints.merge(chunk.map(e => hints.EventChildren(e))).getUrls(),
filters: getReplyFilters(chunk, {kinds: [5]}),
onMount(() => {
loadAll(feedFromFilter(filter), {
// Load deletes for these events
onEvent: batch(300, (chunk: TrustedEvent[]) => {
load({
relays: hints.merge(chunk.map(e => hints.EventChildren(e))).getUrls(),
filters: getReplyFilters(chunk, {kinds: [5]}),
})
}),
})
})
let subs = []
onMount(async () => {
const [{filters}] = await feedLoader.compiler.compile(feed)
subs = getFilterSelections(filters).map(({relay, filters}) =>
subscribe({relays: [relay], filters, onEvent}),
)
})
onDestroy(() => subs.map(sub => sub.close()))
$: calendarEvents = Array.from($events.values())
.filter(e => !repository.isDeleted(e))
.map(e => {
const meta = fromPairs(e.tags)
const isOwn = e.pubkey === $pubkey
return {
editable: isOwn,
id: getAddress(e),
title: meta.title || meta.name, // Backwards compat with a bug
start: secondsToDate(meta.start),
end: secondsToDate(meta.end),
backgroundColor: $themeColors[isOwn ? "accent" : "neutral-100"],
}
})
</script>
{#if $canSign}
@ -110,7 +82,7 @@
plugins={[Interaction, DayGrid]}
options={{
view: "dayGridMonth",
events: calendarEvents,
events: $calendarEvents,
dateClick: onDateClick,
eventClick: onEventClick,
eventContent: getEventContent,

View File

@ -1,8 +1,8 @@
<script lang="ts">
import {feedFromFilter} from "@welshman/feeds"
import {EVENT_TIME} from "@welshman/util"
import Calendar from "src/app/shared/Calendar.svelte"
export let address
</script>
<Calendar group={address} feed={feedFromFilter({kinds: [31923], "#a": [address]})} />
<Calendar group={address} filter={{kinds: [EVENT_TIME], "#a": [address]}} />

View File

@ -1,19 +1,25 @@
<script>
import {ellipsize} from "hurdak"
import {derived} from "svelte/store"
import {remove} from "@welshman/lib"
import {remove, intersection} 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, userFollowsByCommunity, pubkey} from "src/engine"
import {displayGroup, deriveGroup, userFollows, communityListsByAddress, pubkey} from "src/engine"
export let address
export let modal = false
const group = deriveGroup(address)
const members = derived(userFollowsByCommunity, $m => remove($pubkey, $m.get(address) || []))
const members = derived(communityListsByAddress, $m => {
const allMembers = $m.get(address)?.map(l => l.event.pubkey) || []
const otherMembers = remove($pubkey, allMembers)
const followMembers = intersection(otherMembers, Array.from($userFollows))
return followMembers
})
const enter = () => {
const route = router.at("groups").of(address).at("notes")

View File

@ -1,4 +1,5 @@
<script lang="ts">
import {isGroupAddress} from "@welshman/util"
import Card from "src/partials/Card.svelte"
import Anchor from "src/partials/Anchor.svelte"
import PersonSummary from "src/app/shared/PersonSummary.svelte"
@ -24,7 +25,7 @@
<Card interactive on:click={() => openPerson(pubkey)}>
<PersonSummary inert {pubkey}>
<div slot="actions" on:click|stopPropagation>
{#if $adminKey && pubkey !== $session.pubkey}
{#if $adminKey && pubkey !== $session.pubkey && isGroupAddress(address)}
<Anchor on:click={remove} button accent>Remove</Anchor>
{/if}
</div>

View File

@ -1,15 +1,26 @@
<script lang="ts">
import {COMMUNITIES} from "@welshman/util"
import FlexColumn from "src/partials/FlexColumn.svelte"
import GroupMember from "src/app/shared/GroupMember.svelte"
import {deriveGroup} from 'src/engine'
import {load, hints, deriveGroup, communityListsByAddress} from "src/engine"
export let address
const group = deriveGroup(address)
const filters = [{kinds: [COMMUNITIES], "#a": [address]}]
$: members =
$group.members || $communityListsByAddress.get(address)?.map(l => l.event.pubkey) || []
load({
filters,
skipCache: true,
relays: hints.merge([hints.WithinContext(address), hints.User()]).getUrls(),
})
</script>
<FlexColumn>
{#each $group.members || [] as pubkey (pubkey)}
{#each members as pubkey (pubkey)}
<GroupMember {address} {pubkey} />
{:else}
<p class="text-center py-12">No members found.</p>

View File

@ -97,7 +97,7 @@
<form on:submit|preventDefault={() => onSubmit()}>
<AltColor background class="z-feature flex gap-4 overflow-hidden rounded p-3 text-neutral-100">
<PersonCircle class="h-10 w-10" pubkey={$pubkey} />
<div class="w-full">
<div class="w-full min-w-0">
<Compose placeholder="What's up?" bind:this={compose} {onSubmit} style="min-height: 3em;" />
<div class="p-2">
<NoteImages bind:this={images} bind:compose includeInContent />

View File

@ -1,21 +1,15 @@
<script lang="ts">
import {
Scope,
feedFromFilter,
makeIntersectionFeed,
makeKindFeed,
makeScopeFeed,
} from "@welshman/feeds"
import {identity} from "ramda"
import Calendar from "src/app/shared/Calendar.svelte"
import {env, loadGroupMessages} from "src/engine"
import {env, loadGroupMessages, pubkey, userFollows} from "src/engine"
const feed = $env.FORCE_GROUP
? feedFromFilter({kinds: [31923], "#a": [$env.FORCE_GROUP]})
: makeIntersectionFeed(makeKindFeed(31923), makeScopeFeed(Scope.Self, Scope.Follows))
const filter = $env.FORCE_GROUP
? {kinds: [31923], "#a": [$env.FORCE_GROUP]}
: {kinds: [31923], authors: [$pubkey, ...$userFollows].filter(identity)}
if ($env.FORCE_GROUP) {
loadGroupMessages([$env.FORCE_GROUP])
}
</script>
<Calendar {feed} />
<Calendar {filter} />

View File

@ -71,7 +71,7 @@
tabs.push("market")
}
if ($sharedKey) {
if ($sharedKey || address.startsWith("34550")) {
tabs.push("members")
} else if (activeTab === "members") {
activeTab = "notes"

View File

@ -1,7 +1,8 @@
<script>
import {onMount} from "svelte"
import {filter, assoc} from "ramda"
import {now} from "@welshman/lib"
import {now, shuffle} from "@welshman/lib"
import {GROUP, COMMUNITY, getIdFilters} from "@welshman/util"
import {createScroller} from "src/util/misc"
import Anchor from "src/partials/Anchor.svelte"
import FlexColumn from "src/partials/FlexColumn.svelte"
@ -12,11 +13,11 @@
hints,
groups,
repository,
getGroupReqInfo,
loadGiftWraps,
loadGroupMessages,
deriveIsGroupMember,
updateCurrentSession,
communityListsByAddress,
searchGroups,
} from "src/engine"
@ -41,9 +42,10 @@
document.title = "Groups"
onMount(() => {
const {admins} = getGroupReqInfo()
const scroller = createScroller(loadMore, {element})
const loader = loadGiftWraps()
const scroller = createScroller(loadMore, {element})
const communityAddrs = Array.from($communityListsByAddress.keys())
.filter(a => !groups.key(a).get()?.meta)
updateCurrentSession(assoc("groups_last_synced", now()))
@ -51,15 +53,15 @@
load({
skipCache: true,
forcePlatform: false,
relays: hints.User().getUrls(),
filters: [{kinds: [35834, 34550], authors: admins}],
filters: [{kinds: [GROUP, COMMUNITY], limit: 1000 - communityAddrs.length}],
})
load({
skipCache: true,
forcePlatform: false,
relays: hints.User().getUrls(),
filters: [{kinds: [35834, 34550], limit: 500}],
filters: getIdFilters(shuffle(communityAddrs).slice(0, 1000)),
})
return () => {

View File

@ -6,7 +6,9 @@ import {
PROFILE,
HANDLER_INFORMATION,
NAMED_BOOKMARKS,
COMMUNITIES,
FEED,
MUTES,
FOLLOWS,
APP_DATA,
} from "@welshman/util"
@ -50,10 +52,10 @@ const getFiltersForKey = (key: string, authors: string[]) => {
case "pubkey/relays":
return [{authors, kinds: [RELAYS]}]
case "pubkey/profile":
return [{authors, kinds: [PROFILE, FOLLOWS, HANDLER_INFORMATION]}]
return [{authors, kinds: [PROFILE, FOLLOWS, HANDLER_INFORMATION, COMMUNITIES]}]
case "pubkey/user":
return [
{authors, kinds: [PROFILE, RELAYS, FOLLOWS, APP_DATA]},
{authors, kinds: [PROFILE, RELAYS, MUTES, FOLLOWS, COMMUNITIES, APP_DATA]},
{authors, kinds: [APP_DATA], "#d": Object.values(appDataKeys)},
]
}

View File

@ -35,6 +35,7 @@ import {
uniq,
uniqBy,
now,
intersection,
sort,
groupBy,
indexBy,
@ -693,6 +694,18 @@ export const communityListsByPubkey = withGetter(
derived(communityLists, $ls => indexBy($l => $l.event.pubkey, $ls)),
)
export const communityListsByAddress = derived(communityLists, $communityLists => {
const m = new Map<string, PublishedSingleton[]>()
for (const list of $communityLists) {
for (const a of getSingletonValues("a", list)) {
pushToMapKey(m, a, list)
}
}
return m
})
export const getCommunityList = (pk: string) =>
communityListsByPubkey.get().get(pk) as PublishedSingleton | undefined
@ -704,18 +717,6 @@ export const getCommunities = (pk: string) => getSingletonValues("a", getCommuni
export const deriveCommunities = (pk: string) =>
derived(communityListsByPubkey, m => getSingletonValues("a", m.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
export const deriveGroup = address => {
@ -725,11 +726,17 @@ export const deriveGroup = address => {
}
export const searchGroups = derived(
[groups.throttle(300), userFollowsByCommunity],
([$groups, $userFollowsByCommunity]) => {
[groups.throttle(300), communityListsByAddress, userFollows],
([$groups, $communityListsByAddress, $userFollows]) => {
const options = $groups
.filter(group => !repository.deletes.has(group.address))
.map(group => ({group, score: $userFollowsByCommunity.get(group.address)?.length || 0}))
.map(group => {
const lists = $communityListsByAddress.get(group.address) || []
const members = lists.map(l => l.event.pubkey)
const followedMembers = intersection(members, $userFollows)
return {group, score: followedMembers.length}
})
const fuse = new Fuse(options, {
keys: [{name: "group.id", weight: 0.2}, "group.meta.name", "group.meta.about"],
@ -1620,7 +1627,7 @@ export const publish = ({forcePlatform = true, ...request}: MyPublishRequest) =>
return pub
}
export const sign = (template, opts: {anonymous?: boolean; sk?: string}) => {
export const sign = (template, opts: {anonymous?: boolean; sk?: string} = {}) => {
if (opts.anonymous) {
return signer.get().signWithKey(template, generatePrivateKey())
}

View File

@ -90,7 +90,7 @@
</div>
<div
style={$$props.style || "min-height: 6rem"}
class={cx($$props.class, "w-full min-w-0 outline-0")}
class={cx($$props.class, "w-full min-w-0 whitespace-pre-line outline-0")}
{autofocus}
contenteditable
bind:this={input}