Finish the upvote/downvote implementation

This commit is contained in:
Paul Frazee 2022-11-08 12:14:51 -06:00
parent e650d98924
commit 1fbc4cf1f2
32 changed files with 1207 additions and 587 deletions

View file

@ -50,21 +50,45 @@ export async function post(
)
}
export async function like(store: RootStoreModel, uri: string, cid: string) {
return await store.api.app.bsky.feed.like.create(
export async function upvote(store: RootStoreModel, uri: string, cid: string) {
return await store.api.app.bsky.feed.vote.create(
{did: store.me.did || ''},
{
subject: {uri, cid},
direction: 'up',
createdAt: new Date().toISOString(),
},
)
}
export async function unlike(store: RootStoreModel, likeUri: string) {
const likeUrip = new AtUri(likeUri)
return await store.api.app.bsky.feed.like.delete({
did: likeUrip.hostname,
rkey: likeUrip.rkey,
export async function unupvote(store: RootStoreModel, upvoteUri: string) {
const urip = new AtUri(upvoteUri)
return await store.api.app.bsky.feed.vote.delete({
did: urip.hostname,
rkey: urip.rkey,
})
}
export async function downvote(
store: RootStoreModel,
uri: string,
cid: string,
) {
return await store.api.app.bsky.feed.vote.create(
{did: store.me.did || ''},
{
subject: {uri, cid},
direction: 'down',
createdAt: new Date().toISOString(),
},
)
}
export async function undownvote(store: RootStoreModel, downvoteUri: string) {
const urip = new AtUri(downvoteUri)
return await store.api.app.bsky.feed.vote.delete({
did: urip.hostname,
rkey: urip.rkey,
})
}

View file

@ -6,7 +6,8 @@ import * as apilib from '../lib/api'
export class FeedItemMyStateModel {
repost?: string
like?: string
upvote?: string
downvote?: string
constructor() {
makeAutoObservable(this)
@ -29,7 +30,8 @@ export class FeedItemModel implements GetTimeline.FeedItem {
| GetTimeline.UnknownEmbed
replyCount: number = 0
repostCount: number = 0
likeCount: number = 0
upvoteCount: number = 0
downvoteCount: number = 0
indexedAt: string = ''
myState = new FeedItemMyStateModel()
@ -52,26 +54,53 @@ export class FeedItemModel implements GetTimeline.FeedItem {
this.embed = v.embed
this.replyCount = v.replyCount
this.repostCount = v.repostCount
this.likeCount = v.likeCount
this.upvoteCount = v.upvoteCount
this.downvoteCount = v.downvoteCount
this.indexedAt = v.indexedAt
if (v.myState) {
this.myState.like = v.myState.like
this.myState.upvote = v.myState.upvote
this.myState.downvote = v.myState.downvote
this.myState.repost = v.myState.repost
}
}
async toggleLike() {
if (this.myState.like) {
await apilib.unlike(this.rootStore, this.myState.like)
async _clearVotes() {
if (this.myState.upvote) {
await apilib.unupvote(this.rootStore, this.myState.upvote)
runInAction(() => {
this.likeCount--
this.myState.like = undefined
this.upvoteCount--
this.myState.upvote = undefined
})
} else {
const res = await apilib.like(this.rootStore, this.uri, this.cid)
}
if (this.myState.downvote) {
await apilib.undownvote(this.rootStore, this.myState.downvote)
runInAction(() => {
this.likeCount++
this.myState.like = res.uri
this.downvoteCount--
this.myState.downvote = undefined
})
}
}
async toggleUpvote() {
const wasntUpvoted = !this.myState.upvote
await this._clearVotes()
if (wasntUpvoted) {
const res = await apilib.upvote(this.rootStore, this.uri, this.cid)
runInAction(() => {
this.upvoteCount++
this.myState.upvote = res.uri
})
}
}
async toggleDownvote() {
const wasntDownvoted = !this.myState.downvote
await this._clearVotes()
if (wasntDownvoted) {
const res = await apilib.downvote(this.rootStore, this.uri, this.cid)
runInAction(() => {
this.downvoteCount++
this.myState.downvote = res.uri
})
}
}

View file

@ -26,7 +26,7 @@ export class MeModel {
this.did = sess.data.did || ''
this.handle = sess.data.handle
const profile = await this.rootStore.api.app.bsky.actor.getProfile({
user: this.did,
actor: this.did,
})
runInAction(() => {
if (profile?.data) {

View file

@ -57,8 +57,8 @@ export class NotificationsViewItemModel implements GroupedNotification {
}
}
get isLike() {
return this.reason === 'like'
get isUpvote() {
return this.reason === 'vote'
}
get isRepost() {

View file

@ -13,8 +13,9 @@ function* reactKeyGenerator(): Generator<string> {
}
export class PostThreadViewPostMyStateModel {
like?: string
repost?: string
upvote?: string
downvote?: string
constructor() {
makeAutoObservable(this)
@ -40,7 +41,8 @@ export class PostThreadViewPostModel implements GetPostThread.Post {
replyCount: number = 0
replies?: PostThreadViewPostModel[]
repostCount: number = 0
likeCount: number = 0
upvoteCount: number = 0
downvoteCount: number = 0
indexedAt: string = ''
myState = new PostThreadViewPostMyStateModel()
@ -105,18 +107,43 @@ export class PostThreadViewPostModel implements GetPostThread.Post {
}
}
async toggleLike() {
if (this.myState.like) {
await apilib.unlike(this.rootStore, this.myState.like)
async _clearVotes() {
if (this.myState.upvote) {
await apilib.unupvote(this.rootStore, this.myState.upvote)
runInAction(() => {
this.likeCount--
this.myState.like = undefined
this.upvoteCount--
this.myState.upvote = undefined
})
} else {
const res = await apilib.like(this.rootStore, this.uri, this.cid)
}
if (this.myState.downvote) {
await apilib.undownvote(this.rootStore, this.myState.downvote)
runInAction(() => {
this.likeCount++
this.myState.like = res.uri
this.downvoteCount--
this.myState.downvote = undefined
})
}
}
async toggleUpvote() {
const wasntUpvoted = !this.myState.upvote
await this._clearVotes()
if (wasntUpvoted) {
const res = await apilib.upvote(this.rootStore, this.uri, this.cid)
runInAction(() => {
this.upvoteCount++
this.myState.upvote = res.uri
})
}
}
async toggleDownvote() {
const wasntDownvoted = !this.myState.downvote
await this._clearVotes()
if (wasntDownvoted) {
const res = await apilib.downvote(this.rootStore, this.uri, this.cid)
runInAction(() => {
this.downvoteCount++
this.myState.downvote = res.uri
})
}
}

View file

@ -37,7 +37,7 @@ export class ProfileUiModel {
},
{autoBind: true},
)
this.profile = new ProfileViewModel(rootStore, {user: params.user})
this.profile = new ProfileViewModel(rootStore, {actor: params.user})
this.feed = new FeedModel(rootStore, 'author', {
author: params.user,
limit: 10,

View file

@ -1,45 +1,41 @@
import {makeAutoObservable, runInAction} from 'mobx'
import {AtUri} from '../../third-party/uri'
import * as GetLikedBy from '../../third-party/api/src/client/types/app/bsky/feed/getLikedBy'
import * as GetVotes from '../../third-party/api/src/client/types/app/bsky/feed/getVotes'
import {RootStoreModel} from './root-store'
type LikedByItem = GetLikedBy.OutputSchema['likedBy'][number]
type VoteItem = GetVotes.OutputSchema['votes'][number]
export class LikedByViewItemModel implements LikedByItem {
export class VotesViewItemModel implements VoteItem {
// ui state
_reactKey: string = ''
// data
did: string = ''
handle: string = ''
displayName: string = ''
createdAt?: string
direction: 'up' | 'down' = 'up'
indexedAt: string = ''
createdAt: string = ''
actor: GetVotes.Actor = {did: '', handle: ''}
constructor(reactKey: string, v: LikedByItem) {
constructor(reactKey: string, v: VoteItem) {
makeAutoObservable(this)
this._reactKey = reactKey
Object.assign(this, v)
}
}
export class LikedByViewModel {
export class VotesViewModel {
// state
isLoading = false
isRefreshing = false
hasLoaded = false
error = ''
resolvedUri = ''
params: GetLikedBy.QueryParams
params: GetVotes.QueryParams
// data
uri: string = ''
likedBy: LikedByViewItemModel[] = []
votes: VotesViewItemModel[] = []
constructor(
public rootStore: RootStoreModel,
params: GetLikedBy.QueryParams,
) {
constructor(public rootStore: RootStoreModel, params: GetVotes.QueryParams) {
makeAutoObservable(
this,
{
@ -113,7 +109,7 @@ export class LikedByViewModel {
private async _fetch(isRefreshing = false) {
this._xLoading(isRefreshing)
try {
const res = await this.rootStore.api.app.bsky.feed.getLikedBy(
const res = await this.rootStore.api.app.bsky.feed.getVotes(
Object.assign({}, this.params, {uri: this.resolvedUri}),
)
this._replaceAll(res)
@ -123,15 +119,15 @@ export class LikedByViewModel {
}
}
private _replaceAll(res: GetLikedBy.Response) {
this.likedBy.length = 0
private _replaceAll(res: GetVotes.Response) {
this.votes.length = 0
let counter = 0
for (const item of res.data.likedBy) {
for (const item of res.data.votes) {
this._append(counter++, item)
}
}
private _append(keyId: number, item: LikedByItem) {
this.likedBy.push(new LikedByViewItemModel(`item-${keyId}`, item))
private _append(keyId: number, item: VoteItem) {
this.votes.push(new VotesViewItemModel(`item-${keyId}`, item))
}
}