Merge branch 'chat-refactor' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2026-07-23 14:22:48 +03:00
commit 08f5d1d584
9 changed files with 110 additions and 20 deletions

View file

@ -14,6 +14,7 @@ import StatusPopover from 'src/components/status_popover/status_popover.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import UserPopover from 'src/components/user_popover/user_popover.vue' import UserPopover from 'src/components/user_popover/user_popover.vue'
import Timeago from 'src/components/timeago/timeago.vue' import Timeago from 'src/components/timeago/timeago.vue'
import EmojiReactions from 'src/components/emoji_reactions/emoji_reactions.vue'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface' import { useInterfaceStore } from 'src/stores/interface'
@ -25,9 +26,11 @@ import {
faEllipsisH, faEllipsisH,
faTimes, faTimes,
faReply, faReply,
faRetweet,
faStar,
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
library.add(faTimes, faEllipsisH, faCircleNotch, faReply) library.add(faTimes, faEllipsisH, faCircleNotch, faReply, faStar, faRetweet)
const ChatMessage = { const ChatMessage = {
name: 'ChatMessage', name: 'ChatMessage',
@ -52,6 +55,7 @@ const ChatMessage = {
Gallery, Gallery,
LinkPreview, LinkPreview,
ChatMessageDate, ChatMessageDate,
EmojiReactions,
UserPopover, UserPopover,
StatusPopover, StatusPopover,
MentionLink, MentionLink,
@ -153,6 +157,11 @@ const ChatMessage = {
menuOpened: false, menuOpened: false,
} }
}, },
watch: {
focused: function (value) {
this.scrollIfFocused(value)
},
},
methods: { methods: {
onHover(bool) { onHover(bool) {
this.$emit('hover', { this.$emit('hover', {
@ -188,6 +197,22 @@ const ChatMessage = {
this.hovered = false this.hovered = false
this.menuOpened = false this.menuOpened = false
}, },
scrollIfFocused(focused) {
if (this.$el.getBoundingClientRect == null) return
if (focused) {
const rect = this.$el.getBoundingClientRect()
if (rect.top < 100) {
// Post is above screen, match its top to screen top
window.scrollBy(0, rect.top - 100)
} else if (rect.height >= window.innerHeight - 50) {
// Post we want to see is taller than screen so match its top to screen top
window.scrollBy(0, rect.top - 100)
} else if (rect.bottom > window.innerHeight - 50) {
// Post is below screen, match its bottom to screen bottom
window.scrollBy(0, rect.bottom - window.innerHeight + 50)
}
}
},
}, },
} }

View file

@ -11,6 +11,10 @@
} }
} }
.attachments {
min-width: 30em;
}
.chat-message-toolbar { .chat-message-toolbar {
transition: opacity 0.1s; transition: opacity 0.1s;
opacity: 0; opacity: 0;
@ -126,20 +130,26 @@
align-items: flex-start; align-items: flex-start;
} }
.end-spacer {
flex: 1 1 0;
min-width: calc(2.2rem + 0.5rem + 2rem);
}
.reply-indicator { .reply-indicator {
display: flex; display: flex;
place-items: center; place-items: center;
place-content: center; place-content: center;
padding: 0.25em; padding: 0.25em;
width: 1em;
margin: var(--roundness) 0; margin: var(--roundness) 0;
background: var(--border); background: var(--border);
border-radius: var(--roundness); border-radius: var(--roundness);
border: 1px solid var(--border); border: 1px solid var(--border);
}
.end-spacer { + .end-spacer {
flex: 1 1 0; // Compensate for reply indicator
min-width: calc(2.2em + 0.5em + 2em); min-width: calc(2.2rem + 0.5rem + 2rem - (1rem + (1px + 0.25rem) * 2));
}
} }
&.-incoming { &.-incoming {

View file

@ -44,22 +44,24 @@
</span> </span>
</template> </template>
<template #user> <template #user>
<!-- v-if is there because status might not be loaded yet -->
<StatusBody
v-if="customReplyTo && customReplyTo.text.trim().length > 0"
class="reply-body faint"
:status="customReplyTo"
collapse
single-line
/>
<MentionLink <MentionLink
v-else-if="customReplyTo"
class="reply-body" class="reply-body"
:content="replyToName" :content="replyToName"
:url="replyProfileLink" :url="replyProfileLink"
:user-id="message.in_reply_to_user_id" :user-id="message.in_reply_to_user_id"
:user-screen-name="message.in_reply_to_screen_name" :user-screen-name="message.in_reply_to_screen_name"
/> />
<!-- v-if is there because status might not be loaded yet -->
<template v-if="customReplyTo && customReplyTo.text.trim().length > 0">
:
<StatusBody
class="reply-body faint"
:status="customReplyTo"
collapse
single-line
ignore-subject
/>
</template>
</template> </template>
</i18n-t> </i18n-t>
<div <div
@ -143,9 +145,28 @@
:full-content="true" :full-content="true"
> >
<template #footer> <template #footer>
<EmojiReactions :status="message" />
<span <span
class="created-at" class="created-at"
> >
<span
v-if="message.favorited"
>
<FAIcon
class="fa-scale-110"
icon="star"
fixed-width
/>
</span>
<span
v-if="message.repeated"
>
<FAIcon
class="fa-scale-110"
icon="retweet"
fixed-width
/>
</span>
<span <span
v-if="message.visibility" v-if="message.visibility"
class="visibility-icon" class="visibility-icon"

View file

@ -7,6 +7,7 @@ import PostStatusForm from 'src/components/post_status_form/post_status_form.vue
import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue' import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue'
import QuickViewSettings from 'src/components/quick_view_settings/quick_view_settings.vue' import QuickViewSettings from 'src/components/quick_view_settings/quick_view_settings.vue'
import ThreadTree from 'src/components/thread_tree/thread_tree.vue' import ThreadTree from 'src/components/thread_tree/thread_tree.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import { useInterfaceStore } from 'src/stores/interface' import { useInterfaceStore } from 'src/stores/interface'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
@ -426,6 +427,7 @@ const conversation = {
QuickViewSettings, QuickViewSettings,
ChatMessageList, ChatMessageList,
PostStatusForm, PostStatusForm,
RichContent,
}, },
watch: { watch: {
statusId(newVal, oldVal) { statusId(newVal, oldVal) {

View file

@ -10,7 +10,13 @@
class="panel-heading conversation-heading -sticky" class="panel-heading conversation-heading -sticky"
> >
<h1 class="title"> <h1 class="title">
<RichContent
v-if="conversation[0]?.summary"
:html="conversation[0].summary"
/>
<template v-else>
{{ $t('timeline.conversation') }} {{ $t('timeline.conversation') }}
</template>
</h1> </h1>
<button <button
v-if="collapsable" v-if="collapsable"

View file

@ -540,10 +540,9 @@ const Status = {
toggleThreadDisplay() { toggleThreadDisplay() {
this.controlledToggleThreadDisplay() this.controlledToggleThreadDisplay()
}, },
scrollIfFocused(focusedId) { scrollIfFocused(focused) {
if (this.$el.getBoundingClientRect == null) return if (this.$el.getBoundingClientRect == null) return
const id = focusedId if (focused) {
if (this.status.id === id) {
const rect = this.$el.getBoundingClientRect() const rect = this.$el.getBoundingClientRect()
if (rect.top < 100) { if (rect.top < 100) {
// Post is above screen, match its top to screen top // Post is above screen, match its top to screen top

View file

@ -1,6 +1,7 @@
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -15,6 +16,9 @@ library.add(faFile, faMusic, faImage, faLink, faPollH)
const StatusBody = { const StatusBody = {
name: 'StatusBody', name: 'StatusBody',
components: {
RichContent,
},
props: { props: {
status: { status: {
// Main thing // Main thing
@ -44,6 +48,12 @@ const StatusBody = {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
ignoreSubject: {
// Pretend subject line doesn't exist. Useful for chat messages
// to indicate what post reply belongs to
type: Boolean,
default: false,
}
}, },
data() { data() {
return { return {
@ -76,7 +86,7 @@ const StatusBody = {
return this.status.summary.length > 240 return this.status.summary.length > 240
}, },
hasSubject() { hasSubject() {
return !!this.status.summary return !!this.status.summary && !this.ignoreSubject
}, },
// When a status has a subject and is also tall, we should only have one show more/less // When a status has a subject and is also tall, we should only have one show more/less
// button. If the default is to collapse statuses with subjects, we just treat it like // button. If the default is to collapse statuses with subjects, we just treat it like

View file

@ -92,6 +92,23 @@
} }
} }
&.-single-line {
.summary-wrapper {
display: inline-block;
margin: 0;
padding: 0;
border: none;
.summary {
padding: 0;
}
}
.text-wrapper {
display: inline-flex;
}
}
& .tall-status-hider, & .tall-status-hider,
& .tall-subject-hider, & .tall-subject-hider,
& .status-unhider, & .status-unhider,

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="StatusBody" class="StatusBody"
:class="{ '-compact': compact }" :class="{ '-compact': compact, '-single-line': singleLine }"
> >
<div class="body"> <div class="body">
<div <div
@ -36,7 +36,7 @@
</div> </div>
<div <div
class="text-wrapper" class="text-wrapper"
:class="{'-tall-status': hideTallStatus, '-hidden': shouldHide, '-expanded': showingMore}" :class="{'-tall-status': hideTallStatus, '-hidden': shouldHide, '-expanded': showingMore }"
> >
<RichContent <RichContent
v-if="!(singleLine && hasSubject) && !shouldHide" v-if="!(singleLine && hasSubject) && !shouldHide"