Add search page

This commit is contained in:
Jonathan Staab 2022-12-01 10:39:53 -08:00
parent 4b59484cb7
commit f5b5e851f7
9 changed files with 137 additions and 8 deletions

View File

@ -1,5 +1,7 @@
Bugs
- [ ] Scale rather than fly reply box
- [ ] Listener on notes view not adding notes
- [ ] Pin joined relays at the top
- [ ] Load/publish user preferred relays
- [ ] Optimistically load events the user publishes (e.g. to reduce reflow for reactions/replies). Essentially, we can pretend to be our own in-memory relay.
@ -9,7 +11,8 @@ Features
- [x] Chat
- [x] Threads/social
- [ ] Search
- [ ] Followers
- [ ] Followers, blocking
- [ ] Notifications
- [ ] Server discovery
- [ ] Favorite chat rooms

View File

@ -15,6 +15,7 @@
import Anchor from 'src/partials/Anchor.svelte'
import NoteDetail from "src/partials/NoteDetail.svelte"
import NotFound from "src/routes/NotFound.svelte"
import Search from "src/routes/Search.svelte"
import Notes from "src/routes/Notes.svelte"
import Login from "src/routes/Login.svelte"
import Profile from "src/routes/Profile.svelte"
@ -75,6 +76,7 @@
<Router {url}>
<div use:links class="h-full">
<div class="pt-16 text-white h-full">
<Route path="/search" component={Search} />
<Route path="/notes" component={Notes} />
<Route path="/notes/new" component={NoteCreate} />
<Route path="/chat" component={Chat} />
@ -112,6 +114,11 @@
</a>
</li>
{/if}
<li class="cursor-pointer">
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/search">
<i class="fa-solid fa-search mr-2" /> Search
</a>
</li>
<li class="cursor-pointer">
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/notes">
<i class="fa-solid fa-tag mr-2" /> Notes

View File

@ -1,6 +1,9 @@
import './app.css'
import App from './App.svelte'
// Annoying global always fails silently. Figure out an eslint rule instead
window.find = null
const app = new App({
target: document.getElementById('app')
})

View File

@ -7,8 +7,8 @@
import {hasParent, toHtml} from 'src/util/html'
import Anchor from 'src/partials/Anchor.svelte'
import {dispatch} from "src/state/dispatch"
import {channels, findReplyTo} from "src/state/nostr"
import {accounts, modal, annotateNotes} from "src/state/app"
import {findReplyTo} from "src/state/nostr"
import {accounts, modal} from "src/state/app"
import {user} from "src/state/user"
import {formatTimestamp} from 'src/util/misc'
import UserBadge from "src/partials/UserBadge.svelte"

View File

@ -1,10 +1,8 @@
<script>
import {onMount} from 'svelte'
import {writable} from 'svelte/store'
import {fly} from 'svelte/transition'
import {find, propEq} from 'ramda'
import Spinner from 'src/partials/Spinner.svelte'
import {Cursor, channels} from "src/state/nostr"
import {channels} from "src/state/nostr"
import {notesListener, annotateNotes, modal} from "src/state/app"
import {user} from "src/state/user"
import Note from 'src/partials/Note.svelte'

View File

@ -1,7 +1,6 @@
<script>
import {onMount, onDestroy} from 'svelte'
import {writable} from 'svelte/store'
import {fly} from 'svelte/transition'
import {navigate} from "svelte-routing"
import {uniqBy, prop} from 'ramda'
import Anchor from "src/partials/Anchor.svelte"

118
src/routes/Search.svelte Normal file
View File

@ -0,0 +1,118 @@
<script>
import {onMount, onDestroy} from 'svelte'
import {writable} from 'svelte/store'
import {fly} from 'svelte/transition'
import {uniqBy, uniq, pluck, prop} from 'ramda'
import {fuzzy} from "src/util/misc"
import Anchor from "src/partials/Anchor.svelte"
import Input from "src/partials/Input.svelte"
import Spinner from "src/partials/Spinner.svelte"
import Note from "src/partials/Note.svelte"
import {relays, Cursor} from "src/state/nostr"
import {createScroller, accounts, annotateNotes, modal} from "src/state/app"
const notes = writable([])
const people = writable([])
let type = writable('people')
let q = ''
let search
let cursor
let scroller
let modalUnsub
$: search = (
$type === 'people'
? fuzzy($people, {keys: ["name", "about"]})
: fuzzy($notes, {keys: ["content"]})
)
onMount(async () => {
cursor = new Cursor({kinds: [1]})
scroller = createScroller(cursor, async chunk => {
const annotated = await annotateNotes(chunk)
const keys = uniq(pluck('pubkey', chunk))
notes.update($notes => uniqBy(prop('id'), $notes.concat(annotated)))
people.update($people => uniqBy(prop('id'), $people.concat(keys.map(k => $accounts[k]))))
})
// When a modal opens, suspend our subscriptions
modalUnsub = modal.subscribe(async $modal => {
if ($modal) {
cursor.stop()
scroller.stop()
} else {
cursor.start()
scroller.start()
}
})
})
onDestroy(() => {
cursor?.stop()
scroller?.stop()
modalUnsub?.()
})
</script>
<svelte:window on:scroll={scroller?.start} />
<ul class="border-b border-solid border-dark flex max-w-xl m-auto pt-2" in:fly={{y: 20}}>
<li
class="px-8 py-4 cursor-pointer hover:border-b border-solid border-medium"
class:border-b={$type === 'people'}
on:click={() => type.set('people')}>
People
</li>
<li
class="px-8 py-4 cursor-pointer hover:border-b border-solid border-medium"
class:border-b={$type === 'notes'}
on:click={() => type.set('notes')}>
Notes
</li>
</ul>
<div class="max-w-xl m-auto mt-4" in:fly={{y: 20}}>
<Input bind:value={q} placeholder="Search for {$type}">
<i slot="before" class="fa-solid fa-search" />
</Input>
</div>
<ul class="py-8 flex flex-col gap-2 max-w-xl m-auto">
{#each search(q) as e (e.id)}
<li in:fly={{y: 20}}>
{#if e.isAccount}
<a href="/users/{e.pubkey}" class="flex gap-4">
<div
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
style="background-image: url({e.picture})" />
<div class="flex-grow">
<h1 class="text-2xl">{e.name}</h1>
<p>{e.about || ''}</p>
</div>
</a>
{:else}
<Note interactive note={e} />
{#each e.replies as r (r.id)}
<div class="ml-6 border-l border-solid border-medium">
<Note interactive isReply note={r} />
</div>
{/each}
{/if}
</li>
{/each}
</ul>
<!-- This will always be sitting at the bottom in case infinite scrolling can't keep up -->
<Spinner />
{#if $relays.length === 0}
<div class="flex w-full justify-center items-center py-16">
<div class="text-center max-w-md">
You aren't yet connected to any relays. Please click <Anchor href="/settings/relays"
>here</Anchor
> to get started.
</div>
</div>
{/if}

View File

@ -50,6 +50,7 @@ export const ensureAccounts = async (pubkeys, {force = false} = {}) => {
...$accounts[e.pubkey],
...JSON.parse(e.content),
refreshed: now(),
isAccount: true,
}
})

View File

@ -1,6 +1,6 @@
import {writable, get} from 'svelte/store'
import {relayPool, getPublicKey} from 'nostr-tools'
import {last, intersection, uniqBy, prop} from 'ramda'
import {last, find, intersection, uniqBy, prop} from 'ramda'
import {first, noop, ensurePlural} from 'hurdak/lib/hurdak'
import {getLocalJson, setLocalJson, now, timedelta} from "src/util/misc"