Profile: add DM button, load more links

This commit is contained in:
styppo 2023-01-23 23:24:27 +00:00
parent 58b774ec77
commit a96a7dbb7f
No known key found for this signature in database
GPG Key ID: 3AAA685C50724C28
2 changed files with 178 additions and 8 deletions

View File

@ -0,0 +1,104 @@
<template>
<div ref="link" class="async-load-link" @click="load">
<q-spinner v-if="loading" size="sm" />
<span v-else>
{{ noMore ? prefixNoMore : (!hasItems ? prefix : '') }}
<a>
{{ noMore ? labelNoMore : label }}
</a>
</span>
</div>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'AsyncLoadLink',
emits: ['loading', 'loaded'],
props: {
loadFn: {
type: Function,
required: true,
},
label: {
type: String,
default: 'Load more'
},
labelNoMore: {
type: String,
default: 'Try again?'
},
prefix: {
type: String,
default: 'Nothing here.'
},
prefixNoMore: {
type: String,
default: 'Nothing found.'
},
hasItems: {
type: Boolean,
default: false,
},
autoload: {
type: Boolean,
default: false,
}
},
data() {
return {
loading: false,
noMore: false,
observer: null,
}
},
methods: {
async load() {
if (this.loading) return
this.loading = true
this.$emit('loading')
const result = await this.loadFn()
this.noMore = !result || !result.length
this.loading = false
this.$emit('loaded', result)
}
},
mounted() {
if (this.autoload) {
this.observer = new IntersectionObserver(this.load.bind(this), {
root: null,
threshold: 0.5,
})
this.observer.observe(this.$refs.button)
}
},
unmounted() {
if (this.observer) this.observer.disconnect()
}
})
</script>
<style lang="scss" scoped>
@import "assets/theme/colors.scss";
.async-load-link {
width: 100%;
padding: 1rem;
text-align: center;
cursor: pointer;
transition: 120ms ease;
a {
color: $color-primary;
}
&:hover {
background: rgba($color: $color-dark-gray, $alpha: 0.2);
a {
text-decoration: underline;
}
}
}
</style>

View File

