start work on favoriting

This commit is contained in:
Nolan Lawson 2018-02-23 18:23:36 -08:00
parent ae04fddd68
commit 3a17f7ff7b
16 changed files with 128 additions and 48 deletions

View File

@ -0,0 +1,22 @@
import { favoriteStatus, unfavoriteStatus } from '../_api/favorite'
import { store } from '../_store/store'
import { database } from '../_database/database'
import { toast } from '../_utils/toast'
export async function setFavorited(statusId, favorited) {
let instanceName = store.get('currentInstance')
let accessToken = store.get('accessToken')
try {
let status = await (favorited
? favoriteStatus(instanceName, accessToken, statusId)
: unfavoriteStatus(instanceName, accessToken, statusId))
await database.insertStatus(instanceName, status)
let statusModifications = store.get('statusModifications')
let currentStatusModifications = statusModifications[instanceName] =
(statusModifications[instanceName] || {favorites: {}, reblogs: {}})
currentStatusModifications.favorites[statusId] = favorited
store.set({statusModifications: statusModifications})
} catch (e) {
toast.say('Failed to favorite/unfavorite. Please try again.')
}
}

View File

@ -1,10 +1,10 @@
import { get, paramsString } from '../_utils/ajax' import { getWithTimeout, paramsString } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
export async function getBlockedAccounts (instanceName, accessToken, limit = 80) { export async function getBlockedAccounts (instanceName, accessToken, limit = 80) {
let url = `${basename(instanceName)}/api/v1/blocks` let url = `${basename(instanceName)}/api/v1/blocks`
url += '?' + paramsString({ limit }) url += '?' + paramsString({ limit })
return get(url, { return getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }
@ -12,7 +12,7 @@ export async function getBlockedAccounts (instanceName, accessToken, limit = 80)
export async function getMutedAccounts (instanceName, accessToken, limit = 80) { export async function getMutedAccounts (instanceName, accessToken, limit = 80) {
let url = `${basename(instanceName)}/api/v1/mutes` let url = `${basename(instanceName)}/api/v1/mutes`
url += '?' + paramsString({ limit }) url += '?' + paramsString({ limit })
return get(url, { return getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }

16
routes/_api/favorite.js Normal file
View File

@ -0,0 +1,16 @@
import { post } from '../_utils/ajax'
import { basename } from './utils'
export async function favoriteStatus(instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/favourite`
return post(url, null, {
'Authorization': `Bearer ${accessToken}`
})
}
export async function unfavoriteStatus(instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/unfavourite`
return post(url, null, {
'Authorization': `Bearer ${accessToken}`
})
}

View File

@ -1,7 +1,7 @@
import { get } from '../_utils/ajax' import { getWithTimeout } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
export function getInstanceInfo (instanceName) { export function getInstanceInfo (instanceName) {
let url = `${basename(instanceName)}/api/v1/instance` let url = `${basename(instanceName)}/api/v1/instance`
return get(url) return getWithTimeout(url)
} }

View File

@ -1,9 +1,9 @@
import { get } from '../_utils/ajax' import { getWithTimeout } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
export function getLists (instanceName, accessToken) { export function getLists (instanceName, accessToken) {
let url = `${basename(instanceName)}/api/v1/lists` let url = `${basename(instanceName)}/api/v1/lists`
return get(url, { return getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }

View File

@ -1,4 +1,4 @@
import { post, paramsString } from '../_utils/ajax' import { postWithTimeout, paramsString } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
const WEBSITE = 'https://pinafore.social' const WEBSITE = 'https://pinafore.social'
@ -7,7 +7,7 @@ const CLIENT_NAME = 'Pinafore'
export function registerApplication (instanceName, redirectUri) { export function registerApplication (instanceName, redirectUri) {
const url = `${basename(instanceName)}/api/v1/apps` const url = `${basename(instanceName)}/api/v1/apps`
return post(url, { return postWithTimeout(url, {
client_name: CLIENT_NAME, client_name: CLIENT_NAME,
redirect_uris: redirectUri, redirect_uris: redirectUri,
scopes: SCOPES, scopes: SCOPES,
@ -27,7 +27,7 @@ export function generateAuthLink (instanceName, clientId, redirectUri) {
export function getAccessTokenFromAuthCode (instanceName, clientId, clientSecret, code, redirectUri) { export function getAccessTokenFromAuthCode (instanceName, clientId, clientSecret, code, redirectUri) {
let url = `${basename(instanceName)}/oauth/token` let url = `${basename(instanceName)}/oauth/token`
return post(url, { return postWithTimeout(url, {
client_id: clientId, client_id: clientId,
client_secret: clientSecret, client_secret: clientSecret,
redirect_uri: redirectUri, redirect_uri: redirectUri,

View File

@ -1,4 +1,4 @@
import { get, paramsString } from '../_utils/ajax' import { getWithTimeout, paramsString } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
export async function getPinnedStatuses (instanceName, accessToken, accountId) { export async function getPinnedStatuses (instanceName, accessToken, accountId) {
@ -7,7 +7,7 @@ export async function getPinnedStatuses (instanceName, accessToken, accountId) {
limit: 40, limit: 40,
pinned: true pinned: true
}) })
return get(url, { return getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }

View File

@ -1,10 +1,10 @@
import { get, paramsString } from '../_utils/ajax' import { getWithTimeout, paramsString } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
export async function getReblogs (instanceName, accessToken, statusId, limit = 80) { export async function getReblogs (instanceName, accessToken, statusId, limit = 80) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/reblogged_by` let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/reblogged_by`
url += '?' + paramsString({ limit }) url += '?' + paramsString({ limit })
return get(url, { return getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }
@ -12,7 +12,7 @@ export async function getReblogs (instanceName, accessToken, statusId, limit = 8
export async function getFavorites (instanceName, accessToken, statusId, limit = 80) { export async function getFavorites (instanceName, accessToken, statusId, limit = 80) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/favourited_by` let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/favourited_by`
url += '?' + paramsString({ limit }) url += '?' + paramsString({ limit })
return get(url, { return getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }

View File

@ -1,4 +1,4 @@
import { get, paramsString } from '../_utils/ajax' import { getWithTimeout, paramsString } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
export function search (instanceName, accessToken, query) { export function search (instanceName, accessToken, query) {
@ -6,7 +6,7 @@ export function search (instanceName, accessToken, query) {
q: query, q: query,
resolve: true resolve: true
}) })
return get(url, { return getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }

View File

@ -1,4 +1,4 @@
import { get, paramsString } from '../_utils/ajax' import { getWithTimeout, paramsString } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
function getTimelineUrlPath (timeline) { function getTimelineUrlPath (timeline) {
@ -57,14 +57,14 @@ export function getTimeline (instanceName, accessToken, timeline, maxId, since)
// special case - this is a list of descendents and ancestors // special case - this is a list of descendents and ancestors
let statusUrl = `${basename(instanceName)}/api/v1/statuses/${timeline.split('/').slice(-1)[0]}}` let statusUrl = `${basename(instanceName)}/api/v1/statuses/${timeline.split('/').slice(-1)[0]}}`
return Promise.all([ return Promise.all([
get(url, {'Authorization': `Bearer ${accessToken}`}), getWithTimeout(url, {'Authorization': `Bearer ${accessToken}`}),
get(statusUrl, {'Authorization': `Bearer ${accessToken}`}) getWithTimeout(statusUrl, {'Authorization': `Bearer ${accessToken}`})
]).then(res => { ]).then(res => {
return [].concat(res[0].ancestors).concat([res[1]]).concat(res[0].descendants) return [].concat(res[0].ancestors).concat([res[1]]).concat(res[0].descendants)
}) })
} }
return get(url, { return getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }

View File

@ -1,16 +1,16 @@
import { get, paramsString } from '../_utils/ajax' import { getWithTimeout, paramsString } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
export function getVerifyCredentials (instanceName, accessToken) { export function getVerifyCredentials (instanceName, accessToken) {
let url = `${basename(instanceName)}/api/v1/accounts/verify_credentials` let url = `${basename(instanceName)}/api/v1/accounts/verify_credentials`
return get(url, { return getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }
export function getAccount (instanceName, accessToken, accountId) { export function getAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}` let url = `${basename(instanceName)}/api/v1/accounts/${accountId}`
return get(url, { return getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }
@ -18,7 +18,7 @@ export function getAccount (instanceName, accessToken, accountId) {
export async function getRelationship (instanceName, accessToken, accountId) { export async function getRelationship (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/relationships` let url = `${basename(instanceName)}/api/v1/accounts/relationships`
url += '?' + paramsString({id: accountId}) url += '?' + paramsString({id: accountId})
let res = await get(url, { let res = await getWithTimeout(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
return res[0] return res[0]

View File

@ -13,7 +13,7 @@
<IconButton <IconButton
label="Favorite" label="Favorite"
pressable="true" pressable="true"
pressed="{{status.favourited}}" pressed="{{favorited}}"
href="#fa-star" href="#fa-star"
/> />
<IconButton <IconButton
@ -32,13 +32,14 @@
} }
</style> </style>
<script> <script>
import IconButton from '../IconButton.html' import IconButton from '../IconButton.html'
import { store } from '../../_store/store'
export default { export default {
components: { components: {
IconButton IconButton
}, },
store: () => store,
computed: { computed: {
visibility: (status) => status.visibility, visibility: (status) => status.visibility,
boostLabel: (visibility) => { boostLabel: (visibility) => {
@ -63,6 +64,12 @@
}, },
boostDisabled: (visibility) => { boostDisabled: (visibility) => {
return visibility === 'private' || visibility === 'direct' return visibility === 'private' || visibility === 'direct'
},
favorited: (status, $currentStatusModifications) => {
if ($currentStatusModifications && status.id in $currentStatusModifications.favorites) {
return $currentStatusModifications.favorites[status.id]
}
return status.favourited
} }
} }
} }

View File

@ -388,3 +388,15 @@ export async function getNotificationIdsForStatus (instanceName, statusId) {
} }
}) })
} }
//
// insert statuses
//
export async function insertStatus(instanceName, status) {
const db = await getDatabase(instanceName)
cacheStatus(statusesCache, status)
return dbPromise(db, STATUSES_STORE, 'readwrite', (statusesStore) => {
putStatus(statusesStore, status)
})
}

View File

@ -95,4 +95,10 @@ export function instanceComputations (store) {
return !!numberOfNotifications return !!numberOfNotifications
} }
) )
store.compute('currentStatusModifications',
['statusModifications', 'instanceName'],
(statusModifications, instanceName) => {
return statusModifications[instanceName]
})
} }

View File

@ -36,7 +36,8 @@ export const store = new PinaforeStore({
pinnedPages: {}, pinnedPages: {},
instanceLists: {}, instanceLists: {},
pinnedStatuses: {}, pinnedStatuses: {},
instanceInfos: {} instanceInfos: {},
statusModifications: {}
}) })
mixins(PinaforeStore) mixins(PinaforeStore)

View File

@ -1,38 +1,54 @@
const TIMEOUT = 15000 const TIMEOUT = 15000
function fetchWithTimeout (url, options) { function fetchWithTimeout (url, options) {
return Promise.race([ return new Promise((resolve, reject) => {
fetch(url, options), fetch(url, options).then(resolve, reject)
new Promise((resolve, reject) => { setTimeout(() => reject(new Error(`Timed out after ${TIMEOUT / 1000} seconds`)), TIMEOUT)
setTimeout(() => reject(new Error(`Timed out after ${TIMEOUT / 1000} seconds`)), TIMEOUT) })
})
])
} }
export async function post (url, body) { async function _post (url, body, headers, timeout) {
return (await fetchWithTimeout(url, { let fetchFunc = timeout ? fetchWithTimeout : fetch
return (await fetchFunc(url, {
method: 'POST', method: 'POST',
headers: { headers: Object.assign(headers, {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, }),
body: JSON.stringify(body) body: JSON.stringify(body)
})).json() })).json()
} }
async function _get (url, headers, timeout) {
let fetchFunc = timeout ? fetchWithTimeout : fetch
return (await fetchFunc(url, {
method: 'GET',
headers: Object.assign(headers, {
'Accept': 'application/json'
})
})).json()
}
export async function post (url, body, headers = {}) {
return _post(url, body, headers, false)
}
export async function postWithTimeout (url, body, headers = {}) {
return _post(url, body, headers, true)
}
export async function getWithTimeout (url, headers = {}) {
return _get(url, headers, true)
}
export async function get (url, headers = {}) {
return _get(url, headers, false)
}
export function paramsString (paramsObject) { export function paramsString (paramsObject) {
let params = new URLSearchParams() let params = new URLSearchParams()
Object.keys(paramsObject).forEach(key => { Object.keys(paramsObject).forEach(key => {
params.set(key, paramsObject[key]) params.set(key, paramsObject[key])
}) })
return params.toString() return params.toString()
} }
export async function get (url, headers = {}) {
return (await fetchWithTimeout(url, {
method: 'GET',
headers: Object.assign(headers, {
'Accept': 'application/json'
})
})).json()
}