immediately add replies to threads
This commit is contained in:
		
							parent
							
								
									6f1903fec5
								
							
						
					
					
						commit
						7813cf99ed
					
				
					 9 changed files with 129 additions and 22 deletions
				
			
		|  | @ -20,6 +20,43 @@ async function removeDuplicates (instanceName, timelineName, updates) { | |||
|   return updates.filter(update => !existingItemIds.has(update.id)) | ||||
| } | ||||
| 
 | ||||
| async function insertUpdatesIntoTimeline (instanceName, timelineName, updates) { | ||||
|   updates = await removeDuplicates(instanceName, timelineName, updates) | ||||
| 
 | ||||
|   await database.insertTimelineItems(instanceName, timelineName, updates) | ||||
| 
 | ||||
|   let itemIdsToAdd = store.getForTimeline(instanceName, timelineName, 'itemIdsToAdd') || [] | ||||
|   if (updates && updates.length) { | ||||
|     itemIdsToAdd = itemIdsToAdd.concat(updates.map(_ => _.id)) | ||||
|     console.log('adding ', itemIdsToAdd.length, 'items to itemIdsToAdd') | ||||
|     store.setForTimeline(instanceName, timelineName, {itemIdsToAdd: itemIdsToAdd}) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 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') | ||||
|  | @ -27,18 +64,10 @@ async function processFreshUpdates (instanceName, timelineName) { | |||
|     let updates = freshUpdates.slice() | ||||
|     store.setForTimeline(instanceName, timelineName, {freshUpdates: []}) | ||||
| 
 | ||||
|     updates = await removeDuplicates(instanceName, timelineName, updates) | ||||
| 
 | ||||
|     await database.insertTimelineItems(instanceName, timelineName, updates) | ||||
| 
 | ||||
|     let itemIdsToAdd = store.getForTimeline(instanceName, timelineName, 'itemIdsToAdd') || [] | ||||
|     if (updates && updates.length) { | ||||
|       itemIdsToAdd = itemIdsToAdd.concat(updates.map(_ => _.id)) | ||||
|       console.log('adding ', itemIdsToAdd.length, 'items to itemIdsToAdd') | ||||
|       store.setForTimeline(instanceName, timelineName, {itemIdsToAdd: itemIdsToAdd}) | ||||
|     } | ||||
|     stop('processFreshUpdates') | ||||
|     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,14 +66,14 @@ | |||
|       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 => { | ||||
|         if (postedStatusForRealm === realm) { | ||||
|           window.history.back() | ||||
|         } | ||||
|       }, {init: false}) | ||||
|         // if this is a reply, go back immediately after it's posted | ||||
|         this.observe('postedStatusForRealm', postedStatusForRealm => { | ||||
|           if (postedStatusForRealm === realm) { | ||||
|             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