<template> <div class="user-card" :class="classes" > <div :class="{ 'hide-bio': hideBio }" :style="style" class="background-image" /> <div class="panel-heading -flexible-height"> <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"> <FAIcon class="fa-scale-110 fa-old-padding" icon="search-plus" /> </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"> <RichContent :title="user.name" class="user-name" :html="user.name" :emoji="user.emoji" /> <button v-if="!isOtherUser && user.is_local" class="button-unstyled edit-profile-button" @click.stop="openProfileTab" > <FAIcon fixed-width class="icon" icon="edit" :title="$t('user_card.edit_profile')" /> </button> <a v-if="isOtherUser && !user.is_local" :href="user.statusnet_profile_url" target="_blank" class="button-unstyled external-link-button" > <FAIcon class="icon" icon="external-link-alt" /> </a> <AccountActions v-if="isOtherUser && loggedIn" :user="user" :relationship="relationship" /> </div> <div class="bottom-line"> <router-link class="user-screen-name" :title="user.screen_name_ui" :to="userProfileLink(user)" > @{{ user.screen_name_ui }} </router-link> <template v-if="!hideBio"> <span v-if="user.deactivated" class="alert user-role" > {{ $t('user_card.deactivated') }} </span> <span v-if="!!visibleRole" class="alert user-role" > {{ $t(`general.role.${visibleRole}`) }} </span> <span v-if="user.bot" class="alert user-role" > {{ $t('user_card.bot') }} </span> </template> <span v-if="user.locked"> <FAIcon class="lock-icon" icon="lock" size="sm" /> </span> <span v-if="!mergedConfig.hideUserStats && !hideBio" class="dailyAvg" >{{ dailyAvg }} {{ $t('user_card.per_day') }}</span> </div> </div> </div> <div class="user-meta"> <div v-if="relationship.followed_by && 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" > {{ ' ' }} <Select :id="'userHighlightSel'+user.id" v-model="userHighlightType" class="userHighlightSel" > <option value="disabled"> {{ $t('user_card.highlight.disabled') }} </option> <option value="solid"> {{ $t('user_card.highlight.solid') }} </option> <option value="striped"> {{ $t('user_card.highlight.striped') }} </option> <option value="side"> {{ $t('user_card.highlight.side') }} </option> </Select> </div> </div> <div v-if="loggedIn && isOtherUser" class="user-interactions" > <div class="btn-group"> <FollowButton :relationship="relationship" :user="user" /> <template v-if="relationship.following"> <ProgressButton v-if="!relationship.subscribing" class="btn button-default" :click="subscribeUser" :title="$t('user_card.subscribe')" > <FAIcon icon="bell" /> </ProgressButton> <ProgressButton v-else class="btn button-default toggled" :click="unsubscribeUser" :title="$t('user_card.unsubscribe')" > <FALayers> <FAIcon icon="rss" transform="left-5 shrink-6 up-3 rotate-20" flip="horizontal" /> <FAIcon icon="rss" transform="right-5 shrink-6 up-3 rotate-20" /> <FAIcon icon="bell" /> </FALayers> </ProgressButton> </template> </div> <div> <button v-if="relationship.muting" class="btn button-default btn-block toggled" :disabled="user.deactivated" @click="unmuteUser" > {{ $t('user_card.muted') }} </button> <button v-else class="btn button-default btn-block" :disabled="user.deactivated" @click="muteUser" > {{ $t('user_card.mute') }} </button> </div> <div> <button class="btn button-default btn-block" :disabled="user.deactivated" @click="mentionUser" > {{ $t('user_card.mention') }} </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="!mergedConfig.hideUserStats && 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>{{ hideFollowsCount ? $t('user_card.hidden') : user.friends_count }}</span> </div> <div class="user-count" @click.prevent="setProfileView('followers')" > <h5>{{ $t('user_card.followers') }}</h5> <span>{{ hideFollowersCount ? $t('user_card.hidden') : user.followers_count }}</span> </div> </div> <RichContent v-if="!hideBio" class="user-card-bio" :html="user.description_html" :emoji="user.emoji" :handle-links="true" /> </div> </div> </template> <script src="./user_card.js"></script> <style lang="scss"> @import '../../_variables.scss'; .user-card { position: relative; &:hover { --_still-image-img-visibility: visible; --_still-image-canvas-visibility: hidden; --_still-image-label-visibility: hidden; } .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%; border-top-left-radius: calc(var(--panelRadius) - 1px); border-top-right-radius: calc(var(--panelRadius) - 1px); background-color: var(--profileBg); z-index: -2; &.hide-bio { mask-size: 100% 40px; } } &-bio { text-align: center; display: block; line-height: 18px; padding: 1em; margin: 0; a { color: $fallback--link; color: var(--postLink, $fallback--link); } img { object-fit: contain; vertical-align: middle; max-width: 100%; max-height: 400px; } } // 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 { min-width: 0; padding: 16px 0 6px; display: flex; align-items: flex-start; max-height: 56px; > * { min-width: 0; } .Avatar { --_avatarShadowBox: var(--avatarShadow); --_avatarShadowFilter: var(--avatarShadowFilter); --_avatarShadowInset: var(--avatarShadowInset); flex: 1 0 100%; width: 56px; height: 56px; object-fit: cover; } } &-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; svg { color: #FFF; } } &:hover &-overlay { opacity: 1; } } .external-link-button, .edit-profile-button { cursor: pointer; width: 2.5em; text-align: center; margin: -0.5em 0; padding: 0.5em 0; &:not(:hover) .icon { color: $fallback--lightText; color: var(--lightText, $fallback--lightText); } } .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; .top-line { display: flex; } } .user-name { text-overflow: ellipsis; overflow: hidden; flex: 1 1 auto; margin-right: 1em; font-size: 15px; --emoji-size: 14px; } .bottom-line { display: flex; font-weight: light; font-size: 15px; .lock-icon { margin-left: 0.5em; } .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); } .user-role { flex: none; color: $fallback--text; color: var(--alertNeutralText, $fallback--text); background-color: $fallback--fg; background-color: var(--alertNeutral, $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 { padding-top: 0; padding-bottom: 0; flex: 1 0 auto; } .userHighlightText { width: 70px; flex: 1 0 auto; } .userHighlightCl, .userHighlightText, .userHighlightSel { vertical-align: top; margin-right: .5em; margin-bottom: .25em; } } } .user-interactions { position: relative; display: flex; flex-flow: row wrap; margin-right: -.75em; > * { margin: 0 .75em .6em 0; white-space: nowrap; min-width: 95px; } button { margin: 0; } } } .sidebar .edit-profile-button { display: none; } .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>