feat: implement notification filters (all vs mentions) (#1177)
fixes #1176
This commit is contained in:
		
							parent
							
								
									ff1e9e2c41
								
							
						
					
					
						commit
						23bdc6c87e
					
				
					 19 changed files with 343 additions and 125 deletions
				
			
		| 
						 | 
				
			
			@ -6,15 +6,22 @@ import { addStatusOrNotification } from './addStatusOrNotification'
 | 
			
		|||
function processMessage (instanceName, timelineName, message) {
 | 
			
		||||
  mark('processMessage')
 | 
			
		||||
  let { event, payload } = message
 | 
			
		||||
  if (['update', 'notification', 'conversation'].includes(event)) {
 | 
			
		||||
    payload = JSON.parse(payload) // only these payloads are JSON-encoded for some reason
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case 'delete':
 | 
			
		||||
      deleteStatus(instanceName, payload)
 | 
			
		||||
      break
 | 
			
		||||
    case 'update':
 | 
			
		||||
      addStatusOrNotification(instanceName, timelineName, JSON.parse(payload))
 | 
			
		||||
      addStatusOrNotification(instanceName, timelineName, payload)
 | 
			
		||||
      break
 | 
			
		||||
    case 'notification':
 | 
			
		||||
      addStatusOrNotification(instanceName, 'notifications', JSON.parse(payload))
 | 
			
		||||
      addStatusOrNotification(instanceName, 'notifications', payload)
 | 
			
		||||
      if (payload.type === 'mention') {
 | 
			
		||||
        addStatusOrNotification(instanceName, 'notifications/mentions', payload)
 | 
			
		||||
      }
 | 
			
		||||
      break
 | 
			
		||||
    case 'conversation':
 | 
			
		||||
      // This is a hack in order to mostly fit the conversation model into
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +29,7 @@ function processMessage (instanceName, timelineName, message) {
 | 
			
		|||
      // reproduce what is done for statuses for the conversation.
 | 
			
		||||
      //
 | 
			
		||||
      // It will add new DMs as new conversations instead of updating existing threads
 | 
			
		||||
      addStatusOrNotification(instanceName, timelineName, JSON.parse(payload).last_status)
 | 
			
		||||
      addStatusOrNotification(instanceName, timelineName, payload.last_status)
 | 
			
		||||
      break
 | 
			
		||||
  }
 | 
			
		||||
  stop('processMessage')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ function getTimelineUrlPath (timeline) {
 | 
			
		|||
    case 'home':
 | 
			
		||||
      return 'timelines/home'
 | 
			
		||||
    case 'notifications':
 | 
			
		||||
    case 'notifications/mentions':
 | 
			
		||||
      return 'notifications'
 | 
			
		||||
    case 'favorites':
 | 
			
		||||
      return 'favourites'
 | 
			
		||||
| 
						 | 
				
			
			@ -61,6 +62,10 @@ export async function getTimeline (instanceName, accessToken, timeline, maxId, s
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (timeline === 'notifications/mentions') {
 | 
			
		||||
    params.exclude_types = ['follow', 'favourite', 'reblog', 'poll']
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  url += '?' + paramsString(params)
 | 
			
		||||
 | 
			
		||||
  const items = await get(url, auth(accessToken), { timeout: DEFAULT_TIMEOUT })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -146,7 +146,11 @@
 | 
			
		|||
    },
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    computed: {
 | 
			
		||||
      selected: ({ page, name }) => page === name,
 | 
			
		||||
      selected: ({ page, name }) => {
 | 
			
		||||
        return page === name ||
 | 
			
		||||
          // special case – these should both highlight the notifications tab icon
 | 
			
		||||
          (name === 'notifications' && page === 'notifications/mentions')
 | 
			
		||||
      },
 | 
			
		||||
      ariaLabel: ({ selected, name, label, $numberOfNotifications }) => {
 | 
			
		||||
        let res = label
 | 
			
		||||
        if (selected) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										29
									
								
								src/routes/_components/NotificationFilters.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/routes/_components/NotificationFilters.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
<TabSet
 | 
			
		||||
  label="Filters"
 | 
			
		||||
  currentTabName={filter}
 | 
			
		||||
  {tabs}
 | 
			
		||||
  className="notification-filters"
 | 
			
		||||
/>
 | 
			
		||||
<script>
 | 
			
		||||
  import TabSet from './TabSet.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      tabs: [
 | 
			
		||||
        {
 | 
			
		||||
          name: '',
 | 
			
		||||
          label: 'All',
 | 
			
		||||
          href: `/notifications`
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'mentions',
 | 
			
		||||
          label: 'Mentions',
 | 
			
		||||
          href: `/notifications/mentions`
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }),
 | 
			
		||||
    components: {
 | 
			
		||||
      TabSet
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										89
									
								
								src/routes/_components/TabSet.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/routes/_components/TabSet.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
<nav aria-label={label} class={className}>
 | 
			
		||||
  <ul>
 | 
			
		||||
    {#each tabs as tab (tab.name)}
 | 
			
		||||
    <li class="{currentTabName === tab.name ? 'current' : 'not-current'}">
 | 
			
		||||
      <a aria-label="{tab.label} { currentTabName === tab.name ? '(Current)' : ''}"
 | 
			
		||||
         href={tab.href}
 | 
			
		||||
         rel="prefetch">
 | 
			
		||||
        {tab.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 {
 | 
			
		||||
    background: var(--tab-bg-non-selected);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  li.current {
 | 
			
		||||
    border-bottom: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  li.current:hover {
 | 
			
		||||
    background: var(--tab-bg-hover);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  li.not-current: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 {
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      className: ''
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,107 +1,36 @@
 | 
			
		|||
<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>
 | 
			
		||||
<TabSet
 | 
			
		||||
  label="Filters"
 | 
			
		||||
  currentTabName={filter}
 | 
			
		||||
  {tabs}
 | 
			
		||||
  className="account-profile-filters"
 | 
			
		||||
/>
 | 
			
		||||
<script>
 | 
			
		||||
  import TabSet from '../TabSet.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    computed: {
 | 
			
		||||
      filterTabs: ({ account }) => (
 | 
			
		||||
      tabs: ({ account }) => (
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
            filter: '',
 | 
			
		||||
            name: '',
 | 
			
		||||
            label: 'Toots',
 | 
			
		||||
            href: `/accounts/${account.id}`
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            filter: 'with_replies',
 | 
			
		||||
            name: 'with_replies',
 | 
			
		||||
            label: 'Toots and replies',
 | 
			
		||||
            href: `/accounts/${account.id}/with_replies`
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            filter: 'media',
 | 
			
		||||
            name: 'media',
 | 
			
		||||
            label: 'Media',
 | 
			
		||||
            href: `/accounts/${account.id}/media`
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      )
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
      TabSet
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -111,7 +111,7 @@ async function insertStatusThread (instanceName, statusId, statuses) {
 | 
			
		|||
 | 
			
		||||
export async function insertTimelineItems (instanceName, timeline, timelineItems) {
 | 
			
		||||
  /* no await */ scheduleCleanup()
 | 
			
		||||
  if (timeline === 'notifications') {
 | 
			
		||||
  if (timeline === 'notifications' || timeline === 'notifications/mentions') {
 | 
			
		||||
    return insertTimelineNotifications(instanceName, timeline, timelineItems)
 | 
			
		||||
  } else if (timeline.startsWith('status/')) {
 | 
			
		||||
    let statusId = timeline.split('/').slice(-1)[0]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -84,7 +84,7 @@ async function getStatusThread (instanceName, statusId) {
 | 
			
		|||
export async function getTimeline (instanceName, timeline, maxId, limit) {
 | 
			
		||||
  maxId = maxId || null
 | 
			
		||||
  limit = limit || TIMELINE_BATCH_SIZE
 | 
			
		||||
  if (timeline === 'notifications') {
 | 
			
		||||
  if (timeline === 'notifications' || timeline === 'notifications/mentions') {
 | 
			
		||||
    return getNotificationTimeline(instanceName, timeline, maxId, limit)
 | 
			
		||||
  } else if (timeline.startsWith('status/')) {
 | 
			
		||||
    let statusId = timeline.split('/').slice(-1)[0]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -90,6 +90,7 @@
 | 
			
		|||
    <a href="/federated">Federated</a>
 | 
			
		||||
    <a href="/favorites">Favorites</a>
 | 
			
		||||
    <a href="/direct">Conversations</a>
 | 
			
		||||
    <a href="/notifications/mentions">Notification mentions</a>
 | 
			
		||||
  </div>
 | 
			
		||||
{/if}
 | 
			
		||||
<style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,26 +0,0 @@
 | 
			
		|||
{#if $isUserLoggedIn}
 | 
			
		||||
  <TimelinePage timeline="notifications" />
 | 
			
		||||
{:else}
 | 
			
		||||
<HiddenFromSSR>
 | 
			
		||||
  <FreeTextLayout>
 | 
			
		||||
    <h1>Notifications</h1>
 | 
			
		||||
 | 
			
		||||
    <p>Your notifications will appear here when logged in.</p>
 | 
			
		||||
  </FreeTextLayout>
 | 
			
		||||
</HiddenFromSSR>
 | 
			
		||||
{/if}
 | 
			
		||||
<script>
 | 
			
		||||
  import FreeTextLayout from '../_components/FreeTextLayout.html'
 | 
			
		||||
  import { store } from '../_store/store.js'
 | 
			
		||||
  import HiddenFromSSR from '../_components/HiddenFromSSR'
 | 
			
		||||
  import TimelinePage from '../_components/TimelinePage.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    components: {
 | 
			
		||||
      FreeTextLayout,
 | 
			
		||||
      HiddenFromSSR,
 | 
			
		||||
      TimelinePage
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										29
									
								
								src/routes/_pages/notifications/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/routes/_pages/notifications/index.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
{#if $isUserLoggedIn}
 | 
			
		||||
  <NotificationFilters filter="" />
 | 
			
		||||
  <TimelinePage timeline="notifications" />
 | 
			
		||||
{:else}
 | 
			
		||||
<HiddenFromSSR>
 | 
			
		||||
  <FreeTextLayout>
 | 
			
		||||
    <h1>Notifications</h1>
 | 
			
		||||
 | 
			
		||||
    <p>Your notifications will appear here when logged in.</p>
 | 
			
		||||
  </FreeTextLayout>
 | 
			
		||||
</HiddenFromSSR>
 | 
			
		||||
{/if}
 | 
			
		||||
<script>
 | 
			
		||||
  import FreeTextLayout from '../../_components/FreeTextLayout.html'
 | 
			
		||||
  import { store } from '../../_store/store.js'
 | 
			
		||||
  import HiddenFromSSR from '../../_components/HiddenFromSSR'
 | 
			
		||||
  import TimelinePage from '../../_components/TimelinePage.html'
 | 
			
		||||
  import NotificationFilters from '../../_components/NotificationFilters.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    components: {
 | 
			
		||||
      FreeTextLayout,
 | 
			
		||||
      HiddenFromSSR,
 | 
			
		||||
      TimelinePage,
 | 
			
		||||
      NotificationFilters
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										29
									
								
								src/routes/_pages/notifications/mentions.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/routes/_pages/notifications/mentions.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
{#if $isUserLoggedIn}
 | 
			
		||||
  <NotificationFilters filter="mentions" />
 | 
			
		||||
  <TimelinePage timeline="notifications/mentions" />
 | 
			
		||||
{:else}
 | 
			
		||||
<HiddenFromSSR>
 | 
			
		||||
  <FreeTextLayout>
 | 
			
		||||
    <h1>Notification mentions</h1>
 | 
			
		||||
 | 
			
		||||
    <p>Your notification mentions will appear here when logged in.</p>
 | 
			
		||||
  </FreeTextLayout>
 | 
			
		||||
</HiddenFromSSR>
 | 
			
		||||
{/if}
 | 
			
		||||
<script>
 | 
			
		||||
  import FreeTextLayout from '../../_components/FreeTextLayout.html'
 | 
			
		||||
  import { store } from '../../_store/store.js'
 | 
			
		||||
  import HiddenFromSSR from '../../_components/HiddenFromSSR'
 | 
			
		||||
  import TimelinePage from '../../_components/TimelinePage.html'
 | 
			
		||||
  import NotificationFilters from '../../_components/NotificationFilters.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    components: {
 | 
			
		||||
      FreeTextLayout,
 | 
			
		||||
      HiddenFromSSR,
 | 
			
		||||
      TimelinePage,
 | 
			
		||||
      NotificationFilters
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -74,11 +74,16 @@ export async function del (url, headers, options) {
 | 
			
		|||
 | 
			
		||||
export function paramsString (paramsObject) {
 | 
			
		||||
  let res = ''
 | 
			
		||||
  Object.keys(paramsObject).forEach((key, i) => {
 | 
			
		||||
    if (i > 0) {
 | 
			
		||||
      res += '&'
 | 
			
		||||
  let count = -1
 | 
			
		||||
  Object.keys(paramsObject).forEach(key => {
 | 
			
		||||
    let value = paramsObject[key]
 | 
			
		||||
    if (Array.isArray(value)) { // rails convention for encoding multiple values
 | 
			
		||||
      for (let item of value) {
 | 
			
		||||
        res += (++count > 0 ? '&' : '') + encodeURIComponent(key) + '[]=' + encodeURIComponent(item)
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      res += (++count > 0 ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value)
 | 
			
		||||
    }
 | 
			
		||||
    res += encodeURIComponent(key) + '=' + encodeURIComponent(paramsObject[key])
 | 
			
		||||
  })
 | 
			
		||||
  return res
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,9 +3,9 @@
 | 
			
		|||
  <LazyPage {pageComponent} {params} />
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  import Title from './_components/Title.html'
 | 
			
		||||
  import LazyPage from './_components/LazyPage.html'
 | 
			
		||||
  import pageComponent from './_pages/notifications.html'
 | 
			
		||||
  import Title from '../_components/Title.html'
 | 
			
		||||
  import LazyPage from '../_components/LazyPage.html'
 | 
			
		||||
  import pageComponent from '../_pages/notifications/index.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    components: {
 | 
			
		||||
							
								
								
									
										20
									
								
								src/routes/notifications/mentions.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/routes/notifications/mentions.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
<Title name="Notifications" />
 | 
			
		||||
 | 
			
		||||
  <LazyPage {pageComponent} {params} />
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  import Title from '../_components/Title.html'
 | 
			
		||||
  import LazyPage from '../_components/LazyPage.html'
 | 
			
		||||
  import pageComponent from '../_pages/notifications/mentions.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    components: {
 | 
			
		||||
 | 
			
		||||
      Title,
 | 
			
		||||
      LazyPage
 | 
			
		||||
    },
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      pageComponent
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +44,13 @@ export const notifications = [
 | 
			
		|||
  { followedBy: 'admin' }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export const notificationsMentions = [
 | 
			
		||||
  { content: 'notification of unlisted message' },
 | 
			
		||||
  { content: 'notification of followers-only message' },
 | 
			
		||||
  { content: 'notification of direct message' },
 | 
			
		||||
  { content: 'hello foobar' }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export const favorites = [
 | 
			
		||||
  { content: 'notification of direct message' },
 | 
			
		||||
  { content: 'notification of followers-only message' },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								tests/spec/033-notification-filters.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/spec/033-notification-filters.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
import {
 | 
			
		||||
  getUrl, notificationFiltersAll, notificationFiltersMention,
 | 
			
		||||
  notificationsNavButton, validateTimeline
 | 
			
		||||
} from '../utils'
 | 
			
		||||
import { loginAsFoobar } from '../roles'
 | 
			
		||||
import { notificationsMentions, notifications } from '../fixtures'
 | 
			
		||||
 | 
			
		||||
fixture`033-notification-filters.js`
 | 
			
		||||
  .page`http://localhost:4002`
 | 
			
		||||
 | 
			
		||||
test('Shows notification filters', async t => {
 | 
			
		||||
  await loginAsFoobar(t)
 | 
			
		||||
  await t
 | 
			
		||||
    .click(notificationsNavButton)
 | 
			
		||||
    .expect(getUrl()).match(/\/notifications$/)
 | 
			
		||||
    .click(notificationFiltersMention)
 | 
			
		||||
    .expect(getUrl()).match(/\/notifications\/mentions$/)
 | 
			
		||||
  await validateTimeline(t, notificationsMentions)
 | 
			
		||||
  await t.click(notificationFiltersAll)
 | 
			
		||||
    .expect(getUrl()).match(/\/notifications$/)
 | 
			
		||||
  await validateTimeline(t, notifications)
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										65
									
								
								tests/spec/123-notification-filters.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								tests/spec/123-notification-filters.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
import {
 | 
			
		||||
  getNthStatusContent,
 | 
			
		||||
  getUrl, notificationFiltersAll, notificationFiltersMention,
 | 
			
		||||
  notificationsNavButton, sleep
 | 
			
		||||
} from '../utils'
 | 
			
		||||
import { loginAsFoobar } from '../roles'
 | 
			
		||||
import { favoriteStatusAs, postAs } from '../serverActions'
 | 
			
		||||
 | 
			
		||||
fixture`123-notification-filters.js`
 | 
			
		||||
  .page`http://localhost:4002`
 | 
			
		||||
 | 
			
		||||
// maybe in the "mentions" view it should prevent the notification icon from showing (1), (2) etc
 | 
			
		||||
// if those particular notifications were seen by the user... but this is too hard to implement,
 | 
			
		||||
// so I'm going to punt on it. Only the "all" view affects those (1) / (2) / etc badges.
 | 
			
		||||
test('Handles incoming notifications that are mentions', async t => {
 | 
			
		||||
  const timeout = 20000
 | 
			
		||||
  await loginAsFoobar(t)
 | 
			
		||||
  await t
 | 
			
		||||
    .click(notificationsNavButton)
 | 
			
		||||
    .expect(getUrl()).match(/\/notifications$/)
 | 
			
		||||
    .click(notificationFiltersMention)
 | 
			
		||||
    .expect(getUrl()).match(/\/notifications\/mentions$/)
 | 
			
		||||
  await sleep(2000)
 | 
			
		||||
  await postAs('admin', 'hey @foobar I am mentioning you')
 | 
			
		||||
  await t
 | 
			
		||||
    .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page) (1 notification)', {
 | 
			
		||||
      timeout
 | 
			
		||||
    })
 | 
			
		||||
    .expect(getNthStatusContent(1).innerText).contains('hey @foobar I am mentioning you')
 | 
			
		||||
    .click(notificationFiltersAll)
 | 
			
		||||
    .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page)', { timeout })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Handles incoming notifications that are not mentions', async t => {
 | 
			
		||||
  const timeout = 20000
 | 
			
		||||
  let { id: statusId } = await postAs('foobar', 'this is a post that I hope somebody will favorite')
 | 
			
		||||
  await sleep(2000)
 | 
			
		||||
  await loginAsFoobar(t)
 | 
			
		||||
  await t
 | 
			
		||||
    .click(notificationsNavButton)
 | 
			
		||||
    .expect(getUrl()).match(/\/notifications$/)
 | 
			
		||||
    .click(notificationFiltersMention)
 | 
			
		||||
    .expect(getUrl()).match(/\/notifications\/mentions$/)
 | 
			
		||||
  await sleep(2000)
 | 
			
		||||
  await postAs('admin', 'woot I am mentioning you again @foobar')
 | 
			
		||||
  await t
 | 
			
		||||
    .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page) (1 notification)', {
 | 
			
		||||
      timeout
 | 
			
		||||
    })
 | 
			
		||||
    .expect(getNthStatusContent(1).innerText).contains('woot I am mentioning you again @foobar')
 | 
			
		||||
  await sleep(2000)
 | 
			
		||||
  await favoriteStatusAs('admin', statusId)
 | 
			
		||||
  await t
 | 
			
		||||
    .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page) (2 notifications)', {
 | 
			
		||||
      timeout
 | 
			
		||||
    })
 | 
			
		||||
  await sleep(2000)
 | 
			
		||||
  await t
 | 
			
		||||
    .expect(getNthStatusContent(1).innerText).contains('woot I am mentioning you again @foobar')
 | 
			
		||||
    .click(notificationFiltersAll)
 | 
			
		||||
    .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page)', { timeout })
 | 
			
		||||
  await t
 | 
			
		||||
    .expect(getNthStatusContent(1).innerText).contains('this is a post that I hope somebody will favorite')
 | 
			
		||||
    .expect(getNthStatusContent(2).innerText).contains('woot I am mentioning you again @foobar')
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +64,9 @@ export const accountProfileFilterStatuses = $('.account-profile-filters li:nth-c
 | 
			
		|||
export const accountProfileFilterStatusesAndReplies = $('.account-profile-filters li:nth-child(2)')
 | 
			
		||||
export const accountProfileFilterMedia = $('.account-profile-filters li:nth-child(3)')
 | 
			
		||||
 | 
			
		||||
export const notificationFiltersAll = $('.notification-filters li:nth-child(1)')
 | 
			
		||||
export const notificationFiltersMention = $('.notification-filters li:nth-child(2)')
 | 
			
		||||
 | 
			
		||||
export function getComposeModalNthMediaAltInput (n) {
 | 
			
		||||
  return $(`.modal-dialog .compose-media:nth-child(${n}) .compose-media-alt input`)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue