feat: add home/notification filter settings (#1226)

Fixes #1223
Fixes #1224
This commit is contained in:
Nolan Lawson 2019-05-25 13:21:36 -07:00 committed by GitHub
parent 92bff6caaa
commit a17948cf99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 356 additions and 20 deletions

View File

@ -0,0 +1,48 @@
<div class="generic-instance-settings">
<form aria-label={label} ref:form>
{#each options as option, i (option.key) }
{#if i > 0}
<br>
{/if}
<input type="checkbox"
id="instance-option-{option.key}"
name="{option.key}"
on:change="onChange(event)"
>
<label for="instance-option-{option.key}">
{option.label}
</label>
{/each}
</form>
</div>
<style>
.generic-instance-settings {
background: var(--form-bg);
border: 1px solid var(--main-border);
border-radius: 4px;
display: block;
padding: 20px;
line-height: 2em;
}
</style>
<script>
import { store } from '../../../_store/store'
export default {
oncreate () {
let { instanceName, options } = this.get()
let { form } = this.refs
for (let { key, defaultValue } of options) {
form.elements[key].checked = this.store.getInstanceSetting(instanceName, key, defaultValue)
}
},
methods: {
onChange (event) {
let { instanceName } = this.get()
let { target } = event
this.store.setInstanceSetting(instanceName, target.name, target.checked)
}
},
store: () => store
}
</script>

View File

@ -0,0 +1,29 @@
<GenericInstanceSettings
{instanceName}
{options}
label="Home timeline filter settings"
/>
<script>
import GenericInstanceSettings from './GenericInstanceSettings.html'
import { HOME_REBLOGS, HOME_REPLIES } from '../../../_static/instanceSettings'
export default {
data: () => ({
options: [
{
key: HOME_REBLOGS,
label: 'Show boosts',
defaultValue: true
},
{
key: HOME_REPLIES,
label: 'Show replies',
defaultValue: true
}
]
}),
components: {
GenericInstanceSettings
}
}
</script>

View File

@ -0,0 +1,50 @@
<GenericInstanceSettings
{instanceName}
{options}
label="Notification filter settings"
/>
<script>
import GenericInstanceSettings from './GenericInstanceSettings.html'
import {
NOTIFICATION_REBLOGS,
NOTIFICATION_FAVORITES,
NOTIFICATION_FOLLOWS,
NOTIFICATION_MENTIONS,
NOTIFICATION_POLLS
} from '../../../_static/instanceSettings'
export default {
data: () => ({
options: [
{
key: NOTIFICATION_FOLLOWS,
label: 'New followers',
defaultValue: true
},
{
key: NOTIFICATION_FAVORITES,
label: 'Favorites',
defaultValue: true
},
{
key: NOTIFICATION_REBLOGS,
label: 'Boosts',
defaultValue: true
},
{
key: NOTIFICATION_MENTIONS,
label: 'Mentions',
defaultValue: true
},
{
key: NOTIFICATION_POLLS,
label: 'Poll results',
defaultValue: true
}
]
}),
components: {
GenericInstanceSettings
}
}
</script>

View File

@ -59,6 +59,22 @@
import { observe } from 'svelte-extras' import { observe } from 'svelte-extras'
import { createMakeProps } from '../../_actions/createMakeProps' import { createMakeProps } from '../../_actions/createMakeProps'
import { showMoreAndScrollToTop } from '../../_actions/showMoreAndScrollToTop' import { showMoreAndScrollToTop } from '../../_actions/showMoreAndScrollToTop'
import { get } from '../../_utils/lodash-lite'
import {
HOME_REBLOGS,
HOME_REPLIES,
NOTIFICATION_REBLOGS,
NOTIFICATION_FOLLOWS,
NOTIFICATION_FAVORITES,
NOTIFICATION_POLLS,
NOTIFICATION_MENTIONS,
FILTER_FAVORITE,
FILTER_FOLLOW,
FILTER_MENTION,
FILTER_POLL,
FILTER_REBLOG,
FILTER_REPLY
} from '../../_static/instanceSettings'
export default { export default {
oncreate () { oncreate () {
@ -127,9 +143,56 @@
timelineValue !== $firstTimelineItemId && timelineValue !== $firstTimelineItemId &&
timelineValue timelineValue
), ),
itemIds: ({ $timelineItemSummaries }) => ( currentInstanceSettings: ({ $currentInstance, $instanceSettings }) => (
// TODO: filter $instanceSettings[$currentInstance] || {}
$timelineItemSummaries && $timelineItemSummaries.map(_ => _.id) ),
timelineFilters: ({ currentInstanceSettings, timeline }) => {
if (timeline === 'home') {
return {
[FILTER_REBLOG]: get(currentInstanceSettings, [HOME_REBLOGS], true),
[FILTER_REPLY]: get(currentInstanceSettings, [HOME_REPLIES], true)
}
} else if (timeline === 'notifications') {
return {
[FILTER_REBLOG]: get(currentInstanceSettings, [NOTIFICATION_REBLOGS], true),
[FILTER_FOLLOW]: get(currentInstanceSettings, [NOTIFICATION_FOLLOWS], true),
[FILTER_FAVORITE]: get(currentInstanceSettings, [NOTIFICATION_FAVORITES], true),
[FILTER_MENTION]: get(currentInstanceSettings, [NOTIFICATION_MENTIONS], true),
[FILTER_POLL]: get(currentInstanceSettings, [NOTIFICATION_POLLS], true)
}
}
},
showReblogs: ({ timelineFilters }) => get(timelineFilters, [FILTER_REBLOG], true),
showReplies: ({ timelineFilters }) => get(timelineFilters, [FILTER_REPLY], true),
showFollows: ({ timelineFilters }) => get(timelineFilters, [FILTER_FOLLOW], true),
showMentions: ({ timelineFilters }) => get(timelineFilters, [FILTER_MENTION], true),
showPolls: ({ timelineFilters }) => get(timelineFilters, [FILTER_POLL], true),
showFavs: ({ timelineFilters }) => get(timelineFilters, [FILTER_FAVORITE], true),
itemIds: ({
$timelineItemSummaries, showReblogs, showReplies, showFollows, showMentions,
showPolls, showFavs
}) => (
$timelineItemSummaries && $timelineItemSummaries.filter(item => {
switch (item.type) {
case 'poll':
return showPolls
case 'favourite':
return showFavs
case 'reblog':
return showReblogs
case 'mention':
return showMentions
case 'follow':
return showFollows
}
if (item.reblogId) {
return showReblogs
} else if (item.replyId) {
return showReplies
} else {
return true
}
}).map(_ => _.id)
), ),
itemIdsToAdd: ({ $timelineItemSummariesToAdd }) => ( itemIdsToAdd: ({ $timelineItemSummariesToAdd }) => (
// TODO: filter // TODO: filter

View File

@ -4,9 +4,13 @@
{#if verifyCredentials} {#if verifyCredentials}
<h2>Logged in as:</h2> <h2>Logged in as:</h2>
<InstanceUserProfile {verifyCredentials} /> <InstanceUserProfile {verifyCredentials} />
<h2>Push notifications:</h2> <h2>Home timeline filters</h2>
<HomeTimelineFilterSettings {instanceName} />
<h2>Notification filters</h2>
<NotificationFilterSettings {instanceName} />
<h2>Push notifications</h2>
<PushNotificationSettings {instanceName} /> <PushNotificationSettings {instanceName} />
<h2>Theme:</h2> <h2>Theme</h2>
<ThemeSettings {instanceName} /> <ThemeSettings {instanceName} />
<InstanceActions {instanceName} /> <InstanceActions {instanceName} />
@ -23,6 +27,8 @@
import { store } from '../../../_store/store' import { store } from '../../../_store/store'
import SettingsLayout from '../../../_components/settings/SettingsLayout.html' import SettingsLayout from '../../../_components/settings/SettingsLayout.html'
import InstanceUserProfile from '../../../_components/settings/instance/InstanceUserProfile.html' import InstanceUserProfile from '../../../_components/settings/instance/InstanceUserProfile.html'
import HomeTimelineFilterSettings from '../../../_components/settings/instance/HomeTimelineFilterSettings.html'
import NotificationFilterSettings from '../../../_components/settings/instance/NotificationFilterSettings.html'
import PushNotificationSettings from '../../../_components/settings/instance/PushNotificationSettings.html' import PushNotificationSettings from '../../../_components/settings/instance/PushNotificationSettings.html'
import ThemeSettings from '../../../_components/settings/instance/ThemeSettings.html' import ThemeSettings from '../../../_components/settings/instance/ThemeSettings.html'
import InstanceActions from '../../../_components/settings/instance/InstanceActions.html' import InstanceActions from '../../../_components/settings/instance/InstanceActions.html'
@ -43,7 +49,9 @@
InstanceUserProfile, InstanceUserProfile,
PushNotificationSettings, PushNotificationSettings,
ThemeSettings, ThemeSettings,
InstanceActions InstanceActions,
HomeTimelineFilterSettings,
NotificationFilterSettings
} }
} }
</script> </script>

View File

@ -0,0 +1,15 @@
export const HOME_REBLOGS = 'homeReblogs'
export const HOME_REPLIES = 'homeReplies'
export const NOTIFICATION_REBLOGS = 'notificationReblogs'
export const NOTIFICATION_FAVORITES = 'notificationFavs'
export const NOTIFICATION_FOLLOWS = 'notificationFollows'
export const NOTIFICATION_MENTIONS = 'notificationMentions'
export const NOTIFICATION_POLLS = 'notificationPolls'
export const FILTER_REBLOG = 'reblog'
export const FILTER_REPLY = 'reply'
export const FILTER_MENTION = 'mention'
export const FILTER_FOLLOW = 'follow'
export const FILTER_FAVORITE = 'fav'
export const FILTER_POLL = 'poll'

View File

@ -1,3 +1,5 @@
import { get } from '../../_utils/lodash-lite'
export function instanceMixins (Store) { export function instanceMixins (Store) {
Store.prototype.setComposeData = function (realm, obj) { Store.prototype.setComposeData = function (realm, obj) {
let { composeData, currentInstance } = this.get() let { composeData, currentInstance } = this.get()
@ -20,4 +22,18 @@ export function instanceMixins (Store) {
} }
this.set({ composeData }) this.set({ composeData })
} }
Store.prototype.getInstanceSetting = function (instanceName, settingName, defaultValue) {
let { instanceSettings } = this.get()
return get(instanceSettings, [instanceName, settingName], defaultValue)
}
Store.prototype.setInstanceSetting = function (instanceName, settingName, value) {
let { instanceSettings } = this.get()
if (!instanceSettings[instanceName]) {
instanceSettings[instanceName] = {}
}
instanceSettings[instanceName][settingName] = value
this.set({ instanceSettings })
}
} }

View File

@ -19,6 +19,7 @@ const persistedState = {
largeInlineMedia: false, largeInlineMedia: false,
instanceNameInSearch: '', instanceNameInSearch: '',
instanceThemes: {}, instanceThemes: {},
instanceSettings: {},
loggedInInstances: {}, loggedInInstances: {},
loggedInInstancesInOrder: [], loggedInInstancesInOrder: [],
markMediaAsSensitive: false, markMediaAsSensitive: false,

View File

@ -1,22 +1,22 @@
import { import {
getUrl, notificationFiltersAll, notificationFiltersMention, getUrl, notificationsTabAll, notificationsTabMentions,
notificationsNavButton, validateTimeline notificationsNavButton, validateTimeline
} from '../utils' } from '../utils'
import { loginAsFoobar } from '../roles' import { loginAsFoobar } from '../roles'
import { notificationsMentions, notifications } from '../fixtures' import { notificationsMentions, notifications } from '../fixtures'
fixture`033-notification-filters.js` fixture`033-notification-mentions.js`
.page`http://localhost:4002` .page`http://localhost:4002`
test('Shows notification filters', async t => { test('Shows notification mentions', async t => {
await loginAsFoobar(t) await loginAsFoobar(t)
await t await t
.click(notificationsNavButton) .click(notificationsNavButton)
.expect(getUrl()).match(/\/notifications$/) .expect(getUrl()).match(/\/notifications$/)
.click(notificationFiltersMention) .click(notificationsTabMentions)
.expect(getUrl()).match(/\/notifications\/mentions$/) .expect(getUrl()).match(/\/notifications\/mentions$/)
await validateTimeline(t, notificationsMentions) await validateTimeline(t, notificationsMentions)
await t.click(notificationFiltersAll) await t.click(notificationsTabAll)
.expect(getUrl()).match(/\/notifications$/) .expect(getUrl()).match(/\/notifications$/)
await validateTimeline(t, notifications) await validateTimeline(t, notifications)
}) })

View File

@ -0,0 +1,23 @@
import {
validateTimeline, settingsNavButton, instanceSettingHomeReblogs, homeNavButton
} from '../utils'
import { loginAsFoobar } from '../roles'
import { homeTimeline } from '../fixtures'
import { Selector as $ } from 'testcafe'
fixture`034-home-timeline-filters.js`
.page`http://localhost:4002`
test('Filters reblogs from home timeline', async t => {
await loginAsFoobar(t)
await t
.click(settingsNavButton)
.click($('a').withText('Instances'))
.click($('a').withText('localhost:3000'))
.click(instanceSettingHomeReblogs)
.expect(instanceSettingHomeReblogs.checked).notOk()
.click(homeNavButton)
await validateTimeline(t, homeTimeline.filter(({ content }) => {
return content !== 'pinned toot 1'
}))
})

View File

@ -0,0 +1,48 @@
import {
validateTimeline,
settingsNavButton,
instanceSettingNotificationReblogs,
notificationsNavButton,
instanceSettingNotificationFavs,
instanceSettingNotificationFollows,
instanceSettingNotificationMentions
} from '../utils'
import { loginAsFoobar } from '../roles'
import { notifications } from '../fixtures'
import { Selector as $ } from 'testcafe'
fixture`035-notification-timeline-filters.js`
.page`http://localhost:4002`
function setSettingAndGoToNotifications (t, setting) {
return t.click(settingsNavButton)
.click($('a').withText('Instances'))
.click($('a').withText('localhost:3000'))
.click(setting)
.expect(setting.checked).notOk()
.click(notificationsNavButton)
}
test('Filters reblogs from notification timeline', async t => {
await loginAsFoobar(t)
await setSettingAndGoToNotifications(t, instanceSettingNotificationReblogs)
await validateTimeline(t, notifications.filter(_ => !_.rebloggedBy))
})
test('Filters favs from notification timeline', async t => {
await loginAsFoobar(t)
await setSettingAndGoToNotifications(t, instanceSettingNotificationFavs)
await validateTimeline(t, notifications.filter(_ => !_.favoritedBy))
})
test('Filters follows from notification timeline', async t => {
await loginAsFoobar(t)
await setSettingAndGoToNotifications(t, instanceSettingNotificationFollows)
await validateTimeline(t, notifications.filter(_ => !_.followedBy))
})
test('Filters mentions from notification timeline', async t => {
await loginAsFoobar(t)
await setSettingAndGoToNotifications(t, instanceSettingNotificationMentions)
await validateTimeline(t, notifications.filter(_ => !_.content))
})

View File

@ -1,12 +1,12 @@
import { import {
getNthStatusContent, getNthStatusContent,
getUrl, notificationFiltersAll, notificationFiltersMention, getUrl, notificationsTabAll, notificationsTabMentions,
notificationsNavButton, sleep notificationsNavButton, sleep
} from '../utils' } from '../utils'
import { loginAsFoobar } from '../roles' import { loginAsFoobar } from '../roles'
import { favoriteStatusAs, postAs } from '../serverActions' import { favoriteStatusAs, postAs } from '../serverActions'
fixture`123-notification-filters.js` fixture`123-notification-mentions.js`
.page`http://localhost:4002` .page`http://localhost:4002`
// maybe in the "mentions" view it should prevent the notification icon from showing (1), (2) etc // maybe in the "mentions" view it should prevent the notification icon from showing (1), (2) etc
@ -18,7 +18,7 @@ test('Handles incoming notifications that are mentions', async t => {
await t await t
.click(notificationsNavButton) .click(notificationsNavButton)
.expect(getUrl()).match(/\/notifications$/) .expect(getUrl()).match(/\/notifications$/)
.click(notificationFiltersMention) .click(notificationsTabMentions)
.expect(getUrl()).match(/\/notifications\/mentions$/) .expect(getUrl()).match(/\/notifications\/mentions$/)
await sleep(2000) await sleep(2000)
await postAs('admin', 'hey @foobar I am mentioning you') await postAs('admin', 'hey @foobar I am mentioning you')
@ -27,7 +27,7 @@ test('Handles incoming notifications that are mentions', async t => {
timeout timeout
}) })
.expect(getNthStatusContent(1).innerText).contains('hey @foobar I am mentioning you') .expect(getNthStatusContent(1).innerText).contains('hey @foobar I am mentioning you')
.click(notificationFiltersAll) .click(notificationsTabAll)
.expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page)', { timeout }) .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page)', { timeout })
}) })
@ -39,7 +39,7 @@ test('Handles incoming notifications that are not mentions', async t => {
await t await t
.click(notificationsNavButton) .click(notificationsNavButton)
.expect(getUrl()).match(/\/notifications$/) .expect(getUrl()).match(/\/notifications$/)
.click(notificationFiltersMention) .click(notificationsTabMentions)
.expect(getUrl()).match(/\/notifications\/mentions$/) .expect(getUrl()).match(/\/notifications\/mentions$/)
await sleep(2000) await sleep(2000)
await postAs('admin', 'woot I am mentioning you again @foobar') await postAs('admin', 'woot I am mentioning you again @foobar')
@ -57,7 +57,7 @@ test('Handles incoming notifications that are not mentions', async t => {
await sleep(2000) await sleep(2000)
await t await t
.expect(getNthStatusContent(1).innerText).contains('woot I am mentioning you again @foobar') .expect(getNthStatusContent(1).innerText).contains('woot I am mentioning you again @foobar')
.click(notificationFiltersAll) .click(notificationsTabAll)
.expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page)', { timeout }) .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page)', { timeout })
await t await t
.expect(getNthStatusContent(1).innerText).contains('this is a post that I hope somebody will favorite') .expect(getNthStatusContent(1).innerText).contains('this is a post that I hope somebody will favorite')

View File

@ -0,0 +1,29 @@
import {
settingsNavButton, instanceSettingHomeReblogs, homeNavButton, sleep, getNthStatusContent
} from '../utils'
import { loginAsFoobar } from '../roles'
import { Selector as $ } from 'testcafe'
import { postAs, reblogStatusAs } from '../serverActions'
fixture`124-home-timeline-filters.js`
.page`http://localhost:4002`
test('Filters favs from home timeline', async t => {
await postAs('foobar', 'Nobody should boost this')
await sleep(1000)
let { id: statusId } = await postAs('quux', 'I hope someone cool boosts this')
await reblogStatusAs('admin', statusId)
await sleep(2000)
await loginAsFoobar(t)
await t
.expect(getNthStatusContent(1).innerText).contains('I hope someone cool boosts this')
.expect(getNthStatusContent(2).innerText).contains('Nobody should boost this')
.click(settingsNavButton)
.click($('a').withText('Instances'))
.click($('a').withText('localhost:3000'))
.click(instanceSettingHomeReblogs)
.expect(instanceSettingHomeReblogs.checked).notOk()
.click(homeNavButton)
await t
.expect(getNthStatusContent(1).innerText).contains('Nobody should boost this')
})

View File

@ -64,8 +64,14 @@ export const accountProfileFilterStatuses = $('.account-profile-filters li:nth-c
export const accountProfileFilterStatusesAndReplies = $('.account-profile-filters li:nth-child(2)') export const accountProfileFilterStatusesAndReplies = $('.account-profile-filters li:nth-child(2)')
export const accountProfileFilterMedia = $('.account-profile-filters li:nth-child(3)') export const accountProfileFilterMedia = $('.account-profile-filters li:nth-child(3)')
export const notificationFiltersAll = $('.notification-filters li:nth-child(1)') export const notificationsTabAll = $('.notification-filters li:nth-child(1)')
export const notificationFiltersMention = $('.notification-filters li:nth-child(2)') export const notificationsTabMentions = $('.notification-filters li:nth-child(2)')
export const instanceSettingHomeReblogs = $('#instance-option-homeReblogs')
export const instanceSettingNotificationFollows = $('#instance-option-notificationFollows')
export const instanceSettingNotificationFavs = $('#instance-option-notificationFavs')
export const instanceSettingNotificationReblogs = $('#instance-option-notificationReblogs')
export const instanceSettingNotificationMentions = $('#instance-option-notificationMentions')
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`)