Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85805622
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
63 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js
index 47effda73e..e70927f7b8 100644
--- a/src/components/chat_message/chat_message.js
+++ b/src/components/chat_message/chat_message.js
@@ -1,259 +1,258 @@
import { mapState } from 'pinia'
import { defineAsyncComponent } from 'vue'
import Attachment from 'src/components/attachment/attachment.vue'
import ChatMessageDate from 'src/components/chat_message_date/chat_message_date.vue'
import EmojiReactions from 'src/components/emoji_reactions/emoji_reactions.vue'
import Gallery from 'src/components/gallery/gallery.vue'
import LinkPreview from 'src/components/link-preview/link-preview.vue'
import MentionLink from 'src/components/mention_link/mention_link.vue'
import Popover from 'src/components/popover/popover.vue'
import StatusActionButtons from 'src/components/status_action_buttons/status_action_buttons.vue'
import StatusBody from 'src/components/status_body/status_body.vue'
import StatusContent from 'src/components/status_content/status_content.vue'
import StatusPopover from 'src/components/status_popover/status_popover.vue'
import Timeago from 'src/components/timeago/timeago.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import UserPopover from 'src/components/user_popover/user_popover.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faCircleNotch,
faEllipsisH,
faReply,
faRetweet,
faStar,
faTimes,
} from '@fortawesome/free-solid-svg-icons'
library.add(faTimes, faEllipsisH, faCircleNotch, faReply, faStar, faRetweet)
const ChatMessage = {
name: 'ChatMessage',
props: [
'edited',
- 'noHeading',
'chatItem',
'hoveredMessageChain',
'focused',
'repliedTo',
],
emits: ['hover', 'replyRequested', 'heightChange', 'suspendableStateChange'],
data() {
return {
resizeObserver: new ResizeObserver(this.updateVirtualHeight),
mediaPlaying: new Set(),
hovered: false,
menuOpened: false,
}
},
components: {
Popover,
Attachment,
StatusContent,
StatusBody,
StatusActionButtons,
UserAvatar,
Gallery,
LinkPreview,
ChatMessageDate,
EmojiReactions,
UserPopover,
StatusPopover,
MentionLink,
Quote: defineAsyncComponent(() => import('src/components/quote/quote.vue')),
Timeago,
},
inject: ['deleteChatMessage'],
mounted() {
if (this.$refs.root) {
this.resizeObserver.observe(this.$refs.root)
this.updateVirtualHeight([
{
contentRect: this.$refs.root.getBoundingClientRect(),
},
])
}
},
unmounted() {
this.resizeObserver.disconnect()
},
computed: {
isMessage() {
return this.chatItem.type === 'message'
},
message() {
if (!this.isMessage) return null
return this.chatItem.data.retweeted_status ?? this.chatItem.data
},
previousItem() {
return this.chatItem.olderMessage
},
isStatus() {
// ChatMessage only has account_id while Status has full user data
return !!this.message.user
},
authorId() {
return this.isStatus ? this.message.user.id : this.message.account_id
},
author() {
return useUsersStore().findUser(this.authorId)
},
isCurrentUser() {
// mini-hack/optimizaiton:
// - current user would always be in memory so if user is missing it's obviously not us
// - if anon views page then "us" pretty much doesn't exist
if (!this.author || !this.currentUser) return false
return this.author.id === this.currentUser.id
},
// Reply stuff
isCustomReply() {
if (!this.previousItem) return false
if (!this.message.in_reply_to_status_id) return false
return this.previousItem.id !== this.message.in_reply_to_status_id
},
isBrokenReply() {
if (!this.previousItem) return false
return !this.message.in_reply_to_status_id
},
customReplyTo() {
return useStatusesStore().allStatuses.get(
this.message.in_reply_to_status_id,
)
},
replyToName() {
if (this.message.in_reply_to_screen_name) {
return this.message.in_reply_to_screen_name
} else {
const user = useUsersStore().findUser(this.message.in_reply_to_user_id)
return user?.screen_name_ui
}
},
replyProfileLink() {
if (this.isCustomReply) {
const user = useUsersStore().findUser(this.message.in_reply_to_user_id)
return user.statusnet_profile_url
}
},
// Quote stuff
quoteId() {
return this.message.quote_id
},
quoteUrl() {
return this.message.quote_url
},
quoteVisible() {
return this.message.quote_visible
},
// Content
messageForStatusContent() {
return {
...this.message,
summary: '',
emojis: this.message.emojis,
raw_html: this.message.content || this.message.raw_html || '',
text: this.message.content || '',
}
},
hasAttachment() {
return this.message.attachments.length > 0
},
// Stylistic
classnames() {
return {
'-outgoing': this.isCurrentUser,
'-incoming': !this.isCurrentUser,
'-pending': this.message.pending,
'-focused': this.focused,
}
},
popoverMarginStyle() {
if (this.isCurrentUser) {
return {}
} else {
return { left: 50 }
}
},
isSuspendable() {
return this.mediaPlaying.size === 0
},
// Global stuff
...mapState(useInterfaceStore, {
betterShadow: (store) => store.browserSupport.cssFilter,
}),
...mapState(useUsersStore, ['currentUser']),
...mapState(useInstanceStore, ['restrictedNicknames']),
...mapState(useMergedConfigStore, ['mergedConfig']),
},
methods: {
onHover(bool) {
this.$emit('hover', {
isHovered: bool,
messageChainId: this.chatItem.messageChainId,
})
},
updateVirtualHeight(e) {
const [entry] = e
this.$emit('heightChange', {
id: this.chatItem.id,
height: entry.contentRect.height + 1,
element: this.$el,
})
},
visibilityIcon(visibility) {
switch (visibility) {
case 'private':
return 'lock'
case 'unlisted':
return 'lock-open'
case 'direct':
return 'envelope'
case 'local':
return 'igloo'
default:
return 'globe'
}
},
visibilityLocalized() {
return this.$i18n.t('general.scope_in_timeline.' + this.status.visibility)
},
async deleteMessage() {
const confirmed = window.confirm(this.$t('chats.delete_confirm'))
if (confirmed) {
await this.deleteChatMessage({
messageId: this.message.id,
chatId: this.message.chat_id,
})
}
this.hovered = false
this.menuOpened = false
},
addMediaPlaying(id) {
this.mediaPlaying.add(id)
},
removeMediaPlaying(id) {
this.mediaPlaying.delete(id)
},
},
watch: {
isSuspendable: function (suspendable) {
this.$emit('suspendableStateChange', {
id: this.chatItem.id,
suspendable,
})
},
},
}
export default ChatMessage
diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss
index 68803500b7..ddb6e7a48b 100644
--- a/src/components/notification/notification.scss
+++ b/src/components/notification/notification.scss
@@ -1,174 +1,143 @@
// TODO Copypaste from Status, should unify it somehow
.Notification {
box-sizing: border-box;
border-bottom: 1px solid;
border-color: var(--border);
overflow-wrap: break-word;
text-wrap: pretty;
.status-content {
cursor: pointer;
margin-top: calc(var(--status-margin) / 2);
}
--emoji-size: 1em;
&:hover {
--_still-image-img-visibility: visible;
--_still-image-canvas-visibility: hidden;
--_still-image-label-visibility: hidden;
}
&.-muted {
padding: 0.25em 0.6em;
height: 1.2em;
line-height: 1.2em;
text-overflow: ellipsis;
overflow: hidden;
display: flex;
flex-wrap: nowrap;
gap: 1ex;
& .status-username,
& .mute-thread,
& .mute-words {
white-space: nowrap;
}
& .status-username,
& .mute-words {
text-overflow: ellipsis;
overflow: hidden;
}
.status-username {
font-weight: normal;
flex: 0 1 auto;
margin-right: 0.2em;
font-size: smaller;
}
.mute-thread {
flex: 0 0 auto;
}
.mute-words {
flex: 1 0 5em;
margin-left: 0.2em;
&::before {
content: " ";
}
}
.unmute {
flex: 0 0 auto;
margin-left: auto;
display: block;
}
}
.type-icon {
margin: 0 0.1em;
}
&.-type--repeat .type-icon {
color: var(--cGreen);
}
&.-type--follow .type-icon {
color: var(--cBlue);
}
&.-type--follow-request .type-icon {
color: var(--cBlue);
}
&.-type--like .type-icon {
color: var(--cOrange);
}
&.-type--move .type-icon {
color: var(--cBlue);
}
.Status {
flex: 1;
}
- &:hover .animated.Avatar {
- canvas {
- display: none;
- }
-
- img {
- visibility: visible;
- }
- }
-
- &.-non-mention {
- display: flex;
- flex: 1;
- flex-wrap: nowrap;
- padding: var(--status-margin);
- gap: var(--status-margin);
- min-width: 0;
- }
-
.follow-request-accept {
&:hover {
color: var(--text);
}
}
.follow-request-reject {
&:hover {
color: var(--cRed);
}
}
.follow-text,
.move-text {
padding: 0.5em 0;
overflow-wrap: break-word;
display: flex;
justify-content: space-between;
.follow-name {
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
- time {
- white-space: nowrap;
- }
-
- .notification-right {
- flex: 1 1 auto;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: calc(var(--status-margin) / 4);
- }
-
.emoji-reaction-emoji {
font-size: 1.3em;
max-width: 1.25em;
height: 1.25em;
width: auto;
&.-wide {
max-width: 3.75em;
}
}
.emoji-reaction-emoji-image {
vertical-align: middle;
object-fit: contain;
}
}
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index bbc97f05d9..8862b23da6 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -1,209 +1,213 @@
<template>
<article
v-if="notification.type === 'mention' || notification.type === 'status'"
ref="root"
>
<Status
class="Notification panel-body"
:compact="true"
:status-id="notification.status?.id"
@click="interacted"
/>
</article>
<article
v-else
ref="root"
class="NotificationParent panel-body"
:class="{ '-expandable': expandable }"
>
<div
v-if="needMute && !unmuted"
:id="'notif-' +notification.id"
:aria-expanded="statusExpanded"
:aria-controls="'notif-' +notification.id"
class="Notification container -muted"
>
<small>
<UserLink
:user="notification.from_profile"
:at="false"
/>
</small>
<button
class="button-unstyled unmute"
@click.prevent="toggleMute"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="eye-slash"
/>
</button>
</div>
<div
v-else
- class="Notification -non-mention"
+ class="Notification -non-mention activity-layout"
:class="[userClass, { highlighted: userStyle }, '-type--' + notification.type]"
:style="[ userStyle ]"
>
- <a
- class="avatar-container"
- :href="$router.resolve(userProfileLink).href"
- @click.prevent
- >
- <UserPopover
- :user-id="notification.from_profile.id"
- :overlay-centers="true"
+ <div class="activity-avatar">
+ <a
+ :href="$router.resolve(userProfileLink).href"
+ @click.prevent
>
- <UserAvatar
- class="post-avatar"
- :compact="true"
+ <UserPopover
:user-id="notification.from_profile.id"
- />
- </UserPopover>
- </a>
- <div class="notification-right">
- <StatusHeader
- :statusoid="status"
- :time="notification.created_at"
- :user-id="notification.from_profile.id"
- @toggle-expanded="toggleStatusExpanded"
- :expanded="statusExpanded"
- non-mention
- />
- <div class="action">
- <span v-if="notification.type === 'like'">
- <FAIcon
- class="type-icon"
- icon="star"
- />
- {{ ' ' }}
- <small>{{ $t('notifications.favorited_you') }}</small>
- </span>
- <span v-if="notification.type === 'repeat'">
- <FAIcon
- class="type-icon"
- icon="retweet"
- :title="$t('tool_tip.repeat')"
- />
- {{ ' ' }}
- <small>{{ $t('notifications.repeated_you') }}</small>
- </span>
- <span v-if="notification.type === 'follow'">
- <FAIcon
- class="type-icon"
- icon="user-plus"
- />
- {{ ' ' }}
- <small>{{ $t('notifications.followed_you') }}</small>
- </span>
- <span v-if="notification.type === 'follow_request'">
- <FAIcon
- class="type-icon"
- icon="user"
- />
- {{ ' ' }}
- <small>{{ $t('notifications.follow_request') }}</small>
- </span>
- <span v-if="notification.type === 'move'">
- <FAIcon
- class="type-icon"
- icon="suitcase-rolling"
+ :overlay-centers="true"
+ >
+ <UserAvatar
+ class="post-avatar"
+ :compact="true"
+ :user-id="notification.from_profile.id"
/>
- {{ ' ' }}
- <small>{{ $t('notifications.migrated_to') }}</small>
- </span>
- <span v-if="notification.type === 'pleroma:emoji_reaction'">
- <small>
- <i18n-t
- scope="global"
- keypath="notifications.reacted_with"
+ </UserPopover>
+ </a>
+ </div>
+ <StatusHeader
+ class="activity-header"
+ :statusoid="status"
+ :time="notification.created_at"
+ :user-id="notification.from_profile.id"
+ @toggle-expanded="toggleStatusExpanded"
+ :expanded="statusExpanded"
+ non-mention
+ />
+ <div class="activity-subheader">
+ <span v-if="notification.type === 'like'">
+ <FAIcon
+ class="type-icon"
+ icon="star"
+ />
+ {{ ' ' }}
+ <small>{{ $t('notifications.favorited_you') }}</small>
+ </span>
+ <span v-if="notification.type === 'repeat'">
+ <FAIcon
+ class="type-icon"
+ icon="retweet"
+ :title="$t('tool_tip.repeat')"
+ />
+ {{ ' ' }}
+ <small>{{ $t('notifications.repeated_you') }}</small>
+ </span>
+ <span v-if="notification.type === 'follow'">
+ <FAIcon
+ class="type-icon"
+ icon="user-plus"
+ />
+ {{ ' ' }}
+ <small>{{ $t('notifications.followed_you') }}</small>
+ </span>
+ <span v-if="notification.type === 'follow_request'">
+ <FAIcon
+ class="type-icon"
+ icon="user"
+ />
+ {{ ' ' }}
+ <small>{{ $t('notifications.follow_request') }}</small>
+ </span>
+ <span v-if="notification.type === 'move'">
+ <FAIcon
+ class="type-icon"
+ icon="suitcase-rolling"
+ />
+ {{ ' ' }}
+ <small>{{ $t('notifications.migrated_to') }}</small>
+ </span>
+ <span v-if="notification.type === 'pleroma:emoji_reaction'">
+ <small>
+ <i18n-t
+ scope="global"
+ keypath="notifications.reacted_with"
+ >
+ <img
+ v-if="notification.emoji_url"
+ class="emoji-reaction-emoji emoji-reaction-emoji-image"
+ :src="notification.emoji_url"
+ :alt="notification.emoji"
+ :title="notification.emoji"
+ :class="{ ['-wide']: allowNonSquareEmoji }"
>
- <img
- v-if="notification.emoji_url"
- class="emoji-reaction-emoji emoji-reaction-emoji-image"
- :src="notification.emoji_url"
- :alt="notification.emoji"
- :title="notification.emoji"
- :class="{ ['-wide']: allowNonSquareEmoji }"
- >
- <span
- v-else
- class="emoji-reaction-emoji"
- >{{ notification.emoji }}</span>
- </i18n-t>
- </small>
- </span>
- <span v-if="notification.type === 'pleroma:report'">
- <small>{{ $t('notifications.submitted_report') }}</small>
- </span>
- <span v-if="notification.type === 'poll'">
- <FAIcon
- class="type-icon"
- icon="poll-h"
- />
- {{ ' ' }}
- <small>{{ $t('notifications.poll_ended') }}</small>
- </span>
- </div>
+ <span
+ v-else
+ class="emoji-reaction-emoji"
+ >{{ notification.emoji }}</span>
+ </i18n-t>
+ </small>
+ </span>
+ <span v-if="notification.type === 'pleroma:report'">
+ <small>{{ $t('notifications.submitted_report') }}</small>
+ </span>
+ <span v-if="notification.type === 'poll'">
+ <FAIcon
+ class="type-icon"
+ icon="poll-h"
+ />
+ {{ ' ' }}
+ <small>{{ $t('notifications.poll_ended') }}</small>
+ </span>
+ </div>
+ <div class="activity-content">
<div
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
- class="follow-text"
+ class="follow-text activity-content"
>
<UserLink
class="follow-name"
:user="notification.from_profile"
/>
- <div
- v-if="notification.type === 'follow_request'"
- style="white-space: nowrap;"
- >
- <button
- class="button-unstyled"
- :title="$t('tool_tip.accept_follow_request')"
- @click="() => approve(user.id)"
- >
- <FAIcon
- icon="check"
- class="fa-scale-110 fa-old-padding follow-request-accept"
- />
- </button>
- <button
- class="button-unstyled"
- :title="$t('tool_tip.reject_follow_request')"
- @click="() => deny(user.id)"
- >
- <FAIcon
- icon="times"
- class="fa-scale-110 fa-old-padding follow-request-reject"
- />
- </button>
- </div>
</div>
<div
v-else-if="notification.type === 'move'"
- class="move-text"
+ class="move-text activity-content"
>
<UserLink
:user="notification.target"
/>
</div>
<Report
+ class="avatar-container"
v-else-if="notification.type === 'pleroma:report'"
:report-id="notification.report.id"
/>
- <template v-else>
- <StatusContent
- class="status-content"
- :compact="!statusExpanded"
- :status="status"
- :collapse="!statusExpanded"
- @click="onContentClick"
+ <StatusContent
+ v-if="status"
+ class="activity-container"
+ :compact="!statusExpanded"
+ :status="status"
+ :collapse="!statusExpanded"
+ @click="onContentClick"
+ />
+ </div>
+ <div
+ v-if="notification.type === 'follow_request'"
+ style="white-space: nowrap;"
+ class="activity-actions"
+ >
+ <button
+ class="button-unstyled"
+ :title="$t('tool_tip.accept_follow_request')"
+ @click="() => approve(user.id)"
+ >
+ <FAIcon
+ icon="check"
+ class="fa-scale-110 fa-old-padding follow-request-accept"
+ />
+ </button>
+ <button
+ class="button-unstyled"
+ :title="$t('tool_tip.reject_follow_request')"
+ @click="() => deny(user.id)"
+ >
+ <FAIcon
+ icon="times"
+ class="fa-scale-110 fa-old-padding follow-request-reject"
/>
- </template>
+ </button>
</div>
</div>
</article>
</template>
<script src="./notification.js"></script>
<style src="./notification.scss" lang="scss"></style>
+<style src="/src/components/status/activity-layout.scss" lang="scss"></style>
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 643fa7dae5..98eb9959c2 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -1,110 +1,108 @@
<template>
<teleport
:disabled="minimalMode || disableTeleport"
:to="teleportTarget"
>
<component
:is="noHeading ? 'div' : 'aside'"
ref="root"
:class="{ minimal: minimalMode }"
class="Notifications"
:aria-label="$t('notifications.notifications')"
>
<div :class="mainClass">
<div
v-if="!noHeading"
class="notifications-heading panel-heading -sticky"
>
<h1 class="title">
{{ $t('notifications.notifications') }}
<span
v-if="unseenCountBadgeText"
class="badge -notification unseen-count"
>{{ unseenCountBadgeText }}</span>
</h1>
<div
v-if="showScrollTop"
class="rightside-button"
>
<button
class="button-unstyled scroll-to-top-button"
type="button"
:title="$t('general.scroll_to_top')"
@click="scrollToTop"
>
<FALayers class="fa-scale-110 fa-old-padding-layer">
<FAIcon icon="arrow-up" />
<FAIcon
icon="minus"
transform="up-7"
/>
</FALayers>
</button>
</div>
<button
v-if="unseenCount"
class="button-default read-button"
type="button"
@click.prevent="markAsSeen"
>
{{ $t('notifications.read') }}
</button>
<NotificationFilters class="rightside-button" />
</div>
- <div
- role="feed"
- >
+ <div role="feed">
<article
v-if="showExtraNotifications"
class="notification"
>
<extra-notifications />
</article>
<article
v-for="notification in notificationsToDisplay"
:key="notification.id"
class="notification"
:class="{unseen: !minimalMode && shouldShowUnseen(notification)}"
@click="e => notificationClicked(notification)"
>
<div class="notification-overlay" />
<Notification
:notification="notification"
@interacted="e => notificationInteracted(notification)"
/>
</article>
</div>
<div class="panel-footer">
<div
v-if="bottomedOut"
class="new-status-notification text-center faint"
>
{{ $t('notifications.no_more_notifications') }}
</div>
<button
v-else-if="!loading"
class="button-unstyled -link text-center"
@click.prevent="fetchOlderNotifications()"
>
<div class="new-status-notification text-center">
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
</div>
</button>
<div
v-else
class="new-status-notification text-center"
>
<FAIcon
icon="circle-notch"
spin
size="lg"
/>
</div>
</div>
</div>
</component>
</teleport>
</template>
<script src="./notifications.js"></script>
<style lang="scss" src="./notifications.scss"></style>
diff --git a/src/components/status/activity-layout.scss b/src/components/status/activity-layout.scss
new file mode 100644
index 0000000000..3618b6a634
--- /dev/null
+++ b/src/components/status/activity-layout.scss
@@ -0,0 +1,55 @@
+// This code is shared between Status and Notification
+.activity-layout {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ grid-template-rows: auto auto auto auto;
+ grid-template-areas:
+ "avatar header"
+ "avatar subheader"
+ "avatar content"
+ "actions actions";
+ gap: calc(var(--status-margin) / 4) var(--status-margin);
+ overflow: hidden;
+ padding-top: var(--status-margin);
+ padding-left: var(--status-margin);
+ padding-right: var(--status-margin);
+ /* action buttons have HUGE height so we don't need as much margin at bottom */
+ padding-bottom: calc(var(--status-margin) / 2);
+
+ &:hover .animated.Avatar {
+ canvas {
+ display: none;
+ }
+
+ img {
+ visibility: visible;
+ }
+ }
+
+ .activity-avatar {
+ grid-area: avatar;
+ min-width: 0;
+ }
+
+ .activity-header {
+ grid-area: header;
+ min-width: 0;
+ }
+
+ .activity-subheader {
+ grid-area: subheader;
+ display: grid;
+ min-width: 0;
+ }
+
+ .activity-content {
+ grid-area: content;
+ padding: calc(var(--status-margin) / 2) 0;
+ min-width: 0;
+ }
+
+ .activity-actions {
+ grid-area: actions;
+ min-width: 0;
+ }
+}
diff --git a/src/components/status/status.scss b/src/components/status/status.scss
index 267fc958b3..2335eb7b70 100644
--- a/src/components/status/status.scss
+++ b/src/components/status/status.scss
@@ -1,320 +1,250 @@
.Status {
min-width: 0;
white-space: normal;
overflow-wrap: break-word;
text-wrap: pretty;
display: flex;
flex-direction: column;
&:hover {
--_still-image-img-visibility: visible;
--_still-image-canvas-visibility: hidden;
--_still-image-label-visibility: hidden;
}
.gravestone {
padding: var(--status-margin);
display: flex;
.deleted-text {
margin: 0.5em 0;
align-items: center;
}
}
- .status-container {
- display: flex;
- padding-top: var(--status-margin);
- padding-left: var(--status-margin);
- padding-right: var(--status-margin);
- /* action buttons have HUGE height so we don't need as much margin at bottom */
- padding-bottom: calc(var(--status-margin) / 2);
- gap: var(--status-margin);
-
- > * {
- min-width: 0;
- }
- }
-
.pin {
display: flex;
align-items: center;
justify-content: flex-end;
margin-right: 0.5em;
}
._misclick-prevention & {
pointer-events: none;
.attachments {
pointer-events: initial;
cursor: initial;
}
}
- .left-side {
- flex: 0 0 auto;
- }
-
- .right-side {
- flex: 1 1 auto;
- display: flex;
- flex-direction: column;
- gap: calc(var(--status-margin) / 4);
- }
-
- .status-username {
- white-space: nowrap;
- overflow: hidden;
- max-width: 85%;
- font-weight: bold;
- flex-shrink: 1;
- margin-right: 0.4em;
- text-overflow: ellipsis;
-
- --_still_image-label-scale: 0.25;
- --emoji-size: 1em;
- }
-
- .status-content {
- margin-top: calc(var(--status-margin));
- /* action buttons have HUGE height so we don't need as much margin at bottom */
- margin-bottom: calc(var(--status-margin) / 2);
- }
-
.usercard {
margin-bottom: var(--status-margin);
}
.glued-label {
display: inline-flex;
white-space: nowrap;
}
& .heading-reply-row,
& .heading-edited-row {
position: relative;
align-content: baseline;
font-size: 0.85em;
line-height: 130%;
max-width: 100%;
align-items: stretch;
}
& .reply-to-popover,
& .reply-to-no-popover,
& .mentions {
min-width: 0;
margin-right: 0.4em;
flex-shrink: 0;
}
.reply-glued-label {
margin-right: 0.5em;
}
.reply-to-popover {
.faint-link:hover {
// override default
text-decoration: none;
}
&.-strikethrough {
.reply-to::after {
content: "";
display: block;
position: absolute;
top: 50%;
width: 100%;
border-bottom: 1px solid var(--faint);
pointer-events: none;
}
}
}
& .mentions,
& .reply-to {
white-space: nowrap;
position: relative;
}
& .mentions-text,
& .reply-to-text {
color: var(--faint);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mentions-line {
display: inline;
}
.replies {
margin-top: 0.25em;
line-height: 1.3;
font-size: 0.85em;
display: flex;
flex-wrap: wrap;
& > * {
margin-right: 0.4em;
}
}
.reply-link {
height: 17px;
}
.repeat-info {
- display: flex;
- align-items: center;
padding: 0.4em var(--status-margin);
.repeater-avatar {
- flex: 0 0 1.5em;
border-radius: var(--roundness);
margin-left: 2em; // 3.5 (poster avatar size) - 1.5 (repeater avatar size)
width: 1.5em;
height: 1.5em;
+ margin-right: var(--status-margin);
+ vertical-align: middle;
}
- .right-side {
- display: flex;
- flex-direction: row;
- flex: 1 1 auto;
- overflow-x: hidden;
- text-overflow: ellipsis;
- margin-right: 0;
-
- .repeater-name {
- flex: 0 1 auto;
- margin: 0;
- }
-
- .repeat-label {
- white-space: nowrap;
- flex: 0 0 auto;
-
- .repeat-icon {
- vertical-align: middle;
- color: var(--cGreen);
- }
- }
+ .repeat-label {
+ white-space: nowrap;
- .emoji {
- width: 1em;
- height: 1em;
+ .repeat-icon {
vertical-align: middle;
- object-fit: contain;
+ color: var(--cGreen);
}
}
- }
- .status-actions {
- position: relative;
- width: 100%;
- display: grid;
- grid-template-columns: 1fr;
- grid-auto-columns: 1fr;
- grid-auto-flow: column;
- margin-top: var(--status-margin);
+ .emoji {
+ width: 1em;
+ height: 1em;
+ vertical-align: middle;
+ object-fit: contain;
+ }
}
.muted {
padding: 0.25em 0.6em;
height: 1.2em;
line-height: 1.2em;
text-overflow: ellipsis;
overflow: hidden;
display: flex;
flex-wrap: nowrap;
gap: 1ex;
& .status-username,
& .mute-reason {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.status-username {
font-weight: normal;
flex: 0 1 auto;
margin-right: 0.2em;
font-size: smaller;
display: flex;
}
.unmute {
flex: 0 0 auto;
margin-left: auto;
display: block;
}
}
.reply-form {
padding-top: 0;
padding-bottom: 0;
}
.reply-body {
flex: 1;
}
.favs-repeated-users {
margin-top: var(--status-margin);
}
.stats {
width: 100%;
display: flex;
line-height: 1em;
}
.avatar-row {
flex: 1;
position: relative;
display: flex;
align-items: center;
overflow: hidden;
&::before {
content: "";
position: absolute;
height: 100%;
width: 1px;
left: 0;
background-color: var(--textFaint);
}
}
.stat-count {
margin-right: var(--status-margin);
user-select: none;
.stat-title {
color: var(--textFaint);
font-size: 0.85em;
text-transform: uppercase;
position: relative;
}
.stat-number {
font-weight: bolder;
font-size: 1.1em;
line-height: 1em;
color: var(--text);
}
&:hover .stat-title {
text-decoration: underline;
}
}
.thread-tree-show-replies-button {
display: block;
padding: var(--status-margin);
padding-left: var(--status-margin);
}
}
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 235cede85d..7bb8e37ed5 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -1,471 +1,467 @@
<template>
<article
v-if="!hideStatus"
ref="root"
class="Status"
:class="rootClasses"
:aria-label="mainStatus.summary || $t('status.unsubjected')"
>
<div
v-if="error"
class="alert error"
>
{{ error }}
<button
class="fa-scale-110 fa-old-padding"
type="button"
@click="clearError"
>
<FAIcon icon="times" />
</button>
</div>
<template v-if="muted && !isPreview">
- <div class="status-container muted">
+ <div class="muted">
<small class="status-username">
<FAIcon
v-if="muted && isRepeat"
class="fa-scale-110 fa-old-padding repeat-icon"
icon="retweet"
/>
<UserLink
:user="repeater"
:at="false"
/>
</small>
<small class="mute-reason">
{{ muteLocalized }}
</small>
<button
class="unmute button-unstyled"
@click.prevent="toggleMute"
:title="$t('status.show_muted_status')"
>
<FAIcon
icon="eye-slash"
class="fa-scale-110 fa-old-padding"
/>
</button>
</div>
</template>
<template v-else>
<div
- v-if="isRepeat && !noHeading && !inConversation"
+ v-if="isRepeat && !inConversation"
:class="[repeaterClass, { highlighted: repeaterStyle }]"
:style="[repeaterStyle]"
- class="status-container repeat-info"
+ class="repeat-info"
>
<UserAvatar
- class="left-side repeater-avatar"
+ class="repeater-avatar"
:user-id="repeater.id"
/>
- <div class="right-side faint">
- <bdi
- class="status-username repeater-name"
- :title="repeaterName"
+ <bdi
+ class="faint status-username repeater-name"
+ :title="repeaterName"
+ >
+ <router-link
+ v-if="repeaterHtml"
+ :to="repeaterProfileLink"
>
- <router-link
- v-if="repeaterHtml"
- :to="repeaterProfileLink"
- >
- <RichContent
- :html="repeaterHtml"
- :emoji="repeater.emoji"
- :allow-non-square-emoji="allowNonSquareEmoji"
- :pause-mfm="pauseMfm"
- :scale-mfm="scaleMfm"
- :is-local="repeater.is_local"
- />
- </router-link>
- <router-link
- v-else
- :to="repeaterProfileLink"
- >{{ repeaterName }}</router-link>
- </bdi>
- <div class="repeat-label">
- <FAIcon
- icon="retweet"
- class="repeat-icon"
- :title="$t('tool_tip.repeat')"
+ <RichContent
+ :html="repeaterHtml"
+ :emoji="repeater.emoji"
+ :allow-non-square-emoji="allowNonSquareEmoji"
+ :pause-mfm="pauseMfm"
+ :scale-mfm="scaleMfm"
+ :is-local="repeater.is_local"
/>
- {{ $t('timeline.repeated') }}
- </div>
- </div>
+ </router-link>
+ <router-link
+ v-else
+ :to="repeaterProfileLink"
+ >{{ repeaterName }}</router-link>
+ </bdi>
+ {{ ' ' }}
+ <span class="repeat-label">
+ <FAIcon
+ icon="retweet"
+ class="repeat-icon"
+ :title="$t('tool_tip.repeat')"
+ />
+ {{ $t('timeline.repeated') }}
+ </span>
</div>
<div
v-if="!isDeleted"
:class="[userClass, { highlighted: userStyle, '-repeat': isRepeat && !inConversation }]"
:style="[ userStyle ]"
- class="status-container"
+ class="activity-layout"
:data-tags="tags"
>
- <div
- v-if="!noHeading"
- class="left-side"
+ <a
+ v-if="user.name"
+ :href="$router.resolve(userProfileLink).href"
+ class="activity-avatar"
+ @click.prevent
>
- <a
- v-if="user.name"
- :href="$router.resolve(userProfileLink).href"
- @click.prevent
+ <UserPopover
+ :user-id="user.id"
+ :overlay-centers="true"
>
- <UserPopover
+ <UserAvatar
+ class="post-avatar"
+ :compact="compact"
:user-id="user.id"
- :overlay-centers="true"
- >
- <UserAvatar
- class="post-avatar"
- :compact="compact"
- :user-id="user.id"
- />
- </UserPopover>
- </a>
- <UserAvatar
- v-else
- :user-id="user.id"
- class="post-avatar"
- :compact="compact"
- :title="$t('status.unknown_user_info')"
- />
- </div>
- <div class="right-side">
- <StatusHeader
- v-if="!noHeading"
- :status-id="mainStatus.id"
- :user-id="user.id"
- :unmuted="unmuted"
- :time="mainStatus.created_at"
- :replies="replies"
+ />
+ </UserPopover>
+ </a>
+ <UserAvatar
+ v-else
+ :user-id="user.id"
+ class="activity-avatar"
+ :compact="compact"
+ :title="$t('status.unknown_user_info')"
+ />
+ <StatusHeader
+ class="activity-header"
+ :status-id="mainStatus.id"
+ :user-id="user.id"
+ :unmuted="unmuted"
+ :time="mainStatus.created_at"
+ :replies="replies"
- :expandable="expandable"
- :expanded="isExpanded"
- :thread-display-state="threadDisplayState"
+ :expandable="expandable"
+ :expanded="isExpanded"
+ :thread-display-state="threadDisplayState"
- @toggle-expanded="$emit('toggleExpanded')"
- @toggle-thread-display="$emit('toggleThreadDisplay')"
- @dive="$emit('dive')"
- />
+ @toggle-muted="toggleMute"
+ @toggle-expanded="$emit('toggleExpanded')"
+ @toggle-thread-display="$emit('toggleThreadDisplay')"
+ @dive="$emit('dive')"
+ />
+ <div class="activity-subheader">
<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"
>
<span
v-if="isReply"
class="glued-label reply-glued-label"
>
<i18n-t
keypath="status.reply_to_with_arg"
scope="global"
>
<template #replyToWithIcon>
<StatusPopover
v-if="!isPreview"
:status-id="mainStatus.parent_visible && mainStatus.in_reply_to_status_id"
class="reply-to-popover"
style="min-width: 0;"
:class="{ '-strikethrough': !mainStatus.parent_visible }"
:aria-label="$t('tool_tip.reply')"
>
<i18n-t
keypath="status.reply_to_with_icon"
scope="global"
>
<template #icon>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="reply"
flip="horizontal"
/>
</template>
<template #replyTo>
<span
class="reply-to-text"
>
{{ $t('status.reply_to') }}
</span>
</template>
</i18n-t>
</StatusPopover>
<span
v-else
class="reply-to-no-popover"
>
<span class="reply-to-text">{{ $t('status.reply_to') }}</span>
</span>
</template>
<template #user>
<MentionLink
:content="replyToName"
:url="replyProfileLink"
:user-id="status.in_reply_to_user_id"
:user-screen-name="status.in_reply_to_screen_name"
/>
</template>
</i18n-t>
</span>
<!-- This little wrapper is made for sole purpose of "gluing" -->
<!-- "Mentions" label to the first mention -->
<span
v-if="hasMentionsLine"
class="glued-label"
>
<span
class="mentions"
:aria-label="$t('tool_tip.mentions')"
@click.prevent="gotoOriginal(status.in_reply_to_status_id)"
>
<span
class="mentions-text"
>
{{ $t('status.mentions') }}
</span>
</span>
<MentionsLine
v-if="hasMentionsLine"
:mentions="mentionsLine.slice(0, 1)"
class="mentions-line-first"
/>
</span>
{{ ' ' }}
<MentionsLine
v-if="hasMentionsLine"
:mentions="mentionsLine.slice(1)"
class="mentions-line"
/>
</div>
<div
v-if="isEdited && editingAvailable && !isPreview"
class="heading-edited-row"
>
<i18n-t
scope="global"
keypath="status.edited_at"
tag="span"
>
<template #time>
<Timeago
template-key="time.in_past"
:time="mainStatus.edited_at"
:auto-update="60"
:long-format="true"
/>
</template>
</i18n-t>
</div>
+ </div>
+ <div
+ class="activity-content"
+ >
<StatusContent
ref="content"
- class="status-content"
:status="mainStatus"
:focused="focused"
:in-conversation="inConversation"
@mediaplay="addMediaPlaying($event)"
@mediapause="removeMediaPlaying($event)"
@parse-ready="setHeadTailLinks"
/>
<Quote
:status-id="quoteId"
:status-url="quoteUrl"
:status-visible="quoteVisible"
:initially-expanded="quoteExpanded"
/>
<div
v-if="inConversation && !isPreview && replies?.size"
class="replies"
>
<button
v-if="showOtherRepliesInside && replies.size > 1"
class="button-unstyled -link"
:title="$t('status.ancestor_follow', { numReplies: replies.size - 1 }, replies.size - 1)"
@click.prevent="$emit('dive')"
>
{{ $t('status.replies_list_with_others', { numReplies: replies.size - 1 }, replies.size - 1) }}
</button>
<span
v-else
role="header"
:aria-level="headerLevel"
class="faint"
>
{{ $t('status.replies_list') }}
</span>
<span role="list" :aria-label="$t('status.replies_list')">
<span
v-for="reply in replies.values()"
:key="reply.id"
role="listitem"
:aria-label="reply.name"
>
<StatusPopover
:status-id="reply.id"
class="reply-link"
>
{{ reply.name }}
</StatusPopover>
</span>
</span>
</div>
<Transition name="fade">
<div
v-if="shouldDisplayFavsAndRepeats"
class="favs-repeated-users"
>
<div class="stats">
<UserListPopover
v-if="repeatedBy.size > 0"
:user-ids="repeatedBy"
>
<div class="stat-count">
<a class="stat-title">{{ $t('status.repeats') }}</a>
<div class="stat-number">
{{ repeatedBy.size }}
</div>
</div>
</UserListPopover>
<UserListPopover
v-if="favoritedBy.size > 0"
:user-ids="favoritedBy"
>
<div
class="stat-count"
>
<a class="stat-title">{{ $t('status.favorites') }}</a>
<div class="stat-number">
{{ favoritedBy.size }}
</div>
</div>
</UserListPopover>
<router-link
v-if="mainStatus.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">
{{ mainStatus.quotes_count }}
</div>
</div>
</router-link>
<div class="avatar-row">
<AvatarList :user-ids="combinedFavsAndRepeatsUsers" />
</div>
</div>
</div>
</Transition>
<EmojiReactions
- v-if="(mergedConfig.emojiReactionsOnTimeline || focused) && (!noHeading && !isPreview)"
- :status="mainStatus"
- />
-
- <StatusActionButtons
- v-if="!noHeading && !isPreview"
- class="status-action-buttons"
+ v-if="(mergedConfig.emojiReactionsOnTimeline || focused) && (!isPreview)"
:status="mainStatus"
- :replying="replying"
- @toggle-replying="toggleReplyForm"
/>
</div>
+
+ <StatusActionButtons
+ v-if="!isPreview"
+ class="activity-actions"
+ :status="mainStatus"
+ :replying="replying"
+ @toggle-replying="toggleReplyForm"
+ />
</div>
<div
v-else
class="gravestone"
>
- <div class="left-side">
- <UserAvatar
- class="post-avatar"
- :compact="compact"
- />
- </div>
- <div class="right-side">
- <div class="deleted-text">
- {{ $t('status.status_deleted') }}
- </div>
+ <UserAvatar
+ class="post-avatar"
+ :compact="compact"
+ />
+ <div class="deleted-text">
+ {{ $t('status.status_deleted') }}
</div>
</div>
<div
v-if="replying"
- class="status-container reply-form"
+ class="reply-form"
>
<PostStatusForm
ref="postStatusForm"
class="reply-body"
:closeable="true"
:replied-status="mainStatus"
@posted="closeReplyForm"
@draft-done="closeReplyForm"
@close-accepted="closeReplyForm"
/>
</div>
<i18n-t
v-if="inConversation && conversationRank === 'ancestor' && !isPreview && showOtherRepliesBelow && replies?.size > 1"
tag="button"
scope="global"
keypath="status.ancestor_follow_with_icon"
class="button-unstyled -link thread-tree-show-replies-button"
@click.prevent="$emit('dive')"
>
<template #icon>
<FAIcon
icon="angle-double-right"
/>
</template>
<template #text>
<span>
{{ $t('status.ancestor_follow', { numReplies: replies.size - 1 }) }}
</span>
</template>
</i18n-t>
</template>
</article>
</template>
<script src="./status.js"></script>
<style src="./status.scss" lang="scss"></style>
+<style src="./activity-layout.scss" lang="scss"></style>
diff --git a/src/components/status_header/status_header.scss b/src/components/status_header/status_header.scss
index 086f030706..925f464571 100644
--- a/src/components/status_header/status_header.scss
+++ b/src/components/status_header/status_header.scss
@@ -1,68 +1,65 @@
.StatusHeader {
- .status-username {
- white-space: nowrap;
- overflow: hidden;
- max-width: 85%;
- font-weight: bold;
- flex-shrink: 1;
- margin-right: 0.4em;
- text-overflow: ellipsis;
-
- --_still_image-label-scale: 0.25;
- --emoji-size: 1em;
- }
-
- .status-favicon {
- height: 1.2em;
- width: 1.2em;
- margin-right: 0.4em;
- object-fit: contain;
- }
+ display: flex;
+ justify-content: space-between;
+ line-height: 1.1;
+ overflow: hidden;
+ white-space: nowrap;
+ gap: var(--status-margin);
- .heading-name-row {
+ .heading-left {
+ flex: 1 1 5em;
+ align-items: baseline;;
+ min-width: 0;
+ overflow: hidden;
display: flex;
- justify-content: space-between;
- line-height: 1;
+ gap: calc(var(--status-margin) / 2);
- a {
- display: inline-block;
+ .status-username,
+ .account-handle {
white-space: nowrap;
+ overflow-x: hidden;
text-overflow: ellipsis;
- overflow: hidden;
- width: 100%
}
- }
- .account-name {
- display: inline-block;
- min-width: 1em;
- margin-right: 0.4em;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- flex: 1 1 0;
+ .status-username {
+ font-weight: bold;
+ flex-shrink: 0;
- &.unknown {
- min-width: 8em;
+ .emoji {
+ vertical-align: top;
+ }
+
+ --_still_image-label-scale: 0.25;
+ --emoji-size: 1em;
}
- }
- .heading-left {
- display: flex;
- min-width: 0;
+ .account-handle {
+ flex-shrink: 1;
+ min-width: 1em;
+ }
+
+ .status-favicon {
+ height: 1.2em;
+ width: 1.2em;
+ min-width: 1.2em;
+ object-fit: contain;
+ align-self: center;
+ }
}
.heading-right {
+ flex: 0 0 auto;
display: flex;
- flex-shrink: 0;
- align-self: baseline;
word-break: keep-all;
- margin-right: calc(var(--status-margin) / -2);
.visibility-icon,
.button-unstyled {
margin: calc(var(--status-margin) / -2) 0;
padding: calc(var(--status-margin) / 2);
+
+ &:last-child {
+ margin-right: calc(var(--status-margin) / -2);
+ }
}
}
}
diff --git a/src/components/status_header/status_header.vue b/src/components/status_header/status_header.vue
index 13f897d73d..596daeaffb 100644
--- a/src/components/status_header/status_header.vue
+++ b/src/components/status_header/status_header.vue
@@ -1,139 +1,147 @@
<template>
<div class="StatusHeader">
- <div class="heading-name-row">
- <div
- v-if="user"
- class="heading-left"
+ <div
+ v-if="user"
+ class="heading-left"
+ >
+ <RichContent
+ v-if="user.name_html"
+ class="status-username"
+ :html="user.name"
+ :emoji="user.emoji"
+ :allow-non-square-emoji="allowNonSquareEmoji"
+ :is-local="user.is_local"
+ :title="user.name"
+ />
+ <span
+ v-else
+ class="status-username"
+ :title="user.name"
>
- <div
- v-if="user.name_html"
- class="status-username"
- :title="user.name"
- >
- <RichContent
- :html="user.name"
- :emoji="user.emoji"
- :allow-non-square-emoji="allowNonSquareEmoji"
- :is-local="user.is_local"
- />
- </div>
- <div
- v-else
- class="status-username"
- :title="user.name"
- >
- {{ user.name }}
- </div>
- <UserLink
- class="account-name"
- :title="user.screen_name_ui"
- :user="user"
- :at="false"
- />
- <img
- v-if="!!(user && user.favicon)"
- class="status-favicon"
- :title="$t('status.favicon')"
- :src="user.favicon"
- >
- </div>
-
- <span class="heading-right">
- <span
- v-if="!nonMention && status?.pinned"
- class="pin"
- >
- <FAIcon
- icon="thumbtack"
- class="faint"
- />
- <span class="faint">{{ $t('status.pinned') }}</span>
- </span>
- <router-link
- class="timeago faint"
- :to="{ name: 'conversation', params: { statusId: status.id } }"
- :aria-label="$t('status.open_in_thread_view')"
- >
- <Timeago
- :time="time"
- :auto-update="60"
- />
- </router-link>
- <span
- v-if="!nonMention && status.visibility"
- class="visibility-icon"
- :title="visibilityLocalized"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- :icon="visibilityIcon"
- />
- </span>
- <button
- v-if="!nonMention && expandable && !expanded"
- class="button-unstyled"
- :title="$t('status.expand')"
- @click.prevent="$emit('toggleExpanded')"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- icon="plus-square"
- />
- </button>
- <button
- v-if="unmuted"
- class="button-unstyled"
- @click.prevent="toggleMute"
- >
- <FAIcon
- fixed-width
- icon="eye-slash"
- class="fa-scale-110"
- />
- </button>
- <button
- v-if="status && inThreadForest && replies?.size && !simpleTree"
- class="button-unstyled"
- :title="threadShowing ? $t('status.thread_hide') : $t('status.thread_show')"
- :aria-expanded="threadShowing ? 'true' : 'false'"
- @click.prevent="$emit('toggleThreadDisplay')"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- :icon="threadShowing ? 'chevron-up' : 'chevron-down'"
- />
- </button>
- <button
- v-if="!nonMention && expanded && isTreeView && !simpleTree"
- class="button-unstyled"
- :title="$t('status.show_only_conversation_under_this')"
- @click.prevent="$emit('dive')"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- :icon="'angle-double-right'"
- />
- </button>
- <button
- v-if="nonMention && status"
- class="button-unstyled expand-icon"
- :title="$t('tool_tip.toggle_expand')"
- :aria-expanded="expanded"
- @click.prevent="$emit('toggleExpanded')"
- >
- <FAIcon
- class="fa-scale-110"
- fixed-width
- :icon="expanded ? 'compress-alt' : 'expand-alt'"
- />
- </button>
+ {{ user.name }}
</span>
+ {{ ' ' }}
+ <UserLink
+ class="account-handle"
+ :title="user.screen_name_ui"
+ :user="user"
+ :at="false"
+ />
+ {{ ' ' }}
+ <img
+ v-if="!!(user && user.favicon)"
+ class="status-favicon"
+ :title="$t('status.favicon')"
+ :alt="$t('status.favicon')"
+ :src="user.favicon"
+ >
</div>
+
+ <span class="heading-right" :class="{ '-has-actions': true }">
+ <span
+ v-if="!nonMention && status?.pinned"
+ class="pin"
+ >
+ <FAIcon
+ icon="thumbtack"
+ class="faint"
+ />
+ <span class="faint">{{ $t('status.pinned') }}</span>
+ </span>
+ <router-link
+ v-if="status"
+ class="timeago faint"
+ :to="{ name: 'conversation', params: { statusId: status.id } }"
+ :aria-label="$t('status.open_in_thread_view')"
+ >
+ <Timeago
+ :time="time"
+ :auto-update="60"
+ />
+ </router-link>
+ <span
+ v-else
+ class="timeago faint"
+ >
+ <Timeago
+ :time="time"
+ :auto-update="60"
+ />
+ </span>
+ <span
+ v-if="!nonMention && status.visibility"
+ class="visibility-icon"
+ :title="visibilityLocalized"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110"
+ :icon="visibilityIcon"
+ />
+ </span>
+ <button
+ v-if="!nonMention && expandable && !expanded"
+ class="button-unstyled"
+ :title="$t('status.expand')"
+ @click.prevent="$emit('toggleExpanded')"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110"
+ icon="plus-square"
+ />
+ </button>
+ <button
+ v-if="unmuted"
+ class="button-unstyled"
+ @click.prevent="toggleMute"
+ >
+ <FAIcon
+ fixed-width
+ icon="eye-slash"
+ class="fa-scale-110"
+ />
+ </button>
+ <button
+ v-if="status && inThreadForest && replies?.size && !simpleTree"
+ class="button-unstyled"
+ :title="threadShowing ? $t('status.thread_hide') : $t('status.thread_show')"
+ :aria-expanded="threadShowing ? 'true' : 'false'"
+ @click.prevent="$emit('toggleThreadDisplay')"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110"
+ :icon="threadShowing ? 'chevron-up' : 'chevron-down'"
+ />
+ </button>
+ <button
+ v-if="!nonMention && expanded && isTreeView && !simpleTree"
+ class="button-unstyled"
+ :title="$t('status.show_only_conversation_under_this')"
+ @click.prevent="$emit('dive')"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110"
+ :icon="'angle-double-right'"
+ />
+ </button>
+ <button
+ v-if="nonMention && status"
+ class="button-unstyled expand-icon"
+ :title="$t('tool_tip.toggle_expand')"
+ :aria-expanded="expanded"
+ @click.prevent="$emit('toggleExpanded')"
+ >
+ <FAIcon
+ class="fa-scale-110"
+ fixed-width
+ :icon="expanded ? 'compress-alt' : 'expand-alt'"
+ />
+ </button>
+ </span>
</div>
</template>
<script src="./status_header.js"></script>
<style src="./status_header.scss"></style>
diff --git a/src/components/user_link/user_link.vue b/src/components/user_link/user_link.vue
index 49544ff2ee..222bd4590a 100644
--- a/src/components/user_link/user_link.vue
+++ b/src/components/user_link/user_link.vue
@@ -1,46 +1,45 @@
<template>
- <div class="user-profile-link">
- <router-link
- :title="user.screen_name_ui"
- :to="userProfileLink(user)"
- >
- <slot>
- {{ at ? '@' : '' }}{{ user.screen_name_ui }}<UnicodeDomainIndicator
- :user="user"
- />
- </slot>
- </router-link>
- </div>
+ <router-link
+ class="user-profile-link"
+ :title="user.screen_name_ui"
+ :to="userProfileLink(user)"
+ >
+ <slot>
+ {{ at ? '@' : '' }}{{ user.screen_name_ui }}<UnicodeDomainIndicator
+ :user="user"
+ />
+ </slot>
+ </router-link>
</template>
<script>
import UnicodeDomainIndicator from 'src/components/unicode_domain_indicator/unicode_domain_indicator.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const UserLink = {
props: {
user: Object,
at: {
type: Boolean,
default: true,
},
},
components: {
UnicodeDomainIndicator,
},
methods: {
userProfileLink(user) {
return generateProfileLink(
user.id,
user.screen_name,
useInstanceStore().restrictedNicknames,
)
},
},
}
export default UserLink
</script>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Oct 11, 6:15 PM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785903
Default Alt Text
(63 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline
Log In to Comment