diff --git a/components/status/StatusCard.vue b/components/status/StatusCard.vue index c0ac9acc..b9747484 100644 --- a/components/status/StatusCard.vue +++ b/components/status/StatusCard.vue @@ -15,6 +15,7 @@ const props = withDefaults( // Manual overrides hasOlder?: boolean hasNewer?: boolean + // When looking into a detailed view of a post, we can simplify the replying badges // to the main expanded post main?: mastodon.v1.Status @@ -32,9 +33,13 @@ const status = $computed(() => { const directReply = $computed(() => props.hasNewer || (!!status.inReplyToId && (status.inReplyToId === props.newer?.id || status.inReplyToId === props.newer?.reblog?.id))) // Use reblogged status, connect it to further replies const connectReply = $computed(() => props.hasOlder || status.id === props.older?.inReplyToId || status.id === props.older?.reblog?.inReplyToId) +// Open a detailed status, the replies directly to it +const replyToMain = $computed(() => props.main && props.main.id === status.inReplyToId) const rebloggedBy = $computed(() => props.status.reblog ? props.status.account : null) +const statusRoute = $computed(() => getStatusRoute(status)) + const el = ref<HTMLElement>() const router = useRouter() @@ -47,13 +52,12 @@ function onclick(evt: MouseEvent | KeyboardEvent) { } function go(evt: MouseEvent | KeyboardEvent) { - const route = getStatusRoute(status) if (evt.metaKey || evt.ctrlKey) { - window.open(route.href) + window.open(statusRoute.href) } else { cacheStatus(status) - router.push(route) + router.push(statusRoute) } } @@ -70,17 +74,12 @@ const filter = $computed(() => filterResult?.filter as mastodon.v2.Filter) const filterPhrase = $computed(() => filter?.phrase || (filter as any)?.title) const isFiltered = $computed(() => filterPhrase && (props.context ? filter?.context.includes(props.context) : false)) +const isSelfReply = $computed(() => status.inReplyToAccountId === status.account.id) const collapseRebloggedBy = $computed(() => rebloggedBy?.id === status.account.id) - -// Collapse ReplyingTo badge if it is a self-reply (thread) -const collapseReplyingTo = $computed(() => (!rebloggedBy || collapseRebloggedBy) && status.inReplyToAccountId === status.account.id) - -// Only show avatar in ReplyingTo badge if it was reblogged by the same account or if it is against the main post -const simplifyReplyingTo = $computed(() => - (props.main && props.main.account.id === status.inReplyToAccountId) || (rebloggedBy && rebloggedBy.id === status.inReplyToAccountId), -) - const isDM = $computed(() => status.visibility === 'direct') + +const showUpperBorder = $computed(() => props.newer && !directReply) +const showReplyTo = $computed(() => !replyToMain && !directReply) </script> <template> @@ -88,8 +87,7 @@ const isDM = $computed(() => status.visibility === 'direct') v-if="filter?.filterAction !== 'hide'" :id="`status-${status.id}`" ref="el" - relative flex flex-col gap-1 pl-3 pr-4 pt-1 - class="pb-1.5" + relative flex="~ col gap1" p="l-3 r-4 b-2" :class="{ 'hover:bg-active': hover }" tabindex="0" focus:outline-none focus-visible:ring="2 primary" @@ -97,10 +95,37 @@ const isDM = $computed(() => status.visibility === 'direct') @click="onclick" @keydown.enter="onclick" > - <div v-if="newer && !directReply" w-auto h-1px bg-border /> - <div flex justify-between> + <!-- Upper border --> + <div :h="showUpperBorder ? '1px' : '0'" w-auto bg-border mb-1 /> + + <!-- Line connecting to previous status --> + <template v-if="status.inReplyToAccountId"> + <StatusReplyingTo + v-if="showReplyTo" + ml-6 pt-1 pl-5 + :status="status" + :is-self-reply="isSelfReply" + :class="faded ? 'text-secondary-light' : ''" + /> + <div flex="~ col gap-1" items-center pos="absolute top-0 left-0" w="20.5" z--1> + <template v-if="showReplyTo"> + <div w="1px" h="0.5" border="x base" mt-3 /> + <div w="1px" h="0.5" border="x base" /> + <div w="1px" h="0.5" border="x base" /> + </template> + <div w="1px" h-10 border="x base" /> + </div> + </template> + + <!-- Reblog status & Meta --> + <div flex="~ col" justify-between> <slot name="meta"> - <div v-if="rebloggedBy && !collapseRebloggedBy" relative text-secondary ws-nowrap flex="~" items-center p="t-1 b-0.5 x-1px" bg-base> + <div + v-if="rebloggedBy && !collapseRebloggedBy" + flex="~" items-center + p="t-1 b-0.5 x-1px" + relative text-secondary ws-nowrap + > <div i-ri:repeat-fill me-46px text-primary w-16px h-16px /> <div absolute top-1 ms-24px w-32px h-32px rounded-full> <AccountHoverWrapper :account="rebloggedBy"> @@ -111,11 +136,11 @@ const isDM = $computed(() => status.visibility === 'direct') </div> <AccountInlineInfo font-bold :account="rebloggedBy" :avatar="false" text-sm /> </div> - <div v-else /> </slot> - <StatusReplyingTo v-if="!directReply && !collapseReplyingTo" :status="status" :simplified="!!simplifyReplyingTo" :class="faded ? 'text-secondary-light' : ''" pt1 /> </div> + <div flex gap-3 :class="{ 'text-secondary': faded }"> + <!-- Avatar --> <div relative> <div v-if="collapseRebloggedBy" absolute flex items-center justify-center top--6px px-2px py-3px rounded-full bg-base> <div i-ri:repeat-fill text-primary w-16px h-16px /> @@ -125,24 +150,25 @@ const isDM = $computed(() => status.visibility === 'direct') <AccountBigAvatar :account="status.account" /> </NuxtLink> </AccountHoverWrapper> - <div v-if="connectReply" w-full h-full flex justify-center> - <div class="w-2.5px" bg-primary-light /> + + <div v-if="connectReply" w-full h-full flex mt--3px justify-center> + <div w-1px border="x base" /> </div> </div> + + <!-- Main --> <div flex="~ col 1" min-w-0> + <!-- Account Info --> <div flex items-center space-x-1> <AccountHoverWrapper :account="status.account"> <StatusAccountDetails :account="status.account" /> </AccountHoverWrapper> - <div v-if="!directReply && collapseReplyingTo" flex="~" ps-1 items-center justify-center> - <StatusReplyingTo :collapsed="true" :status="status" :class="faded ? 'text-secondary-light' : ''" /> - </div> <div flex-auto /> <div v-show="!userSettings.zenMode" text-sm text-secondary flex="~ row nowrap" hover:underline> <AccountBotIndicator v-if="status.account.bot" me-2 /> <div flex> <CommonTooltip :content="createdAt"> - <a :title="status.createdAt" :href="getStatusRoute(status).href" @click.prevent="go($event)"> + <a :title="status.createdAt" :href="statusRoute.href" @click.prevent="go($event)"> <time text-sm ws-nowrap hover:underline :datetime="status.createdAt"> {{ timeago }} </time> @@ -153,6 +179,8 @@ const isDM = $computed(() => status.visibility === 'direct') </div> <StatusActionsMore v-if="actions !== false" :status="status" me--2 /> </div> + + <!-- Content --> <StatusContent :status="status" :context="context" mb2 :class="{ 'mt-2 mb1': isDM }" /> <StatusActions v-if="actions !== false" v-show="!userSettings.zenMode" :status="status" /> </div> diff --git a/components/status/StatusReplyingTo.vue b/components/status/StatusReplyingTo.vue index b0fb811d..773fc558 100644 --- a/components/status/StatusReplyingTo.vue +++ b/components/status/StatusReplyingTo.vue @@ -1,10 +1,12 @@ <script setup lang="ts"> import type { mastodon } from 'masto' -const { status, collapsed = false, simplified = false } = defineProps<{ +const { + status, + isSelfReply = false, +} = defineProps<{ status: mastodon.v1.Status - collapsed?: boolean - simplified?: boolean + isSelfReply: boolean }>() const isSelf = $computed(() => status.inReplyToAccountId === status.account.id) @@ -12,21 +14,25 @@ const account = isSelf ? computed(() => status.account) : useAccountById(status. </script> <template> - <div v-if="status.inReplyToAccountId" flex="~ wrap" gap-1 items-end> - <NuxtLink - v-if="status.inReplyToId" - flex="~" items-center h-auto font-bold text-sm text-secondary gap-1 - :to="getStatusInReplyToRoute(status)" - :title="$t('status.replying_to', [account ? getDisplayName(account) : $t('status.someone')])" - > - <template v-if="account"> - <div i-ri:reply-fill :class="collapsed ? '' : 'scale-x-[-1]'" text-secondary-light /> - <template v-if="!collapsed"> - <AccountAvatar v-if="isSelf || simplified || status.inReplyToAccountId === currentUser?.account.id" :account="account" :link="false" w-5 h-5 mx="0.5" /> - <AccountInlineInfo v-else :account="account" :link="false" mx="0.5" /> + <NuxtLink + v-if="status.inReplyToId" + flex="~ gap2" items-center h-auto text-sm text-secondary + :to="getStatusInReplyToRoute(status)" + :title=" $t('status.replying_to', [account ? getDisplayName(account) : $t('status.someone')])" + > + <template v-if="isSelfReply"> + <span btn-text p0 mb-1>{{ $t('status.show_full_thread') }}</span> + </template> + <template v-else> + <div i-ri-chat-1-line /> + <i18n-t keypath="status.replying_to"> + <template v-if="account"> + <AccountInlineInfo :account="account" :link="false" mx1 /> </template> - </template> - <div i-ri:question-answer-line text-secondary-light text-lg /> - </NuxtLink> - </div> + <template v-else> + {{ $t('status.someone') }} + </template> + </i18n-t> + </template> + </NuxtLink> </template> diff --git a/locales/en-US.json b/locales/en-US.json index 44ef5b78..9b9de0ca 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -355,6 +355,7 @@ }, "reblogged": "{0} reblogged", "replying_to": "Replying to {0}", + "show_full_thread": "Show Full thread", "someone": "someone", "spoiler_show_less": "Show less", "spoiler_show_more": "Show more", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 882bd3c1..4b90c156 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -326,6 +326,8 @@ "finished": "已在 {0} 结束" }, "reblogged": "{0} 转发了", + "replying_to": "回复{0}", + "show_full_thread": "显示完整贴文串", "someone": "某人", "spoiler_show_less": "隐藏", "spoiler_show_more": "显示更多", diff --git a/pages/[[server]]/@[account]/[status].vue b/pages/[[server]]/@[account]/[status].vue index 60c54e6f..4f41c93a 100644 --- a/pages/[[server]]/@[account]/[status].vue +++ b/pages/[[server]]/@[account]/[status].vue @@ -91,8 +91,12 @@ onReactivated(() => { <template v-for="(comment, di) of context?.descendants" :key="comment.id"> <StatusCard - :status="comment" :actions="comment.visibility !== 'direct'" context="account" - :older="context?.descendants[di + 1]" :newer="context?.descendants[di - 1]" :has-newer="di === 0" :main="status" + :status="comment" + :actions="comment.visibility !== 'direct'" context="account" + :older="context?.descendants[di + 1]" + :newer="context?.descendants[di - 1]" + :has-newer="di === 0" + :main="status" /> </template> </div>