From 3db36c25eeab01e1bbf85d3989af861be05716e3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 10 Jul 2026 17:09:17 +0300 Subject: [PATCH] move SABs margin into status --- src/components/chat_message/chat_message.js | 22 ++++++-- src/components/chat_message/chat_message.scss | 46 +++++++++++++---- src/components/chat_message/chat_message.vue | 50 ++++++++++++++----- .../chat_message_list/chat_message_list.js | 18 ++++++- .../chat_message_list/chat_message_list.vue | 4 +- src/components/conversation/conversation.js | 11 +++- src/components/conversation/conversation.scss | 23 +++++++++ src/components/conversation/conversation.vue | 38 ++++++++++++-- src/components/status/status.scss | 4 ++ src/components/status/status.vue | 1 + .../status_action_buttons/action_button.js | 5 +- .../status_action_buttons/action_button.vue | 2 +- .../action_button_container.js | 2 +- .../action_button_container.vue | 2 + .../status_action_buttons.js | 37 ++++++++++++-- .../status_action_buttons.scss | 7 ++- .../status_action_buttons.vue | 5 ++ src/i18n/en.json | 2 + 18 files changed, 235 insertions(+), 44 deletions(-) diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js index 59e1db57d..b3436191b 100644 --- a/src/components/chat_message/chat_message.js +++ b/src/components/chat_message/chat_message.js @@ -1,3 +1,4 @@ +import { find } from 'lodash' import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'vuex' @@ -7,6 +8,8 @@ import Gallery from 'src/components/gallery/gallery.vue' import LinkPreview from 'src/components/link-preview/link-preview.vue' import Popover from 'src/components/popover/popover.vue' import StatusContent from 'src/components/status_content/status_content.vue' +import StatusBody from 'src/components/status_body/status_body.vue' +import StatusActionButtons from 'src/components/status_action_buttons/status_action_buttons.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import UserPopover from 'src/components/user_popover/user_popover.vue' @@ -25,18 +28,19 @@ library.add(faTimes, faEllipsisH, faCircleNotch) const ChatMessage = { name: 'ChatMessage', - props: ['edited', 'noHeading', 'chatItem', 'hoveredMessageChain'], - emits: ['hover'], + props: ['edited', 'noHeading', 'previousItem', 'chatItem', 'previousItem', 'hoveredMessageChain'], + emits: ['hover', 'replyRequested'], components: { Popover, Attachment, StatusContent, + StatusBody, + StatusActionButtons, UserAvatar, Gallery, LinkPreview, ChatMessageDate, - UserPopover, - }, + UserPopover,}, computed: { // Returns HH:MM (hours and minutes) in local time. createdAt() { @@ -65,6 +69,16 @@ const ChatMessage = { isMessage() { return this.chatItem.type === 'message' }, + isCustomReply() { + if (!this.previousItem) return false + console.log('==') + console.log('PREV', toValue(this.previousItem.data.raw_html)) + console.log('CURR', toValue(this.chatItem.data.raw_html)) + return this.previousItem.data.id !== this.chatItem.data.in_reply_to_status_id + }, + customReplyTo() { + return find(this.$store.state.statuses.allStatuses, { id: this.chatItem.data.in_reply_to_status_id }) + }, messageForStatusContent() { return { summary: '', diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss index 2f65fbd11..34382c42d 100644 --- a/src/components/chat_message/chat_message.scss +++ b/src/components/chat_message/chat_message.scss @@ -11,22 +11,53 @@ } } - .chat-message-menu { + .chat-message-toolbar { transition: opacity 0.1s; opacity: 0; position: absolute; top: -0.8em; + right: 0.4rem; + z-index: 10; - button { + .quick-action-buttons { + justify-items: end; + grid-template-columns: auto auto auto; + } + + .simple-button { padding-top: 0.2em; padding-bottom: 0.2em; } + + &.-visible { + opacity: 1; + } } .menu-icon { cursor: pointer; } + .reply-to-header { + white-space: nowrap; + width: 100%; + display: flex; + align-items: baseline; + gap: 0.5em; + + .reply-label { + display: inline-block; + line-height: 1; + } + + .reply-body { + line-height: 1; + display: inline-block; + overflow-x: hidden; + text-overflow: ellipsis; + } + } + .popover { width: 12em; } @@ -66,7 +97,6 @@ font-size: 0.8em; margin: -1em 0 -0.5em; font-style: italic; - opacity: 0.8; } .without-attachment { @@ -112,14 +142,8 @@ align-items: flex-end; } - .chat-message-menu { - right: 0.4rem; - } - } - - .-incoming { - .chat-message-menu { - left: 0.4rem; + .reply-to-header { + justify-content: end; } } diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue index 971d9ebd6..b1fa85fe7 100644 --- a/src/components/chat_message/chat_message.vue +++ b/src/components/chat_message/chat_message.vue @@ -25,6 +25,25 @@
+ + + + {{ $t('status.reply_to') }} + + +
+ +
@@ -87,9 +113,9 @@ :title="visibilityLocalized" > {{ createdAt }} diff --git a/src/components/chat_message_list/chat_message_list.js b/src/components/chat_message_list/chat_message_list.js index b0c078819..8197e813a 100644 --- a/src/components/chat_message_list/chat_message_list.js +++ b/src/components/chat_message_list/chat_message_list.js @@ -20,7 +20,7 @@ const ChatMessageList = { hoveredMessageChainId: undefined, } }, - emits: ['messageDelete'], + emits: ['messageDelete', 'replyRequested'], computed: { chatItems() { const messages = [ @@ -107,6 +107,22 @@ const ChatMessageList = { onMessageDelete({ messageId, chatId }) { this.$emit('messageDelete', { messageId, chatId }) }, + onReplyRequested(message) { + this.$emit('replyRequested', message) + }, + getPreviousItem(index) { + let result + + this.chatItems.slice(0, index).reverse().some((item) => { + const isMessage = item.type === 'message' + if (isMessage) { + result = item + } + return isMessage + }) + + return result + } }, } diff --git a/src/components/chat_message_list/chat_message_list.vue b/src/components/chat_message_list/chat_message_list.vue index 4fc52155c..cd1688a8c 100644 --- a/src/components/chat_message_list/chat_message_list.vue +++ b/src/components/chat_message_list/chat_message_list.vue @@ -1,12 +1,14 @@ diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 7c03ae469..67ed1c307 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -2,6 +2,7 @@ import { clone, filter, findIndex, get, reduce } from 'lodash' import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'vuex' +import StatusContent from 'src/components/status_content/status_content.vue' import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue' import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue' @@ -20,9 +21,11 @@ import { faAngleDoubleDown, faAngleDoubleLeft, faChevronLeft, + faReply, + faTimes, } from '@fortawesome/free-solid-svg-icons' -library.add(faAngleDoubleDown, faAngleDoubleLeft, faChevronLeft) +library.add(faAngleDoubleDown, faAngleDoubleLeft, faChevronLeft, faReply, faTimes) const sortById = (a, b) => { const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id @@ -105,6 +108,7 @@ const conversation = { inlineDivePosition: null, loadStatusError: null, unsuspendibleIds: new Set(), + explicitReplyStatus: null, } }, created() { @@ -121,9 +125,11 @@ const conversation = { return maxDepth >= 1 ? maxDepth : 1 }, lastStatus() { - console.log('LAST STATUS', this.conversation[this.conversation.length - 1]) return this.conversation[this.conversation.length - 1] }, + replyStatus() { + return this.explicitReplyStatus ?? this.lastStatus + }, streamingEnabled() { return ( this.mergedConfig.useStreamingApi && @@ -415,6 +421,7 @@ const conversation = { QuickViewSettings, ChatMessageList, PostStatusForm, + StatusContent, }, watch: { statusId(newVal, oldVal) { diff --git a/src/components/conversation/conversation.scss b/src/components/conversation/conversation.scss index 0fc320141..8bd391703 100644 --- a/src/components/conversation/conversation.scss +++ b/src/components/conversation/conversation.scss @@ -93,8 +93,31 @@ } } + .auto-reply-to-section { + margin: 0 1em; + gap: 0.5em; + + h4 { + margin: 0.5em 0; + line-height: 1.5; + + button { + line-height: 1.5; + } + } + + .reply-to-preview { + display: flex; + align-items: center; + gap: 0.5em; + } + } + .chat-view-reply-form { position: sticky; + display: flex; + flex-direction: column; + align-items: stretch; bottom: 0; } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 5d4e94a2e..f2cc5168f 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -202,22 +202,50 @@ >
diff --git a/src/components/status/status.scss b/src/components/status/status.scss index f710088d6..1214d8b12 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -385,4 +385,8 @@ text-decoration: underline; } } + + .status-action-buttons { + margin-top: var(--status-margin); + } } diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 4dcc1e07d..8c7ac9429 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -512,6 +512,7 @@ {{ button.counter?.(funcArg) }} diff --git a/src/components/status_action_buttons/action_button_container.js b/src/components/status_action_buttons/action_button_container.js index 1f4cfc3f6..4d316741d 100644 --- a/src/components/status_action_buttons/action_button_container.js +++ b/src/components/status_action_buttons/action_button_container.js @@ -42,7 +42,7 @@ export default { ), ), }, - props: ['button', 'status'], + props: ['button', 'status', 'defaultButton','hideLabel'], emits: ['emojiPickerShown'], mounted() { if (this.button.name === 'mute') { diff --git a/src/components/status_action_buttons/action_button_container.vue b/src/components/status_action_buttons/action_button_container.vue index 24f9a3c3b..72b0136df 100644 --- a/src/components/status_action_buttons/action_button_container.vue +++ b/src/components/status_action_buttons/action_button_container.vue @@ -10,6 +10,7 @@ @@ -131,6 +132,7 @@ v-else :button="button" :status="status" + :hide-label="hideLabel" v-bind="$attrs" @emoji-picker-shown="e => $emit('emojiPickerShown', e)" /> diff --git a/src/components/status_action_buttons/status_action_buttons.js b/src/components/status_action_buttons/status_action_buttons.js index e02b71b0c..d952faa6a 100644 --- a/src/components/status_action_buttons/status_action_buttons.js +++ b/src/components/status_action_buttons/status_action_buttons.js @@ -15,7 +15,31 @@ import { faEllipsisH } from '@fortawesome/free-solid-svg-icons' library.add(faEllipsisH) const StatusActionButtons = { - props: ['status', 'replying'], + props: { + status: { + type: Object, + required: true, + }, + replying: { + type: Boolean, + default: false, + }, + fixedPinned: { + type: Boolean, + default: false, + }, + pinned: { + type: Set, + }, + useDefaultButtons: { + type: Boolean, + default: false, + }, + hideLabels: { + type: Boolean, + default: false, + } + }, emits: ['toggleReplying', 'onSuccess', 'onError'], data() { return { @@ -36,14 +60,19 @@ const StatusActionButtons = { ConfirmModal: defineAsyncComponent( () => import('src/components/confirm_modal/confirm_modal.vue'), ), - ActionButtonContainer, }, computed: { ...mapState(useSyncConfigStore, { - pinnedItems: (store) => - new Set(store.prefsStorage.collections.pinnedStatusActions), + userPinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedStatusActions), }), + pinnedItems() { + if (this.fixedPinned) { + return this.pinned + } else { + return this.userPinnedItems + } + }, buttons() { return BUTTONS.filter((x) => (x.if ? x.if(this.funcArg) : true)) }, diff --git a/src/components/status_action_buttons/status_action_buttons.scss b/src/components/status_action_buttons/status_action_buttons.scss index db149b418..b37343203 100644 --- a/src/components/status_action_buttons/status_action_buttons.scss +++ b/src/components/status_action_buttons/status_action_buttons.scss @@ -8,7 +8,6 @@ grid-auto-flow: row dense; grid-auto-rows: 1fr; grid-gap: 0.5em 0.1em; - margin-top: var(--status-margin); } .pin-action-button { @@ -17,6 +16,12 @@ padding: 0.5em; margin: 0; } + + .quick-action.popover-wrapper { + button { + padding: 0 + } + } } // popover .extra-action-buttons { diff --git a/src/components/status_action_buttons/status_action_buttons.vue b/src/components/status_action_buttons/status_action_buttons.vue index e0432048a..34e5e25ea 100644 --- a/src/components/status_action_buttons/status_action_buttons.vue +++ b/src/components/status_action_buttons/status_action_buttons.vue @@ -21,6 +21,8 @@ :close="() => { /* no-op */ }" :do-action="doAction" @emoji-picker-shown="onEmojiPickerShown" + :default-button-style="useDefaultButtons" + :hide-label="hideLabels" />