forked from cybrespace/pinafore
		
	immediately add replies to threads
This commit is contained in:
		
							parent
							
								
									6f1903fec5
								
							
						
					
					
						commit
						7813cf99ed
					
				
					 9 changed files with 129 additions and 22 deletions
				
			
		| 
						 | 
				
			
			@ -20,13 +20,7 @@ async function removeDuplicates (instanceName, timelineName, updates) {
 | 
			
		|||
  return updates.filter(update => !existingItemIds.has(update.id))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function processFreshUpdates (instanceName, timelineName) {
 | 
			
		||||
  mark('processFreshUpdates')
 | 
			
		||||
  let freshUpdates = store.getForTimeline(instanceName, timelineName, 'freshUpdates')
 | 
			
		||||
  if (freshUpdates && freshUpdates.length) {
 | 
			
		||||
    let updates = freshUpdates.slice()
 | 
			
		||||
    store.setForTimeline(instanceName, timelineName, {freshUpdates: []})
 | 
			
		||||
 | 
			
		||||
async function insertUpdatesIntoTimeline (instanceName, timelineName, updates) {
 | 
			
		||||
  updates = await removeDuplicates(instanceName, timelineName, updates)
 | 
			
		||||
 | 
			
		||||
  await database.insertTimelineItems(instanceName, timelineName, updates)
 | 
			
		||||
| 
						 | 
				
			
			@ -37,8 +31,43 @@ async function processFreshUpdates (instanceName, timelineName) {
 | 
			
		|||
    console.log('adding ', itemIdsToAdd.length, 'items to itemIdsToAdd')
 | 
			
		||||
    store.setForTimeline(instanceName, timelineName, {itemIdsToAdd: itemIdsToAdd})
 | 
			
		||||
  }
 | 
			
		||||
    stop('processFreshUpdates')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function insertUpdatesIntoThreads (instanceName, updates) {
 | 
			
		||||
  if (!updates.length) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let threads = store.getThreadsForTimeline(instanceName)
 | 
			
		||||
 | 
			
		||||
  for (let timelineName of Object.keys(threads)) {
 | 
			
		||||
    let thread = threads[timelineName]
 | 
			
		||||
    let updatesForThisThread = updates.filter(status => {
 | 
			
		||||
      return thread.includes(status.in_reply_to_id) && !thread.includes(status.id)
 | 
			
		||||
    })
 | 
			
		||||
    let itemIdsToAdd = store.getForTimeline(instanceName, timelineName, 'itemIdsToAdd') || []
 | 
			
		||||
    for (let update of updatesForThisThread) {
 | 
			
		||||
      if (!itemIdsToAdd.includes(update.id)) {
 | 
			
		||||
        itemIdsToAdd.push(update.id)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    console.log('adding ', itemIdsToAdd.length, 'items to itemIdsToAdd for thread', timelineName)
 | 
			
		||||
    store.setForTimeline(instanceName, timelineName, {itemIdsToAdd: itemIdsToAdd})
 | 
			
		||||
    console.log('timelineName', timelineName, 'itemIdsToAdd', itemIdsToAdd)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function processFreshUpdates (instanceName, timelineName) {
 | 
			
		||||
  mark('processFreshUpdates')
 | 
			
		||||
  let freshUpdates = store.getForTimeline(instanceName, timelineName, 'freshUpdates')
 | 
			
		||||
  if (freshUpdates && freshUpdates.length) {
 | 
			
		||||
    let updates = freshUpdates.slice()
 | 
			
		||||
    store.setForTimeline(instanceName, timelineName, {freshUpdates: []})
 | 
			
		||||
 | 
			
		||||
    await insertUpdatesIntoTimeline(instanceName, timelineName, updates)
 | 
			
		||||
    await insertUpdatesIntoThreads(instanceName, updates.filter(status => status.in_reply_to_id))
 | 
			
		||||
  }
 | 
			
		||||
  stop('processFreshUpdates')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const lazilyProcessFreshUpdates = throttle((instanceName, timelineName) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
import { store } from '../_store/store'
 | 
			
		||||
import { toast } from '../_utils/toast'
 | 
			
		||||
import { postStatusToServer } from '../_api/statuses'
 | 
			
		||||
import { postStatus as postStatusToServer } from '../_api/statuses'
 | 
			
		||||
import { addStatusOrNotification } from './addStatusOrNotification'
 | 
			
		||||
import { database } from '../_database/database'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,3 +95,18 @@ export async function showMoreItemsForCurrentTimeline () {
 | 
			
		|||
  })
 | 
			
		||||
  stop('showMoreItemsForCurrentTimeline')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function showMoreItemsForCurrentThread () {
 | 
			
		||||
  mark('showMoreItemsForCurrentThread')
 | 
			
		||||
  let instanceName = store.get('currentInstance')
 | 
			
		||||
  let timelineName = store.get('currentTimeline')
 | 
			
		||||
  let itemIdsToAdd = store.get('itemIdsToAdd')
 | 
			
		||||
  // TODO: update database and do this merge correctly
 | 
			
		||||
  let timelineItemIds = store.getForTimeline(instanceName, timelineName, 'timelineItemIds')
 | 
			
		||||
  timelineItemIds = timelineItemIds.concat(itemIdsToAdd)
 | 
			
		||||
  store.setForTimeline(instanceName, timelineName, {
 | 
			
		||||
    itemIdsToAdd: [],
 | 
			
		||||
    timelineItemIds: timelineItemIds
 | 
			
		||||
  })
 | 
			
		||||
  stop('showMoreItemsForCurrentThread')
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,7 +66,6 @@
 | 
			
		|||
      if (realm !== 'home') {
 | 
			
		||||
        // if this is a reply, populate the handle immediately
 | 
			
		||||
        insertHandleForReply(realm)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
        // if this is a reply, go back immediately after it's posted
 | 
			
		||||
        this.observe('postedStatusForRealm', postedStatusForRealm => {
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +73,7 @@
 | 
			
		|||
            window.history.back()
 | 
			
		||||
          }
 | 
			
		||||
        }, {init: false})
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
      ComposeAuthor,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -60,7 +60,12 @@
 | 
			
		|||
  import VirtualList from '../virtualList/VirtualList.html'
 | 
			
		||||
  import { timelines } from '../../_static/timelines'
 | 
			
		||||
  import { database } from '../../_database/database'
 | 
			
		||||
  import { initializeTimeline, fetchTimelineItemsOnScrollToBottom, setupTimeline } from '../../_actions/timeline'
 | 
			
		||||
  import {
 | 
			
		||||
    initializeTimeline,
 | 
			
		||||
    fetchTimelineItemsOnScrollToBottom,
 | 
			
		||||
    setupTimeline,
 | 
			
		||||
    showMoreItemsForCurrentThread
 | 
			
		||||
  } from '../../_actions/timeline'
 | 
			
		||||
  import LoadingPage from '../LoadingPage.html'
 | 
			
		||||
  import { focusWithCapture, blurWithCapture } from '../../_utils/events'
 | 
			
		||||
  import { showMoreItemsForCurrentTimeline } from '../../_actions/timeline'
 | 
			
		||||
| 
						 | 
				
			
			@ -195,7 +200,10 @@
 | 
			
		|||
          let scrollTop = this.get('scrollTop')
 | 
			
		||||
          let shouldShowHeader = this.store.get('shouldShowHeader')
 | 
			
		||||
          let showHeader = this.store.get('showHeader')
 | 
			
		||||
          if (scrollTop === 0 && !shouldShowHeader && !showHeader) {
 | 
			
		||||
          if (timelineName.startsWith('status/')) {
 | 
			
		||||
            // this is a thread, just insert the statuses already
 | 
			
		||||
            showMoreItemsForCurrentThread()
 | 
			
		||||
          } else if (scrollTop === 0 && !shouldShowHeader && !showHeader) {
 | 
			
		||||
            // if the user is scrolled to the top and we're not showing the header, then
 | 
			
		||||
            // just insert the statuses. this is "chat room mode"
 | 
			
		||||
            showMoreItemsForCurrentTimeline()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
import pickBy from 'lodash/pickBy'
 | 
			
		||||
 | 
			
		||||
export function timelineMixins (Store) {
 | 
			
		||||
  Store.prototype.setForTimeline = function (instanceName, timelineName, obj) {
 | 
			
		||||
    let valuesToSet = {}
 | 
			
		||||
| 
						 | 
				
			
			@ -23,4 +25,18 @@ export function timelineMixins (Store) {
 | 
			
		|||
    let timelineName = this.get('currentTimeline')
 | 
			
		||||
    this.setForTimeline(instanceName, timelineName, obj)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Store.prototype.getThreadsForTimeline = function (instanceName) {
 | 
			
		||||
    let root = this.get('timelineData_timelineItemIds') || {}
 | 
			
		||||
    let instanceData = root[instanceName] = root[instanceName] || {}
 | 
			
		||||
 | 
			
		||||
    return pickBy(instanceData, (value, key) => {
 | 
			
		||||
      return key.startsWith('status/')
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Store.prototype.getThreadsForCurrentTimeline = function () {
 | 
			
		||||
    let instanceName = this.get('currentInstance')
 | 
			
		||||
    return this.getThreadsForTimeline(instanceName)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,7 @@ test('account handle populated correctly for replies', async t => {
 | 
			
		|||
    .expect(composeInput.value).eql('')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('replying to posts wth mentions', async t => {
 | 
			
		||||
test('replying to posts with mentions', async t => {
 | 
			
		||||
  await t.useRole(foobarRole)
 | 
			
		||||
    .click(getNthReplyButton(1))
 | 
			
		||||
    .expect(getUrl()).contains('/statuses')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								tests/spec/103-compose.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								tests/spec/103-compose.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
import { foobarRole } from '../roles'
 | 
			
		||||
import {
 | 
			
		||||
  composeInput, getNthReplyButton, getNthStatus, getUrl, homeNavButton, notificationsNavButton,
 | 
			
		||||
  postStatusButton
 | 
			
		||||
} from '../utils'
 | 
			
		||||
 | 
			
		||||
fixture`103-compose.js`
 | 
			
		||||
  .page`http://localhost:4002`
 | 
			
		||||
 | 
			
		||||
test('statuses show up in home timeline', async t => {
 | 
			
		||||
  await t.useRole(foobarRole)
 | 
			
		||||
    .typeText(composeInput, 'hello world', {paste: true})
 | 
			
		||||
    .click(postStatusButton)
 | 
			
		||||
    .expect(getNthStatus(0).innerText).contains('hello world')
 | 
			
		||||
    .click(notificationsNavButton)
 | 
			
		||||
    .expect(getUrl()).contains('/notifications')
 | 
			
		||||
    .click(homeNavButton)
 | 
			
		||||
    .expect(getUrl()).eql('http://localhost:4002/')
 | 
			
		||||
    .expect(getNthStatus(0).innerText).contains('hello world')
 | 
			
		||||
    .navigateTo('/')
 | 
			
		||||
    .expect(getNthStatus(0).innerText).contains('hello world')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('statuses in threads show up in right order', async t => {
 | 
			
		||||
  await t.useRole(foobarRole)
 | 
			
		||||
    .navigateTo('/accounts/5')
 | 
			
		||||
    .click(getNthStatus(2))
 | 
			
		||||
    .expect(getUrl()).contains('/statuses')
 | 
			
		||||
    .click(getNthReplyButton(3))
 | 
			
		||||
    .expect(getUrl()).contains('/reply')
 | 
			
		||||
    .typeText(composeInput, 'my reply!', {paste: true})
 | 
			
		||||
    .click(postStatusButton)
 | 
			
		||||
    .expect(getUrl()).match(/statuses\/[^/]+$/)
 | 
			
		||||
    .expect(getNthStatus(5).innerText).contains('@baz my reply!')
 | 
			
		||||
    .navigateTo('/accounts/5')
 | 
			
		||||
    .click(getNthStatus(2))
 | 
			
		||||
    .expect(getNthStatus(5).innerText).contains('@baz my reply!')
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +25,7 @@ export const passwordInput = $('input#user_password')
 | 
			
		|||
export const authorizeInput = $('button[type=submit]:not(.negative)')
 | 
			
		||||
export const logInToInstanceLink = $('a[href="/settings/instances/add"]')
 | 
			
		||||
export const searchInput = $('.search-input')
 | 
			
		||||
export const postStatusButton = $('.compose-box-button')
 | 
			
		||||
 | 
			
		||||
export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({
 | 
			
		||||
  innerCount: el => parseInt(el.innerText, 10)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue