add reblogging/unreblogging

This commit is contained in:
Nolan Lawson 2018-02-24 18:20:33 -08:00
parent a82cc57f83
commit 00ccf35777
9 changed files with 167 additions and 18 deletions

View file

@ -5,7 +5,7 @@ import { toast } from '../_utils/toast'
export async function setFavorited (statusId, favorited) { export async function setFavorited (statusId, favorited) {
if (!store.get('online')) { if (!store.get('online')) {
toast.say('You cannot favorite or unfavorite while offline.') toast.say(`You cannot ${favorited ? 'favorite' : 'unfavorite'} while offline.`)
return return
} }
let instanceName = store.get('currentInstance') let instanceName = store.get('currentInstance')
@ -22,6 +22,6 @@ export async function setFavorited (statusId, favorited) {
store.set({statusModifications: statusModifications}) store.set({statusModifications: statusModifications})
} catch (e) { } catch (e) {
console.error(e) console.error(e)
toast.say('Failed to favorite or unfavorite. ' + (e.message || '')) toast.say(`Failed to ${favorited ? 'favorite' : 'unfavorite'}. ` + (e.message || ''))
} }
} }

27
routes/_actions/reblog.js Normal file
View file

@ -0,0 +1,27 @@
import { store } from '../_store/store'
import { database } from '../_database/database'
import { toast } from '../_utils/toast'
import { reblogStatus, unreblogStatus } from '../_api/reblog'
export async function setReblogged (statusId, reblogged) {
if (!store.get('online')) {
toast.say(`You cannot ${reblogged ? 'boost' : 'unboost'} while offline.`)
return
}
let instanceName = store.get('currentInstance')
let accessToken = store.get('accessToken')
try {
await (reblogged
? reblogStatus(instanceName, accessToken, statusId)
: unreblogStatus(instanceName, accessToken, statusId))
await database.setStatusReblogged(instanceName, statusId, reblogged)
let statusModifications = store.get('statusModifications')
let currentStatusModifications = statusModifications[instanceName] =
(statusModifications[instanceName] || {favorites: {}, reblogs: {}})
currentStatusModifications.reblogs[statusId] = reblogged
store.set({statusModifications: statusModifications})
} catch (e) {
console.error(e)
toast.say(`Failed to ${reblogged ? 'boost' : 'unboost'}. ` + (e.message || ''))
}
}

12
routes/_api/reblog.js Normal file
View file

@ -0,0 +1,12 @@
import { post } from '../_utils/ajax'
import { basename, auth } from './utils'
export async function reblogStatus (instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/reblog`
return post(url, null, auth(accessToken))
}
export async function unreblogStatus (instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/unreblog`
return post(url, null, auth(accessToken))
}

View file

@ -4,11 +4,13 @@
href="#fa-reply" href="#fa-reply"
/> />
<IconButton <IconButton
label="{{boostLabel}}" label="{{reblogLabel}}"
pressable="{{!boostDisabled}}" pressable="{{!reblogDisabled}}"
pressed="{{status.reblogged}}" pressed="{{reblogged}}"
disabled="{{boostDisabled}}" disabled="{{reblogDisabled}}"
href="{{boostIcon}}" href="{{reblogIcon}}"
delegateKey="{{reblogKey}}"
ref:reblogNode
/> />
<IconButton <IconButton
label="Favorite" label="Favorite"
@ -38,17 +40,19 @@
import { store } from '../../_store/store' import { store } from '../../_store/store'
import { registerClickDelegate, unregisterClickDelegate } from '../../_utils/delegate' import { registerClickDelegate, unregisterClickDelegate } from '../../_utils/delegate'
import { setFavorited } from '../../_actions/favorite' import { setFavorited } from '../../_actions/favorite'
import { setReblogged } from '../../_actions/reblog'
export default { export default {
oncreate() { oncreate() {
this.onFavoriteClick = this.onFavoriteClick.bind(this) this.onFavoriteClick = this.onFavoriteClick.bind(this)
this.onReblogClick = this.onReblogClick.bind(this)
let favoriteKey = this.get('favoriteKey') registerClickDelegate(this.get('favoriteKey'), this.onFavoriteClick)
registerClickDelegate(favoriteKey, this.onFavoriteClick) registerClickDelegate(this.get('reblogKey'), this.onReblogClick)
}, },
ondestroy() { ondestroy() {
let favoriteKey = this.get('favoriteKey') unregisterClickDelegate(this.get('favoriteKey'))
unregisterClickDelegate(favoriteKey) unregisterClickDelegate(this.get('reblogKey'))
}, },
components: { components: {
IconButton IconButton
@ -59,11 +63,16 @@
let statusId = this.get('statusId') let statusId = this.get('statusId')
let favorited = this.get('favorited') let favorited = this.get('favorited')
/* no await */ setFavorited(statusId, !favorited) /* no await */ setFavorited(statusId, !favorited)
},
onReblogClick() {
let statusId = this.get('statusId')
let reblogged = this.get('reblogged')
/* no await */ setReblogged(statusId, !reblogged)
} }
}, },
computed: { computed: {
visibility: (status) => status.visibility, visibility: (status) => status.visibility,
boostLabel: (visibility) => { reblogLabel: (visibility) => {
switch (visibility) { switch (visibility) {
case 'private': case 'private':
return 'Cannot be boosted because this is followers-only' return 'Cannot be boosted because this is followers-only'
@ -73,7 +82,7 @@
return 'Boost' return 'Boost'
} }
}, },
boostIcon: (visibility) => { reblogIcon: (visibility) => {
switch (visibility) { switch (visibility) {
case 'private': case 'private':
return '#fa-lock' return '#fa-lock'
@ -83,9 +92,15 @@
return '#fa-retweet' return '#fa-retweet'
} }
}, },
boostDisabled: (visibility) => { reblogDisabled: (visibility) => {
return visibility === 'private' || visibility === 'direct' return visibility === 'private' || visibility === 'direct'
}, },
reblogged: (status, $currentStatusModifications) => {
if ($currentStatusModifications && status.id in $currentStatusModifications.reblogs) {
return $currentStatusModifications.reblogs[status.id]
}
return status.reblogged
},
favorited: (status, $currentStatusModifications) => { favorited: (status, $currentStatusModifications) => {
if ($currentStatusModifications && status.id in $currentStatusModifications.favorites) { if ($currentStatusModifications && status.id in $currentStatusModifications.favorites) {
return $currentStatusModifications.favorites[status.id] return $currentStatusModifications.favorites[status.id]
@ -93,7 +108,8 @@
return status.favourited return status.favourited
}, },
statusId: (status) => status.id, statusId: (status) => status.id,
favoriteKey: (statusId, timelineType, timelineValue) => `fav-${timelineType}-${timelineValue}-${statusId}` favoriteKey: (statusId, timelineType, timelineValue) => `fav-${timelineType}-${timelineValue}-${statusId}`,
reblogKey: (statusId, timelineType, timelineValue) => `reblog-${timelineType}-${timelineValue}-${statusId}`,
} }
} }
</script> </script>

View file

@ -416,3 +416,11 @@ export async function setStatusFavorited (instanceName, statusId, favorited) {
status.favourites_count = (status.favourites_count || 0) + delta status.favourites_count = (status.favourites_count || 0) + delta
}) })
} }
export async function setStatusReblogged (instanceName, statusId, reblogged) {
return updateStatus(instanceName, statusId, status => {
let delta = (reblogged ? 1 : 0) - (status.reblogged ? 1 : 0)
status.reblogged = reblogged
status.reblogs_count = (status.reblogs_count || 0) + delta
})
}

View file

