implement pinned statuses
This commit is contained in:
parent
5adc975bef
commit
3213714f4b
|
@ -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}`)
|
||||
|
|
23
routes/_actions/pinnedStatuses.js
Normal file
23
routes/_actions/pinnedStatuses.js
Normal 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})
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
35
routes/_components/timeline/PinnedStatuses.html
Normal file
35
routes/_components/timeline/PinnedStatuses.html
Normal 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>
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -34,7 +34,8 @@ const store = new PinaforeStore({
|
|||
autoplayGifs: false,
|
||||
markMediaAsSensitive: false,
|
||||
pinnedPages: {},
|
||||
instanceLists: {}
|
||||
instanceLists: {},
|
||||
pinnedStatuses: {}
|
||||
})
|
||||
|
||||
mixins(PinaforeStore)
|
||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue