瀏覽代碼

Add Hashtag component

Daniel Supernault 2 年之前
父節點
當前提交
b12b956b7d
共有 1 個文件被更改,包括 332 次插入0 次删除
  1. 332 0
      resources/assets/components/Hashtag.vue

+ 332 - 0
resources/assets/components/Hashtag.vue

@@ -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>