From 2b7f12613ec04e3ce8be62b169457ad62aa83a65 Mon Sep 17 00:00:00 2001
From: Alexander Tumin <iamtakingiteasy@eientei.org>
Date: Tue, 1 Mar 2022 00:35:00 +0300
Subject: Add bot indication to user icon on statuses

---
 src/components/status/status.vue | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

(limited to 'src/components/status/status.vue')

diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 3bb29db6..8f51a778 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -77,6 +77,7 @@
         <UserAvatar
           v-if="retweet"
           class="left-side repeater-avatar"
+          :bot="botIndicator"
           :better-shadow="betterShadow"
           :user="statusoid.user"
         />
@@ -124,6 +125,7 @@
             @click.stop.prevent.capture.native="toggleUserExpanded"
           >
             <UserAvatar
+              :bot="botIndicator"
               :compact="compact"
               :better-shadow="betterShadow"
               :user="status.user"
@@ -407,7 +409,10 @@
         class="gravestone"
       >
         <div class="left-side">
-          <UserAvatar :compact="compact" />
+          <UserAvatar
+            :compact="compact"
+            :bot="botIndicator"
+          />
         </div>
         <div class="right-side">
           <div class="deleted-text">
-- 
cgit v1.2.3-70-g09d2


From 0f2fd8a3523e9e2cd1ca6fe287eb7304895f2cba Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sat, 7 Aug 2021 00:33:06 -0400
Subject: 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(-)

(limited to 'src/components/status/status.vue')

diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index e663ba15..0abb7abd 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 cea5f88f..783cc894 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"
       />
     </div>
     <div v-if="isLinearView">
diff --git a/src/components/status/status.js b/src/components/status/status.js
index d8f94926..9d423631 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 {
@@ -304,6 +311,12 @@ const Status = {
     },
     isSuspendable () {
       return !this.replying && this.mediaPlaying.length === 0
+    },
+    inThreadForest () {
+      return !!this.controlledThreadDisplayStatus
+    },
+    threadShowing () {
+      return this.controlledThreadDisplayStatus === 'showing'
     }
   },
   methods: {
@@ -353,6 +366,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 3bb29db6..2ebf5638 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -219,6 +219,19 @@
                     class="fa-scale-110"
                   />
                 </button>
+                <button
+                  v-if="inThreadForest && replies && replies.length"
+                  class="button-unstyled"
+                  :title="threadShowing ? $t('status.thread_hide') : $t('status.thread_show')"
+                  :aria-expanded="threadShowing ? 'true' : 'false'"
+                  @click.prevent="toggleThreadDisplay"
+                >
+                  <FAIcon
+                    fixed-width
+                    class="fa-scale-110"
+                    :icon="threadShowing ? 'angle-double-up' : 'angle-double-down'"
+                  />
+                </button>
               </span>
             </div>
             <div
diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js
index 4f95122a..88b60109 100644
--- a/src/components/thread_tree/thread_tree.js
+++ b/src/components/thread_tree/thread_tree.js
@@ -21,7 +21,14 @@ const ThreadTree = {
     getHighlight: Function,
     getReplies: Function,
     setHighlight: Function,
-    toggleExpanded: Function
+    toggleExpanded: Function,
+
+    // to control display of the whole thread forest
+    toggleThreadDisplay: Function,
+    threadDisplayStatus: Object,
+    showThreadRecursively: Function,
+    totalReplyCount: Object,
+    totalReplyDepth: Object
   },
   computed: {
     reverseLookupTable () {
@@ -35,6 +42,9 @@ const ThreadTree = {
       debug('getReplies:', this.getReplies(this.status.id))
       return this.getReplies(this.status.id).map(({ id }) => 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 8256eee6..9f591585 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"
     />
     <div
-      v-if="currentReplies.length"
+      v-if="currentReplies.length && threadShowing"
       class="thread-tree-replies"
     >
       <thread-tree
         v-for="replyStatus in currentReplies"
         :key="replyStatus.id"
         ref="childComponent"
+        :depth="depth + 1"
         :status="replyStatus"
 
         :in-profile="inProfile"
@@ -40,16 +45,40 @@
         :set-highlight="setHighlight"
         :toggle-expanded="toggleExpanded"
 
-        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"
       />
     </div>
+    <div
+      v-if="currentReplies.length && !threadShowing"
+      class="thread-tree-replies thread-tree-replies-hidden"
+    >
+      <button
+        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] }) }}
+      </button>
+    </div>
   </div>
 </template>
 
 <script src="./thread_tree.js"></script>
 
 <style lang="scss">
