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) | ||||
|           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,12 +1,14 @@ | |||
| <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)}} | ||||
|     {{#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 | ||||
|  | @ -17,6 +19,7 @@ | |||
|       {{elseif status && status.reblog}} | ||||
|         boosted | ||||
|       {{/if}} | ||||
|     {{/if}} | ||||
|   </span> | ||||
| </div> | ||||
| <style> | ||||
|  | @ -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,6 +31,7 @@ export function getDatabase (instanceName) { | |||
|     } | ||||
|     req.onupgradeneeded = (e) => { | ||||
|       let db = req.result | ||||
|       if (e.oldVersion < 1) { | ||||
|         db.createObjectStore(META_STORE, {keyPath: 'key'}) | ||||
|         db.createObjectStore(STATUSES_STORE, {keyPath: 'id'}) | ||||
|         db.createObjectStore(ACCOUNTS_STORE, {keyPath: 'id'}) | ||||
|  | @ -40,6 +42,10 @@ export function getDatabase (instanceName) { | |||
|         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) | ||||
|   }) | ||||
|   return databaseCache[instanceName] | ||||
|  |  | |||
|  | @ -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