commit: 0582f19e7c2c6f916b427d5ecfbbb571178ce841
parent 7e1e8ea42925dffbce1d75e463f38a2c051c764d
Author: Tusooa Zhu <tusooa@kazv.moe>
Date: Fri, 6 Aug 2021 20:18:27 -0400
Add tree-style thread display
Diffstat:
8 files changed, 224 insertions(+), 21 deletions(-)
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
@@ -1,5 +1,8 @@
import { reduce, filter, findIndex, clone, get } from 'lodash'
import Status from '../status/status.vue'
+import ThreadTree from '../thread_tree/thread_tree.vue'
+
+const debug = console.log
const sortById = (a, b) => {
const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id
@@ -53,6 +56,15 @@ const conversation = {
}
},
computed: {
+ displayStyle () {
+ return this.$store.state.config.conversationDisplay
+ },
+ isTreeView () {
+ return this.displayStyle === 'tree'
+ },
+ isLinearView () {
+ return this.displayStyle === 'linear'
+ },
hideStatus () {
if (this.$refs.statusComponent && this.$refs.statusComponent[0]) {
return this.virtualHidden && this.$refs.statusComponent[0].suspendable
@@ -90,6 +102,49 @@ const conversation = {
return sortAndFilterConversation(conversation, this.status)
},
+ 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)
+ .sort((a, b) => reverseLookupTable[a] - reverseLookupTable[b])
+
+ a.topLevel = a.topLevel.filter(k => a.forest[id].contains(k))
+ return a
+ }, {
+ forest: {},
+ topLevel: this.conversation.map(s => s.id)
+ })
+
+ 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[child], depth + 1, processed)].reduce((a, b) => a.concat(b), [])
+ }).reduce((a, b) => a.concat(b), [])
+
+ const linearized = walk(threads.forest, threads.topLevel)
+
+ return linearized
+ },
+ 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
+ },
replies () {
let i = 1
// eslint-disable-next-line camelcase
@@ -109,7 +164,7 @@ const conversation = {
}, {})
},
isExpanded () {
- return this.expanded || this.isPage
+ return !!(this.expanded || this.isPage)
},
hiddenStyle () {
const height = (this.status && this.status.virtualHeight) || '120px'
@@ -117,7 +172,8 @@ const conversation = {
}
},
components: {
- Status
+ Status,
+ ThreadTree
},
watch: {
statusId (newVal, oldVal) {
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
@@ -18,24 +18,47 @@
{{ $t('timeline.collapse') }}
</button>
</div>
- <status
- v-for="status in conversation"
- :key="status.id"
- ref="statusComponent"
- :inline-expanded="collapsable && isExpanded"
- :statusoid="status"
- :expandable="!isExpanded"
- :show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
- :focused="focused(status.id)"
- :in-conversation="isExpanded"
- :highlight="getHighlight()"
- :replies="getReplies(status.id)"
- :in-profile="inProfile"
- :profile-user-id="profileUserId"
- class="conversation-status status-fadein panel-body"
- @goto="setHighlight"
- @toggleExpanded="toggleExpanded"
- />
+ <div v-if="isTreeView">
+ <thread-tree
+ v-for="status in topLevel"
+ :key="status.id"
+ ref="statusComponent"
+
+ :status="status"
+ :in-profile="inProfile"
+ :conversation="conversation"
+ :collapsable="collapsable"
+ :is-expanded="isExpanded"
+ :pinned-status-ids-object="pinnedStatusIdsObject"
+ :profile-user-id="profileUserId"
+
+ :focused="focused"
+ :get-replies="getReplies"
+ :get-highlight="getHighlight"
+ :set-highlight="setHighlight"
+ :toggle-expanded="toggleExpanded"
+ />
+ </div>
+ <div v-if="isLinearView">
+ <status
+ v-for="status in conversation"
+ :key="status.id"
+ ref="statusComponent"
+ :inline-expanded="collapsable && isExpanded"
+ :statusoid="status"
+ :expandable="!isExpanded"
+ :show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
+ :focused="focused(status.id)"
+ :in-conversation="isExpanded"
+ :highlight="getHighlight()"
+ :replies="getReplies(status.id)"
+ :in-profile="inProfile"
+ :profile-user-id="profileUserId"
+ class="conversation-status status-fadein panel-body"
+ @goto="setHighlight"
+ @toggleExpanded="toggleExpanded"
+ />
+ </div>
</div>
<div
v-else
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
@@ -20,6 +20,11 @@ const GeneralTab = {
value: mode,
label: this.$t(`settings.subject_line_${mode === 'masto' ? 'mastodon' : mode}`)
})),
+ conversationDisplayOptions: ['tree', 'linear'].map(mode => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.conversation_display_${mode}`)
+ })),
mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(mode => ({
key: mode,
value: mode,
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
@@ -154,6 +154,15 @@
</li>
<li>
<ChoiceSetting
+ id="conversationDisplay"
+ path="conversationDisplay"
+ :options="conversationDisplayOptions"
+ >
+ {{ $t('settings.conversation_display') }}
+ </ChoiceSetting>
+ </li>
+ <li>
+ <ChoiceSetting
id="mentionLinkDisplay"
path="mentionLinkDisplay"
:options="mentionLinkDisplayOptions"
diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js
@@ -0,0 +1,52 @@
+import Status from '../status/status.vue'
+
+const debug = console.log
+
+const ThreadTree = {
+ components: {
+ Status
+ },
+ name: 'ThreadTree',
+ props: {
+ depth: Number,
+ status: Object,
+ inProfile: Boolean,
+ conversation: Array,
+ collapsable: Boolean,
+ isExpanded: Boolean,
+ pinnedStatusIdsObject: Object,
+ profileUserId: String,
+
+ focused: Function,
+ getHighlight: Function,
+ getReplies: Function,
+ setHighlight: Function,
+ toggleExpanded: Function
+ },
+ computed: {
+ reverseLookupTable () {
+ return this.conversation.reduce((table, status, index) => {
+ table[status.id] = index
+ return table
+ }, {})
+ },
+ currentReplies () {
+ debug('status:', this.status)
+ debug('getReplies:', this.getReplies(this.status.id))
+ return this.getReplies(this.status.id).map(({ id }) => this.statusById(id))
+ },
+ },
+ methods: {
+ statusById (id) {
+ return this.conversation[this.reverseLookupTable[id]]
+ },
+ collapseThread () {
+ },
+ showThread () {
+ },
+ showAllSubthreads () {
+ }
+ }
+}
+
+export default ThreadTree
diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue
@@ -0,0 +1,55 @@
+<template>
+ <div class="thread-tree panel-body">
+ <status
+ :key="status.id"
+ ref="statusComponent"
+ :inline-expanded="collapsable && isExpanded"
+ :statusoid="status"
+ :expandable="!isExpanded"
+ :show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
+ :focused="focused(status.id)"
+ :in-conversation="isExpanded"
+ :highlight="getHighlight()"
+ :replies="getReplies(status.id)"
+ :in-profile="inProfile"
+ :profile-user-id="profileUserId"
+ class="conversation-status status-fadein panel-body"
+ @goto="setHighlight"
+ @toggleExpanded="toggleExpanded"
+ />
+ <div
+ v-if="currentReplies.length"
+ class="thread-tree-replies"
+ >
+ <thread-tree
+ v-for="replyStatus in currentReplies"
+ :key="replyStatus.id"
+ ref="childComponent"
+ :status="replyStatus"
+
+ :in-profile="inProfile"
+ :conversation="conversation"
+ :collapsable="collapsable"
+ :is-expanded="isExpanded"
+ :pinned-status-ids-object="pinnedStatusIdsObject"
+ :profile-user-id="profileUserId"
+
+ :focused="focused"
+ :get-replies="getReplies"
+ :get-highlight="getHighlight"
+ :set-highlight="setHighlight"
+ :toggle-expanded="toggleExpanded"
+
+ class="conversation-status status-fadein panel-body"
+ />
+ </div>
+ </div>
+</template>
+
+<script src="./thread_tree.js"></script>
+
+<style lang="scss">
+.thread-tree-replies {
+ margin-left: 1em;
+}
+</style>
diff --git a/src/modules/config.js b/src/modules/config.js
@@ -12,6 +12,7 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0]
export const multiChoiceProperties = [
'postContentType',
'subjectLineBehavior',
+ 'conversationDisplay', // tree | linear
'mentionLinkDisplay' // short | full_for_remote | full
]
@@ -81,7 +82,8 @@ export const defaultState = {
hidePostStats: undefined, // instance default
hideUserStats: undefined, // instance default
virtualScrolling: undefined, // instance default
- sensitiveByDefault: undefined // instance default
+ sensitiveByDefault: undefined, // instance default
+ conversationDisplay: undefined // instance default
}
// caching the instance default properties
diff --git a/src/modules/instance.js b/src/modules/instance.js
@@ -53,6 +53,7 @@ const defaultState = {
theme: 'pleroma-dark',
virtualScrolling: true,
sensitiveByDefault: false,
+ conversationDisplay: 'tree',
// Nasty stuff
customEmoji: [],