+@import '../../_variables.scss';
 .thread-tree-replies {
   margin-left: 1em;
 }
+.thread-tree-replies-hidden {
+  padding: 1em;
+  border-bottom: 1px solid var(--border, #222);
+}
+.conversation-status.conversation-status-treeview:last-child,
+.Conversation.-expanded .conversation-status.conversation-status-treeview:last-child {
+  border-bottom: 1px solid var(--border, #222);
+}
 </style>
-- 
cgit v1.2.3-70-g09d2


From ace1f5067c90be2fa0b8da22d39b0e2c88f590fb Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sat, 7 Aug 2021 10:28:45 -0400
Subject: 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 +++
 src/components/status_content/status_content.js | 59 ++++++++++++++++++++++++-
 src/components/thread_tree/thread_tree.js       | 11 ++++-
 src/components/thread_tree/thread_tree.vue      | 10 +++++
 7 files changed, 130 insertions(+), 5 deletions(-)

(limited to 'src/components/status/status.vue')

diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 0abb7abd..6fc86b2c 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 783cc894..08cb72d0 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"
       />
     </div>
     <div v-if="isLinearView">
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 9d423631..d5ee7d4e 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 2ebf5638..8fdebe44 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 dec8914a..65ec85c4 100644
--- a/src/components/status_content/status_content.js
+++ b/src/components/status_content/status_content.js
@@ -23,6 +23,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)
@@ -71,6 +109,25 @@ const StatusContent = {
     Gallery,
     LinkPreview,
     StatusBody
+  },
+  methods: {
+    toggleShowingTall () {
+      controlledOrUncontrolledToggle(this, 'showingTall')
+    },
+    toggleExpandingSubject () {
+      controlledOrUncontrolledToggle(this, 'expandingSubject')
+    },
+    toggleShowMore () {
+      if (this.mightHideBecauseTall) {
+        this.toggleShowingTall()
+      } else if (this.mightHideBecauseSubject) {
+        this.toggleExpandingSubject()
+      }
+    },
+    setMedia () {
+      const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
+      return () => this.$store.dispatch('setMedia', attachments)
+    }
   }
 }
 
diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js
index 88b60109..46245bdf 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 bbdda6fb..d7077bfd 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"
       />
     </div>
     <div
-- 
cgit v1.2.3-70-g09d2


From 84a3cd92a3da885fe1a76809c589b8f464eed432 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sat, 7 Aug 2021 11:59:10 -0400
Subject: 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(-)

(limited to 'src/components/status/status.vue')

diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 6fc86b2c..584f310c 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 08cb72d0..84e8d8b2 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -18,9 +18,22 @@
         {{ $t('timeline.collapse') }}
       </button>
     </div>
+    <div
+      v-if="diveMode"
+      class="conversation-undive-box"
+    >
+      <i18n
+        path="status.show_all_conversation"
+        tag="button"
+        class="button-unstyled -link"
+        @click.prevent="undive"
+      >
+        <FAIcon icon="angle-double-left" />
+      </i18n>
+    </div>
     <div v-if="isTreeView">
       <thread-tree
-        v-for="status in topLevel"
+        v-for="status in showingTopLevel"
         :key="status.id"
         ref="statusComponent"
         :depth="0"
@@ -47,6 +60,7 @@
         :status-content-properties="statusContentProperties"
         :set-status-content-property="setStatusContentProperty"
         :toggle-status-content-property="toggleStatusContentProperty"
+        :dive="diveIntoStatus"
       />
     </div>
     <div v-if="isLinearView">
@@ -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 d5ee7d4e..f119f42e 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 8fdebe44..c2df6021 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -229,7 +229,19 @@
                   <FAIcon
                     fixed-width
                     class="fa-scale-110"
