Merge remote-tracking branch 'upstream/develop' into shigusegubu
* upstream/develop: (51 commits) Add floating post-status button on mobile Update user settings icon to pencil I18n: Update Czech translation user_card.vue: Copy over .status-content img styling Make minId reset with minVisibleId to prevent gaps when showing new Only connect to chat when authenticating in the first place Check for websocket token before connecting to chat user_card.vue: Set img.emoji to 32×32px #432 - prevent post status form textarea keydown event propagation Merge content types translations Use a cross browser safe solution to get scroll top Revert modifier class notation Update classname Update tests Rename UserCardContent to UserCard Update block class name of BasicUserCard component Update modifier class notation compatibility with upcoming changes Re-do UserCardContent css Load post status content type from instance config ...
This commit is contained in:
commit
b4ee6b5210
66 changed files with 1953 additions and 543 deletions
|
|
@ -160,6 +160,7 @@
|
|||
|
||||
.hider {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
white-space: nowrap;
|
||||
margin: 10px;
|
||||
padding: 5px;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||
import UserCard from '../user_card/user_card.vue'
|
||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ const BasicUserCard = {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
UserCardContent,
|
||||
UserCard,
|
||||
UserAvatar
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
<template>
|
||||
<div class="user-card">
|
||||
<div class="basic-user-card">
|
||||
<router-link :to="userProfileLink(user)">
|
||||
<UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
|
||||
</router-link>
|
||||
<div class="user-card-expanded-content" v-if="userExpanded">
|
||||
<user-card-content :user="user" :switcher="false"></user-card-content>
|
||||
<div class="basic-user-card-expanded-content" v-if="userExpanded">
|
||||
<UserCard :user="user" :rounded="true" :bordered="true"/>
|
||||
</div>
|
||||
<div class="user-card-collapsed-content" v-else>
|
||||
<div :title="user.name" class="user-card-user-name">
|
||||
<div class="basic-user-card-collapsed-content" v-else>
|
||||
<div :title="user.name" class="basic-user-card-user-name">
|
||||
<span v-if="user.name_html" v-html="user.name_html"></span>
|
||||
<span v-else>{{ user.name }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<router-link class="user-card-screen-name" :to="userProfileLink(user)">
|
||||
<router-link class="basic-user-card-screen-name" :to="userProfileLink(user)">
|
||||
@{{user.screen_name}}
|
||||
</router-link>
|
||||
</div>
|
||||
|
|
@ -26,15 +26,15 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.user-card {
|
||||
.basic-user-card {
|
||||
display: flex;
|
||||
flex: 1 0;
|
||||
margin: 0;
|
||||
padding-top: 0.6em;
|
||||
padding-right: 1em;
|
||||
padding-bottom: 0.6em;
|
||||
padding-left: 1em;
|
||||
border-bottom: 1px solid;
|
||||
margin: 0;
|
||||
border-bottom-color: $fallback--border;
|
||||
border-bottom-color: var(--border, $fallback--border);
|
||||
|
||||
|
|
@ -57,23 +57,6 @@
|
|||
&-expanded-content {
|
||||
flex: 1;
|
||||
margin-left: 0.7em;
|
||||
border-radius: $fallback--panelRadius;
|
||||
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-style: solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
border-width: 1px;
|
||||
overflow: hidden;
|
||||
|
||||
.panel-heading {
|
||||
background: transparent;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
align-content: stretch;
|
||||
flex-grow: 1;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.25em;
|
||||
|
||||
.attachments, .attachment {
|
||||
margin: 0 0.5em 0 0;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ const ImageCropper = {
|
|||
submit () {
|
||||
this.submitting = true
|
||||
this.avatarUploadError = null
|
||||
this.submitHandler(this.cropper, this.filename)
|
||||
this.submitHandler(this.cropper, this.file)
|
||||
.then(() => this.destroy())
|
||||
.catch((err) => {
|
||||
this.submitError = err
|
||||
|
|
@ -88,14 +88,14 @@ const ImageCropper = {
|
|||
readFile () {
|
||||
const fileInput = this.$refs.input
|
||||
if (fileInput.files != null && fileInput.files[0] != null) {
|
||||
this.file = fileInput.files[0]
|
||||
let reader = new window.FileReader()
|
||||
reader.onload = (e) => {
|
||||
this.dataUrl = e.target.result
|
||||
this.$emit('open')
|
||||
}
|
||||
reader.readAsDataURL(fileInput.files[0])
|
||||
this.filename = fileInput.files[0].name || 'unknown'
|
||||
this.$emit('changed', fileInput.files[0], reader)
|
||||
reader.readAsDataURL(this.file)
|
||||
this.$emit('changed', this.file, reader)
|
||||
}
|
||||
},
|
||||
clearError () {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
margin-top: 0.5em;
|
||||
|
||||
.card-image {
|
||||
flex-shrink: 0;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="modal-view" v-if="showing" @click.prevent="hide">
|
||||
<div class="modal-view media-modal-view" v-if="showing" @click.prevent="hide">
|
||||
<img class="modal-image" v-if="type === 'image'" :src="currentMedia.url"></img>
|
||||
<VideoAttachment
|
||||
class="modal-image"
|
||||
|
|
@ -32,18 +32,7 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.modal-view {
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
.media-modal-view {
|
||||
&:hover {
|
||||
.modal-view-button-arrow {
|
||||
opacity: 0.75;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||
import { throttle } from 'lodash'
|
||||
|
||||
const MobilePostStatusModal = {
|
||||
components: {
|
||||
PostStatusForm
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
hidden: false,
|
||||
postFormOpen: false,
|
||||
scrollingDown: false,
|
||||
inputActive: false,
|
||||
oldScrollPos: 0,
|
||||
amountScrolled: 0
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('scroll', this.handleScroll)
|
||||
window.addEventListener('resize', this.handleOSK)
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('scroll', this.handleScroll)
|
||||
window.removeEventListener('resize', this.handleOSK)
|
||||
},
|
||||
computed: {
|
||||
currentUser () {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
isHidden () {
|
||||
return this.hidden || this.inputActive
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openPostForm () {
|
||||
this.postFormOpen = true
|
||||
this.hidden = true
|
||||
|
||||
const el = this.$el.querySelector('textarea')
|
||||
this.$nextTick(function () {
|
||||
el.focus()
|
||||
})
|
||||
},
|
||||
closePostForm () {
|
||||
this.postFormOpen = false
|
||||
this.hidden = false
|
||||
},
|
||||
handleOSK () {
|
||||
// This is a big hack: we're guessing from changed window sizes if the
|
||||
// on-screen keyboard is active or not. This is only really important
|
||||
// for phones in portrait mode and it's more important to show the button
|
||||
// in normal scenarios on all phones, than it is to hide it when the
|
||||
// keyboard is active.
|
||||
// Guesswork based on https://www.mydevice.io/#compare-devices
|
||||
|
||||
// for example, iphone 4 and android phones from the same time period
|
||||
const smallPhone = window.innerWidth < 350
|
||||
const smallPhoneKbOpen = smallPhone && window.innerHeight < 345
|
||||
|
||||
const biggerPhone = !smallPhone && window.innerWidth < 450
|
||||
const biggerPhoneKbOpen = biggerPhone && window.innerHeight < 560
|
||||
if (smallPhoneKbOpen || biggerPhoneKbOpen) {
|
||||
this.inputActive = true
|
||||
} else {
|
||||
this.inputActive = false
|
||||
}
|
||||
},
|
||||
handleScroll: throttle(function () {
|
||||
const scrollAmount = window.scrollY - this.oldScrollPos
|
||||
const scrollingDown = scrollAmount > 0
|
||||
|
||||
if (scrollingDown !== this.scrollingDown) {
|
||||
this.amountScrolled = 0
|
||||
this.scrollingDown = scrollingDown
|
||||
if (!scrollingDown) {
|
||||
this.hidden = false
|
||||
}
|
||||
} else if (scrollingDown) {
|
||||
this.amountScrolled += scrollAmount
|
||||
if (this.amountScrolled > 100 && !this.hidden) {
|
||||
this.hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
this.oldScrollPos = window.scrollY
|
||||
this.scrollingDown = scrollingDown
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
export default MobilePostStatusModal
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<div v-if="currentUser">
|
||||
<div
|
||||
class="post-form-modal-view modal-view"
|
||||
v-show="postFormOpen"
|
||||
@click="closePostForm"
|
||||
>
|
||||
<div class="post-form-modal-panel panel" @click.stop="">
|
||||
<div class="panel-heading">{{$t('post_status.new_status')}}</div>
|
||||
<PostStatusForm class="panel-body" @posted="closePostForm"/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="new-status-button"
|
||||
:class="{ 'hidden': isHidden }"
|
||||
@click="openPostForm"
|
||||
>
|
||||
<i class="icon-edit" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./mobile_post_status_modal.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.post-form-modal-view {
|
||||
max-height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-form-modal-panel {
|
||||
flex-shrink: 0;
|
||||
margin: 25% 0 4em 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.new-status-button {
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
border-radius: 100%;
|
||||
position: fixed;
|
||||
bottom: 1.5em;
|
||||
right: 1.5em;
|
||||
// TODO: this needs its own color, it has to stand out enough and link color
|
||||
// is not very optimal for this particular use.
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--btn, $fallback--fg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
|
||||
transition: 0.35s transform;
|
||||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
|
||||
&.hidden {
|
||||
transform: translateY(150%);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.5em;
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 801px) {
|
||||
.new-status-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import Status from '../status/status.vue'
|
||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||
import UserCard from '../user_card/user_card.vue'
|
||||
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ const Notification = {
|
|||
},
|
||||
props: [ 'notification' ],
|
||||
components: {
|
||||
Status, UserAvatar, UserCardContent
|
||||
Status, UserAvatar, UserCard
|
||||
},
|
||||
methods: {
|
||||
toggleUserExpanded () {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@
|
|||
<UserAvatar :compact="true" :betterShadow="betterShadow" :src="notification.action.user.profile_image_url_original"/>
|
||||
</a>
|
||||
<div class='notification-right'>
|
||||
<div class="usercard notification-usercard" v-if="userExpanded">
|
||||
<user-card-content :user="notification.action.user" :switcher="false"></user-card-content>
|
||||
</div>
|
||||
<UserCard :user="notification.action.user" :rounded="true" :bordered="true" v-if="userExpanded"/>
|
||||
<span class="notification-details">
|
||||
<div class="name-and-action">
|
||||
<span class="username" v-if="!!notification.action.user.name_html" :title="'@'+notification.action.user.screen_name" v-html="notification.action.user.name_html"></span>
|
||||
|
|
@ -25,7 +23,11 @@
|
|||
<small>{{$t('notifications.followed_you')}}</small>
|
||||
</span>
|
||||
</div>
|
||||
<small class="timeago"><router-link v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
|
||||
<div class="timeago">
|
||||
<router-link v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }" class="faint-link">
|
||||
<timeago :since="notification.action.created_at" :auto-update="240"></timeago>
|
||||
</router-link>
|
||||
</div>
|
||||
</span>
|
||||
<div class="follow-text" v-if="notification.type === 'follow'">
|
||||
<router-link :to="userProfileLink(notification.action.user)">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ const Notifications = {
|
|||
const store = this.$store
|
||||
const credentials = store.state.users.currentUser.credentials
|
||||
|
||||
notificationsFetcher.startFetching({ store, credentials })
|
||||
const fetcherId = notificationsFetcher.startFetching({ store, credentials })
|
||||
this.$store.commit('setNotificationFetcher', { fetcherId })
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -45,10 +45,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.notification-usercard {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.non-mention {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
|
@ -126,7 +122,7 @@
|
|||
}
|
||||
|
||||
.timeago {
|
||||
font-size: 12px;
|
||||
margin-right: .2em;
|
||||
}
|
||||
|
||||
.icon-retweet.lit {
|
||||
|
|
|
|||
|
|
@ -174,6 +174,9 @@ const PostStatusForm = {
|
|||
},
|
||||
formattingOptionsEnabled () {
|
||||
return this.$store.state.instance.formattingOptionsEnabled
|
||||
},
|
||||
postFormats () {
|
||||
return this.$store.state.instance.postFormats || []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -222,6 +225,9 @@ const PostStatusForm = {
|
|||
this.highlighted = 0
|
||||
}
|
||||
},
|
||||
onKeydown (e) {
|
||||
e.stopPropagation()
|
||||
},
|
||||
setCaret ({target: {selectionStart}}) {
|
||||
this.caret = selectionStart
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
ref="textarea"
|
||||
@click="setCaret"
|
||||
@keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control"
|
||||
@keydown="onKeydown"
|
||||
@keydown.down="cycleForward"
|
||||
@keydown.up="cycleBackward"
|
||||
@keydown.shift.tab="cycleBackward"
|
||||
|
|
@ -30,15 +31,17 @@
|
|||
@drop="fileDrop"
|
||||
@dragover.prevent="fileDrag"
|
||||
@input="resize"
|
||||
@paste="paste">
|
||||
@paste="paste"
|
||||
:disabled="posting"
|
||||
>
|
||||
</textarea>
|
||||
<div class="visibility-tray">
|
||||
<span class="text-format" v-if="formattingOptionsEnabled">
|
||||
<label for="post-content-type" class="select">
|
||||
<select id="post-content-type" v-model="newStatus.contentType" class="form-control">
|
||||
<option value="text/plain">{{$t('post_status.content_type.plain_text')}}</option>
|
||||
<option value="text/html">HTML</option>
|
||||
<option value="text/markdown">Markdown</option>
|
||||
<option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat">
|
||||
{{$t(`post_status.content_type["${postFormat}"]`)}}
|
||||
</option>
|
||||
</select>
|
||||
<i class="icon-down-open"></i>
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ const settings = {
|
|||
currentSaveStateNotice () {
|
||||
return this.$store.state.interface.settings.currentSaveStateNotice
|
||||
},
|
||||
postFormats () {
|
||||
return this.$store.state.instance.postFormats || []
|
||||
},
|
||||
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel }
|
||||
},
|
||||
watch: {
|
||||
|
|
|
|||
|
|
@ -105,17 +105,9 @@
|
|||
{{$t('settings.post_status_content_type')}}
|
||||
<label for="postContentType" class="select">
|
||||
<select id="postContentType" v-model="postContentTypeLocal">
|
||||
<option value="text/plain">
|
||||
{{$t('settings.status_content_type_plain')}}
|
||||
{{postContentTypeDefault == 'text/plain' ? $t('settings.instance_default_simple') : ''}}
|
||||
</option>
|
||||
<option value="text/html">
|
||||
HTML
|
||||
{{postContentTypeDefault == 'text/html' ? $t('settings.instance_default_simple') : ''}}
|
||||
</option>
|
||||
<option value="text/markdown">
|
||||
Markdown
|
||||
{{postContentTypeDefault == 'text/markdown' ? $t('settings.instance_default_simple') : ''}}
|
||||
<option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat">
|
||||
{{$t(`post_status.content_type["${postFormat}"]`)}}
|
||||
{{postContentTypeDefault === postFormat ? $t('settings.instance_default_simple') : ''}}
|
||||
</option>
|
||||
</select>
|
||||
<i class="icon-down-open"/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||
import UserCard from '../user_card/user_card.vue'
|
||||
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
|
||||
|
||||
// TODO: separate touch gesture stuff into their own utils if more components want them
|
||||
|
|
@ -12,7 +12,7 @@ const SideDrawer = {
|
|||
closed: true,
|
||||
touchCoord: [0, 0]
|
||||
}),
|
||||
components: { UserCardContent },
|
||||
components: { UserCard },
|
||||
computed: {
|
||||
currentUser () {
|
||||
return this.$store.state.users.currentUser
|
||||
|
|
|
|||
|
|
@ -8,19 +8,14 @@
|
|||
@touchmove="touchMove"
|
||||
>
|
||||
<div class="side-drawer-heading" @click="toggleDrawer">
|
||||
<user-card-content :user="currentUser" :switcher="false" :hideBio="true" v-if="currentUser"/>
|
||||
<UserCard :user="currentUser" :hideBio="true" v-if="currentUser"/>
|
||||
<div class="side-drawer-logo-wrapper" v-else>
|
||||
<img :src="logo"/>
|
||||
<span>{{sitename}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
<li v-if="currentUser" @click="toggleDrawer">
|
||||
<router-link :to="{ name: 'new-status', params: { username: currentUser.screen_name } }">
|
||||
{{ $t("post_status.new_status") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-else @click="toggleDrawer">
|
||||
<li v-if="!currentUser" @click="toggleDrawer">
|
||||
<router-link :to="{ name: 'login' }">
|
||||
{{ $t("login.login") }}
|
||||
</router-link>
|
||||
|
|
@ -119,14 +114,14 @@
|
|||
}
|
||||
|
||||
.side-drawer-container-open {
|
||||
transition-delay: 0.0s;
|
||||
transition-property: left;
|
||||
transition: 0.35s;
|
||||
transition-property: background-color;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.side-drawer-container-closed {
|
||||
left: -100%;
|
||||
transition-delay: 0.5s;
|
||||
transition-property: left;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.side-drawer-click-outside {
|
||||
|
|
@ -181,15 +176,6 @@
|
|||
display: flex;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.profile-panel-background {
|
||||
border-radius: 0;
|
||||
.panel-heading {
|
||||
background: transparent;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.side-drawer ul {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import FavoriteButton from '../favorite_button/favorite_button.vue'
|
|||
import RetweetButton from '../retweet_button/retweet_button.vue'
|
||||
import DeleteButton from '../delete_button/delete_button.vue'
|
||||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||
import UserCard from '../user_card/user_card.vue'
|
||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||
import Gallery from '../gallery/gallery.vue'
|
||||
import LinkPreview from '../link-preview/link-preview.vue'
|
||||
|
|
@ -259,7 +259,7 @@ const Status = {
|
|||
RetweetButton,
|
||||
DeleteButton,
|
||||
PostStatusForm,
|
||||
UserCardContent,
|
||||
UserCard,
|
||||
UserAvatar,
|
||||
Gallery,
|
||||
LinkPreview
|
||||
|
|
|
|||
|
|
@ -31,9 +31,7 @@
|
|||
</router-link>
|
||||
</div>
|
||||
<div class="status-body">
|
||||
<div class="usercard" v-if="userExpanded">
|
||||
<user-card-content :user="status.user" :switcher="false"></user-card-content>
|
||||
</div>
|
||||
<UserCard :user="status.user" :rounded="true" :bordered="true" class="status-usercard" v-if="userExpanded"/>
|
||||
<div v-if="!noHeading" class="media-heading">
|
||||
<div class="heading-name-row">
|
||||
<div class="name-and-account-name">
|
||||
|
|
@ -248,8 +246,7 @@ $status-margin: 0.75em;
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.usercard {
|
||||
margin: 0;
|
||||
.status-usercard {
|
||||
margin-bottom: $status-margin;
|
||||
}
|
||||
|
||||
|
|
@ -422,6 +419,11 @@ $status-margin: 0.75em;
|
|||
max-height: 400px;
|
||||
vertical-align: middle;
|
||||
object-fit: contain;
|
||||
|
||||
&.emoji {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
|
|
|
|||
|
|
@ -132,7 +132,9 @@ const Timeline = {
|
|||
}
|
||||
if (count > 0) {
|
||||
// only 'stream' them when you're scrolled to the top
|
||||
if (window.pageYOffset < 15 &&
|
||||
const doc = document.documentElement
|
||||
const top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
|
||||
if (top < 15 &&
|
||||
!this.paused &&
|
||||
!(this.unfocused && this.$store.state.config.pauseOnUnfocused)
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { requestFollow, requestUnfollow } from '../../services/follow_manipulate
|
|||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
||||
export default {
|
||||
props: [ 'user', 'switcher', 'selected', 'hideBio' ],
|
||||
props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered' ],
|
||||
data () {
|
||||
return {
|
||||
followRequestInProgress: false,
|
||||
|
|
@ -16,7 +16,14 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
headingStyle () {
|
||||
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
|
||||
}]
|
||||
},
|
||||
style () {
|
||||
const color = this.$store.state.config.customTheme.colors
|
||||
? this.$store.state.config.customTheme.colors.bg // v2
|
||||
: this.$store.state.config.colors.bg // v1
|
||||
|
|
@ -93,22 +100,30 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
followUser () {
|
||||
const store = this.$store
|
||||
this.followRequestInProgress = true
|
||||
requestFollow(this.user, this.$store).then(({sent}) => {
|
||||
requestFollow(this.user, store).then(({sent}) => {
|
||||
this.followRequestInProgress = false
|
||||
this.followRequestSent = sent
|
||||
})
|
||||
},
|
||||
unfollowUser () {
|
||||
const store = this.$store
|
||||
this.followRequestInProgress = true
|
||||
requestUnfollow(this.user, this.$store).then(() => {
|
||||
requestUnfollow(this.user, store).then(() => {
|
||||
this.followRequestInProgress = false
|
||||
store.commit('removeStatus', { timeline: 'friends', userId: this.user.id })
|
||||
})
|
||||
},
|
||||
blockUser () {
|
||||
const store = this.$store
|
||||
store.state.api.backendInteractor.blockUser(this.user.id)
|
||||
.then((blockedUser) => store.commit('addNewUsers', [blockedUser]))
|
||||
.then((blockedUser) => {
|
||||
store.commit('addNewUsers', [blockedUser])
|
||||
store.commit('removeStatus', { timeline: 'friends', userId: this.user.id })
|
||||
store.commit('removeStatus', { timeline: 'public', userId: this.user.id })
|
||||
store.commit('removeStatus', { timeline: 'publicAndExternal', userId: this.user.id })
|
||||
})
|
||||
},
|
||||
unblockUser () {
|
||||
const store = this.$store
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div id="heading" class="profile-panel-background" :style="headingStyle">
|
||||
<div class="panel-heading text-center">
|
||||
<div class="user-card" :class="classes" :style="style">
|
||||
<div class="panel-heading">
|
||||
<div class='user-info'>
|
||||
<div class='container'>
|
||||
<router-link :to="userProfileLink(user)">
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div>
|
||||
<div :title="user.name" class='user-name' v-else>{{user.name}}</div>
|
||||
<router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser">
|
||||
<i class="button-icon icon-cog usersettings" :title="$t('tool_tip.user_settings')"></i>
|
||||
<i class="button-icon icon-pencil usersettings" :title="$t('tool_tip.user_settings')"></i>
|
||||
</router-link>
|
||||
<a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser && !user.is_local">
|
||||
<i class="icon-link-ext usersettings"></i>
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body profile-panel-body" v-if="!hideBio">
|
||||
<div class="panel-body" v-if="!hideBio">
|
||||
<div v-if="!hideUserStatsLocal && switcher" class="user-counts">
|
||||
<div class="user-count" v-on:click.prevent="setProfileView('statuses')">
|
||||
<h5>{{ $t('user_card.statuses') }}</h5>
|
||||
|
|
@ -123,40 +123,75 @@
|
|||
<span>{{user.followers_count}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p @click.prevent="linkClicked" v-if="!hideBio && user.description_html" class="profile-bio" v-html="user.description_html"></p>
|
||||
<p v-else-if="!hideBio" class="profile-bio">{{ user.description }}</p>
|
||||
<p @click.prevent="linkClicked" v-if="!hideBio && user.description_html" class="user-card-bio" v-html="user.description_html"></p>
|
||||
<p v-else-if="!hideBio" class="user-card-bio">{{ user.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./user_card_content.js"></script>
|
||||
<script src="./user_card.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.profile-panel-background {
|
||||
.user-card {
|
||||
background-size: cover;
|
||||
border-radius: $fallback--panelRadius;
|
||||
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
overflow: hidden;
|
||||
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
.panel-heading {
|
||||
padding: .5em 0;
|
||||
text-align: center;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-panel-body {
|
||||
word-wrap: break-word;
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%);
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%);
|
||||
.panel-body {
|
||||
word-wrap: break-word;
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%);
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%);
|
||||
}
|
||||
|
||||
.profile-bio {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -393,25 +428,4 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.usercard {
|
||||
width: fill-available;
|
||||
border-radius: $fallback--panelRadius;
|
||||
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-style: solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
border-width: 1px;
|
||||
overflow: hidden;
|
||||
|
||||
.panel-heading {
|
||||
background: transparent;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import LoginForm from '../login_form/login_form.vue'
|
||||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||
import UserCard from '../user_card/user_card.vue'
|
||||
|
||||
const UserPanel = {
|
||||
computed: {
|
||||
|
|
@ -9,7 +9,7 @@ const UserPanel = {
|
|||
components: {
|
||||
LoginForm,
|
||||
PostStatusForm,
|
||||
UserCardContent
|
||||
UserCard
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="user-panel">
|
||||
<div v-if='user' class="panel panel-default" style="overflow: visible;">
|
||||
<user-card-content :user="user" :switcher="false" :hideBio="true"></user-card-content>
|
||||
<UserCard :user="user" :hideBio="true" rounded="top"/>
|
||||
<div class="panel-footer">
|
||||
<post-status-form v-if='user'></post-status-form>
|
||||
</div>
|
||||
|
|
@ -11,13 +11,3 @@
|
|||
</template>
|
||||
|
||||
<script src="./user_panel.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.user-panel {
|
||||
.profile-panel-background .panel-heading {
|
||||
background: transparent;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { compose } from 'vue-compose'
|
||||
import get from 'lodash/get'
|
||||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||
import UserCard from '../user_card/user_card.vue'
|
||||
import FollowCard from '../follow_card/follow_card.vue'
|
||||
import Timeline from '../timeline/timeline.vue'
|
||||
import withLoadMore from '../../hocs/with_load_more/with_load_more'
|
||||
|
|
@ -141,10 +141,13 @@ const UserProfile = {
|
|||
}
|
||||
this.cleanUp()
|
||||
this.startUp()
|
||||
},
|
||||
$route () {
|
||||
this.$refs.tabSwitcher.activateTab(0)()
|
||||
}
|
||||
},
|
||||
components: {
|
||||
UserCardContent,
|
||||
UserCard,
|
||||
Timeline,
|
||||
FollowerList,
|
||||
FriendList
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="user.id" class="user-profile panel panel-default">
|
||||
<user-card-content
|
||||
:user="user"
|
||||
:switcher="true"
|
||||
:selected="timeline.viewing"
|
||||
/>
|
||||
<tab-switcher :renderOnlyFocused="true">
|
||||
<UserCard :user="user" :switcher="true" :selected="timeline.viewing" rounded="top"/>
|
||||
<tab-switcher :renderOnlyFocused="true" ref="tabSwitcher">
|
||||
<Timeline
|
||||
:label="$t('user_card.statuses')"
|
||||
:disabled="!user.statuses_count"
|
||||
|
|
@ -64,11 +60,6 @@
|
|||
flex: 2;
|
||||
flex-basis: 500px;
|
||||
|
||||
.profile-panel-background .panel-heading {
|
||||
background: transparent;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.userlist-placeholder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
|
|||
|
|
@ -159,8 +159,8 @@ const UserSettings = {
|
|||
}
|
||||
reader.readAsDataURL(file)
|
||||
},
|
||||
submitAvatar (cropper) {
|
||||
const img = cropper.getCroppedCanvas().toDataURL('image/jpeg')
|
||||
submitAvatar (cropper, file) {
|
||||
const img = cropper.getCroppedCanvas().toDataURL(file.type)
|
||||
return this.$store.state.api.backendInteractor.updateAvatar({ params: { img } }).then((user) => {
|
||||
if (!user.error) {
|
||||
this.$store.commit('addNewUsers', [user])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue