Add topic search, tweak topic autocomplete

This commit is contained in:
Jonathan Staab 2023-04-12 17:29:48 -05:00
parent 8573140134
commit 6d4a9c8d0d
10 changed files with 83 additions and 34 deletions

View File

@ -1,8 +1,10 @@
# Current
- [ ] Buttons on profile detail is broken
- [ ] Topics
- [ ] Improve topic suggestions and rendering
- [ ] Add topic search, keep cache of topics
- [x] Improve topic suggestions and rendering
- [x] Add topic search, keep cache of topics
- [ ] Add ability to follow topics
- [ ] Relays bounty
- [ ] Ability to create custom feeds
- [ ] Add global/following/network tabs to relay detail
@ -69,7 +71,7 @@
- [ ] Review QR codes, search, basic affordances for link navigation
- [ ] Add delete button to notes
- [ ] Log in as user button
- [ ] Separate notifications out by type, mute certain kinds
- [ ] Separate notifications out by type, mute certain kinds. Likes are extraneous
- [ ] Relay recommendations based on follows/followers
- [ ] Make the note relays button modal make sense, one relay with no explanation is not good
- [ ] Linkify invoices

View File

@ -6,6 +6,7 @@ import {throttle} from "throttle-debounce"
import {writable} from "svelte/store"
import {ensurePlural, noop, createMap} from "hurdak/lib/hurdak"
import {Tags} from "src/util/nostr"
import {fuzzy} from "src/util/misc"
import user from "src/agent/user"
const Adapter = window.indexedDB ? IncrementalIndexedDBAdapter : Loki.LokiMemoryAdapter
@ -224,3 +225,9 @@ export const onReady = cb => {
}
})
}
export const searchPeople = watch("people", t =>
fuzzy(t.all({"kind0.name": {$type: "string"}}), {
keys: ["kind0.name", "kind0.about", "pubkey"],
})
)

View File

