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}`
|
||||
|
||||
if (timeline.startsWith('tag/')) {
|
||||
url += '/' + timeline.split('/').slice(-1)[0]
|
||||
url += '/' + timeline.split('/')[1]
|
||||
} else if (timeline.startsWith('account/')) {
|
||||
url += '/' + timeline.split('/').slice(-1)[0] + '/statuses'
|
||||
url += '/' + timeline.split('/')[1] + '/statuses'
|
||||
} else if (timeline.startsWith('list/')) {
|
||||
url += '/' + timeline.split('/').slice(-1)[0]
|
||||
url += '/' + timeline.split('/')[1]
|
||||
}
|
||||
|
||||
let params = {}
|
||||
|
@ -51,6 +51,14 @@ export function getTimeline (instanceName, accessToken, timeline, maxId, since,
|
|||
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)
|
||||
|
||||
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}
|
||||
<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}
|
||||
<AccountProfilePage accountId={params.accountId} filter="" />
|
||||
<script>
|
||||
import TimelinePage from '../../../_components/TimelinePage.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'
|
||||
import AccountProfilePage from '../../../_components/profile/AccountProfilePage.html'
|
||||
|
||||
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: {
|
||||
TimelinePage,
|
||||
FreeTextLayout,
|
||||
HiddenFromSSR,
|
||||
DynamicPageBanner,
|
||||
AccountProfile,
|
||||
PinnedStatuses
|
||||
AccountProfilePage
|
||||
}
|
||||
}
|
||||
</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 => (
|
||||
currentTimeline && currentTimeline.split('/')[0])
|
||||
)
|
||||
store.compute('currentTimelineValue', ['currentTimeline'], currentTimeline => (
|
||||
currentTimeline && currentTimeline.split('/').slice(-1)[0])
|
||||
)
|
||||
store.compute('currentTimelineValue', ['currentTimeline'], currentTimeline => {
|
||||
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) => (
|
||||
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)};
|
||||
|
||||
--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%)};
|
||||
|
||||
--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 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) {
|
||||
return $(`.modal-dialog .compose-media:nth-child(${n}) .compose-media-alt input`)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue