more work on profile page
This commit is contained in:
		
							parent
							
								
									5e7438cb52
								
							
						
					
					
						commit
						88d59678f2
					
				
					 14 changed files with 309 additions and 205 deletions
				
			
		| 
						 | 
				
			
			@ -1,20 +1,26 @@
 | 
			
		|||
<div class="account-profile {{headerIsMissing ? 'header-is-missing' : ''}}" style="background-image: url({{profile.header}});">
 | 
			
		||||
  <div class="account-profile-grid">
 | 
			
		||||
    <div class="account-profile-avatar">
 | 
			
		||||
      <img src="{{profile.avatar}}">
 | 
			
		||||
      <img src="{{profile.avatar}}" aria-hidden="true">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="account-profile-name">
 | 
			
		||||
      {{profile.display_name}}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="account-profile-following">
 | 
			
		||||
    <div class="account-profile-followed-by">
 | 
			
		||||
      {{#if relationship && relationship.followed_by}}
 | 
			
		||||
      <span>
 | 
			
		||||
        Follows you
 | 
			
		||||
      </span>
 | 
			
		||||
      {{/if}}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="account-profile-follow">
 | 
			
		||||
      <svg>
 | 
			
		||||
        <use xlink:href="#fa-user-plus" />
 | 
			
		||||
      </svg>
 | 
			
		||||
      {{#if verifyCredentials && relationship && verifyCredentials.id !== relationship.id}}
 | 
			
		||||
        <IconButton
 | 
			
		||||
          label="{{relationship && relationship.following ? 'Unfollow' : 'Follow'}}"
 | 
			
		||||
          href="{{relationship && relationship.following ? '#fa-user-times' : '#fa-user-plus'}}"
 | 
			
		||||
          big="true"
 | 
			
		||||
        />
 | 
			
		||||
      {{/if}}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="account-profile-note">
 | 
			
		||||
      {{{profile.note}}}
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +38,7 @@
 | 
			
		|||
 | 
			
		||||
  .account-profile.header-is-missing {
 | 
			
		||||
    padding-top: 0;
 | 
			
		||||
    background-color: #ccc;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .account-profile-background {
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +52,7 @@
 | 
			
		|||
 | 
			
		||||
  .account-profile-grid {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-areas: "avatar     name        following   follow"
 | 
			
		||||
    grid-template-areas: "avatar     name        followed-by   follow"
 | 
			
		||||
                         "avatar     note        note        note";
 | 
			
		||||
    grid-template-columns: min-content auto 1fr min-content;
 | 
			
		||||
    grid-column-gap: 10px;
 | 
			
		||||
| 
						 | 
				
			
			@ -68,19 +75,19 @@
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .account-profile-following, .account-profile-avatar, .account-profile-follow,
 | 
			
		||||
  .account-profile-followed-by, .account-profile-avatar, .account-profile-follow,
 | 
			
		||||
      .account-profile-name, .account-profile-username, .account-profile-note {
 | 
			
		||||
    z-index: 10;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .account-profile-following {
 | 
			
		||||
    grid-area: following;
 | 
			
		||||
  .account-profile-followed-by {
 | 
			
		||||
    grid-area: followed-by;
 | 
			
		||||
    align-self: center;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
    color: var(--deemphasized-text-color);
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
  }
 | 
			
		||||
  .account-profile-following span {
 | 
			
		||||
  .account-profile-followed-by span {
 | 
			
		||||
    background: rgba(30, 30, 30, 0.2);
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    padding: 3px 5px;
 | 
			
		||||
| 
						 | 
				
			
			@ -99,11 +106,6 @@
 | 
			
		|||
    grid-area: follow;
 | 
			
		||||
    align-self: center;
 | 
			
		||||
  }
 | 
			
		||||
  .account-profile-follow svg {
 | 
			
		||||
    width: 32px;
 | 
			
		||||
    height: 32px;
 | 
			
		||||
    fill: var(--svg-fill);
 | 
			
		||||
  }
 | 
			
		||||
  .account-profile-name {
 | 
			
		||||
    grid-area: name;
 | 
			
		||||
    font-size: 1.5em;
 | 
			
		||||
| 
						 | 
				
			
			@ -121,9 +123,14 @@
 | 
			
		|||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import IconButton from './IconButton.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    computed: {
 | 
			
		||||
      headerIsMissing: (profile) => profile.header.endsWith('missing.png')
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
      IconButton
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										56
									
								
								routes/_components/IconButton.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								routes/_components/IconButton.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,56 @@
 | 
			
		|||
{{#if pressable}}
 | 
			
		||||
  <button type="button"
 | 
			
		||||
          aria-label="{{label}}"
 | 
			
		||||
          aria-pressed="{{!!pressed}}"
 | 
			
		||||
          class="icon-button {{pressed ? 'pressed' : ''}} {{big ? 'big-icon' : ''}}">
 | 
			
		||||
    <svg>
 | 
			
		||||
      <use xlink:href="{{href}}" />
 | 
			
		||||
    </svg>
 | 
			
		||||
  </button>
 | 
			
		||||
{{else}}
 | 
			
		||||
  <button type="button"
 | 
			
		||||
          aria-label="{{label}}"
 | 
			
		||||
          class="icon-button {{big ? 'big-icon' : ''}}">
 | 
			
		||||
    <svg>
 | 
			
		||||
      <use xlink:href="{{href}}" />
 | 
			
		||||
    </svg>
 | 
			
		||||
  </button>
 | 
			
		||||
{{/if}}
 | 
			
		||||
<style>
 | 
			
		||||
  button.icon-button {
 | 
			
		||||
    padding: 6px 10px;
 | 
			
		||||
    background: none;
 | 
			
		||||
    border: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  button.icon-button svg {
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
    fill: var(--action-button-fill-color);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  button.icon-button.big-icon svg {
 | 
			
		||||
    width: 32px;
 | 
			
		||||
    height: 32px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  button.icon-button:hover svg {
 | 
			
		||||
    fill: var(--action-button-fill-color-hover);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  button.icon-button:active svg {
 | 
			
		||||
    fill: var(--action-button-fill-color-active);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  button.icon-button.pressed svg {
 | 
			
		||||
    fill: var(--action-button-fill-color-pressed)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  button.icon-button.pressed:hover svg {
 | 
			
		||||
    fill: var(--action-button-fill-color-pressed-hover);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  button.icon-button.pressed:active svg {
 | 
			
		||||
    fill: var(--action-button-fill-color-pressed-active);
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,30 +1,24 @@
 | 
			
		|||
<div class="status-toolbar">
 | 
			
		||||
  <button aria-label="Reply" type="button">
 | 
			
		||||
    <svg>
 | 
			
		||||
      <use xlink:href="#fa-reply" />
 | 
			
		||||
    </svg>
 | 
			
		||||
  </button>
 | 
			
		||||
  <button aria-label="Boost" aria-pressed="{{status.reblogged}}" class="{{status.reblogged ? 'selected' : ''}}" type="button">
 | 
			
		||||
    <svg>
 | 
			
		||||
      {{#if status.visibility === 'private'}}
 | 
			
		||||
        <use xlink:href="#fa-lock" />
 | 
			
		||||
      {{elseif status.visibility === 'direct'}}
 | 
			
		||||
        <use xlink:href="#fa-envelope" />
 | 
			
		||||
      {{else}}
 | 
			
		||||
        <use xlink:href="#fa-retweet" />
 | 
			
		||||
      {{/if}}
 | 
			
		||||
    </svg>
 | 
			
		||||
  </button>
 | 
			
		||||
  <button aria-label="Favorite" aria-pressed="{{status.favourited}}" class="{{status.favourited ? 'selected' : ''}}" type="button">
 | 
			
		||||
    <svg>
 | 
			
		||||
      <use xlink:href="#fa-star" />
 | 
			
		||||
    </svg>
 | 
			
		||||
  </button>
 | 
			
		||||
  <button aria-label="Show more actions" type="button">
 | 
			
		||||
    <svg>
 | 
			
		||||
      <use xlink:href="#fa-ellipsis-h" />
 | 
			
		||||
    </svg>
 | 
			
		||||
  </button>
 | 
			
		||||
  <IconButton
 | 
			
		||||
    label="Reply"
 | 
			
		||||
    href="#fa-reply"
 | 
			
		||||
    />
 | 
			
		||||
  <IconButton
 | 
			
		||||
    label="Boost"
 | 
			
		||||
    pressable="true"
 | 
			
		||||
    pressed="{{status.reblogged}}"
 | 
			
		||||
    href="{{status.visibility === 'private' ? '#fa-lock' : status.visibility === 'direct' ? '#fa-envelope' : '#fa-retweet'}}"
 | 
			
		||||
  />
 | 
			
		||||
  <IconButton
 | 
			
		||||
    label="Favorite"
 | 
			
		||||
    pressable="true"
 | 
			
		||||
    pressed="{{status.favourited}}"
 | 
			
		||||
    href="#fa-star"
 | 
			
		||||
    />
 | 
			
		||||
  <IconButton
 | 
			
		||||
    label="Show more actions"
 | 
			
		||||
    href="#fa-ellipsis-h"
 | 
			
		||||
  />
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
  .status-toolbar {
 | 
			
		||||
| 
						 | 
				
			
			@ -32,41 +26,14 @@
 | 
			
		|||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .status-toolbar button {
 | 
			
		||||
    padding: 6px 10px;
 | 
			
		||||
    background: none;
 | 
			
		||||
    border: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .status-toolbar button svg {
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
    fill: var(--action-button-fill-color);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .status-toolbar button:hover svg {
 | 
			
		||||
    fill: var(--action-button-fill-color-hover);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .status-toolbar button:active svg {
 | 
			
		||||
    fill: var(--action-button-fill-color-active);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .status-toolbar button.selected svg {
 | 
			
		||||
    fill: var(--action-button-fill-color-pressed)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .status-toolbar button.selected:hover svg {
 | 
			
		||||
    fill: var(--action-button-fill-color-pressed-hover);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .status-toolbar button.selected:active svg {
 | 
			
		||||
    fill: var(--action-button-fill-color-pressed-active);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
  import IconButton from '../IconButton.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    components: {
 | 
			
		||||
      IconButton
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +1,10 @@
 | 
			
		|||
<div class="lazy-timeline">
 | 
			
		||||
  {{#if !$initialized}}
 | 
			
		||||
    <div transition:fade>
 | 
			
		||||
    <!-- <div transition:fade> -->
 | 
			
		||||
      <div class="loading-page">
 | 
			
		||||
        <LoadingSpinner />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- </div> -->
 | 
			
		||||
  {{/if}}
 | 
			
		||||
  {{#await promise}}
 | 
			
		||||
  {{then constructor}}
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +33,8 @@
 | 
			
		|||
<script>
 | 
			
		||||
  import { importTimeline } from '../../_utils/asyncModules'
 | 
			
		||||
  import LoadingSpinner from '../LoadingSpinner.html'
 | 
			
		||||
  import { fade } from 'svelte-transitions'
 | 
			
		||||
  // TODO: the transition seems to occasionally cause an error in Svelte, transition_run is undefined
 | 
			
		||||
  //import { fade } from 'svelte-transitions'
 | 
			
		||||
  import { store } from '../../_utils/store'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
| 
						 | 
				
			
			@ -49,9 +50,9 @@
 | 
			
		|||
    }),
 | 
			
		||||
    components: {
 | 
			
		||||
      LoadingSpinner
 | 
			
		||||
    },
 | 
			
		||||
    }/*,
 | 
			
		||||
    transitions: {
 | 
			
		||||
      fade
 | 
			
		||||
    }
 | 
			
		||||
    }*/
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										61
									
								
								routes/_utils/database/cache.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								routes/_utils/database/cache.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
import QuickLRU from 'quick-lru'
 | 
			
		||||
 | 
			
		||||
export const statusesCache = {
 | 
			
		||||
  maxSize: 100,
 | 
			
		||||
  caches: {}
 | 
			
		||||
}
 | 
			
		||||
export const accountsCache = {
 | 
			
		||||
  maxSize: 50,
 | 
			
		||||
  caches: {}
 | 
			
		||||
}
 | 
			
		||||
export const relationshipsCache = {
 | 
			
		||||
  maxSize: 20,
 | 
			
		||||
  caches: {}
 | 
			
		||||
}
 | 
			
		||||
export const metaCache = {
 | 
			
		||||
  maxSize: 20,
 | 
			
		||||
  caches: {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
  window.cacheStats = {
 | 
			
		||||
    statuses: statusesCache,
 | 
			
		||||
    accounts: accountsCache,
 | 
			
		||||
    relationships: relationshipsCache,
 | 
			
		||||
    meta: metaCache
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getOrCreateInstanceCache(cache, instanceName) {
 | 
			
		||||
  let cached = cache.caches[instanceName]
 | 
			
		||||
  if (!cached) {
 | 
			
		||||
    cached = cache.caches[instanceName] = new QuickLRU({maxSize: cache.maxSize})
 | 
			
		||||
  }
 | 
			
		||||
  return cached
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function clearCache(cache, instanceName) {
 | 
			
		||||
  delete cache.caches[instanceName]
 | 
			
		||||
}
 | 
			
		||||
export function setInCache(cache, instanceName, key, value) {
 | 
			
		||||
  let instanceCache = getOrCreateInstanceCache(cache, instanceName)
 | 
			
		||||
  return instanceCache.set(key, value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getInCache(cache, instanceName, key) {
 | 
			
		||||
  let instanceCache = getOrCreateInstanceCache(cache, instanceName)
 | 
			
		||||
  return instanceCache.get(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function hasInCache(cache, instanceName, key) {
 | 
			
		||||
  let instanceCache = getOrCreateInstanceCache(cache, instanceName)
 | 
			
		||||
  let res = instanceCache.has(key)
 | 
			
		||||
  if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
    if (res) {
 | 
			
		||||
      cache.hits = (cache.hits || 0) + 1
 | 
			
		||||
    } else {
 | 
			
		||||
      cache.misses = (cache.misses || 0) + 1
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return res
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
export const STATUSES_STORE = 'statuses'
 | 
			
		||||
export const TIMELINE_STORE = 'timelines'
 | 
			
		||||
export const META_STORE = 'meta'
 | 
			
		||||
export const ACCOUNTS_STORE= 'accounts'
 | 
			
		||||
export const ACCOUNTS_STORE = 'accounts'
 | 
			
		||||
export const RELATIONSHIPS_STORE = 'relationships'
 | 
			
		||||
| 
						 | 
				
			
			@ -10,69 +10,44 @@ import {
 | 
			
		|||
import {
 | 
			
		||||
  META_STORE,
 | 
			
		||||
  TIMELINE_STORE,
 | 
			
		||||
  STATUSES_STORE, ACCOUNTS_STORE
 | 
			
		||||
  STATUSES_STORE,
 | 
			
		||||
  ACCOUNTS_STORE,
 | 
			
		||||
  RELATIONSHIPS_STORE
 | 
			
		||||
} from './constants'
 | 
			
		||||
 | 
			
		||||
import QuickLRU from 'quick-lru'
 | 
			
		||||
import {
 | 
			
		||||
  statusesCache,
 | 
			
		||||
  relationshipsCache,
 | 
			
		||||
  accountsCache,
 | 
			
		||||
  metaCache,
 | 
			
		||||
  clearCache,
 | 
			
		||||
  getInCache,
 | 
			
		||||
  hasInCache,
 | 
			
		||||
  setInCache
 | 
			
		||||
} from './cache'
 | 
			
		||||
 | 
			
		||||
const statusesCache = {
 | 
			
		||||
  maxSize: 100,
 | 
			
		||||
  caches: {}
 | 
			
		||||
}
 | 
			
		||||
const accountsCache = {
 | 
			
		||||
  maxSize: 50,
 | 
			
		||||
  caches: {}
 | 
			
		||||
}
 | 
			
		||||
const metaCache = {
 | 
			
		||||
  maxSize: 20,
 | 
			
		||||
  caches: {}
 | 
			
		||||
}
 | 
			
		||||
//
 | 
			
		||||
// helpers
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
  window.cacheStats = {
 | 
			
		||||
    statuses: {
 | 
			
		||||
      cache: statusesCache,
 | 
			
		||||
      hits: 0,
 | 
			
		||||
      misses: 0
 | 
			
		||||
    },
 | 
			
		||||
    accounts: {
 | 
			
		||||
      cache: accountsCache,
 | 
			
		||||
      hits: 0,
 | 
			
		||||
      misses: 0
 | 
			
		||||
    },
 | 
			
		||||
    meta: {
 | 
			
		||||
      cache: accountsCache,
 | 
			
		||||
      hits: 0,
 | 
			
		||||
      misses: 0
 | 
			
		||||
    }
 | 
			
		||||
async function getGenericEntityWithId(store, cache, instanceName, id) {
 | 
			
		||||
  if (hasInCache(cache, instanceName, id)) {
 | 
			
		||||
    return getInCache(cache, instanceName, id)
 | 
			
		||||
  }
 | 
			
		||||
  const db = await getDatabase(instanceName)
 | 
			
		||||
  let result = await dbPromise(db, store, 'readonly', (store, callback) => {
 | 
			
		||||
    store.get(id).onsuccess = (e) => callback(e.target.result)
 | 
			
		||||
  })
 | 
			
		||||
  setInCache(cache, instanceName, id, result)
 | 
			
		||||
  return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function clearCache(cache, instanceName) {
 | 
			
		||||
  delete cache.caches[instanceName]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getOrCreateInstanceCache(cache, instanceName) {
 | 
			
		||||
  let cached = cache.caches[instanceName]
 | 
			
		||||
  if (!cached) {
 | 
			
		||||
    cached = cache.caches[instanceName] = new QuickLRU({maxSize: cache.maxSize})
 | 
			
		||||
  }
 | 
			
		||||
  return cached
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setInCache(cache, instanceName, key, value) {
 | 
			
		||||
  let instanceCache = getOrCreateInstanceCache(cache, instanceName)
 | 
			
		||||
  return instanceCache.set(key, value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getInCache(cache, instanceName, key) {
 | 
			
		||||
  let instanceCache = getOrCreateInstanceCache(cache, instanceName)
 | 
			
		||||
  return instanceCache.get(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hasInCache(cache, instanceName, key) {
 | 
			
		||||
  let instanceCache = getOrCreateInstanceCache(cache, instanceName)
 | 
			
		||||
  return instanceCache.has(key)
 | 
			
		||||
async function setGenericEntityWithId(store, cache, instanceName, entity) {
 | 
			
		||||
  setInCache(cache, instanceName, entity.id, entity)
 | 
			
		||||
  const db = await getDatabase(instanceName)
 | 
			
		||||
  return await dbPromise(db, store, 'readwrite', (store) => {
 | 
			
		||||
    store.put(entity)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
| 
						 | 
				
			
			@ -129,23 +104,7 @@ export async function insertStatuses(instanceName, timeline, statuses) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
export async function getStatus(instanceName, statusId) {
 | 
			
		||||
  if (hasInCache(statusesCache, instanceName, statusId)) {
 | 
			
		||||
    if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
      window.cacheStats.statuses.hits++
 | 
			
		||||
    }
 | 
			
		||||
    return getInCache(statusesCache, instanceName, statusId)
 | 
			
		||||
  }
 | 
			
		||||
  const db = await getDatabase(instanceName)
 | 
			
		||||
  let result = await dbPromise(db, STATUSES_STORE, 'readonly', (store, callback) => {
 | 
			
		||||
    store.get(statusId).onsuccess = (e) => {
 | 
			
		||||
      callback(e.target.result && e.target.result)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  setInCache(statusesCache, instanceName, statusId, result)
 | 
			
		||||
  if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
    window.cacheStats.statuses.misses++
 | 
			
		||||
  }
 | 
			
		||||
  return result
 | 
			
		||||
  return await getGenericEntityWithId(STATUSES_STORE, statusesCache, instanceName, statusId)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
| 
						 | 
				
			
			@ -154,9 +113,6 @@ export async function getStatus(instanceName, statusId) {
 | 
			
		|||
 | 
			
		||||
async function getMetaProperty(instanceName, key) {
 | 
			
		||||
  if (hasInCache(metaCache, instanceName, key)) {
 | 
			
		||||
    if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
      window.cacheStats.meta.hits++
 | 
			
		||||
    }
 | 
			
		||||
    return getInCache(metaCache, instanceName, key)
 | 
			
		||||
  }
 | 
			
		||||
  const db = await getDatabase(instanceName)
 | 
			
		||||
| 
						 | 
				
			
			@ -166,9 +122,6 @@ async function getMetaProperty(instanceName, key) {
 | 
			
		|||
    }
 | 
			
		||||
  })
 | 
			
		||||
  setInCache(metaCache, instanceName, key, result)
 | 
			
		||||
  if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
    window.cacheStats.meta.misses++
 | 
			
		||||
  }
 | 
			
		||||
  return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -200,34 +153,23 @@ export async function setInstanceInfo(instanceName, value) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// accounts
 | 
			
		||||
// accounts/relationships
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
export async function getAccount(instanceName, accountId) {
 | 
			
		||||
  if (hasInCache(accountsCache, instanceName, accountId)) {
 | 
			
		||||
    if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
      window.cacheStats.accounts.hits++
 | 
			
		||||
    }
 | 
			
		||||
    return getInCache(accountsCache, instanceName, accountId)
 | 
			
		||||
  }
 | 
			
		||||
  const db = await getDatabase(instanceName)
 | 
			
		||||
  let result = await dbPromise(db, ACCOUNTS_STORE, 'readonly', (store, callback) => {
 | 
			
		||||
    store.get(accountId).onsuccess = (e) => {
 | 
			
		||||
      callback(e.target.result && e.target.result)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
    window.cacheStats.accounts.misses++
 | 
			
		||||
  }
 | 
			
		||||
  return result
 | 
			
		||||
  return await getGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, accountId)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function setAccount(instanceName, account) {
 | 
			
		||||
  setInCache(accountsCache, instanceName, account.id, account)
 | 
			
		||||
  const db = await getDatabase(instanceName)
 | 
			
		||||
  return await dbPromise(db, ACCOUNTS_STORE, 'readwrite', (store) => {
 | 
			
		||||
    store.put(account)
 | 
			
		||||
  })
 | 
			
		||||
  return await setGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, account)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getRelationship(instanceName, accountId) {
 | 
			
		||||
  return await getGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, accountId)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function setRelationship(instanceName, relationship) {
 | 
			
		||||
  return await setGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, relationship)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,14 @@
 | 
			
		|||
const openReqs = {}
 | 
			
		||||
const databaseCache = {}
 | 
			
		||||
 | 
			
		||||
const DB_VERSION = 2
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  META_STORE,
 | 
			
		||||
  TIMELINE_STORE,
 | 
			
		||||
  STATUSES_STORE,
 | 
			
		||||
  ACCOUNTS_STORE
 | 
			
		||||
  ACCOUNTS_STORE,
 | 
			
		||||
  RELATIONSHIPS_STORE
 | 
			
		||||
} from './constants'
 | 
			
		||||
 | 
			
		||||
export function getDatabase(instanceName) {
 | 
			
		||||
| 
						 | 
				
			
			@ -17,19 +20,24 @@ export function getDatabase(instanceName) {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  databaseCache[instanceName] = new Promise((resolve, reject) => {
 | 
			
		||||
    let req = indexedDB.open(instanceName, 1)
 | 
			
		||||
    let req = indexedDB.open(instanceName, DB_VERSION)
 | 
			
		||||
    openReqs[instanceName] = req
 | 
			
		||||
    req.onerror = reject
 | 
			
		||||
    req.onblocked = () => {
 | 
			
		||||
      console.log('idb blocked')
 | 
			
		||||
    }
 | 
			
		||||
    req.onupgradeneeded = () => {
 | 
			
		||||
    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'})
 | 
			
		||||
      let timelineStore = db.createObjectStore(TIMELINE_STORE, {keyPath: 'id'})
 | 
			
		||||
      timelineStore.createIndex('statusId', 'statusId')
 | 
			
		||||
      if (e.oldVersion < 1) {
 | 
			
		||||
        db.createObjectStore(META_STORE, {keyPath: 'key'})
 | 
			
		||||
        db.createObjectStore(STATUSES_STORE, {keyPath: 'id'})
 | 
			
		||||
        db.createObjectStore(ACCOUNTS_STORE, {keyPath: 'id'})
 | 
			
		||||
        let timelineStore = db.createObjectStore(TIMELINE_STORE, {keyPath: 'id'})
 | 
			
		||||
        timelineStore.createIndex('statusId', 'statusId')
 | 
			
		||||
      }
 | 
			
		||||
      if (e.oldVersion < 2) {
 | 
			
		||||
        db.createObjectStore(RELATIONSHIPS_STORE, {keyPath: 'id'})
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    req.onsuccess = () => resolve(req.result)
 | 
			
		||||
  })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import { get } from '../ajax'
 | 
			
		||||
import { get, paramsString } from '../ajax'
 | 
			
		||||
import { basename } from './utils'
 | 
			
		||||
 | 
			
		||||
export function getVerifyCredentials(instanceName, accessToken) {
 | 
			
		||||
| 
						 | 
				
			
			@ -13,4 +13,13 @@ export function getAccount(instanceName, accessToken, accountId) {
 | 
			
		|||
  return get(url, {
 | 
			
		||||
    'Authorization': `Bearer ${accessToken}`
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getRelationship(instanceName, accessToken, accountId) {
 | 
			
		||||
  let url = `${basename(instanceName)}/api/v1/accounts/relationships`
 | 
			
		||||
  url += '?' + paramsString({id: accountId})
 | 
			
		||||
  let res = await get(url, {
 | 
			
		||||
    'Authorization': `Bearer ${accessToken}`
 | 
			
		||||
  })
 | 
			
		||||
  return res[0]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
import { Store } from 'svelte/store.js'
 | 
			
		||||
import { storeObservers } from './storeObservers'
 | 
			
		||||
 | 
			
		||||
const LOCAL_STORAGE_KEYS = new Set([
 | 
			
		||||
  "currentInstance",
 | 
			
		||||
| 
						 | 
				
			
			@ -112,6 +113,12 @@ store.compute(
 | 
			
		|||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
store.compute(
 | 
			
		||||
  'currentVerifyCredentials',
 | 
			
		||||
  ['currentInstance', 'verifyCredentials'],
 | 
			
		||||
  (currentInstance, verifyCredentials) => verifyCredentials && verifyCredentials[currentInstance]
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
store.compute('currentTimelineData', ['currentInstance', 'currentTimeline', 'timelines'],
 | 
			
		||||
  (currentInstance, currentTimeline, timelines) => {
 | 
			
		||||
  return ((timelines && timelines[currentInstance]) || {})[currentTimeline] || {}
 | 
			
		||||
| 
						 | 
				
			
			@ -122,6 +129,8 @@ store.compute('runningUpdate', ['currentTimelineData'], (currentTimelineData) =>
 | 
			
		|||
store.compute('initialized',   ['currentTimelineData'], (currentTimelineData) => currentTimelineData.initialized)
 | 
			
		||||
store.compute('lastStatusId',  ['statusIds'], (statusIds) => statusIds.length && statusIds[statusIds.length - 1])
 | 
			
		||||
 | 
			
		||||
storeObservers(store)
 | 
			
		||||
 | 
			
		||||
if (process.browser && process.env.NODE_ENV !== 'production') {
 | 
			
		||||
  window.store = store // for debugging
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										7
									
								
								routes/_utils/storeObservers.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								routes/_utils/storeObservers.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
import { updateVerifyCredentialsForInstance } from '../settings/instances/_actions/[instanceName]'
 | 
			
		||||
 | 
			
		||||
export function storeObservers(store) {
 | 
			
		||||
  store.observe('currentInstance', (currentInstance) => {
 | 
			
		||||
    updateVerifyCredentialsForInstance(currentInstance)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +12,10 @@
 | 
			
		|||
  {{#if $isUserLoggedIn}}
 | 
			
		||||
  <DynamicPageBanner title="{{profileName}}" />
 | 
			
		||||
    {{#if $currentAccountProfile}}
 | 
			
		||||
      <AccountProfile profile="{{$currentAccountProfile}}" />
 | 
			
		||||
      <AccountProfile profile="{{$currentAccountProfile}}"
 | 
			
		||||
                      relationship="{{$currentAccountRelationship}}"
 | 
			
		||||
                      verifyCredentials="{{$currentVerifyCredentials}}"
 | 
			
		||||
      />
 | 
			
		||||
    {{/if}}
 | 
			
		||||
  <LazyTimeline timeline='account/{{params.accountId}}' />
 | 
			
		||||
  {{else}}
 | 
			
		||||
| 
						 | 
				
			
			@ -32,13 +35,16 @@
 | 
			
		|||
  import { store } from '../_utils/store.js'
 | 
			
		||||
  import HiddenFromSSR from '../_components/HiddenFromSSR'
 | 
			
		||||
  import DynamicPageBanner from '../_components/DynamicPageBanner.html'
 | 
			
		||||
  import { showAccountProfile } from './_actions/[accountId]'
 | 
			
		||||
  import { updateProfileAndRelationship } from './_actions/[accountId]'
 | 
			
		||||
  import AccountProfile from '../_components/AccountProfile.html'
 | 
			
		||||
  import { updateVerifyCredentialsForInstance } from '../settings/instances/_actions/[instanceName]'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate() {
 | 
			
		||||
      let accountId = this.get('params').accountId
 | 
			
		||||
      showAccountProfile(accountId)
 | 
			
		||||
      let instanceName = this.store.get('currentInstance')
 | 
			
		||||
      updateProfileAndRelationship(accountId)
 | 
			
		||||
      updateVerifyCredentialsForInstance(instanceName)
 | 
			
		||||
    },
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    computed: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,24 +1,54 @@
 | 
			
		|||
import { getAccount } from '../../_utils/mastodon/user'
 | 
			
		||||
import { getAccount, getRelationship } from '../../_utils/mastodon/user'
 | 
			
		||||
import { database } from '../../_utils/database/database'
 | 
			
		||||
import { store } from '../../_utils/store'
 | 
			
		||||
 | 
			
		||||
export async function showAccountProfile(accountId) {
 | 
			
		||||
  store.set({currentAccountProfile: null})
 | 
			
		||||
  let instanceName = store.get('currentInstance')
 | 
			
		||||
  let accessToken = store.get('accessToken')
 | 
			
		||||
 | 
			
		||||
async function updateAccount(accountId, instanceName, accessToken) {
 | 
			
		||||
  let localPromise = database.getAccount(instanceName, accountId)
 | 
			
		||||
  let remotePromise = getAccount(instanceName, accessToken, accountId).then(account => {
 | 
			
		||||
    database.setAccount(instanceName, account)
 | 
			
		||||
    return account
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  let localAccount = await localPromise
 | 
			
		||||
  store.set({currentAccountProfile: localAccount})
 | 
			
		||||
  try {
 | 
			
		||||
    let remoteAccount = await remotePromise
 | 
			
		||||
    store.set({currentAccountProfile: remoteAccount})
 | 
			
		||||
    store.set({currentAccountProfile: (await localPromise)})
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error("couldn't fetch profile", e)
 | 
			
		||||
    console.error(e)
 | 
			
		||||
  }
 | 
			
		||||
  try {
 | 
			
		||||
    store.set({currentAccountProfile: (await remotePromise)})
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function updateRelationship(accountId, instanceName, accessToken) {
 | 
			
		||||
  let localPromise = database.getRelationship(instanceName, accountId)
 | 
			
		||||
  let remotePromise = getRelationship(instanceName, accessToken, accountId).then(relationship => {
 | 
			
		||||
    database.setRelationship(instanceName, relationship)
 | 
			
		||||
    return relationship
 | 
			
		||||
  })
 | 
			
		||||
  try {
 | 
			
		||||
    store.set({currentAccountRelationship: (await localPromise)})
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
  }
 | 
			
		||||
  try {
 | 
			
		||||
    store.set({currentAccountRelationship: (await remotePromise)})
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function updateProfileAndRelationship(accountId) {
 | 
			
		||||
  store.set({
 | 
			
		||||
    currentAccountProfile: null,
 | 
			
		||||
    currentAccountRelationship: null
 | 
			
		||||
  })
 | 
			
		||||
  let instanceName = store.get('currentInstance')
 | 
			
		||||
  let accessToken = store.get('accessToken')
 | 
			
		||||
 | 
			
		||||
  await Promise.all([
 | 
			
		||||
    updateAccount(accountId, instanceName, accessToken),
 | 
			
		||||
    updateRelationship(accountId, instanceName, accessToken)
 | 
			
		||||
  ])
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -63,5 +63,5 @@
 | 
			
		|||
  --deemphasized-text-color: #666;
 | 
			
		||||
  --focus-outline: $focus-outline;
 | 
			
		||||
 | 
			
		||||
  --status-direct-background: darken($body-bg-color, 5%)
 | 
			
		||||
  --status-direct-background: darken($body-bg-color, 5%);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue