mirror of
https://github.com/styppo/hamstr.git
synced 2024-10-18 05:23:28 +00:00
Add basic profile search
This commit is contained in:
parent
a96a7dbb7f
commit
c18c4ed988
@ -1,52 +1,68 @@
|
||||
<template>
|
||||
<div class="relative-position">
|
||||
<div class="searchbox" :class="{focused}">
|
||||
<div class="searchbox-wrapper">
|
||||
<div class="searchbox-icon">
|
||||
<BaseIcon icon="search" />
|
||||
</div>
|
||||
<div class="searchbox-input">
|
||||
<form @submit="search">
|
||||
<q-form @submit.stop="search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
placeholder="Search profiles"
|
||||
v-model="query"
|
||||
@focus="toggleFocus"
|
||||
@blur="toggleFocus"
|
||||
@keyup="search"
|
||||
>
|
||||
</form>
|
||||
</q-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Transition name="fade">
|
||||
<div v-if="focused" class="searchbox-results">
|
||||
<div v-if="!results.length" class="query-example">
|
||||
<b>npub…</b> or <b>[user]@domain</b> or <b>name</b>
|
||||
</div>
|
||||
<UserCard v-for="pubkey in results" :key="pubkey" :pubkey="pubkey" class="searchbox-results-item" clickable />
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseIcon from 'components/BaseIcon'
|
||||
import {Notify} from 'quasar'
|
||||
import SearchProvider from 'src/nostr/SearchProvider'
|
||||
import UserCard from 'components/User/UserCard.vue'
|
||||
|
||||
export default {
|
||||
name: 'SearchBox',
|
||||
components: {
|
||||
UserCard,
|
||||
BaseIcon,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
provider: new SearchProvider(),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
focused: false,
|
||||
query: '',
|
||||
results: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
toggleFocus() {
|
||||
this.focused = !this.focused
|
||||
},
|
||||
async search(e) {
|
||||
e.preventDefault()
|
||||
|
||||
Notify.create({
|
||||
message: 'Coming soon',
|
||||
color: 'info',
|
||||
})
|
||||
async search() {
|
||||
if (this.query) {
|
||||
this.results = (await this.provider.queryProfiles(this.query)).slice(0, 200)
|
||||
} else {
|
||||
this.results = []
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -92,6 +108,43 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
&-results {
|
||||
position: absolute;
|
||||
width: calc(100% + 1rem);
|
||||
min-height: 48px;
|
||||
max-height: 70vh;
|
||||
overflow: hidden;
|
||||
background-color: $color-bg;
|
||||
border-radius: .5rem;
|
||||
z-index: 600;
|
||||
margin-top: -.75rem;
|
||||
box-shadow: $shadow-white;
|
||||
overflow-y: scroll;
|
||||
scrollbar-color: transparent transparent;
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb { /* Foreground */
|
||||
background: $color-dark-gray;
|
||||
}
|
||||
&::-webkit-scrollbar-track { /* Background */
|
||||
background: transparent;
|
||||
}
|
||||
&-item {
|
||||
transition: 120ms ease;
|
||||
margin: 0 !important;
|
||||
padding: 1rem;
|
||||
&:hover {
|
||||
background-color: rgba($color: $color-dark-gray, $alpha: 0.1);
|
||||
}
|
||||
}
|
||||
.query-example {
|
||||
color: $color-light-gray;
|
||||
font-size: .95rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
&.focused {
|
||||
border: 1px solid rgba($color: $color-primary, $alpha: 1);
|
||||
svg {
|
||||
|
34
src/nostr/SearchProvider.js
Normal file
34
src/nostr/SearchProvider.js
Normal file
@ -0,0 +1,34 @@
|
||||
import {useProfileStore} from 'src/nostr/store/ProfileStore'
|
||||
import Nip05 from 'src/utils/Nip05'
|
||||
import {bech32prefix, bech32ToHex, isBech32} from 'src/utils/utils'
|
||||
|
||||
export default class SearchProvider {
|
||||
constructor() {
|
||||
this.profiles = useProfileStore()
|
||||
}
|
||||
|
||||
async queryProfiles(query) {
|
||||
const results = new Set()
|
||||
const [user, domain] = (query?.split('@') || [])
|
||||
if (domain) {
|
||||
(await SearchProvider.queryNip05(user, domain)).forEach(pubkey => results.add(pubkey))
|
||||
this.profiles.findByNip05(query).forEach(pubkey => results.add(pubkey))
|
||||
} else if (isBech32(query) && bech32prefix(query) === 'npub') {
|
||||
results.add(bech32ToHex(query))
|
||||
} else {
|
||||
this.profiles.findByName(query).forEach(pubkey => results.add(pubkey))
|
||||
}
|
||||
return Array.from(results)
|
||||
}
|
||||
|
||||
static async queryNip05(user, domain) {
|
||||
const names = await Nip05.fetchNames(domain)
|
||||
if (!names) return []
|
||||
if (user) {
|
||||
return Object.entries(names)
|
||||
.filter(([name, _]) => name?.toLowerCase().startsWith(user.toLowerCase()))
|
||||
.map(([_, pubkey]) => pubkey)
|
||||
}
|
||||
return Object.values(names)
|
||||
}
|
||||
}
|
@ -8,7 +8,17 @@ export const useProfileStore = defineStore('profile', {
|
||||
getters: {
|
||||
get(state) {
|
||||
return pubkey => state.profiles[pubkey]
|
||||
}
|
||||
},
|
||||
findByName(state) {
|
||||
return query => Object.values(state.profiles)
|
||||
.filter(profile => profile.name?.toLowerCase().startsWith(query?.toLowerCase()))
|
||||
.map(profile => profile.pubkey)
|
||||
},
|
||||
findByNip05(state) {
|
||||
return query => Object.values(state.profiles)
|
||||
.filter(profile => profile.nip05.url?.toLowerCase().endsWith(query?.toLowerCase()))
|
||||
.map(profile => profile.pubkey)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
addEvent(event) {
|
||||
|
@ -16,6 +16,17 @@ export default class Nip05 {
|
||||
}
|
||||
}
|
||||
|
||||
static async fetchNames(domain) {
|
||||
const url = `https://${domain}/.well-known/nostr.json`
|
||||
try {
|
||||
const res = await fetch(url)
|
||||
const json = await res.json()
|
||||
return json?.names
|
||||
} catch (e) {
|
||||
//console.warn(`Failed to fetch NIP05 data for ${nip05Id}`, e)
|
||||
}
|
||||
}
|
||||
|
||||
static async verify(pubkey, nip05Id) {
|
||||
const pk = await Nip05.fetchPubkey(nip05Id)
|
||||
return pk && pk === pubkey
|
||||
|
Loading…
Reference in New Issue
Block a user