Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
FloatingGhost 2022-06-08 18:09:16 +01:00
commit f6cf509a04
229 changed files with 9798 additions and 5400 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.$data[toggle] !== undefined || this.$props[toggle] !== undefined) && 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
},
@ -166,6 +225,18 @@ const Status = {
muteWordHits () {
return muteWordHits(this.status, this.muteWords)
},
rtBotStatus () {
return this.statusoid.user.bot
},
botStatus () {
return this.status.user.bot
},
botIndicator () {
return this.botStatus && !this.hideBotIndication
},
rtBotIndicator () {
return this.rtBotStatus && !this.hideBotIndication
},
mentionsLine () {
if (!this.headTailLinks) return []
const writtenSet = new Set(this.headTailLinks.writtenMentions.map(_ => _.url))
@ -186,26 +257,34 @@ const Status = {
return this.mentionsLine.length > 0
},
muted () {
if (this.statusoid.user.id === this.currentUser.id) return false
const reasonsToMute = this.userIsMuted ||
// Thread is muted
status.thread_muted ||
// Wordfiltered
this.muteWordHits.length > 0 ||
// bot status
(this.muteBotStatuses && this.botStatus && !this.compact)
return !this.unmuted && !this.shouldNotMute && reasonsToMute
},
userIsMuted () {
if (this.statusoid.user.id === this.currentUser.id) return false
const { status } = this
const { reblog } = status
const relationship = this.$store.getters.relationship(status.user.id)
const relationshipReblog = reblog && this.$store.getters.relationship(reblog.user.id)
const reasonsToMute = (
// Post is muted according to BE
status.muted ||
return status.muted ||
// Reprööt of a muted post according to BE
(reblog && reblog.muted) ||
// Muted user
relationship.muting ||
// Muted user of a reprööt
(relationshipReblog && relationshipReblog.muting) ||
// Thread is muted
status.thread_muted ||
// Wordfiltered
this.muteWordHits.length > 0
)
const excusesNotToMute = (
(relationshipReblog && relationshipReblog.muting)
},
shouldNotMute () {
const { status } = this
const { reblog } = status
return (
(
this.inProfile && (
// Don't mute user's posts on user timeline (except reblogs)
@ -218,14 +297,26 @@ const Status = {
(this.inConversation && status.thread_muted)
// No excuses if post has muted words
) && !this.muteWordHits.length > 0
return !this.unmuted && !excusesNotToMute && reasonsToMute
},
hideMutedUsers () {
return this.mergedConfig.hideMutedPosts
},
hideMutedThreads () {
return this.mergedConfig.hideMutedThreads
},
hideFilteredStatuses () {
return this.mergedConfig.hideFilteredStatuses
},
hideWordFilteredPosts () {
return this.mergedConfig.hideWordFilteredPosts
},
hideStatus () {
return (this.muted && this.hideFilteredStatuses) || this.virtualHidden
return (!this.shouldNotMute) && (
(this.muted && this.hideFilteredStatuses) ||
(this.userIsMuted && this.hideMutedUsers) ||
(this.status.thread_muted && this.hideMutedThreads) ||
(this.muteWordHits.length > 0 && this.hideWordFilteredPosts)
)
},
isFocused () {
// retweet or root of an expanded conversation
@ -275,6 +366,12 @@ const Status = {
hidePostStats () {
return this.mergedConfig.hidePostStats
},
muteBotStatuses () {
return this.mergedConfig.muteBotStatuses
},
hideBotIndication () {
return this.mergedConfig.hideBotIndication
},
currentUser () {
return this.$store.state.users.currentUser
},
@ -286,6 +383,15 @@ const Status = {
},
isSuspendable () {
return !this.replying && this.mediaPlaying.length === 0
},
inThreadForest () {
return !!this.controlledThreadDisplayStatus
},
threadShowing () {
return this.controlledThreadDisplayStatus === 'showing'
},
visibilityLocalized () {
return this.$i18n.t('general.scope_in_timeline.' + this.status.visibility)
}
},
methods: {
@ -310,7 +416,7 @@ const Status = {
this.error = undefined
},
toggleReplying () {
this.replying = !this.replying
controlledOrUncontrolledToggle(this, 'replying')
},
gotoOriginal (id) {
if (this.inConversation) {
@ -330,17 +436,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) {
@ -354,6 +462,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
@ -370,11 +483,6 @@ const Status = {
'isSuspendable': function (val) {
this.suspendable = val
}
},
filters: {
capitalize: function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
}
}

View file

@ -1,10 +1,10 @@
@import '../../_variables.scss';
$status-margin: 0.75em;
.Status {
min-width: 0;
white-space: normal;
word-wrap: break-word;
word-break: break-word;
&:hover {
--_still-image-img-visibility: visible;
@ -26,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;
@ -47,7 +40,11 @@ $status-margin: 0.75em;
.status-container {
display: flex;
padding: $status-margin;
padding: var(--status-margin, $status-margin);
> * {
min-width: 0;
}
&.-repeat {
padding-top: 0;
@ -55,7 +52,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;
@ -71,7 +68,7 @@ $status-margin: 0.75em;
}
.left-side {
margin-right: $status-margin;
margin-right: var(--status-margin, $status-margin);
}
.right-side {
@ -80,12 +77,11 @@ $status-margin: 0.75em;
}
.usercard {
margin-bottom: $status-margin;
margin-bottom: var(--status-margin, $status-margin);
}
.status-username {
white-space: nowrap;
font-size: 14px;
overflow: hidden;
max-width: 85%;
font-weight: bold;
@ -110,7 +106,7 @@ $status-margin: 0.75em;
.heading-name-row {
display: flex;
justify-content: space-between;
line-height: 18px;
line-height: 1.3;
a {
display: inline-block;
@ -163,19 +159,25 @@ $status-margin: 0.75em;
& .heading-reply-row {
position: relative;
align-content: baseline;
font-size: 12px;
line-height: 160%;
font-size: 0.85em;
margin-top: 0.2em;
line-height: 130%;
max-width: 100%;
align-items: stretch;
}
& .reply-to-popover,
& .reply-to-no-popover {
& .reply-to-no-popover,
& .mentions {
min-width: 0;
margin-right: 0.4em;
flex-shrink: 0;
}
.reply-glued-label {
margin-right: 0.5em;
}
.reply-to-popover {
.reply-to:hover::before {
content: '';
@ -209,7 +211,6 @@ $status-margin: 0.75em;
& .reply-to {
white-space: nowrap;
position: relative;
padding-right: 0.25em;
}
& .mentions-text,
@ -226,8 +227,8 @@ $status-margin: 0.75em;
.replies {
margin-top: 0.25em;
line-height: 18px;
font-size: 12px;
line-height: 1.3;
font-size: 0.85em;
display: flex;
flex-wrap: wrap;
@ -241,7 +242,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;
@ -287,7 +288,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;
@ -355,7 +356,7 @@ $status-margin: 0.75em;
}
.favs-repeated-users {
margin-top: $status-margin;
margin-top: var(--status-margin, $status-margin);
}
.stats {
@ -382,19 +383,19 @@ $status-margin: 0.75em;
}
.stat-count {
margin-right: $status-margin;
margin-right: var(--status-margin, $status-margin);
user-select: none;
.stat-title {
color: var(--faint, $fallback--faint);
font-size: 12px;
font-size: 0.85em;
text-transform: uppercase;
position: relative;
}
.stat-number {
font-weight: bolder;
font-size: 16px;
font-size: 1.1em;
line-height: 1em;
}
@ -408,13 +409,13 @@ $status-margin: 0.75em;
margin-left: 20px;
}
.avatar:not(.repeater-avatar) {
.post-avatar {
width: 40px;
height: 40px;
// TODO define those other way somehow?
// stylelint-disable rscss/class-format
&.avatar-compact {
&.-compact {
width: 32px;
height: 32px;
}

View file

@ -1,6 +1,7 @@
<template>
<div
v-if="!hideStatus"
ref="root"
class="Status"
:class="[{ '-focused': isFocused }, { '-conversation': inlineExpanded }]"
>
@ -77,6 +78,7 @@
<UserAvatar
v-if="retweet"
class="left-side repeater-avatar"
:bot="rtBotIndicator"
:better-shadow="betterShadow"
:user="statusoid.user"
/>
@ -99,6 +101,7 @@
:to="retweeterProfileLink"
>{{ retweeter }}</router-link>
</span>
{{ ' ' }}
<FAIcon
icon="retweet"
class="repeat-icon"
@ -119,16 +122,18 @@
v-if="!noHeading"
class="left-side"
>
<router-link
:to="userProfileLink"
@click.stop.prevent.capture.native="toggleUserExpanded"
<a
:href="$router.resolve(userProfileLink).href"
@click.stop.prevent.capture="toggleUserExpanded"
>
<UserAvatar
class="post-avatar"
:bot="botIndicator"
:compact="compact"
:better-shadow="betterShadow"
:user="status.user"
/>
</router-link>
</a>
</div>
<div class="right-side">
<UserCard
@ -188,7 +193,7 @@
<span
v-if="status.visibility"
class="visibility-icon"
:title="status.visibility | capitalize"
:title="visibilityLocalized"
>
<FAIcon
fixed-width
@ -219,6 +224,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
@ -227,7 +257,7 @@
>
<span
v-if="isReply"
class="glued-label"
class="glued-label reply-glued-label"
>
<StatusPopover
v-if="!isPreview"
@ -246,6 +276,7 @@
icon="reply"
flip="horizontal"
/>
{{ ' ' }}
<span
class="reply-to-text"
>
@ -265,7 +296,6 @@
:url="replyProfileLink"
:user-id="status.in_reply_to_user_id"
:user-screen-name="status.in_reply_to_screen_name"
:first-mention="false"
/>
</span>
@ -306,6 +336,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"
@ -315,7 +351,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"
@ -407,7 +456,11 @@
class="gravestone"
>
<div class="left-side">
<UserAvatar :compact="compact" />
<UserAvatar
class="post-avatar"
:compact="compact"
:bot="botIndicator"
/>
</div>
<div class="right-side">
<div class="deleted-text">