@ -344,7 +344,7 @@ addHandler(10002, e => {
// Topics
const processTopics = e => {
const matches = Array.from(e.content.matchAll(/#(\w{2,100})/g))
const matches = Array.from(e.content.toLowerCase().matchAll(/#(\w{2,100})/g))
if (matches.length > 0) {
topics.patch(matches.map(nth(1)).map(objOf("name")))

View File

@ -72,7 +72,7 @@
<div>Search below to find some interesting people.</div>
</div>
{/each}
<PersonSearch hideFollowing />
<PersonSearch />
</Content>
</Modal>
{:else if needsPeople()}

View File

@ -122,7 +122,7 @@
<br />
{/each}
{:else if type === "topic"}
<Anchor on:click={() => openTopic(value)}>{value}</Anchor>
<Anchor on:click={() => openTopic(value)}>#{value}</Anchor>
{:else if type === "link"}
<Anchor external href={value}>
{value.replace(/https?:\/\/(www\.)?/, "")}

View File

@ -1,42 +1,28 @@
<script>
import {fuzzy} from "src/util/misc"
import {personKinds} from "src/util/nostr"
import Input from "src/partials/Input.svelte"
import Spinner from "src/partials/Spinner.svelte"
import PersonInfo from "src/app/shared/PersonInfo.svelte"
import {getUserReadRelays} from "src/agent/relays"
import {watch} from "src/agent/db"
import {searchPeople} from "src/agent/db"
import network from "src/agent/network"
import user from "src/agent/user"
export let hideFollowing = false
let q
let results = []
const {petnamePubkeys} = user
const search = watch("people", t =>
fuzzy(t.all({"kind0.name": {$type: "string"}}), {
keys: ["kind0.name", "kind0.about", "pubkey"],
})
)
$: results = $search(q).slice(0, 50)
$: results = $searchPeople(q).slice(0, 50)
// Prime our database, in case we don't have any people stored yet
network.load({
relays: getUserReadRelays(),
filter: {kinds: personKinds, limit: 10},
filter: {kinds: [0], limit: 10},
})
document.title = "Search"
</script>
<Input bind:value={q} placeholder="Search for people">
<i slot="before" class="fa-solid fa-search" />
</Input>
{#each results as person (person.pubkey)}
{#if person.pubkey !== user.getPubkey() && !(hideFollowing && $petnamePubkeys.includes(person.pubkey))}
{#if person.pubkey !== user.getPubkey()}
<PersonInfo {person} />
{/if}
{:else}

View File

@ -1,16 +1,62 @@
<script>
import {identity, sortBy, prop} from "ramda"
import {fuzzy} from "src/util/misc"
import {modal} from "src/partials/state"
import Input from "src/partials/Input.svelte"
import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
import PersonSearch from "src/app/shared/PersonSearch.svelte"
import Anchor from "src/partials/Anchor.svelte"
import PersonInfo from "src/app/shared/PersonInfo.svelte"
import {watch} from "src/agent/db"
import user from "src/agent/user"
let q
$: search = watch(["people", "topics"], (p, t) => {
const topics = t
.all()
.map(topic => ({type: "topic", id: topic.name, topic, text: "#" + topic.name}))
const people = p
.all({"kind0.name": {$type: "string"}, pubkey: {$ne: user.getPubkey()}})
.map(person => ({
person,
type: "person",
id: person.pubkey,
text: "@" + [person.kind0.name, person.kind0.about].filter(identity).join(" "),
}))
return fuzzy(sortBy(prop("id"), topics.concat(people)), {keys: ["text"]})
})
const openTopic = topic => {
modal.push({type: "topic/feed", topic})
}
document.title = "Search"
</script>
<Content>
<div class="flex flex-col items-center justify-center">
<Heading>Profile Search</Heading>
<Heading>Search</Heading>
<p>
Search for people on Nostr. For now, only profiles that have already been loaded will appear
in search results.
Search for people and topics on Nostr. For now, only results that have already been loaded
will appear in search results.
</p>
</div>
<PersonSearch />
<Input bind:value={q} placeholder="Search for people or topics">
<i slot="before" class="fa-solid fa-search" />
</Input>
{#each $search(q).slice(0, 50) as result (result.type + result.id)}
{#if result.type === "topic"}
<Anchor
type="unstyled"
class="flex gap-4 border-l-2 border-solid border-gray-7 py-2 px-4 transition-all
hover:border-accent hover:bg-gray-8"
on:click={() => openTopic(result.topic.name)}>
#{result.topic.name}
</Anchor>
{:else if result.type === "person"}
<PersonInfo person={result.person} />
{/if}
{/each}
</Content>

View File

@ -6,10 +6,10 @@
export let topic
const relays = sampleRelays(getUserReadRelays())
const filter = [{kinds: [1], "#t": [topic.replace("#", "")]}]
const filter = [{kinds: [1], "#t": [topic]}]
</script>
<Content>
<Heading class="text-center">{topic}</Heading>
<Heading class="text-center">#{topic}</Heading>
<Feed {relays} {filter} />
</Content>

View File

@ -44,6 +44,8 @@
}
const autocomplete = ({person = null, force = false} = {}) => {
let completed = false
const {selection, node, offset, word} = getInfo()
const annotate = (prefix, text, value) => {
@ -67,10 +69,12 @@
selection.collapse(span.nextSibling, 0)
selection.getRangeAt(0).insertNode(space)
selection.collapse(space, 2)
completed = true
}
// Mentions
if ((force || word.length > 1) && word.startsWith("@") && person) {
if ((force || word.length > 1) && word.startsWith("@")) {
annotate("@", displayPerson(person).trim(), pubkeyEncoder.encode(person.pubkey))
}
@ -80,6 +84,8 @@
}
suggestions.setData([])
return completed
}
const onKeyDown = e => {
@ -105,7 +111,9 @@
// Only autocomplete topics on space
if (["Space"].includes(e.code)) {
autocomplete()
if (autocomplete()) {
e.preventDefault()
}
}
}

View File

@ -193,7 +193,7 @@ export const parseContent = ({content, tags = []}) => {
const topic = first(text.match(/^#\w+/i))
if (topic) {
return ["topic", topic, topic]
return ["topic", topic, topic.slice(1)]
}
}