diff --git a/routes/_actions/instances.js b/routes/_actions/instances.js index 86f8e632..5ad962c1 100644 --- a/routes/_actions/instances.js +++ b/routes/_actions/instances.js @@ -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}`) diff --git a/routes/_actions/pinnedStatuses.js b/routes/_actions/pinnedStatuses.js new file mode 100644 index 00000000..4956c0e5 --- /dev/null +++ b/routes/_actions/pinnedStatuses.js @@ -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}) + } + ) + + +} \ No newline at end of file diff --git a/routes/_components/search/Search.html b/routes/_components/search/Search.html index e1c3c825..48d63302 100644 --- a/routes/_components/search/Search.html +++ b/routes/_components/search/Search.html @@ -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) diff --git a/routes/_components/status/Status.html b/routes/_components/status/Status.html index 02601cfa..233bc267 100644 --- a/routes/_components/status/Status.html +++ b/routes/_components/status/Status.html @@ -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}} - + {{#if (notification && (notification.type === 'reblog' || notification.type === 'favourite')) || status.reblog || timelineType === 'pinned'}} + {{/if}} diff --git a/routes/_components/status/StatusHeader.html b/routes/_components/status/StatusHeader.html index 8836d6d9..791a1512 100644 --- a/routes/_components/status/StatusHeader.html +++ b/routes/_components/status/StatusHeader.html @@ -1,21 +1,24 @@
- + - - {{getAccount(notification, status).display_name || ('@' + getAccount(notification, status).username)}} - - {{#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}} + + {{account.display_name || ('@' + account.username)}} + + {{#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}}
@@ -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 } diff --git a/routes/_components/timeline/PinnedStatuses.html b/routes/_components/timeline/PinnedStatuses.html new file mode 100644 index 00000000..1fa7d4db --- /dev/null +++ b/routes/_components/timeline/PinnedStatuses.html @@ -0,0 +1,35 @@ +
+ {{#if pinnedStatuses}} + {{#each pinnedStatuses as status, index}} + + {{/each}} + {{/if}} +
+ \ No newline at end of file diff --git a/routes/_database/constants.js b/routes/_database/constants.js index 03454d8c..e69def9a 100644 --- a/routes/_database/constants.js +++ b/routes/_database/constants.js @@ -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' diff --git a/routes/_database/databaseLifecycle.js b/routes/_database/databaseLifecycle.js index d40e618c..78ff571c 100644 --- a/routes/_database/databaseLifecycle.js +++ b/routes/_database/databaseLifecycle.js @@ -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) }) diff --git a/routes/_database/timelines.js b/routes/_database/timelines.js index 0a18a153..ac2eca4a 100644 --- a/routes/_database/timelines.js +++ b/routes/_database/timelines.js @@ -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) + } + }) +} \ No newline at end of file diff --git a/routes/_store/store.js b/routes/_store/store.js index ab37262f..70071d0a 100644 --- a/routes/_store/store.js +++ b/routes/_store/store.js @@ -34,7 +34,8 @@ const store = new PinaforeStore({ autoplayGifs: false, markMediaAsSensitive: false, pinnedPages: {}, - instanceLists: {} + instanceLists: {}, + pinnedStatuses: {} }) mixins(PinaforeStore) diff --git a/routes/accounts/[accountId].html b/routes/accounts/[accountId].html index d9638d26..64107ed1 100644 --- a/routes/accounts/[accountId].html +++ b/routes/accounts/[accountId].html @@ -13,6 +13,7 @@ verifyCredentials="{{$currentVerifyCredentials}}" /> {{/if}} + {{else}} @@ -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 } } \ No newline at end of file