diff options
Diffstat (limited to 'src/components/user_card')
| -rw-r--r-- | src/components/user_card/user_card.js | 44 | ||||
| -rw-r--r-- | src/components/user_card/user_card.scss | 348 | ||||
| -rw-r--r-- | src/components/user_card/user_card.vue | 436 |
3 files changed, 448 insertions, 380 deletions
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 367fbc6c..8b64a07e 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -5,6 +5,8 @@ import FollowButton from '../follow_button/follow_button.vue' import ModerationTools from '../moderation_tools/moderation_tools.vue' import AccountActions from '../account_actions/account_actions.vue' import Select from '../select/select.vue' +import UserLink from '../user_link/user_link.vue' +import RichContent from 'src/components/rich_content/rich_content.jsx' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { mapGetters } from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' @@ -13,7 +15,9 @@ import { faRss, faSearchPlus, faExternalLinkAlt, - faEdit + faEdit, + faTimes, + faExpandAlt } from '@fortawesome/free-solid-svg-icons' library.add( @@ -21,12 +25,21 @@ library.add( faBell, faSearchPlus, faExternalLinkAlt, - faEdit + faEdit, + faTimes, + faExpandAlt ) export default { props: [ - 'userId', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar' + 'userId', + 'switcher', + 'selected', + 'hideBio', + 'rounded', + 'bordered', + 'avatarAction', // default - open profile, 'zoom' - zoom, function - call function + 'onClose' ], data () { return { @@ -46,15 +59,16 @@ export default { }, classes () { return [{ - 'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius - 'user-card-rounded': this.rounded === true, // set border-radius for all sides - 'user-card-bordered': this.bordered === true // set border for all sides + '-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius + '-rounded': this.rounded === true, // set border-radius for all sides + '-bordered': this.bordered === true, // set border for all sides + '-popover': !!this.onClose // set popover rounding }] }, style () { return { backgroundImage: [ - `linear-gradient(to bottom, var(--profileTint), var(--profileTint))`, + 'linear-gradient(to bottom, var(--profileTint), var(--profileTint))', `url(${this.user.cover_photo})` ].join(', ') } @@ -111,6 +125,10 @@ export default { hideFollowersCount () { return this.isOtherUser && this.user.hide_followers_count }, + showModerationMenu () { + const privileges = this.loggedIn.privileges + return this.loggedIn.role === 'admin' || privileges.includes('users_manage_activation_state') || privileges.includes('users_delete') || privileges.includes('users_manage_tags') + }, ...mapGetters(['mergedConfig']) }, components: { @@ -120,7 +138,9 @@ export default { AccountActions, ProgressButton, FollowButton, - Select + Select, + RichContent, + UserLink }, methods: { muteUser () { @@ -164,10 +184,16 @@ export default { mimetype: 'image' } this.$store.dispatch('setMedia', [attachment]) - this.$store.dispatch('setCurrent', attachment) + this.$store.dispatch('setCurrentMedia', attachment) }, mentionUser () { this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user }) + }, + onAvatarClickHandler (e) { + if (this.onAvatarClick) { + e.preventDefault() + this.onAvatarClick() + } } } } diff --git a/src/components/user_card/user_card.scss b/src/components/user_card/user_card.scss new file mode 100644 index 00000000..a0bbc6a6 --- /dev/null +++ b/src/components/user_card/user_card.scss @@ -0,0 +1,348 @@ +@import '../../_variables.scss'; + +.user-card { + position: relative; + z-index: 1; + + &: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); + // Autoprefixer 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(--__roundnessTop, --panelRadius) - 1px); + border-top-right-radius: calc(var(--__roundnessTop, --panelRadius) - 1px); + border-bottom-left-radius: calc(var(--__roundnessBottom, --panelRadius) - 1px); + border-bottom-right-radius: calc(var(--__roundnessBottom, --panelRadius) - 1px); + background-color: var(--profileBg); + z-index: -2; + + &.hide-bio { + mask-size: 100% 40px; + } + } + + &-bio { + text-align: center; + display: block; + line-height: 1.3; + 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; + } + } + + &.-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); + + --__roundnessTop: var(--panelRadius); + --__roundnessBottom: 0; + } + + &.-rounded { + border-radius: $fallback--panelRadius; + border-radius: var(--panelRadius, $fallback--panelRadius); + + --__roundnessTop: var(--panelRadius); + --__roundnessBottom: var(--panelRadius); + } + + &.-popover { + border-radius: $fallback--tooltipRadius; + border-radius: var(--tooltipRadius, $fallback--tooltipRadius); + + --__roundnessTop: var(--tooltipRadius); + --__roundnessBottom: var(--tooltipRadius); + } + + &.-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; + + a { + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); + + &:hover { + color: var(--icon); + } + } + + .container { + min-width: 0; + padding: 16px 0 6px; + display: flex; + align-items: flex-start; + max-height: 56px; + + > * { + min-width: 0; + } + + > a { + vertical-align: middle; + display: flex; + } + + .Avatar { + --_avatarShadowBox: var(--avatarShadow); + --_avatarShadowFilter: var(--avatarShadowFilter); + --_avatarShadowInset: var(--avatarShadowInset); + + width: 56px; + height: 56px; + object-fit: cover; + } + } + + &-avatar { + 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; + line-height: 2em; + + --emoji-size: 1.7em; + + .top-line, + .bottom-line { + display: flex; + } + } + + .user-name { + text-overflow: ellipsis; + overflow: hidden; + flex: 1 1 auto; + margin-right: 1em; + font-size: 1.1em; + } + + .bottom-line { + font-weight: light; + font-size: 1.1em; + align-items: baseline; + + .lock-icon { + margin-left: 0.5em; + } + + .user-screen-name { + min-width: 1px; + flex: 0 1 auto; + text-overflow: ellipsis; + overflow: hidden; + } + + .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; + 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; + } +} diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 528b92fb..897d89f9 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -8,25 +8,32 @@ :style="style" class="background-image" /> - <div class="panel-heading"> + <div :class="onClose ? '' : panel-heading -flexible-height"> <div class="user-info"> <div class="container"> <a - v-if="allowZoomingAvatar" - class="user-info-avatar-link" + v-if="avatarAction === 'zoom'" + class="user-info-avatar -link" @click="zoomAvatar" > <UserAvatar :better-shadow="betterShadow" :user="user" /> - <div class="user-info-avatar-link-overlay"> + <div class="user-info-avatar -link -overlay"> <FAIcon class="fa-scale-110 fa-old-padding" icon="search-plus" /> </div> </a> + <UserAvatar + v-else-if="typeof avatarAction === 'function'" + class="user-info-avatar" + :better-shadow="betterShadow" + :user="user" + @click="avatarAction" + /> <router-link v-else :to="userProfileLink(user)" @@ -38,21 +45,16 @@ </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" + <router-link + :to="userProfileLink(user)" class="user-name" > - {{ user.name }} - </div> + <RichContent + :title="user.name" + :html="user.name" + :emoji="user.emoji" + /> + </router-link> <button v-if="!isOtherUser && user.is_local" class="button-unstyled edit-profile-button" @@ -65,7 +67,7 @@ :title="$t('user_card.edit_profile')" /> </button> - <button + <a v-if="isOtherUser && !user.is_local" :href="user.statusnet_profile_url" target="_blank" @@ -75,23 +77,47 @@ class="icon" icon="external-link-alt" /> - </button> + </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" + v-if="onClose" :to="userProfileLink(user)" + class="button-unstyled external-link-button" + @click="onClose" > - @{{ user.screen_name_ui }} + <FAIcon + class="icon" + icon="expand-alt" + /> </router-link> + <button + v-if="onClose" + class="button-unstyled external-link-button" + @click="onClose" + > + <FAIcon + class="icon" + icon="times" + /> + </button> + </div> + <div class="bottom-line"> + <user-link + class="user-screen-name" + :user="user" + /> <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" > @@ -144,6 +170,7 @@ class="userHighlightCl" type="color" > + {{ ' ' }} <Select :id="'userHighlightSel'+user.id" v-model="userHighlightType" @@ -169,7 +196,10 @@ class="user-interactions" > <div class="btn-group"> - <FollowButton :relationship="relationship" /> + <FollowButton + :relationship="relationship" + :user="user" + /> <template v-if="relationship.following"> <ProgressButton v-if="!relationship.subscribing" @@ -204,6 +234,7 @@ <button v-if="relationship.muting" class="btn button-default btn-block toggled" + :disabled="user.deactivated" @click="unmuteUser" > {{ $t('user_card.muted') }} @@ -211,6 +242,7 @@ <button v-else class="btn button-default btn-block" + :disabled="user.deactivated" @click="muteUser" > {{ $t('user_card.mute') }} @@ -219,13 +251,14 @@ <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"" + v-if="showModerationMenu" :user="user" /> </div> @@ -267,356 +300,17 @@ <span>{{ hideFollowersCount ? $t('user_card.hidden') : user.followers_count }}</span> </div> </div> - <!-- eslint-disable vue/no-v-html --> - <p - v-if="!hideBio && user.description_html" + <RichContent + v-if="!hideBio" class="user-card-bio" - @click.prevent="linkClicked" - v-html="user.description_html" + :html="user.description_html" + :emoji="user.emoji" + :handle-links="true" /> - <!-- 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; - - &:hover .Avatar { - --_still-image-img-visibility: visible; - --_still-image-canvas-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); - - &.hide-bio { - mask-size: 100% 40px; - } - } - - p { - margin-bottom: 0; - } - - &-bio { - text-align: center; - - a { - color: $fallback--link; - color: var(--postLink, $fallback--link); - } - - 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 { - --_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; - - 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; - - .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> +<style lang="scss" src="./user_card.scss" /> |
