Merge branch 'develop' into 'tusooa/1222-in-reply-to'

# Conflicts:
#   src/components/status/status.vue
This commit is contained in:
HJ 2024-12-26 23:40:16 +00:00
commit 6335a937c9
464 changed files with 29571 additions and 7423 deletions

View file

@ -0,0 +1,42 @@
export default {
name: 'Post',
selector: '.Status',
states: {
selected: '.-focused'
},
validInnerComponents: [
'Text',
'Link',
'Icon',
'Border',
'Button',
'ButtonUnstyled',
'RichContent',
'Input',
'Avatar',
'Attachment',
'PollGraph'
],
validInnerComponentsLite: [
'Text',
'Link',
'Icon',
'Border',
'ButtonUnstyled',
'RichContent',
'Avatar'
],
defaultRules: [
{
directives: {
background: '--bg'
}
},
{
state: ['selected'],
directives: {
background: '--inheritedBackground, 10'
}
}
]
}

View file

@ -39,7 +39,8 @@ import {
faThumbtack,
faChevronUp,
faChevronDown,
faAngleDoubleRight
faAngleDoubleRight,
faPlay
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -59,7 +60,8 @@ library.add(
faThumbtack,
faChevronUp,
faChevronDown,
faAngleDoubleRight
faAngleDoubleRight,
faPlay
)
const camelCase = name => name.charAt(0).toUpperCase() + name.slice(1)
@ -133,6 +135,7 @@ const Status = {
'showPinned',
'inProfile',
'profileUserId',
'inQuote',
'simpleTree',
'controlledThreadDisplayStatus',
@ -151,6 +154,7 @@ const Status = {
'controlledSetMediaPlaying',
'dive'
],
emits: ['interacted'],
data () {
return {
uncontrolledReplying: false,
@ -159,7 +163,8 @@ const Status = {
uncontrolledMediaPlaying: [],
suspendable: true,
error: null,
headTailLinks: null
headTailLinks: null,
displayQuote: !this.inQuote
}
},
computed: {
@ -227,17 +232,14 @@ const Status = {
muteWordHits () {
return muteWordHits(this.status, this.muteWords)
},
rtBotStatus () {
return this.statusoid.user.bot
},
botStatus () {
return this.status.user.bot
return this.status.user.actor_type === 'Service'
},
botIndicator () {
return this.botStatus && !this.hideBotIndication
showActorTypeIndicator () {
return !this.hideBotIndication
},
rtBotIndicator () {
return this.rtBotStatus && !this.hideBotIndication
sensitiveStatus () {
return this.status.nsfw
},
mentionsLine () {
if (!this.headTailLinks) return []
@ -266,7 +268,9 @@ const Status = {
// Wordfiltered
this.muteWordHits.length > 0 ||
// bot status
(this.muteBotStatuses && this.botStatus && !this.compact)
(this.muteBotStatuses && this.botStatus && !this.compact) ||
// sensitive status
(this.muteSensitiveStatuses && this.sensitiveStatus && !this.compact)
return !this.unmuted && !this.shouldNotMute && reasonsToMute
},
userIsMuted () {
@ -369,9 +373,15 @@ const Status = {
hidePostStats () {
return this.mergedConfig.hidePostStats
},
shouldDisplayFavsAndRepeats () {
return !this.hidePostStats && this.isFocused && (this.combinedFavsAndRepeatsUsers.length > 0 || this.statusFromGlobalRepository.quotes_count)
},
muteBotStatuses () {
return this.mergedConfig.muteBotStatuses
},
muteSensitiveStatuses () {
return this.mergedConfig.muteSensitiveStatuses
},
hideBotIndication () {
return this.mergedConfig.hideBotIndication
},
@ -401,6 +411,44 @@ const Status = {
},
editingAvailable () {
return this.$store.state.instance.editingAvailable
},
hasVisibleQuote () {
return this.status.quote_url && this.status.quote_visible
},
hasInvisibleQuote () {
return this.status.quote_url && !this.status.quote_visible
},
quotedStatus () {
return this.status.quote_id ? this.$store.state.statuses.allStatusesObject[this.status.quote_id] : undefined
},
shouldDisplayQuote () {
return this.quotedStatus && this.displayQuote
},
scrobblePresent () {
if (this.mergedConfig.hideScrobbles) return false
if (!this.status.user.latestScrobble) return false
const value = this.mergedConfig.hideScrobblesAfter.match(/\d+/gs)[0]
const unit = this.mergedConfig.hideScrobblesAfter.match(/\D+/gs)[0]
let multiplier = 60 * 1000 // minutes is smallest unit
switch (unit) {
case 'm':
break
case 'h':
multiplier *= 60 // hour
break
case 'd':
multiplier *= 60 // hour
multiplier *= 24 // day
break
}
const maxAge = Number(value) * multiplier
const createdAt = Date.parse(this.status.user.latestScrobble.created_at)
const age = Date.now() - createdAt
if (age > maxAge) return false
return this.status.user.latestScrobble.artist
},
scrobble () {
return this.status.user.latestScrobble
}
},
methods: {
@ -420,9 +468,11 @@ const Status = {
this.error = error
},
clearError () {
this.$emit('interacted')
this.error = undefined
},
toggleReplying () {
this.$emit('interacted')
controlledOrUncontrolledToggle(this, 'replying')
},
gotoOriginal (id) {
@ -469,6 +519,18 @@ const Status = {
window.scrollBy(0, rect.bottom - window.innerHeight + 50)
}
}
},
toggleDisplayQuote () {
if (this.shouldDisplayQuote) {
this.displayQuote = false
} else if (!this.quotedStatus) {
this.$store.dispatch('fetchStatus', this.status.quote_id)
.then(() => {
this.displayQuote = true
})
} else {
this.displayQuote = true
}
}
},
watch: {

View file

@ -1,5 +1,3 @@
@import '../../_variables.scss';
.Status {
min-width: 0;
white-space: normal;
@ -12,24 +10,8 @@
--_still-image-label-visibility: hidden;
}
&.-focused {
background-color: $fallback--lightBg;
background-color: var(--selectedPost, $fallback--lightBg);
color: $fallback--text;
color: var(--selectedPostText, $fallback--text);
--lightText: var(--selectedPostLightText, $fallback--light);
--faint: var(--selectedPostFaintText, $fallback--faint);
--faintLink: var(--selectedPostFaintLink, $fallback--faint);
--postLink: var(--selectedPostPostLink, $fallback--faint);
--postFaintLink: var(--selectedPostFaintPostLink, $fallback--faint);
--icon: var(--selectedPostIcon, $fallback--icon);
}
.gravestone {
padding: var(--status-margin, $status-margin);
color: $fallback--faint;
color: var(--faint, $fallback--faint);
padding: var(--status-margin);
display: flex;
.deleted-text {
@ -40,7 +22,7 @@
.status-container {
display: flex;
padding: var(--status-margin, $status-margin);
padding: var(--status-margin);
> * {
min-width: 0;
@ -52,7 +34,7 @@
}
.pin {
padding: var(--status-margin, $status-margin) var(--status-margin, $status-margin) 0;
padding: var(--status-margin) var(--status-margin) 0;
display: flex;
align-items: center;
justify-content: flex-end;
@ -68,7 +50,7 @@
}
.left-side {
margin-right: var(--status-margin, $status-margin);
margin-right: var(--status-margin);
}
.right-side {
@ -77,7 +59,7 @@
}
.usercard {
margin-bottom: var(--status-margin, $status-margin);
margin-bottom: var(--status-margin);
}
.status-username {
@ -90,7 +72,7 @@
text-overflow: ellipsis;
--_still_image-label-scale: 0.25;
--emoji-size: 14px;
--emoji-size: 1em;
}
.status-favicon {
@ -135,11 +117,6 @@
.button-unstyled {
padding: 5px;
margin: -5px;
&:hover svg {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
}
.svg-inline--fa {
@ -181,7 +158,7 @@
.reply-to-popover {
.reply-to:hover::before {
content: '';
content: "";
display: block;
position: absolute;
bottom: 0;
@ -197,7 +174,7 @@
&.-strikethrough {
.reply-to::after {
content: '';
content: "";
display: block;
position: absolute;
top: 50%;
@ -243,16 +220,15 @@
}
.repeat-info {
padding: 0.4em var(--status-margin, $status-margin);
padding: 0.4em var(--status-margin);
.repeat-icon {
color: $fallback--cGreen;
color: var(--cGreen, $fallback--cGreen);
color: var(--cGreen);
}
}
.repeater-avatar {
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
border-radius: var(--roundness);
margin-left: 28px;
width: 20px;
height: 20px;
@ -289,7 +265,7 @@
position: relative;
width: 100%;
display: flex;
margin-top: var(--status-margin, $status-margin);
margin-top: var(--status-margin);
> * {
max-width: 4em;
@ -305,6 +281,7 @@
overflow: hidden;
display: flex;
flex-wrap: nowrap;
gap: 1ex;
& .status-username,
& .mute-thread,
@ -336,7 +313,7 @@
margin-left: 0.2em;
&::before {
content: ' ';
content: " ";
}
}
@ -357,7 +334,7 @@
}
.favs-repeated-users {
margin-top: var(--status-margin, $status-margin);
margin-top: var(--status-margin);
}
.stats {
@ -368,27 +345,27 @@
.avatar-row {
flex: 1;
overflow: hidden;
position: relative;
display: flex;
align-items: center;
overflow: hidden;
&::before {
content: '';
content: "";
position: absolute;
height: 100%;
width: 1px;
left: 0;
background-color: var(--faint, $fallback--faint);
background-color: var(--textFaint);
}
}
.stat-count {
margin-right: var(--status-margin, $status-margin);
margin-right: var(--status-margin);
user-select: none;
.stat-title {
color: var(--faint, $fallback--faint);
color: var(--textFaint);
font-size: 0.85em;
text-transform: uppercase;
position: relative;
@ -398,6 +375,7 @@
font-weight: bolder;
font-size: 1.1em;
line-height: 1em;
color: var(--text);
}
&:hover .stat-title {
@ -422,4 +400,22 @@
}
}
}
.quoted-status {
margin-top: 0.5em;
border: 1px solid var(--border);
border-radius: var(--roundness);
&.-unavailable-prompt {
padding: 0.5em;
}
}
.display-quoted-status-button {
margin: 0.5em;
&-icon {
color: inherit;
}
}
}

View file

@ -30,17 +30,30 @@
:at="false"
/>
</small>
<small
v-if="muteSensitiveStatuses && status.nsfw"
class="mute-thread"
>
{{ $t('status.sensitive_muted') }}
</small>
<small
v-if="muteBotStatuses && botStatus"
class="mute-thread"
>
{{ $t('status.bot_muted') }}
</small>
<small
v-if="showReasonMutedThread"
class="mute-thread"
>
{{ $t('status.thread_muted') }}
</small>
<small
v-if="showReasonMutedThread && muteWordHits.length > 0"
class="mute-thread"
>
{{ $t('status.thread_muted_and_words') }}
<span>
{{ $t('status.thread_muted') }}
</span>
<span
v-if="muteWordHits.length > 0"
>
{{ $t('status.thread_muted_and_words') }}
</span>
</small>
<small
class="mute-words"
@ -79,12 +92,12 @@
<UserAvatar
v-if="retweet"
class="left-side repeater-avatar"
:bot="rtBotIndicator"
:show-actor-type-indicator="showActorTypeIndicator"
:better-shadow="betterShadow"
:user="statusoid.user"
/>
<div class="right-side faint">
<span
<bdi
class="status-username repeater-name"
:title="retweeter"
>
@ -101,7 +114,7 @@
v-else
:to="retweeterProfileLink"
>{{ retweeter }}</router-link>
</span>
</bdi>
{{ ' ' }}
<FAIcon
icon="retweet"
@ -133,7 +146,7 @@
>
<UserAvatar
class="post-avatar"
:bot="botIndicator"
:show-actor-type-indicator="showActorTypeIndicator"
:compact="compact"
:better-shadow="betterShadow"
:user="status.user"
@ -180,7 +193,7 @@
<span class="heading-right">
<router-link
class="timeago faint-link"
class="timeago faint"
:to="{ name: 'conversation', params: { id: status.id } }"
>
<Timeago
@ -249,6 +262,47 @@
</button>
</span>
</div>
<div
v-if="scrobblePresent"
class="status-rich-presence"
>
<a
v-if="scrobble.externalLink"
:href="scrobble.externalLink"
target="_blank"
>
{{ scrobble.artist }} {{ scrobble.title }}
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="play"
/>
<span class="status-rich-presence-time">
<Timeago
template-key="time.in_past"
:time="scrobble.created_at"
:auto-update="60"
/>
</span>
</a>
<span v-if="!scrobble.externalLink">
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="music"
/>
{{ scrobble.artist }} {{ scrobble.title }}
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="play"
/>
<span class="status-rich-presence-time">
<Timeago
template-key="time.in_past"
:time="scrobble.created_at"
:auto-update="60"
/>
</span>
</span>
</div>
<div
v-if="isReply || hasMentionsLine"
class="heading-reply-row"
@ -345,6 +399,7 @@
class="heading-edited-row"
>
<i18n-t
scope="global"
keypath="status.edited_at"
tag="span"
>
@ -377,13 +432,55 @@
@parseReady="setHeadTailLinks"
/>
<article
v-if="hasVisibleQuote"
class="quoted-status"
>
<button
class="button-unstyled -link display-quoted-status-button"
:aria-expanded="shouldDisplayQuote"
@click="toggleDisplayQuote"
>
{{ shouldDisplayQuote ? $t('status.hide_quote') : $t('status.display_quote') }}
<FAIcon
class="display-quoted-status-button-icon"
:icon="shouldDisplayQuote ? 'chevron-up' : 'chevron-down'"
/>
</button>
<Status
v-if="shouldDisplayQuote"
:statusoid="quotedStatus"
:in-quote="true"
/>
</article>
<p
v-else-if="hasInvisibleQuote"
class="quoted-status -unavailable-prompt"
>
<i18n-t
scope="global"
keypath="status.invisible_quote"
>
<template #link>
<bdi>
<a
:href="status.quote_url"
target="_blank"
>
{{ status.quote_url }}
</a>
</bdi>
</template>
</i18n-t>
</p>
<div
v-if="inConversation && !isPreview && replies && replies.length"
class="replies"
>
<button
v-if="showOtherRepliesAsButton && replies.length > 1"
class="button-unstyled -link faint"
class="button-unstyled -link"
:title="$tc('status.ancestor_follow', replies.length - 1, { numReplies: replies.length - 1 })"
@click.prevent="dive"
>
@ -411,7 +508,7 @@
<transition name="fade">
<div
v-if="!hidePostStats && isFocused && combinedFavsAndRepeatsUsers.length > 0"
v-if="shouldDisplayFavsAndRepeats"
class="favs-repeated-users"
>
<div class="stats">
@ -439,6 +536,19 @@
</div>
</div>
</UserListPopover>
<router-link
v-if="statusFromGlobalRepository.quotes_count > 0"
:to="{ name: 'quotes', params: { id: status.id } }"
>
<div
class="stat-count"
>
<a class="stat-title">{{ $t('status.quotes') }}</a>
<div class="stat-number">
{{ statusFromGlobalRepository.quotes_count }}
</div>
</div>
</router-link>
<div class="avatar-row">
<AvatarList :users="combinedFavsAndRepeatsUsers" />
</div>
@ -464,14 +574,17 @@
:visibility="status.visibility"
:logged-in="loggedIn"
:status="status"
@click="$emit('interacted')"
/>
<favorite-button
:logged-in="loggedIn"
:status="status"
@click="$emit('interacted')"
/>
<ReactButton
v-if="loggedIn"
:status="status"
@click="$emit('interacted')"
/>
<extra-buttons
:status="status"
@ -489,7 +602,7 @@
<UserAvatar
class="post-avatar"
:compact="compact"
:bot="botIndicator"
:show-actor-type-indicator="showActorTypeIndicator"
/>
</div>
<div class="right-side">