mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
fix some community/calendar bugs
This commit is contained in:
parent
c15453b996
commit
1dccb3e95c
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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]}} />
|
||||
|
@ -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")
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 />
|
||||
|
@ -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} />
|
||||
|
@ -71,7 +71,7 @@
|
||||
tabs.push("market")
|
||||
}
|
||||
|
||||
if ($sharedKey) {
|
||||
if ($sharedKey || address.startsWith("34550")) {
|
||||
tabs.push("members")
|
||||
} else if (activeTab === "members") {
|
||||
activeTab = "notes"
|
||||
|
@ -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 () => {
|
||||
|
@ -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)},
|
||||
]
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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}
|
||||
|
Loading…
Reference in New Issue
Block a user