aboutsummaryrefslogtreecommitdiff
path: root/src/components/user_card
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/user_card')
-rw-r--r--src/components/user_card/user_card.js51
-rw-r--r--src/components/user_card/user_card.scss352
-rw-r--r--src/components/user_card/user_card.vue399
3 files changed, 460 insertions, 342 deletions
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index cd8ca420..67879307 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -4,7 +4,9 @@ import ProgressButton from '../progress_button/progress_button.vue'
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 UserNote from '../user_note/user_note.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'
@@ -14,7 +16,9 @@ import {
faRss,
faSearchPlus,
faExternalLinkAlt,
- faEdit
+ faEdit,
+ faTimes,
+ faExpandAlt
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -22,12 +26,22 @@ 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',
+ 'hasNoteEditor'
],
data () {
return {
@@ -47,15 +61,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(', ')
}
@@ -112,6 +127,16 @@ 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')
+ },
+ hasNote () {
+ return this.relationship.note
+ },
+ supportsNote () {
+ return 'note' in this.relationship
+ },
...mapGetters(['mergedConfig'])
},
components: {
@@ -122,7 +147,9 @@ export default {
ProgressButton,
FollowButton,
Select,
- RichContent
+ RichContent,
+ UserLink,
+ UserNote
},
methods: {
muteUser () {
@@ -166,10 +193,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..cdb8cb57
--- /dev/null
+++ b/src/components/user_card/user_card.scss
@@ -0,0 +1,352 @@
+@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;
+ }
+ }
+
+ .user-note {
+ margin: 0 .75em .6em 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 6b69d15a..349c7cb1 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,12 +45,16 @@
</router-link>
<div class="user-summary">
<div class="top-line">
- <RichContent
- :title="user.name"
+ <router-link
+ :to="userProfileLink(user)"
class="user-name"
- :html="user.name"
- :emoji="user.emoji"
- />
+ >
+ <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"
@@ -72,17 +83,41 @@
: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"
>
@@ -135,6 +170,7 @@
class="userHighlightCl"
type="color"
>
+ {{ ' ' }}
<Select
:id="'userHighlightSel'+user.id"
v-model="userHighlightType"
@@ -160,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"
@@ -195,6 +234,7 @@
<button
v-if="relationship.muting"
class="btn button-default btn-block toggled"
+ :disabled="user.deactivated"
@click="unmuteUser"
>
{{ $t('user_card.muted') }}
@@ -202,6 +242,7 @@
<button
v-else
class="btn button-default btn-block"
+ :disabled="user.deactivated"
@click="muteUser"
>
{{ $t('user_card.mute') }}
@@ -210,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 === &quot;admin&quot;"
+ v-if="showModerationMenu"
:user="user"
/>
</div>
@@ -226,6 +268,12 @@
>
<RemoteFollow :user="user" />
</div>
+ <UserNote
+ v-if="loggedIn && isOtherUser && (hasNote || (hasNoteEditor && supportsNote))"
+ :user="user"
+ :relationship="relationship"
+ :editable="hasNoteEditor"
+ />
</div>
</div>
<div
@@ -263,6 +311,7 @@
class="user-card-bio"
:html="user.description_html"
:emoji="user.emoji"
+ :handle-links="true"
/>
</div>
</div>
@@ -270,320 +319,4 @@
<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);
-
- &.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 {
- 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;
-
- .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>
+<style lang="scss" src="./user_card.scss" />