Merge pull request #14 from lnbits/search-nostr

initial drafting of nostr global search for products
This commit is contained in:
Vlad Stan 2023-03-13 22:26:19 +02:00 committed by GitHub
commit de1a13ca27
5 changed files with 173 additions and 31 deletions

View file

@ -1,4 +1,53 @@
<div>
<div v-if="searchNostr" class="row q-col-gutter-md">
<div class="col-12 col-md-7 q-gutter-y-md">
<q-card>
<!-- <q-card-section class="row items-center q-pb-none">
<div class="text-h6">Search Globally</div>
<q-space />
<q-btn
icon="close"
flat
round
dense
@click="searchNostr = !searchNostr"
/>
</q-card-section> -->
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Search Globally</h5>
<p>Search for products on Nostr</p>
</div>
</div>
</q-card-section>
<q-card-section>
<q-input
class="q-ml-md"
standout
square
dense
outlined
clearable
v-model.trim="search"
label="Search products"
@keydown.enter="searchProducts"
hint="Enter search terms separated by spaces"
>
<template v-slot:after>
<q-btn
dense
flat
icon="search"
label="Search"
@click="searchProducts"
/>
</template>
</q-input>
</q-card-section>
</q-card>
</div>
</div>
<q-toolbar>
<q-breadcrumbs>
<q-breadcrumbs-el label="Market" icon="home"></q-breadcrumbs-el>

View file