@ -7,7 +7,7 @@ function fetchWithTimeout (url, options) {
}) })
} }
async function throwErrorIfInvalidResponse(response) { async function throwErrorIfInvalidResponse (response) {
let json = await response.json() let json = await response.json()
if (response.status >= 200 && response.status < 300) { if (response.status >= 200 && response.status < 300) {
return json return json

View file

@ -5,7 +5,7 @@ import {
} from '../utils' } from '../utils'
import { foobarRole } from '../roles' import { foobarRole } from '../roles'
fixture`12-favorite-unfavorite.js` fixture`30-favorite-unfavorite.js`
.page`http://localhost:4002` .page`http://localhost:4002`
test('favorites a status', async t => { test('favorites a status', async t => {
@ -54,7 +54,7 @@ test('unfavorites a status', async t => {
.expect(getNthFavorited(1)).eql('true') .expect(getNthFavorited(1)).eql('true')
}) })
test('Keeps the correct count', async t => { test('Keeps the correct favorites count', async t => {
await t.useRole(foobarRole) await t.useRole(foobarRole)
.hover(getNthStatus(4)) .hover(getNthStatus(4))
.click(getNthFavoriteButton(4)) .click(getNthFavoriteButton(4))

View file

@ -0,0 +1,78 @@
import {
getNthReblogButton, getNthReblogged, getNthStatus, getReblogsCount, getUrl, homeNavButton,
notificationsNavButton,
scrollToBottomOfTimeline, scrollToTopOfTimeline
} from '../utils'
import { foobarRole } from '../roles'
fixture`31-reblog-unreblog.js`
.page`http://localhost:4002`
test('reblogs a status', async t => {
await t.useRole(foobarRole)
.hover(getNthStatus(0))
.expect(getNthReblogged(0)).eql('false')
.click(getNthReblogButton(0))
.expect(getNthReblogged(0)).eql('true')
// scroll down and back up to force an unrender
await scrollToBottomOfTimeline(t)
await scrollToTopOfTimeline(t)
await t
.hover(getNthStatus(0))
.expect(getNthReblogged(0)).eql('true')
.click(notificationsNavButton)
.click(homeNavButton)
.expect(getNthReblogged(0)).eql('true')
.click(notificationsNavButton)
.expect(getUrl()).contains('/notifications')
.click(homeNavButton)
.expect(getUrl()).eql('http://localhost:4002/')
.expect(getNthReblogged(0)).eql('true')
.click(getNthReblogButton(0))
.expect(getNthReblogged(0)).eql('false')
})
test('unreblogs a status', async t => {
await t.useRole(foobarRole)
.hover(getNthStatus(4))
.expect(getNthReblogged(4)).eql('false')
.click(getNthReblogButton(4))
.expect(getNthReblogged(4)).eql('true')
.click(getNthReblogButton(4))
.expect(getNthReblogged(4)).eql('false')
// scroll down and back up to force an unrender
await scrollToBottomOfTimeline(t)
await scrollToTopOfTimeline(t)
await t
.hover(getNthStatus(4))
.expect(getNthReblogged(4)).eql('false')
.click(notificationsNavButton)
.click(homeNavButton)
.expect(getNthReblogged(4)).eql('false')
.click(notificationsNavButton)
.navigateTo('/')
.expect(getNthReblogged(4)).eql('false')
.click(getNthReblogButton(4))
.expect(getNthReblogged(4)).eql('true')
})
test('Keeps the correct reblogs count', async t => {
await t.useRole(foobarRole)
.hover(getNthStatus(4))
.expect(getNthReblogged(4)).eql('true')
.click(getNthStatus(4))
.expect(getUrl()).contains('/status')
.expect(getNthReblogged(0)).eql('true')
.expect(getReblogsCount()).eql(2)
.click(homeNavButton)
.expect(getUrl()).eql('http://localhost:4002/')
.hover(getNthStatus(4))
.click(getNthReblogButton(4))
.expect(getNthReblogged(4)).eql('false')
.click(getNthStatus(4))
.expect(getUrl()).contains('/status')
.expect(getNthReblogged(0)).eql('false')
.expect(getReblogsCount()).eql(1)
})

View file

@ -51,6 +51,14 @@ export function getFavoritesCount () {
return favoritesCountElement.innerCount return favoritesCountElement.innerCount
} }
export function getNthReblogButton (n) {
return getNthStatus(n).find('.status-toolbar button:nth-child(2)')
}
export function getNthReblogged (n) {
return getNthReblogButton(n).getAttribute('aria-pressed')
}
export function getReblogsCount () { export function getReblogsCount () {
return reblogsCountElement.innerCount return reblogsCountElement.innerCount
} }