From 7b4043b91bc36cae02b00ab73a22f66874cee833 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 8 Sep 2021 23:22:11 -0400 Subject: [PATCH 01/14] 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 bc0aeaf06..31908815b 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 03/14] 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 7b37357af..b185dcab4 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -729,6 +729,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 fe57afd18310c7b6eb414d630f9e1e43ac4d6caf Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 9 Sep 2021 00:03:10 -0400 Subject: [PATCH 04/14] 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 91c331359..b8f6f9a0b 100644 --- a/src/components/status_body/status_body.js +++ b/src/components/status_body/status_body.js @@ -26,14 +26,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 } @@ -115,9 +117,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 527a4cf54..cf72ccb84 100644 --- a/src/components/status_content/status_content.js +++ b/src/components/status_content/status_content.js @@ -116,12 +116,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 69635aad1..0a09cda48 100644 --- a/src/components/status_content/status_content.vue +++ b/src/components/status_content/status_content.vue @@ -8,6 +8,12 @@ :status="status" :compact="compact" :single-line="singleLine" + :showing-tall="showingTall" + :expanding-subject="expandingSubject" + :showing-long-subject="showingLongSubject" + :toggle-showing-tall="toggleShowingTall" + :toggle-expanding-subject="toggleExpandingSubject" + :toggle-showing-long-subject="toggleShowingLongSubject" @parseReady="$emit('parseReady', $event)" >
From 9eb6d48f69e3a9fc3707440387172c61b3d5fc5a Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 15 Sep 2021 23:35:17 -0400 Subject: [PATCH 05/14] 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 d4972fbc7..1e97bbf0e 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -101,13 +101,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] }, @@ -243,7 +246,6 @@ const conversation = { return this.topLevel }, diveRoot () { - (() => {})(this.conversation) const statusId = this.inlineDivePosition || this.statusId const isTopLevel = !this.parentOf(statusId) return isTopLevel ? null : statusId @@ -257,7 +259,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 470c96f49e91eb3218d7bbbbd68dc3148069e5d7 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 10 Sep 2021 15:24:23 -0400 Subject: [PATCH 06/14] 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 1e97bbf0e..7f9f24b57 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 @@ -165,8 +162,6 @@ const conversation = { forest: {} }) - debug('threads = ', threads) - const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => { if (processed[id]) { return [] @@ -192,7 +187,6 @@ const conversation = { }, {}) }, totalReplyCount () { - debug('replyIds=', this.replyIds) const sizes = {} const subTreeSizeFor = (id) => { if (sizes[id]) { @@ -202,7 +196,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 @@ -224,7 +217,6 @@ const conversation = { }, {}) }, depths () { - debug('threadTree', this.threadTree) return this.threadTree.reduce((a, k) => { a[k.id] = k.depth return a @@ -233,7 +225,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 () { @@ -409,14 +400,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 fd21c5bc5..0e499b85b 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 @@ -62,8 +59,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 71f3162f5dca1892e14155de641e7cc002db8fe6 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 16 Sep 2021 00:29:14 -0400 Subject: [PATCH 07/14] 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 700b97640..73fad45fb 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 }, @@ -351,7 +391,7 @@ const Status = { this.error = undefined }, toggleReplying () { - this.replying = !this.replying + controlledOrUncontrolledToggle(this, 'replying') }, gotoOriginal (id) { if (this.inConversation) { @@ -371,10 +411,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 f0cf2c79f67edd0a6da1e848d41de70f539fed56 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 16 Sep 2021 09:22:49 -0400 Subject: [PATCH 08/14] 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 73fad45fb..7bdcb6659 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -439,9 +439,6 @@ const Status = { } } }, - mounted () { - this.scrollIfHighlighted(this.highlight) - }, watch: { 'highlight': function (id) { this.scrollIfHighlighted(id) From 8bcf38d5b34858d3e7f6c7fdb5e53e4688ab80e1 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 22 Nov 2021 11:20:22 -0500 Subject: [PATCH 09/14] 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 a088e6bc8..24d842c2a 100644 --- a/src/components/status_body/status_body.vue +++ b/src/components/status_body/status_body.vue @@ -17,14 +17,14 @@ From 2d519a0822d92faefcdc480af892dac0b9492808 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 6 Mar 2022 13:50:15 -0500 Subject: [PATCH 10/14] Make 'Show full conversation' button have left border in embbeded mode --- src/components/conversation/conversation.vue | 38 ++++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 4d64cf08b..73c613b94 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -19,29 +19,29 @@
-
- - - - {{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }} - - -
+
+ + + + {{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }} + + +
Date: Sun, 6 Mar 2022 13:57:48 -0500 Subject: [PATCH 11/14] Split conversation display style into two different settings linear => linear (now default) simple_tree => tree / conversationTreeAdvanced=false tree => tree / conversationTreeAdvanced=true --- src/components/conversation/conversation.js | 4 ++-- src/components/settings_modal/tabs/general_tab.js | 2 +- src/components/settings_modal/tabs/general_tab.vue | 5 +++++ src/modules/config.js | 1 + src/modules/instance.js | 3 ++- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index b9ebe7ebe..46228e37b 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -81,10 +81,10 @@ const conversation = { return this.$store.getters.mergedConfig.conversationDisplay }, isTreeView () { - return this.displayStyle === 'tree' || this.displayStyle === 'simple_tree' + return !this.isLinearView }, treeViewIsSimple () { - return this.displayStyle === 'simple_tree' + return !this.$store.getters.mergedConfig.conversationTreeAdvanced }, isLinearView () { return this.displayStyle === 'linear' diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index a963d2047..8ae0021ce 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -20,7 +20,7 @@ const GeneralTab = { value: mode, label: this.$t(`settings.subject_line_${mode === 'masto' ? 'mastodon' : mode}`) })), - conversationDisplayOptions: ['tree', 'simple_tree', 'linear'].map(mode => ({ + conversationDisplayOptions: ['tree', 'linear'].map(mode => ({ key: mode, value: mode, label: this.$t(`settings.conversation_display_${mode}`) diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index d5ae78108..28b39d7b2 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -165,6 +165,11 @@ v-if="conversationDisplay !== 'linear'" class="setting-list suboptions" > +
  • + + {{ $t('settings.tree_advanced') }} + +
  • +
  • + + {{ $t('settings.tree_fade_ancestors') }} + +