a3305799c7
* 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 ...
637 lines
15 KiB
Vue
637 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 === "admin""
|
|
: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>
|