Some search box styling, mock trends

This commit is contained in:
styppo 2022-12-24 00:31:31 +00:00
parent 028e870f5f
commit bcde6aa0b3
No known key found for this signature in database
GPG Key ID: 3AAA685C50724C28
10 changed files with 188 additions and 68 deletions

View File

@ -54,10 +54,6 @@ export default defineComponent({
display: flex; display: flex;
align-items: center; align-items: center;
h2 { h2 {
font-size: 1.5em;
font-weight: bold;
line-height: unset;
letter-spacing: unset;
margin: .5rem 0; margin: .5rem 0;
} }
.back-button { .back-button {

View File

@ -159,6 +159,7 @@ export default {
&-content { &-content {
margin-left: 12px; margin-left: 12px;
flex-grow: 1; flex-grow: 1;
max-width: 570px;
&-header { &-header {
.in-reply-to { .in-reply-to {
color: $color-dark-gray; color: $color-dark-gray;

View File

@ -8,34 +8,149 @@
<BaseIcon icon="search" /> <BaseIcon icon="search" />
</div> </div>
<div class="searchbox-input"> <div class="searchbox-input">
<input <form @submit="searchProfile">
type="text" <input
placeholder="Search Profiles" type="text"
@focus="toggleFocus" placeholder="Search Hamstr"
@blur="toggleFocus" v-model="query"
> @focus="toggleFocus"
@blur="toggleFocus"
>
</form>
</div> </div>
</div> </div>
</div> </div>
<div v-if="domainMode">
<div class="flex row justify-between no-wrap">
<h2 class="text-h6 text-bold q-my-none"> {{ domain }} {{ $t('users') }}</h2>
<q-btn icon="close" @click.stop="domainMode = false" />
</div>
<div v-if="domainDefaultPubkey">
<h2 class="text-caption text-bold q-my-none"> {{ $t('nip05Maintainer') }} </h2>
<BaseUserCard :pubkey="domainDefaultPubkey"/>
</div>
<q-list class="q-pt-xs q-pl-sm" style="overflow-y: auto; max-height: 40vh;">
<div v-for="user in domainUsers" :key="user.pubkey">
<BaseUserCard :pubkey="user.pubkey" />
</div>
</q-list>
<q-separator color='accent' />
</div>
</template> </template>
<script> <script>
import BaseIcon from 'components/BaseIcon' import BaseIcon from 'components/BaseIcon'
import {Notify} from 'quasar'
import {searchDomain, queryName} from 'nostr-tools/nip05'
import helpersMixin from 'src/utils/mixin'
export default { export default {
name: 'SearchBar', name: 'SearchBox',
components: { components: {
BaseIcon, BaseIcon,
}, },
mixins: [helpersMixin],
data() { data() {
return { return {
isFocused: false isFocused: false,
query: '',
searching: false,
domainMode: false,
domainNames: {},
profilesUsed: new Set(),
}
},
computed: {
validSearch() {
if (this.query === '') return true
if (this.query.match(/^[a-f0-9A-F]{64}$/)) return true
if (this.isBech32Key(this.query) && this.bech32ToHex(this.query).match(/^[a-f0-9A-F]{64}$/)) return true
if (this.query.match(/^([a-z0-9A-Z-_.\u00C0-\u1FFF\u2800-\uFFFD]*@)?[a-z0-9A-Z-_]+[.]{1}[a-z0-9A-Z-_.]+$/)) return true
return false
},
domainDefaultPubkey() {
return this.domainNames._
},
domainUsers() {
let users = Object.keys(this.domainNames).filter((name) => name !== '_').map((name) => { return { 'name': name, 'pubkey': this.domainNames[name] } })
return users
},
domain() {
let [name, domain] = this.query.split('@')
return domain || name
} }
}, },
methods: { methods: {
toggleFocus() { toggleFocus() {
this.isFocused = !this.isFocused this.isFocused = !this.isFocused
} },
async searchProfile(e) {
e.preventDefault()
if (!this.validSearch) {
Notify.create({
message: 'Invalid format! Please enter full public key or NIP05 identifier',
color: 'negative'
})
return
}
this.searching = true
this.query = this.query.trim().toLowerCase()
if (this.query.match(/^[a-f0-9]{64}$/)) {
this.toProfile(this.query)
this.query = ''
this.searching = false
return
}
if (this.isBech32Key(this.query) && this.bech32ToHex(this.query).match(/^[a-f0-9A-F]{64}$/)) {
this.toProfile(this.bech32ToHex(this.query))
this.query = ''
this.searching = false
return
}
if (this.query.match(/^([a-z0-9-_.\u00C0-\u1FFF\u2800-\uFFFD]*@)?[a-z0-9-_.]+[.]{1}[a-z0-9-_.]+$/)) {
// if (!this.query.match(/^[a-z0-9-_.\u00C0-\u1FFF\u2800-\uFFFD]?@/)) {
if (this.query.match(/^@/) || !this.query.match(/@/)) {
// this.query = '_' + this.query
// else if (!this.query.match(/@/)) this.query = '_@' + this.query
this.domainNames = await searchDomain(this.domain)
// this.domainUsers
if (this.domainUsers.length || this.domainDefaultPubkey) {
if (this.domainDefaultPubkey) this.useProfile(this.domainDefaultPubkey)
if (this.domainUsers.length) this.domainUsers.forEach((user) => this.useProfile(user.pubkey))
this.searching = false
this.domainMode = true
return
}
}
// }
console.log('this.domainUsers', this.domainUsers)
let pubkey = await queryName(this.query)
console.log('queryName returned: ', pubkey)
if (pubkey) {
this.toProfile(pubkey)
this.query = ''
this.searching = false
return
}
}
this.searching = false
Notify.create({
message: 'No user found! Please enter full public key or NIP05 identifier and double check search string',
color: 'negative'
})
},
useProfile(pubkey) {
if (this.profilesUsed.has(pubkey)) return
this.profilesUsed.add(pubkey)
this.$store.dispatch('useProfile', {pubkey})
},
}, },
} }
</script> </script>

View File

@ -97,7 +97,7 @@
</template> </template>
</q-input> </q-input>
</q-card-section> </q-card-section>
<!-- <div v-if='isBeck32Key(key)'> <!-- <div v-if='isBech32Key(key)'>
{{ hexKey }} {{ hexKey }}
</div> --> </div> -->
</q-form> </q-form>
@ -234,14 +234,14 @@ export default defineComponent({
// npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s // npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s
// nsec1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzs46ahj9 // nsec1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzs46ahj9
// 32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245 // 32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245
if (this.isBeck32Key(this.key)) { if (this.isBech32Key(this.key)) {
return this.beck32ToHex(this.key) return this.bech32ToHex(this.key)
} }
return this.key?.toLowerCase() return this.key?.toLowerCase()
}, },
isBech32Pub() { isBech32Pub() {
if (this.isBeck32Key(this.key)) { if (this.isBech32Key(this.key)) {
let { prefix } = decode(this.key.toLowerCase()) let { prefix } = decode(this.key.toLowerCase())
return prefix === 'npub' return prefix === 'npub'
} }
@ -249,7 +249,7 @@ export default defineComponent({
}, },
isBech32Sec() { isBech32Sec() {
if (this.isBeck32Key(this.key)) { if (this.isBech32Key(this.key)) {
let { prefix } = decode(this.key.toLowerCase()) let { prefix } = decode(this.key.toLowerCase())
return prefix === 'nsec' return prefix === 'nsec'
} }

View File

@ -132,7 +132,7 @@ export default defineComponent({
validSearch() { validSearch() {
if (this.searchingProfile === '') return true if (this.searchingProfile === '') return true
if (this.searchingProfile.match(/^[a-f0-9A-F]{64}$/)) return true if (this.searchingProfile.match(/^[a-f0-9A-F]{64}$/)) return true
if (this.isBeck32Key(this.searchingProfile) && this.beck32ToHex(this.searchingProfile).match(/^[a-f0-9A-F]{64}$/)) return true if (this.isBech32Key(this.searchingProfile) && this.bech32ToHex(this.searchingProfile).match(/^[a-f0-9A-F]{64}$/)) return true
if (this.searchingProfile.match(/^([a-z0-9A-Z-_.\u00C0-\u1FFF\u2800-\uFFFD]*@)?[a-z0-9A-Z-_]+[.]{1}[a-z0-9A-Z-_.]+$/)) return true if (this.searchingProfile.match(/^([a-z0-9A-Z-_.\u00C0-\u1FFF\u2800-\uFFFD]*@)?[a-z0-9A-Z-_]+[.]{1}[a-z0-9A-Z-_.]+$/)) return true
return false return false
}, },
@ -174,8 +174,8 @@ export default defineComponent({
return return
} }
if (this.isBeck32Key(this.searchingProfile) && this.beck32ToHex(this.searchingProfile).match(/^[a-f0-9A-F]{64}$/)) { if (this.isBech32Key(this.searchingProfile) && this.bech32ToHex(this.searchingProfile).match(/^[a-f0-9A-F]{64}$/)) {
this.toProfile(this.beck32ToHex(this.searchingProfile)) this.toProfile(this.bech32ToHex(this.searchingProfile))
this.searchingProfile = '' this.searchingProfile = ''
this.searching = false this.searching = false
return return

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="trends-item"> <div class="trends-item">
<h3>{{ data.name }}</h3> <h3>{{ data.name }}</h3>
<span>{{ normalizedTweetCount }} Tweets</span> <span>{{ nicePostCount }} posts</span>
</div> </div>
</template> </template>
@ -15,10 +15,10 @@ export default {
} }
}, },
computed: { computed: {
normalizedTweetCount(){ nicePostCount() {
const stringNumber = this.data.tweetsCount.toString(); const stringNumber = this.data.postCount.toString()
if(stringNumber.length > 4){ if (stringNumber.length > 4) {
return stringNumber.substring(0, stringNumber.length - 3) + "K" return stringNumber.substring(0, stringNumber.length - 3) + 'K'
} }
return stringNumber return stringNumber
} }
@ -27,16 +27,17 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
@import '@/assets/theme/colors.scss'; @import 'assets/theme/colors.scss';
.trends-item{
.trends-item {
padding: 1rem; padding: 1rem;
border-top: $border-dark; border-top: $border-dark;
h3{ h3 {
margin: 0; margin: 0;
color: #fff; color: #fff;
} }
span{ span {
color: $color-dark-gray; color: $color-dark-gray;
} }
} }
</style> </style>

View File

@ -19,58 +19,50 @@
</template> </template>
<script> <script>
import TrendsItem from '@/components/Trends/Item' import TrendsItem from 'components/Trends/Item'
import { getTrends } from '@/services/api'
export default { export default {
name: 'Trends', name: 'Trends',
components:{ components: {
TrendsItem, TrendsItem,
}, },
data(){ data() {
return{ return {
trends: [] trends: [
{
name: '#nostr',
postCount: 58
}
]
} }
}, },
computed: { computed: {
sortedTrends(){ sortedTrends() {
const trendsArray = this.trends; const trendsArray = this.trends
trendsArray.sort((a,b) => a.tweetsCount > b.tweetsCount ? -1 : 1, 0) trendsArray.sort((a, b) => a.postCount > b.postCount ? -1 : 1)
return trendsArray return trendsArray
} }
}, },
async mounted(){
try{
const response = await getTrends();
const trends = response.data.trends;
this.trends = trends;
} catch(err){
this.$notification({
type: 'error',
message: 'Error when fetching trends'
})
}
},
} }
</script> </script>
<style lang="scss"> <style lang="scss">
@import '@/assets/theme/colors.scss'; @import 'assets/theme/colors.scss';
.trends{
.trends {
background-color: rgba($color: $color-dark-gray, $alpha: 0.1); background-color: rgba($color: $color-dark-gray, $alpha: 0.1);
border-radius: 1rem; border-radius: 1rem;
&-wrapper{ &-wrapper {
} }
&-header{ &-header {
padding: 1rem; padding: 1rem;
h3{ h3 {
margin: 0; margin: 0;
font-size: 1.5rem; font-size: 1.5rem;
color: #fff; color: #fff;
} }
} }
&-body{ &-body {
} }
} }
</style> </style>

View File

@ -1 +1,13 @@
// app global css in SCSS form h2 {
font-size: 1.5em;
font-weight: bold;
line-height: unset;
letter-spacing: unset;
}
h3 {
font-size: 1.2em;
font-weight: bold;
line-height: unset;
letter-spacing: unset;
}

View File

@ -42,7 +42,8 @@
<div class="layout-sidebar"> <div class="layout-sidebar">
<div class="layout-sidebar-fixed"> <div class="layout-sidebar-fixed">
<TheSearchMenu/> <search-box />
<trends />
</div> </div>
</div> </div>
</div> </div>
@ -90,16 +91,18 @@ const { getVerticalScrollPosition, setVerticalScrollPosition} = scroll
import { activateSub, deactivateSub, destroyStreams } from '../query' import { activateSub, deactivateSub, destroyStreams } from '../query'
import MainMenu from 'components/MainMenu/index.vue' import MainMenu from 'components/MainMenu/index.vue'
import TheUserMenu from 'components/TheUserMenu.vue' import TheUserMenu from 'components/TheUserMenu.vue'
import TheSearchMenu from 'components/TheSearchMenu.vue'
import TheKeyInitializationDialog from 'components/TheKeyInitializationDialog.vue' import TheKeyInitializationDialog from 'components/TheKeyInitializationDialog.vue'
import SearchBox from 'components/SearchBox/index.vue'
import Trends from 'components/Trends/index.vue'
import { setCssVar, getCssVar } from 'quasar' import { setCssVar, getCssVar } from 'quasar'
export default defineComponent({ export default defineComponent({
name: 'MainLayout', name: 'MainLayout',
components: { components: {
MainMenu, MainMenu,
SearchBox,
Trends,
TheUserMenu, TheUserMenu,
TheSearchMenu,
TheKeyInitializationDialog, TheKeyInitializationDialog,
}, },
@ -338,7 +341,7 @@ export default defineComponent({
</script> </script>
<style lang='scss'> <style lang="scss">
@import 'assets/theme/colors.scss'; @import 'assets/theme/colors.scss';
@import 'assets/variables.scss'; @import 'assets/variables.scss';

View File

@ -287,21 +287,21 @@ export default {
return false return false
}, },
isBeck32Key(key) { isBech32Key(key) {
if (typeof key !== 'string') return false if (typeof key !== 'string') return false
try { try {
let { prefix } = decode(key.toLowerCase()) let { prefix } = decode(key.toLowerCase())
if (!['npub', 'nsec'].includes(prefix)) return false if (!['npub', 'nsec'].includes(prefix)) return false
if (prefix === 'npub') this.watchOnly = true if (prefix === 'npub') this.watchOnly = true
if (prefix === 'nsec') this.watchOnly = false if (prefix === 'nsec') this.watchOnly = false
if (!this.isKey(this.beck32ToHex(key))) return false if (!this.isKey(this.bech32ToHex(key))) return false
} catch (error) { } catch (error) {
return false return false
} }
return true return true
}, },
beck32ToHex(key) { bech32ToHex(key) {
let { data } = decode(key.toLowerCase()) let { data } = decode(key.toLowerCase())
return data.reduce((s, byte) => { return data.reduce((s, byte) => {
let hex = byte.toString(16) let hex = byte.toString(16)