Merge remote-tracking branch 'upstream/develop' into emoji-selector-update

* upstream/develop: (116 commits)
  Password reset page
  add a comment
  force img updating immediately
  Fixed "sequimiento" to "seguimiento".
  Replace `/api/externalprofile/show.json` with a MastoAPI equialent
  Use mastodon api in follow requests
  "Optional" in lowercase.
  Update es.json
  fix pin/unpin status logic
  rename a mutation
  update
  fix user avatar fallback logic
  remove dead code
  Corrected "Media Proxy" translation.
  Update es.json
  make bio textarea resizable vertically only
  remove dead code
  Make image orientation consistent on FF, fix videos w/ modal
  remove dead code
  fix crazy watch logic in conversation
  ...
This commit is contained in:
Henry Jameson 2019-09-08 13:44:29 +03:00
commit db086fe1fd
80 changed files with 2652 additions and 340 deletions

View file

@ -190,6 +190,7 @@
.video {
width: 100%;
height: 100%;
}
.play-icon {
@ -286,7 +287,7 @@
}
img {
image-orientation: from-image;
image-orientation: from-image; // NOTE: only FF supports this
}
}
}

View file

@ -87,6 +87,7 @@
&-expanded-content {
flex: 1;
margin-left: 0.7em;
min-width: 0;
}
}
</style>

View file

@ -42,7 +42,7 @@ const conversation = {
'statusoid',
'collapsable',
'isPage',
'showPinned'
'pinnedStatusIdsObject'
],
created () {
if (this.isPage) {
@ -110,7 +110,7 @@ const conversation = {
Status
},
watch: {
'$route': 'fetchConversation',
status: 'fetchConversation',
expanded (value) {
if (value) {
this.fetchConversation()
@ -149,9 +149,6 @@ const conversation = {
},
toggleExpanded () {
this.expanded = !this.expanded
if (!this.expanded) {
this.setHighlight(null)
}
}
}
}

View file

@ -21,7 +21,7 @@
:inline-expanded="collapsable && isExpanded"
:statusoid="status"
:expandable="!isExpanded"
:show-pinned="showPinned"
:show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
:focused="focused(status.id)"
:in-conversation="isExpanded"
:highlight="getHighlight()"

View file

@ -16,6 +16,16 @@ const ExtraButtons = {
this.$store.dispatch('unpinStatus', this.status.id)
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
muteConversation () {
this.$store.dispatch('muteConversation', this.status.id)
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
unmuteConversation () {
this.$store.dispatch('unmuteConversation', this.status.id)
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
}
},
computed: {
@ -31,8 +41,8 @@ const ExtraButtons = {
canPin () {
return this.ownStatus && (this.status.visibility === 'public' || this.status.visibility === 'unlisted')
},
enabled () {
return this.canPin || this.canDelete
canMute () {
return !!this.currentUser
}
}
}

View file

@ -1,6 +1,6 @@
<template>
<v-popover
v-if="enabled"
v-if="canDelete || canMute || canPin"
trigger="click"
placement="top"
class="extra-button-popover"
@ -9,6 +9,20 @@
>
<div slot="popover">
<div class="dropdown-menu">
<button
v-if="canMute && !status.muted"
class="dropdown-item dropdown-item-icon"
@click.prevent="muteConversation"
>
<i class="icon-eye-off" /><span>{{ $t("status.mute_conversation") }}</span>
</button>
<button
v-if="canMute && status.muted"
class="dropdown-item dropdown-item-icon"
@click.prevent="unmuteConversation"
>
<i class="icon-eye-off" /><span>{{ $t("status.unmute_conversation") }}</span>
</button>
<button
v-if="!status.pinned && canPin"
v-close-popover

View file

@ -1,8 +1,6 @@
const FeaturesPanel = {
computed: {
chat: function () {
return this.$store.state.instance.chatAvailable && (!this.$store.state.chatDisabled)
},
chat: function () { return this.$store.state.instance.chatAvailable },
gopher: function () { return this.$store.state.instance.gopherAvailable },
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },

View file

@ -61,13 +61,17 @@
}
&.contain-fit {
img, video {
img,
video,
canvas {
object-fit: contain;
}
}
&.cover-fit {
img, video {
img,
video,
canvas {
object-fit: cover;
}
}

View file

@ -2,9 +2,6 @@ const InstanceSpecificPanel = {
computed: {
instanceSpecificPanelContent () {
return this.$store.state.instance.instanceSpecificPanelContent
},
show () {
return !this.$store.state.config.hideISP
}
}
}

View file

@ -1,8 +1,5 @@
<template>
<div
v-if="show"
class="instance-specific-panel"
>
<div class="instance-specific-panel">
<div class="panel panel-default">
<div class="panel-body">
<!-- eslint-disable vue/no-v-html -->
@ -14,6 +11,3 @@
</template>
<script src="./instance_specific_panel.js" ></script>
<style lang="scss">
</style>

View file

@ -13,8 +13,8 @@ const Interactions = {
}
},
methods: {
onModeSwitch (index, dataset) {
this.filterMode = tabModeDict[dataset.filter]
onModeSwitch (key) {
this.filterMode = tabModeDict[key]
}
},
components: {

View file

@ -10,18 +10,15 @@
:on-switch="onModeSwitch"
>
<span
data-tab-dummy
data-filter="mentions"
key="mentions"
:label="$t('nav.mentions')"
/>
<span
data-tab-dummy
data-filter="likes+repeats"
key="likes+repeats"
:label="$t('interactions.favs_repeats')"
/>
<span
data-tab-dummy
data-filter="follows"
key="follows"
:label="$t('interactions.follows')"
/>
</tab-switcher>

View file

@ -5,6 +5,11 @@ const LinkPreview = {
'size',
'nsfw'
],
data () {
return {
imageLoaded: false
}
},
computed: {
useImage () {
// Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid
@ -15,6 +20,15 @@ const LinkPreview = {
useDescription () {
return this.card.description && /\S/.test(this.card.description)
}
},
created () {
if (this.useImage) {
const newImg = new Image()
newImg.onload = () => {
this.imageLoaded = true
}
newImg.src = this.card.image
}
}
}

View file

@ -7,7 +7,7 @@
rel="noopener"
>
<div
v-if="useImage"
v-if="useImage && imageLoaded"
class="card-image"
:class="{ 'small-image': size === 'small' }"
>

View file

@ -33,6 +33,11 @@
type="password"
>
</div>
<div class="form-group">
<router-link :to="{name: 'password-reset'}">
{{ $t('password_reset.forgot_password') }}
</router-link>
</div>
</template>
<div

View file

@ -63,6 +63,7 @@
max-width: 90%;
max-height: 90%;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
image-orientation: from-image; // NOTE: only FF supports this
}
.modal-view-button-arrow {

View file

@ -1,14 +1,12 @@
import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue'
import MobilePostStatusModal from '../mobile_post_status_modal/mobile_post_status_modal.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
const MobileNav = {
components: {
SideDrawer,
Notifications,
MobilePostStatusModal
Notifications
},
data: () => ({
notificationsCloseGesture: undefined,

View file

@ -70,7 +70,6 @@
ref="sideDrawer"
:logout="logout"
/>
<MobilePostStatusModal />
</div>
</template>

View file

@ -34,14 +34,19 @@
@import '../../_variables.scss';
.post-form-modal-view {
max-height: 100%;
display: block;
align-items: flex-start;
}
.post-form-modal-panel {
flex-shrink: 0;
margin: 25% 0 4em 0;
margin-top: 25%;
margin-bottom: 2em;
width: 100%;
max-width: 700px;
@media (orientation: landscape) {
margin-top: 8%;
}
}
.new-status-button {

View file

@ -0,0 +1,62 @@
import { mapState } from 'vuex'
import passwordResetApi from '../../services/new_api/password_reset.js'
const passwordReset = {
data: () => ({
user: {
email: ''
},
isPending: false,
success: false,
throttled: false,
error: null
}),
computed: {
...mapState({
signedIn: (state) => !!state.users.currentUser,
instance: state => state.instance
}),
mailerEnabled () {
return this.instance.mailerEnabled
}
},
created () {
if (this.signedIn) {
this.$router.push({ name: 'root' })
}
},
methods: {
dismissError () {
this.error = null
},
submit () {
this.isPending = true
const email = this.user.email
const instance = this.instance.server
passwordResetApi({ instance, email }).then(({ status }) => {
this.isPending = false
this.user.email = ''
if (status === 204) {
this.success = true
this.error = null
} else if (status === 404 || status === 400) {
this.error = this.$t('password_reset.not_found')
this.$nextTick(() => {
this.$refs.email.focus()
})
} else if (status === 429) {
this.throttled = true
this.error = this.$t('password_reset.too_many_requests')
}
}).catch(() => {
this.isPending = false
this.user.email = ''
this.error = this.$t('general.generic_error')
})
}
}
}
export default passwordReset

View file

@ -0,0 +1,116 @@
<template>
<div class="settings panel panel-default">
<div class="panel-heading">
{{ $t('password_reset.password_reset') }}
</div>
<div class="panel-body">
<form
class="password-reset-form"
@submit.prevent="submit"
>
<div class="container">
<div v-if="!mailerEnabled">
<p>
{{ $t('password_reset.password_reset_disabled') }}
</p>
</div>
<div v-else-if="success || throttled">
<p v-if="success">
{{ $t('password_reset.check_email') }}
</p>
<div class="form-group text-center">
<router-link :to="{name: 'root'}">
{{ $t('password_reset.return_home') }}
</router-link>
</div>
</div>
<div v-else>
<p>
{{ $t('password_reset.instruction') }}
</p>
<div class="form-group">
<input
ref="email"
v-model="user.email"
:disabled="isPending"
:placeholder="$t('password_reset.placeholder')"
class="form-control"
type="input"
>
</div>
<div class="form-group">
<button
:disabled="isPending"
type="submit"
class="btn btn-default btn-block"
>
{{ $t('general.submit') }}
</button>
</div>
</div>
<p
v-if="error"
class="alert error notice-dismissible"
>
<span>{{ error }}</span>
<a
class="button-icon dismiss"
@click.prevent="dismissError()"
>
<i class="icon-cancel" />
</a>
</p>
</div>
</form>
</div>
</div>
</template>
<script src="./password_reset.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.password-reset-form {
display: flex;
flex-direction: column;
align-items: center;
margin: 0.6em;
.container {
display: flex;
flex: 1 0;
flex-direction: column;
margin-top: 0.6em;
max-width: 18rem;
}
.form-group {
display: flex;
flex-direction: column;
margin-bottom: 1em;
padding: 0.3em 0.0em 0.3em;
line-height: 24px;
}
.error {
text-align: center;
animation-name: shakeError;
animation-duration: 0.4s;
animation-timing-function: ease-in-out;
}
.alert {
padding: 0.5em;
margin: 0.3em 0.0em 1em;
}
.notice-dismissible {
padding-right: 2rem;
}
.icon-cancel {
cursor: pointer;
}
}
</style>

View file

@ -268,6 +268,7 @@ $validations-cRed: #f04124;
textarea {
min-height: 100px;
resize: vertical;
}
.form-group {

View file

@ -75,8 +75,8 @@ const Search = {
const length = this[tabName].length
return length === 0 ? '' : ` (${length})`
},
onResultTabSwitch (_index, dataset) {
this.currenResultTab = dataset.filter
onResultTabSwitch (key) {
this.currenResultTab = key
},
getActiveTab () {
if (this.visibleStatuses.length > 0) {

View file

@ -31,21 +31,18 @@
<tab-switcher
ref="tabSwitcher"
:on-switch="onResultTabSwitch"
:custom-active="currenResultTab"
:active-tab="currenResultTab"
>
<span
data-tab-dummy
data-filter="statuses"
key="statuses"
:label="$t('user_card.statuses') + resultCount('visibleStatuses')"
/>
<span
data-tab-dummy
data-filter="people"
key="people"
:label="$t('search.people') + resultCount('users')"
/>
<span
data-tab-dummy
data-filter="hashtags"
key="hashtags"
:label="$t('search.hashtags') + resultCount('hashtags')"
/>
</tab-switcher>

View file

@ -20,6 +20,11 @@ const SearchBar = {
toggleHidden () {
this.hidden = !this.hidden
this.$emit('toggled', this.hidden)
this.$nextTick(() => {
if (!this.hidden) {
this.$refs.searchInput.focus()
}
})
}
}
}

View file

@ -335,7 +335,7 @@ const Status = {
return
}
}
if (target.className.match(/hashtag/)) {
if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) {
// Extract tag name from link url
const tag = extractTagFromUrl(target.href)
if (tag) {

View file

@ -32,7 +32,7 @@
</template>
<template v-else>
<div
v-if="showPinned && statusoid.pinned"
v-if="showPinned"
class="status-pin"
>
<i class="fa icon-pin faint" />

View file

@ -7,8 +7,10 @@
v-if="animated"
ref="canvas"
/>
<!-- NOTE: key is required to force to re-render img tag when src is changed -->
<img
ref="src"
:key="src"
:src="src"
:referrerpolicy="referrerpolicy"
@load="onLoad"

View file

@ -14,7 +14,7 @@ export default Vue.component('tab-switcher', {
required: false,
type: Function
},
customActive: {
activeTab: {
required: false,
type: String
},
@ -29,6 +29,16 @@ export default Vue.component('tab-switcher', {
active: this.$slots.default.findIndex(_ => _.tag)
}
},
computed: {
activeIndex () {
// In case of controlled component
if (this.activeTab) {
return this.$slots.default.findIndex(slot => this.activeTab === slot.key)
} else {
return this.active
}
}
},
beforeUpdate () {
const currentSlot = this.$slots.default[this.active]
if (!currentSlot.tag) {
@ -36,22 +46,14 @@ export default Vue.component('tab-switcher', {
}
},
methods: {
activateTab (index, dataset) {
activateTab (index) {
return (e) => {
e.preventDefault()
if (typeof this.onSwitch === 'function') {
this.onSwitch.call(null, index, this.$slots.default[index].elm.dataset)
this.onSwitch.call(null, this.$slots.default[index].key)
}
this.active = index
}
},
isActiveTab (index) {
const customActiveIndex = this.$slots.default.findIndex(slot => {
const dataFilter = slot.data && slot.data.attrs && slot.data.attrs['data-filter']
return this.customActive && this.customActive === dataFilter
})
return customActiveIndex > -1 ? customActiveIndex === index : index === this.active
}
},
render (h) {
@ -61,13 +63,13 @@ export default Vue.component('tab-switcher', {
const classesTab = ['tab']
const classesWrapper = ['tab-wrapper']
if (this.isActiveTab(index)) {
if (this.activeIndex === index) {
classesTab.push('active')
classesWrapper.push('active')
}
if (slot.data.attrs.image) {
return (
<div class={ classesWrapper.join(' ')}>
<div class={classesWrapper.join(' ')}>
<button
disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)}
@ -79,7 +81,7 @@ export default Vue.component('tab-switcher', {
)
}
return (
<div class={ classesWrapper.join(' ')}>
<div class={classesWrapper.join(' ')}>
<button
disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)}
@ -91,7 +93,7 @@ export default Vue.component('tab-switcher', {
const contents = this.$slots.default.map((slot, index) => {
if (!slot.tag) return
const active = index === this.active
const active = this.activeIndex === index
if (this.renderOnlyFocused) {
return active
? <div class="active">{slot}</div>

View file

@ -1,7 +1,20 @@
import Status from '../status/status.vue'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import Conversation from '../conversation/conversation.vue'
import { throttle } from 'lodash'
import { throttle, keyBy } from 'lodash'
export const getExcludedStatusIdsByPinning = (statuses, pinnedStatusIds) => {
const ids = []
if (pinnedStatusIds && pinnedStatusIds.length > 0) {
for (let status of statuses) {
if (!pinnedStatusIds.includes(status.id)) {
break
}
ids.push(status.id)
}
}
return ids
}
const Timeline = {
props: [
@ -11,7 +24,8 @@ const Timeline = {
'userId',
'tag',
'embedded',
'count'
'count',
'pinnedStatusIds'
],
data () {
return {
@ -39,6 +53,15 @@ const Timeline = {
body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : []),
footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : [])
}
},
// id map of statuses which need to be hidden in the main list due to pinning logic
excludedStatusIdsObject () {
const ids = getExcludedStatusIdsByPinning(this.timeline.visibleStatuses, this.pinnedStatusIds)
// Convert id array to object
return keyBy(ids)
},
pinnedStatusIdsObject () {
return keyBy(this.pinnedStatusIds)
}
},
components: {

View file

@ -28,13 +28,25 @@
</div>
<div :class="classes.body">
<div class="timeline">
<conversation
v-for="status in timeline.visibleStatuses"
:key="status.id"
class="status-fadein"
:statusoid="status"
:collapsable="true"
/>
<template v-for="statusId in pinnedStatusIds">
<conversation
v-if="timeline.statusesObject[statusId]"
:key="statusId + '-pinned'"
class="status-fadein"
:statusoid="timeline.statusesObject[statusId]"
:collapsable="true"
:pinned-status-ids-object="pinnedStatusIdsObject"
/>
</template>
<template v-for="status in timeline.visibleStatuses">
<conversation
v-if="!excludedStatusIdsObject[status.id]"
:key="status.id"
class="status-fadein"
:statusoid="status"
:collapsable="true"
/>
</template>
</div>
</div>
<div :class="classes.footer">

View file

@ -16,7 +16,7 @@ const UserAvatar = {
},
computed: {
imgSrc () {
return this.showPlaceholder ? '/images/avi.png' : this.src
return this.showPlaceholder ? '/images/avi.png' : this.user.profile_image_url_original
}
},
methods: {

View file

@ -3,7 +3,7 @@
class="avatar"
:alt="user.screen_name"
:title="user.screen_name"
:src="user.profile_image_url_original"
:src="imgSrc"
:class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
:image-load-error="imageLoadError"
/>

View file

@ -7,7 +7,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', 'rounded', 'bordered' ],
props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar' ],
data () {
return {
followRequestInProgress: false,
@ -162,6 +162,14 @@ export default {
},
reportUser () {
this.$store.dispatch('openUserReportingModal', this.user.id)
},
zoomAvatar () {
const attachment = {
url: this.user.profile_image_url_original,
mimetype: 'image'
}
this.$store.dispatch('setMedia', [attachment])
this.$store.dispatch('setCurrent', attachment)
}
}
}

View file

@ -7,7 +7,23 @@
<div class="panel-heading">
<div class="user-info">
<div class="container">
<router-link :to="userProfileLink(user)">
<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"
@ -351,6 +367,7 @@
.container {
padding: 16px 0 6px;
display: flex;
align-items: flex-start;
max-height: 56px;
.avatar {
@ -372,6 +389,35 @@
}
}
&-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);

View file

@ -22,21 +22,23 @@ const FriendList = withLoadMore({
additionalPropNames: ['userId']
})(List)
const defaultTabKey = 'statuses'
const UserProfile = {
data () {
return {
error: false,
userId: null
userId: null,
tab: defaultTabKey
}
},
created () {
// Make sure that timelines used in this page are empty
this.cleanUp()
const routeParams = this.$route.params
this.load(routeParams.name || routeParams.id)
this.tab = get(this.$route, 'query.tab', defaultTabKey)
},
destroyed () {
this.cleanUp()
this.stopFetching()
},
computed: {
timeline () {
@ -67,17 +69,36 @@ const UserProfile = {
},
methods: {
load (userNameOrId) {
const startFetchingTimeline = (timeline, userId) => {
// Clear timeline only if load another user's profile
if (userId !== this.$store.state.statuses.timelines[timeline].userId) {
this.$store.commit('clearTimeline', { timeline })
}
this.$store.dispatch('startFetchingTimeline', { timeline, userId })
}
const loadById = (userId) => {
this.userId = userId
startFetchingTimeline('user', userId)
startFetchingTimeline('media', userId)
if (this.isUs) {
startFetchingTimeline('favorites', userId)
}
// Fetch all pinned statuses immediately
this.$store.dispatch('fetchPinnedStatuses', userId)
}
// Reset view
this.userId = null
this.error = false
// Check if user data is already loaded in store
const user = this.$store.getters.findUser(userNameOrId)
if (user) {
this.userId = user.id
this.fetchTimelines()
loadById(user.id)
} else {
this.$store.dispatch('fetchUser', userNameOrId)
.then(({ id }) => {
this.userId = id
this.fetchTimelines()
})
.then(({ id }) => loadById(id))
.catch((reason) => {
const errorMessage = get(reason, 'error.error')
if (errorMessage === 'No user with such user_id') { // Known error
@ -90,40 +111,33 @@ const UserProfile = {
})
}
},
fetchTimelines () {
const userId = this.userId
this.$store.dispatch('startFetchingTimeline', { timeline: 'user', userId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'media', userId })
if (this.isUs) {
this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId })
}
// Fetch all pinned statuses immediately
this.$store.dispatch('fetchPinnedStatuses', userId)
},
cleanUp () {
stopFetching () {
this.$store.dispatch('stopFetching', 'user')
this.$store.dispatch('stopFetching', 'favorites')
this.$store.dispatch('stopFetching', 'media')
this.$store.commit('clearTimeline', { timeline: 'user' })
this.$store.commit('clearTimeline', { timeline: 'favorites' })
this.$store.commit('clearTimeline', { timeline: 'media' })
},
switchUser (userNameOrId) {
this.stopFetching()
this.load(userNameOrId)
},
onTabSwitch (tab) {
this.tab = tab
this.$router.replace({ query: { tab } })
}
},
watch: {
'$route.params.id': function (newVal) {
if (newVal) {
this.cleanUp()
this.load(newVal)
this.switchUser(newVal)
}
},
'$route.params.name': function (newVal) {
if (newVal) {
this.cleanUp()
this.load(newVal)
this.switchUser(newVal)
}
},
$route () {
this.$refs.tabSwitcher.activateTab(0)()
'$route.query': function (newVal) {
this.tab = newVal.tab || defaultTabKey
}
},
components: {

View file

@ -8,36 +8,28 @@
:user="user"
:switcher="true"
:selected="timeline.viewing"
:allow-zooming-avatar="true"
rounded="top"
/>
<tab-switcher
ref="tabSwitcher"
:active-tab="tab"
:render-only-focused="true"
:on-switch="onTabSwitch"
>
<div :label="$t('user_card.statuses')">
<div class="timeline">
<template v-for="statusId in user.pinnedStatuseIds">
<Conversation
v-if="timeline.statusesObject[statusId]"
:key="statusId"
class="status-fadein"
:statusoid="timeline.statusesObject[statusId]"
:collapsable="true"
:show-pinned="true"
/>
</template>
</div>
<Timeline
:count="user.statuses_count"
:embedded="true"
:title="$t('user_profile.timeline_title')"
:timeline="timeline"
:timeline-name="'user'"
:user-id="userId"
/>
</div>
<Timeline
key="statuses"
:label="$t('user_card.statuses')"
:count="user.statuses_count"
:embedded="true"
:title="$t('user_profile.timeline_title')"
:timeline="timeline"
timeline-name="user"
:user-id="userId"
:pinned-status-ids="user.pinnedStatusIds"
/>
<div
v-if="followsTabVisible"
key="followees"
:label="$t('user_card.followees')"
:disabled="!user.friends_count"
>
@ -52,6 +44,7 @@
</div>
<div
v-if="followersTabVisible"
key="followers"
:label="$t('user_card.followers')"
:disabled="!user.followers_count"
>
@ -68,6 +61,7 @@
</FollowerList>
</div>
<Timeline
key="media"
:label="$t('user_card.media')"
:disabled="!media.visibleStatuses.length"
:embedded="true"
@ -78,6 +72,7 @@
/>
<Timeline
v-if="isUs"
key="favorites"
:label="$t('user_card.favorites')"
:disabled="!favorites.visibleStatuses.length"
:embedded="true"

View file

@ -21,11 +21,12 @@ const WhoToFollow = {
name: i.display_name,
screen_name: i.acct,
profile_image_url: i.avatar || '/images/avi.png',
profile_image_url_original: i.avatar || '/images/avi.png'
profile_image_url_original: i.avatar || '/images/avi.png',
statusnet_profile_url: i.url
}
this.users.push(user)
this.$store.state.api.backendInteractor.externalProfile(user.screen_name)
this.$store.state.api.backendInteractor.fetchUser({ id: user.screen_name })
.then((externalUser) => {
if (!externalUser.error) {
this.$store.commit('addNewUsers', [externalUser])

View file

@ -13,7 +13,7 @@ function showWhoToFollow (panel, reply) {
toFollow.img = img
toFollow.name = name
panel.$store.state.api.backendInteractor.externalProfile(name)
panel.$store.state.api.backendInteractor.fetchUser({ id: name })
.then((externalUser) => {
if (!externalUser.error) {
panel.$store.commit('addNewUsers', [externalUser])