diff --git a/src/app/requests.ts b/src/app/requests.ts
index 7a5e7c7b..3d9bdaee 100644
--- a/src/app/requests.ts
+++ b/src/app/requests.ts
@@ -8,6 +8,7 @@ import {
COMMENT,
matchFilters,
getTagValues,
+ getTagValue,
} from "@welshman/util"
import type {TrustedEvent, Filter} from "@welshman/util"
import {feedFromFilters, makeRelayFeed, makeIntersectionFeed} from "@welshman/feeds"
@@ -155,16 +156,25 @@ export const makeCalendarFeed = ({
onExhausted?: () => void
initialEvents?: TrustedEvent[]
}) => {
- const events = writable(initialEvents)
-
+ let exhaustedScrollers = 0
let backwardWindow = [now() - MONTH, now()]
let forwardWindow = [now(), now() + MONTH]
+ const getStart = (event: TrustedEvent) => parseInt(getTagValue("start", event.tags) || "")
+
+ const getEnd = (event: TrustedEvent) => parseInt(getTagValue("end", event.tags) || "")
+
+ const events = writable(sortBy(getStart, initialEvents))
+
const insertEvent = (event: TrustedEvent) => {
+ const start = getStart(event)
+
+ if (isNaN(start) || isNaN(getEnd(event))) return
+
events.update($events => {
for (let i = 0; i < $events.length; i++) {
if ($events[i].id === event.id) return $events
- if ($events[i].created_at < event.created_at) return insert(i, event, $events)
+ if (getStart($events[i]) > start) return insert(i, event, $events)
}
return [...$events, event]
@@ -201,8 +211,6 @@ export const makeCalendarFeed = ({
const loadTimeframe = (since: number, until: number) => {
const hashes = daysBetween(since, until).map(String)
- console.log(since, until, hashes)
-
load({
relays,
filters: [{kinds: [EVENT_TIME], "#D": hashes}],
@@ -210,6 +218,12 @@ export const makeCalendarFeed = ({
})
}
+ const maybeExhausted = () => {
+ if (++exhaustedScrollers === 2) {
+ onExhausted?.()
+ }
+ }
+
const backwardScroller = createScroller({
element,
reverse: true,
@@ -222,6 +236,7 @@ export const makeCalendarFeed = ({
loadTimeframe(since, until)
} else {
backwardScroller.stop()
+ maybeExhausted()
}
},
})
@@ -237,6 +252,7 @@ export const makeCalendarFeed = ({
loadTimeframe(since, until)
} else {
forwardScroller.stop()
+ maybeExhausted()
}
},
})
diff --git a/src/lib/components/Divider.svelte b/src/lib/components/Divider.svelte
index 8861dbcf..12fc59d4 100644
--- a/src/lib/components/Divider.svelte
+++ b/src/lib/components/Divider.svelte
@@ -1,15 +1,16 @@
-
+
{#if children}
{@render children?.()}
-
+
{/if}
diff --git a/src/routes/spaces/[relay]/calendar/+page.svelte b/src/routes/spaces/[relay]/calendar/+page.svelte
index bb57bf06..5e3ca708 100644
--- a/src/routes/spaces/[relay]/calendar/+page.svelte
+++ b/src/routes/spaces/[relay]/calendar/+page.svelte
@@ -3,10 +3,11 @@
import type {Readable} from "svelte/store"
import {readable} from "svelte/store"
import {page} from "$app/stores"
- import {sortBy, now, last} from "@welshman/lib"
+ import {now, last} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {REACTION, DELETE, EVENT_TIME, getTagValue} from "@welshman/util"
import {formatTimestampAsDate} from "@welshman/app"
+ import {fly} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
@@ -24,8 +25,6 @@
const createEvent = () => pushModal(EventCreate, {url})
- const getEnd = (event: TrustedEvent) => parseInt(getTagValue("end", event.tags) || "")
-
const getStart = (event: TrustedEvent) => parseInt(getTagValue("start", event.tags) || "")
let element: HTMLElement
@@ -36,23 +35,59 @@
type Item = {
event: TrustedEvent
dateDisplay?: string
+ isFirstFutureEvent?: boolean
}
- const items = $derived(
- sortBy(e => getStart(e), $events).reduce- ((r, event) => {
- const end = getEnd(event)
- const start = getStart(event)
+ const items = $derived.by(() => {
+ const todayDateDisplay = formatTimestampAsDate(now())
- if (isNaN(start) || isNaN(end)) return r
+ let haveISeenTheFuture = false
+ let prevDateDisplay: string
- const prevDateDisplay =
- r.length > 0 ? formatTimestampAsDate(getStart(last(r).event)) : undefined
- const newDateDisplay = formatTimestampAsDate(start)
+ return $events.map
- (event => {
+ const newDateDisplay = formatTimestampAsDate(getStart(event))
const dateDisplay = prevDateDisplay === newDateDisplay ? undefined : newDateDisplay
+ const isFuture = todayDateDisplay === newDateDisplay || event.created_at > now()
+ const isFirstFutureEvent = !haveISeenTheFuture && isFuture
- return [...r, {event, dateDisplay}]
- }, []),
- )
+ prevDateDisplay = newDateDisplay
+ haveISeenTheFuture = isFuture
+
+ return {event, dateDisplay, isFirstFutureEvent}
+ })
+ })
+
+ let previousScrollHeight = 0
+ let prevFirstEventId = ""
+ let initialScrollDone = false
+
+ $effect(() => {
+ if (initialScrollDone) {
+ // If new events are prepended, adjust the scroll position so that the viewport content remains anchored
+ if (prevFirstEventId && items[0].event.id !== prevFirstEventId) {
+ const newScrollHeight = element.scrollHeight
+ const delta = newScrollHeight - previousScrollHeight
+
+ if (delta > 0) {
+ element.scrollTop += delta
+ }
+ }
+ } else if (items.length > 0) {
+ const {event} = items.find(({event}) => getStart(event) >= now()) || last(items)
+ const {offsetTop, clientHeight} = document.querySelector(
+ ".calendar-event-" + event.id,
+ ) as HTMLElement
+
+ // On initial load, center the scroll container on today's date (or the next available event)
+ element.scrollTop = offsetTop - element.clientHeight / 2 + clientHeight / 2
+ initialScrollDone = true
+ }
+
+ if (items.length > 0) {
+ previousScrollHeight = element.scrollHeight
+ prevFirstEventId = items[0].event.id
+ }
+ })
onMount(() => {
const feedFilters = [{kinds: [EVENT_TIME], "#h": [GENERAL]}]
@@ -95,21 +130,30 @@
{/snippet}