immediately add replies to threads

This commit is contained in:
Nolan Lawson 2018-03-09 22:31:26 -08:00
parent 6f1903fec5
commit 7813cf99ed
9 changed files with 129 additions and 22 deletions

View file

@ -20,6 +20,43 @@ async function removeDuplicates (instanceName, timelineName, updates) {
return updates.filter(update => !existingItemIds.has(update.id)) 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) { async function processFreshUpdates (instanceName, timelineName) {
mark('processFreshUpdates') mark('processFreshUpdates')
let freshUpdates = store.getForTimeline(instanceName, timelineName, 'freshUpdates') let freshUpdates = store.getForTimeline(instanceName, timelineName, 'freshUpdates')
@ -27,18 +64,10 @@ async function processFreshUpdates (instanceName, timelineName) {
let updates = freshUpdates.slice() let updates = freshUpdates.slice()
store.setForTimeline(instanceName, timelineName, {freshUpdates: []}) store.setForTimeline(instanceName, timelineName, {freshUpdates: []})
updates = await removeDuplicates(instanceName, timelineName, updates) await insertUpdatesIntoTimeline(instanceName, timelineName, updates)
await insertUpdatesIntoThreads(instanceName, updates.filter(status => status.in_reply_to_id))
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')
} }
stop('processFreshUpdates')
} }
const lazilyProcessFreshUpdates = throttle((instanceName, timelineName) => { const lazilyProcessFreshUpdates = throttle((instanceName, timelineName) => {

View file

@ -1,6 +1,6 @@
import { store } from '../_store/store' import { store } from '../_store/store'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
import { postStatusToServer } from '../_api/statuses' import { postStatus as postStatusToServer } from '../_api/statuses'
import { addStatusOrNotification } from './addStatusOrNotification' import { addStatusOrNotification } from './addStatusOrNotification'
import { database } from '../_database/database' import { database } from '../_database/database'

View file

@ -95,3 +95,18 @@ export async function showMoreItemsForCurrentTimeline () {
}) })
stop('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')
}

View file

@ -66,14 +66,14 @@
if (realm !== 'home') { if (realm !== 'home') {
// if this is a reply, populate the handle immediately // if this is a reply, populate the handle immediately
insertHandleForReply(realm) insertHandleForReply(realm)
}
// if this is a reply, go back immediately after it's posted // if this is a reply, go back immediately after it's posted
this.observe('postedStatusForRealm', postedStatusForRealm => { this.observe('postedStatusForRealm', postedStatusForRealm => {
if (postedStatusForRealm === realm) { if (postedStatusForRealm === realm) {
window.history.back() window.history.back()
} }
}, {init: false}) }, {init: false})
}
}, },
components: { components: {
ComposeAuthor, ComposeAuthor,

View file

@ -60,7 +60,12 @@
import VirtualList from '../virtualList/VirtualList.html' import VirtualList from '../virtualList/VirtualList.html'
import { timelines } from '../../_static/timelines' import { timelines } from '../../_static/timelines'
import { database } from '../../_database/database' 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 LoadingPage from '../LoadingPage.html'
import { focusWithCapture, blurWithCapture } from '../../_utils/events' import { focusWithCapture, blurWithCapture } from '../../_utils/events'
import { showMoreItemsForCurrentTimeline } from '../../_actions/timeline' import { showMoreItemsForCurrentTimeline } from '../../_actions/timeline'
@ -195,7 +200,10 @@
let scrollTop = this.get('scrollTop') let scrollTop = this.get('scrollTop')
let shouldShowHeader = this.store.get('shouldShowHeader') let shouldShowHeader = this.store.get('shouldShowHeader')
let showHeader = this.store.get('showHeader') 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 // 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" // just insert the statuses. this is "chat room mode"
showMoreItemsForCurrentTimeline() showMoreItemsForCurrentTimeline()

View file

@ -1,3 +1,5 @@
import pickBy from 'lodash/pickBy'
export function timelineMixins (Store) { export function timelineMixins (Store) {
Store.prototype.setForTimeline = function (instanceName, timelineName, obj) { Store.prototype.setForTimeline = function (instanceName, timelineName, obj) {
let valuesToSet = {} let valuesToSet = {}
@ -23,4 +25,18 @@ export function timelineMixins (Store) {
let timelineName = this.get('currentTimeline') let timelineName = this.get('currentTimeline')
this.setForTimeline(instanceName, timelineName, obj) 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)
}
} }

View file

@ -31,7 +31,7 @@ test('account handle populated correctly for replies', async t => {
.expect(composeInput.value).eql('') .expect(composeInput.value).eql('')
}) })
test('replying to posts wth mentions', async t => { test('replying to posts with mentions', async t => {
await t.useRole(foobarRole) await t.useRole(foobarRole)
.click(getNthReplyButton(1)) .click(getNthReplyButton(1))
.expect(getUrl()).contains('/statuses') .expect(getUrl()).contains('/statuses')

38
tests/spec/103-compose.js Normal file
View 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!')
})

View file

@ -25,6 +25,7 @@ export const passwordInput = $('input#user_password')
export const authorizeInput = $('button[type=submit]:not(.negative)') export const authorizeInput = $('button[type=submit]:not(.negative)')
export const logInToInstanceLink = $('a[href="/settings/instances/add"]') export const logInToInstanceLink = $('a[href="/settings/instances/add"]')
export const searchInput = $('.search-input') export const searchInput = $('.search-input')
export const postStatusButton = $('.compose-box-button')
export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({ export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({
innerCount: el => parseInt(el.innerText, 10) innerCount: el => parseInt(el.innerText, 10)