Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85710347
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
19 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index a19918884c..95525dc1a0 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -1,499 +1,501 @@
import { reduce, filter, findIndex, clone, get } from 'lodash'
import Status from '../status/status.vue'
import ThreadTree from '../thread_tree/thread_tree.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faAngleDoubleDown,
faAngleDoubleLeft,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
library.add(
faAngleDoubleDown,
faAngleDoubleLeft,
faChevronLeft
)
// const debug = console.log
const debug = () => {}
const sortById = (a, b) => {
const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id
const idB = b.type === 'retweet' ? b.retweeted_status.id : b.id
const seqA = Number(idA)
const seqB = Number(idB)
const isSeqA = !Number.isNaN(seqA)
const isSeqB = !Number.isNaN(seqB)
if (isSeqA && isSeqB) {
return seqA < seqB ? -1 : 1
} else if (isSeqA && !isSeqB) {
return -1
} else if (!isSeqA && isSeqB) {
return 1
} else {
return idA < idB ? -1 : 1
}
}
const sortAndFilterConversation = (conversation, statusoid) => {
if (statusoid.type === 'retweet') {
conversation = filter(
conversation,
(status) => (status.type === 'retweet' || status.id !== statusoid.retweeted_status.id)
)
} else {
conversation = filter(conversation, (status) => status.type !== 'retweet')
}
return conversation.filter(_ => _).sort(sortById)
}
const conversation = {
data () {
return {
highlight: null,
expanded: false,
threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
statusContentPropertiesObject: {},
diveHistory: []
}
},
props: [
'statusId',
'collapsable',
'isPage',
'pinnedStatusIdsObject',
'inProfile',
'profileUserId',
'virtualHidden'
],
created () {
if (this.isPage) {
this.fetchConversation()
}
},
computed: {
maxDepthToShowByDefault () {
return 4
},
displayStyle () {
return this.$store.state.config.conversationDisplay
},
isTreeView () {
return this.displayStyle === 'tree' || this.displayStyle === 'simple_tree'
},
treeViewIsSimple () {
return this.displayStyle === 'simple_tree'
},
isLinearView () {
return this.displayStyle === 'linear'
},
hideStatus () {
if (this.$refs.statusComponent && this.$refs.statusComponent[0]) {
return this.virtualHidden && this.$refs.statusComponent[0].suspendable
} else {
return this.virtualHidden
}
},
status () {
return this.$store.state.statuses.allStatusesObject[this.statusId]
},
originalStatusId () {
if (this.status.retweeted_status) {
return this.status.retweeted_status.id
} else {
return this.statusId
}
},
conversationId () {
return this.getConversationId(this.statusId)
},
conversation () {
if (!this.status) {
return []
}
if (!this.isExpanded) {
return [this.status]
}
const conversation = clone(this.$store.state.statuses.conversationsObject[this.conversationId])
const statusIndex = findIndex(conversation, { id: this.originalStatusId })
if (statusIndex !== -1) {
conversation[statusIndex] = this.status
}
return sortAndFilterConversation(conversation, this.status)
},
conversationDive () {
},
statusMap () {
return this.conversation.reduce((res, s) => {
res[s.id] = s
return res
}, {})
},
threadTree () {
const reverseLookupTable = this.conversation.reduce((table, status, index) => {
table[status.id] = index
return table
}, {})
const threads = this.conversation.reduce((a, cur) => {
const id = cur.id
a.forest[id] = this.getReplies(id)
.map(s => s.id)
return a
}, {
- forest: {},
+ forest: {}
})
debug('threads = ', threads)
const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => {
if (processed[id]) {
return []
}
processed[id] = true
return [{
status: this.conversation[reverseLookupTable[id]],
id,
depth
}, walk(forest, forest[id], depth + 1, processed)].reduce((a, b) => a.concat(b), [])
}).reduce((a, b) => a.concat(b), [])
const linearized = walk(threads.forest, this.topLevel.map(k => k.id))
return linearized
},
replyIds () {
return this.conversation.map(k => k.id)
.reduce((res, id) => {
res[id] = (this.replies[id] || []).map(k => k.id)
return res
}, {})
},
totalReplyCount () {
debug('replyIds=', this.replyIds)
const sizes = {}
const subTreeSizeFor = (id) => {
if (sizes[id]) {
return sizes[id]
}
sizes[id] = 1 + this.replyIds[id].map(cid => subTreeSizeFor(cid)).reduce((a, b) => a + b, 0)
return sizes[id]
}
this.conversation.map(k => k.id).map(subTreeSizeFor)
debug('totalReplyCount=', sizes)
return Object.keys(sizes).reduce((res, id) => {
res[id] = sizes[id] - 1 // exclude itself
return res
}, {})
},
totalReplyDepth () {
const depths = {}
const subTreeDepthFor = (id) => {
if (depths[id]) {
return depths[id]
}
depths[id] = 1 + this.replyIds[id].map(cid => subTreeDepthFor(cid)).reduce((a, b) => a > b ? a : b, 0)
return depths[id]
}
this.conversation.map(k => k.id).map(subTreeDepthFor)
return Object.keys(depths).reduce((res, id) => {
res[id] = depths[id] - 1 // exclude itself
return res
}, {})
},
depths () {
debug('threadTree', this.threadTree)
return this.threadTree.reduce((a, k) => {
a[k.id] = k.depth
return a
}, {})
},
topLevel () {
const topLevel = this.conversation.reduce((tl, cur) =>
tl.filter(k => this.getReplies(cur.id).map(v => v.id).indexOf(k.id) === -1), this.conversation)
- debug("toplevel =", topLevel)
+ debug('toplevel =', topLevel)
return topLevel
},
showingTopLevel () {
if (this.canDive && this.diveRoot) {
return [this.statusMap[this.diveRoot]]
}
return this.topLevel
},
diveRoot () {
return this.diveHistory[this.diveHistory.length - 1]
},
diveDepth () {
return this.canDive && this.diveRoot ? this.depths[this.diveRoot] : 0
},
diveMode () {
return this.canDive && !!this.diveRoot
},
replies () {
let i = 1
// eslint-disable-next-line camelcase
return reduce(this.conversation, (result, { id, in_reply_to_status_id }) => {
/* eslint-disable camelcase */
const irid = in_reply_to_status_id
/* eslint-enable camelcase */
if (irid) {
result[irid] = result[irid] || []
result[irid].push({
name: `#${i}`,
id: id
})
}
i++
return result
}, {})
},
isExpanded () {
return !!(this.expanded || this.isPage)
},
hiddenStyle () {
const height = (this.status && this.status.virtualHeight) || '120px'
return this.virtualHidden ? { height } : {}
},
threadDisplayStatus () {
return this.conversation.reduce((a, k) => {
const id = k.id
const depth = this.depths[id]
const status = (() => {
if (this.threadDisplayStatusObject[id]) {
return this.threadDisplayStatusObject[id]
}
if ((depth - this.diveDepth) <= this.maxDepthToShowByDefault) {
return 'showing'
} else {
return 'hidden'
}
})()
a[id] = status
return a
}, {})
},
statusContentProperties () {
return this.conversation.reduce((a, k) => {
const id = k.id
- const depth = this.depths[id]
const props = (() => {
if (this.statusContentPropertiesObject[id]) {
return this.statusContentPropertiesObject[id]
}
return {
showingTall: false,
expandingSubject: false,
- showingLongSubject: false,
+ showingLongSubject: false
}
})()
a[id] = props
return a
}, {})
},
canDive () {
return this.isTreeView && this.isExpanded
},
focused () {
return (id) => {
return (this.isExpanded) && id === this.highlight
}
},
maybeHighlight () {
return this.isExpanded ? this.highlight : null
}
},
components: {
Status,
ThreadTree
},
watch: {
statusId (newVal, oldVal) {
const newConversationId = this.getConversationId(newVal)
const oldConversationId = this.getConversationId(oldVal)
if (newConversationId && oldConversationId && newConversationId === oldConversationId) {
this.setHighlight(this.originalStatusId)
} else {
this.fetchConversation()
}
},
expanded (value) {
if (value) {
this.fetchConversation()
} else {
// if we collapse it, we should reset the dive
this._diven = false
}
},
virtualHidden (value) {
this.$store.dispatch(
'setVirtualHeight',
{ statusId: this.statusId, height: `${this.$el.clientHeight}px` }
)
},
highlight (value, old) {
console.log('highlight:', old, ' => ', value)
}
},
methods: {
conversationFetched () {
if (!this.isExpanded) {
return
}
if (!this._diven) {
if (!this.threadDisplayStatus[this.statusId]) {
return
}
this._diven = true
const parentOrSelf = this.parentOrSelf(this.originalStatusId)
console.log(
'this.threadDisplayStatus ', this.threadDisplayStatus,
'this.statusId', this.statusId)
if (this.threadDisplayStatus[this.statusId] === 'hidden') {
this.diveIntoStatus(parentOrSelf, /* preventScroll */ true)
this.tryScrollTo(this.statusId)
}
}
},
fetchConversation () {
if (this.status) {
this.$store.state.api.backendInteractor.fetchConversation({ id: this.statusId })
.then(({ ancestors, descendants }) => {
this.$store.dispatch('addNewStatuses', { statuses: ancestors })
this.$store.dispatch('addNewStatuses', { statuses: descendants })
this.setHighlight(this.originalStatusId)
})
.then(this.conversationFetched)
} else {
this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId })
.then((status) => {
this.$store.dispatch('addNewStatuses', { statuses: [status] })
this.fetchConversation()
})
}
},
getReplies (id) {
return this.replies[id] || []
},
getHighlight () {
return this.isExpanded ? this.highlight : null
},
setHighlight (id) {
console.log('setHighlight', id)
if (!id) return
this.highlight = id
this.$store.dispatch('fetchFavsAndRepeats', id)
this.$store.dispatch('fetchEmojiReactionsBy', id)
},
toggleExpanded () {
this.expanded = !this.expanded
},
getConversationId (statusId) {
const status = this.$store.state.statuses.allStatusesObject[statusId]
return get(status, 'retweeted_status.statusnet_conversation_id', get(status, 'statusnet_conversation_id'))
},
setThreadDisplay (id, nextStatus) {
this.threadDisplayStatusObject = {
...this.threadDisplayStatusObject,
[id]: nextStatus
}
},
toggleThreadDisplay (id) {
const depth = this.depths[id]
debug('depth = ', depth)
debug(
'threadDisplayStatus = ', this.threadDisplayStatus,
'threadDisplayStatusObject = ', this.threadDisplayStatusObject)
const curStatus = this.threadDisplayStatus[id]
const nextStatus = curStatus === 'showing' ? 'hidden' : 'showing'
debug('toggling', id, 'to', nextStatus)
this.setThreadDisplay(id, nextStatus)
},
setThreadDisplayRecursively (id, nextStatus) {
this.setThreadDisplay(id, nextStatus)
this.getReplies(id).map(k => k.id).map(id => this.setThreadDisplayRecursively(id, nextStatus))
},
showThreadRecursively (id) {
this.setThreadDisplayRecursively(id, 'showing')
},
setStatusContentProperty (id, name, value) {
this.statusContentPropertiesObject = {
...this.statusContentPropertiesObject,
[id]: {
...this.statusContentPropertiesObject[id],
[name]: value
}
}
},
toggleStatusContentProperty (id, name) {
this.setStatusContentProperty(id, name, !this.statusContentProperties[id][name])
},
leastShowingAncestor (id) {
let cur = id
let parent = this.parentOf(cur)
while (cur) {
// if the parent is showing it means cur is visible
if (this.threadDisplayStatus[parent] === 'showing') {
return cur
}
parent = this.parentOf(parent)
cur = this.parentOf(cur)
}
// nothing found, fall back to toplevel
- return topLevel[0].id
+ return this.topLevel[0] ? this.topLevel[0].id : undefined
},
diveIntoStatus (id, preventScroll) {
this.diveHistory = [...this.diveHistory, id]
if (!preventScroll) {
this.goToCurrent()
}
},
diveBack () {
const oldHighlight = this.highlight
this.diveHistory = [...this.diveHistory.slice(0, this.diveHistory.length - 1)]
if (oldHighlight) {
this.tryScrollTo(this.leastShowingAncestor(oldHighlight))
}
},
undive () {
const oldHighlight = this.highlight
this.diveHistory = []
if (oldHighlight) {
this.tryScrollTo(this.leastShowingAncestor(oldHighlight))
} else {
this.goToCurrent()
}
},
tryScrollTo (id) {
+ if (!id) {
+ return
+ }
if (this.isPage) {
// set statusId
this.$router.push({ name: 'conversation', params: { id } })
}
this.setHighlight(id)
},
goToCurrent () {
this.tryScrollTo(this.diveRoot || this.topLevel[0].id)
},
statusById (id) {
return this.statusMap[id]
},
parentOf (id) {
const { in_reply_to_status_id: parentId } = this.statusById(id)
return parentId
},
parentOrSelf (id) {
return this.parentOf(id) || id
}
}
}
export default conversation
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
index 65ec85c4a0..527a4cf543 100644
--- a/src/components/status_content/status_content.js
+++ b/src/components/status_content/status_content.js
@@ -1,134 +1,133 @@
import Attachment from '../attachment/attachment.vue'
import Poll from '../poll/poll.vue'
import Gallery from '../gallery/gallery.vue'
import StatusBody from 'src/components/status_body/status_body.vue'
import LinkPreview from '../link-preview/link-preview.vue'
import { mapGetters, mapState } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faCircleNotch,
faFile,
faMusic,
faImage,
faLink,
faPollH
} from '@fortawesome/free-solid-svg-icons'
library.add(
faCircleNotch,
faFile,
faMusic,
faImage,
faLink,
faPollH
)
const camelCase = name => name.charAt(0).toUpperCase() + name.slice(1)
const controlledOrUncontrolledGetters = list => list.reduce((res, name) => {
const camelized = camelCase(name)
const toggle = `controlledToggle${camelized}`
const controlledName = `controlled${camelized}`
const uncontrolledName = `uncontrolled${camelized}`
res[name] = function () {
return this[toggle] ? this[controlledName] : this[uncontrolledName]
}
return res
}, {})
const controlledOrUncontrolledToggle = (obj, name) => {
const camelized = camelCase(name)
const toggle = `controlledToggle${camelized}`
- const controlledName = `controlled${camelized}`
const uncontrolledName = `uncontrolled${camelized}`
if (obj[toggle]) {
obj[toggle]()
} else {
obj[uncontrolledName] = !obj[uncontrolledName]
}
}
const StatusContent = {
name: 'StatusContent',
props: [
'status',
'compact',
'focused',
'noHeading',
'fullContent',
'singleLine',
'controlledShowingTall',
'controlledExpandingSubject',
'controlledToggleShowingTall',
'controlledToggleExpandingSubject'
],
data () {
return {
uncontrolledShowingTall: this.fullContent || (this.inConversation && this.focused),
uncontrolledShowingLongSubject: false,
// not as computed because it sets the initial state which will be changed later
uncontrolledExpandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject
}
},
computed: {
...controlledOrUncontrolledGetters(['showingTall', 'expandingSubject', 'showingLongSubject']),
hideAttachments () {
return (this.mergedConfig.hideAttachments && !this.inConversation) ||
(this.mergedConfig.hideAttachmentsInConv && this.inConversation)
},
nsfwClickthrough () {
if (!this.status.nsfw) {
return false
}
if (this.status.summary && this.localCollapseSubjectDefault) {
return false
}
return true
},
attachmentSize () {
if (this.compact) {
return 'small'
} else if ((this.mergedConfig.hideAttachments && !this.inConversation) ||
(this.mergedConfig.hideAttachmentsInConv && this.inConversation) ||
(this.status.attachments.length > this.maxThumbnails)) {
return 'hide'
}
return 'normal'
},
maxThumbnails () {
return this.mergedConfig.maxThumbnails
},
...mapGetters(['mergedConfig']),
...mapState({
currentUser: state => state.users.currentUser
})
},
components: {
Attachment,
Poll,
Gallery,
LinkPreview,
StatusBody
},
methods: {
toggleShowingTall () {
controlledOrUncontrolledToggle(this, 'showingTall')
},
toggleExpandingSubject () {
controlledOrUncontrolledToggle(this, 'expandingSubject')
},
toggleShowMore () {
if (this.mightHideBecauseTall) {
this.toggleShowingTall()
} else if (this.mightHideBecauseSubject) {
this.toggleExpandingSubject()
}
},
setMedia () {
const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
return () => this.$store.dispatch('setMedia', attachments)
}
}
}
export default StatusContent
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Sep 18, 11:29 PM (9 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1768474
Default Alt Text
(19 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline
Log In to Comment