feat: add space search to recent activity page (#59) #119

Merged
hodlbod merged 5 commits from :feature/59-space-search into dev 2026-04-03 16:58:35 +00:00
Showing only changes of commit dc6ce7bf86 - Show all commits
+73 -9
View File
@@ -15,10 +15,13 @@
getIdAndAddress,
} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {repository} from "@welshman/app"
import {repository, createSearch} from "@welshman/app"
import History from "@assets/icons/history.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import {createScroller} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import PageContent from "@lib/components/PageContent.svelte"
import SpaceBar from "@app/components/SpaceBar.svelte"
import NoteItem from "@app/components/NoteItem.svelte"
@@ -28,6 +31,7 @@
import CalendarEventItem from "@app/components/CalendarEventItem.svelte"
import RecentConversation from "@app/components/RecentConversation.svelte"
import {decodeRelay, deriveEventsForUrl, CONTENT_KINDS} from "@app/core/state"
import {goToEvent} from "@app/util/routes"
const url = decodeRelay($page.params.relay!)
const since = ago(3, MONTH)
@@ -36,15 +40,17 @@
const content = deriveEventsForUrl(url, [{kinds: CONTENT_KINDS, since}])
const comments = deriveEventsForUrl(url, [{kinds: [COMMENT], since}])
type ActivityItem = {
type: "message" | "content"
event: TrustedEvent
count: number
timestamp: number
}
const recentActivity = derived(
[messages, content, comments],
([$messages, $content, $comments]) => {
const activity: Array<{
type: "message" | "content"
event: TrustedEvent
count: number
timestamp: number
}> = []
const activity: Array<ActivityItem> = []
const byRoom = groupBy(e => getTagValue("h", e.tags), $messages)
for (const roomMessages of byRoom.values()) {
@@ -88,9 +94,40 @@
},
)
const allSpaceEvents = derived([messages, content], ([$messages, $content]) => [
...$messages,
...$content,
])
const searchIndex = $derived.by(() =>
createSearch($allSpaceEvents, {
getValue: (event: TrustedEvent) => event.id,
fuseOptions: {keys: ["content", "tags.1"]},
}),
)
Outdated
Review

Let's switch to pure NIP 50 search, I think we need to move away from storing so many events in memory.

Let's switch to pure NIP 50 search, I think we need to move away from storing so many events in memory.
let term = $state("")
let limit = $state(20)
let element: Element | undefined = $state()
const searchResults = $derived(term ? searchIndex.searchOptions(term) : [])
const filteredActivity = $derived.by(() => {
if (!term) return $recentActivity
const matchedIds = new Set(searchResults.map((e: TrustedEvent) => e.id))
return $recentActivity.filter(a => matchedIds.has(a.event.id))
})
const clearSearch = () => {
term = ""
}
const onSearchResultClick = (event: TrustedEvent) => {
goToEvent(event, {keepFocus: true})
}
onMount(() => {
const scroller = createScroller({
element: element!,
@@ -112,10 +149,37 @@
<div bind:this={element}>
<PageContent class="flex flex-col gap-2 p-2 pt-4">
{#if $recentActivity.length === 0}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon size={4} icon={Magnifier} />
<input
bind:value={term}
class="min-w-0 grow"
type="text"
placeholder="Search this space..." />
{#if term}
<Button onclick={clearSearch} class="btn btn-ghost btn-xs btn-square">
<Icon size={4} icon={CloseCircle} />
</Button>
{/if}
</label>
{#if term && searchResults.length > 0 && filteredActivity.length === 0}
<p class="text-xs uppercase tracking-wide opacity-60 pt-2">Search Results</p>
{#each searchResults as event (event.id)}
<button
class="card2 card2-sm bg-alt transition-colors hover:bg-base-200 text-left"
onclick={() => onSearchResultClick(event)}>
<p class="line-clamp-2 text-sm">
{event.content.trim() || getTagValue("title", event.tags) || "(No text content)"}
</p>
</button>
{/each}
{:else if term && searchResults.length === 0}
<p class="flex flex-col items-center py-20 text-center">No results found.</p>
{:else if filteredActivity.length === 0}
hodlbod marked this conversation as resolved Outdated
Outdated
Review

Take a look at how the room search works — we should add a search icon to the PageBar which shows the same kind of popover/dialog when clicked.

Take a look at how the room search works — we should add a search icon to the PageBar which shows the same kind of popover/dialog when clicked.
<p class="flex flex-col items-center py-20 text-center">No recent activity found!</p>
{:else}
{#each $recentActivity.slice(0, limit) as { type, event, count = 0 } (event.id)}
{#each filteredActivity.slice(0, limit) as { type, event, count = 0 } (event.id)}
Review

searchId/currentSearchId are redundant with AbortController, they can be removed

searchId/currentSearchId are redundant with AbortController, they can be removed
{#if type === "message"}
<RecentConversation {url} {event} {count} />
{:else if event.kind === THREAD}