Merge pull request #793 from StCyr/bugfix/756/singlepost-improvement

WIP: single post timeline improvement
pull/1073/head
Maxence Lange 2020-10-13 23:59:24 -01:00 zatwierdzone przez GitHub
commit e156bcdada
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
18 zmienionych plików z 294 dodań i 144 usunięć

Wyświetl plik

@ -50,7 +50,7 @@ return [
['name' => 'Navigation#resizedGetPublic', 'url' => '/document/public/resized', 'verb' => 'GET'],
['name' => 'ActivityPub#actor', 'url' => '/users/{username}', 'verb' => 'GET'],
['name' => 'ActivityPub#actorAlias', 'url' => '/@{username}/', 'verb' => 'GET'],
['name' => 'ActivityPub#actorAlias', 'url' => '/@{username}', 'verb' => 'GET'],
['name' => 'ActivityPub#inbox', 'url' => '/@{username}/inbox', 'verb' => 'POST'],
['name' => 'ActivityPub#getInbox', 'url' => '/@{username}/inbox', 'verb' => 'GET'],
['name' => 'ActivityPub#sharedInbox', 'url' => '/inbox', 'verb' => 'POST'],

Wyświetl plik

@ -76,8 +76,15 @@ class SocialLimitsQueryBuilder extends SocialCrossQueryBuilder {
* Limit the request to the Id (string)
*
* @param string $id
* @param bool $prim
*/
public function limitToInReplyTo(string $id) {
public function limitToInReplyTo(string $id, bool $prim = false) {
if ($prim) {
$this->limitToDBField('in_reply_to_prim', $this->prim($id), false);
return;
}
$this->limitToDBField('in_reply_to', $id, false);
}

Wyświetl plik

@ -274,11 +274,7 @@ class StreamRequest extends StreamRequestBuilder {
$qb->limitToInReplyTo($id);
$qb->limitPaginate($since, $limit);
$expr = $qb->expr();
$qb->linkToCacheActors('ca', 's.attributed_to_prim');
$qb->andWhere($expr->eq('s.attributed_to', 'ca.id_prim'));
if ($asViewer) {
$qb->limitToViewer('sd', 'f', true);
$qb->leftJoinStreamAction();

Wyświetl plik

@ -39,7 +39,7 @@
<span>In reply to</span>
<actor-avatar :actor="replyTo.actor_info" :size="16" />
<strong>{{ replyTo.actor_info.account }}</strong>
<a class="icon-close" @click="replyTo=null" />
<a class="icon-close" @click="closeReply()" />
</p>
<div class="reply-to-preview">
{{ replyTo.content }}
@ -610,6 +610,7 @@ export default {
mounted() {
this.$root.$on('composer-reply', (data) => {
this.replyTo = data
this.type = 'direct'
})
},
methods: {
@ -737,7 +738,7 @@ export default {
let contentHtml = element.innerHTML
// Extract mentions from content and create an array ot of them
// Extract mentions from content and create an array out of them
let to = []
const mentionRegex = /<span class="mention"[^>]+><a[^>]+><img[^>]+>@([\w-_.]+@[\w-.]+)/g
let match = null
@ -748,6 +749,11 @@ export default {
}
} while (match)
// Add author of original post in case of reply
if (this.replyTo !== null) {
to.push(this.replyTo.actor_info.account)
}
// Extract hashtags from content and create an array ot of them
const hashtagRegex = />#([^<]+)</g
let hashtags = []
@ -824,6 +830,11 @@ export default {
})
},
closeReply() {
this.replyTo = null
// View may want to hide the composer
this.$store.commit('setComposerDisplayStatus', false)
},
remoteSearchAccounts(text) {
return axios.get(generateUrl('apps/social/api/v1/global/accounts/search?search=' + text))
},

Wyświetl plik

@ -22,7 +22,10 @@
<template>
<div class="emptycontent">
<img :src="imageUrl" class="icon-illustration" alt="">
<img v-if="item.image"
:src="imageUrl"
class="icon-illustration"
alt="">
<h2>{{ item.title }}</h2>
<p>{{ item.description }}</p>
</div>

Wyświetl plik

@ -22,7 +22,7 @@
<template>
<!-- Show button only if user is authenticated and she is not the same as the account viewed -->
<div v-if="!serverData.public && actorInfo && actorInfo.viewerLink!='viewer'">
<div v-if="!serverData.public && accountInfo && accountInfo.viewerLink!='viewer'">
<button v-if="isCurrentUserFollowing" :class="{'icon-loading-small': followLoading}"
@click="unfollow()"
@mouseover="followingText=t('social', 'Unfollow')" @mouseleave="followingText=t('social', 'Following')">
@ -36,17 +36,23 @@
</template>
<script>
import accountMixins from '../mixins/accountMixins'
import currentUser from '../mixins/currentUserMixin'
export default {
name: 'FollowButton',
mixins: [
accountMixins,
currentUser
],
props: {
account: {
type: String,
default: ''
},
uid: {
type: String,
default: ''
}
},
data: function() {
@ -55,9 +61,6 @@ export default {
}
},
computed: {
actorInfo() {
return this.$store.getters.getAccount(this.account)
},
followLoading() {
return false
},

Wyświetl plik

@ -1,7 +1,7 @@
<template>
<masonry>
<div v-for="(item, index) in attachments" :key="index">
<img :src="generateUrl('/apps/social/document/get/resized?id=' + item.id)" @click="showModal(index)">
<img :src="imageUrl(item)" @click="showModal(index)">
</div>
<modal v-show="modal" :has-previous="current > 0" :has-next="current < (attachments.length - 1)"
size="full" @close="closeModal" @previous="showPrevious"
@ -15,6 +15,7 @@
<script>
import serverData from '../mixins/serverData'
import Modal from '@nextcloud/vue/dist/Components/Modal'
import { generateUrl } from '@nextcloud/router'
@ -23,7 +24,9 @@ export default {
components: {
Modal
},
mixins: [],
mixins: [
serverData
],
props: {
attachments: {
type: Array,
@ -37,7 +40,24 @@ export default {
}
},
methods: {
displayResizedImage() {
/**
* @function imageUrl
* @description Returns the URL where to get a resized version of the attachement
* @param {object} item - The attachment
* @returns {string} The URL
*/
imageUrl(item) {
if (this.serverData.public) {
return generateUrl('/apps/social/document/public/resized?id=' + item.id)
} else {
return generateUrl('/apps/social/document/get/resized?id=' + item.id)
}
},
/**
* @function displayImage
* @description Displays the currently selected attachment's image
*/
displayImage() {
var canvas = this.$refs.modalCanvas
var ctx = canvas.getContext('2d')
var img = new Image()
@ -60,7 +80,7 @@ export default {
},
showModal(idx) {
this.current = idx
this.displayResizedImage()
this.displayImage()
this.modal = true
},
closeModal() {
@ -68,11 +88,11 @@ export default {
},
showPrevious() {
this.current--
this.displayResizedImage()
this.displayImage()
},
showNext() {
this.current++
this.displayResizedImage()
this.displayImage()
}
}
}

Wyświetl plik

@ -21,7 +21,7 @@
-->
<template>
<div v-if="account && accountInfo" class="user-profile">
<div v-if="profileAccount && accountInfo" class="user-profile">
<div>
<avatar v-if="accountInfo.local" :user="localUid" :disable-tooltip="true"
:size="128" />
@ -34,7 +34,7 @@
{{ accountInfo.website.value }}
</a>
</p>
<follow-button :account="accountInfo.account" />
<follow-button :account="accountInfo.account" :uid="uid" />
<button v-if="serverData.public" class="primary" @click="followRemote">
{{ t('social', 'Follow') }}
</button>
@ -97,6 +97,7 @@
</style>
<script>
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import accountMixins from '../mixins/accountMixins'
import serverData from '../mixins/serverData'
import currentUser from '../mixins/currentUserMixin'
import follow from '../mixins/follow'
@ -110,8 +111,9 @@ export default {
Avatar
},
mixins: [
serverData,
accountMixins,
currentUser,
serverData,
follow
],
props: {
@ -130,9 +132,6 @@ export default {
// Returns only the local part of a username
return (this.uid.indexOf('@') === -1) ? this.uid : this.uid.substr(0, this.uid.indexOf('@'))
},
account() {
return (this.uid.indexOf('@') === -1) ? this.uid + '@' + this.hostname : this.uid
},
displayName() {
if (typeof this.accountInfo.name !== 'undefined' && this.accountInfo.name !== '') {
return this.accountInfo.name
@ -140,13 +139,11 @@ export default {
if (typeof this.accountInfo.preferredUsername !== 'undefined' && this.accountInfo.preferredUsername !== '') {
return this.accountInfo.preferredUsername
}
return this.account
},
accountInfo: function() {
return this.$store.getters.getAccount(this.account)
return this.profileAccount
},
getCount() {
return (field) => this.accountInfo.details.count ? this.accountInfo.details.count[field] : ''
let account = this.accountInfo
return (field) => account.details.count ? account.details.count[field] : ''
},
avatarUrl() {
return generateUrl('/apps/social/api/v1/global/actor/avatar?id=' + this.accountInfo.id)

Wyświetl plik

@ -1,5 +1,5 @@
<template>
<div class="timeline-entry" @click="getSinglePostTimeline">
<div class="timeline-entry">
<div v-if="item.type === 'SocialAppNotification'">
{{ actionSummary }}
</div>
@ -30,7 +30,6 @@
</template>
<script>
import Logger from '../logger'
import TimelinePost from './TimelinePost.vue'
export default {
@ -95,35 +94,6 @@ export default {
}
},
methods: {
getSinglePostTimeline(e) {
// Do not call the single-post view when clicking on a link, a post attachment miniature or the post's author
if (e.target.tagName === 'A' || e.target.tagName === 'IMG' || e.target.className.startsWith('post-author')) {
Logger.debug('will not call single-post', { event: e })
return
}
// Display internal or external post
if (!this.item.local) {
if (this.item.type === 'Note') {
window.open(this.item.id)
} else if (this.item.type === 'Announce') {
window.open(this.item.object)
} else {
Logger.warn("Don't know what to do with posts of type " + this.item.type, { post: this.item })
}
} else {
this.$router.push({ name: 'single-post',
params: {
account: this.item.actor_info.preferredUsername,
id: this.item.id,
localId: this.item.id.split('/')[this.item.id.split('/').length - 1],
type: 'single-post'
}
})
}
},
userDisplayName(actorInfo) {
return actorInfo.name !== '' ? actorInfo.name : actorInfo.preferredUsername
}

Wyświetl plik

@ -60,6 +60,7 @@ import InfiniteLoading from 'vue-infinite-loading'
import TimelineEntry from './TimelineEntry.vue'
import CurrentUserMixin from './../mixins/currentUserMixin'
import EmptyContent from './EmptyContent.vue'
import Logger from '../logger.js'
export default {
name: 'Timeline',
@ -113,6 +114,9 @@ export default {
tags: {
image: 'img/undraw/profile.svg',
title: t('social', 'No posts found for this tag')
},
'single-post': {
title: t('social', 'No replies found')
}
}
}
@ -122,6 +126,7 @@ export default {
if (typeof this.emptyContent[this.$route.params.type] !== 'undefined') {
return this.emptyContent[this.$route.params.type]
}
if (typeof this.emptyContent[this.$route.name] !== 'undefined') {
let content = this.emptyContent[this.$route.name]
// Change text on profile page when accessed by another user or a public (non-authenticated) user
@ -130,7 +135,9 @@ export default {
}
return this.$route.name === 'timeline' ? this.emptyContent['default'] : content
}
// Fallback
Logger.log('Did not find any empty content for this route', { 'routeType': this.$route.params.type, 'routeName': this.$route.name })
return this.emptyContent.default
},
timeline: function() {

Wyświetl plik

@ -26,9 +26,9 @@
</span>
</a>
</div>
<div :data-timestamp="timestamp" class="post-timestamp live-relative-timestamp">
<a :data-timestamp="timestamp" class="post-timestamp live-relative-timestamp" @click="getSinglePostTimeline">
{{ relativeTimestamp }}
</div>
</a>
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="item.content" class="post-message">
@ -64,6 +64,7 @@ import 'linkifyjs/string'
import popoverMenu from './../mixins/popoverMenu'
import currentUser from './../mixins/currentUserMixin'
import PostAttachment from './PostAttachment.vue'
import Logger from '../logger'
import MessageContent from './MessageContent'
import moment from '@nextcloud/moment'
import { generateUrl } from '@nextcloud/router'
@ -140,10 +141,36 @@ export default {
}
},
methods: {
/**
* @function getSinglePostTimeline
* @description Opens the timeline of the post clicked
*/
getSinglePostTimeline(e) {
// Display internal or external post
if (!this.item.local) {
if (this.item.type === 'Note') {
window.open(this.item.id)
} else if (this.item.type === 'Announce') {
window.open(this.item.object)
} else {
Logger.warn("Don't know what to do with posts of type " + this.item.type, { post: this.item })
}
} else {
this.$router.push({ name: 'single-post',
params: {
account: this.item.actor_info.preferredUsername,
id: this.item.id,
localId: this.item.id.split('/')[this.item.id.split('/').length - 1],
type: 'single-post'
}
})
}
},
userDisplayName(actorInfo) {
return actorInfo.name !== '' ? actorInfo.name : actorInfo.preferredUsername
},
reply() {
this.$store.commit('setComposerDisplayStatus', true)
this.$root.$emit('composer-reply', this.item)
},
boost() {

Wyświetl plik

@ -0,0 +1,48 @@
/*
* @copyright Copyright (c) 2019 Cyrille Bollu <cyrpub@bollu.be>
*
* @author Cyrille Bollu <cyrpub@bollu.be>
*
* @license GNU AGPL version 3 or any later version
*
* @file provides global account related methods
*
* @mixin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import serverData from './serverData'
export default {
mixins: [
serverData
],
computed: {
/** @function Returns the complete account name */
profileAccount() {
return (this.uid.indexOf('@') === -1) ? this.uid + '@' + this.hostname : this.uid
},
/** @functions Returns detailed information about an account (account must be loaded in the store first) */
accountInfo() {
return this.$store.getters.getAccount(this.profileAccount)
},
/** @function Somewhat duplicate with accountInfo(), but needed (for some reason) to avoid glitches
* where components would first show "user not found" before display an account's account info */
accountLoaded() {
return this.$store.getters.accountLoaded(this.profileAccount)
}
}
}

Wyświetl plik

@ -1,6 +1,10 @@
/*
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @file Provides global methods for using the serverData structure.
*
* @mixin
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
@ -22,7 +26,17 @@
export default {
computed: {
serverData: function() {
/** @description Returns the serverData object
* @property {String} account - The account that the user wants to follow (Only in 'OStatus.vue')
* @property cliUrl
* @property cloudAddress
* @property firstrun
* @property isAdmin
* @property {String} local - The local part of the account that the user wants to follow
* @property {boolean} public - False when the page is accessed by an authenticated user. True otherwise
* @property setup
*/
serverData() {
return this.$store.getters.getServerData
},
hostname() {

Wyświetl plik

@ -46,13 +46,10 @@ const mutations = {
let users = []
for (var index in data) {
const actor = data[index].actor_info
if (typeof actor !== 'undefined' && account !== actor.account) {
users.push(actor.id)
addAccount(state, {
actorId: actor.id,
data: actor
})
}
addAccount(state, {
actorId: actor.id,
data: actor
})
}
Vue.set(state.accounts[_getActorIdForAccount(account)], 'followersList', users)
},
@ -79,6 +76,9 @@ const mutations = {
}
const getters = {
getAllAccounts(state) {
return (account) => { return state.accounts }
},
getAccount(state, getters) {
return (account) => {
return state.accounts[_getActorIdForAccount(account)]

Wyświetl plik

@ -1,6 +1,8 @@
/*
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @file Timeline related store
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Jonas Sulzer <jonas@violoncello.ch>
*
@ -26,12 +28,31 @@ import axios from '@nextcloud/axios'
import Vue from 'vue'
import { generateUrl } from '@nextcloud/router'
/**
* @property {object} timeline - The posts' collection
* @property {int} since - Time (EPOCH) of the most recent post
* @property {string} type - Timeline's type: 'home', 'single-post',...
* @property {object} params - Timeline's parameters
* @property {string} account -
*/
const state = {
timeline: {},
since: Math.floor(Date.now() / 1000) + 1,
type: 'home',
/**
* @namespace params
* @property {string} account ???
* @property {string} id
* @property {string} localId
* @property {string} type ???
*/
params: {},
account: ''
account: '',
/* Tells whether the composer should be displayed or not.
* It's up to the view to honor this status or not.
* @member {boolean}
*/
composerDisplayStatus: false
}
const mutations = {
addToTimeline(state, data) {
@ -53,6 +74,9 @@ const mutations = {
setTimelineParams(state, params) {
state.params = params
},
setComposerDisplayStatus(state, status) {
state.composerDisplayStatus = status
},
setAccount(state, account) {
state.account = account
},
@ -90,6 +114,9 @@ const mutations = {
}
}
const getters = {
getComposerDisplayStatus(state) {
return state.composerDisplayStatus
},
getTimeline(state) {
return Object.values(state.timeline).sort(function(a, b) {
return b.publishedTime - a.publishedTime
@ -120,12 +147,11 @@ const actions = {
post(context, post) {
return new Promise((resolve, reject) => {
axios.post(generateUrl('apps/social/api/v1/post'), { data: post }).then((response) => {
// eslint-disable-next-line no-console
console.log('Post created with token ' + response.data.result.token)
Logger.info('Post created with token ' + response.data.result.token)
resolve(response)
}).catch((error) => {
OC.Notification.showTemporary('Failed to create a post')
console.error('Failed to create a post', error.response)
Logger.error('Failed to create a post', { 'error': error.response })
reject(error)
})
})
@ -133,11 +159,10 @@ const actions = {
postDelete(context, post) {
return axios.delete(generateUrl(`apps/social/api/v1/post?id=${post.id}`)).then((response) => {
context.commit('removePost', post)
// eslint-disable-next-line no-console
console.log('Post deleted with token ' + response.data.result.token)
Logger.info('Post deleted with token ' + response.data.result.token)
}).catch((error) => {
OC.Notification.showTemporary('Failed to delete the post')
console.error('Failed to delete the post', error)
Logger.error('Failed to delete the post', { 'error': error })
})
},
postLike(context, { post, parentAnnounce }) {
@ -147,7 +172,7 @@ const actions = {
resolve(response)
}).catch((error) => {
OC.Notification.showTemporary('Failed to like post')
console.error('Failed to like post', error.response)
Logger.error('Failed to like post', { 'error': error.response })
reject(error)
})
})
@ -161,19 +186,18 @@ const actions = {
}
}).catch((error) => {
OC.Notification.showTemporary('Failed to unlike post')
console.error('Failed to unlike post', error)
Logger.error('Failed to unlike post', { 'error': error })
})
},
postBoost(context, { post, parentAnnounce }) {
return new Promise((resolve, reject) => {
axios.post(generateUrl(`apps/social/api/v1/post/boost?postId=${post.id}`)).then((response) => {
context.commit('boostPost', { post, parentAnnounce })
// eslint-disable-next-line no-console
console.log('Post boosted with token ' + response.data.result.token)
Logger.info('Post boosted with token ' + response.data.result.token)
resolve(response)
}).catch((error) => {
OC.Notification.showTemporary('Failed to create a boost post')
console.error('Failed to create a boost post', error.response)
Logger.error('Failed to create a boost post', { 'error': error.response })
reject(error)
})
})
@ -181,11 +205,10 @@ const actions = {
postUnBoost(context, { post, parentAnnounce }) {
return axios.delete(generateUrl(`apps/social/api/v1/post/boost?postId=${post.id}`)).then((response) => {
context.commit('unboostPost', { post, parentAnnounce })
// eslint-disable-next-line no-console
console.log('Boost deleted with token ' + response.data.result.token)
Logger.info('Boost deleted with token ' + response.data.result.token)
}).catch((error) => {
OC.Notification.showTemporary('Failed to delete the boost')
console.error('Failed to delete the boost', error)
Logger.error('Failed to delete the boost', { 'error': error })
})
},
refreshTimeline(context) {
@ -216,22 +239,8 @@ const actions = {
throw response.message
}
let result = []
// Also load replies when displaying a single post timeline
if (state.type === 'single-post') {
result.push(response.data)
// axios.get(generateUrl(``)).then((response) => {
// if (response.status !== -1) {
// result.concat(response.data.result)
// }
// }
} else {
result = response.data.result
}
// Add results to timeline
context.commit('addToTimeline', result)
context.commit('addToTimeline', response.data.result)
return response.data
})

Wyświetl plik

@ -1,5 +1,5 @@
<template>
<div v-if="accountInfo">
<div v-if="account">
<div v-if="!serverData.local">
<h2>{{ t('social', 'Follow on Nextcloud Social') }}</h2>
<p>{{ t('social', 'Hello') }} <avatar :user="currentUser.uid" :size="16" />{{ currentUser.displayName }}</p>
@ -23,6 +23,7 @@
</button>
</div>
</div>
<!-- Some unauthenticated user wants to follow a local account -->
<div v-if="serverData.local">
<p>{{ t('social', 'You are going to follow:') }}</p>
<avatar :user="serverData.local" :disable-tooltip="true" :size="128" />
@ -34,7 +35,7 @@
<p>{{ t('social', 'This step is needed as the user is probably not registered on the same server as you are. We will redirect you to your homeserver to follow this account.') }}</p>
</div>
</div>
<div v-else :class="{ 'icon-loading-dark': !accountInfo }" />
<div v-else :class="{ 'icon-loading-dark': !account }" />
</template>
<style scoped>
@ -62,7 +63,8 @@
<script>
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import axios from '@nextcloud/axios'
import currentuserMixin from './../mixins/currentUserMixin'
import accountMixins from '../mixins/accountMixins'
import currentuserMixin from '../mixins/currentUserMixin'
import { loadState } from '@nextcloud/initial-state'
import { generateUrl } from '@nextcloud/router'
@ -71,37 +73,36 @@ export default {
components: {
Avatar
},
mixins: [currentuserMixin],
mixins: [
accountMixins,
currentuserMixin
],
data() {
return {
remote: ''
remote: '',
account: {}
}
},
computed: {
isFollowing() {
return this.$store.getters.isFollowingUser(this.account)
},
account() {
return this.serverData.account
return this.$store.getters.isFollowingUser(this.account.id)
},
avatarUrl() {
return generateUrl('/apps/social/api/v1/global/actor/avatar?id=' + this.accountInfo.id)
},
accountInfo: function() {
return this.$store.getters.getAccount(this.serverData.account)
return generateUrl('/apps/social/api/v1/global/actor/avatar?id=' + this.account.id)
},
currentUser() {
return window.oc_current_user
},
displayName() {
if (typeof this.accountInfo.name !== 'undefined' && this.accountInfo.name !== '') {
return this.accountInfo.name
if (typeof this.account.id === 'undefined') {
return (this.serverData.account ? this.serverData.account : this.serverData.local)
}
return this.account
return (this.account.name ? this.account.name : this.account.preferredUsername)
}
},
beforeMount: function() {
// importing server data into the store
// importing server data into the store and fetching viewed account's information
try {
const serverData = loadState('social', 'serverData')
if (serverData.currentUser) {
@ -109,10 +110,14 @@ export default {
}
this.$store.commit('setServerData', serverData)
if (this.serverData.account && !this.serverData.local) {
this.$store.dispatch('fetchAccountInfo', this.serverData.account)
this.$store.dispatch('fetchAccountInfo', this.serverData.account).then((result) => {
this.account = result
})
}
if (this.serverData.local) {
this.$store.dispatch('fetchPublicAccountInfo', this.serverData.local)
this.$store.dispatch('fetchPublicAccountInfo', this.serverData.local).then((result) => {
this.account = result
})
}
} catch {
/* empty */
@ -120,7 +125,7 @@ export default {
},
methods: {
follow() {
this.$store.dispatch('followAccount', { currentAccount: this.cloudId, accountToFollow: this.account }).then(() => {
this.$store.dispatch('followAccount', { currentAccount: this.cloudId, accountToFollow: this.account.account }).then(() => {
})
},

Wyświetl plik

@ -40,6 +40,7 @@
<script>
import ProfileInfo from './../components/ProfileInfo.vue'
import EmptyContent from '../components/EmptyContent.vue'
import accountMixins from '../mixins/accountMixins'
import serverData from '../mixins/serverData'
export default {
@ -49,6 +50,7 @@ export default {
ProfileInfo
},
mixins: [
accountMixins,
serverData
],
data() {
@ -58,18 +60,9 @@ export default {
}
},
computed: {
profileAccount() {
return (this.uid.indexOf('@') === -1) ? this.uid + '@' + this.hostname : this.uid
},
timeline: function() {
return this.$store.getters.getTimeline
},
accountInfo: function() {
return this.$store.getters.getAccount(this.profileAccount)
},
accountLoaded() {
return this.$store.getters.accountLoaded(this.profileAccount)
},
emptyContentData() {
return {
image: 'img/undraw/profile.svg',
@ -78,12 +71,12 @@ export default {
}
}
},
// Start fetching account information before mounting the component
beforeMount() {
let fetchMethod = ''
this.uid = this.$route.params.account || this.serverData.account
// Are we authenticated?
let fetchMethod = ''
if (this.serverData.public) {
fetchMethod = 'fetchPublicAccountInfo'
} else {
@ -95,8 +88,6 @@ export default {
this.$store.dispatch(fetchMethod, this.profileAccount).then((response) => {
this.uid = response.account
})
},
methods: {
}
}
</script>

Wyświetl plik

@ -1,7 +1,9 @@
<template>
<div class="social__wrapper">
<timeline-entry :item="mainPost" />
<timeline-list :type="$route.params.type" />
<profile-info v-if="accountLoaded && accountInfo" :uid="uid" />
<composer v-show="composerDisplayStatus" />
<timeline-entry class="main-post" :item="mainPost" />
<timeline-list v-if="timeline" :type="$route.params.type" />
</div>
</template>
@ -19,44 +21,80 @@
</style>
<script>
import Logger from '../logger'
import TimelineEntry from './../components/TimelineEntry.vue'
import TimelineList from './../components/TimelineList.vue'
import Composer from '../components/Composer.vue'
import ProfileInfo from '../components/ProfileInfo.vue'
import TimelineEntry from '../components/TimelineEntry.vue'
import TimelineList from '../components/TimelineList.vue'
import currentUserMixin from '../mixins/currentUserMixin'
import accountMixins from '../mixins/accountMixins'
import serverData from '../mixins/serverData'
import { loadState } from '@nextcloud/initial-state'
export default {
name: 'TimelineSinglePost',
components: {
Composer,
ProfileInfo,
TimelineEntry,
TimelineList
},
mixins: [
accountMixins,
currentUserMixin,
serverData
],
data() {
return {
mainPost: {}
mainPost: {},
uid: this.$route.params.account
}
},
computed: {
/**
* @description Tells whether Composer shall be displayed or not
* @returns {boolean}
*/
composerDisplayStatus() {
return this.$store.getters.getComposerDisplayStatus
},
/**
* @description Extracts the viewed account name from the URL
* @returns {String}
*/
account() {
return window.location.href.split('/')[window.location.href.split('/').length - 2].substr(1)
},
/**
* @description Returns the timeline currently loaded in the store
* @returns {Object}
*/
timeline: function() {
return this.$store.getters.getTimeline
}
},
beforeMount: function() {
// Get data of post clicked on
if (typeof this.$route.params.id === 'undefined') {
Logger.debug('displaying the single post timeline for a non logged-in user')
this.mainPost = loadState('social', 'item')
} else {
this.mainPost = this.$store.getters.getPostFromTimeline(this.$route.params.id)
}
// Set params for the TimelineList component
// Fetch information of the related account
this.$store.dispatch(this.serverData.public ? 'fetchPublicAccountInfo' : 'fetchAccountInfo', this.account).then((response) => {
// We need to update this.uid because we may have asked info for an account whose domain part was a host-meta,
// and the account returned by the backend always uses a non host-meta'ed domain for its ID
this.uid = response.account
})
// Fetch single post timeline
let params = {
account: window.location.href.split('/')[window.location.href.split('/').length - 2].substr(1),
account: this.account,
id: window.location.href,
localId: window.location.href.split('/')[window.location.href.split('/').length - 1],
type: 'single-post'
}
this.$store.dispatch('changeTimelineType', {
type: 'single-post',
params: params
@ -66,3 +104,7 @@ export default {
}
}
</script>
<style>
</style>