feat: add account page filters (#1124)
* start on account page filters fixes #1021 * making progress * more progress, just need style now * fix lint * fix style and add test
This commit is contained in:
parent
4d11e0ffbe
commit
6744de59f8
|
@ -27,11 +27,11 @@ export function getTimeline (instanceName, accessToken, timeline, maxId, since,
|
||||||
let url = `${basename(instanceName)}/api/v1/${timelineUrlName}`
|
let url = `${basename(instanceName)}/api/v1/${timelineUrlName}`
|
||||||
|
|
||||||
if (timeline.startsWith('tag/')) {
|
if (timeline.startsWith('tag/')) {
|
||||||
url += '/' + timeline.split('/').slice(-1)[0]
|
url += '/' + timeline.split('/')[1]
|
||||||
} else if (timeline.startsWith('account/')) {
|
} else if (timeline.startsWith('account/')) {
|
||||||
url += '/' + timeline.split('/').slice(-1)[0] + '/statuses'
|
url += '/' + timeline.split('/')[1] + '/statuses'
|
||||||
} else if (timeline.startsWith('list/')) {
|
} else if (timeline.startsWith('list/')) {
|
||||||
url += '/' + timeline.split('/').slice(-1)[0]
|
url += '/' + timeline.split('/')[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
let params = {}
|
let params = {}
|
||||||
|
@ -51,6 +51,14 @@ export function getTimeline (instanceName, accessToken, timeline, maxId, since,
|
||||||
params.local = true
|
params.local = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeline.startsWith('account/')) {
|
||||||
|
if (timeline.endsWith('media')) {
|
||||||
|
params.only_media = true
|
||||||
|
} else {
|
||||||
|
params.exclude_replies = !timeline.endsWith('/with_replies')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
url += '?' + paramsString(params)
|
url += '?' + paramsString(params)
|
||||||
|
|
||||||
return get(url, auth(accessToken), { timeout: DEFAULT_TIMEOUT })
|
return get(url, auth(accessToken), { timeout: DEFAULT_TIMEOUT })
|
||||||
|
|
107
src/routes/_components/profile/AccountProfileFilters.html
Normal file
107
src/routes/_components/profile/AccountProfileFilters.html
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
<nav aria-label="Filters" class="account-profile-filters">
|
||||||
|
<ul>
|
||||||
|
{#each filterTabs as filterTab (filterTab.href)}
|
||||||
|
<li class="{filter === filterTab.filter ? 'current-filter' : 'not-current-filter'}">
|
||||||
|
<a aria-label="{filterTab.label} { filter === filterTab.filter ? '(Current)' : ''}"
|
||||||
|
href={filterTab.href}
|
||||||
|
rel="prefetch">
|
||||||
|
{filterTab.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<style>
|
||||||
|
li {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reset */
|
||||||
|
ul, li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
margin: 5px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
border: 1px solid var(--main-border);
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
background: var(--tab-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
li:not(:first-child) {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover {
|
||||||
|
background: var(--button-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
li.not-current-filter {
|
||||||
|
background: var(--tab-bg-non-selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
li.current-filter {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.current-filter:hover {
|
||||||
|
background: var(--tab-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
li.not-current-filter:hover {
|
||||||
|
background: var(--tab-bg-hover-non-selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
li:active {
|
||||||
|
background: var(--tab-bg-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: 10px;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
font-size: 1.1em;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
filterTabs: ({ account }) => (
|
||||||
|
[
|
||||||
|
{
|
||||||
|
filter: '',
|
||||||
|
label: 'Toots',
|
||||||
|
href: `/accounts/${account.id}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filter: 'with_replies',
|
||||||
|
label: 'Toots and replies',
|
||||||
|
href: `/accounts/${account.id}/with_replies`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filter: 'media',
|
||||||
|
label: 'Media',
|
||||||
|
href: `/accounts/${account.id}/media`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
66
src/routes/_components/profile/AccountProfilePage.html
Normal file
66
src/routes/_components/profile/AccountProfilePage.html
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
{#if $isUserLoggedIn}
|
||||||
|
<TimelinePage {timeline} >
|
||||||
|
<DynamicPageBanner title="" ariaTitle="Profile page for {accountName}"/>
|
||||||
|
{#if $currentAccountProfile && $currentVerifyCredentials}
|
||||||
|
<AccountProfile account={$currentAccountProfile}
|
||||||
|
relationship={$currentAccountRelationship}
|
||||||
|
verifyCredentials={$currentVerifyCredentials}
|
||||||
|
/>
|
||||||
|
<AccountProfileFilters account={$currentAccountProfile} {filter} />
|
||||||
|
{/if}
|
||||||
|
{#if !filter}
|
||||||
|
<PinnedStatuses {accountId} />
|
||||||
|
{/if}
|
||||||
|
</TimelinePage>
|
||||||
|
{:else}
|
||||||
|
<HiddenFromSSR>
|
||||||
|
<FreeTextLayout>
|
||||||
|
<h1>Profile</h1>
|
||||||
|
|
||||||
|
<p>A user timeline will appear here when logged in.</p>
|
||||||
|
</FreeTextLayout>
|
||||||
|
</HiddenFromSSR>
|
||||||
|
{/if}
|
||||||
|
<script>
|
||||||
|
import TimelinePage from '../TimelinePage.html'
|
||||||
|
import FreeTextLayout from '../FreeTextLayout.html'
|
||||||
|
import { store } from '../../_store/store.js'
|
||||||
|
import HiddenFromSSR from '../HiddenFromSSR'
|
||||||
|
import DynamicPageBanner from '../DynamicPageBanner.html'
|
||||||
|
import { updateProfileAndRelationship, clearProfileAndRelationship } from '../../_actions/accounts'
|
||||||
|
import AccountProfile from './AccountProfile.html'
|
||||||
|
import PinnedStatuses from '../timeline/PinnedStatuses.html'
|
||||||
|
import AccountProfileFilters from './AccountProfileFilters.html'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
oncreate () {
|
||||||
|
let { accountId } = this.get()
|
||||||
|
clearProfileAndRelationship()
|
||||||
|
updateProfileAndRelationship(accountId)
|
||||||
|
},
|
||||||
|
store: () => store,
|
||||||
|
computed: {
|
||||||
|
profileName: ({ $currentAccountProfile }) => {
|
||||||
|
return ($currentAccountProfile && ('@' + $currentAccountProfile.acct)) || ''
|
||||||
|
},
|
||||||
|
shortProfileName: ({ $currentAccountProfile }) => {
|
||||||
|
return ($currentAccountProfile && ('@' + $currentAccountProfile.username)) || ''
|
||||||
|
},
|
||||||
|
accountName: ({ $currentAccountProfile }) => {
|
||||||
|
return ($currentAccountProfile && ($currentAccountProfile.display_name || $currentAccountProfile.username)) || ''
|
||||||
|
},
|
||||||
|
timeline: ({ accountId, filter }) => (
|
||||||
|
`account/${accountId}` + (filter ? `/${filter}` : '')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
TimelinePage,
|
||||||
|
FreeTextLayout,
|
||||||
|
HiddenFromSSR,
|
||||||
|
DynamicPageBanner,
|
||||||
|
AccountProfile,
|
||||||
|
PinnedStatuses,
|
||||||
|
AccountProfileFilters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,59 +1,10 @@
|
||||||
{#if $isUserLoggedIn}
|
<AccountProfilePage accountId={params.accountId} filter="" />
|
||||||
<TimelinePage timeline="account/{params.accountId}">
|
|
||||||
<DynamicPageBanner title="" ariaTitle="Profile page for {accountName}"/>
|
|
||||||
{#if $currentAccountProfile && $currentVerifyCredentials}
|
|
||||||
<AccountProfile account={$currentAccountProfile}
|
|
||||||
relationship={$currentAccountRelationship}
|
|
||||||
verifyCredentials={$currentVerifyCredentials}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<PinnedStatuses accountId={params.accountId} />
|
|
||||||
</TimelinePage>
|
|
||||||
{:else}
|
|
||||||
<HiddenFromSSR>
|
|
||||||
<FreeTextLayout>
|
|
||||||
<h1>Profile</h1>
|
|
||||||
|
|
||||||
<p>A user timeline will appear here when logged in.</p>
|
|
||||||
</FreeTextLayout>
|
|
||||||
</HiddenFromSSR>
|
|
||||||
{/if}
|
|
||||||
<script>
|
<script>
|
||||||
import TimelinePage from '../../../_components/TimelinePage.html'
|
import AccountProfilePage from '../../../_components/profile/AccountProfilePage.html'
|
||||||
import FreeTextLayout from '../../../_components/FreeTextLayout.html'
|
|
||||||
import { store } from '../../../_store/store.js'
|
|
||||||
import HiddenFromSSR from '../../../_components/HiddenFromSSR'
|
|
||||||
import DynamicPageBanner from '../../../_components/DynamicPageBanner.html'
|
|
||||||
import { updateProfileAndRelationship, clearProfileAndRelationship } from '../../../_actions/accounts'
|
|
||||||
import AccountProfile from '../../../_components/profile/AccountProfile.html'
|
|
||||||
import PinnedStatuses from '../../../_components/timeline/PinnedStatuses.html'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
|
||||||
let { params } = this.get()
|
|
||||||
let { accountId } = params
|
|
||||||
clearProfileAndRelationship()
|
|
||||||
updateProfileAndRelationship(accountId)
|
|
||||||
},
|
|
||||||
store: () => store,
|
|
||||||
computed: {
|
|
||||||
profileName: ({ $currentAccountProfile }) => {
|
|
||||||
return ($currentAccountProfile && ('@' + $currentAccountProfile.acct)) || ''
|
|
||||||
},
|
|
||||||
shortProfileName: ({ $currentAccountProfile }) => {
|
|
||||||
return ($currentAccountProfile && ('@' + $currentAccountProfile.username)) || ''
|
|
||||||
},
|
|
||||||
accountName: ({ $currentAccountProfile }) => {
|
|
||||||
return ($currentAccountProfile && ($currentAccountProfile.display_name || $currentAccountProfile.username)) || ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
TimelinePage,
|
AccountProfilePage
|
||||||
FreeTextLayout,
|
|
||||||
HiddenFromSSR,
|
|
||||||
DynamicPageBanner,
|
|
||||||
AccountProfile,
|
|
||||||
PinnedStatuses
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
10
src/routes/_pages/accounts/[accountId]/media.html
Normal file
10
src/routes/_pages/accounts/[accountId]/media.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<AccountProfilePage accountId={params.accountId} filter="media" />
|
||||||
|
<script>
|
||||||
|
import AccountProfilePage from '../../../_components/profile/AccountProfilePage.html'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
AccountProfilePage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
10
src/routes/_pages/accounts/[accountId]/with_replies.html
Normal file
10
src/routes/_pages/accounts/[accountId]/with_replies.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<AccountProfilePage accountId={params.accountId} filter="with_replies" />
|
||||||
|
<script>
|
||||||
|
import AccountProfilePage from '../../../_components/profile/AccountProfilePage.html'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
AccountProfilePage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -23,9 +23,17 @@ export function timelineComputations (store) {
|
||||||
store.compute('currentTimelineType', ['currentTimeline'], currentTimeline => (
|
store.compute('currentTimelineType', ['currentTimeline'], currentTimeline => (
|
||||||
currentTimeline && currentTimeline.split('/')[0])
|
currentTimeline && currentTimeline.split('/')[0])
|
||||||
)
|
)
|
||||||
store.compute('currentTimelineValue', ['currentTimeline'], currentTimeline => (
|
store.compute('currentTimelineValue', ['currentTimeline'], currentTimeline => {
|
||||||
currentTimeline && currentTimeline.split('/').slice(-1)[0])
|
if (!currentTimeline) {
|
||||||
)
|
return void 0
|
||||||
|
}
|
||||||
|
let split = currentTimeline.split('/')
|
||||||
|
let len = split.length
|
||||||
|
if (split[len - 1] === 'with_replies' || split[len - 1] === 'media') {
|
||||||
|
return split[len - 2]
|
||||||
|
}
|
||||||
|
return split[len - 1]
|
||||||
|
})
|
||||||
store.compute('firstTimelineItemId', ['timelineItemSummaries'], (timelineItemSummaries) => (
|
store.compute('firstTimelineItemId', ['timelineItemSummaries'], (timelineItemSummaries) => (
|
||||||
getFirstIdFromItemSummaries(timelineItemSummaries)
|
getFirstIdFromItemSummaries(timelineItemSummaries)
|
||||||
))
|
))
|
||||||
|
|
20
src/routes/accounts/[accountId]/media.html
Normal file
20
src/routes/accounts/[accountId]/media.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<Title name="Profile with media" />
|
||||||
|
|
||||||
|
<LazyPage {pageComponent} {params} />
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Title from '../../_components/Title.html'
|
||||||
|
import LazyPage from '../../_components/LazyPage.html'
|
||||||
|
import pageComponent from '../../_pages/accounts/[accountId]/media.html'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
|
||||||
|
Title,
|
||||||
|
LazyPage
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
pageComponent
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
20
src/routes/accounts/[accountId]/with_replies.html
Normal file
20
src/routes/accounts/[accountId]/with_replies.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<Title name="Profile with replies" />
|
||||||
|
|
||||||
|
<LazyPage {pageComponent} {params} />
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Title from '../../_components/Title.html'
|
||||||
|
import LazyPage from '../../_components/LazyPage.html'
|
||||||
|
import pageComponent from '../../_pages/accounts/[accountId]/with_replies.html'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
|
||||||
|
Title,
|
||||||
|
LazyPage
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
pageComponent
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -100,4 +100,10 @@
|
||||||
--file-drop-mask: #{rgba(255, 255, 255, 0.8)};
|
--file-drop-mask: #{rgba(255, 255, 255, 0.8)};
|
||||||
|
|
||||||
--banner-fill: #{$main-theme-color};
|
--banner-fill: #{$main-theme-color};
|
||||||
|
|
||||||
|
--tab-bg: #{$main-bg-color};
|
||||||
|
--tab-bg-non-selected: #{darken($main-bg-color, 3%)};
|
||||||
|
--tab-bg-active: #{darken($main-bg-color, 25%)};
|
||||||
|
--tab-bg-hover: #{darken($main-bg-color, 4%)};
|
||||||
|
--tab-bg-hover-non-selected: #{darken($main-bg-color, 7%)};
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,4 +38,10 @@
|
||||||
--settings-list-item-bg-hover: #{lighten($main-bg-color, 3%)};
|
--settings-list-item-bg-hover: #{lighten($main-bg-color, 3%)};
|
||||||
|
|
||||||
--banner-fill: #{lighten($main-theme-color, 10%)};
|
--banner-fill: #{lighten($main-theme-color, 10%)};
|
||||||
|
|
||||||
|
--tab-bg: #{$main-bg-color};
|
||||||
|
--tab-bg-non-selected: #{darken($main-bg-color, 2%)};
|
||||||
|
--tab-bg-active: #{lighten($main-bg-color, 15%)};
|
||||||
|
--tab-bg-hover: #{lighten($main-bg-color, 1%)};
|
||||||
|
--tab-bg-hover-non-selected: #{darken($main-bg-color, 1%)};
|
||||||
}
|
}
|
||||||
|
|
31
tests/spec/031-account-filters.js
Normal file
31
tests/spec/031-account-filters.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import {
|
||||||
|
accountProfileFilterMedia, accountProfileFilterStatuses,
|
||||||
|
accountProfileFilterStatusesAndReplies,
|
||||||
|
avatarInComposeBox,
|
||||||
|
getNthPinnedStatus, getNthStatus,
|
||||||
|
getUrl
|
||||||
|
} from '../utils'
|
||||||
|
import { loginAsFoobar } from '../roles'
|
||||||
|
|
||||||
|
fixture`031-account-filters.js`
|
||||||
|
.page`http://localhost:4002`
|
||||||
|
|
||||||
|
test('Basic account filters test', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t
|
||||||
|
.click(avatarInComposeBox)
|
||||||
|
.expect(getUrl()).contains('/accounts/2')
|
||||||
|
.expect(getNthPinnedStatus(1).innerText).contains('this is unlisted')
|
||||||
|
.expect(getNthStatus(1).innerText).contains('this is unlisted')
|
||||||
|
.click(accountProfileFilterStatusesAndReplies)
|
||||||
|
.expect(getUrl()).contains('/accounts/2/with_replies')
|
||||||
|
.expect(getNthPinnedStatus(1).exists).notOk()
|
||||||
|
.expect(getNthStatus(1).innerText).contains('this is unlisted')
|
||||||
|
.click(accountProfileFilterMedia)
|
||||||
|
.expect(getNthPinnedStatus(1).exists).notOk()
|
||||||
|
.expect(getNthStatus(1).innerText).contains('kitten CW')
|
||||||
|
.click(accountProfileFilterStatuses)
|
||||||
|
.expect(getUrl()).contains('/accounts/2')
|
||||||
|
.expect(getNthPinnedStatus(1).innerText).contains('this is unlisted')
|
||||||
|
.expect(getNthStatus(1).innerText).contains('this is unlisted')
|
||||||
|
})
|
|
@ -60,6 +60,10 @@ export const composeModalPostPrivacyButton = $('.modal-dialog .compose-box-toolb
|
||||||
|
|
||||||
export const postPrivacyDialogButtonUnlisted = $('[aria-label="Post privacy dialog"] li:nth-child(2) button')
|
export const postPrivacyDialogButtonUnlisted = $('[aria-label="Post privacy dialog"] li:nth-child(2) button')
|
||||||
|
|
||||||
|
export const accountProfileFilterStatuses = $('.account-profile-filters li:nth-child(1)')
|
||||||
|
export const accountProfileFilterStatusesAndReplies = $('.account-profile-filters li:nth-child(2)')
|
||||||
|
export const accountProfileFilterMedia = $('.account-profile-filters li:nth-child(3)')
|
||||||
|
|
||||||
export function getComposeModalNthMediaAltInput (n) {
|
export function getComposeModalNthMediaAltInput (n) {
|
||||||
return $(`.modal-dialog .compose-media:nth-child(${n}) .compose-media-alt input`)
|
return $(`.modal-dialog .compose-media:nth-child(${n}) .compose-media-alt input`)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue