implement pinned statuses

This commit is contained in:
Nolan Lawson 2018-02-11 10:35:25 -08:00
parent 5adc975bef
commit 3213714f4b
11 changed files with 170 additions and 45 deletions

View File

@ -38,7 +38,9 @@ export async function logOutOfInstance (instanceName) {
loggedInInstances: loggedInInstances,
instanceThemes: instanceThemes,
loggedInInstancesInOrder: loggedInInstancesInOrder,
currentInstance: newInstance
currentInstance: newInstance,
searchResults: null,
queryInSearch: ''
})
store.save()
toast.say(`Logged out of ${instanceName}`)

View File

@ -0,0 +1,23 @@
import { store } from '../_store/store'
import { cacheFirstUpdateAfter } from '../_utils/sync'
import { getPinnedStatuses } from '../_api/pinnedStatuses'
import { database } from '../_database/database'
export async function updatePinnedStatusesForAccount(accountId) {
let instanceName = store.get('currentInstance')
let accessToken = store.get('accessToken')
await cacheFirstUpdateAfter(
() => getPinnedStatuses(instanceName, accessToken, accountId),
() => database.getPinnedStatuses(instanceName, accountId),
statuses => database.insertPinnedStatuses(instanceName, accountId, statuses),
statuses => {
let $pinnedStatuses = store.get('pinnedStatuses')
$pinnedStatuses[instanceName] = $pinnedStatuses[instanceName] || {}
$pinnedStatuses[instanceName][accountId] = statuses
store.set({pinnedStatuses: $pinnedStatuses})
}
)
}

View File