-                    :icon="threadShowing ? 'angle-double-up' : 'angle-double-down'"
+                    :icon="threadShowing ? 'chevron-up' : 'chevron-down'"
+                  />
+                </button>
+                <button
+                  v-if="dive"
+                  class="button-unstyled"
+                  :title="$t('status.show_only_conversation_under_this')"
+                  @click.prevent="dive"
+                >
+                  <FAIcon
+                    fixed-width
+                    class="fa-scale-110"
+                    :icon="'angle-double-right'"
                   />
                 </button>
               </span>
diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js
index 46245bdf..3e8eedb1 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 d7077bfd..adf7bcdf 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"
       />
     </div>
     <div
       v-if="currentReplies.length && !threadShowing"
       class="thread-tree-replies thread-tree-replies-hidden"
     >
-      <button
+      <i18n
+        tag="button"
+        path="status.thread_show_full_with_icon"
         class="button-unstyled -link thread-tree-show-replies-button"
-        @click="showThreadRecursively(status.id)"
+        @click.prevent="showThreadRecursively(status.id)"
       >
-        {{ $tc('status.thread_show_full', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }}
-      </button>
+        <FAIcon place="icon" icon="angle-double-down" />
+        <span place="text">
+          {{ $tc('status.thread_show_full', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }}
+        </span>
+      </i18n>
     </div>
   </div>
 </template>
-- 
cgit v1.2.3-70-g09d2


From d15d24c11c57ecfc49705af648b1e8f73caec51e Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sat, 7 Aug 2021 14:11:34 -0400
Subject: 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(-)

(limited to 'src/components/status/status.vue')

diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 584f310c..b2af1d6c 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 84e8d8b2..99bc7bcc 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -31,6 +31,19 @@
         <FAIcon icon="angle-double-left" />
       </i18n>
     </div>
+    <div
+      v-if="diveMode"
+      class="conversation-undive-box"
+    >
+      <i18n
+        path="status.return_to_last_showing"
+        tag="button"
+        class="button-unstyled -link"
+        @click.prevent="diveBack"
+      >
+        <FAIcon icon="chevron-left" />
+      </i18n>
+    </div>
     <div v-if="isTreeView">
       <thread-tree
         v-for="status in showingTopLevel"
@@ -60,7 +73,7 @@
         :status-content-properties="statusContentProperties"
         :set-status-content-property="setStatusContentProperty"
         :toggle-status-content-property="toggleStatusContentProperty"
-        :dive="diveIntoStatus"
+        :dive="canDive ? diveIntoStatus : undefined"
       />
     </div>
     <div v-if="isLinearView">
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index c2df6021..47d35de8 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -220,7 +220,7 @@
                   />
                 </button>
                 <button
-                  v-if="inThreadForest && replies && replies.length"
+                  v-if="inThreadForest && replies && replies.length && toggleThreadDisplay"
                   class="button-unstyled"
                   :title="threadShowing ? $t('status.thread_hide') : $t('status.thread_show')"
                   :aria-expanded="threadShowing ? 'true' : 'false'"
diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js
index 3e8eedb1..ec7d5884 100644
--- a/src/components/thread_tree/thread_tree.js
+++ b/src/components/thread_tree/thread_tree.js
@@ -1,6 +1,7 @@
 import Status from '../status/status.vue'
 
-const debug = console.log
+// const debug = console.log
+const debug = () => {}
 
 const ThreadTree = {
   components: {
diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue
index adf7bcdf..f0a103d1 100644
--- a/src/components/thread_tree/thread_tree.vue
+++ b/src/components/thread_tree/thread_tree.vue
@@ -74,7 +74,10 @@
         class="button-unstyled -link thread-tree-show-replies-button"
         @click.prevent="showThreadRecursively(status.id)"
       >
-        <FAIcon place="icon" icon="angle-double-down" />
+        <FAIcon
+          place="icon"
+          icon="angle-double-down"
+        />
         <span place="text">
           {{ $tc('status.thread_show_full', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }}
         </span>
-- 
cgit v1.2.3-70-g09d2


From d9a9f97751b2f51f55848652e5126700aea0f3fe Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sat, 7 Aug 2021 18:53:23 -0400
Subject: Add simple tree style navigation

---
 src/components/conversation/conversation.js       | 70 ++++++++++++++++++++---
 src/components/conversation/conversation.vue      |  3 +-
 src/components/settings_modal/tabs/general_tab.js |  2 +-
 src/components/status/status.js                   | 16 ++++--
 src/components/status/status.vue                  |  4 +-
 src/components/thread_tree/thread_tree.js         | 14 ++++-
 src/components/thread_tree/thread_tree.vue        | 22 ++++++-
 src/modules/instance.js                           |  2 +-
 8 files changed, 113 insertions(+), 20 deletions(-)

(limited to 'src/components/status/status.vue')

diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index b2af1d6c..a1991888 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -80,7 +80,10 @@ const conversation = {
       return this.$store.state.config.conversationDisplay
     },
     isTreeView () {
-      return this.displayStyle === 'tree'
+      return this.displayStyle === 'tree' || this.displayStyle === 'simple_tree'
+    },
+    treeViewIsSimple () {
+      return this.displayStyle === 'simple_tree'
     },
     isLinearView () {
       return this.displayStyle === 'linear'
@@ -297,6 +300,14 @@ const conversation = {
     },
     canDive () {
       return this.isTreeView && this.isExpanded
+    },
+    focused () {
+      return (id) => {
+        return (this.isExpanded) && id === this.highlight
+      }
+    },
+    maybeHighlight () {
+      return this.isExpanded ? this.highlight : null
     }
   },
   components: {
@@ -316,6 +327,9 @@ const conversation = {
     expanded (value) {
       if (value) {
         this.fetchConversation()
+      } else {
+        // if we collapse it, we should reset the dive
+        this._diven = false
       }
     },
     virtualHidden (value) {
@@ -323,6 +337,9 @@ const conversation = {
         'setVirtualHeight',
         { statusId: this.statusId, height: `${this.$el.clientHeight}px` }
       )
+    },
+    highlight (value, old) {
+      console.log('highlight:', old, ' => ', value)
     }
   },
   methods: {
@@ -341,7 +358,8 @@ const conversation = {
           'this.threadDisplayStatus ', this.threadDisplayStatus,
           'this.statusId', this.statusId)
         if (this.threadDisplayStatus[this.statusId] === 'hidden') {
-          this.diveIntoStatus(parentOrSelf)
+          this.diveIntoStatus(parentOrSelf, /* preventScroll */ true)
+          this.tryScrollTo(this.statusId)
         }
       }
     },
@@ -365,18 +383,16 @@ const conversation = {
     getReplies (id) {
       return this.replies[id] || []
     },
-    focused (id) {
-      return (this.isExpanded) && id === this.statusId
+    getHighlight () {
+      return this.isExpanded ? this.highlight : null
     },
     setHighlight (id) {
+      console.log('setHighlight', id)
       if (!id) return
       this.highlight = id
       this.$store.dispatch('fetchFavsAndRepeats', id)
       this.$store.dispatch('fetchEmojiReactionsBy', id)
     },
-    getHighlight () {
-      return this.isExpanded ? this.highlight : null
-    },
     toggleExpanded () {
       this.expanded = !this.expanded
     },
@@ -420,14 +436,52 @@ const conversation = {
     toggleStatusContentProperty (id, name) {
       this.setStatusContentProperty(id, name, !this.statusContentProperties[id][name])
     },
-    diveIntoStatus (id) {
+    leastShowingAncestor (id) {
+      let cur = id
+      let parent = this.parentOf(cur)
+      while (cur) {
+        // if the parent is showing it means cur is visible
+        if (this.threadDisplayStatus[parent] === 'showing') {
+          return cur
+        }
+        parent = this.parentOf(parent)
+        cur = this.parentOf(cur)
+      }
+      // nothing found, fall back to toplevel
+      return topLevel[0].id
+    },
+    diveIntoStatus (id, preventScroll) {
       this.diveHistory = [...this.diveHistory, id]
+      if (!preventScroll) {
+        this.goToCurrent()
+      }
     },
     diveBack () {
+      const oldHighlight = this.highlight
       this.diveHistory = [...this.diveHistory.slice(0, this.diveHistory.length - 1)]
+      if (oldHighlight) {
+        this.tryScrollTo(this.leastShowingAncestor(oldHighlight))
+      }
     },
     undive () {
+      const oldHighlight = this.highlight
       this.diveHistory = []
+      if (oldHighlight) {
+        this.tryScrollTo(this.leastShowingAncestor(oldHighlight))
+      } else {
+        this.goToCurrent()
+      }
+    },
+    tryScrollTo (id) {
+      if (this.isPage) {
+        // set statusId
+        this.$router.push({ name: 'conversation', params: { id } })
+      }
+
+      this.setHighlight(id)
+    },
+    goToCurrent () {
+      this.tryScrollTo(this.diveRoot || this.topLevel[0].id)
     },
     statusById (id) {
       return this.statusMap[id]
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index 99bc7bcc..0ffd8c37 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -61,10 +61,11 @@
 
         :focused="focused"
         :get-replies="getReplies"
-        :get-highlight="getHighlight"
+        :highlight="maybeHighlight"
         :set-highlight="setHighlight"
         :toggle-expanded="toggleExpanded"
 
+        :simple="treeViewIsSimple"
         :toggle-thread-display="toggleThreadDisplay"
         :thread-display-status="threadDisplayStatus"
         :show-thread-recursively="showThreadRecursively"
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index abe8f6f7..c43abe8a 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', 'linear'].map(mode => ({
+      conversationDisplayOptions: ['tree', 'simple_tree', 'linear'].map(mode => ({
         key: mode,
         value: mode,
         label: this.$t(`settings.conversation_display_${mode}`)
diff --git a/src/components/status/status.js b/src/components/status/status.js
index f119f42e..15c2d029 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -97,6 +97,7 @@ const Status = {
     'inProfile',
     'profileUserId',
 
+    'simpleTree',
     'controlledThreadDisplayStatus',
     'controlledToggleThreadDisplay',
 
@@ -379,10 +380,9 @@ const Status = {
     },
     toggleThreadDisplay () {
       this.controlledToggleThreadDisplay()
-    }
-  },
-  watch: {
-    'highlight': function (id) {
+    },
+    scrollIfHighlighted (highlightId) {
+      const id = highlightId
       if (this.status.id === id) {
         let rect = this.$el.getBoundingClientRect()
         if (rect.top < 100) {
@@ -396,6 +396,14 @@ const Status = {
           window.scrollBy(0, rect.bottom - window.innerHeight + 50)
         }
       }
+    }
+  },
+  mounted () {
+    this.scrollIfHighlighted(this.highlight)
+  },
+  watch: {
+    'highlight': function (id) {
+      this.scrollIfHighlighted(id)
     },
     'status.repeat_num': function (num) {
       // refetch repeats when repeat_num is changed in any way
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 47d35de8..e4812711 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -220,7 +220,7 @@
                   />
                 </button>
                 <button
-                  v-if="inThreadForest && replies && replies.length && toggleThreadDisplay"
+                  v-if="inThreadForest && replies && replies.length && !simpleTree"
                   class="button-unstyled"
                   :title="threadShowing ? $t('status.thread_hide') : $t('status.thread_show')"
                   :aria-expanded="threadShowing ? 'true' : 'false'"
@@ -233,7 +233,7 @@
                   />
                 </button>
                 <button
-                  v-if="dive"
+                  v-if="dive && !simpleTree"
                   class="button-unstyled"
                   :title="$t('status.show_only_conversation_under_this')"
                   @click.prevent="dive"
diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js
index ec7d5884..cb1d2242 100644
--- a/src/components/thread_tree/thread_tree.js
+++ b/src/components/thread_tree/thread_tree.js
@@ -1,5 +1,16 @@
 import Status from '../status/status.vue'
 
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+  faAngleDoubleDown,
+  faAngleDoubleRight
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+  faAngleDoubleDown,
+  faAngleDoubleRight
+)
+
 // const debug = console.log
 const debug = () => {}
 
@@ -19,11 +30,12 @@ const ThreadTree = {
     profileUserId: String,
 
     focused: Function,
-    getHighlight: Function,
+    highlight: String,
     getReplies: Function,
     setHighlight: Function,
     toggleExpanded: Function,
 
+    simple: Boolean,
     // to control display of the whole thread forest
     toggleThreadDisplay: Function,
     threadDisplayStatus: Object,
diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue
index f0a103d1..b803f80f 100644
--- a/src/components/thread_tree/thread_tree.vue
+++ b/src/components/thread_tree/thread_tree.vue
@@ -9,12 +9,13 @@
       :show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
       :focused="focused(status.id)"
       :in-conversation="isExpanded"
-      :highlight="getHighlight()"
+      :highlight="highlight"
       :replies="getReplies(status.id)"
       :in-profile="inProfile"
       :profile-user-id="profileUserId"
       class="conversation-status conversation-status-treeview status-fadein panel-body"
 
+      :simple-tree="simple"
       :controlled-thread-display-status="threadDisplayStatus[status.id]"
       :controlled-toggle-thread-display="() => toggleThreadDisplay(status.id)"
 
@@ -49,10 +50,11 @@
 
         :focused="focused"
         :get-replies="getReplies"
-        :get-highlight="getHighlight"
+        :highlight="highlight"
         :set-highlight="setHighlight"
         :toggle-expanded="toggleExpanded"
 
+        :simple="simple"
         :toggle-thread-display="toggleThreadDisplay"
         :thread-display-status="threadDisplayStatus"
         :show-thread-recursively="showThreadRecursively"
@@ -69,6 +71,22 @@
       class="thread-tree-replies thread-tree-replies-hidden"
     >
       <i18n
+        v-if="simple"
+        tag="button"
+        path="status.thread_follow_with_icon"
+        class="button-unstyled -link thread-tree-show-replies-button"
+        @click.prevent="dive(status.id)"
+      >
+        <FAIcon
+          place="icon"
+          icon="angle-double-right"
+        />
+        <span place="text">
+          {{ $tc('status.thread_follow', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id] }) }}
+        </span>
+      </i18n>
+      <i18n
+        v-else
         tag="button"
         path="status.thread_show_full_with_icon"
         class="button-unstyled -link thread-tree-show-replies-button"
diff --git a/src/modules/instance.js b/src/modules/instance.js
index a160a557..8a5bbbd1 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -53,7 +53,7 @@ const defaultState = {
   theme: 'pleroma-dark',
   virtualScrolling: true,
   sensitiveByDefault: false,
-  conversationDisplay: 'tree',
+  conversationDisplay: 'simple_tree',
 
   // Nasty stuff
   customEmoji: [],
-- 
cgit v1.2.3-70-g09d2


From 863255d52fdcbbabe45c86c7e36ebafc0f7e1c53 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sun, 5 Sep 2021 16:35:47 -0400
Subject: Make position of other replies button a pref

---
 src/components/conversation/conversation.js        |  9 +++++
 src/components/conversation/conversation.vue       |  7 ++--
 src/components/settings_modal/tabs/general_tab.js  |  5 +++
 src/components/settings_modal/tabs/general_tab.vue | 40 +++++++++++++++-------
 src/components/status/status.js                    |  1 +
 src/components/status/status.vue                   | 15 +++++++-
 src/modules/config.js                              |  2 ++
 src/modules/instance.js                            |  1 +
 8 files changed, 63 insertions(+), 17 deletions(-)

(limited to 'src/components/status/status.vue')

diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 423930af..d4972fbc 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 f0eb88c1..b3d97075 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"
             />
             <div
-              v-if="getReplies(status.id).length > 1"
+              v-if="showOtherRepliesButtonBelowStatus && getReplies(status.id).length > 1"
               class="thread-ancestor-dive-box"
             >
               <div
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index c43abe8a..a963d204 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -25,6 +25,11 @@ const GeneralTab = {
         value: mode,
         label: this.$t(`settings.conversation_display_${mode}`)
       })),
+      conversationOtherRepliesButtonOptions: ['below', 'inside'].map(mode => ({
+        key: mode,
+        value: mode,
+        label: this.$t(`settings.conversation_other_replies_button_${mode}`)
+      })),
       mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(mode => ({
         key: mode,
         value: mode,
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index f97d92c3..d5ae7810 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -161,19 +161,33 @@
             {{ $t('settings.conversation_display') }}
           </ChoiceSetting>
         </li>
-        <li v-if="conversationDisplay !== 'linear'">
-          <label for="maxDepthInThread">
-            {{ $t('settings.max_depth_in_thread') }}
-          </label>
-          <input
-            id="maxDepthInThread"
-            path.number="maxDepthInThread"
-            class="number-input"
-            type="number"
-            min="3"
-            step="1"
-          >
-        </li>
+        <ul
+          v-if="conversationDisplay !== 'linear'"
+          class="setting-list suboptions"
+        >
+          <li>
+            <label for="maxDepthInThread">
+              {{ $t('settings.max_depth_in_thread') }}
+            </label>
+            <input
+              id="maxDepthInThread"
+              path.number="maxDepthInThread"
+              class="number-input"
+              type="number"
+              min="3"
+              step="1"
+            >
+          </li>
+          <li>
+            <ChoiceSetting
+              id="conversationOtherRepliesButton"
+              path="conversationOtherRepliesButton"
+              :options="conversationOtherRepliesButtonOptions"
+            >
+              {{ $t('settings.conversation_other_replies_button') }}
+            </ChoiceSetting>
+          </li>
+        </ul>
         <li>
           <ChoiceSetting
             id="mentionLinkDisplay"
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 15c2d029..700b9764 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 e4812711..bc0aeaf0 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"
           >
-            <span class="faint">{{ $t('status.replies_list') }}</span>
+            <button
+              v-if="showOtherRepliesAsButton"
+              class="button-unstyled -link faint"
+              :title="$tc('status.ancestor_follow', replies.length - 1, { numReplies: replies.length - 1 })"
+              @click.prevent="dive"
+            >
+              {{ $t('status.replies_list') }}
+            </button>
+            <span
+              v-else
+              class="faint"
+            >
+              {{ $t('status.replies_list') }}
+            </span>
             <StatusPopover
               v-for="reply in replies"
               :key="reply.id"
diff --git a/src/modules/config.js b/src/modules/config.js
index b3c7e5ab..a111abca 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -13,6 +13,7 @@ export const multiChoiceProperties = [
   'postContentType',
   'subjectLineBehavior',
   'conversationDisplay', // tree | linear
+  'conversationOtherRepliesButton', // below | inside
   'mentionLinkDisplay' // short | full_for_remote | full
 ]
 
@@ -84,6 +85,7 @@ export const defaultState = {
   virtualScrolling: undefined, // instance default
   sensitiveByDefault: undefined, // instance default
   conversationDisplay: undefined, // instance default
+  conversationOtherRepliesButton: undefined, // instance default
   maxDepthInThread: 6
 }
 
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 853be737..cd17c23d 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -54,6 +54,7 @@ const defaultState = {
   virtualScrolling: true,
   sensitiveByDefault: false,
   conversationDisplay: 'simple_tree',
+  conversationOtherRepliesButton: 'below',
   maxDepthInThread: 6,
 
   // Nasty stuff
-- 
cgit v1.2.3-70-g09d2


From cebb4224ac0143f6969c7d3e907a7d25eb38b4c7 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Wed, 8 Sep 2021 23:22:11 -0400
Subject: 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(-)

(limited to 'src/components/status/status.vue')

diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index bc0aeaf0..31908815 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -347,7 +347,7 @@
             class="replies"
           >
             <button
-              v-if="showOtherRepliesAsButton"
+              v-if="showOtherRepliesAsButton && replies.length > 1"
               class="button-unstyled -link faint"
               :title="$tc('status.ancestor_follow', replies.length - 1, { numReplies: replies.length - 1 })"
               @click.prevent="dive"
-- 
cgit v1.2.3-70-g09d2


From 654996fdbe007850a1d3e92d31e5c68b8fdd6a82 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Wed, 8 Sep 2021 23:28:44 -0400
Subject: Add other replies count for reply list link

---
 src/components/status/status.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/components/status/status.vue')

diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 31908815..45a21d33 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -352,7 +352,7 @@
               :title="$tc('status.ancestor_follow', replies.length - 1, { numReplies: replies.length - 1 })"
               @click.prevent="dive"
             >
-              {{ $t('status.replies_list') }}
+              {{ $tc('status.replies_list_with_others', replies.length - 1, { numReplies: replies.length - 1 }) }}
             </button>
             <span
               v-else
-- 
cgit v1.2.3-70-g09d2

