mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-22 21:13:16 +00:00
Add Hashtag component
This commit is contained in:
parent
b447db082f
commit
b12b956b7d
1 changed files with 332 additions and 0 deletions
332
resources/assets/components/Hashtag.vue
Normal file
332
resources/assets/components/Hashtag.vue
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
<template>
|
||||||
|
<div class="hashtag-component">
|
||||||
|
<div class="container-fluid mt-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3 d-md-block">
|
||||||
|
<sidebar :user="profile" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="card border-0 shadow-sm mb-3" style="border-radius: 18px;">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="media align-items-center py-3">
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="h3 text-break mb-0">
|
||||||
|
<span class="text-lighter">#</span>{{ hashtag.name }}
|
||||||
|
</p>
|
||||||
|
<p v-if="hashtag.count && hashtag.count > 100" class="mb-0 text-muted font-weight-bold">
|
||||||
|
{{ formatCount(hashtag.count) }} Posts
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<template v-if="hashtag && hashtag.hasOwnProperty('following') && feed && feed.length">
|
||||||
|
<button
|
||||||
|
v-if="hashtag.following"
|
||||||
|
:disabled="followingLoading"
|
||||||
|
class="btn btn-light hashtag-follow border rounded-pill font-weight-bold py-1 px-4"
|
||||||
|
@click="unfollowHashtag()"
|
||||||
|
>
|
||||||
|
<b-spinner v-if="followingLoading" small />
|
||||||
|
<span v-else>
|
||||||
|
{{ $t('profile.unfollow') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
:disabled="followingLoading"
|
||||||
|
class="btn btn-primary hashtag-follow font-weight-bold rounded-pill py-1 px-4"
|
||||||
|
@click="followHashtag()"
|
||||||
|
>
|
||||||
|
<b-spinner v-if="followingLoading" small />
|
||||||
|
<span v-else>
|
||||||
|
{{ $t('profile.follow') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="isLoaded && feedLoaded">
|
||||||
|
<div class="row mx-0 hashtag-feed">
|
||||||
|
<div class="col-6 col-md-4 col-lg-3 p-1" v-for="(status, index) in feed" :key="'tlob:'+index">
|
||||||
|
<a
|
||||||
|
class="card info-overlay card-md-border-0"
|
||||||
|
:href="statusUrl(status)"
|
||||||
|
@click.prevent="goToPost(status)">
|
||||||
|
<div class="square">
|
||||||
|
<div v-if="status.sensitive" class="square-content">
|
||||||
|
<div class="info-overlay-text-label">
|
||||||
|
<h5 class="text-white m-auto font-weight-bold">
|
||||||
|
<span>
|
||||||
|
<span class="far fa-eye-slash fa-lg p-2 d-flex-inline"></span>
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<blur-hash-canvas
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
:hash="status.media_attachments[0].blurhash"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="square-content">
|
||||||
|
<blur-hash-image
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
:hash="status.media_attachments[0].blurhash"
|
||||||
|
:src="status.media_attachments[0].url"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span v-if="status.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
|
||||||
|
<span v-if="status.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
|
||||||
|
<span v-if="status.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
|
||||||
|
<div class="info-overlay-text">
|
||||||
|
<h5 class="text-white m-auto font-weight-bold">
|
||||||
|
<span>
|
||||||
|
<span class="far fa-comment fa-lg p-2 d-flex-inline"></span>
|
||||||
|
<span class="d-flex-inline">{{formatCount(status.reply_count)}}</span>
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="canLoadMore" class="col-12">
|
||||||
|
<intersect @enter="enterIntersect">
|
||||||
|
<div class="d-flex justify-content-center py-5">
|
||||||
|
<b-spinner />
|
||||||
|
</div>
|
||||||
|
</intersect>
|
||||||
|
|
||||||
|
<!-- <div v-else class="ph-item">
|
||||||
|
<div class="ph-picture big"></div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="feedLoaded && !feed.length" class="row mx-0 hashtag-feed justify-content-center">
|
||||||
|
<div class="col-12 col-md-8 text-center">
|
||||||
|
<img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;max-width:400px">
|
||||||
|
<p class="lead text-muted font-weight-bold">{{ $t('hashtags.emptyFeed') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="row justify-content-center align-items-center pt-5 mt-5">
|
||||||
|
<b-spinner />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<drawer />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
import Drawer from './partials/drawer.vue';
|
||||||
|
import Intersect from 'vue-intersect'
|
||||||
|
import Sidebar from './partials/sidebar.vue';
|
||||||
|
import Rightbar from './partials/rightbar.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
"drawer": Drawer,
|
||||||
|
"intersect": Intersect,
|
||||||
|
"sidebar": Sidebar,
|
||||||
|
"rightbar": Rightbar,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoaded: false,
|
||||||
|
profile: undefined,
|
||||||
|
canLoadMore: false,
|
||||||
|
isIntersecting: false,
|
||||||
|
feedLoaded: false,
|
||||||
|
feed: [],
|
||||||
|
page: 1,
|
||||||
|
hashtag: {
|
||||||
|
name: this.id,
|
||||||
|
count: 0
|
||||||
|
},
|
||||||
|
followingLoading: false,
|
||||||
|
maxId: undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
'$route': 'init'
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
init() {
|
||||||
|
this.profile = window._sharedData.user;
|
||||||
|
axios.get('/api/v1/tags/' + this.id, {
|
||||||
|
params: {
|
||||||
|
'_pe': 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.hashtag = res.data;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
swal('Error', 'Something went wrong, please try again later!', 'error');
|
||||||
|
this.isLoaded = true;
|
||||||
|
this.feedLoaded = true;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.fetchFeed();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchFeed() {
|
||||||
|
axios.get('/api/v1/timelines/tag/' + this.id, {
|
||||||
|
params: {
|
||||||
|
limit: 80,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if(res.data && res.data.length) {
|
||||||
|
this.feed = res.data;
|
||||||
|
this.maxId = res.data[res.data.length - 1].id;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.feedLoaded = true;
|
||||||
|
this.isLoaded = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.canLoadMore = res;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.feedLoaded = true;
|
||||||
|
this.isLoaded = true;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
statusUrl(status) {
|
||||||
|
return '/i/web/post/' + status.id;
|
||||||
|
},
|
||||||
|
|
||||||
|
formatCount(val) {
|
||||||
|
return App.util.format.count(val);
|
||||||
|
},
|
||||||
|
|
||||||
|
enterIntersect() {
|
||||||
|
if(this.isIntersecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isIntersecting = true;
|
||||||
|
axios.get('/api/v1/timelines/tag/' + this.id, {
|
||||||
|
params: {
|
||||||
|
max_id: this.maxId,
|
||||||
|
limit: 40,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if(res.data && res.data.length) {
|
||||||
|
this.feed.push(...res.data);
|
||||||
|
this.maxId = res.data[res.data.length - 1].id;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.canLoadMore = res;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isIntersecting = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
goToPost(status) {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'post',
|
||||||
|
path: `/i/web/post/${status.id}`,
|
||||||
|
params: {
|
||||||
|
id: status.id,
|
||||||
|
cachedStatus: status,
|
||||||
|
cachedProfile: this.profile
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
followHashtag() {
|
||||||
|
this.followingLoading = true;
|
||||||
|
axios.post('/api/v1/tags/' + this.id + '/follow')
|
||||||
|
.then(res => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.hashtag.following = true;
|
||||||
|
this.followingLoading = false;
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
unfollowHashtag() {
|
||||||
|
this.followingLoading = true;
|
||||||
|
axios.post('/api/v1/tags/' + this.id + '/unfollow')
|
||||||
|
.then(res => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.hashtag.following = false;
|
||||||
|
this.followingLoading = false;
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.hashtag-component {
|
||||||
|
.hashtag-feed {
|
||||||
|
.card,
|
||||||
|
.info-overlay-text,
|
||||||
|
.info-overlay-text-label,
|
||||||
|
img,
|
||||||
|
canvas {
|
||||||
|
border-radius: 18px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hashtag-follow {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ph-wrapper {
|
||||||
|
padding: 0.25rem;
|
||||||
|
|
||||||
|
.ph-item {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
.ph-picture {
|
||||||
|
height: auto;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in a new issue