first stab at account pages
This commit is contained in:
		
							parent
							
								
									5d9eba58be
								
							
						
					
					
						commit
						ab291a2c7e
					
				
					 10 changed files with 199 additions and 57 deletions
				
			
		
							
								
								
									
										44
									
								
								routes/_components/DynamicPageBanner.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								routes/_components/DynamicPageBanner.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
<div class="dynamic-page-banner">
 | 
			
		||||
  <h1 class="dynamic-page-title">{{title}}</h1>
 | 
			
		||||
  <button type="button"
 | 
			
		||||
          class="dynamic-page-go-back"
 | 
			
		||||
          on:click="onGoBack(event)">Back</button>
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
  .dynamic-page-banner {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    margin: 0 20px 20px;
 | 
			
		||||
  }
 | 
			
		||||
  h1.dynamic-page-title {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
  }
 | 
			
		||||
  button.dynamic-page-go-back {
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
    color: var(--anchor-text);
 | 
			
		||||
    border: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    background: none;
 | 
			
		||||
  }
 | 
			
		||||
  button.dynamic-page-go-back:hover {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
  }
 | 
			
		||||
  button.dynamic-page-go-back::before {
 | 
			
		||||
    content: '<';
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  export default {
 | 
			
		||||
    methods: {
 | 
			
		||||
      onGoBack(e) {
 | 
			
		||||
        e.preventDefault()
 | 
			
		||||
        window.history.back();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -50,6 +50,9 @@
 | 
			
		|||
    font-size: 16px;
 | 
			
		||||
    color: var(--nav-text-color);
 | 
			
		||||
    padding-left: 10px;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media (max-width: 767px) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,8 +32,8 @@
 | 
			
		|||
    </div>
 | 
			
		||||
  {{/if}}
 | 
			
		||||
  {{#if !status.spoiler_text || spoilerShown}}
 | 
			
		||||
    <div class="status-content">
 | 
			
		||||
      {{{hydratedContent}}}
 | 
			
		||||
    <div class="status-content" ref:contentNode>
 | 
			
		||||
      {{{emojifiedContent}}}
 | 
			
		||||
    </div>
 | 
			
		||||
  {{/if}}
 | 
			
		||||
  {{#if originalMediaAttachments && originalMediaAttachments.length}}
 | 
			
		||||
| 
						 | 
				
			
			@ -295,6 +295,9 @@
 | 
			
		|||
  const relativeFormat = new IntlRelativeFormat('en-US');
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate() {
 | 
			
		||||
      this.hashtagifyContent()
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
      Avatar,
 | 
			
		||||
      Media,
 | 
			
		||||
| 
						 | 
				
			
			@ -311,15 +314,9 @@
 | 
			
		|||
      originalStatus: (status) => status.reblog ? status.reblog : status,
 | 
			
		||||
      originalAccount: (originalStatus) => originalStatus.account,
 | 
			
		||||
      originalMediaAttachments: (originalStatus) => originalStatus.media_attachments,
 | 
			
		||||
      hydratedContent: (originalStatus) => {
 | 
			
		||||
      emojifiedContent: (originalStatus) => {
 | 
			
		||||
        let status = originalStatus
 | 
			
		||||
        let content = status.content
 | 
			
		||||
        if (status.tags && status.tags.length) {
 | 
			
		||||
          for (let tag of status.tags) {
 | 
			
		||||
            let {name, url} = tag
 | 
			
		||||
            content = replaceAll(content, url, `/tags/${name}`)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (status.emojis && status.emojis.length) {
 | 
			
		||||
          for (let emoji of status.emojis) {
 | 
			
		||||
            let { shortcode, url } = emoji
 | 
			
		||||
| 
						 | 
				
			
			@ -337,11 +334,34 @@
 | 
			
		|||
    methods: {
 | 
			
		||||
      onClickSpoilerButton() {
 | 
			
		||||
        this.set({spoilerShown: !this.get('spoilerShown')})
 | 
			
		||||
        this.hashtagifyContent()
 | 
			
		||||
        this.fire('recalculateHeight')
 | 
			
		||||
      },
 | 
			
		||||
      onClickSensitiveMediaButton() {
 | 
			
		||||
        this.set({sensitiveShown: !this.get('sensitiveShown')})
 | 
			
		||||
        this.fire('recalculateHeight')
 | 
			
		||||
      },
 | 
			
		||||
      hashtagifyContent() {
 | 
			
		||||
        if (!this.refs.contentNode) {
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        let status = this.get('originalStatus')
 | 
			
		||||
        mark('hydrateHashtags')
 | 
			
		||||
        if (status.tags && status.tags.length) {
 | 
			
		||||
          let anchorTags = Array.from(this.refs.contentNode.querySelectorAll(
 | 
			
		||||
            'a[class~=hashtag][href^=http]'))
 | 
			
		||||
          for (let tag of status.tags) {
 | 
			
		||||
            let { name } = tag
 | 
			
		||||
            for (let anchorTag of anchorTags) {
 | 
			
		||||
              if (anchorTag.getAttribute('href').endsWith(`/tags/${name}`)) {
 | 
			
		||||
                anchorTag.setAttribute('href', `/tags/${name}`)
 | 
			
		||||
                anchorTag.removeAttribute('target')
 | 
			
		||||
                anchorTag.removeAttribute('rel')
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        stop('hydrateHashtags')
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,6 +38,22 @@
 | 
			
		|||
          this.fire('initialized')
 | 
			
		||||
        }))
 | 
			
		||||
      })
 | 
			
		||||
      this.observe('statuses', statuses => {
 | 
			
		||||
        let cachedAccountNames = this.store.get('cachedAccountNames') || {}
 | 
			
		||||
        for (let status of statuses) {
 | 
			
		||||
          cachedAccountNames[status.account.id] = {
 | 
			
		||||
            username: status.account.username,
 | 
			
		||||
            acct: status.account.acct
 | 
			
		||||
          }
 | 
			
		||||
          if (status.reblog) {
 | 
			
		||||
            cachedAccountNames[status.reblog.account.id] = {
 | 
			
		||||
              username: status.reblog.account.username,
 | 
			
		||||
              acct: status.reblog.account.acct
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        this.store.set({'cachedAccountNames': cachedAccountNames})
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      StatusListItem: StatusListItem,
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +74,9 @@
 | 
			
		|||
        } else if (timeline.startsWith('tag/')) {
 | 
			
		||||
          let tag = timeline.split('/').slice(-1)[0]
 | 
			
		||||
          return `#${tag} timeline for ${$currentInstance}`
 | 
			
		||||
        } else if (timeline.startsWith('account/')) {
 | 
			
		||||
          let account = timeline.split('/').slice(-1)[0]
 | 
			
		||||
          return `Account #${account} on ${$currentInstance}`
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,8 @@
 | 
			
		|||
/*
 | 
			
		||||
import worker from 'workerize-loader!./databaseCore'
 | 
			
		||||
const database = process.browser && worker()
 | 
			
		||||
export const database = process.browser && worker()
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  database
 | 
			
		||||
}
 | 
			
		||||
import * as dbCore from './databaseCore'
 | 
			
		||||
 | 
			
		||||
export { dbCore as database }
 | 
			
		||||
| 
						 | 
				
			
			@ -1,24 +1,29 @@
 | 
			
		|||
import { get, paramsString } from '../ajax'
 | 
			
		||||
import { basename } from './utils'
 | 
			
		||||
 | 
			
		||||
function getTimelineUrlName(timeline) {
 | 
			
		||||
function getTimelineUrlPath(timeline) {
 | 
			
		||||
  switch (timeline) {
 | 
			
		||||
    case 'local':
 | 
			
		||||
    case 'federated':
 | 
			
		||||
      return 'public'
 | 
			
		||||
      return 'timelines/public'
 | 
			
		||||
    case 'home':
 | 
			
		||||
      return 'home'
 | 
			
		||||
    default:
 | 
			
		||||
      return 'tag'
 | 
			
		||||
      return 'timelines/home'
 | 
			
		||||
  }
 | 
			
		||||
  if (timeline.startsWith('tag/')) {
 | 
			
		||||
    return 'timelines/tag'
 | 
			
		||||
  } else if (timeline.startsWith('account/')) {
 | 
			
		||||
    return 'accounts'
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getTimeline(instanceName, accessToken, timeline, maxId, since) {
 | 
			
		||||
  let timelineUrlName = getTimelineUrlName(timeline)
 | 
			
		||||
  let url = `${basename(instanceName)}/api/v1/timelines/${timelineUrlName}`
 | 
			
		||||
  let timelineUrlName = getTimelineUrlPath(timeline)
 | 
			
		||||
  let url = `${basename(instanceName)}/api/v1/${timelineUrlName}`
 | 
			
		||||
 | 
			
		||||
  if (timeline.startsWith('tag/')) {
 | 
			
		||||
    url += '/' + timeline.split('/').slice(-1)[0]
 | 
			
		||||
  } else if (timeline.startsWith('account/')) {
 | 
			
		||||
    url += '/' + timeline.split('/').slice(-1)[0] +'/statuses'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let params = {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,4 +6,11 @@ export function getVerifyCredentials(instanceName, accessToken) {
 | 
			
		|||
  return get(url, {
 | 
			
		||||
    'Authorization': `Bearer ${accessToken}`
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getAccount(instanceName, accessToken, accountId) {
 | 
			
		||||
  let url = `${basename(instanceName)}/api/v1/accounts/${accountId}`
 | 
			
		||||
  return get(url, {
 | 
			
		||||
    'Authorization': `Bearer ${accessToken}`
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,9 @@
 | 
			
		|||
import { Store } from 'svelte/store.js'
 | 
			
		||||
 | 
			
		||||
const DONT_STORE_THESE_KEYS = [
 | 
			
		||||
  'cachedAccountNames'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const LS = process.browser && localStorage
 | 
			
		||||
class LocalStorageStore extends Store {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +22,8 @@ class LocalStorageStore extends Store {
 | 
			
		|||
      this.set(newState)
 | 
			
		||||
      this.onchange((state, changed) => {
 | 
			
		||||
        Object.keys(changed).forEach(change => {
 | 
			
		||||
          if (!this._computed[change]) { // TODO: better way to ignore computed values?
 | 
			
		||||
          if (!DONT_STORE_THESE_KEYS.includes(change) &&
 | 
			
		||||
              !this._computed[change]) { // TODO: better way to ignore computed values?
 | 
			
		||||
            this.lastChanged[change] = true
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +79,12 @@ store.compute(
 | 
			
		|||
    }, loggedInInstances[currentInstance])
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
store.compute(
 | 
			
		||||
  'accessToken',
 | 
			
		||||
  ['currentInstanceData'],
 | 
			
		||||
  (currentInstanceData) => currentInstanceData.access_token
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
store.compute(
 | 
			
		||||
  'currentTheme',
 | 
			
		||||
  ['currentInstance', 'instanceThemes'],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										63
									
								
								routes/accounts/[accountId].html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								routes/accounts/[accountId].html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
<:Head>
 | 
			
		||||
  <title>{{'Pinafore – ' + (cachedProfileName || profileName || '')}}</title>
 | 
			
		||||
</:Head>
 | 
			
		||||
 | 
			
		||||
<Layout page='tags'
 | 
			
		||||
        dynamicPage="{{cachedProfileName || profileName || ''}}"
 | 
			
		||||
        dynamicHref="/accounts/{{params.accountId}}"
 | 
			
		||||
        dynamicLabel="{{cachedShortProfileName || shortProfileName || ''}}"
 | 
			
		||||
        dynamicIcon="#fa-user" >
 | 
			
		||||
  {{#if $isUserLoggedIn}}
 | 
			
		||||
  <DynamicPageBanner title="{{cachedProfileName || profileName || ''}}" />
 | 
			
		||||
  <LazyTimeline timeline='account/{{params.accountId}}' />
 | 
			
		||||
  {{else}}
 | 
			
		||||
  <HiddenFromSSR>
 | 
			
		||||
    <FreeTextLayout>
 | 
			
		||||
      <h1>Profile</h1>
 | 
			
		||||
 | 
			
		||||
      <p>A user timeline will appear here when logged in.</p>
 | 
			
		||||
    </FreeTextLayout>
 | 
			
		||||
  </HiddenFromSSR>
 | 
			
		||||
  {{/if}}
 | 
			
		||||
</Layout>
 | 
			
		||||
<script>
 | 
			
		||||
  import Layout from '../_components/Layout.html'
 | 
			
		||||
  import LazyTimeline from '../_components/LazyTimeline.html'
 | 
			
		||||
  import FreeTextLayout from '../_components/FreeTextLayout.html'
 | 
			
		||||
  import { store } from '../_utils/store.js'
 | 
			
		||||
  import HiddenFromSSR from '../_components/HiddenFromSSR'
 | 
			
		||||
  import DynamicPageBanner from '../_components/DynamicPageBanner.html'
 | 
			
		||||
  import { getAccount } from '../_utils/mastodon/user'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    async oncreate() {
 | 
			
		||||
      let currentInstance = this.store.get('currentInstance')
 | 
			
		||||
      let accessToken = this.store.get('accessToken')
 | 
			
		||||
      let accountId = this.get('params').accountId
 | 
			
		||||
      let account = await getAccount(currentInstance, accessToken, accountId)
 | 
			
		||||
      this.set({account: account})
 | 
			
		||||
    },
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    computed: {
 | 
			
		||||
      profileName: (account) => {
 | 
			
		||||
        return account && ('@' + account.acct)
 | 
			
		||||
      },
 | 
			
		||||
      shortProfileName: (account) => {
 | 
			
		||||
        return account && ('@' + account.username)
 | 
			
		||||
      },
 | 
			
		||||
      cachedProfileName: ($cachedAccountNames, params) => {
 | 
			
		||||
        return $cachedAccountNames && $cachedAccountNames[params.accountId] && ('@' + $cachedAccountNames[params.accountId].acct)
 | 
			
		||||
      },
 | 
			
		||||
      cachedShortProfileName: ($cachedAccountNames, params) => {
 | 
			
		||||
        return $cachedAccountNames && $cachedAccountNames[params.accountId] && ('@' + $cachedAccountNames[params.accountId].username)
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
      Layout,
 | 
			
		||||
      LazyTimeline,
 | 
			
		||||
      FreeTextLayout,
 | 
			
		||||
      HiddenFromSSR,
 | 
			
		||||
      DynamicPageBanner
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -8,10 +8,7 @@
 | 
			
		|||
        dynamicLabel="{{'#' + params.tagName}}"
 | 
			
		||||
        dynamicIcon="#fa-hashtag" >
 | 
			
		||||
  {{#if $isUserLoggedIn}}
 | 
			
		||||
  <div class="dynamic-page-banner">
 | 
			
		||||
    <h1 class="dynamic-page-title">{{'#' + params.tagName}}</h1>
 | 
			
		||||
    <button type="button" class="dynamic-page-go-back" on:click="onGoBack(event)">Back</button>
 | 
			
		||||
  </div>
 | 
			
		||||
  <DynamicPageBanner title="{{'#' + params.tagName}}"/>
 | 
			
		||||
  <LazyTimeline timeline='tag/{{params.tagName}}' />
 | 
			
		||||
  {{else}}
 | 
			
		||||
  <HiddenFromSSR>
 | 
			
		||||
| 
						 | 
				
			
			@ -23,37 +20,13 @@
 | 
			
		|||
  </HiddenFromSSR>
 | 
			
		||||
  {{/if}}
 | 
			
		||||
</Layout>
 | 
			
		||||
<style>
 | 
			
		||||
  .dynamic-page-banner {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    margin: 0 20px 20px;
 | 
			
		||||
  }
 | 
			
		||||
  h1.dynamic-page-title {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
  }
 | 
			
		||||
  button.dynamic-page-go-back {
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
    color: var(--anchor-text);
 | 
			
		||||
    border: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    background: none;
 | 
			
		||||
  }
 | 
			
		||||
  button.dynamic-page-go-back:hover {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
  }
 | 
			
		||||
  button.dynamic-page-go-back::before {
 | 
			
		||||
    content: '<';
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import Layout from '../_components/Layout.html'
 | 
			
		||||
  import LazyTimeline from '../_components/LazyTimeline.html'
 | 
			
		||||
  import FreeTextLayout from '../_components/FreeTextLayout.html'
 | 
			
		||||
  import { store } from '../_utils/store.js'
 | 
			
		||||
  import HiddenFromSSR from '../_components/HiddenFromSSR'
 | 
			
		||||
  import DynamicPageBanner from '../_components/DynamicPageBanner.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    store: () => store,
 | 
			
		||||
| 
						 | 
				
			
			@ -61,13 +34,8 @@
 | 
			
		|||
      Layout,
 | 
			
		||||
      LazyTimeline,
 | 
			
		||||
      FreeTextLayout,
 | 
			
		||||
      HiddenFromSSR
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      onGoBack(e) {
 | 
			
		||||
        e.preventDefault()
 | 
			
		||||
        window.history.back();
 | 
			
		||||
      }
 | 
			
		||||
      HiddenFromSSR,
 | 
			
		||||
      DynamicPageBanner
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue