mirror of
https://github.com/styppo/hamstr.git
synced 2024-10-18 13:33:22 +00:00
Profile: add DM button, load more links
This commit is contained in:
parent
58b774ec77
commit
a96a7dbb7f
104
src/components/AsyncLoadLink.vue
Normal file
104
src/components/AsyncLoadLink.vue
Normal 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>
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user