Merge branch 'from/develop/tusooa/tree-threading' into 'develop'

Add the option to display threads as trees

See merge request pleroma/pleroma-fe!1407
This commit is contained in:
HJ 2022-03-13 17:31:46 +00:00
commit e34d71fc1f
21 changed files with 1168 additions and 95 deletions

View file

@ -35,7 +35,10 @@ import {
faStar,
faEyeSlash,
faEye,
faThumbtack
faThumbtack,
faChevronUp,
faChevronDown,
faAngleDoubleRight
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -52,9 +55,47 @@ library.add(
faEllipsisH,
faEyeSlash,
faEye,
faThumbtack
faThumbtack,
faChevronUp,
faChevronDown,
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: {
@ -89,20 +130,38 @@ const Status = {
'inlineExpanded',
'showPinned',
'inProfile',
'profileUserId'
'profileUserId',
'simpleTree',
'controlledThreadDisplayStatus',
'controlledToggleThreadDisplay',
'showOtherRepliesAsButton',
'controlledShowingTall',
'controlledToggleShowingTall',
'controlledExpandingSubject',
'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
},
@ -318,6 +377,12 @@ const Status = {
},
isSuspendable () {
return !this.replying && this.mediaPlaying.length === 0
},
inThreadForest () {
return !!this.controlledThreadDisplayStatus
},
threadShowing () {
return this.controlledThreadDisplayStatus === 'showing'
}
},
methods: {
@ -340,7 +405,7 @@ const Status = {
this.error = undefined
},
toggleReplying () {
this.replying = !this.replying
controlledOrUncontrolledToggle(this, 'replying')
},
gotoOriginal (id) {
if (this.inConversation) {
@ -360,17 +425,19 @@ 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
}
},
watch: {
'highlight': function (id) {
},
toggleThreadDisplay () {
this.controlledToggleThreadDisplay()
},
scrollIfHighlighted (highlightId) {
const id = highlightId
if (this.status.id === id) {
let rect = this.$el.getBoundingClientRect()
if (rect.top < 100) {
@ -384,6 +451,11 @@ const Status = {
window.scrollBy(0, rect.bottom - window.innerHeight + 50)
}
}
}
},
watch: {
'highlight': function (id) {
this.scrollIfHighlighted(id)
},
'status.repeat_num': function (num) {
// refetch repeats when repeat_num is changed in any way

View file

@ -1,7 +1,5 @@
@import '../../_variables.scss';
$status-margin: 0.75em;
.Status {
min-width: 0;
white-space: normal;
@ -28,15 +26,8 @@ $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;
padding: var(--status-margin, $status-margin);
color: $fallback--faint;
color: var(--faint, $fallback--faint);
display: flex;
@ -49,7 +40,7 @@ $status-margin: 0.75em;
.status-container {
display: flex;
padding: $status-margin;
padding: var(--status-margin, $status-margin);
&.-repeat {
padding-top: 0;
@ -57,7 +48,7 @@ $status-margin: 0.75em;
}
.pin {
padding: $status-margin $status-margin 0;
padding: var(--status-margin, $status-margin) var(--status-margin, $status-margin) 0;
display: flex;
align-items: center;
justify-content: flex-end;
@ -73,7 +64,7 @@ $status-margin: 0.75em;
}
.left-side {
margin-right: $status-margin;
margin-right: var(--status-margin, $status-margin);
}
.right-side {
@ -82,7 +73,7 @@ $status-margin: 0.75em;
}
.usercard {
margin-bottom: $status-margin;
margin-bottom: var(--status-margin, $status-margin);
}
.status-username {
@ -248,7 +239,7 @@ $status-margin: 0.75em;
}
.repeat-info {
padding: 0.4em $status-margin;
padding: 0.4em var(--status-margin, $status-margin);
.repeat-icon {
color: $fallback--cGreen;
@ -294,7 +285,7 @@ $status-margin: 0.75em;
position: relative;
width: 100%;
display: flex;
margin-top: $status-margin;
margin-top: var(--status-margin, $status-margin);
> * {
max-width: 4em;
@ -362,7 +353,7 @@ $status-margin: 0.75em;
}
.favs-repeated-users {
margin-top: $status-margin;
margin-top: var(--status-margin, $status-margin);
}
.stats {
@ -389,7 +380,7 @@ $status-margin: 0.75em;
}
.stat-count {
margin-right: $status-margin;
margin-right: var(--status-margin, $status-margin);
user-select: none;
.stat-title {

View file

@ -221,6 +221,31 @@
class="fa-scale-110"
/>
</button>
<button
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'"
@click.prevent="toggleThreadDisplay"
>
<FAIcon
fixed-width
class="fa-scale-110"
:icon="threadShowing ? 'chevron-up' : 'chevron-down'"
/>
</button>
<button
v-if="dive && !simpleTree"
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>
</div>
<div
@ -308,6 +333,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"
@ -317,7 +348,20 @@
v-if="inConversation && !isPreview && replies && replies.length"
class="replies"
>
<span class="faint">{{ $t('status.replies_list') }}</span>
<button
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"
>
{{ $tc('status.replies_list_with_others', replies.length - 1, { numReplies: replies.length - 1 }) }}
</button>
<span
v-else
class="faint"
>
{{ $t('status.replies_list') }}
</span>
<StatusPopover
v-for="reply in replies"
:key="reply.id"