implement pinned statuses
This commit is contained in:
parent
5adc975bef
commit
3213714f4b
|
@ -38,7 +38,9 @@ export async function logOutOfInstance (instanceName) {
|
||||||
loggedInInstances: loggedInInstances,
|
loggedInInstances: loggedInInstances,
|
||||||
instanceThemes: instanceThemes,
|
instanceThemes: instanceThemes,
|
||||||
loggedInInstancesInOrder: loggedInInstancesInOrder,
|
loggedInInstancesInOrder: loggedInInstancesInOrder,
|
||||||
currentInstance: newInstance
|
currentInstance: newInstance,
|
||||||
|
searchResults: null,
|
||||||
|
queryInSearch: ''
|
||||||
})
|
})
|
||||||
store.save()
|
store.save()
|
||||||
toast.say(`Logged out of ${instanceName}`)
|
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})
|
this.set({loading: true})
|
||||||
try {
|
try {
|
||||||
let results = await search(instanceName, accessToken, queryInSearch)
|
let results = await search(instanceName, accessToken, queryInSearch)
|
||||||
|
let currentQueryInSearch = this.store.get('queryInSearch') // avoid race conditions
|
||||||
|
if (currentQueryInSearch === queryInSearch) {
|
||||||
this.store.set({
|
this.store.set({
|
||||||
searchResultsForQuery: queryInSearch,
|
searchResultsForQuery: queryInSearch,
|
||||||
searchResults: results
|
searchResults: results
|
||||||
})
|
})
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.say('Error during search: ' + (e.name || '') + ' ' + (e.message || ''))
|
toast.say('Error during search: ' + (e.name || '') + ' ' + (e.message || ''))
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
aria-setsize="{{length}}"
|
aria-setsize="{{length}}"
|
||||||
aria-label="Status by {{originalStatus.account.display_name || originalStatus.account.username}}"
|
aria-label="Status by {{originalStatus.account.display_name || originalStatus.account.username}}"
|
||||||
on:recalculateHeight>
|
on:recalculateHeight>
|
||||||
{{#if (notification && (notification.type === 'reblog' || notification.type === 'favourite')) || status.reblog}}
|
{{#if (notification && (notification.type === 'reblog' || notification.type === 'favourite')) || status.reblog || timelineType === 'pinned'}}
|
||||||
<StatusHeader :notification :status :isStatusInNotification />
|
<StatusHeader :notification :status :isStatusInNotification :timelineType />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<StatusAuthorName status="{{originalStatus}}" :isStatusInOwnThread :isStatusInNotification />
|
<StatusAuthorName status="{{originalStatus}}" :isStatusInOwnThread :isStatusInNotification />
|
||||||
<StatusAuthorHandle status="{{originalStatus}}" :isStatusInNotification />
|
<StatusAuthorHandle status="{{originalStatus}}" :isStatusInNotification />
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
<div class="status-header {{isStatusInNotification ? 'status-in-notification' : ''}}">
|
<div class="status-header {{isStatusInNotification ? 'status-in-notification' : ''}}">
|
||||||
<svg>
|
<svg>
|
||||||
<use xlink:href="{{getIcon(notification, status)}}"/>
|
<use xlink:href="{{icon}}"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>
|
<span>
|
||||||
<a href="/accounts/{{getAccount(notification, status).id}}"
|
{{#if timelineType === 'pinned'}}
|
||||||
focus-key="{{focusKey}}"
|
Pinned toot
|
||||||
>
|
{{else}}
|
||||||
{{getAccount(notification, status).display_name || ('@' + getAccount(notification, status).username)}}
|
<a href="/accounts/{{account.id}}"
|
||||||
|
focus-key="{{focusKey}}" >
|
||||||
|
{{account.display_name || ('@' + account.username)}}
|
||||||
</a>
|
</a>
|
||||||
{{#if notification && notification.type === 'reblog'}}
|
{{#if notification && notification.type === 'reblog'}}
|
||||||
boosted your status
|
boosted your status
|
||||||
|
@ -17,6 +19,7 @@
|
||||||
{{elseif status && status.reblog}}
|
{{elseif status && status.reblog}}
|
||||||
boosted
|
boosted
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<style>
|
<style>
|
||||||
|
@ -66,18 +69,18 @@
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
statusId: (status) => status.id,
|
statusId: (status) => status.id,
|
||||||
focusKey: (statusId) => `status-header-${statusId}`
|
focusKey: (statusId) => `status-header-${statusId}`,
|
||||||
},
|
icon: (notification, status, timelineType) => {
|
||||||
helpers: {
|
if (timelineType === 'pinned') {
|
||||||
getIcon(notification, status) {
|
return '#fa-thumb-tack'
|
||||||
if ((notification && notification.type === 'reblog') || (status && status.reblog)) {
|
} else if ((notification && notification.type === 'reblog') || (status && status.reblog)) {
|
||||||
return '#fa-retweet'
|
return '#fa-retweet'
|
||||||
} else if (notification && notification.type === 'follow') {
|
} else if (notification && notification.type === 'follow') {
|
||||||
return '#fa-user-plus'
|
return '#fa-user-plus'
|
||||||
}
|
}
|
||||||
return '#fa-star'
|
return '#fa-star'
|
||||||
},
|
},
|
||||||
getAccount(notification, status) {
|
account: (notification, status) => {
|
||||||
if (notification && notification.account) {
|
if (notification && notification.account) {
|
||||||
return 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 RELATIONSHIPS_STORE = 'relationships'
|
||||||
export const NOTIFICATIONS_STORE = 'notifications'
|
export const NOTIFICATIONS_STORE = 'notifications'
|
||||||
export const NOTIFICATION_TIMELINES_STORE = 'notification_timelines'
|
export const NOTIFICATION_TIMELINES_STORE = 'notification_timelines'
|
||||||
|
export const PINNED_STATUSES_STORE = 'pinned_statuses'
|
||||||
|
|
|
@ -5,13 +5,14 @@ import {
|
||||||
ACCOUNTS_STORE,
|
ACCOUNTS_STORE,
|
||||||
RELATIONSHIPS_STORE,
|
RELATIONSHIPS_STORE,
|
||||||
NOTIFICATIONS_STORE,
|
NOTIFICATIONS_STORE,
|
||||||
NOTIFICATION_TIMELINES_STORE
|
NOTIFICATION_TIMELINES_STORE,
|
||||||
|
PINNED_STATUSES_STORE
|
||||||
} from './constants'
|
} from './constants'
|
||||||
|
|
||||||
const openReqs = {}
|
const openReqs = {}
|
||||||
const databaseCache = {}
|
const databaseCache = {}
|
||||||
|
|
||||||
const DB_VERSION = 1
|
const DB_VERSION = 2
|
||||||
|
|
||||||
export function getDatabase (instanceName) {
|
export function getDatabase (instanceName) {
|
||||||
if (!instanceName) {
|
if (!instanceName) {
|
||||||
|
@ -30,6 +31,7 @@ export function getDatabase (instanceName) {
|
||||||
}
|
}
|
||||||
req.onupgradeneeded = (e) => {
|
req.onupgradeneeded = (e) => {
|
||||||
let db = req.result
|
let db = req.result
|
||||||
|
if (e.oldVersion < 1) {
|
||||||
db.createObjectStore(META_STORE, {keyPath: 'key'})
|
db.createObjectStore(META_STORE, {keyPath: 'key'})
|
||||||
db.createObjectStore(STATUSES_STORE, {keyPath: 'id'})
|
db.createObjectStore(STATUSES_STORE, {keyPath: 'id'})
|
||||||
db.createObjectStore(ACCOUNTS_STORE, {keyPath: 'id'})
|
db.createObjectStore(ACCOUNTS_STORE, {keyPath: 'id'})
|
||||||
|
@ -40,6 +42,10 @@ export function getDatabase (instanceName) {
|
||||||
db.createObjectStore(NOTIFICATION_TIMELINES_STORE, {keyPath: 'id'})
|
db.createObjectStore(NOTIFICATION_TIMELINES_STORE, {keyPath: 'id'})
|
||||||
.createIndex('notificationId', 'notificationId')
|
.createIndex('notificationId', 'notificationId')
|
||||||
}
|
}
|
||||||
|
if (e.oldVersion < 2) {
|
||||||
|
db.createObjectStore(PINNED_STATUSES_STORE, {keyPath: 'id'})
|
||||||
|
}
|
||||||
|
}
|
||||||
req.onsuccess = () => resolve(req.result)
|
req.onsuccess = () => resolve(req.result)
|
||||||
})
|
})
|
||||||
return databaseCache[instanceName]
|
return databaseCache[instanceName]
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { toReversePaddedBigInt } from './utils'
|
import { toPaddedBigInt, toReversePaddedBigInt } from './utils'
|
||||||
import { dbPromise, getDatabase } from './databaseLifecycle'
|
import { dbPromise, getDatabase } from './databaseLifecycle'
|
||||||
import { accountsCache, getInCache, hasInCache, notificationsCache, setInCache, statusesCache } from './cache'
|
import { accountsCache, getInCache, hasInCache, notificationsCache, setInCache, statusesCache } from './cache'
|
||||||
import {
|
import {
|
||||||
ACCOUNTS_STORE,
|
ACCOUNTS_STORE,
|
||||||
NOTIFICATION_TIMELINES_STORE,
|
NOTIFICATION_TIMELINES_STORE,
|
||||||
NOTIFICATIONS_STORE,
|
NOTIFICATIONS_STORE, PINNED_STATUSES_STORE,
|
||||||
STATUS_TIMELINES_STORE,
|
STATUS_TIMELINES_STORE,
|
||||||
STATUSES_STORE
|
STATUSES_STORE
|
||||||
} from './constants'
|
} from './constants'
|
||||||
|
@ -57,6 +57,14 @@ function cloneForStorage (obj) {
|
||||||
return res
|
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
|
// pagination
|
||||||
//
|
//
|
||||||
|
@ -214,11 +222,7 @@ async function insertTimelineNotifications (instanceName, timeline, notification
|
||||||
|
|
||||||
async function insertTimelineStatuses (instanceName, timeline, statuses) {
|
async function insertTimelineStatuses (instanceName, timeline, statuses) {
|
||||||
for (let status of statuses) {
|
for (let status of statuses) {
|
||||||
setInCache(statusesCache, instanceName, status.id, status)
|
cacheStatus(status, instanceName)
|
||||||
setInCache(accountsCache, instanceName, status.account.id, status.account)
|
|
||||||
if (status.reblog) {
|
|
||||||
setInCache(accountsCache, instanceName, status.reblog.account.id, status.reblog.account)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const db = await getDatabase(instanceName)
|
const db = await getDatabase(instanceName)
|
||||||
let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
|
let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
|
||||||
|
@ -271,3 +275,47 @@ export async function getNotification (instanceName, id) {
|
||||||
setInCache(notificationsCache, instanceName, id, result)
|
setInCache(notificationsCache, instanceName, id, result)
|
||||||
return 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,
|
autoplayGifs: false,
|
||||||
markMediaAsSensitive: false,
|
markMediaAsSensitive: false,
|
||||||
pinnedPages: {},
|
pinnedPages: {},
|
||||||
instanceLists: {}
|
instanceLists: {},
|
||||||
|
pinnedStatuses: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
mixins(PinaforeStore)
|
mixins(PinaforeStore)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
verifyCredentials="{{$currentVerifyCredentials}}"
|
verifyCredentials="{{$currentVerifyCredentials}}"
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
<PinnedStatuses accountId="{{params.accountId}}" />
|
||||||
<LazyTimeline timeline='account/{{params.accountId}}' />
|
<LazyTimeline timeline='account/{{params.accountId}}' />
|
||||||
{{else}}
|
{{else}}
|
||||||
<HiddenFromSSR>
|
<HiddenFromSSR>
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
import { updateProfileAndRelationship } from '../_actions/accounts'
|
import { updateProfileAndRelationship } from '../_actions/accounts'
|
||||||
import AccountProfile from '../_components/AccountProfile.html'
|
import AccountProfile from '../_components/AccountProfile.html'
|
||||||
import { updateVerifyCredentialsForInstance } from '../_actions/instances'
|
import { updateVerifyCredentialsForInstance } from '../_actions/instances'
|
||||||
|
import PinnedStatuses from '../_components/timeline/PinnedStatuses.html'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate() {
|
oncreate() {
|
||||||
|
@ -57,7 +59,8 @@
|
||||||
FreeTextLayout,
|
FreeTextLayout,
|
||||||
HiddenFromSSR,
|
HiddenFromSSR,
|
||||||
DynamicPageBanner,
|
DynamicPageBanner,
|
||||||
AccountProfile
|
AccountProfile,
|
||||||
|
PinnedStatuses
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
Loading…
Reference in a new issue