Page MenuHomePhorge

No OneTemporary

Size
33 KB
Referenced Files
None
Subscribers
None
diff --git a/src/components/activity_header/activity_header.scss b/src/components/activity_header/activity_header.scss
index 6459e1b9cf..713effe4a8 100644
--- a/src/components/activity_header/activity_header.scss
+++ b/src/components/activity_header/activity_header.scss
@@ -1,81 +1,82 @@
.ActivityHeader {
display: grid;
- line-height: 1.1;
+ line-height: var(--__header-line-height);
grid-template-columns: auto auto minmax(min-content, 1fr);
- grid-template-rows: 1.1em;
+ grid-template-rows: var(--__header-height);
grid-template-areas:
"title handle misc";
- gap: calc(var(--status-margin) / 4) calc(var(--status-margin) / 2);
+ gap: 0 calc(var(--status-margin) / 2);
&.-multiline {
grid-template-columns: max-content minmax(min-content, 1fr);
- grid-template-rows: 1.1em 1.1em;
+ grid-template-rows: var(--__header-height) var(--__header-height);
grid-template-areas:
"title misc"
"handle handle";
}
.user-title,
.user-handle {
white-space: nowrap;
}
.user-title {
grid-area: title;
font-weight: bold;
- overflow-x: hidden;
- text-overflow: ellipsis;
+ overflow-x: clip;
+ text-overflow: clip;
+ min-width: 3em;
.emoji {
vertical-align: top;
}
--_still_image-label-scale: 0.25;
--emoji-size: 1em;
}
.user-handle {
grid-area: handle;
display: flex;
min-width: 1em;
.user-profile-link {
flex: 0 1 auto;
overflow-x: hidden;
text-overflow: ellipsis;
&::after {
content: ' ';
display: inline-block;
width: 0.25em;
}
}
.instance-favicon {
flex: 0 0 auto;
height: 1.1em;
width: 1.1em;
min-width: 1.1em;
object-fit: contain;
vertical-align: middle;
}
}
.misc {
grid-area: misc;
flex: 0 0 auto;
display: flex;
word-break: keep-all;
justify-self: end;
.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/activity-layout.scss b/src/components/status/activity-layout.scss
index c7d5f37011..6d95946be2 100644
--- a/src/components/status/activity-layout.scss
+++ b/src/components/status/activity-layout.scss
@@ -1,75 +1,83 @@
// 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"
"avatar content"
"actions actions";
gap: calc(var(--status-margin) / 2) var(--status-margin);
overflow: hidden;
padding-top: var(--status-margin);
padding-left: var(--status-margin);
padding-right: var(--status-margin);
+ --__header-line-height: 1.2;
+ --__header-height: calc(var(--__header-line-height) * 1em);
+ --_avatar-size: calc(var(--__header-height) * 3);
+
&.-width-efficient {
grid-template-areas:
"avatar header"
"avatar subheader"
"content content"
"content content"
"actions actions";
&.-compact {
+ --_avatar-size: calc(var(--__header-height) * 2);
+
grid-template-areas:
"avatar header"
"avatar header"
"subheader subheader"
"content content"
"actions actions";
}
}
/* 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;
+ align-self: start;
+ display: flex;
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.vue b/src/components/status/status.vue
index 03983add6b..342ae94609 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -1,472 +1,468 @@
<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="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 && !inConversation"
:class="[repeaterClass, { highlighted: repeaterStyle }]"
:style="[repeaterStyle]"
class="repeat-info"
>
<UserAvatar
class="repeater-avatar"
:user-id="repeater.id"
/>
<bdi
class="faint status-username repeater-name"
:title="repeaterName"
>
<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>
{{ ' ' }}
<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, '-width-efficient': shouldBeWidthEfficient, '-compact': compact }]"
:style="[ userStyle ]"
class="activity-layout"
:data-tags="tags"
>
- <a
+ <UserPopover
v-if="user.name"
- :href="$router.resolve(userProfileLink).href"
class="activity-avatar"
- @click.prevent
+ :user-id="user.id"
+ overlay-centers
+ link
>
- <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>
+ />
+ </UserPopover>
<UserAvatar
v-else
:user-id="user.id"
class="activity-avatar"
:compact="compact"
:title="$t('status.unknown_user_info')"
/>
<ActivityHeader
class="activity-header"
:status-id="mainStatus.id"
:user-id="user.id"
:unmuted="unmuted"
:time="mainStatus.created_at"
:replies="replies"
:multiline="shouldBeWidthEfficient"
:compact="compact"
:expandable="expandable"
:expanded="isExpanded"
:thread-display-state="threadDisplayState"
@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')"
:link="isExpanded"
>
<i18n-t
keypath="status.reply_to_with_icon"
scope="global"
>
<template #icon>
<FAIcon
- class="fa-scale-110 fa-old-padding"
+ class="fa-scale-110"
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"
: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"
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) && (!isPreview)"
:status="mainStatus"
/>
</div>
<StatusActionButtons
v-if="!isPreview"
class="activity-actions"
:status="mainStatus"
:replying="replying"
@toggle-replying="toggleReplyForm"
/>
</div>
<div
v-else
class="gravestone"
>
<UserAvatar
class="post-avatar"
:compact="compact"
/>
<div class="deleted-text">
{{ $t('status.status_deleted') }}
</div>
</div>
<div
v-if="replying"
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_body/status_body.scss b/src/components/status_body/status_body.scss
index a475c0e776..a896d1c53c 100644
--- a/src/components/status_body/status_body.scss
+++ b/src/components/status_body/status_body.scss
@@ -1,216 +1,217 @@
.StatusBody {
display: flex;
flex-direction: column;
.emoji {
--_still_image-label-scale: 0.5;
}
.attachments {
margin-top: 0.5em;
}
& .text,
& .summary {
white-space: pre-wrap;
overflow-wrap: break-word;
text-wrap: pretty;
line-height: var(--post-line-height);
}
.summary {
display: block;
font-style: italic;
margin: 0;
font-size: 1rem;
font-weight: normal;
}
.text {
&.-single-line {
white-space-collapse: collapse;
text-overflow: ellipsis;
overflow: hidden;
height: 1.4em;
}
}
.summary-wrapper {
margin-top: 0;
margin-bottom: 0.5em;
border-style: solid;
border-width: 0 0 1px;
border-color: var(--border);
flex-grow: 0;
&.-tall {
position: relative;
.summary {
max-height: 2em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
.text-wrapper {
position: relative;
text-overflow: ellipsis;
overflow-wrap: break-word;
overflow: hidden;
display: flex;
flex-flow: column nowrap;
+ padding: 0.1em 0;
&.-tall-status {
z-index: 1;
&:not(.-hidden) {
height: 16em;
}
.media-body {
min-height: 0;
mask:
linear-gradient(to top, white, transparent) bottom/100% 8em no-repeat,
linear-gradient(to top, white, white);
/* Autoprefixed seem to ignore this one, and also syntax is different */
/* stylelint-disable mask-composite */
/* stylelint-disable declaration-property-value-no-unknown */
/* stylelint-disable scss/declaration-property-value-no-unknown */
/* TODO check if this is still needed */
mask-composite: xor;
/* stylelint-enable scss/declaration-property-value-no-unknown */
/* stylelint-enable declaration-property-value-no-unknown */
/* stylelint-enable mask-composite */
mask-composite: exclude;
}
}
&.-expanded {
overflow: visible;
}
}
&.-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-subject-hider,
& .status-unhider,
& .cw-status-hider {
display: inline-block;
overflow-wrap: break-word;
text-wrap: pretty;
width: 100%;
text-align: center;
margin: 0.1em 0;
}
.status-unhider {
margin-top: auto;
position: sticky;
bottom: 0;
padding-bottom: 1em;
}
.tall-subject-hider {
// position: absolute;
padding-bottom: 0.5em;
&:not(.cw-status-hider) {
position: absolute;
margin-top: 10em;
height: 5em;
line-height: 8em;
z-index: 2;
}
}
& .status-unhider,
& .cw-status-hider {
overflow-wrap: break-word;
text-wrap: pretty;
svg {
color: inherit;
}
}
.toggle-button {
padding: 0.5em;
}
&.-compact {
align-items: start;
flex-direction: row;
& .body,
& .attachments {
max-height: 3.25em;
}
.body {
overflow: hidden;
white-space: normal;
min-width: 5em;
flex: 5 1 auto;
mask-size: auto 3.5em, auto auto;
mask-position: 0 0, 0 0;
mask-repeat: repeat-x, repeat;
mask-image: linear-gradient(to bottom, white 2em, transparent 3em);
/* Autoprefixed seem to ignore this one, and also syntax is different */
/* stylelint-disable mask-composite */
/* stylelint-disable declaration-property-value-no-unknown */
/* stylelint-disable scss/declaration-property-value-no-unknown */
/* TODO check if this is still needed */
mask-composite: xor;
/* stylelint-enable scss/declaration-property-value-no-unknown */
/* stylelint-enable declaration-property-value-no-unknown */
/* stylelint-enable mask-composite */
mask-composite: exclude;
}
.attachments {
margin-top: 0;
flex: 1 1 0;
min-width: 5em;
height: 100%;
margin-left: 0.5em;
}
.summary-wrapper {
.summary::after {
content: ": ";
}
line-height: inherit;
margin: 0;
border: none;
}
.text-wrapper {
display: inline-block;
width: 100%;
}
}
}
diff --git a/src/components/still-image/still-image-emoji-popover.vue b/src/components/still-image/still-image-emoji-popover.vue
index f80f111f75..19b69aedad 100644
--- a/src/components/still-image/still-image-emoji-popover.vue
+++ b/src/components/still-image/still-image-emoji-popover.vue
@@ -1,97 +1,107 @@
<template>
<Popover
ref="emojiPopover"
+ class="emoji-trigger"
trigger="click"
placement="top"
:bound-to="{ x: 'container' }"
:offset="{ y: 10 }"
@show="fetchEmojiPacksIfAdmin"
>
<template #trigger>
<StillImage v-bind="$attrs" />
</template>
<template #content>
<div class="emoji-popover">
<h3>{{ $attrs.title }}</h3>
<div class="emoji-popover-centered">
<StillImage
class="emoji"
v-bind="$attrs"
/>
</div>
<template v-if="isUserAdmin && !isLocal">
<span
v-if="adminPacksLocalLoading"
class="loading-spinner"
>
<FAIcon
class="fa-old-padding"
spin
icon="circle-notch"
/>
</span>
<div v-else>
<button
class="button button-default btn emoji-popover-button"
type="button"
:disabled="packName == ''"
@click="copyToLocalPack"
>
{{ $t('admin_dash.emoji.copy_to_pack') }}
</button>
<SelectComponent
v-model="packName"
>
<option
value=""
disabled
hidden
>
{{ $t('admin_dash.emoji.emoji_pack') }}
</option>
<option
v-for="(pack, listPackName) in adminPacksLocal"
:key="listPackName"
:label="listPackName"
>
{{ listPackName }}
</option>
</SelectComponent>
</div>
</template>
</div>
</template>
</Popover>
</template>
<script src="./still-image-emoji-popover" />
<style>
- .emoji-popover {
- margin: 0 0.5em 0.5em;
- text-align: center;
+.emoji-trigger {
+ border-radius: var(--roundness);
- .emoji {
- width: calc(var(--emoji-size) * 3);
- height: calc(var(--emoji-size) * 3);
- }
+ &:focus,
+ &:focus-visible {
+ box-shadow: 0 0 0 2px var(--accent);
+ }
+}
+
+.emoji-popover {
+ margin: 0 0.5em 0.5em;
+ text-align: center;
+
+ .emoji {
+ width: calc(var(--emoji-size) * 3);
+ height: calc(var(--emoji-size) * 3);
+ }
- .emoji-popover-centered {
- display: flex;
- align-items: center;
- justify-content: center;
- }
+ .emoji-popover-centered {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
- .emoji-popover-button {
- width: 100%;
- margin-top: 0.5em;
- }
+ .emoji-popover-button {
+ width: 100%;
+ margin-top: 0.5em;
+ }
- .Select {
- width: 100%;
- }
+ .Select {
+ width: 100%;
}
+}
</style>
diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue
index d9e96d52dd..81ccc6dd8b 100644
--- a/src/components/user_avatar/user_avatar.vue
+++ b/src/components/user_avatar/user_avatar.vue
@@ -1,92 +1,92 @@
<template>
<span
class="Avatar"
:class="{ '-compact': compact }"
>
<StillImage
v-if="user"
class="avatar"
:alt="user.screen_name_ui"
:title="user.screen_name_ui"
:src="url ? url : imgSrc(user.profile_image_url_original)"
:image-load-error="imageLoadError"
:class="{ '-compact': compact, '-better-shadow': betterShadow }"
/>
<div
v-else
class="avatar -placeholder"
:class="{ '-compact': compact }"
/>
<FAIcon
v-if="showActorTypeIndicator && user?.actor_type === 'Service'"
icon="robot"
class="actor-type-indicator"
/>
<FAIcon
v-if="showActorTypeIndicator && user?.actor_type === 'Group'"
icon="people-group"
class="actor-type-indicator"
/>
</span>
</template>
<script src="./user_avatar.js"></script>
<style lang="scss">
.Avatar {
--_avatarShadowBox: var(--shadow);
--_avatarShadowFilter: var(--shadowFilter);
--_avatarShadowInset: var(--shadowInset);
--_still-image-label-visibility: hidden;
display: inline-block;
position: relative;
- width: 3.5em;
- height: 3.5em;
+ width: var(--_avatar-size, 3.5em);
+ height: var(--_avatar-size, 3.5em);
&.-compact {
- width: 2.2em;
- height: 2.2em;
+ width: var(--_avatar-size, 2.2em);
+ height: var(--_avatar-size, 2.2em);
border-radius: var(--roundness);
}
.avatar {
width: 100%;
height: 100%;
box-shadow: var(--_avatarShadowBox);
border-radius: var(--roundness);
&.-better-shadow {
box-shadow: var(--_avatarShadowInset);
filter: var(--_avatarShadowFilter);
}
&.-animated::before {
display: none;
}
&.-compact {
border-radius: var(--roundness);
}
&.-placeholder {
background-color: var(--background);
}
}
img {
width: 100%;
height: 100%;
}
.actor-type-indicator {
position: absolute;
bottom: 0;
right: 0;
margin: -0.2em;
padding: 0.2em;
background: rgb(127 127 127 / 50%);
color: #fff;
border-radius: var(--roundness);
}
}
</style>
diff --git a/src/components/user_popover/user_popover.js b/src/components/user_popover/user_popover.js
index 6a9abf11ea..25281e5e09 100644
--- a/src/components/user_popover/user_popover.js
+++ b/src/components/user_popover/user_popover.js
@@ -1,22 +1,46 @@
import { mapState } from 'pinia'
import Popover from 'src/components/popover/popover.vue'
import UserCard from 'src/components/user_card/user_card.vue'
+import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
+import { useUsersStore } from 'src/stores/users.js'
+
+import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const UserPopover = {
name: 'UserPopover',
- props: ['userId', 'overlayCenters', 'overlayCentersSelector'],
+ props: {
+ userId: String,
+ overlayCenters: Boolean,
+ disabled: Boolean,
+ link: Boolean,
+ overlayCentersSelector: String,
+ },
components: {
UserCard,
Popover,
},
- computed: mapState(useMergedConfigStore, {
- userPopoverAvatarAction: (state) =>
- state.mergedConfig.userPopoverAvatarAction,
- userPopoverOverlay: (state) => state.mergedConfig.userPopoverOverlay,
- }),
+ computed: {
+ user() {
+ return this.userObject ?? useUsersStore().findUser(this.userId)
+ },
+ href() {
+ return this.$router.resolve(
+ generateProfileLink(
+ this.user.id,
+ this.user.screen_name,
+ useInstanceStore().restrictedNicknames,
+ ),
+ ).href
+ },
+ ...mapState(useMergedConfigStore, {
+ userPopoverAvatarAction: (state) =>
+ state.mergedConfig.userPopoverAvatarAction,
+ userPopoverOverlay: (state) => state.mergedConfig.userPopoverOverlay,
+ }),
+ },
}
export default UserPopover
diff --git a/src/components/user_popover/user_popover.vue b/src/components/user_popover/user_popover.vue
index 888c928482..803caa4dd4 100644
--- a/src/components/user_popover/user_popover.vue
+++ b/src/components/user_popover/user_popover.vue
@@ -1,54 +1,56 @@
<template>
<Popover
trigger="click"
popover-class="popover-default user-popover"
:overlay-centers-selector="overlayCentersSelector || '.user-info .Avatar'"
:overlay-centers="overlayCenters && userPopoverOverlay"
+ :link="link"
+ :href="href"
v-bind="$attrs"
>
<template #trigger>
<slot />
</template>
<template #content="{close}">
<UserCard
class="user-popover"
:compact="true"
:show-close="true"
:show-expand="true"
:user-id="userId"
:hide-bio="true"
:avatar-action="userPopoverAvatarAction == 'close' ? close : userPopoverAvatarAction"
@close="close"
/>
</template>
</Popover>
</template>
<script src="./user_popover.js"></script>
<style lang="scss">
/* popover styles load on-demand, so we need to override */
/* stylelint-disable block-no-empty */
.user-popover {
margin: 0;
.user-info {
width: 100%;
margin: 1.2em;
}
.user-identity {
min-width: calc(min(30em, 98vw));
}
.user-card-inner {
display: flex;
}
&.popover {
overflow: hidden;
}
}
/* stylelint-enable block-no-empty */
</style>

File Metadata

Mime Type
text/x-diff
Expires
Mon, Oct 12, 9:15 PM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1786484
Default Alt Text
(33 KB)

Event Timeline