@ -77,10 +77,13 @@
this.set({loading: true})
try {
let results = await search(instanceName, accessToken, queryInSearch)
this.store.set({
searchResultsForQuery: queryInSearch,
searchResults: results
})
let currentQueryInSearch = this.store.get('queryInSearch') // avoid race conditions
if (currentQueryInSearch === queryInSearch) {
this.store.set({
searchResultsForQuery: queryInSearch,
searchResults: results
})
}
} catch (e) {
toast.say('Error during search: ' + (e.name || '') + ' ' + (e.message || ''))
console.error(e)

View File

@ -7,8 +7,8 @@
aria-setsize="{{length}}"
aria-label="Status by {{originalStatus.account.display_name || originalStatus.account.username}}"
on:recalculateHeight>
{{#if (notification && (notification.type === 'reblog' || notification.type === 'favourite')) || status.reblog}}
<StatusHeader :notification :status :isStatusInNotification />
{{#if (notification && (notification.type === 'reblog' || notification.type === 'favourite')) || status.reblog || timelineType === 'pinned'}}
<StatusHeader :notification :status :isStatusInNotification :timelineType />
{{/if}}
<StatusAuthorName status="{{originalStatus}}" :isStatusInOwnThread :isStatusInNotification />
<StatusAuthorHandle status="{{originalStatus}}" :isStatusInNotification />

View File

@ -1,21 +1,24 @@
<div class="status-header {{isStatusInNotification ? 'status-in-notification' : ''}}">
<svg>
<use xlink:href="{{getIcon(notification, status)}}"/>
<use xlink:href="{{icon}}"/>
</svg>
<span>
<a href="/accounts/{{getAccount(notification, status).id}}"
focus-key="{{focusKey}}"
>
{{getAccount(notification, status).display_name || ('@' + getAccount(notification, status).username)}}
</a>
{{#if notification && notification.type === 'reblog'}}
boosted your status
{{elseif notification && notification.type === 'favourite'}}
favorited your status
{{elseif notification && notification.type === 'follow'}}
followed you
{{elseif status && status.reblog}}
boosted
{{#if timelineType === 'pinned'}}
Pinned toot
{{else}}
<a href="/accounts/{{account.id}}"
focus-key="{{focusKey}}" >
{{account.display_name || ('@' + account.username)}}
</a>
{{#if notification && notification.type === 'reblog'}}
boosted your status
{{elseif notification && notification.type === 'favourite'}}
favorited your status
{{elseif notification && notification.type === 'follow'}}
followed you
{{elseif status && status.reblog}}
boosted
{{/if}}
{{/if}}
</span>
</div>
@ -66,18 +69,18 @@
export default {
computed: {
statusId: (status) => status.id,
focusKey: (statusId) => `status-header-${statusId}`
},
helpers: {
getIcon(notification, status) {
if ((notification && notification.type === 'reblog') || (status && status.reblog)) {
focusKey: (statusId) => `status-header-${statusId}`,
icon: (notification, status, timelineType) => {
if (timelineType === 'pinned') {
return '#fa-thumb-tack'
} else if ((notification && notification.type === 'reblog') || (status && status.reblog)) {
return '#fa-retweet'
} else if (notification && notification.type === 'follow') {
return '#fa-user-plus'
}
return '#fa-star'
},
getAccount(notification, status) {
account: (notification, status) => {
if (notification && notification.account) {
return notification.account
}

View File

@ -0,0 +1,35 @@
<div role="feed" aria-label="Pinned toots" classes="pinned-statuses">
{{#if pinnedStatuses}}
{{#each pinnedStatuses as status, index}}
<Status :status
timelineType="pinned"
timelineValue="{{accountId}}"
:index
length="{{pinnedStatuses.length}}"
/>
{{/each}}
{{/if}}
</div>
<script>
import { store } from '../../_store/store'
import Status from '../status/Status.html'
import LoadingPage from '../../_components/LoadingPage.html'
import { updatePinnedStatusesForAccount } from '../../_actions/pinnedStatuses'
export default {
async oncreate() {
let accountId = this.get('accountId')
await updatePinnedStatusesForAccount(accountId)
},
computed: {
pinnedStatuses: ($pinnedStatuses, $currentInstance, accountId) => {
return $pinnedStatuses[$currentInstance] && $pinnedStatuses[$currentInstance][accountId]
}
},
store: () => store,
components: {
Status,
LoadingPage
}
}
</script>

View File

@ -5,3 +5,4 @@ export const ACCOUNTS_STORE = 'accounts'
export const RELATIONSHIPS_STORE = 'relationships'
export const NOTIFICATIONS_STORE = 'notifications'
export const NOTIFICATION_TIMELINES_STORE = 'notification_timelines'
export const PINNED_STATUSES_STORE = 'pinned_statuses'

View File

@ -5,13 +5,14 @@ import {
ACCOUNTS_STORE,
RELATIONSHIPS_STORE,
NOTIFICATIONS_STORE,
NOTIFICATION_TIMELINES_STORE
NOTIFICATION_TIMELINES_STORE,
PINNED_STATUSES_STORE
} from './constants'
const openReqs = {}
const databaseCache = {}
const DB_VERSION = 1
const DB_VERSION = 2
export function getDatabase (instanceName) {
if (!instanceName) {
@ -30,15 +31,20 @@ export function getDatabase (instanceName) {
}
req.onupgradeneeded = (e) => {
let db = req.result
db.createObjectStore(META_STORE, {keyPath: 'key'})
db.createObjectStore(STATUSES_STORE, {keyPath: 'id'})
db.createObjectStore(ACCOUNTS_STORE, {keyPath: 'id'})
db.createObjectStore(RELATIONSHIPS_STORE, {keyPath: 'id'})
db.createObjectStore(NOTIFICATIONS_STORE, {keyPath: 'id'})
db.createObjectStore(STATUS_TIMELINES_STORE, {keyPath: 'id'})
if (e.oldVersion < 1) {
db.createObjectStore(META_STORE, {keyPath: 'key'})
db.createObjectStore(STATUSES_STORE, {keyPath: 'id'})
db.createObjectStore(ACCOUNTS_STORE, {keyPath: 'id'})
db.createObjectStore(RELATIONSHIPS_STORE, {keyPath: 'id'})
db.createObjectStore(NOTIFICATIONS_STORE, {keyPath: 'id'})
db.createObjectStore(STATUS_TIMELINES_STORE, {keyPath: 'id'})
.createIndex('statusId', 'statusId')
db.createObjectStore(NOTIFICATION_TIMELINES_STORE, {keyPath: 'id'})
db.createObjectStore(NOTIFICATION_TIMELINES_STORE, {keyPath: 'id'})
.createIndex('notificationId', 'notificationId')
}
if (e.oldVersion < 2) {
db.createObjectStore(PINNED_STATUSES_STORE, {keyPath: 'id'})
}
}
req.onsuccess = () => resolve(req.result)
})

View File

@ -1,10 +1,10 @@
import { toReversePaddedBigInt } from './utils'
import { toPaddedBigInt, toReversePaddedBigInt } from './utils'
import { dbPromise, getDatabase } from './databaseLifecycle'
import { accountsCache, getInCache, hasInCache, notificationsCache, setInCache, statusesCache } from './cache'
import {
ACCOUNTS_STORE,
NOTIFICATION_TIMELINES_STORE,
NOTIFICATIONS_STORE,
NOTIFICATIONS_STORE, PINNED_STATUSES_STORE,
STATUS_TIMELINES_STORE,
STATUSES_STORE
} from './constants'
@ -57,6 +57,14 @@ function cloneForStorage (obj) {
return res
}
function cacheStatus(status, instanceName) {
setInCache(statusesCache, instanceName, status.id, status)
setInCache(accountsCache, instanceName, status.account.id, status.account)
if (status.reblog) {
setInCache(accountsCache, instanceName, status.reblog.account.id, status.reblog.account)
}
}
//
// pagination
//
@ -214,11 +222,7 @@ async function insertTimelineNotifications (instanceName, timeline, notification
async function insertTimelineStatuses (instanceName, timeline, statuses) {
for (let status of statuses) {
setInCache(statusesCache, instanceName, status.id, status)
setInCache(accountsCache, instanceName, status.account.id, status.account)
if (status.reblog) {
setInCache(accountsCache, instanceName, status.reblog.account.id, status.reblog.account)
}
cacheStatus(status, instanceName)
}
const db = await getDatabase(instanceName)
let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
@ -271,3 +275,47 @@ export async function getNotification (instanceName, id) {
setInCache(notificationsCache, instanceName, id, result)
return result
}
//
// pinned statuses
//
export async function insertPinnedStatuses (instanceName, accountId, statuses) {
for (let status of statuses) {
cacheStatus(status, instanceName)
}
const db = await getDatabase(instanceName)
let storeNames = [PINNED_STATUSES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
await dbPromise(db, storeNames, 'readwrite', (stores) => {
let [ pinnedStatusesStore, statusesStore, accountsStore ] = stores
statuses.forEach((status, i) => {
storeStatus(statusesStore, accountsStore, status)
pinnedStatusesStore.put({
id: accountId + '\u0000' + toPaddedBigInt(i),
statusId: status.id
})
})
})
}
export async function getPinnedStatuses (instanceName, accountId) {
let storeNames = [PINNED_STATUSES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
const db = await getDatabase(instanceName)
return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ pinnedStatusesStore, statusesStore, accountsStore ] = stores
let keyRange = IDBKeyRange.bound(
accountId + '\u0000',
accountId + '\u0000\uffff'
)
pinnedStatusesStore.getAll(keyRange).onsuccess = e => {
let pinnedResults = e.target.result
let res = new Array(pinnedResults.length)
pinnedResults.forEach((pinnedResult, i) => {
fetchStatus(statusesStore, accountsStore, pinnedResult.statusId, status => {
res[i] = status
})
})
callback(res)
}
})
}

View File

@ -34,7 +34,8 @@ const store = new PinaforeStore({
autoplayGifs: false,
markMediaAsSensitive: false,
pinnedPages: {},
instanceLists: {}
instanceLists: {},
pinnedStatuses: {}
})
mixins(PinaforeStore)

View File

@ -13,6 +13,7 @@
verifyCredentials="{{$currentVerifyCredentials}}"
/>
{{/if}}
<PinnedStatuses accountId="{{params.accountId}}" />
<LazyTimeline timeline='account/{{params.accountId}}' />
{{else}}
<HiddenFromSSR>
@ -34,6 +35,7 @@
import { updateProfileAndRelationship } from '../_actions/accounts'
import AccountProfile from '../_components/AccountProfile.html'
import { updateVerifyCredentialsForInstance } from '../_actions/instances'
import PinnedStatuses from '../_components/timeline/PinnedStatuses.html'
export default {
oncreate() {
@ -57,7 +59,8 @@
FreeTextLayout,
HiddenFromSSR,
DynamicPageBanner,
AccountProfile
AccountProfile,
PinnedStatuses
}
}
</script>