@ -4,13 +4,49 @@ async function customerMarket(path) {
name: 'customer-market',
template,
props: ['products', 'change-page'],
props: [
'products',
'change-page',
'search-nostr',
'relays',
'update-products',
'update-stalls'
],
data: function () {
return {}
return {
search: null
}
},
methods: {
changePageM(page, opts) {
this.$emit('change-page', page, opts)
},
async searchProducts() {
this.$q.loading.show()
let searchTags = this.search.split(' ')
const pool = new NostrTools.SimplePool()
let relays = Array.from(this.relays)
let merchants = new Set()
let productEvents = await pool.list(relays, [
{
kinds: [30018],
'#t': searchTags,
search: this.search, // NIP50, not very well supported
limit: 100
}
])
productEvents.map(e => merchants.add(e.pubkey))
let stallEvents = await pool.list(relays, [
{
kinds: [30017],
authors: Array.from(merchants)
}
])
pool.close(relays)
await this.$emit('update-data', [...stallEvents, ...productEvents])
this.$q.loading.hide()
}
},
created() {}

View file

@ -60,12 +60,12 @@
label="Add to cart"
@click="$emit('add-to-cart', product)"
/>
<q-btn
<!-- <q-btn
class="q-mt-md q-ml-md"
color="primary"
icon="share"
label="Share"
/>
/> -->
</div>
</div>
<!-- RATING TO BE DONE -->

View file

@ -10,7 +10,7 @@ const market = async () => {
'wss://nostr.zebedee.cloud'
]
const eventToObj = event => {
event.content = JSON.parse(event.content)
event.content = JSON.parse(event.content) || null
return {
...event,
...Object.values(event.tags).reduce((acc, tag) => {
@ -34,6 +34,7 @@ const market = async () => {
data: function () {
return {
account: null,
accountMetadata: null,
accountDialog: {
show: false,
data: {
@ -41,6 +42,7 @@ const market = async () => {
key: null
}
},
searchNostr: false,
drawer: false,
pubkeys: new Set(),
relays: new Set(),
@ -62,13 +64,15 @@ const market = async () => {
if (this.activeStall) {
products = products.filter(p => p.stall_id == this.activeStall)
}
if (!this.searchText || this.searchText.length < 2) return products
const searchText = this.searchText.toLowerCase()
if (!searchText || searchText.length < 2) return products
return products.filter(p => {
return (
p.name.toLowerCase().includes(searchText) ||
p.description.toLowerCase().includes(searchText) ||
p.categories.toLowerCase().includes(searchText)
(p.description &&
p.description.toLowerCase().includes(searchText)) ||
(p.categories &&
p.categories.toString().toLowerCase().includes(searchText))
)
})
},
@ -87,9 +91,12 @@ const market = async () => {
return window.nostr
},
isValidKey() {
return this.accountDialog.data.key
?.toLowerCase()
?.match(/^[0-9a-f]{64}$/)
let key = this.accountDialog.data.key
if (key && key.startsWith('n')) {
let {type, data} = NostrTools.nip19.decode(key)
key = data
}
return key?.toLowerCase()?.match(/^[0-9a-f]{64}$/)
}
},
async created() {
@ -103,6 +110,9 @@ const market = async () => {
if (merchants && merchants.length) {
this.pubkeys = new Set(merchants)
}
if (this.account) {
this.pubkeys.add(this.account.pubkey)
}
if (relays && relays.length) {
this.relays = new Set([...defaultRelays, ...relays])
} else {
@ -141,16 +151,6 @@ const market = async () => {
this.$q.loading.hide()
},
methods: {
naddr() {
let naddr = NostrTools.nip19.naddrEncode({
identifier: '1234',
pubkey:
'c1415f950a1e3431de2bc5ee35144639e2f514cf158279abff9ed77d50118796',
kind: 30018,
relays: defaultRelays
})
console.log(naddr)
},
async deleteAccount() {
await LNbits.utils
.confirmDialog(
@ -169,6 +169,10 @@ const market = async () => {
}
if (this.isValidKey) {
let {key, watchOnly} = this.accountDialog.data
if (key.startsWith('n')) {
let {type, data} = NostrTools.nip19.decode(key)
key = data
}
this.$q.localStorage.set('diagonAlley.account', {
privkey: watchOnly ? null : key,
pubkey: watchOnly ? key : NostrTools.getPublicKey(key),
@ -191,6 +195,36 @@ const market = async () => {
this.accountDialog.data.watchOnly = true
return
},
async updateData(events) {
let products = new Map()
let stalls = new Map()
this.stalls.forEach(s => stalls.set(s.id, s))
this.products.forEach(p => products.set(p.id, p))
events.map(eventToObj).map(e => {
if (e.kind == 30018) {
//it's a product `d` is the prod. id
products.set(e.d, {...e.content, id: e.d[0], categories: e.t})
} else if (e.kind == 30017) {
// it's a stall `d` is the stall id
stalls.set(e.d, {...e.content, id: e.d[0], pubkey: e.pubkey})
return
}
})
this.stalls = await Array.from(stalls.values())
this.products = Array.from(products.values()).map(obj => {
let stall = this.stalls.find(s => s.id == obj.stall_id)
obj.stallName = stall.name
obj.images = [obj.image]
if (obj.currency != 'sat') {
obj.formatedPrice = this.getAmountFormated(obj.price, obj.currency)
}
return obj
})
},
async initNostr() {
this.$q.loading.show()
const pool = new NostrTools.SimplePool()
@ -205,12 +239,14 @@ const market = async () => {
authors: Array.from(this.pubkeys)
}
])
.then(events => {
console.log(events)
.then(async events => {
this.events = events || []
this.events.map(eventToObj).map(e => {
if (e.kind == 0) {
this.profiles.set(e.pubkey, e.content)
if (e.pubkey == this.account?.pubkey) {
this.accountMetadata = this.profiles.get(this.account.pubkey)
}
return
} else if (e.kind == 30018) {
//it's a product `d` is the prod. id

View file

@ -7,11 +7,19 @@
</q-toolbar>
<div>
<div v-if="account" class="bg-transparent q-ma-md">
<!-- <q-avatar size="56px" class="q-mb-sm">
<img src="https://cdn.quasar.dev/img/boy-avatar.png" />
<q-avatar size="56px" class="q-mb-sm">
<img
:src="accountMetadata?.picture || '/nostrmarket/static/images/blank-avatar.webp'"
/>
</q-avatar>
<div class="text-weight-bold">Razvan Stoenescu</div>
<div>@rstoenescu</div> -->
{%raw%}
<div class="text-weight-bold">
{{ `${account.pubkey.slice(0, 5)}...${account.pubkey.slice(-5)}` }}
</div>
<div v-if="accountMetadata && accountMetadata.name">
{{ accountMetadata.name }}
</div>
{%endraw%}
<q-btn
label="Delete data"
class="q-mt-md"
@ -148,15 +156,24 @@
{{ activePage }}
</q-toolbar-title>
{%endraw%}
<q-space></q-space>
<q-btn
v-if="!activeStall"
color="primary"
label="Search"
icon="travel_explore"
@click="searchNostr = !searchNostr"
><q-tooltip>Search for products on Nostr</q-tooltip></q-btn
>
<q-input
class="float-left q-ml-md"
class="q-ml-md"
standout
square
dense
outlined
clearable
v-model.trim="searchText"
label="Search for products"
label="Filter products"
>
<template v-slot:append>
<q-icon v-if="!searchText" name="search" />
@ -176,8 +193,11 @@
></customer-stall>
<customer-market
v-else
:search-nostr="searchNostr"
:relays="relays"
:products="filterProducts"
@change-page="navigateTo"
@update-data="updateData"
></customer-market>
</q-page-container>
<!-- ACCOUNT DIALOG -->
@ -196,11 +216,12 @@
<q-card-section class="q-pt-none">
<q-input
dense
label="Private key (hex)"
label="nsec/npub/hex"
v-model="accountDialog.data.key"
autofocus
@keyup.enter="createAccount"
:error="!isValidKey"
:error="accountDialog.data.key && !isValidKey"
hint="Enter you private key (recommended) or public key"
></q-input>
<q-item tag="label">