Skip to content

Commit

Permalink
Search users
Browse files Browse the repository at this point in the history
  • Loading branch information
SnowCait committed Nov 27, 2024
1 parent 5f28d2e commit 57dd54e
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 19 deletions.
11 changes: 9 additions & 2 deletions web/src/lib/RxNostrHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ export async function fetchLastEvent(filter: LazyFilter): Promise<Event | undefi
});
}

export async function fetchEvents(filters: LazyFilter[]): Promise<Event[]> {
export async function fetchEvents(
filters: LazyFilter[],
relays: string[] | undefined = undefined
): Promise<Event[]> {
const { promise, resolve } = Promise.withResolvers<Event[]>();
const events: Event[] = [];
const req = createRxBackwardReq();
Expand All @@ -68,7 +71,11 @@ export async function fetchEvents(filters: LazyFilter[]): Promise<Event[]> {
resolve(events);
}
});
req.emit(filters);
if (relays !== undefined && relays.length > 0) {
req.emit(filters, { relays });
} else {
req.emit(filters);
}
req.over();
return await promise;
}
4 changes: 3 additions & 1 deletion web/src/lib/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@
"search": "Search",
"options": "Search options",
"mine": "My notes",
"proxy": "Include external SNS notes"
"proxy": "Include external SNS notes",
"notes": "Notes",
"users": "Users"
},
"public_chat": {
"create_channel": "Create channel",
Expand Down
4 changes: 3 additions & 1 deletion web/src/lib/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@
"search": "検索",
"options": "検索オプション",
"mine": "自分の投稿",
"proxy": "外部 SNS の投稿を含める"
"proxy": "外部 SNS の投稿を含める",
"notes": "投稿",
"users": "ユーザー"
},
"public_chat": {
"create_channel": "チャンネルを作成",
Expand Down
95 changes: 95 additions & 0 deletions web/src/lib/timelines/SearchTimeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { createRxBackwardReq, uniq, type LazyFilter } from 'rx-nostr';
import { filter, tap } from 'rxjs';
import { get, writable } from 'svelte/store';
import { authorActionReqEmit } from '$lib/author/Action';
import { minTimelineLength, searchRelays } from '$lib/Constants';
import { EventItem } from '$lib/Items';
import { fetchEvents } from '$lib/RxNostrHelper';
import { referencesReqEmit, rxNostr } from './MainTimeline';
import type { Timeline } from './Timeline';
import { oldestCreatedAt } from './TimelineHelper';

export class SearchTimeline implements Timeline {
items = writable<EventItem[]>([]);
#completed = false;

constructor(public readonly filter: LazyFilter) {}

subscribe(): void {
console.debug('[search timeline subscribe]', this.filter);
}

unsubscribe(): void {
console.debug('[search timeline unsubscribe]', this.filter);
this.items.set([]);
}

async load(): Promise<void> {
if (this.#completed) {
return;
}

console.debug('[search timeline load]', this.filter);
const $items = get(this.items);
const firstLength = $items.length;
const filterBase = { ...this.filter };
const { promise, resolve } = Promise.withResolvers<void>();
const req = createRxBackwardReq();
rxNostr
.use(req)
.pipe(
uniq(),
filter(({ event }) => !$items.some((item) => item.event.id === event.id)),
tap(({ event }) => {
referencesReqEmit(event);
authorActionReqEmit(event);
})
)
.subscribe({
next: ({ event }) => {
console.debug('[search next]', event);
const item = new EventItem(event);
const index = $items.findIndex(
(x) => x.event.created_at < item.event.created_at
);
if (index < 0) {
$items.push(item);
} else {
$items.splice(index, 0, item);
}
this.items.set($items);
},
complete: () => {
console.debug('[search complete]', firstLength, $items.length);
resolve();
}
});
const until = oldestCreatedAt($items);
req.emit([{ ...filterBase, until, since: until - 15 * 60 }], { relays: searchRelays });
req.over();
await promise;

const length = $items.length - firstLength;
if (length < minTimelineLength) {
const limit = minTimelineLength - length;
const events = await fetchEvents(
[{ ...filterBase, until: oldestCreatedAt($items), limit }],
searchRelays
);
const _items = events
.filter((event) => !$items.some((item) => item.event.id === event.id))
.splice(0, limit)
.map((event) => new EventItem(event));
if (_items.length === 0) {
this.#completed = true;
}
$items.push(..._items);
this.items.set($items);
}
console.log('[search loaded]', firstLength, $items.length);
}

get completed() {
return this.#completed;
}
}
123 changes: 108 additions & 15 deletions web/src/routes/(app)/search/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
<script lang="ts">
import { Tabs } from '@svelteuidev/core';
import type { Filter } from 'nostr-tools';
import type { Unsubscriber } from 'svelte/store';
import { _ } from 'svelte-i18n';
import { afterNavigate } from '$app/navigation';
import { page } from '$app/stores';
import { Search } from '$lib/Search';
import { appName, minTimelineLength } from '$lib/Constants';
import { followingHashtags } from '$lib/Interest';
import type { EventItem } from '$lib/Items';
import { EventItem } from '$lib/Items';
import TimelineView from '../TimelineView.svelte';
import SearchForm from './SearchForm.svelte';
import Trending from './Trending.svelte';
import FollowHashtagButton from '$lib/components/FollowHashtagButton.svelte';
import UnfollowHashtagButton from '$lib/components/UnfollowHashtagButton.svelte';
import { unique } from '$lib/Array';
import { SearchTimeline } from '$lib/timelines/SearchTimeline';
let query = '';
let mine = false;
Expand All @@ -24,6 +27,11 @@
let items: EventItem[] = [];
let completed = false;
let showLoading = false;
let showUsersLoading = false;
let usersSearch: SearchTimeline | undefined;
let unsubscribe: Unsubscriber | undefined;
let metadataItems: EventItem[] = [];
let tabKey = 'notes';
const search = new Search();
Expand All @@ -47,9 +55,16 @@
items = [];
completed = false;
const parsedQuery = search.parseQuery(query, mine);
const { fromPubkeys, toPubkeys, kinds, keyword, since, until } = parsedQuery;
hashtags = parsedQuery.hashtags;
const {
fromPubkeys,
toPubkeys,
hashtags: _hashtags,
kinds,
keyword,
since,
until
} = search.parseQuery(query, mine);
hashtags = _hashtags;
sinceFilter = since;
untilFilter = until;
Expand Down Expand Up @@ -77,11 +92,20 @@
}
console.debug('[search filter base]', filter);
await load();
switch (tabKey) {
case 'notes': {
await load();
break;
}
case 'users': {
initializeUsersSearch();
break;
}
}
});
async function load() {
if (query === '' || filter === undefined || completed) {
if (query === '' || filter === undefined || completed || tabKey !== 'notes') {
return;
}
Expand Down Expand Up @@ -129,6 +153,56 @@
showLoading = false;
}
async function loadUsers(): Promise<void> {
if (usersSearch === undefined || tabKey !== 'users') {
return;
}
showUsersLoading = true;
await usersSearch.load();
showUsersLoading = false;
}
async function tabChanged(event: CustomEvent): Promise<void> {
const { key } = event.detail as { key: string };
tabKey = key;
switch (key) {
case 'notes': {
console.debug('[search notes]', filter);
if (items.length === 0) {
await load();
}
break;
}
case 'users': {
console.debug('[search users]', filter.search, usersSearch?.items);
if (filter.search) {
await initializeUsersSearch();
}
break;
}
}
}
async function initializeUsersSearch() {
if (usersSearch === undefined) {
usersSearch = new SearchTimeline({ kinds: [0], search: filter.search });
unsubscribe = usersSearch.items.subscribe((value) => {
metadataItems = value;
});
await loadUsers();
} else if (usersSearch.filter.search !== filter.search) {
unsubscribe?.();
usersSearch.unsubscribe();
usersSearch = new SearchTimeline({ kinds: [0], search: filter.search });
unsubscribe = usersSearch.items.subscribe((value) => {
metadataItems = value;
});
await loadUsers();
}
}
</script>

<svelte:head>
Expand All @@ -143,12 +217,6 @@
<SearchForm {query} {mine} />
</section>

{#if query === ''}
<section>
<Trending />
</section>
{/if}

{#if hashtags.length > 0}
<section>
{#each hashtags as hashtag}
Expand All @@ -161,9 +229,30 @@
</section>
{/if}

<section>
<TimelineView {items} {load} {showLoading} />
</section>
{#if query === ''}
<section>
<Trending />
</section>
{:else}
<Tabs on:change={tabChanged}>
<Tabs.Tab label={$_('search.notes')} tabKey="notes">
<section>
<TimelineView {items} {load} {showLoading} />
</section>
</Tabs.Tab>
{#if filter.search}
<Tabs.Tab label={$_('search.users')} tabKey="users">
<section>
<TimelineView
items={metadataItems}
load={loadUsers}
showLoading={showUsersLoading}
/>
</section>
</Tabs.Tab>
{/if}
</Tabs>
{/if}

<style>
h1 a {
Expand All @@ -175,6 +264,10 @@
margin-top: 1rem;
}
:global(button.svelteui-Tab) {
border-radius: 0;
}
@media screen and (max-width: 600px) {
h1 {
margin: 0.67em;
Expand Down

0 comments on commit 57dd54e

Please sign in to comment.