immediately add replies to threads
This commit is contained in:
parent
6f1903fec5
commit
7813cf99ed
|
@ -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) => {
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
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 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)
|
||||||
|
|
Loading…
Reference in a new issue