Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85629649
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
15 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index c088fa1a06..510f9dbb16 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -1,215 +1,238 @@
-import { map, slice, sortBy, toInteger, each, find, flatten, maxBy, last, merge, max } from 'lodash'
+import { remove, map, slice, sortBy, toInteger, each, find, flatten, maxBy, last, merge, max } from 'lodash'
import moment from 'moment'
import apiService from '../services/api/api.service.js'
// import parse from '../services/status_parser/status_parser.js'
export const defaultState = {
allStatuses: [],
maxId: 0,
timelines: {
public: {
statuses: [],
faves: [],
visibleStatuses: [],
newStatusCount: 0,
maxId: 0,
minVisibleId: 0,
loading: false
},
publicAndExternal: {
statuses: [],
faves: [],
visibleStatuses: [],
newStatusCount: 0,
maxId: 0,
minVisibleId: 0,
loading: false
},
friends: {
statuses: [],
faves: [],
visibleStatuses: [],
newStatusCount: 0,
maxId: 0,
minVisibleId: 0,
loading: false
}
}
}
export const prepareStatus = (status) => {
// Parse nsfw tags
if (status.nsfw === undefined) {
const nsfwRegex = /#nsfw/i
status.nsfw = !!status.text.match(nsfwRegex)
}
// Set created_at_parsed to initial value
status.created_at_parsed = status.created_at
return status
}
const updateTimestampsInStatuses = (statuses) => {
return map(statuses, (statusoid) => {
const status = statusoid.retweeted_status || statusoid
// Parse date
status.created_at_parsed = moment(status.created_at).fromNow()
return status
})
}
const statusType = (status) => {
if (status.is_post_verb) {
return 'status'
}
if (status.retweeted_status) {
return 'retweet'
}
if (typeof status.uri === 'string' && status.uri.match(/fave/)) {
return 'favorite'
}
+ if (status.text.match(/deleted notice {{tag/)) {
+ return 'deletion'
+ }
+
return 'unknown'
}
export const findMaxId = (...args) => {
return (maxBy(flatten(args), 'id') || {}).id
}
const mergeOrAdd = (arr, item) => {
const oldItem = find(arr, {id: item.id})
if (oldItem) {
// We already have this, so only merge the new info.
merge(oldItem, item)
return oldItem
} else {
// This is a new item, prepare it
prepareStatus(item)
arr.push(item)
return item
}
}
export const mutations = {
addNewStatuses (state, { statuses, showImmediately = false, timeline }) {
const allStatuses = state.allStatuses
const timelineObject = state.timelines[timeline]
// Set the maxId to the new id if it's larger.
const updateMaxId = ({id}) => {
timelineObject.maxId = max([id, timelineObject.maxId])
}
const addStatus = (status, showImmediately, addToTimeline = true) => {
// Remember the current amount of statuses
// We need that to calculate new status count.
const prevLength = timelineObject.statuses.length
updateMaxId(status)
status = mergeOrAdd(allStatuses, status)
// Some statuses should only be added to the global status repository.
if (addToTimeline) {
mergeOrAdd(timelineObject.statuses, status)
}
if (showImmediately) {
// Add it directly to the visibleStatuses, don't change
// newStatusCount
mergeOrAdd(timelineObject.visibleStatuses, status)
} else {
// Just change newStatuscount
timelineObject.newStatusCount += (timelineObject.statuses.length - prevLength)
}
return status
}
const favoriteStatus = (favorite) => {
const status = find(allStatuses, { id: toInteger(favorite.in_reply_to_status_id) })
if (status) {
status.fave_num += 1
}
return status
}
const processors = {
'status': (status) => {
addStatus(status, showImmediately)
},
'retweet': (status) => {
// RetweetedStatuses are never shown immediately
const retweetedStatus = addStatus(status.retweeted_status, false, false)
- const retweet = addStatus(status, showImmediately)
+
+ let retweet
+ // If the retweeted status is already there, don't add the retweet
+ // to the timeline.
+ if (find(timelineObject.visibleStatuses, {id: retweetedStatus.id})) {
+ // Already have it visible, don't add to timeline, don't show.
+ retweet = addStatus(status, false, false)
+ } else {
+ retweet = addStatus(status, showImmediately)
+ }
+
retweet.retweeted_status = retweetedStatus
},
- 'favorite': (status) => {
- updateMaxId(status)
- favoriteStatus(status)
+ 'favorite': (favorite) => {
+ updateMaxId(favorite)
+ favoriteStatus(favorite)
+ },
+ 'deletion': ({uri}) => {
+ remove(allStatuses, { tag: uri })
+ remove(timelineObject.statuses, { tag: uri })
+ remove(timelineObject.visibleStatuses, { tag: uri })
+ },
+ 'default': (unknown) => {
+ console.log(unknown)
}
}
each(statuses, (status) => {
const type = statusType(status)
- processors[type](status)
+ const processor = processors[type] || processors['default']
+ processor(status)
})
// Keep the visible statuses sorted
timelineObject.visibleStatuses = sortBy(timelineObject.visibleStatuses, ({id}) => -id)
timelineObject.statuses = sortBy(timelineObject.statuses, ({id}) => -id)
timelineObject.minVisibleId = (last(timelineObject.statuses) || {}).id
},
showNewStatuses (state, { timeline }) {
const oldTimeline = (state.timelines[timeline])
oldTimeline.newStatusCount = 0
oldTimeline.visibleStatuses = slice(oldTimeline.statuses, 0, 50)
},
updateTimestamps (state) {
updateTimestampsInStatuses(state.allStatuses)
},
setFavorited (state, { status, value }) {
const newStatus = find(state.allStatuses, status)
newStatus.favorited = value
},
setRetweeted (state, { status, value }) {
const newStatus = find(state.allStatuses, status)
newStatus.repeated = value
},
setLoading (state, { timeline, value }) {
state.timelines[timeline].loading = value
},
setNsfw (state, { id, nsfw }) {
const newStatus = find(state.allStatuses, { id })
newStatus.nsfw = nsfw
}
}
const statuses = {
state: defaultState,
actions: {
favorite ({ rootState, commit }, status) {
// Optimistic favoriting...
commit('setFavorited', { status, value: true })
apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
},
unfavorite ({ rootState, commit }, status) {
// Optimistic favoriting...
commit('setFavorited', { status, value: false })
apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
},
retweet ({ rootState, commit }, status) {
// Optimistic retweeting...
commit('setRetweeted', { status, value: true })
apiService.retweet({ id: status.id, credentials: rootState.users.currentUser.credentials })
}
},
mutations
}
export default statuses
diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js
index 80bb5fc681..d6e1dc2027 100644
--- a/test/unit/specs/modules/statuses.spec.js
+++ b/test/unit/specs/modules/statuses.spec.js
@@ -1,178 +1,196 @@
import { cloneDeep } from 'lodash'
import { defaultState, mutations, findMaxId, prepareStatus } from '../../../../src/modules/statuses.js'
const makeMockStatus = ({id, text, is_post_verb = true}) => {
return {
id,
name: 'status',
text: text || `Text number ${id}`,
fave_num: 0,
uri: '',
is_post_verb
}
}
describe('Statuses.prepareStatus', () => {
it('sets nsfw for statuses with the #nsfw tag', () => {
const safe = makeMockStatus({id: 1, text: 'Hello oniichan'})
const nsfw = makeMockStatus({id: 1, text: 'Hello oniichan #nsfw'})
expect(prepareStatus(safe).nsfw).to.eq(false)
expect(prepareStatus(nsfw).nsfw).to.eq(true)
})
it('leaves existing nsfw settings alone', () => {
const nsfw = makeMockStatus({id: 1, text: 'Hello oniichan #nsfw'})
nsfw.nsfw = false
expect(prepareStatus(nsfw).nsfw).to.eq(false)
})
it('sets the created_at_parsed property', () => {
const status = makeMockStatus({id: 1})
status.created_at = ''
expect(status.created_at_parsed).to.eq(undefined)
const prepared = prepareStatus(status)
expect(prepared.created_at_parsed).to.not.eq(undefined)
})
})
describe('Statuses.findMaxId', () => {
it('returns the largest id in any of the given arrays', () => {
const statusesOne = [{ id: 100 }, { id: 2 }]
const statusesTwo = [{ id: 3 }]
const maxId = findMaxId(statusesOne, statusesTwo)
expect(maxId).to.eq(100)
})
it('returns undefined for empty arrays', () => {
const maxId = findMaxId([], [])
expect(maxId).to.eq(undefined)
})
})
describe('The Statuses module', () => {
it('adds the status to allStatuses and to the given timeline', () => {
const state = cloneDeep(defaultState)
const status = makeMockStatus({id: 1})
mutations.addNewStatuses(state, { statuses: [status], timeline: 'public' })
expect(state.allStatuses).to.eql([status])
expect(state.timelines.public.statuses).to.eql([status])
expect(state.timelines.public.visibleStatuses).to.eql([])
expect(state.timelines.public.newStatusCount).to.equal(1)
})
it('adds the status to allStatuses and to the given timeline, directly visible', () => {
const state = cloneDeep(defaultState)
const status = makeMockStatus({id: 1})
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
expect(state.allStatuses).to.eql([status])
expect(state.timelines.public.statuses).to.eql([status])
expect(state.timelines.public.visibleStatuses).to.eql([status])
expect(state.timelines.public.newStatusCount).to.equal(0)
})
+ it('removes statuses by tag on deletion', () => {
+ const state = cloneDeep(defaultState)
+ const status = makeMockStatus({id: 1})
+ status.tag = 'xxx'
+ const deletion = makeMockStatus({id: 2, is_post_verb: false})
+ deletion.text = 'Dolus deleted notice {{tag:gs.smuglo.li,2016-11-18:noticeId=1038007:objectType=note}}.'
+ deletion.uri = 'xxx'
+
+ mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
+ mutations.addNewStatuses(state, { statuses: [deletion], showImmediately: true, timeline: 'public' })
+
+ expect(state.allStatuses).to.eql([])
+ expect(state.timelines.public.statuses).to.eql([])
+ expect(state.timelines.public.visibleStatuses).to.eql([])
+ })
+
it('keeps a descending by id order in timeline.visibleStatuses and timeline.statuses', () => {
const state = cloneDeep(defaultState)
const status = makeMockStatus({id: 2})
const statusTwo = makeMockStatus({id: 1})
const statusThree = makeMockStatus({id: 3})
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
mutations.addNewStatuses(state, { statuses: [statusTwo], showImmediately: true, timeline: 'public' })
expect(state.timelines.public.minVisibleId).to.equal(1)
mutations.addNewStatuses(state, { statuses: [statusThree], showImmediately: true, timeline: 'public' })
expect(state.timelines.public.statuses).to.eql([statusThree, status, statusTwo])
expect(state.timelines.public.visibleStatuses).to.eql([statusThree, status, statusTwo])
})
it('splits retweets from their status and links them', () => {
const state = cloneDeep(defaultState)
const status = makeMockStatus({id: 1})
const retweet = makeMockStatus({id: 2, is_post_verb: false})
const modStatus = makeMockStatus({id: 1, text: 'something else'})
retweet.retweeted_status = status
// It adds both statuses, but only the retweet to visible.
mutations.addNewStatuses(state, { statuses: [retweet], timeline: 'public', showImmediately: true })
expect(state.timelines.public.visibleStatuses).to.have.length(1)
expect(state.timelines.public.statuses).to.have.length(1)
expect(state.allStatuses).to.have.length(2)
expect(state.allStatuses[0].id).to.equal(1)
expect(state.allStatuses[1].id).to.equal(2)
// It refers to the modified status.
mutations.addNewStatuses(state, { statuses: [modStatus], timeline: 'public' })
expect(state.allStatuses).to.have.length(2)
expect(state.allStatuses[0].id).to.equal(1)
expect(state.allStatuses[0].text).to.equal(modStatus.text)
expect(state.allStatuses[1].id).to.equal(2)
expect(retweet.retweeted_status.text).to.eql(modStatus.text)
})
it('replaces existing statuses with the same id', () => {
const state = cloneDeep(defaultState)
const status = makeMockStatus({id: 1})
const modStatus = makeMockStatus({id: 1, text: 'something else'})
// Add original status
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
expect(state.timelines.public.visibleStatuses).to.have.length(1)
expect(state.allStatuses).to.have.length(1)
// Add new version of status
mutations.addNewStatuses(state, { statuses: [modStatus], showImmediately: true, timeline: 'public' })
expect(state.timelines.public.visibleStatuses).to.have.length(1)
expect(state.allStatuses).to.have.length(1)
expect(state.allStatuses[0].text).to.eql(modStatus.text)
})
it('replaces existing statuses with the same id, coming from a retweet', () => {
const state = cloneDeep(defaultState)
const status = makeMockStatus({id: 1})
const modStatus = makeMockStatus({id: 1, text: 'something else'})
const retweet = makeMockStatus({id: 2, is_post_verb: false})
retweet.retweeted_status = modStatus
// Add original status
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
expect(state.timelines.public.visibleStatuses).to.have.length(1)
expect(state.allStatuses).to.have.length(1)
// Add new version of status
mutations.addNewStatuses(state, { statuses: [retweet], showImmediately: false, timeline: 'public' })
expect(state.timelines.public.visibleStatuses).to.have.length(1)
+ // Don't add the retweet itself if the tweet is visible
+ expect(state.timelines.public.statuses).to.have.length(1)
expect(state.allStatuses).to.have.length(2)
expect(state.allStatuses[0].text).to.eql(modStatus.text)
})
it('handles favorite actions', () => {
const state = cloneDeep(defaultState)
const status = makeMockStatus({id: 1})
const favorite = {
id: 2,
is_post_verb: false,
in_reply_to_status_id: '1', // The API uses strings here...
uri: 'tag:shitposter.club,2016-08-21:fave:3895:note:773501:2016-08-21T16:52:15+00:00',
text: 'a favorited something by b'
}
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
mutations.addNewStatuses(state, { statuses: [favorite], showImmediately: true, timeline: 'public' })
expect(state.timelines.public.visibleStatuses.length).to.eql(1)
expect(state.timelines.public.visibleStatuses[0].fave_num).to.eql(1)
expect(state.timelines.public.maxId).to.eq(favorite.id)
})
})
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Aug 9, 2:24 AM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1724135
Default Alt Text
(15 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline
Log In to Comment