@ -18,6 +18,16 @@
<strong>{{ followers?.length ? `${followers?.length}+` : 0 }}</strong> Followers <strong>{{ followers?.length ? `${followers?.length}+` : 0 }}</strong> Followers
</a> </a>
</p> </p>
<p class="actions">
<a @click="goToConversation">
<BaseIcon icon="messages" />
<q-tooltip>Send private message</q-tooltip>
</a>
<a>
<q-icon name="bolt" size="sm" />
<q-tooltip>Tip with Bitcoin Lightning</q-tooltip>
</a>
</p>
</div> </div>
</div> </div>
@ -47,7 +57,7 @@
actions actions
/> />
</template> </template>
<ListPlaceholder :count="posts?.length" :loading="loadingNotes" /> <AsyncLoadLink :load-fn="loadMorePosts" :has-items="!!posts?.length" />
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="replies" class="no-padding"> <q-tab-panel name="replies" class="no-padding">
<template v-for="(thread, i) in replies"> <template v-for="(thread, i) in replies">
@ -57,7 +67,7 @@
:thread="thread" :thread="thread"
/> />
</template> </template>
<ListPlaceholder :count="replies?.length" :loading="loadingNotes" /> <AsyncLoadLink :load-fn="loadMorePosts" :has-items="!!replies?.length" />
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="reactions" class="no-padding"> <q-tab-panel name="reactions" class="no-padding">
<template v-for="(thread, i) in reactions"> <template v-for="(thread, i) in reactions">
@ -67,7 +77,7 @@
:thread="thread" :thread="thread"
/> />
</template> </template>
<ListPlaceholder :count="reactions?.length" :loading="loadingReactions" /> <AsyncLoadLink :load-fn="loadMoreReactions" :has-items="!!reactions?.length" />
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="relays" class="no-padding"> <q-tab-panel name="relays" class="no-padding">
<ListPlaceholder :count="0" /> <ListPlaceholder :count="0" />
@ -85,21 +95,27 @@ import ListPost from 'components/Post/ListPost.vue'
import Thread from 'components/Post/Thread.vue' import Thread from 'components/Post/Thread.vue'
import ListPlaceholder from 'components/ListPlaceholder.vue' import ListPlaceholder from 'components/ListPlaceholder.vue'
import FollowButton from 'components/User/FollowButton.vue' import FollowButton from 'components/User/FollowButton.vue'
import BaseIcon from 'components/BaseIcon/index.vue'
import AsyncLoadLink from 'components/AsyncLoadLink.vue'
import {useAppStore} from 'stores/App' import {useAppStore} from 'stores/App'
import {useNostrStore} from 'src/nostr/NostrStore' import {useNostrStore} from 'src/nostr/NostrStore'
import {bech32ToHex, hexToBech32} from 'src/utils/utils' import {bech32ToHex, hexToBech32} from 'src/utils/utils'
import Defer from 'src/utils/Defer' import Defer from 'src/utils/Defer'
import {EventKind} from 'src/nostr/model/Event'
import DateUtils from 'src/utils/DateUtils'
export default defineComponent({ export default defineComponent({
name: 'Profile', name: 'Profile',
components: { components: {
FollowButton,
ListPlaceholder,
Thread,
PageHeader, PageHeader,
UserAvatar, UserAvatar,
UserName, UserName,
BaseIcon,
FollowButton,
Thread,
ListPlaceholder,
ListPost, ListPost,
AsyncLoadLink,
}, },
mixins: [Defer()], mixins: [Defer()],
setup() { setup() {
@ -127,7 +143,7 @@ export default defineComponent({
return this.nostr.getPostsByAuthor(this.pubkey) return this.nostr.getPostsByAuthor(this.pubkey)
}, },
posts() { posts() {
return this.notes?.filter(note => !note.hasAncestor()).slice(0, 50) return this.notes?.filter(note => !note.hasAncestor())
}, },
replies() { replies() {
return this.notes?.filter(note => note.hasAncestor()) return this.notes?.filter(note => note.hasAncestor())
@ -151,6 +167,24 @@ export default defineComponent({
}, },
}, },
methods: { methods: {
loadMorePosts() {
const oldest = this.notes?.[this.notes.length - 1]?.createdAt || DateUtils.now()
return this.nostr.fetch({
kinds: [EventKind.NOTE],
authors: [this.pubkey],
until: oldest,
limit: 100,
})
},
loadMoreReactions() {
const oldest = this.reactions?.[this.reactions.length - 1]?.createdAt || DateUtils.now()
return this.nostr.fetch({
kinds: [EventKind.REACTION],
authors: [this.pubkey],
until: oldest,
limit: 100,
})
},
goToFollowers(tab = 'following') { goToFollowers(tab = 'following') {
this.$router.push({ this.$router.push({
name: 'followers', name: 'followers',
@ -159,7 +193,15 @@ export default defineComponent({
tab, tab,
} }
}) })
} },
goToConversation() {
this.$router.push({
name: 'conversation',
params: {
pubkey: hexToBech32(this.pubkey, 'npub'),
}
})
},
}, },
watch: { watch: {
activeTab() { activeTab() {
@ -207,6 +249,9 @@ export default defineComponent({
flex-grow: 1; flex-grow: 1;
} }
} }
p:last-child {
margin-bottom: 0;
}
.followers { .followers {
a { a {
cursor: pointer; cursor: pointer;
@ -222,6 +267,27 @@ export default defineComponent({
margin-left: 1rem; margin-left: 1rem;
} }
} }
.actions {
display: flex;
a {
svg, i {
width: 24px;
height: 24px;
color: $color-light-gray;
fill: $color-light-gray;
//fill: $color-fg;
transition: 120ms ease;
}
&:hover svg, &:hover i {
fill: $color-fg;
color: $color-fg;
//fill: $color-primary;
}
}
a + a {
margin-left: .5rem;
}
}
} }
} }
&-tabs { &-tabs {