Skip to content

Commit

Permalink
feat: show quote reposts (#1080)
Browse files Browse the repository at this point in the history
* feat: fetch quote reposts of a post

* refactor: minor refactor

* feat: show quote reposts in stats (#1082)

* feat: add ui

* refactor: move getQuoteReposts to QuotesPopup

* feat: show quote text

* style: polish displaying quotes reposts in stats

Co-authored-by: Rahul Trivedi <[email protected]>
Co-authored-by: Lilian Desvaux de Marigny <[email protected]>

* fix: asyncs and trys

* fix: don't mutate avatar in-place

* fix: make cache return only copies of the objects

* fix: make cache return only copies of the objects

Co-authored-by: jack dishman <[email protected]>
Co-authored-by: Lilian Desvaux de Marigny <[email protected]>
Co-authored-by: Christos Panagiotakopoulos <[email protected]>
  • Loading branch information
4 people authored Jul 19, 2022
1 parent 3840f1f commit 8170325
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 38 deletions.
7 changes: 5 additions & 2 deletions src/backend/reposts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ export interface IGetRepostsOptions {
offset?: number
limit?: number
following?: string
type?: `simple` | `quote`
}

export async function getReposts(
filter: { authorID: string; postCID?: string },
filter: { authorID?: string; postCID?: string },
options: IGetRepostsOptions,
): Promise<IRepostResponse[]> {
const { sort, offset = 0, limit = 10, following } = options
const { sort, offset = 0, limit = 10, following, type } = options

if (sort === `FOLLOWING` && !following) {
throw new Error(`Following not specified`)
}
Expand All @@ -64,6 +66,7 @@ export async function getReposts(
following,
offset,
limit,
...(type ? { type } : {}),
},
})

Expand Down
3 changes: 3 additions & 0 deletions src/backend/utilities/caching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export default function cache<T>(fetchFunction: (key: string) => Promise<T>) {
if (!update) {
const cached = _cache.get(key)
if (cached !== undefined) {
if (typeof cached === `object`) {
return { ...cached }
}
return cached
}
}
Expand Down
22 changes: 13 additions & 9 deletions src/components/ProfilePreview.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="flex items-center">
<div v-if="profile !== null" class="flex items-center">
<Avatar :authorID="profile.id" :avatar="avatar" size="w-12 h-12" />
<div class="h-12 flex-grow px-4">
<nuxt-link :to="`/id/` + profile.id" class="flex flex-col">
Expand Down Expand Up @@ -54,18 +54,22 @@ export default Vue.extend({
data(): IData {
return {
isFollowing: false,
avatar: undefined,
avatar: ``,
}
},
async created() {
// fetch avatar
if (this.profile.avatar !== null && this.profile.avatar !== ``) {
this.avatar = await getPhotoFromIPFS(this.profile.avatar)
try {
// fetch avatar
if (this.profile.avatar !== null && this.profile.avatar !== ``) {
this.avatar = await getPhotoFromIPFS(this.profile.avatar)
}
// Check if I am following the listed person
getFollowersAndFollowing(this.profile.id, true).then(({ followers }) => {
this.isFollowing = followers.has(this.$store.state.session.id)
})
} catch (err) {
this.$toastError(err as string)
}
// Check if I am following the listed person
getFollowersAndFollowing(this.profile.id, true).then(({ followers }) => {
this.isFollowing = followers.has(this.$store.state.session.id)
})
},
methods: {
getPhotoFromIPFS,
Expand Down
132 changes: 132 additions & 0 deletions src/components/popups/QuotesPopup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<template>
<div
class="bg-darkBG dark:bg-gray5 modal-animation fixed top-0 bottom-0 left-0 right-0 z-30 flex h-screen w-full items-center justify-center bg-opacity-50 dark:bg-opacity-50"
@click.self="$emit(`close`)"
>
<!-- Container -->
<div
v-if="postCID !== null"
class="popup min-h-40 w-full lg:w-600 bg-lightBG dark:bg-darkBGStop card-animation max-h-90 z-10 overflow-y-auto rounded-lg px-6 pt-4 pb-2 shadow-lg"
>
<div class="sticky flex items-center justify-between mb-6">
<h2 class="text-lightPrimaryText dark:text-darkPrimaryText text-3xl font-semibold">Quoted this post</h2>
<button class="focus:outline-none bg-gray1 dark:bg-gray5 rounded-full p-1" @click="$emit(`close`)">
<CloseIcon />
</button>
</div>
<div v-show="isLoading" class="modal-animation flex w-full justify-center z-20 mt-24">
<div
class="loader m-5 border-2 border-gray1 dark:border-gray7 h-8 w-8 rounded-3xl"
:style="`border-top: 2px solid` + $color.hex"
></div>
</div>
<article v-if="!isLoading">
<div v-for="p in quoteReposts" :key="p.authorID + p.timestamp" class="flex flex-col">
<div class="flex items-center">
<Avatar :avatar="p.avatar" :authorID="p.authorID" size="w-12 h-12" />
<div class="h-12 flex flex-col px-4">
<nuxt-link :to="`/id/` + p.authorID" class="flex items-center">
<span v-if="p.name != ``" class="text-base font-medium text-lightPrimaryText dark:text-darkPrimaryText">
{{ p.name }}
</span>
<span v-else class="text-gray5 dark:text-gray3 text-base font-medium"> {{ p.authorID }} </span>
<span class="text-gray5 dark:text-gray3 text-sm ml-2">@{{ p.authorID }}</span>
</nuxt-link>
<span class="mt-1 text-xs text-gray5 dark:text-gray3">{{ $formatDate(p.timestamp) }}</span>
</div>
</div>
<div
class="my-4 pb-4 border-b border-lightBorder dark:border-darkBorder text-lightPrimaryText dark:text-darkPrimaryText"
>
{{ p.content }}
</div>
</div>
</article>
<p v-if="!isLoading && quoteReposts.length === 0" class="text-sm text-gray5 dark:text-gray3 text-center mt-14">
None of the reposters quoted this post
</p>
</div>
</div>
</template>

<script lang="ts">
import Vue from 'vue'
import CloseIcon from '@/components/icons/X.vue'
import Avatar from '@/components/Avatar.vue'
import { getReposts, IGetRepostsOptions } from '@/backend/reposts'
import { getRegularPost } from '@/backend/post'
import { createDefaultProfile, getProfile, Profile } from '@/backend/profile'
import { getPhotoFromIPFS } from '@/backend/getPhoto'
interface IData {
isLoading: boolean
profiles: Array<Profile>
quoteReposters: Array<string>
quoteReposts: Array<any>
followers: Set<string>
}
export default Vue.extend({
components: { CloseIcon, Avatar },
props: {
postCID: {
type: String,
required: true,
},
},
data(): IData {
return {
isLoading: true,
profiles: [],
quoteReposters: [],
quoteReposts: [],
followers: new Set(),
}
},
created() {
this.getQuoteReposts()
},
methods: {
updateFollowers(): void {
this.$emit(`updateFollowers`)
},
async getReposterProfile(p: string) {
let profile = createDefaultProfile(p)
let avatar = ``
await getProfile(p).then((fetchedProfile) => {
if (fetchedProfile.profile) {
profile = fetchedProfile.profile
}
if (profile.avatar !== ``) {
getPhotoFromIPFS(profile.avatar).then((a) => {
avatar = a
})
}
})
return { profile, avatar }
},
async getQuoteReposts() {
const options: IGetRepostsOptions = { sort: `NEW`, offset: 0, limit: 1000, type: `quote` }
const reposts = await getReposts({ postCID: this.postCID }, options)
reposts.forEach((repost) => {
this.fetchQuote(repost.repost._id, repost.repost.authorID)
})
this.isLoading = false
},
async fetchQuote(cid: string, authorID: string) {
const { data: content } = await getRegularPost(cid)
const { profile, avatar } = await this.getReposterProfile(authorID)
const q = {
content: content.content,
timestamp: content.timestamp,
authorID: content.authorID,
name: profile.name,
avatar,
}
this.quoteReposts.push(q)
},
},
})
</script>
29 changes: 19 additions & 10 deletions src/components/popups/RepostersPopup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
:style="`border-top: 2px solid` + $color.hex"
></div>
</div>
<article v-show="!isLoading">
<article v-if="!isLoading && profiles.length > 0">
<div v-for="p in profiles" :key="p.id">
<ProfilePreview :profile="p" class="pb-4" @updateFollowers="updateFollowers" />
</div>
Expand Down Expand Up @@ -59,8 +59,8 @@ export default Vue.extend({
followers: new Set(),
}
},
mounted() {
this.initReposters()
async mounted() {
await this.initReposters()
window.addEventListener(`click`, this.handleCloseClick, true)
},
destroyed() {
Expand Down Expand Up @@ -89,17 +89,26 @@ export default Vue.extend({
},
async getFollowers(p: string) {
let profile = createDefaultProfile(p)
const fetchedProfile = await getProfile(p)
if (fetchedProfile.profile) {
profile = fetchedProfile.profile
try {
const fetchedProfile = await getProfile(p)
if (fetchedProfile.profile) {
profile = fetchedProfile.profile
}
this.profiles.push(profile)
} catch (err) {
this.$toastError(err as string)
}
this.profiles.push(profile)
},
async initReposters() {
const options: IGetRepostsOptions = { sort: `NEW`, offset: 0, limit: 1000 }
this.reposters = await getReposters(this.postCID, options)
this.reposters.forEach(this.getFollowers)
this.isLoading = false
try {
this.reposters = await getReposters(this.postCID, options)
this.reposters.forEach(await this.getFollowers)
} catch (err) {
this.$toastError(err as string)
} finally {
this.isLoading = false
}
},
},
})
Expand Down
37 changes: 20 additions & 17 deletions src/components/post/Actions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,20 @@
</div>
</div>
</div>
<button v-if="profiles.length > 0" class="text-sm text-primary w-1/5 h-fit" @click="openReposters">
See reposters
</button>
<button v-else class="text-sm text-primary w-1/5 h-fit cursor-default" disabled style="opacity: 0">
See reposters
</button>
<div v-if="repostsCount > 0" class="flex flex-col w-1/5">
<!-- Show reposters and quotes -->
<button class="text-sm text-primary h-fit flex items-center" @click="openReposters">
<RepostIcon :isActive="true" :shrink="true" class="mr-2 p-1" />
<p>See reposters</p>
</button>
<button class="text-sm text-primary h-fit flex items-center mt-2" @click="$emit(`openQuotes`)">
<QuoteIcon class="mr-2 p-1" />
<p>See quotes</p>
</button>
</div>
<div v-else class="flex flex-grow">
<!-- Filler -->
</div>
</div>
<!-- Comments Activity -->
<div class="flex h-44 justify-between">
Expand Down Expand Up @@ -420,6 +428,8 @@ import CloseIcon from '@/components/icons/X.vue'
import StatsIcon from '@/components/icons/Stats.vue'
import ChevronLeft from '@/components/icons/ChevronLeft.vue'
import ChevronRight from '@/components/icons/ChevronRight.vue'
import RepostIcon from '@/components/icons/Repost.vue'
import QuoteIcon from '@/components/icons/Quote.vue'
import Avatar from '@/components/Avatar.vue'
import { feelings } from '@/config/config'
Expand All @@ -432,7 +442,6 @@ import {
getCommentsStats,
ICommentsStats,
} from '@/backend/comment'
import { getReposters, IGetRepostsOptions } from '@/backend/reposts'
import { createDefaultProfile, getProfile, Profile } from '@/backend/profile'
import { getFollowersAndFollowing } from '@/backend/following'
import { getPhotoFromIPFS } from '@/backend/getPhoto'
Expand All @@ -456,7 +465,7 @@ interface IData {
showDropdown: boolean
toggleStats: boolean
toggleReposters: boolean
reposters: Array<string>
quoteReposts: Array<any>
profiles: Array<Profile>
followers: Set<string>
following: Set<string>
Expand Down Expand Up @@ -484,6 +493,8 @@ export default Vue.extend({
ChevronLeft,
ChevronRight,
SendIcon,
RepostIcon,
QuoteIcon,
},
props: {
postCID: {
Expand Down Expand Up @@ -522,7 +533,7 @@ export default Vue.extend({
showDropdown: false,
toggleStats: this.openStats,
toggleReposters: false,
reposters: [],
quoteReposts: [],
profiles: [],
followers: new Set(),
following: new Set(),
Expand All @@ -546,7 +557,6 @@ export default Vue.extend({
},
created() {
this.initComments()
this.initReposters()
this.isLoading = false
},
mounted() {
Expand Down Expand Up @@ -728,19 +738,12 @@ export default Vue.extend({
return new Promise((resolve) => setTimeout(resolve, ms))
},
openReposters() {
// this.toggleStats = false
// this.toggleReposters = true
this.$emit(`reposters`)
},
closeReposters() {
this.toggleStats = true
this.toggleReposters = false
},
async initReposters() {
const options: IGetRepostsOptions = { sort: `NEW`, offset: 0, limit: 1000 }
this.reposters = await getReposters(this.postCID, options)
this.reposters.forEach(this.getFollowers)
},
async getFollowers(p: string) {
let profile = createDefaultProfile(p)
const fetchedProfile = await getProfile(p)
Expand Down
Loading

0 comments on commit 8170325

Please sign in to comment.