feat: extract `StatusQuote` component with handling nested quote and hide preview card

pull/3443/head
TAKAHASHI Shuuji 2025-12-01 22:59:35 +09:00
rodzic 438ee860a9
commit 3ef90023ff
5 zmienionych plików z 107 dodań i 73 usunięć

Wyświetl plik

@ -5,10 +5,12 @@ const {
status,
newer,
withAction = true,
isNested = false,
} = defineProps<{
status: mastodon.v1.Status | mastodon.v1.StatusEdit
newer?: mastodon.v1.Status
withAction?: boolean
isNested?: boolean
}>()
const { translation } = await useTranslation(status, getLanguageCode())
@ -26,42 +28,6 @@ const vnode = computed(() => {
inReplyToStatus: newer,
})
})
function isQuoteType(quote: mastodon.v1.Status['quote']): quote is mastodon.v1.Quote | mastodon.v1.ShallowQuote {
return !!quote
}
function isShallowQuoteType(quote: mastodon.v1.Quote | mastodon.v1.ShallowQuote): quote is mastodon.v1.ShallowQuote {
return 'quotedStatusId' in quote
}
const quoteState = computed(() => {
if (!isQuoteType(status.quote)) {
return null
}
return status.quote.state
})
const shallowQuotedStatus = ref<mastodon.v1.Status | null>(null)
watchEffect(async () => {
if (!isQuoteType(status.quote) || !isShallowQuoteType(status.quote) || !status.quote.quotedStatusId) {
shallowQuotedStatus.value = null
return
}
shallowQuotedStatus.value = await fetchStatus(status.quote.quotedStatusId)
})
const quotedStatus = computed(() => {
if (!isQuoteType(status.quote)) {
return null
}
if (isShallowQuoteType(status.quote)) {
if (!status.quote.quotedStatusId) {
return null
}
return shallowQuotedStatus.value
}
return status.quote.quotedStatus
})
</script>
<template>
@ -74,37 +40,7 @@ const quotedStatus = computed(() => {
<component :is="vnode" v-if="vnode" />
</span>
<div v-else />
<template
v-if="quotedStatus"
>
<StatusCard
v-show="quoteState === 'accepted'"
:status="quotedStatus"
:actions="false"
:newer="newer"
border-1 my-3
/>
<p>(state.state: {{ JSON.stringify(status.quote?.state) }})</p>
<!--
TODO: handle non-accepted quoted post
pending: never;
accepted: never;
rejected: never;
revoked: never;
deleted: never;
unauthorized: never;
pending: The quote has been created but requires the original author's manual approval before it can be displayed to others.
accepted: The quote has been approved by the original author and is ready to be displayed.
rejected: The original author has explicitly rejected the quote, and it will not be displayed.
revoked: The quote was previously accepted but the original author has since revoked it.
deleted: The quote was accepted, but the original post has since been deleted.
unauthorized: The user is not authorized to see the quote (e.g., it was a private post).
blocked_account: The user has blocked the account that was quoted.
blocked_domain: The user has blocked the domain of the account that was quoted.
muted_account: The user has muted the account that was quoted.
-->
</template>
<StatusQuote :status="status" :is-nested="isNested" />
<template v-if="translation.visible">
<div my2 h-px border="b base" bg-base />
<ContentRich v-if="translation.success" class="line-compact" :content="translation.text" :emojis="status.emojis" />

Wyświetl plik

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
const { actions = true, older, newer, hasOlder, hasNewer, main, account, ...props } = defineProps<{
const { actions = true, isNested = false, older, newer, hasOlder, hasNewer, main, account, ...props } = defineProps<{
status: mastodon.v1.Status
followedTag?: string | null
actions?: boolean
@ -9,6 +9,7 @@ const { actions = true, older, newer, hasOlder, hasNewer, main, account, ...prop
hover?: boolean
inNotification?: boolean
isPreview?: boolean
isNested?: boolean
// If we know the prev and next status in the timeline, we can simplify the card
older?: mastodon.v1.Status
@ -211,6 +212,7 @@ const forceShow = ref(false)
:context="context"
:is-preview="isPreview"
:in-notification="inNotification"
:is-nested="isNested"
mb2 :class="{ 'mt-2 mb1': isDM }"
/>
<StatusActions v-if="actions !== false" v-show="!getPreferences(userSettings, 'zenMode')" :status="status" />

Wyświetl plik

@ -7,6 +7,7 @@ const { status, context } = defineProps<{
context?: mastodon.v2.FilterContext | 'details'
isPreview?: boolean
inNotification?: boolean
isNested: boolean
}>()
const isDM = computed(() => status.visibility === 'direct')
@ -41,7 +42,7 @@ const allowEmbeddedMedia = computed(() => status.card?.html && embeddedMediaPref
'ms--3.5 mt--1 ms--1': isDM && context !== 'details',
}"
>
<StatusBody v-if="(!isFiltered && isSensitiveNonSpoiler) || hideAllMedia" :status="status" :newer="newer" :with-action="!isDetails" :class="isDetails ? 'text-xl' : ''" />
<StatusBody v-if="(!isFiltered && isSensitiveNonSpoiler) || hideAllMedia" :status="status" :newer="newer" :with-action="!isDetails" :is-nested="isNested" :class="isDetails ? 'text-xl' : ''" />
<StatusSpoiler :enabled="hasSpoilerOrSensitiveMedia || isFiltered" :filter="isFiltered" :sensitive-non-spoiler="isSensitiveNonSpoiler || hideAllMedia" :is-d-m="isDM">
<template v-if="spoilerTextPresent" #spoiler>
<p>
@ -51,7 +52,7 @@ const allowEmbeddedMedia = computed(() => status.card?.html && embeddedMediaPref
<template v-else-if="filterPhrase" #spoiler>
<p>{{ `${$t('status.filter_hidden_phrase')}: ${filterPhrase}` }}</p>
</template>
<StatusBody v-if="!(isSensitiveNonSpoiler || hideAllMedia)" :status="status" :newer="newer" :with-action="!isDetails" :class="isDetails ? 'text-xl' : ''" />
<StatusBody v-if="!(isSensitiveNonSpoiler || hideAllMedia)" :status="status" :newer="newer" :with-action="!isDetails" :is-nested="isNested" :class="isDetails ? 'text-xl' : ''" />
<StatusTranslation :status="status" />
<StatusPoll v-if="status.poll" :status="status" />
<StatusMedia
@ -60,7 +61,7 @@ const allowEmbeddedMedia = computed(() => status.card?.html && embeddedMediaPref
:is-preview="isPreview"
/>
<StatusPreviewCard
v-if="status.card && !allowEmbeddedMedia"
v-if="status.card && !allowEmbeddedMedia && !isNested"
:card="status.card"
:small-picture-only="status.mediaAttachments?.length > 0"
/>

Wyświetl plik

@ -1,11 +1,12 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
const { actions = true, ...props } = defineProps<{
const { actions = true, isNested = false, ...props } = defineProps<{
status: mastodon.v1.Status
newer?: mastodon.v1.Status
command?: boolean
actions?: boolean
isNested?: boolean
}>()
defineEmits<{
@ -35,7 +36,7 @@ useHydratedHead({
<AccountInfo :account="status.account" />
</AccountHoverWrapper>
</NuxtLink>
<StatusContent :status="status" :newer="newer" context="details" />
<StatusContent :status="status" :newer="newer" context="details" :is-nested="isNested" />
<div flex="~ gap-1" items-center text-secondary text-sm>
<div flex>
<div>{{ createdAt }}</div>

Wyświetl plik

@ -0,0 +1,94 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
const {
status,
isNested = false,
} = defineProps<{
status: mastodon.v1.Status | mastodon.v1.StatusEdit
isNested?: boolean
}>()
function isQuoteType(quote: mastodon.v1.Status['quote']): quote is mastodon.v1.Quote | mastodon.v1.ShallowQuote {
return !!quote
}
function isShallowQuoteType(quote: mastodon.v1.Quote | mastodon.v1.ShallowQuote): quote is mastodon.v1.ShallowQuote {
return 'quotedStatusId' in quote
}
const quoteState = computed(() => {
if (!isQuoteType(status.quote)) {
return null
}
return status.quote.state
})
const shallowQuotedStatus = ref<mastodon.v1.Status | null>(null)
watchEffect(async () => {
if (!isQuoteType(status.quote) || !isShallowQuoteType(status.quote) || !status.quote.quotedStatusId) {
shallowQuotedStatus.value = null
return
}
shallowQuotedStatus.value = await fetchStatus(status.quote.quotedStatusId)
})
const quotedStatus = computed(() => {
if (!isQuoteType(status.quote)) {
return null
}
if (isShallowQuoteType(status.quote)) {
if (!status.quote.quotedStatusId) {
return null
}
return shallowQuotedStatus.value
}
return status.quote.quotedStatus
})
</script>
<template>
<div
v-if="isNested && quotedStatus"
flex b="~ 1" rounded-lg bg-card mt-3 p-3
>
Quoted post by
<AccountInlineInfo :account="quotedStatus.account" :link="false" mx-1 />
</div>
<template
v-else-if="quotedStatus"
>
<StatusCard
v-show="quoteState === 'accepted'"
:status="quotedStatus"
:actions="false"
:is-nested="true"
b="base 1" rounded-lg hover:bg-active my-3
/>
<p>(state.state: {{ JSON.stringify(status.quote?.state) }})</p>
<!--
TODO: handle non-accepted quoted post
pending: never;
accepted: never;
rejected: never;
revoked: never;
deleted: never;
unauthorized: never;
pending: The quote has been created but requires the original author's manual approval before it can be displayed to others.
accepted: The quote has been approved by the original author and is ready to be displayed.
rejected: The original author has explicitly rejected the quote, and it will not be displayed.
revoked: The quote was previously accepted but the original author has since revoked it.
deleted: The quote was accepted, but the original post has since been deleted.
unauthorized: The user is not authorized to see the quote (e.g., it was a private post).
blocked_account: The user has blocked the account that was quoted.
blocked_domain: The user has blocked the domain of the account that was quoted.
muted_account: The user has muted the account that was quoted.
-->
</template>
</template>
<style scoped>
* {
cursor: pointer;
}
</style>