From 814a27749a9759477e51790c4c94539a3233f462 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 6 Aug 2021 20:18:27 -0400 Subject: [PATCH 01/45] Add tree-style thread display --- src/components/conversation/conversation.js | 60 ++++++++++++++++++- src/components/conversation/conversation.vue | 59 ++++++++++++------ .../settings_modal/tabs/general_tab.js | 5 ++ .../settings_modal/tabs/general_tab.vue | 9 +++ src/components/thread_tree/thread_tree.js | 52 ++++++++++++++++ src/components/thread_tree/thread_tree.vue | 55 +++++++++++++++++ src/modules/config.js | 6 +- src/modules/instance.js | 1 + 8 files changed, 225 insertions(+), 22 deletions(-) create mode 100644 src/components/thread_tree/thread_tree.js create mode 100644 src/components/thread_tree/thread_tree.vue diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 069c0b407..e663ba157 100644 --- 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 index 3fb26d923..cea5f88f0 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -18,24 +18,47 @@ {{ $t('timeline.collapse') }} - +
+ +
+
+ +
({ + key: mode, + value: mode, + label: this.$t(`settings.conversation_display_${mode}`) + })), loopSilentAvailable: // Firefox Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index d3e71b31e..6d4c9249b 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -78,6 +78,15 @@ {{ $t('settings.virtual_scrolling') }} +
  • + + {{ $t('settings.conversation_display') }} + +
  • diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js new file mode 100644 index 000000000..4f95122a5 --- /dev/null +++ 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 new file mode 100644 index 000000000..8256eee6c --- /dev/null +++ b/src/components/thread_tree/thread_tree.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/modules/config.js b/src/modules/config.js index 33e2cb50f..3a418bb68 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -11,7 +11,8 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0] */ export const multiChoiceProperties = [ 'postContentType', - 'subjectLineBehavior' + 'subjectLineBehavior', + 'conversationDisplay' // tree | linear ] export const defaultState = { @@ -70,7 +71,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 index 539b9c66f..18d6f0ec0 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -43,6 +43,7 @@ const defaultState = { theme: 'pleroma-dark', virtualScrolling: true, sensitiveByDefault: false, + conversationDisplay: 'tree', // Nasty stuff customEmoji: [], From b1f4b1bff3dae5ea6437d5d98d6e03ea9deb7916 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 00:33:06 -0400 Subject: [PATCH 02/45] Implement thread folding/expanding --- src/components/conversation/conversation.js | 105 +++++++++++++++++-- src/components/conversation/conversation.vue | 7 ++ src/components/status/status.js | 22 +++- src/components/status/status.vue | 13 +++ src/components/thread_tree/thread_tree.js | 12 ++- src/components/thread_tree/thread_tree.vue | 35 ++++++- 6 files changed, 180 insertions(+), 14 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index e663ba157..0abb7abd2 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -38,7 +38,8 @@ const conversation = { data () { return { highlight: null, - expanded: false + expanded: false, + threadDisplayStatusObject: {} // id => 'showing' | 'hidden' } }, props: [ @@ -56,6 +57,9 @@ const conversation = { } }, computed: { + maxDepthToShowByDefault () { + return 4 + }, displayStyle () { return this.$store.state.config.conversationDisplay }, @@ -112,15 +116,14 @@ const conversation = { 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) }) + debug('threads = ', threads) + const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => { if (processed[id]) { return [] @@ -131,18 +134,63 @@ const conversation = { status: this.conversation[reverseLookupTable[id]], id, depth - }, walk(forest, forest[child], depth + 1, processed)].reduce((a, b) => a.concat(b), []) + }, walk(forest, forest[id], depth + 1, processed)].reduce((a, b) => a.concat(b), []) }).reduce((a, b) => a.concat(b), []) - const linearized = walk(threads.forest, threads.topLevel) + 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 }, replies () { @@ -169,6 +217,25 @@ const conversation = { 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.maxDepthToShowByDefault) { + return 'showing' + } else { + return 'hidden' + } + })() + + a[id] = status + return a + }, {}) } }, components: { @@ -235,6 +302,30 @@ const conversation = { 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') } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index cea5f88f0..783cc8941 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -23,6 +23,7 @@ v-for="status in topLevel" :key="status.id" ref="statusComponent" + :depth="0" :status="status" :in-profile="inProfile" @@ -37,6 +38,12 @@ :get-highlight="getHighlight" :set-highlight="setHighlight" :toggle-expanded="toggleExpanded" + + :toggle-thread-display="toggleThreadDisplay" + :thread-display-status="threadDisplayStatus" + :show-thread-recursively="showThreadRecursively" + :total-reply-count="totalReplyCount" + :total-reply-depth="totalReplyDepth" />
    diff --git a/src/components/status/status.js b/src/components/status/status.js index ac4815346..0b7ea433b 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -35,7 +35,9 @@ import { faStar, faEyeSlash, faEye, - faThumbtack + faThumbtack, + faAngleDoubleUp, + faAngleDoubleDown } from '@fortawesome/free-solid-svg-icons' library.add( @@ -52,7 +54,9 @@ library.add( faEllipsisH, faEyeSlash, faEye, - faThumbtack + faThumbtack, + faAngleDoubleUp, + faAngleDoubleDown ) const Status = { @@ -89,7 +93,10 @@ const Status = { 'inlineExpanded', 'showPinned', 'inProfile', - 'profileUserId' + 'profileUserId', + + 'controlledThreadDisplayStatus', + 'controlledToggleThreadDisplay' ], data () { return { @@ -286,6 +293,12 @@ const Status = { }, isSuspendable () { return !this.replying && this.mediaPlaying.length === 0 + }, + inThreadForest () { + return !!this.controlledThreadDisplayStatus + }, + threadShowing () { + return this.controlledThreadDisplayStatus === 'showing' } }, methods: { @@ -335,6 +348,9 @@ const Status = { }, setHeadTailLinks (headTailLinks) { this.headTailLinks = headTailLinks + }, + toggleThreadDisplay () { + this.controlledToggleThreadDisplay() } }, watch: { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 2684e4152..4035ced17 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -219,6 +219,19 @@ class="fa-scale-110" /> +
    this.statusById(id)) }, + threadShowing () { + return this.threadDisplayStatus[this.status.id] === 'showing' + } }, methods: { statusById (id) { diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index 8256eee6c..9f5915851 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -13,18 +13,23 @@ :replies="getReplies(status.id)" :in-profile="inProfile" :profile-user-id="profileUserId" - class="conversation-status status-fadein panel-body" + class="conversation-status conversation-status-treeview status-fadein panel-body" + + :controlled-thread-display-status="threadDisplayStatus[status.id]" + :controlled-toggle-thread-display="() => toggleThreadDisplay(status.id)" + @goto="setHighlight" @toggleExpanded="toggleExpanded" />
    +
    + +
    From a4a0858110af9e5acfc679834ce55480a8354a88 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 01:05:26 -0400 Subject: [PATCH 03/45] Make show full thread message account for numbers --- src/components/thread_tree/thread_tree.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index 9f5915851..bbdda6fbf 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -60,7 +60,7 @@ class="button-unstyled -link thread-tree-show-replies-button" @click="showThreadRecursively(status.id)" > - {{ $t('status.thread_show_full', { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }} + {{ $tc('status.thread_show_full', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }} From e6120df5505ceecc9992ebb9957d1130b0f110df Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 01:06:16 -0400 Subject: [PATCH 04/45] Add English translations for message threading --- src/i18n/en.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index 018d3b7d3..4690f2f3d 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -456,6 +456,9 @@ "subject_line_email": "Like email: \"re: subject\"", "subject_line_mastodon": "Like mastodon: copy as is", "subject_line_noop": "Do not copy", + "conversation_display": "Conversation display style", + "conversation_display_tree": "Tree-style", + "conversation_display_linear": "Linear-style", "post_status_content_type": "Post status content type", "sensitive_by_default": "Mark posts as sensitive by default", "stop_gifs": "Play-on-hover GIFs", @@ -717,7 +720,10 @@ "nsfw": "NSFW", "expand": "Expand", "you": "(You)", - "plus_more": "+{number} more" + "plus_more": "+{number} more", + "thread_hide": "Hide this thread", + "thread_show": "Show this thread", + "thread_show_full": "Show everything under this thread ({numStatus} status in total, max depth {depth}) | Show everything under this thread ({numStatus} statuses in total, max depth {depth})" }, "user_card": { "approve": "Approve", From 436597a906900a461fd1941e5e4200573195dbfc Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 10:28:45 -0400 Subject: [PATCH 05/45] Make status display controlled --- src/components/conversation/conversation.js | 37 ++++++++++++- src/components/conversation/conversation.vue | 3 ++ src/components/status/status.js | 9 +++- src/components/status/status.vue | 6 +++ .../status_content/status_content.js | 53 ++++++++++++++++++- src/components/thread_tree/thread_tree.js | 11 +++- src/components/thread_tree/thread_tree.vue | 10 ++++ 7 files changed, 124 insertions(+), 5 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 0abb7abd2..6fc86b2c2 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -2,7 +2,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 debug = console.log +const debug = () => {} const sortById = (a, b) => { const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id @@ -39,7 +40,8 @@ const conversation = { return { highlight: null, expanded: false, - threadDisplayStatusObject: {} // id => 'showing' | 'hidden' + threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' + statusContentPropertiesObject: {} } }, props: [ @@ -236,6 +238,25 @@ const conversation = { 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, + } + })() + + a[id] = props + return a + }, {}) } }, components: { @@ -326,6 +347,18 @@ const conversation = { }, 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]) } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 783cc8941..08cb72d04 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -44,6 +44,9 @@ :show-thread-recursively="showThreadRecursively" :total-reply-count="totalReplyCount" :total-reply-depth="totalReplyDepth" + :status-content-properties="statusContentProperties" + :set-status-content-property="setStatusContentProperty" + :toggle-status-content-property="toggleStatusContentProperty" />
    diff --git a/src/components/status/status.js b/src/components/status/status.js index 0b7ea433b..71d32d8a2 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -96,7 +96,14 @@ const Status = { 'profileUserId', 'controlledThreadDisplayStatus', - 'controlledToggleThreadDisplay' + 'controlledToggleThreadDisplay', + + 'controlledShowingTall', + 'controlledToggleShowingTall', + 'controlledExpandingSubject', + 'controlledToggleExpandingSubject', + 'controlledShowingLongSubject', + 'controlledToggleShowingLongSubject' ], data () { return { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 4035ced17..cdb490c3e 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -319,6 +319,12 @@ :no-heading="noHeading" :highlight="highlight" :focused="isFocused" + :controlled-showing-tall="controlledShowingTall" + :controlled-expanding-subject="controlledExpandingSubject" + :controlled-showing-long-subject="controlledShowingLongSubject" + :controlled-toggle-showing-tall="controlledToggleShowingTall" + :controlled-toggle-expanding-subject="controlledToggleExpandingSubject" + :controlled-toggle-showing-long-subject="controlledToggleShowingLongSubject" @mediaplay="addMediaPlaying($event)" @mediapause="removeMediaPlaying($event)" @parseReady="setHeadTailLinks" diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js index 1b80ee097..75ebd1805 100644 --- a/src/components/status_content/status_content.js +++ b/src/components/status_content/status_content.js @@ -24,6 +24,31 @@ library.add( 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: [ @@ -31,9 +56,22 @@ const StatusContent = { 'focused', 'noHeading', 'fullContent', - 'singleLine' + '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) @@ -91,6 +129,19 @@ const StatusContent = { 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) diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index 88b601097..46245bdfc 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -28,7 +28,10 @@ const ThreadTree = { threadDisplayStatus: Object, showThreadRecursively: Function, totalReplyCount: Object, - totalReplyDepth: Object + totalReplyDepth: Object, + statusContentProperties: Object, + setStatusContentProperty: Function, + toggleStatusContentProperty: Function }, computed: { reverseLookupTable () { @@ -44,6 +47,9 @@ const ThreadTree = { }, threadShowing () { return this.threadDisplayStatus[this.status.id] === 'showing' + }, + currentProp () { + return this.statusContentProperties[this.status.id] } }, methods: { @@ -55,6 +61,9 @@ const ThreadTree = { showThread () { }, showAllSubthreads () { + }, + toggleCurrentProp (name) { + this.toggleStatusContentProperty(this.status.id, name) } } } diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index bbdda6fbf..d7077bfde 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -18,6 +18,13 @@ :controlled-thread-display-status="threadDisplayStatus[status.id]" :controlled-toggle-thread-display="() => toggleThreadDisplay(status.id)" + :controlled-showing-tall="currentProp.showingTall" + :controlled-expanding-subject="currentProp.expandingSubject" + :controlled-showing-long-subject="currentProp.showingLongSubject" + :controlled-toggle-showing-tall="() => toggleCurrentProp('ShowingTall')" + :controlled-toggle-expanding-subject="() => toggleCurrentProp('expandingSubject')" + :controlled-toggle-showing-long-subject="() => toggleCurrentProp('showingLongSubject')" + @goto="setHighlight" @toggleExpanded="toggleExpanded" /> @@ -50,6 +57,9 @@ :show-thread-recursively="showThreadRecursively" :total-reply-count="totalReplyCount" :total-reply-depth="totalReplyDepth" + :status-content-properties="statusContentProperties" + :set-status-content-property="setStatusContentProperty" + :toggle-status-content-property="toggleStatusContentProperty" />
    Date: Sat, 7 Aug 2021 11:59:10 -0400 Subject: [PATCH 06/45] Support diving into one status in a conversation --- src/components/conversation/conversation.js | 32 +++++++++++++++++++- src/components/conversation/conversation.vue | 30 +++++++++++++++++- src/components/status/status.js | 13 +++++--- src/components/status/status.vue | 14 ++++++++- src/components/thread_tree/thread_tree.js | 3 +- src/components/thread_tree/thread_tree.vue | 15 ++++++--- 6 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 6fc86b2c2..584f310ce 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -2,6 +2,17 @@ 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 +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faAngleDoubleDown, + faAngleDoubleLeft +) + // const debug = console.log const debug = () => {} @@ -41,7 +52,8 @@ const conversation = { highlight: null, expanded: false, threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' - statusContentPropertiesObject: {} + statusContentPropertiesObject: {}, + diveRoot: null } }, props: [ @@ -195,6 +207,18 @@ const conversation = { debug("toplevel =", topLevel) return topLevel }, + showingTopLevel () { + if (this.diveRoot) { + return [this.conversation.filter(k => this.diveRoot === k.id)[0]] + } + return this.topLevel + }, + diveDepth () { + return this.diveRoot ? this.depths[this.diveRoot] : 0 + }, + diveMode () { + return !!this.diveRoot + }, replies () { let i = 1 // eslint-disable-next-line camelcase @@ -359,6 +383,12 @@ const conversation = { }, toggleStatusContentProperty (id, name) { this.setStatusContentProperty(id, name, !this.statusContentProperties[id][name]) + }, + diveIntoStatus (id) { + this.diveRoot = id + }, + undive () { + this.diveRoot = null } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 08cb72d04..84e8d8b2a 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -18,9 +18,22 @@ {{ $t('timeline.collapse') }}
    +
    + + + +
    @@ -65,6 +79,16 @@ :in-profile="inProfile" :profile-user-id="profileUserId" class="conversation-status status-fadein panel-body" + + :toggle-thread-display="toggleThreadDisplay" + :thread-display-status="threadDisplayStatus" + :show-thread-recursively="showThreadRecursively" + :total-reply-count="totalReplyCount" + :total-reply-depth="totalReplyDepth" + :status-content-properties="statusContentProperties" + :set-status-content-property="setStatusContentProperty" + :toggle-status-content-property="toggleStatusContentProperty" + @goto="setHighlight" @toggleExpanded="toggleExpanded" /> @@ -82,6 +106,10 @@ @import '../../_variables.scss'; .Conversation { + .conversation-undive-box { + padding: 1em; + } + .conversation-undive-box, .conversation-status { border-bottom-width: 1px; border-bottom-style: solid; diff --git a/src/components/status/status.js b/src/components/status/status.js index 71d32d8a2..b2c9e9f0a 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -36,8 +36,9 @@ import { faEyeSlash, faEye, faThumbtack, - faAngleDoubleUp, - faAngleDoubleDown + faChevronUp, + faChevronDown, + faAngleDoubleRight } from '@fortawesome/free-solid-svg-icons' library.add( @@ -55,8 +56,9 @@ library.add( faEyeSlash, faEye, faThumbtack, - faAngleDoubleUp, - faAngleDoubleDown + faChevronUp, + faChevronDown, + faAngleDoubleRight ) const Status = { @@ -103,7 +105,8 @@ const Status = { 'controlledExpandingSubject', 'controlledToggleExpandingSubject', 'controlledShowingLongSubject', - 'controlledToggleShowingLongSubject' + 'controlledToggleShowingLongSubject', + 'dive' ], data () { return { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index cdb490c3e..6eedcc3db 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -229,7 +229,19 @@ + + diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index 46245bdfc..3e8eedb14 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -31,7 +31,8 @@ const ThreadTree = { totalReplyDepth: Object, statusContentProperties: Object, setStatusContentProperty: Function, - toggleStatusContentProperty: Function + toggleStatusContentProperty: Function, + dive: Function }, computed: { reverseLookupTable () { diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index d7077bfde..adf7bcdf7 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -24,6 +24,7 @@ :controlled-toggle-showing-tall="() => toggleCurrentProp('ShowingTall')" :controlled-toggle-expanding-subject="() => toggleCurrentProp('expandingSubject')" :controlled-toggle-showing-long-subject="() => toggleCurrentProp('showingLongSubject')" + :dive="dive ? () => dive(status.id) : undefined" @goto="setHighlight" @toggleExpanded="toggleExpanded" @@ -60,18 +61,24 @@ :status-content-properties="statusContentProperties" :set-status-content-property="setStatusContentProperty" :toggle-status-content-property="toggleStatusContentProperty" + :dive="dive" />
    - + + + {{ $tc('status.thread_show_full', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }} + +
    From f0c717ce5f837a2171080cfe5776bd3872101b86 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 11:59:41 -0400 Subject: [PATCH 07/45] Add English translations for diving --- src/i18n/en.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index 4690f2f3d..e172af598 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -723,7 +723,10 @@ "plus_more": "+{number} more", "thread_hide": "Hide this thread", "thread_show": "Show this thread", - "thread_show_full": "Show everything under this thread ({numStatus} status in total, max depth {depth}) | Show everything under this thread ({numStatus} statuses in total, max depth {depth})" + "thread_show_full": "Show everything under this thread ({numStatus} status in total, max depth {depth}) | Show everything under this thread ({numStatus} statuses in total, max depth {depth})", + "thread_show_full_with_icon": "{icon} {text}", + "show_all_conversation": "{0} Show full conversation", + "show_only_conversation_under_this": "Only show replies to this status" }, "user_card": { "approve": "Approve", From 93bcee95163c72ee6afd0cbb4a16453fb0586876 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 14:11:34 -0400 Subject: [PATCH 08/45] Add dive functionality --- src/components/conversation/conversation.js | 69 +++++++++++++++++--- src/components/conversation/conversation.vue | 15 ++++- src/components/status/status.vue | 2 +- src/components/thread_tree/thread_tree.js | 3 +- src/components/thread_tree/thread_tree.vue | 5 +- 5 files changed, 80 insertions(+), 14 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 584f310ce..b2af1d6cb 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -5,12 +5,14 @@ import ThreadTree from '../thread_tree/thread_tree.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faAngleDoubleDown, - faAngleDoubleLeft + faAngleDoubleLeft, + faChevronLeft } from '@fortawesome/free-solid-svg-icons' library.add( faAngleDoubleDown, - faAngleDoubleLeft + faAngleDoubleLeft, + faChevronLeft ) // const debug = console.log @@ -53,7 +55,7 @@ const conversation = { expanded: false, threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' statusContentPropertiesObject: {}, - diveRoot: null + diveHistory: [] } }, props: [ @@ -120,6 +122,14 @@ const conversation = { 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 @@ -208,16 +218,19 @@ const conversation = { return topLevel }, showingTopLevel () { - if (this.diveRoot) { - return [this.conversation.filter(k => this.diveRoot === k.id)[0]] + if (this.canDive && this.diveRoot) { + return [this.statusMap[this.diveRoot]] } return this.topLevel }, + diveRoot () { + return this.diveHistory[this.diveHistory.length - 1] + }, diveDepth () { - return this.diveRoot ? this.depths[this.diveRoot] : 0 + return this.canDive && this.diveRoot ? this.depths[this.diveRoot] : 0 }, diveMode () { - return !!this.diveRoot + return this.canDive && !!this.diveRoot }, replies () { let i = 1 @@ -252,7 +265,7 @@ const conversation = { if (this.threadDisplayStatusObject[id]) { return this.threadDisplayStatusObject[id] } - if (depth <= this.maxDepthToShowByDefault) { + if ((depth - this.diveDepth) <= this.maxDepthToShowByDefault) { return 'showing' } else { return 'hidden' @@ -281,6 +294,9 @@ const conversation = { a[id] = props return a }, {}) + }, + canDive () { + return this.isTreeView && this.isExpanded } }, components: { @@ -310,6 +326,25 @@ const conversation = { } }, 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) + } + } + }, fetchConversation () { if (this.status) { this.$store.state.api.backendInteractor.fetchConversation({ id: this.statusId }) @@ -318,6 +353,7 @@ const conversation = { 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) => { @@ -385,10 +421,23 @@ const conversation = { this.setStatusContentProperty(id, name, !this.statusContentProperties[id][name]) }, diveIntoStatus (id) { - this.diveRoot = id + this.diveHistory = [...this.diveHistory, id] + }, + diveBack () { + this.diveHistory = [...this.diveHistory.slice(0, this.diveHistory.length - 1)] }, undive () { - this.diveRoot = null + this.diveHistory = [] + }, + 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 } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 84e8d8b2a..99bc7bccb 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -31,6 +31,19 @@ +
    + + + +
    diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 6eedcc3db..41dd84851 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -220,7 +220,7 @@ />
    diff --git a/src/modules/config.js b/src/modules/config.js index 3a418bb68..44f73aae3 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -72,7 +72,8 @@ export const defaultState = { hideUserStats: undefined, // instance default virtualScrolling: undefined, // instance default sensitiveByDefault: undefined, // instance default - conversationDisplay: undefined // instance default + conversationDisplay: undefined, // instance default + maxDepthInThread: 6 } // caching the instance default properties diff --git a/src/modules/instance.js b/src/modules/instance.js index 7cfd933f5..4f024ca2e 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -44,6 +44,7 @@ const defaultState = { virtualScrolling: true, sensitiveByDefault: false, conversationDisplay: 'simple_tree', + maxDepthInThread: 6, // Nasty stuff customEmoji: [], From 9029344e1f2f4b6b7a81c0c41288cf785cd63bc4 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 20:14:37 -0400 Subject: [PATCH 18/45] Add English translations for max depth in thread --- src/i18n/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/en.json b/src/i18n/en.json index 03c56b199..d85ca8352 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -460,6 +460,7 @@ "conversation_display_tree": "Tree-style", "conversation_display_simple_tree": "Simplified tree-style", "conversation_display_linear": "Linear-style", + "max_depth_in_thread": "Maximum number of levels in thread to display by default", "post_status_content_type": "Post status content type", "sensitive_by_default": "Mark posts as sensitive by default", "stop_gifs": "Play-on-hover GIFs", From 46feecee70b5b04b657a66f14756dccbfb467acb Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 21:35:52 -0400 Subject: [PATCH 19/45] Fix status undefined in parentOf --- src/components/conversation/conversation.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 853161dff..bd8315b89 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -488,7 +488,11 @@ const conversation = { return this.statusMap[id] }, parentOf (id) { - const { in_reply_to_status_id: parentId } = this.statusById(id) + const status = this.statusById(id) + if (!status) { + return undefined + } + const { in_reply_to_status_id: parentId } = status return parentId }, parentOrSelf (id) { From b7e179c1fe1a1d08d79381c0eec15ae46a8a5e04 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 8 Aug 2021 09:46:10 -0400 Subject: [PATCH 20/45] Fix the bug where toggleShowingTall does not work --- src/components/thread_tree/thread_tree.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index b803f80fb..fa1e5f862 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -22,7 +22,7 @@ :controlled-showing-tall="currentProp.showingTall" :controlled-expanding-subject="currentProp.expandingSubject" :controlled-showing-long-subject="currentProp.showingLongSubject" - :controlled-toggle-showing-tall="() => toggleCurrentProp('ShowingTall')" + :controlled-toggle-showing-tall="() => toggleCurrentProp('showingTall')" :controlled-toggle-expanding-subject="() => toggleCurrentProp('expandingSubject')" :controlled-toggle-showing-long-subject="() => toggleCurrentProp('showingLongSubject')" :dive="dive ? () => dive(status.id) : undefined" From f8d25d2c2af42c936569b240836b9c64b174ee14 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 8 Aug 2021 12:40:17 -0400 Subject: [PATCH 21/45] Optimize thread display --- src/_variables.scss | 2 + src/components/conversation/conversation.vue | 215 +++++++++++-------- src/components/status/status.scss | 9 - src/components/thread_tree/thread_tree.vue | 16 +- 4 files changed, 139 insertions(+), 103 deletions(-) diff --git a/src/_variables.scss b/src/_variables.scss index 9004d551a..099d36064 100644 --- a/src/_variables.scss +++ b/src/_variables.scss @@ -30,3 +30,5 @@ $fallback--attachmentRadius: 10px; $fallback--chatMessageRadius: 10px; $fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; + +$status-margin: 0.75em; diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 0ffd8c37c..cd108f695 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -18,94 +18,102 @@ {{ $t('timeline.collapse') }} -
    - +
    - - -
    -
    - + + +
    +
    - - -
    -
    - + + +
    +
    + -
    -
    - +
    +
    + + @goto="setHighlight" + @toggleExpanded="toggleExpanded" + /> +
    .conversation-status { + border-top-left-radius: $fallback--panelRadius; + border-top-left-radius: var(--panelRadius, $fallback--panelRadius); + } + + /* first element in conversation body */ + &.-expanded .conversation-body { + .conversation-undive-box:nth-child(1), + & > .conversation-status:nth-child(1), + & > .thread-body:nth-child(1) > .thread-tree:nth-child(1) > .conversation-status:nth-child(1), { + border-top: none; + } + } + + /* first unexpanded statuses in timeline */ + &:first-child:not(.-expanded) { + .conversation-body { + .conversation-status { + border-top: none; + } + } + } + + /* expanded conversation in timeline */ + &.status-fadein.-expanded .thread-body { + border-left-width: 4px; + border-left-style: solid; + border-left-color: $fallback--cRed; + border-left-color: var(--cRed, $fallback--cRed); + border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; + border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + border-bottom: 1px solid var(--border, $fallback--border); + } &.-expanded { .conversation-status:last-child { border-bottom: none; - border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; - border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); } } } diff --git a/src/components/status/status.scss b/src/components/status/status.scss index 71305dd7e..6c5730b71 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -1,7 +1,5 @@ @import '../../_variables.scss'; -$status-margin: 0.75em; - .Status { min-width: 0; white-space: normal; @@ -26,13 +24,6 @@ $status-margin: 0.75em; --icon: var(--selectedPostIcon, $fallback--icon); } - &.-conversation { - border-left-width: 4px; - border-left-style: solid; - border-left-color: $fallback--cRed; - border-left-color: var(--cRed, $fallback--cRed); - } - .gravestone { padding: $status-margin; color: $fallback--faint; diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index fa1e5f862..aafad66ea 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -109,14 +109,16 @@ From 2a6f0c4a3442b86cc7f5748c88e46d32667f6e28 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 8 Aug 2021 12:55:04 -0400 Subject: [PATCH 22/45] Optimize thread border radius --- src/components/conversation/conversation.vue | 12 +++++++++--- src/components/thread_tree/thread_tree.vue | 3 +-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index cd108f695..11b5b3d81 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -130,10 +130,13 @@ .Conversation { .conversation-undive-box { padding: $status-margin; + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: var(--border, $fallback--border); + border-radius: 0; } /* HACK: we want the border width to scale with the status *below it* */ - .conversation-undive-box, .conversation-status { border-top-width: 1px; border-top-style: solid; @@ -146,11 +149,14 @@ border-top-left-radius: var(--panelRadius, $fallback--panelRadius); } - /* first element in conversation body */ + /* first element in a reply tree, the border is supplied by reply tree instead + for radius to display properly + */ &.-expanded .conversation-body { .conversation-undive-box:nth-child(1), & > .conversation-status:nth-child(1), - & > .thread-body:nth-child(1) > .thread-tree:nth-child(1) > .conversation-status:nth-child(1), { + & > .thread-body:nth-child(1) > .thread-tree:nth-child(1) > .conversation-status:nth-child(1), + .thread-tree:nth-child(1) > .conversation-status:nth-child(1) { border-top: none; } } diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index aafad66ea..79ba0cc55 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -111,10 +111,9 @@ .thread-tree-replies { margin-left: $status-margin; border-left: 1px solid var(--border, $fallback--border); + border-top: 1px solid var(--border, $fallback--border); border-top-left-radius: $fallback--panelRadius; border-top-left-radius: var(--panelRadius, $fallback--panelRadius); - border-bottom-left-radius: $fallback--panelRadius; - border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius); } .thread-tree-replies-hidden { From 2c91a020bcab1dc77b8201aa34b1bb8ceb5e3724 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 8 Aug 2021 13:29:49 -0400 Subject: [PATCH 23/45] Make dive/undive button clickable along the whole row --- src/components/conversation/conversation.vue | 4 ++++ src/components/thread_tree/thread_tree.vue | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 11b5b3d81..86a227ce9 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -134,6 +134,10 @@ border-bottom-style: solid; border-bottom-color: var(--border, $fallback--border); border-radius: 0; + /* Make the button stretch along the whole row */ + display: flex; + align-items: stretch; + flex-direction: column; } /* HACK: we want the border width to scale with the status *below it* */ diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index 79ba0cc55..46d65101d 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -119,5 +119,9 @@ .thread-tree-replies-hidden { padding: $status-margin; border-top: 1px solid var(--border, $fallback--border); + /* Make the button stretch along the whole row */ + display: flex; + align-items: stretch; + flex-direction: column; } From 38a67cc2e2fa3adb3de4d543038214d32469a4c6 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Tue, 10 Aug 2021 21:28:13 -0400 Subject: [PATCH 24/45] Remove horizontal border and thicken vertical border in a thread tree --- src/components/conversation/conversation.vue | 32 +++----------------- src/components/thread_tree/thread_tree.vue | 7 ++--- 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 86a227ce9..c866b9830 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -142,36 +142,14 @@ /* HACK: we want the border width to scale with the status *below it* */ .conversation-status { - border-top-width: 1px; - border-top-style: solid; - border-top-color: var(--border, $fallback--border); + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: var(--border, $fallback--border); border-radius: 0; } - &.-expanded .conversation-body .thread-tree:nth-child(1) > .conversation-status { - border-top-left-radius: $fallback--panelRadius; - border-top-left-radius: var(--panelRadius, $fallback--panelRadius); - } - - /* first element in a reply tree, the border is supplied by reply tree instead - for radius to display properly - */ - &.-expanded .conversation-body { - .conversation-undive-box:nth-child(1), - & > .conversation-status:nth-child(1), - & > .thread-body:nth-child(1) > .thread-tree:nth-child(1) > .conversation-status:nth-child(1), - .thread-tree:nth-child(1) > .conversation-status:nth-child(1) { - border-top: none; - } - } - - /* first unexpanded statuses in timeline */ - &:first-child:not(.-expanded) { - .conversation-body { - .conversation-status { - border-top: none; - } - } + &.-expanded .thread-tree .conversation-status { + border-bottom: none; } /* expanded conversation in timeline */ diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index 46d65101d..dce03f27a 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -110,15 +110,12 @@ @import '../../_variables.scss'; .thread-tree-replies { margin-left: $status-margin; - border-left: 1px solid var(--border, $fallback--border); - border-top: 1px solid var(--border, $fallback--border); - border-top-left-radius: $fallback--panelRadius; - border-top-left-radius: var(--panelRadius, $fallback--panelRadius); + border-left: 2px solid var(--border, $fallback--border); } .thread-tree-replies-hidden { padding: $status-margin; - border-top: 1px solid var(--border, $fallback--border); + //border-top: 1px solid var(--border, $fallback--border); /* Make the button stretch along the whole row */ display: flex; align-items: stretch; From 7dd5e0fc8ec6659d27918857f183977726631dcf Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Tue, 10 Aug 2021 23:58:27 -0400 Subject: [PATCH 25/45] Implement Misskey-style tree view Now the tree will be always rooted at the highlighted status, and all its ancestors shown linearly on the top. Enhancement: If an ancestor has more than one reply (i.e. it has a child that is not on current status's ancestor chain), we are given a link to root the thread at that status. --- src/components/conversation/conversation.js | 61 +++++----- src/components/conversation/conversation.vue | 118 +++++++++++++++---- 2 files changed, 124 insertions(+), 55 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index bd8315b89..817b9bda7 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -55,7 +55,7 @@ const conversation = { expanded: false, threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' statusContentPropertiesObject: {}, - diveHistory: [] + inlineDivePosition: null } }, props: [ @@ -231,7 +231,10 @@ const conversation = { return this.topLevel }, diveRoot () { - return this.diveHistory[this.diveHistory.length - 1] + (() => {})(this.conversation) + const statusId = this.inlineDivePosition || this.statusId + const isTopLevel = !this.parentOf(statusId) + return isTopLevel ? null : statusId }, diveDepth () { return this.canDive && this.diveRoot ? this.depths[this.diveRoot] : 0 @@ -332,7 +335,6 @@ const conversation = { this.fetchConversation() } else { // if we collapse it, we should reset the dive - this._diven = false this.undive() } }, @@ -348,19 +350,6 @@ const conversation = { if (!this.isExpanded) { return } - - if (!this._diven) { - if (!this.threadDisplayStatus[this.statusId]) { - return - } - this._diven = true - const parentOrSelf = this.parentOrSelf(this.originalStatusId) - // If current status is not visible - if (this.threadDisplayStatus[parentOrSelf] === 'hidden') { - this.diveIntoStatus(parentOrSelf, /* preventScroll */ true) - this.tryScrollTo(this.statusId) - } - } }, fetchConversation () { if (this.status) { @@ -449,26 +438,15 @@ const conversation = { return this.topLevel[0] ? this.topLevel[0].id : undefined }, diveIntoStatus (id, preventScroll) { - this.diveHistory = [...this.diveHistory, id] - if (!preventScroll) { - this.goToCurrent() - } + this.tryScrollTo(id) }, - diveBack () { - const oldHighlight = this.highlight - this.diveHistory = [...this.diveHistory.slice(0, this.diveHistory.length - 1)] - if (oldHighlight) { - this.tryScrollTo(this.leastVisibleAncestor(oldHighlight)) - } + diveToTopLevel () { + this.tryScrollTo(this.topLevel[0].id) }, + // only used when we are not on a page undive () { - const oldHighlight = this.highlight - this.diveHistory = [] - if (oldHighlight) { - this.tryScrollTo(this.leastVisibleAncestor(oldHighlight)) - } else { - this.goToCurrent() - } + this.inlineDivePosition = null + this.setHighlight(this.statusId) }, tryScrollTo (id) { if (!id) { @@ -477,8 +455,9 @@ const conversation = { if (this.isPage) { // set statusId this.$router.push({ name: 'conversation', params: { id } }) + } else { + this.inlineDivePosition = id } - this.setHighlight(id) }, goToCurrent () { @@ -493,10 +472,24 @@ const conversation = { return undefined } const { in_reply_to_status_id: parentId } = status + if (!this.statusMap[parentId]) { + return undefined + } return parentId }, parentOrSelf (id) { return this.parentOf(id) || id + }, + // Ancestors of some status, from top to bottom + ancestorsOf (id) { + const ancestors = [] + let cur = this.parentOf(id) + while (cur) { + ancestors.unshift(this.statusMap[cur]) + cur = this.parentOf(cur) + } + // console.log('ancestors = ', ancestors, 'conversation = ', this.conversation.map(k => k.id), 'statusContentProperties=', this.statusContentProperties) + return ancestors } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index c866b9830..20ce54a66 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -21,34 +21,88 @@
    -
    - - - -
    +
    +
    + +
    +
    + + + + {{ $tc('status.ancestor_follow', getReplies(status.id).length - 1, { numReplies: getReplies(status.id).length - 1 }) }} + + +
    +
    +
    +
    From 1fdba77ae5bfc5aa7ce26b678faad24420ec2218 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Tue, 10 Aug 2021 23:59:51 -0400 Subject: [PATCH 26/45] Add English translation for Misskey-style tree view --- src/i18n/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index d85ca8352..b7ba6375b 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -729,8 +729,9 @@ "thread_show_full_with_icon": "{icon} {text}", "thread_follow": "See the remaining part of this thread ({numStatus} status in total) | See the remaining part of this thread ({numStatus} statuses in total)", "thread_follow_with_icon": "{icon} {text}", + "ancestor_follow": "See {numReplies} other reply under this status | See {numReplies} other replies under this status", + "ancestor_follow_with_icon": "{icon} {text}", "show_all_conversation": "{0} Show full conversation", - "return_to_last_showing": "{0} Return to last position", "show_only_conversation_under_this": "Only show replies to this status" }, "user_card": { From 5a37ea5a8e9e4ceb6e09bb4ba4909f02dd4e1509 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 11 Aug 2021 00:12:16 -0400 Subject: [PATCH 27/45] Highlight ancestor of the current status when diving back to top --- src/components/conversation/conversation.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 817b9bda7..e9bbca180 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -441,7 +441,7 @@ const conversation = { this.tryScrollTo(id) }, diveToTopLevel () { - this.tryScrollTo(this.topLevel[0].id) + this.tryScrollTo(this.topLevelAncestorOrSelfId(this.diveRoot) || this.topLevel[0].id) }, // only used when we are not on a page undive () { @@ -490,6 +490,15 @@ const conversation = { } // console.log('ancestors = ', ancestors, 'conversation = ', this.conversation.map(k => k.id), 'statusContentProperties=', this.statusContentProperties) return ancestors + }, + topLevelAncestorOrSelfId (id) { + let cur = id + let parent = this.parentOf(id) + while (parent) { + cur = this.parentOf(cur) + parent = this.parentOf(parent) + } + return cur } } } From e01445b1d93e381b3fcb934942423a48cf4fb9db Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 11 Aug 2021 00:17:17 -0400 Subject: [PATCH 28/45] Clean up --- src/components/conversation/conversation.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index e9bbca180..3cc5f8861 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -346,11 +346,6 @@ const conversation = { } }, methods: { - conversationFetched () { - if (!this.isExpanded) { - return - } - }, fetchConversation () { if (this.status) { this.$store.state.api.backendInteractor.fetchConversation({ id: this.statusId }) @@ -359,7 +354,6 @@ const conversation = { 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) => { @@ -488,7 +482,6 @@ const conversation = { ancestors.unshift(this.statusMap[cur]) cur = this.parentOf(cur) } - // console.log('ancestors = ', ancestors, 'conversation = ', this.conversation.map(k => k.id), 'statusContentProperties=', this.statusContentProperties) return ancestors }, topLevelAncestorOrSelfId (id) { From 89761743b8ae82eded1fa3afaf1dab0d5244f2d9 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 11 Aug 2021 00:22:47 -0400 Subject: [PATCH 29/45] Reset thread open state when collapsed --- src/components/conversation/conversation.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 3cc5f8861..4c4291618 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -334,8 +334,7 @@ const conversation = { if (value) { this.fetchConversation() } else { - // if we collapse it, we should reset the dive - this.undive() + this.resetDisplayState() } }, virtualHidden (value) { @@ -492,6 +491,10 @@ const conversation = { parent = this.parentOf(parent) } return cur + }, + resetDisplayState () { + this.undive() + this.threadDisplayStatusObject = {} } } } From 5643314922be20448cd9bdceabc30b01af218418 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 11 Aug 2021 00:30:27 -0400 Subject: [PATCH 30/45] Optimise thread ancestor display style --- src/components/conversation/conversation.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 20ce54a66..f0e118e56 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -194,7 +194,12 @@ flex-direction: column; } - .thread-ancestor { + .thread-ancestors { + margin-left: $status-margin; + border-left: 2px solid var(--border, $fallback--border); + } + + .thread-ancestor .StatusContent { --link: var(--faintLink); --text: var(--faint); color: var(--text); From 4648bc6f72c44a7128066922fc87c44d25220425 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 11 Aug 2021 00:49:38 -0400 Subject: [PATCH 31/45] Optimise thread ancestor borders --- src/components/conversation/conversation.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index f0e118e56..9aea7b20b 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -220,7 +220,6 @@ //border-left: 2px solid var(--border, $fallback--border); } - /* HACK: we want the border width to scale with the status *below it* */ .conversation-status { border-bottom-width: 1px; border-bottom-style: solid; @@ -229,10 +228,18 @@ } .thread-ancestor-has-other-replies .conversation-status, + .thread-ancestor:last-child .conversation-status, + .thread-ancestor:last-child .thread-ancestor-dive-box, &.-expanded .thread-tree .conversation-status { border-bottom: none; } + .thread-ancestors + .thread-tree > .conversation-status { + border-top-width: 1px; + border-top-style: solid; + border-top-color: var(--border, $fallback--border); + } + /* expanded conversation in timeline */ &.status-fadein.-expanded .thread-body { border-left-width: 4px; From a917bdc34bd163445c9f2a166b7fe9d495593cb5 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 13 Aug 2021 18:53:31 -0400 Subject: [PATCH 32/45] Make other replies button stretch along the row --- src/components/conversation/conversation.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 9aea7b20b..6c8c6ef07 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -211,9 +211,11 @@ border-bottom-color: var(--border, $fallback--border); border-radius: 0; /* Make the button stretch along the whole row */ - display: flex; - align-items: stretch; - flex-direction: column; + &, &-inner { + display: flex; + align-items: stretch; + flex-direction: column; + } } .thread-ancestor-dive-box-inner { padding: $status-margin; From 00cc72134454cc7a28107baa33ec3e3002252267 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 5 Sep 2021 11:16:48 -0400 Subject: [PATCH 33/45] Improve "show full conversation" interaction Now we only show that button when there are other statuses out of sight (other toplevel statuses exist outside of the current thread tree). --- src/components/conversation/conversation.js | 8 ++++++++ src/components/conversation/conversation.vue | 12 +++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 4c4291618..423930afb 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -224,6 +224,9 @@ const conversation = { debug('toplevel =', topLevel) return topLevel }, + otherTopLevelCount () { + return this.topLevel.length - 1 + }, showingTopLevel () { if (this.canDive && this.diveRoot) { return [this.statusMap[this.diveRoot]] @@ -242,6 +245,11 @@ const conversation = { diveMode () { return this.canDive && !!this.diveRoot }, + shouldShowAllConversationButton () { + // The "show all conversation" button tells the user that there exist + // other toplevel statuses, so do not show it if there is only a single root + return this.diveMode && this.topLevel.length > 1 + }, replies () { let i = 1 // eslint-disable-next-line camelcase diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 6c8c6ef07..f0eb88c12 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -20,16 +20,22 @@
    - + + + {{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }} +
    Date: Sun, 5 Sep 2021 11:19:43 -0400 Subject: [PATCH 34/45] Add English translation for show all conversation button improvement --- src/i18n/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index b7ba6375b..57533308d 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -731,7 +731,8 @@ "thread_follow_with_icon": "{icon} {text}", "ancestor_follow": "See {numReplies} other reply under this status | See {numReplies} other replies under this status", "ancestor_follow_with_icon": "{icon} {text}", - "show_all_conversation": "{0} Show full conversation", + "show_all_conversation_with_icon": "{icon} {text}", + "show_all_conversation": "Show full conversation ({numStatus} other status) | Show full conversation ({numStatus} other statuses)", "show_only_conversation_under_this": "Only show replies to this status" }, "user_card": { From 70ec3ad09e414759d632aba4f1083c518c1be6cd Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 5 Sep 2021 16:35:47 -0400 Subject: [PATCH 35/45] Make position of other replies button a pref --- src/components/conversation/conversation.js | 9 +++++ src/components/conversation/conversation.vue | 7 ++-- .../settings_modal/tabs/general_tab.js | 5 +++ .../settings_modal/tabs/general_tab.vue | 40 +++++++++++++------ src/components/status/status.js | 1 + src/components/status/status.vue | 15 ++++++- src/modules/config.js | 4 +- src/modules/instance.js | 1 + 8 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 423930afb..d4972fbc7 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -92,6 +92,15 @@ const conversation = { isLinearView () { return this.displayStyle === 'linear' }, + otherRepliesButtonPosition () { + return this.$store.getters.mergedConfig.conversationOtherRepliesButton + }, + showOtherRepliesButtonBelowStatus () { + return this.otherRepliesButtonPosition === 'below' + }, + showOtherRepliesButtonInsideStatus () { + return this.otherRepliesButtonPosition === 'inside' + }, hideStatus () { if (this.$refs.statusComponent && this.$refs.statusComponent[0]) { return this.virtualHidden && this.$refs.statusComponent[0].suspendable diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index f0eb88c12..b3d97075d 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -66,13 +66,14 @@ :profile-user-id="profileUserId" class="conversation-status status-fadein panel-body" - :simple="treeViewIsSimple" + :simple-tree="treeViewIsSimple" :toggle-thread-display="toggleThreadDisplay" :thread-display-status="threadDisplayStatus" :show-thread-recursively="showThreadRecursively" :total-reply-count="totalReplyCount" :total-reply-depth="totalReplyDepth" - :dive="(!treeViewIsSimple) ? () => diveIntoStatus(status.id) : null" + :show-other-replies-as-button="showOtherRepliesButtonInsideStatus" + :dive="() => diveIntoStatus(status.id)" :controlled-showing-tall="statusContentProperties[status.id].showingTall" :controlled-expanding-subject="statusContentProperties[status.id].expandingSubject" @@ -85,7 +86,7 @@ @toggleExpanded="toggleExpanded" />
    ({ + key: mode, + value: mode, + label: this.$t(`settings.conversation_other_replies_button_${mode}`) + })), loopSilentAvailable: // Firefox Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index 536bfce35..580e27f28 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -87,19 +87,33 @@ {{ $t('settings.conversation_display') }} -
  • - - -
  • +
      +
    • + + +
    • +
    • + + {{ $t('settings.conversation_other_replies_button') }} + +
    • +
    diff --git a/src/components/status/status.js b/src/components/status/status.js index a8ecf19c1..69237999d 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -100,6 +100,7 @@ const Status = { 'simpleTree', 'controlledThreadDisplayStatus', 'controlledToggleThreadDisplay', + 'showOtherRepliesAsButton', 'controlledShowingTall', 'controlledToggleShowingTall', diff --git a/src/components/status/status.vue b/src/components/status/status.vue index c180ea9e0..72211d550 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -346,7 +346,20 @@ v-if="inConversation && !isPreview && replies && replies.length" class="replies" > - {{ $t('status.replies_list') }} + + + {{ $t('status.replies_list') }} + Date: Sun, 5 Sep 2021 16:36:27 -0400 Subject: [PATCH 36/45] Add English translation for position of other replies button pref --- src/i18n/en.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/i18n/en.json b/src/i18n/en.json index 57533308d..323a8da7f 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -460,6 +460,9 @@ "conversation_display_tree": "Tree-style", "conversation_display_simple_tree": "Simplified tree-style", "conversation_display_linear": "Linear-style", + "conversation_other_replies_button": "Show the \"other replies\" button", + "conversation_other_replies_button_below": "Below statuses", + "conversation_other_replies_button_inside": "Inside statuses", "max_depth_in_thread": "Maximum number of levels in thread to display by default", "post_status_content_type": "Post status content type", "sensitive_by_default": "Mark posts as sensitive by default", From c634e153050724e2a19304f6ee97acf203d4eac3 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 8 Sep 2021 23:22:11 -0400 Subject: [PATCH 37/45] Do not display replies inside status as link if there are no other replies --- src/components/status/status.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 72211d550..3b8e457fd 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -347,7 +347,7 @@ class="replies" > Date: Wed, 8 Sep 2021 23:29:17 -0400 Subject: [PATCH 39/45] Add English translations for other replies count --- src/i18n/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/en.json b/src/i18n/en.json index 323a8da7f..0cbb9409d 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -710,6 +710,7 @@ "reply_to": "Reply to", "mentions": "Mentions", "replies_list": "Replies:", + "replies_list_with_others": "Replies (+{numReplies} other): | Replies (+{numReplies} others):", "mute_conversation": "Mute conversation", "unmute_conversation": "Unmute conversation", "status_unavailable": "Status unavailable", From 708633d6b12f798b977c9490f7187f7f2d10c6c8 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 9 Sep 2021 00:03:10 -0400 Subject: [PATCH 40/45] Fix controlled status display toggles --- src/components/status_body/status_body.js | 16 +++++++++------- src/components/status_content/status_content.js | 8 ++------ src/components/status_content/status_content.vue | 6 ++++++ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js index ef5423076..8eb930ce9 100644 --- a/src/components/status_body/status_body.js +++ b/src/components/status_body/status_body.js @@ -25,14 +25,16 @@ const StatusContent = { 'focused', 'noHeading', 'fullContent', - 'singleLine' + 'singleLine', + 'showingTall', + 'expandingSubject', + 'showingLongSubject', + 'toggleShowingTall', + 'toggleExpandingSubject', + 'toggleShowingLongSubject' ], data () { return { - showingTall: this.fullContent || (this.inConversation && this.focused), - showingLongSubject: false, - // not as computed because it sets the initial state which will be changed later - expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject, postLength: this.status.text.length, parseReadyDone: false } @@ -113,9 +115,9 @@ const StatusContent = { }, toggleShowMore () { if (this.mightHideBecauseTall) { - this.showingTall = !this.showingTall + this.toggleShowingTall() } else if (this.mightHideBecauseSubject) { - this.expandingSubject = !this.expandingSubject + this.toggleExpandingSubject() } }, generateTagLink (tag) { diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js index fc1306e51..b5e453ef2 100644 --- a/src/components/status_content/status_content.js +++ b/src/components/status_content/status_content.js @@ -134,12 +134,8 @@ const StatusContent = { toggleExpandingSubject () { controlledOrUncontrolledToggle(this, 'expandingSubject') }, - toggleShowMore () { - if (this.mightHideBecauseTall) { - this.toggleShowingTall() - } else if (this.mightHideBecauseSubject) { - this.toggleExpandingSubject() - } + toggleShowingLongSubject () { + controlledOrUncontrolledToggle(this, 'showingLongSubject') }, setMedia () { const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue index 5cebc697e..6ba1e2d30 100644 --- a/src/components/status_content/status_content.vue +++ b/src/components/status_content/status_content.vue @@ -4,6 +4,12 @@
    From 0b576b64e233ba5c7d491f07fb2e4120950b2fd8 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 10 Sep 2021 15:24:23 -0400 Subject: [PATCH 41/45] Clean up debug code for tree threading --- src/components/conversation/conversation.js | 15 --------------- src/components/thread_tree/thread_tree.js | 5 ----- 2 files changed, 20 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index d4972fbc7..74257d5e4 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -15,9 +15,6 @@ library.add( 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 @@ -162,8 +159,6 @@ const conversation = { forest: {} }) - debug('threads = ', threads) - const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => { if (processed[id]) { return [] @@ -189,7 +184,6 @@ const conversation = { }, {}) }, totalReplyCount () { - debug('replyIds=', this.replyIds) const sizes = {} const subTreeSizeFor = (id) => { if (sizes[id]) { @@ -199,7 +193,6 @@ const conversation = { 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 @@ -221,7 +214,6 @@ const conversation = { }, {}) }, depths () { - debug('threadTree', this.threadTree) return this.threadTree.reduce((a, k) => { a[k.id] = k.depth return a @@ -230,7 +222,6 @@ const conversation = { 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) return topLevel }, otherTopLevelCount () { @@ -404,14 +395,8 @@ const conversation = { } }, 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) { diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index cb1d22426..649f70381 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -11,9 +11,6 @@ library.add( faAngleDoubleRight ) -// const debug = console.log -const debug = () => {} - const ThreadTree = { components: { Status @@ -55,8 +52,6 @@ const ThreadTree = { }, {}) }, currentReplies () { - debug('status:', this.status) - debug('getReplies:', this.getReplies(this.status.id)) return this.getReplies(this.status.id).map(({ id }) => this.statusById(id)) }, threadShowing () { From 12ed9a179956174fc3e227a255959e5f53c0a450 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 15 Sep 2021 23:35:17 -0400 Subject: [PATCH 42/45] Fix virtual scrolling for tree threading Ref: tree-threading --- src/components/conversation/conversation.js | 15 ++++++++++----- src/components/conversation/conversation.vue | 2 +- src/components/thread_tree/thread_tree.js | 7 +++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 74257d5e4..7f9f24b57 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -98,13 +98,16 @@ const conversation = { showOtherRepliesButtonInsideStatus () { return this.otherRepliesButtonPosition === 'inside' }, - hideStatus () { + suspendable () { if (this.$refs.statusComponent && this.$refs.statusComponent[0]) { - return this.virtualHidden && this.$refs.statusComponent[0].suspendable + return this.$refs.statusComponent.every(s => s.suspendable) } else { - return this.virtualHidden + return true } }, + hideStatus () { + return this.virtualHidden && this.suspendable + }, status () { return this.$store.state.statuses.allStatusesObject[this.statusId] }, @@ -234,7 +237,6 @@ const conversation = { return this.topLevel }, diveRoot () { - (() => {})(this.conversation) const statusId = this.inlineDivePosition || this.statusId const isTopLevel = !this.parentOf(statusId) return isTopLevel ? null : statusId @@ -248,7 +250,10 @@ const conversation = { shouldShowAllConversationButton () { // The "show all conversation" button tells the user that there exist // other toplevel statuses, so do not show it if there is only a single root - return this.diveMode && this.topLevel.length > 1 + return this.isTreeView && this.isExpanded && this.diveMode && this.topLevel.length > 1 + }, + shouldShowAncestors () { + return this.isTreeView && this.isExpanded && this.ancestorsOf(this.diveRoot).length }, replies () { let i = 1 diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index b3d97075d..0cd745399 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -43,7 +43,7 @@ class="thread-body" >
    s.suspendable) + } + return selfSuspendable + }, reverseLookupTable () { return this.conversation.reduce((table, status, index) => { table[status.id] = index From 78f573af471461eb73ab556b20e091b8290c1db3 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 16 Sep 2021 00:29:14 -0400 Subject: [PATCH 43/45] Make replying and mediaPlaying controlled $refs is not a reliable way to deal with child components under tree threading as it is not reactive, but the children may change at any time. The only good way seems to be making these states aggregated on the conversation component. Ref: tree-threading --- src/components/conversation/conversation.js | 21 ++++++-- src/components/conversation/conversation.vue | 4 ++ src/components/status/status.js | 50 ++++++++++++++++++-- src/components/thread_tree/thread_tree.js | 3 ++ src/components/thread_tree/thread_tree.vue | 4 ++ 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 7f9f24b57..9aa7b1835 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -99,6 +99,10 @@ const conversation = { return this.otherRepliesButtonPosition === 'inside' }, suspendable () { + if (this.isTreeView) { + return Object.entries(this.statusContentProperties) + .every(([k, prop]) => !prop.replying && prop.mediaPlaying.length === 0) + } if (this.$refs.statusComponent && this.$refs.statusComponent[0]) { return this.$refs.statusComponent.every(s => s.suspendable) } else { @@ -303,14 +307,21 @@ const conversation = { return this.conversation.reduce((a, k) => { const id = k.id const props = (() => { - if (this.statusContentPropertiesObject[id]) { - return this.statusContentPropertiesObject[id] - } - return { + const def = { showingTall: false, expandingSubject: false, - showingLongSubject: false + showingLongSubject: false, + isReplying: false, + mediaPlaying: [] } + + if (this.statusContentPropertiesObject[id]) { + return { + ...def, + ...this.statusContentPropertiesObject[id] + } + } + return def })() a[id] = props diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 0cd745399..4d64cf08b 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -78,9 +78,13 @@ :controlled-showing-tall="statusContentProperties[status.id].showingTall" :controlled-expanding-subject="statusContentProperties[status.id].expandingSubject" :controlled-showing-long-subject="statusContentProperties[status.id].showingLongSubject" + :controlled-replying="statusContentProperties[status.id].replying" + :controlled-media-playing="statusContentProperties[status.id].mediaPlaying" :controlled-toggle-showing-tall="() => toggleStatusContentProperty(status.id, 'showingTall')" :controlled-toggle-expanding-subject="() => toggleStatusContentProperty(status.id, 'expandingSubject')" :controlled-toggle-showing-long-subject="() => toggleStatusContentProperty(status.id, 'showingLongSubject')" + :controlled-toggle-replying="() => toggleStatusContentProperty(status.id, 'replying')" + :controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)" @goto="setHighlight" @toggleExpanded="toggleExpanded" diff --git a/src/components/status/status.js b/src/components/status/status.js index 69237999d..2408beaae 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -61,6 +61,41 @@ library.add( faAngleDoubleRight ) +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 uncontrolledName = `uncontrolled${camelized}` + if (obj[toggle]) { + obj[toggle]() + } else { + obj[uncontrolledName] = !obj[uncontrolledName] + } +} + +const controlledOrUncontrolledSet = (obj, name, val) => { + const camelized = camelCase(name) + const set = `controlledSet${camelized}` + const uncontrolledName = `uncontrolled${camelized}` + if (obj[set]) { + obj[set](val) + } else { + obj[uncontrolledName] = val + } +} + const Status = { name: 'Status', components: { @@ -108,20 +143,25 @@ const Status = { 'controlledToggleExpandingSubject', 'controlledShowingLongSubject', 'controlledToggleShowingLongSubject', + 'controlledReplying', + 'controlledToggleReplying', + 'controlledMediaPlaying', + 'controlledSetMediaPlaying', 'dive' ], data () { return { - replying: false, + uncontrolledReplying: false, unmuted: false, userExpanded: false, - mediaPlaying: [], + uncontrolledMediaPlaying: [], suspendable: true, error: null, headTailLinks: null } }, computed: { + ...controlledOrUncontrolledGetters(['replying', 'mediaPlaying']), muteWords () { return this.mergedConfig.muteWords }, @@ -333,7 +373,7 @@ const Status = { this.error = undefined }, toggleReplying () { - this.replying = !this.replying + controlledOrUncontrolledToggle(this, 'replying') }, gotoOriginal (id) { if (this.inConversation) { @@ -353,10 +393,10 @@ const Status = { return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames) }, addMediaPlaying (id) { - this.mediaPlaying.push(id) + controlledOrUncontrolledSet(this, 'mediaPlaying', this.mediaPlaying.concat(id)) }, removeMediaPlaying (id) { - this.mediaPlaying = this.mediaPlaying.filter(mediaId => mediaId !== id) + controlledOrUncontrolledSet(this, 'mediaPlaying', this.mediaPlaying.filter(mediaId => mediaId !== id)) }, setHeadTailLinks (headTailLinks) { this.headTailLinks = headTailLinks diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index 0e499b85b..71e637254 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -80,6 +80,9 @@ const ThreadTree = { }, toggleCurrentProp (name) { this.toggleStatusContentProperty(this.status.id, name) + }, + setCurrentProp (name, newVal) { + this.setStatusContentProperty(this.status.id, name) } } } diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index dce03f27a..cee223e81 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -22,9 +22,13 @@ :controlled-showing-tall="currentProp.showingTall" :controlled-expanding-subject="currentProp.expandingSubject" :controlled-showing-long-subject="currentProp.showingLongSubject" + :controlled-replying="currentProp.replying" + :controlled-media-playing="currentProp.mediaPlaying" :controlled-toggle-showing-tall="() => toggleCurrentProp('showingTall')" :controlled-toggle-expanding-subject="() => toggleCurrentProp('expandingSubject')" :controlled-toggle-showing-long-subject="() => toggleCurrentProp('showingLongSubject')" + :controlled-toggle-replying="() => toggleCurrentProp('replying')" + :controlled-set-media-playing="(newVal) => setCurrentProp('mediaPlaying', newVal)" :dive="dive ? () => dive(status.id) : undefined" @goto="setHighlight" From fe0e7bd63c9d5a0f0092f95f54ca02b5f44e2a86 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 16 Sep 2021 09:22:49 -0400 Subject: [PATCH 44/45] Fix timeline jump when scrolling Ref: tree-threading --- src/components/conversation/conversation.js | 19 ++++++++++++++++++- src/components/status/status.js | 3 --- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 9aa7b1835..b9ebe7ebe 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -469,7 +469,24 @@ const conversation = { } else { this.inlineDivePosition = id } - this.setHighlight(id) + // Because the conversation can be unmounted when out of sight + // and mounted again when it comes into sight, + // the `mounted` or `created` function in `status` should not + // contain scrolling calls, as we do not want the page to jump + // when we scroll with an expanded conversation. + // + // Now the method is to rely solely on the `highlight` watcher + // in `status` components. + // In linear views, all statuses are rendered at all times, but + // in tree views, it is possible that a change in active status + // removes and adds status components (e.g. an originally child + // status becomes an ancestor status, and thus they will be + // different). + // Here, let the components be rendered first, in order to trigger + // the `highlight` watcher. + this.$nextTick(() => { + this.setHighlight(id) + }) }, goToCurrent () { this.tryScrollTo(this.diveRoot || this.topLevel[0].id) diff --git a/src/components/status/status.js b/src/components/status/status.js index 2408beaae..ad6b3bd76 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -421,9 +421,6 @@ const Status = { } } }, - mounted () { - this.scrollIfHighlighted(this.highlight) - }, watch: { 'highlight': function (id) { this.scrollIfHighlighted(id) From a08785904d88f3feb993599d59f71d8c9c85bfef Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 22 Nov 2021 11:20:22 -0500 Subject: [PATCH 45/45] Fix showingLongSubject not correctly propagated --- src/components/status_body/status_body.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue index 9f01c4702..fde63a729 100644 --- a/src/components/status_body/status_body.vue +++ b/src/components/status_body/status_body.vue @@ -14,14 +14,14 @@