add favorites, refactor local/federated

This commit is contained in:
Nolan Lawson 2018-02-07 22:49:50 -08:00
parent fa401d727f
commit 016ecfca8d
14 changed files with 280 additions and 15 deletions

View File

@ -19,4 +19,7 @@ module.exports = [
{id:'fa-user-plus', src:'node_modules/font-awesome-svg-png/white/svg/user-plus.svg', title: 'Follow'}, {id:'fa-user-plus', src:'node_modules/font-awesome-svg-png/white/svg/user-plus.svg', title: 'Follow'},
{id:'fa-external-link', src:'node_modules/font-awesome-svg-png/white/svg/external-link.svg', title: 'External Link'}, {id:'fa-external-link', src:'node_modules/font-awesome-svg-png/white/svg/external-link.svg', title: 'External Link'},
{id:'fa-search', src:'node_modules/font-awesome-svg-png/white/svg/search.svg', title: 'Search'}, {id:'fa-search', src:'node_modules/font-awesome-svg-png/white/svg/search.svg', title: 'Search'},
{id:'fa-comments', src:'node_modules/font-awesome-svg-png/white/svg/comments.svg', title: 'Conversations'},
{id:'fa-paperclip', src:'node_modules/font-awesome-svg-png/white/svg/paperclip.svg', title: 'Paperclip'},
{id:'fa-thumbtack', src:'node_modules/font-awesome-svg-png/white/svg/thumb-tack.svg', title: 'Thumbtack'},
] ]

View File

@ -1,4 +1,9 @@
<div class="dynamic-page-banner"> <div class="dynamic-page-banner {{icon ? 'dynamic-page-with-icon' : ''}}">
{{#if icon}}
<svg>
<use xlink:href="{{icon}}" />
</svg>
{{/if}}
<h1 class="dynamic-page-title">{{title}}</h1> <h1 class="dynamic-page-title">{{title}}</h1>
<button type="button" <button type="button"
class="dynamic-page-go-back" class="dynamic-page-go-back"
@ -6,10 +11,19 @@
</div> </div>
<style> <style>
.dynamic-page-banner { .dynamic-page-banner {
display: flex; display: grid;
align-items: center; align-items: center;
justify-content: space-between;
margin: 20px 20px 20px; margin: 20px 20px 20px;
grid-template-columns: 1fr min-content;
grid-column-gap: 10px;
}
.dynamic-page-banner.dynamic-page-with-icon {
grid-template-columns: min-content 1fr min-content;
}
.dynamic-page-banner svg {
width: 24px;
height: 24px;
fill: var(--body-text-color);
} }
h1.dynamic-page-title { h1.dynamic-page-title {
margin: 0; margin: 0;
@ -23,6 +37,7 @@
border: 0; border: 0;
padding: 0; padding: 0;
background: none; background: none;
justify-self: flex-end;
} }
button.dynamic-page-go-back:hover { button.dynamic-page-go-back:hover {
text-decoration: underline; text-decoration: underline;

View File

@ -2,7 +2,9 @@
<button type="button" <button type="button"
aria-label="{{label}}" aria-label="{{label}}"
aria-pressed="{{!!pressed}}" aria-pressed="{{!!pressed}}"
class="icon-button {{pressed ? 'pressed' : ''}} {{big ? 'big-icon' : ''}}"> class="icon-button {{pressed ? 'pressed' : ''}} {{big ? 'big-icon' : ''}}"
on:click
>
<svg> <svg>
<use xlink:href="{{href}}" /> <use xlink:href="{{href}}" />
</svg> </svg>
@ -10,7 +12,9 @@
{{else}} {{else}}
<button type="button" <button type="button"
aria-label="{{label}}" aria-label="{{label}}"
class="icon-button {{big ? 'big-icon' : ''}}"> class="icon-button {{big ? 'big-icon' : ''}}"
on:click
>
<svg> <svg>
<use xlink:href="{{href}}" /> <use xlink:href="{{href}}" />
</svg> </svg>

View File

@ -6,14 +6,24 @@
<li> <li>
<NavItem :page name="notifications" href="/notifications" svg="#fa-bell" label="Notifications" /> <NavItem :page name="notifications" href="/notifications" svg="#fa-bell" label="Notifications" />
</li> </li>
<li>
<NavItem :page name="search" href="/search" svg="#fa-search" label="Search" />
</li>
{{#if $pinnedPage === '/local'}}
<li> <li>
<NavItem :page name="local" href="/local" svg="#fa-users" label="Local" /> <NavItem :page name="local" href="/local" svg="#fa-users" label="Local" />
</li> </li>
{{elseif $pinnedPage === '/federated'}}
<li> <li>
<NavItem :page name="federated" href="/federated" svg="#fa-globe" label="Federated" /> <NavItem :page name="federated" href="/federated" svg="#fa-globe" label="Federated" />
</li> </li>
{{elseif $pinnedPage === '/favorites'}}
<li> <li>
<NavItem :page name="search" href="/search" svg="#fa-search" label="Search" /> <NavItem :page name="favorites" href="/favorites" svg="#fa-star" label="Favorites" />
</li>
{{/if}}
<li>
<NavItem :page name="community" href="/community" svg="#fa-comments" label="Community" />
</li> </li>
<li> <li>
<NavItem :page name="settings" href="/settings" svg="#fa-gear" label="Settings" /> <NavItem :page name="settings" href="/settings" svg="#fa-gear" label="Settings" />
@ -49,7 +59,10 @@
</style> </style>
<script> <script>
import NavItem from './NavItem' import NavItem from './NavItem'
import { store } from '../_store/store'
export default { export default {
store: () => store,
components: { components: {
NavItem NavItem
} }

View File

@ -46,4 +46,9 @@ export function instanceComputations(store) {
['currentInstance', 'verifyCredentials'], ['currentInstance', 'verifyCredentials'],
(currentInstance, verifyCredentials) => verifyCredentials && verifyCredentials[currentInstance] (currentInstance, verifyCredentials) => verifyCredentials && verifyCredentials[currentInstance]
) )
store.compute(
'pinnedPage',
['pinnedPages', 'currentInstance'],
(pinnedPages, currentInstance) => (currentInstance && pinnedPages[currentInstance]) || '/local')
} }

View File

@ -12,7 +12,8 @@ const KEYS_TO_STORE_IN_LOCAL_STORAGE = new Set([
"loggedInInstances", "loggedInInstances",
"loggedInInstancesInOrder", "loggedInInstancesInOrder",
"autoplayGifs", "autoplayGifs",
"markMediaAsSensitive" "markMediaAsSensitive",
"pinnedPages"
]) ])
class PinaforeStore extends LocalStorageStore { class PinaforeStore extends LocalStorageStore {
@ -31,7 +32,8 @@ const store = new PinaforeStore({
spoilersShown: {}, spoilersShown: {},
sensitivesShown: {}, sensitivesShown: {},
autoplayGifs: false, autoplayGifs: false,
markMediaAsSensitive: false markMediaAsSensitive: false,
pinnedPages: {}
}) })
mixins(PinaforeStore) mixins(PinaforeStore)

View File

@ -10,6 +10,8 @@ function getTimelineUrlPath(timeline) {
return 'timelines/home' return 'timelines/home'
case 'notifications': case 'notifications':
return 'notifications' return 'notifications'
case 'favorites':
return 'favourites'
} }
if (timeline.startsWith('tag/')) { if (timeline.startsWith('tag/')) {
return 'timelines/tag' return 'timelines/tag'

View File

@ -0,0 +1,28 @@
<div class="page-list-wrapper">
{{#if label}}
<ul class="page-list" aria-label="{{label}}">
<slot></slot>
</ul>
{{else}}
<ul class="page-list">
<slot></slot>
</ul>
{{/if}}
</div>
<style>
.page-list-wrapper {
margin: 20px 20px;
}
ul.page-list {
list-style: none;
width: 100%;
border: 1px solid var(--settings-list-item-border);
margin: 0 auto;
box-sizing: border-box;
}
@media (max-width: 767px) {
.page-list-wrapper {
margin: 20px 0;
}
}
</style>

View File

@ -0,0 +1,85 @@
<li class="page-list-item">
<a :href>
<svg>
<use xlink:href="{{icon}}" />
</svg>
<span aria-label="{{label}} {{$pinnedPage === href ? 'Pinned page' : 'Unpinned page'}}">
{{label}}
</span>
<IconButton pressable="true"
pressed="{{$pinnedPage === href}}"
label="Pin page"
href="#fa-thumbtack"
on:click="onPinClick(event)"
/>
</a>
</li>
<style>
.page-list-item {
border: 1px solid var(--settings-list-item-border);
font-size: 1.3em;
display: flex;
flex-direction: column;
}
.page-list-item a {
padding: 20px 40px;
background: var(--settings-list-item-bg);
display: grid;
grid-template-columns: min-content 1fr min-content;
align-items: center;
}
.page-list-item a, .page-list-item a:visited {
color: var(--body-text-color);
}
.page-list-item a:hover {
text-decoration: none;
background: var(--settings-list-item-bg-hover);
color: var(--body-text-color);
}
.page-list-item a:active {
background: var(--settings-list-item-bg-active);
}
.page-list-item svg {
width: 24px;
height: 24px;
display: inline-block;
margin-right: 20px;
fill: var(--body-text-color);
}
.page-list-item span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 767px) {
.page-list-item a {
padding: 20px 10px;
}
.page-list-item svg {
margin-right: 10px;
}
}
</style>
<script>
import { store } from '../../_store/store'
import IconButton from '../../_components/IconButton'
export default {
store: () => store,
components: {
IconButton
},
methods: {
onPinClick(e) {
e.preventDefault()
let currentInstance = this.store.get('currentInstance')
let pinnedPages = this.store.get('pinnedPages')
let href = this.get('href')
pinnedPages[currentInstance] = href
this.store.set({pinnedPages: pinnedPages})
}
}
}
</script>

View File

@ -0,0 +1,61 @@
<:Head>
<title>Pinafore Community</title>
</:Head>
<Layout page='community'>
{{#if $isUserLoggedIn}}
<div class="community-page">
<PageList label="Community list">
<PageListItem href="/local"
label="Local Timeline"
icon="#fa-users"
/>
<PageListItem href="/federated"
label="Federated Timeline"
icon="#fa-globe"
/>
<PageListItem href="/favorites"
label="Favorites"
icon="#fa-star"
/>
</PageList>
</div>
{{else}}
<HiddenFromSSR>
<FreeTextLayout>
<h1>More</h1>
<p>More options appear here when logged in.</p>
</FreeTextLayout>
</HiddenFromSSR>
{{/if}}
</Layout>
<style>
.community-page {
margin: 20px;
}
@media (max-width: 767px) {
.community-page {
margin: 20px 10px;
}
}
</style>
<script>
import Layout from '../_components/Layout.html'
import FreeTextLayout from '../_components/FreeTextLayout.html'
import { store } from '../_store/store.js'
import HiddenFromSSR from '../_components/HiddenFromSSR'
import PageList from './_components/PageList.html'
import PageListItem from './_components/PageListItem.html'
export default {
store: () => store,
components: {
Layout,
FreeTextLayout,
HiddenFromSSR,
PageList,
PageListItem
}
};
</script>

38
routes/favorites.html Normal file
View File

@ -0,0 +1,38 @@
<:Head>
<title>Pinafore Favorites</title>
</:Head>
<Layout page='favorites' virtual="true" virtualRealm="favorites">
{{#if $isUserLoggedIn}}
<DynamicPageBanner title="Favorites" icon="#fa-star"/>
<LazyTimeline timeline='favorites' />
{{else}}
<HiddenFromSSR>
<FreeTextLayout>
<h1>Favorites</h1>
<p>Your favorites will appear here when logged in.</p>
</FreeTextLayout>
</HiddenFromSSR>
{{/if}}
</Layout>
<script>
import Layout from './_components/Layout.html'
import LazyTimeline from './_components/timeline/LazyTimeline.html'
import FreeTextLayout from './_components/FreeTextLayout.html'
import { store } from './_store/store.js'
import HiddenFromSSR from './_components/HiddenFromSSR'
import DynamicPageBanner from './_components/DynamicPageBanner.html'
export default {
store: () => store,
components: {
Layout,
LazyTimeline,
FreeTextLayout,
HiddenFromSSR,
DynamicPageBanner
}
}
</script>

View File

@ -4,6 +4,7 @@
<Layout page='federated' virtual="true" virtualRealm="federated"> <Layout page='federated' virtual="true" virtualRealm="federated">
{{#if $isUserLoggedIn}} {{#if $isUserLoggedIn}}
<DynamicPageBanner title="Federated Timeline" icon="#fa-globe"/>
<LazyTimeline timeline='federated' /> <LazyTimeline timeline='federated' />
{{else}} {{else}}
<HiddenFromSSR> <HiddenFromSSR>
@ -22,6 +23,7 @@
import FreeTextLayout from './_components/FreeTextLayout.html' import FreeTextLayout from './_components/FreeTextLayout.html'
import { store } from './_store/store.js' import { store } from './_store/store.js'
import HiddenFromSSR from './_components/HiddenFromSSR' import HiddenFromSSR from './_components/HiddenFromSSR'
import DynamicPageBanner from './_components/DynamicPageBanner.html'
export default { export default {
store: () => store, store: () => store,
@ -29,7 +31,8 @@
Layout, Layout,
LazyTimeline, LazyTimeline,
FreeTextLayout, FreeTextLayout,
HiddenFromSSR HiddenFromSSR,
DynamicPageBanner
} }
} }
</script> </script>

View File

@ -4,6 +4,7 @@
<Layout page='local' virtual="true" virtualRealm="local"> <Layout page='local' virtual="true" virtualRealm="local">
{{#if $isUserLoggedIn}} {{#if $isUserLoggedIn}}
<DynamicPageBanner title="Local Timeline" icon="#fa-users"/>
<LazyTimeline timeline='local' /> <LazyTimeline timeline='local' />
{{else}} {{else}}
<HiddenFromSSR> <HiddenFromSSR>
@ -22,6 +23,7 @@
import FreeTextLayout from './_components/FreeTextLayout.html' import FreeTextLayout from './_components/FreeTextLayout.html'
import { store } from './_store/store.js' import { store } from './_store/store.js'
import HiddenFromSSR from './_components/HiddenFromSSR' import HiddenFromSSR from './_components/HiddenFromSSR'
import DynamicPageBanner from './_components/DynamicPageBanner.html'
export default { export default {
store: () => store, store: () => store,
@ -29,7 +31,8 @@
Layout, Layout,
LazyTimeline, LazyTimeline,
FreeTextLayout, FreeTextLayout,
HiddenFromSSR HiddenFromSSR,
DynamicPageBanner
} }
} }
</script> </script>

View File

@ -84,6 +84,9 @@ body.offline,body.theme-hotpants.offline,body.theme-majesty.offline,body.theme-o
<symbol id="fa-user-plus" viewBox="0 0 2048 1792"><title>Follow</title><path d="M704 896q-159 0-271.5-112.5T320 512t112.5-271.5T704 128t271.5 112.5T1088 512 975.5 783.5 704 896zm960 128h352q13 0 22.5 9.5t9.5 22.5v192q0 13-9.5 22.5t-22.5 9.5h-352v352q0 13-9.5 22.5t-22.5 9.5h-192q-13 0-22.5-9.5t-9.5-22.5v-352h-352q-13 0-22.5-9.5t-9.5-22.5v-192q0-13 9.5-22.5t22.5-9.5h352V672q0-13 9.5-22.5t22.5-9.5h192q13 0 22.5 9.5t9.5 22.5v352zm-736 224q0 52 38 90t90 38h256v238q-68 50-171 50H267q-121 0-194-69T0 1405q0-53 3.5-103.5t14-109T44 1084t43-97.5 62-81 85.5-53.5T346 832q19 0 39 17 79 61 154.5 91.5T704 971t164.5-30.5T1023 849q20-17 39-17 132 0 217 96h-223q-52 0-90 38t-38 90v192z"></path></symbol> <symbol id="fa-user-plus" viewBox="0 0 2048 1792"><title>Follow</title><path d="M704 896q-159 0-271.5-112.5T320 512t112.5-271.5T704 128t271.5 112.5T1088 512 975.5 783.5 704 896zm960 128h352q13 0 22.5 9.5t9.5 22.5v192q0 13-9.5 22.5t-22.5 9.5h-352v352q0 13-9.5 22.5t-22.5 9.5h-192q-13 0-22.5-9.5t-9.5-22.5v-352h-352q-13 0-22.5-9.5t-9.5-22.5v-192q0-13 9.5-22.5t22.5-9.5h352V672q0-13 9.5-22.5t22.5-9.5h192q13 0 22.5 9.5t9.5 22.5v352zm-736 224q0 52 38 90t90 38h256v238q-68 50-171 50H267q-121 0-194-69T0 1405q0-53 3.5-103.5t14-109T44 1084t43-97.5 62-81 85.5-53.5T346 832q19 0 39 17 79 61 154.5 91.5T704 971t164.5-30.5T1023 849q20-17 39-17 132 0 217 96h-223q-52 0-90 38t-38 90v192z"></path></symbol>
<symbol id="fa-external-link" viewBox="0 0 1792 1792"><title>External Link</title><path d="M1408 928v320q0 119-84.5 203.5T1120 1536H288q-119 0-203.5-84.5T0 1248V416q0-119 84.5-203.5T288 128h704q14 0 23 9t9 23v64q0 14-9 23t-23 9H288q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113V928q0-14 9-23t23-9h64q14 0 23 9t9 23zm384-864v512q0 26-19 45t-45 19-45-19l-176-176-652 652q-10 10-23 10t-23-10L695 983q-10-10-10-23t10-23l652-652-176-176q-19-19-19-45t19-45 45-19h512q26 0 45 19t19 45z"></path></symbol> <symbol id="fa-external-link" viewBox="0 0 1792 1792"><title>External Link</title><path d="M1408 928v320q0 119-84.5 203.5T1120 1536H288q-119 0-203.5-84.5T0 1248V416q0-119 84.5-203.5T288 128h704q14 0 23 9t9 23v64q0 14-9 23t-23 9H288q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113V928q0-14 9-23t23-9h64q14 0 23 9t9 23zm384-864v512q0 26-19 45t-45 19-45-19l-176-176-652 652q-10 10-23 10t-23-10L695 983q-10-10-10-23t10-23l652-652-176-176q-19-19-19-45t19-45 45-19h512q26 0 45 19t19 45z"></path></symbol>
<symbol id="fa-search" viewBox="0 0 1792 1792"><title>Search</title><path d="M1216 832q0-185-131.5-316.5T768 384 451.5 515.5 320 832t131.5 316.5T768 1280t316.5-131.5T1216 832zm512 832q0 52-38 90t-90 38q-54 0-90-38l-343-342q-179 124-399 124-143 0-273.5-55.5t-225-150-150-225T64 832t55.5-273.5 150-225 225-150T768 128t273.5 55.5 225 150 150 225T1472 832q0 220-124 399l343 343q37 37 37 90z"></path></symbol> <symbol id="fa-search" viewBox="0 0 1792 1792"><title>Search</title><path d="M1216 832q0-185-131.5-316.5T768 384 451.5 515.5 320 832t131.5 316.5T768 1280t316.5-131.5T1216 832zm512 832q0 52-38 90t-90 38q-54 0-90-38l-343-342q-179 124-399 124-143 0-273.5-55.5t-225-150-150-225T64 832t55.5-273.5 150-225 225-150T768 128t273.5 55.5 225 150 150 225T1472 832q0 220-124 399l343 343q37 37 37 90z"></path></symbol>
<symbol id="fa-comments" viewBox="0 0 1792 1792"><title>Conversations</title><path d="M1408 768q0 139-94 257t-256.5 186.5T704 1280q-86 0-176-16-124 88-278 128-36 9-86 16h-3q-11 0-20.5-8t-11.5-21q-1-3-1-6.5t.5-6.5 2-6l2.5-5 3.5-5.5 4-5 4.5-5 4-4.5q5-6 23-25t26-29.5 22.5-29 25-38.5 20.5-44q-124-72-195-177T0 768q0-139 94-257t256.5-186.5T704 256t353.5 68.5T1314 511t94 257zm384 256q0 120-71 224.5T1526 1425q10 24 20.5 44t25 38.5 22.5 29 26 29.5 23 25q1 1 4 4.5t4.5 5 4 5 3.5 5.5l2.5 5 2 6 .5 6.5-1 6.5q-3 14-13 22t-22 7q-50-7-86-16-154-40-278-128-90 16-176 16-271 0-472-132 58 4 88 4 161 0 309-45t264-129q125-92 192-212t67-254q0-77-23-152 129 71 204 178t75 230z"></path></symbol>
<symbol id="fa-paperclip" viewBox="0 0 1792 1792"><title>Paperclip</title><path d="M1596 1385q0 117-79 196t-196 79q-135 0-235-100L309 784Q196 669 196 513q0-159 110-270t269-111q158 0 273 113l605 606q10 10 10 22 0 16-30.5 46.5T1386 950q-13 0-23-10L757 333q-79-77-181-77-106 0-179 75t-73 181q0 105 76 181l776 777q63 63 145 63 64 0 106-42t42-106q0-82-63-145L825 659q-26-24-60-24-29 0-48 19t-19 48q0 32 25 59l410 410q10 10 10 22 0 16-31 47t-47 31q-12 0-22-10L633 851q-63-61-63-149 0-82 57-139t139-57q88 0 149 63l581 581q100 98 100 235z"></path></symbol>
<symbol id="fa-thumbtack" viewBox="0 0 1792 1792"><title>Thumbtack</title><path d="M800 864V416q0-14-9-23t-23-9-23 9-9 23v448q0 14 9 23t23 9 23-9 9-23zm672 352q0 26-19 45t-45 19H979l-51 483q-2 12-10.5 20.5T897 1792h-1q-27 0-32-27l-76-485H384q-26 0-45-19t-19-45q0-123 78.5-221.5T576 896V384q-52 0-90-38t-38-90 38-90 90-38h640q52 0 90 38t38 90-38 90-90 38v512q99 0 177.5 98.5T1472 1216z"></path></symbol>
</svg><!-- end insert svg here --> </svg><!-- end insert svg here -->
</svg> </svg>
<!-- The application will be rendered inside this element, <!-- The application will be rendered inside this element,