implement hashtags

This commit is contained in:
Nolan Lawson 2018-01-21 20:02:32 -08:00
parent 0460a7de2d
commit a11f31bb3f
8 changed files with 126 additions and 7 deletions

View file

@ -1,5 +1,5 @@
<:Window bind:innerHeight /> <:Window bind:innerHeight />
<Nav page={{page}} /> <Nav :page :dynamicPage :dynamicHref :dynamicIcon :dynamicLabel/>
<div class="container" on:scroll="onScroll(event)" on:fullscreen="onFullscreenChange()" ref:node> <div class="container" on:scroll="onScroll(event)" on:fullscreen="onFullscreenChange()" ref:node>
<main> <main>

View file

@ -15,6 +15,12 @@
<li> <li>
<NavItem :page name="settings" href="/settings" svg="#fa-gears" label="Settings" /> <NavItem :page name="settings" href="/settings" svg="#fa-gears" label="Settings" />
</li> </li>
{{#if dynamicPage}}
<li>
<NavItem :page name="{{dynamicPage}}" href="{{dynamicHref}}" svg="{{dynamicIcon}}"
label="{{dynamicLabel}}" forceCurrent="true" />
</li>
{{/if}}
</ul> </ul>
</nav> </nav>

View file

@ -1,6 +1,6 @@
<a class='main-nav-link {{page === name ? "selected" : ""}}' <a class='main-nav-link {{forceCurrent || page === name ? "selected" : ""}}'
aria-label='{{page === name ? `${label} (current page)` : label}}' aria-label='{{forceCurrent || page === name ? `${label} (current page)` : label}}'
aria-current="{{page === name}}" aria-current="{{forceCurrent || page === name}}"
href='{{href}}'> href='{{href}}'>
<svg> <svg>
<use xlink:href="{{svg}}" /> <use xlink:href="{{svg}}" />

View file

@ -32,7 +32,7 @@
</div> </div>
{{/if}} {{/if}}
{{#if !status.spoiler_text || spoilerShown}} {{#if !status.spoiler_text || spoilerShown}}
<div class="status-content"> <div class="status-content" ref:contentNode>
{{{status.content}}} {{{status.content}}}
</div> </div>
{{/if}} {{/if}}
@ -286,6 +286,9 @@
const relativeFormat = new IntlRelativeFormat('en-US'); const relativeFormat = new IntlRelativeFormat('en-US');
export default { export default {
oncreate() {
this.hydrateHtml()
},
components: { components: {
Avatar, Avatar,
Media, Media,
@ -306,11 +309,26 @@
methods: { methods: {
onClickSpoilerButton() { onClickSpoilerButton() {
this.set({spoilerShown: !this.get('spoilerShown')}) this.set({spoilerShown: !this.get('spoilerShown')})
this.hydrateHtml()
this.fire('recalculateHeight') this.fire('recalculateHeight')
}, },
onClickSensitiveMediaButton() { onClickSensitiveMediaButton() {
this.set({sensitiveShown: !this.get('sensitiveShown')}) this.set({sensitiveShown: !this.get('sensitiveShown')})
this.fire('recalculateHeight') this.fire('recalculateHeight')
},
hydrateHtml() {
let status = this.get('originalStatus')
if (status.tags && status.tags.length && this.refs.contentNode) {
let anchorTags = this.refs.contentNode.querySelectorAll('a[rel=tag]')
for (let tag of status.tags) {
let {name, url} = tag
for (let anchorTag of anchorTags) {
if (anchorTag.getAttribute('href') === url) {
anchorTag.setAttribute('href', `/tags/${name}`)
}
}
}
}
} }
} }
} }

View file

@ -52,7 +52,14 @@
key: status.id key: status.id
})), })),
lastStatusId: (statuses) => statuses.length && statuses[statuses.length - 1].id, lastStatusId: (statuses) => statuses.length && statuses[statuses.length - 1].id,
label: (timeline, $currentInstance) => `${timelines[timeline].label} timeline for ${$currentInstance}` label: (timeline, $currentInstance) => {
if (timelines[timeline]) {
`${timelines[timeline].label} timeline for ${$currentInstance}`
} else if (timeline.startsWith('tag/')) {
let tag = timeline.split('/').slice(-1)[0]
return `#${tag} timeline for ${$currentInstance}`
}
}
}, },
store: () => store, store: () => store,
components: { components: {

View file

@ -1,10 +1,26 @@
import { get, paramsString } from '../ajax' import { get, paramsString } from '../ajax'
import { basename } from './utils' import { basename } from './utils'
function getTimelineUrlName(timeline) {
switch (timeline) {
case 'local':
case 'federated':
return 'public'
case 'home':
return 'home'
default:
return 'tag'
}
}
export function getTimeline(instanceName, accessToken, timeline, maxId, since) { export function getTimeline(instanceName, accessToken, timeline, maxId, since) {
let timelineUrlName = timeline === 'local' || timeline === 'federated' ? 'public' : timeline let timelineUrlName = getTimelineUrlName(timeline)
let url = `${basename(instanceName)}/api/v1/timelines/${timelineUrlName}` let url = `${basename(instanceName)}/api/v1/timelines/${timelineUrlName}`
if (timeline.startsWith('tag/')) {
url += '/' + timeline.split('/').slice(-1)[0]
}
let params = {} let params = {}
if (since) { if (since) {
params.since = since params.since = since

View file

@ -0,0 +1,66 @@
<:Head>
<title>Pinafore #{{params.tagName}}</title>
</:Head>
<Layout page='tags'
dynamicPage="{{params.tagName}}"
dynamicHref="/tags/{{params.tagName}}"
dynamicLabel="{{'#' + params.tagName}}"
dynamicIcon="#fa-hashtag" >
{{#if $isUserLoggedIn}}
<div class="hashtag-banner">
<h1 class="hashtag-title">{{'#' + params.tagName}}</h1>
<a href="#" class="hashtag-go-back" on:click="onGoBack(event)">Back</a>
</div>
<LazyTimeline timeline='tag/{{params.tagName}}' />
{{else}}
<HiddenFromSSR>
<FreeTextLayout>
<h1>#{{params.tagName}}</h1>
<p>A hashtag timeline will appear here when logged in.</p>
</FreeTextLayout>
</HiddenFromSSR>
{{/if}}
</Layout>
<style>
.hashtag-banner {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 20px 20px;
}
h1.hashtag-title {
margin: 0;
}
a.hashtag-go-back {
font-size: 1.3em;
}
a.hashtag-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'
export default {
store: () => store,
components: {
Layout,
LazyTimeline,
FreeTextLayout,
HiddenFromSSR
},
methods: {
onGoBack(e) {
e.preventDefault()
window.history.back();
}
}
}
</script>

View file

@ -134,6 +134,12 @@ body.offline,body.theme-hotpants.offline,body.theme-majesty.offline,body.theme-o
<path d="M555 1335l78-141q-87-63-136-159t-49-203q0-121 61-225-229 117-381 353 167 258 427 375zm389-759q0-20-14-34t-34-14q-125 0-214.5 89.5T592 832q0 20 14 34t34 14 34-14 14-34q0-86 61-147t147-61q20 0 34-14t14-34zm363-191q0 7-1 9-106 189-316 567t-315 566l-49 89q-10 16-28 16-12 0-134-70-16-10-16-28 0-12 44-87-143-65-263.5-173T20 1029Q0 998 0 960t20-69q153-235 380-371t496-136q89 0 180 17l54-97q10-16 28-16 5 0 18 6t31 15.5 33 18.5 31.5 18.5T1291 358q16 10 16 27zm37 447q0 139-79 253.5T1056 1250l280-502q8 45 8 84zm448 128q0 35-20 69-39 64-109 145-150 172-347.5 267T896 1536l74-132q212-18 392.5-137T1664 960q-115-179-282-294l63-112q95 64 182.5 153T1772 891q20 34 20 69z"/> <path d="M555 1335l78-141q-87-63-136-159t-49-203q0-121 61-225-229 117-381 353 167 258 427 375zm389-759q0-20-14-34t-34-14q-125 0-214.5 89.5T592 832q0 20 14 34t34 14 34-14 14-34q0-86 61-147t147-61q20 0 34-14t14-34zm363-191q0 7-1 9-106 189-316 567t-315 566l-49 89q-10 16-28 16-12 0-134-70-16-10-16-28 0-12 44-87-143-65-263.5-173T20 1029Q0 998 0 960t20-69q153-235 380-371t496-136q89 0 180 17l54-97q10-16 28-16 5 0 18 6t31 15.5 33 18.5 31.5 18.5T1291 358q16 10 16 27zm37 447q0 139-79 253.5T1056 1250l280-502q8 45 8 84zm448 128q0 35-20 69-39 64-109 145-150 172-347.5 267T896 1536l74-132q212-18 392.5-137T1664 960q-115-179-282-294l63-112q95 64 182.5 153T1772 891q20 34 20 69z"/>
</symbol> </symbol>
<symbol id="fa-hashtag" viewBox="0 0 1792 1792">
<title>Hashtag</title>
<path d="M991 1024l64-256H801l-64 256h254zm768-504l-56 224q-7 24-31 24h-327l-64 256h311q15 0 25 12 10 14 6 28l-56 224q-5 24-31 24h-327l-81 328q-7 24-31 24H873q-16 0-26-12-9-12-6-28l78-312H665l-81 328q-7 24-31 24H328q-15 0-25-12-9-12-6-28l78-312H64q-15 0-25-12-9-12-6-28l56-224q7-24 31-24h327l64-256H200q-15 0-25-12-10-14-6-28l56-224q5-24 31-24h327l81-328q7-24 32-24h224q15 0 25 12 9 12 6 28l-78 312h254l81-328q7-24 32-24h224q15 0 25 12 9 12 6 28l-78 312h311q15 0 25 12 9 12 6 28z"/>
</symbol>
</svg> </svg>