pleroma-fe/src/components/user_card/user_card.vue
Henry Jameson a3305799c7 Merge remote-tracking branch 'upstream/develop' into emoji-selector-update
* upstream/develop: (42 commits)
  Fix formatting in oc.json
  avoid using global class
  fix logo moving bug when lightbox is open
  Reserve scrollbar gap when body scroll is locked
  setting display: initial makes trouble, instead, toggle display: none using classname
  lock body scroll
  add body-scroll-lock directive
  install body-scroll-lock
  wire up props with PostStatusModal
  rename component
  recover autofocusing behavior
  refactor MobilePostStatusModal using new PostStatusModal
  add new module and modal to post new status
  remove needless condition
  add mention button
  wire up user state with global store
  collapse fav/repeat notifications from muted users
  do not collapse thread muted posts in conversation
  detect thread-muted posts
  do not change word based muting logic
  ...
2019-09-25 20:26:49 +03:00

636 lines
15 KiB
Vue

<template>
<div
class="user-card"
:class="classes"
>
<div
:class="{ 'hide-bio': hideBio }"
:style="style"
class="background-image"
/>
<div class="panel-heading">
<div class="user-info">
<div class="container">
<a
v-if="allowZoomingAvatar"
class="user-info-avatar-link"
@click="zoomAvatar"
>
<UserAvatar
:better-shadow="betterShadow"
:user="user"
/>
<div class="user-info-avatar-link-overlay">
<i class="button-icon icon-zoom-in" />
</div>
</a>
<router-link
v-else
:to="userProfileLink(user)"
>
<UserAvatar
:better-shadow="betterShadow"
:user="user"
/>
</router-link>
<div class="user-summary">
<div class="top-line">
<!-- eslint-disable vue/no-v-html -->
<div
v-if="user.name_html"
:title="user.name"
class="user-name"
v-html="user.name_html"
/>
<!-- eslint-enable vue/no-v-html -->
<div
v-else
:title="user.name"
class="user-name"
>
{{ user.name }}
</div>
<router-link
v-if="!isOtherUser"
:to="{ name: 'user-settings' }"
>
<i
class="button-icon icon-wrench usersettings"
:title="$t('tool_tip.user_settings')"
/>
</router-link>
<a
v-if="isOtherUser && !user.is_local"
:href="user.statusnet_profile_url"
target="_blank"
>
<i class="icon-link-ext usersettings" />
</a>
</div>
<div class="bottom-line">
<router-link
class="user-screen-name"
:to="userProfileLink(user)"
>
@{{ user.screen_name }}
</router-link>
<span
v-if="!hideBio && !!visibleRole"
class="alert staff"
>{{ visibleRole }}</span>
<span v-if="user.locked"><i class="icon icon-lock" /></span>
<span
v-if="!hideUserStatsLocal && !hideBio"
class="dailyAvg"
>{{ dailyAvg }} {{ $t('user_card.per_day') }}</span>
</div>
</div>
</div>
<div class="user-meta">
<div
v-if="user.follows_you && loggedIn && isOtherUser"
class="following"
>
{{ $t('user_card.follows_you') }}
</div>
<div
v-if="isOtherUser && (loggedIn || !switcher)"
class="highlighter"
>
<!-- id's need to be unique, otherwise vue confuses which user-card checkbox belongs to -->
<input
v-if="userHighlightType !== 'disabled'"
:id="'userHighlightColorTx'+user.id"
v-model="userHighlightColor"
class="userHighlightText"
type="text"
>
<input
v-if="userHighlightType !== 'disabled'"
:id="'userHighlightColor'+user.id"
v-model="userHighlightColor"
class="userHighlightCl"
type="color"
>
<label
for="style-switcher"
class="userHighlightSel select"
>
<select
:id="'userHighlightSel'+user.id"
v-model="userHighlightType"
class="userHighlightSel"
>
<option value="disabled">No highlight</option>
<option value="solid">Solid bg</option>
<option value="striped">Striped bg</option>
<option value="side">Side stripe</option>
</select>
<i class="icon-down-open" />
</label>
</div>
</div>
<div
v-if="loggedIn && isOtherUser"
class="user-interactions"
>
<div v-if="!user.following">
<button
class="btn btn-default btn-block"
:disabled="followRequestInProgress"
:title="user.requested ? $t('user_card.follow_again') : ''"
@click="followUser"
>
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else-if="user.requested">
{{ $t('user_card.follow_sent') }}
</template>
<template v-else>
{{ $t('user_card.follow') }}
</template>
</button>
</div>
<div v-else-if="followRequestInProgress">
<button
class="btn btn-default btn-block pressed"
disabled
:title="$t('user_card.follow_unfollow')"
@click="unfollowUser"
>
{{ $t('user_card.follow_progress') }}
</button>
</div>
<div
v-else
class="btn-group"
>
<button
class="btn btn-default pressed"
:title="$t('user_card.follow_unfollow')"
@click="unfollowUser"
>
{{ $t('user_card.following') }}
</button>
<ProgressButton
v-if="!user.subscribed"
class="btn btn-default"
:click="subscribeUser"
:title="$t('user_card.subscribe')"
>
<i class="icon-bell-alt" />
</ProgressButton>
<ProgressButton
v-else
class="btn btn-default pressed"
:click="unsubscribeUser"
:title="$t('user_card.unsubscribe')"
>
<i class="icon-bell-ringing-o" />
</ProgressButton>
</div>
<div>
<button
class="btn btn-default btn-block"
@click="mentionUser"
>
{{ $t('user_card.mention') }}
</button>
</div>
<div>
<button
v-if="user.muted"
class="btn btn-default btn-block pressed"
@click="unmuteUser"
>
{{ $t('user_card.muted') }}
</button>
<button
v-else
class="btn btn-default btn-block"
@click="muteUser"
>
{{ $t('user_card.mute') }}
</button>
</div>
<div>
<button
v-if="user.statusnet_blocking"
class="btn btn-default btn-block pressed"
@click="unblockUser"
>
{{ $t('user_card.blocked') }}
</button>
<button
v-else
class="btn btn-default btn-block"
@click="blockUser"
>
{{ $t('user_card.block') }}
</button>
</div>
<div>
<button
class="btn btn-default btn-block"
@click="reportUser"
>
{{ $t('user_card.report') }}
</button>
</div>
<ModerationTools
v-if="loggedIn.role === &quot;admin&quot;"
:user="user"
/>
</div>
<div
v-if="!loggedIn && user.is_local"
class="user-interactions"
>
<RemoteFollow :user="user" />
</div>
</div>
</div>
<div
v-if="!hideBio"
class="panel-body"
>
<div
v-if="!hideUserStatsLocal && switcher"
class="user-counts"
>
<div
class="user-count"
@click.prevent="setProfileView('statuses')"
>
<h5>{{ $t('user_card.statuses') }}</h5>
<span>{{ user.statuses_count }} <br></span>
</div>
<div
class="user-count"
@click.prevent="setProfileView('friends')"
>
<h5>{{ $t('user_card.followees') }}</h5>
<span>{{ user.friends_count }}</span>
</div>
<div
class="user-count"
@click.prevent="setProfileView('followers')"
>
<h5>{{ $t('user_card.followers') }}</h5>
<span>{{ user.followers_count }}</span>
</div>
</div>
<!-- eslint-disable vue/no-v-html -->
<p
v-if="!hideBio && user.description_html"
class="user-card-bio"
@click.prevent="linkClicked"
v-html="user.description_html"
/>
<!-- eslint-enable vue/no-v-html -->
<p
v-else-if="!hideBio"
class="user-card-bio"
>
{{ user.description }}
</p>
</div>
</div>
</template>
<script src="./user_card.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.user-card {
position: relative;
.panel-heading {
padding: .5em 0;
text-align: center;
box-shadow: none;
background: transparent;
flex-direction: column;
align-items: stretch;
// create new stacking context
position: relative;
}
.panel-body {
word-wrap: break-word;
border-bottom-right-radius: inherit;
border-bottom-left-radius: inherit;
// create new stacking context
position: relative;
}
.background-image {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
mask: linear-gradient(to top, white, transparent) bottom no-repeat,
linear-gradient(to top, white, white);
// Autoprefixed seem to ignore this one, and also syntax is different
-webkit-mask-composite: xor;
mask-composite: exclude;
background-size: cover;
mask-size: 100% 60%;
&.hide-bio {
mask-size: 100% 40px;
}
}
p {
margin-bottom: 0;
}
&-bio {
text-align: center;
img {
object-fit: contain;
vertical-align: middle;
max-width: 100%;
max-height: 400px;
&.emoji {
width: 32px;
height: 32px;
}
}
}
// Modifiers
&-rounded-t {
border-top-left-radius: $fallback--panelRadius;
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
border-top-right-radius: $fallback--panelRadius;
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
}
&-rounded {
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
}
&-bordered {
border-width: 1px;
border-style: solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
}
}
.user-info {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
padding: 0 26px;
.container {
padding: 16px 0 6px;
display: flex;
align-items: flex-start;
max-height: 56px;
.avatar {
flex: 1 0 100%;
width: 56px;
height: 56px;
box-shadow: 0px 1px 8px rgba(0,0,0,0.75);
box-shadow: var(--avatarShadow);
object-fit: cover;
}
}
&:hover .animated.avatar {
canvas {
display: none;
}
img {
visibility: visible;
}
}
&-avatar-link {
position: relative;
cursor: pointer;
&-overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
border-radius: $fallback--avatarRadius;
border-radius: var(--avatarRadius, $fallback--avatarRadius);
opacity: 0;
transition: opacity .2s ease;
i {
color: #FFF;
}
}
&:hover &-overlay {
opacity: 1;
}
}
.usersettings {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
opacity: .8;
}
.user-summary {
display: block;
margin-left: 0.6em;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 1 0;
// This is so that text doesn't get overlapped by avatar's shadow if it has
// big one
z-index: 1;
img {
width: 26px;
height: 26px;
vertical-align: middle;
object-fit: contain
}
.top-line {
display: flex;
}
}
.user-name {
text-overflow: ellipsis;
overflow: hidden;
flex: 1 1 auto;
margin-right: 1em;
font-size: 15px;
img {
object-fit: contain;
height: 16px;
width: 16px;
vertical-align: middle;
}
}
.bottom-line {
display: flex;
font-weight: light;
font-size: 15px;
.user-screen-name {
min-width: 1px;
flex: 0 1 auto;
text-overflow: ellipsis;
overflow: hidden;
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
.dailyAvg {
min-width: 1px;
flex: 0 0 auto;
margin-left: 1em;
font-size: 0.7em;
color: $fallback--text;
color: var(--text, $fallback--text);
}
// TODO use proper colors
.staff {
flex: none;
text-transform: capitalize;
color: $fallback--text;
color: var(--btnText, $fallback--text);
background-color: $fallback--fg;
background-color: var(--btn, $fallback--fg);
}
}
.user-meta {
margin-bottom: .15em;
display: flex;
align-items: baseline;
font-size: 14px;
line-height: 22px;
flex-wrap: wrap;
.following {
flex: 1 0 auto;
margin: 0;
margin-bottom: .25em;
text-align: left;
}
.highlighter {
flex: 0 1 auto;
display: flex;
flex-wrap: wrap;
margin-right: -.5em;
align-self: start;
.userHighlightCl {
padding: 2px 10px;
flex: 1 0 auto;
}
.userHighlightSel,
.userHighlightSel.select {
padding-top: 0;
padding-bottom: 0;
flex: 1 0 auto;
}
.userHighlightSel.select i {
line-height: 22px;
}
.userHighlightText {
width: 70px;
flex: 1 0 auto;
}
.userHighlightCl,
.userHighlightText,
.userHighlightSel,
.userHighlightSel.select {
height: 22px;
vertical-align: top;
margin-right: .5em;
margin-bottom: .25em;
}
}
}
.user-interactions {
position: relative;
display: flex;
flex-flow: row wrap;
justify-content: space-between;
margin-right: -.75em;
> * {
flex: 1 0 0;
margin: 0 .75em .6em 0;
white-space: nowrap;
}
button {
margin: 0;
&.pressed {
// TODO: This should be themed.
border-bottom-color: rgba(255, 255, 255, 0.2);
border-top-color: rgba(0, 0, 0, 0.2);
}
}
}
}
.user-counts {
display: flex;
line-height:16px;
padding: .5em 1.5em 0em 1.5em;
text-align: center;
justify-content: space-between;
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
flex-wrap: wrap;
}
.user-count {
flex: 1 0 auto;
padding: .5em 0 .5em 0;
margin: 0 .5em;
h5 {
font-size:1em;
font-weight: bolder;
margin: 0 0 0.25em;
}
a {
text-decoration: none;
}
}
</style>