forked from cybrespace/pinafore
		
	implement pinned statuses
This commit is contained in:
		
							parent
							
								
									5adc975bef
								
							
						
					
					
						commit
						3213714f4b
					
				
					 11 changed files with 170 additions and 45 deletions
				
			
		| 
						 | 
				
			
			@ -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…
	
	Add table
		
		Reference in a new issue