kopia lustrzana https://github.com/pixelfed/pixelfed
				
				
				
			Add Discover components
							rodzic
							
								
									bb97b55c66
								
							
						
					
					
						commit
						b447db082f
					
				|  | @ -0,0 +1,405 @@ | |||
| <template> | ||||
| 	<div class="web-wrapper"> | ||||
| 		<div v-if="isLoaded" class="container-fluid mt-3"> | ||||
| 			<div class="row"> | ||||
| 				<div class="col-md-4 col-lg-3"> | ||||
| 					<sidebar :user="profile" /> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div v-if="tab == 'index'" class="col-md-8 col-lg-9 mt-n4"> | ||||
| 					<div v-if="profile.is_admin" class="d-md-flex my-md-3"> | ||||
| 						<grid-card | ||||
| 							:dark="true" | ||||
| 							:title="'Hello ' + profile.username" | ||||
| 							subtitle="Welcome to the new Discover experience! Only admins can see this" | ||||
| 							button-text="Manage Discover Settings" | ||||
| 							button-link="/i/web/discover/settings" | ||||
| 							icon-class="fal fa-cog" | ||||
| 							:small="true" /> | ||||
| 					</div> | ||||
| 					<!-- <section class="mb-1 mb-md-3 mb-lg-4"> | ||||
| 						<news-slider /> | ||||
| 					</section> --> | ||||
| 
 | ||||
| 					<!-- <discover-spotlight /> --> | ||||
| 
 | ||||
| 					<!-- <div class="d-md-flex my-md-3"> | ||||
| 						<grid-card | ||||
| 							:dark="true" | ||||
| 							title="The Not So Trending" | ||||
| 							subtitle="Explore the posts that deserve more attention" | ||||
| 							button-text="Explore posts" | ||||
| 							icon-class="fal fa-analytics" | ||||
| 							button-link="/i/web/discover/future-trending" | ||||
| 							:button-event="true" | ||||
| 							v-on:btn-click="toggleTab('trending')" | ||||
| 							:small="true" /> | ||||
| 
 | ||||
| 						<grid-card | ||||
| 							title="Behind The Posts" | ||||
| 							subtitle="Discover the people" | ||||
| 							button-text="Discover People" | ||||
| 							button-link="/i/web/discover/people" | ||||
| 							icon-class="fal fa-user-friends" | ||||
| 							:small="true" /> | ||||
| 					</div> --> | ||||
| 
 | ||||
| 					<daily-trending v-on:btn-click="toggleTab('trending')"/> | ||||
| 
 | ||||
| 					<!-- <div class="d-md-flex my-md-3"> | ||||
| 						<grid-card | ||||
| 							title="Explore Loops" | ||||
| 							subtitle="Loops are short, looping videos" | ||||
| 							button-text="Explore Loops" | ||||
| 							icon-class="fal fa-camcorder" | ||||
| 							button-link="/i/web/discover/loops" | ||||
| 							:small="false" /> | ||||
| 
 | ||||
| 						<grid-card | ||||
| 							:dark="true" | ||||
| 							title="Popular Places" | ||||
| 							subtitle="Explore posts by popular locations" | ||||
| 							button-text="Explore Popular Places" | ||||
| 							icon-class="fal fa-map" | ||||
| 							:button-event="true" | ||||
| 							v-on:btn-click="toggleTab('popular-places')" | ||||
| 							button-link="/i/web/discover/popular-places" | ||||
| 							:small="false" /> | ||||
| 					</div> --> | ||||
| 
 | ||||
| 					<div class="d-md-flex my-md-3"> | ||||
| 						<grid-card | ||||
| 							v-if="config.hashtags.enabled" | ||||
| 							:dark="true" | ||||
| 							title="My Hashtags" | ||||
| 							subtitle="Explore posts tagged with hashtags you follow" | ||||
| 							button-text="Explore Posts" | ||||
| 							button-link="/i/web/discover/my-hashtags" | ||||
| 							icon-class="fal fa-hashtag" | ||||
| 							:small="false" /> | ||||
| 
 | ||||
| 						<grid-card | ||||
| 							v-if="config.memories.enabled" | ||||
| 							title="My Memories" | ||||
| 							subtitle="A distant look back" | ||||
| 							button-text="View Memories" | ||||
| 							button-link="/i/web/discover/my-memories" | ||||
| 							icon-class="fal fa-history" | ||||
| 							:small="false" /> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<div class="d-md-flex my-md-3"> | ||||
| 						<grid-card | ||||
| 							v-if="config.insights.enabled" | ||||
| 							title="Account Insights" | ||||
| 							subtitle="Get a rich overview of your account activity and interactions" | ||||
| 							button-text="View Account Insights" | ||||
| 							icon-class="fal fa-user-circle" | ||||
| 							button-link="/i/web/discover/account-insights" | ||||
| 							:small="false" /> | ||||
| 
 | ||||
| 						<grid-card | ||||
| 							v-if="config.friends.enabled" | ||||
| 							:dark="true" | ||||
| 							title="Find Friends" | ||||
| 							subtitle="Find accounts to follow based on common interests" | ||||
| 							button-text="Find Friends & Followers" | ||||
| 							button-link="/i/web/discover/find-friends" | ||||
| 							icon-class="fal fa-user-plus" | ||||
| 							:small="false" /> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<div class="d-md-flex my-md-3"> | ||||
| 						<grid-card | ||||
| 							v-if="config.server.enabled && config.server.domains && config.server.domains.length" | ||||
| 							:dark="true" | ||||
| 							title="Server Timelines" | ||||
| 							subtitle="Browse timelines of a specific remote instance" | ||||
| 							button-text="Browse Server Feeds" | ||||
| 							icon-class="fal fa-list" | ||||
| 							button-link="/i/web/discover/server-timelines" | ||||
| 							:small="false" /> | ||||
| 
 | ||||
| 						<!-- <grid-card | ||||
| 							title="Curate the Spotlight" | ||||
| 							subtitle="Apply to curate the spotlight for one week" | ||||
| 							button-text="Apply to Curate Spotlight" | ||||
| 							button-link="/i/web/discover/spotlight/curate/apply" | ||||
| 							icon-class="fal fa-thumbs-up" | ||||
| 							:small="false" /> --> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div v-else-if="tab == 'trending'" class="col-md-8 col-lg-9 mt-n4"> | ||||
| 					<discover :profile="profile" /> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div v-else-if="tab == 'popular-places'" class="col-md-8 col-lg-9 mt-n4"> | ||||
| 					<section class="mt-3 mb-5 section-explore"> | ||||
| 						<div class="profile-timeline"> | ||||
| 							<div class="row p-0 mt-5"> | ||||
| 								<div class="col-12 mb-4 d-flex justify-content-between align-items-center"> | ||||
| 									<p class="d-block d-md-none h1 font-weight-bold mb-0 font-default">Popular Places</p> | ||||
| 									<p class="d-none d-md-block display-4 font-weight-bold mb-0 font-default">Popular Places</p> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 
 | ||||
| 						<div class="row mt-5"> | ||||
| 							<div class="col-12 col-md-12 mb-3"> | ||||
| 								<div class="card-img big"> | ||||
| 									<img src="/img/places/nyc.jpg"> | ||||
| 									<div class="title font-default">New York City</div> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<div class="col-12 col-md-6 mb-3"> | ||||
| 								<div class="card-img"> | ||||
| 									<img src="/img/places/edmonton.jpg"> | ||||
| 									<div class="title font-default">Edmonton</div> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<div class="col-12 col-md-6 mb-3"> | ||||
| 								<div class="card-img"> | ||||
| 									<img src="/img/places/paris.jpg"> | ||||
| 									<div class="title font-default">Paris</div> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<div class="col-12 col-md-4 mb-3"> | ||||
| 								<div class="card-img"> | ||||
| 									<img src="/img/places/london.jpg"> | ||||
| 									<div class="title font-default">London</div> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<div class="col-12 col-md-4 mb-3"> | ||||
| 								<div class="card-img"> | ||||
| 									<img src="/img/places/vancouver.jpg"> | ||||
| 									<div class="title font-default">Vancouver</div> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<div class="col-12 col-md-4 mb-3"> | ||||
| 								<div class="card-img"> | ||||
| 									<img src="/img/places/toronto.jpg"> | ||||
| 									<div class="title font-default">Toronto</div> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</section> | ||||
| 				</div> | ||||
| 
 | ||||
| 			</div> | ||||
| 
 | ||||
| 			<drawer /> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
| 	import Drawer from './partials/drawer.vue'; | ||||
| 	import Sidebar from './partials/sidebar.vue'; | ||||
| 	import Rightbar from './partials/rightbar.vue'; | ||||
| 	import Discover from './sections/DiscoverFeed.vue'; | ||||
| 	import DiscoverNewsSlider from './partials/discover/news-slider.vue'; | ||||
| 	import DiscoverSpotlight from './partials/discover/discover-spotlight.vue'; | ||||
| 	import DailyTrending from './partials/discover/daily-trending.vue'; | ||||
| 	import DiscoverGridCard from './partials/discover/grid-card.vue'; | ||||
| 
 | ||||
| 	export default { | ||||
| 		 components: { | ||||
| 		 	"drawer": Drawer, | ||||
|             "sidebar": Sidebar, | ||||
|             "rightbar": Rightbar, | ||||
|             "discover": Discover, | ||||
|             "news-slider": DiscoverNewsSlider, | ||||
|             "discover-spotlight": DiscoverSpotlight, | ||||
|             "daily-trending": DailyTrending, | ||||
|             "grid-card": DiscoverGridCard | ||||
|         }, | ||||
| 
 | ||||
|         data() { | ||||
|         	return { | ||||
|         		isLoaded: false, | ||||
|         		profile: undefined, | ||||
|         		config: {}, | ||||
|         		tab: 'index', | ||||
|         		popularAccounts: [], | ||||
|         		followingIndex: undefined | ||||
|         	} | ||||
|         }, | ||||
| 
 | ||||
|         updated() { | ||||
| 			// let u = new URLSearchParams(window.location.search); | ||||
| 			// if(u.has('ft') && u.get('ft') == '1') { | ||||
| 			// 	this.tab = 'index'; | ||||
| 			// } | ||||
|         }, | ||||
| 
 | ||||
|         mounted() { | ||||
| 			this.profile = window._sharedData.user; | ||||
| 			this.fetchConfig(); | ||||
|         }, | ||||
| 
 | ||||
|         methods: { | ||||
| 			fetchConfig() { | ||||
| 				axios.get('/api/pixelfed/v2/discover/meta') | ||||
| 				.then(res => { | ||||
| 					this.config = res.data; | ||||
| 					this.isLoaded = true; | ||||
| 					window._sharedData.discoverMeta = res.data; | ||||
| 					// this.fetchPopularAccounts(); | ||||
| 				}) | ||||
| 			}, | ||||
| 
 | ||||
|         	fetchPopularAccounts() { | ||||
|         		// axios.get('/api/pixelfed/discover/accounts/popular') | ||||
|         		// .then(res => { | ||||
|         		// 	this.popularAccounts = res.data; | ||||
|         		// }) | ||||
|         	}, | ||||
| 
 | ||||
|         	followProfile(index) { | ||||
|         		event.currentTarget.blur(); | ||||
|         		this.followingIndex = index; | ||||
|         		let id = this.popularAccounts[index].id; | ||||
| 
 | ||||
|         		axios.post('/api/v1/accounts/' + id + '/follow') | ||||
| 				.then(res => { | ||||
|         			this.followingIndex = undefined; | ||||
|         			this.popularAccounts.splice(index, 1); | ||||
| 				}).catch(err => { | ||||
|         			this.followingIndex = undefined; | ||||
| 					swal('Oops!', 'An error occured when attempting to follow this account.', 'error'); | ||||
| 				}); | ||||
|         	}, | ||||
| 
 | ||||
|         	goToProfile(account) { | ||||
| 				this.$router.push({ | ||||
| 					path: `/i/web/profile/${account.id}`, | ||||
| 					params: { | ||||
| 						id: account.id, | ||||
| 						cachedProfile: account, | ||||
| 						cachedUser: this.profile | ||||
| 					} | ||||
| 				}) | ||||
| 			}, | ||||
| 
 | ||||
| 			toggleTab(index) { | ||||
| 				this.tab = index; | ||||
| 				setTimeout(() => { | ||||
| 					window.scrollTo({top: 0, behavior: 'smooth'}); | ||||
| 				}, 300); | ||||
| 			}, | ||||
| 
 | ||||
| 			openManageModal() { | ||||
| 				event.currentTarget.blur(); | ||||
| 				swal('Settings', 'Discover settings here', 'info'); | ||||
| 			} | ||||
|         } | ||||
| 	} | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| 	.card-img { | ||||
| 		position: relative; | ||||
| 
 | ||||
| 		img { | ||||
| 			object-fit: cover; | ||||
| 			width: 100%; | ||||
| 			height: 200px; | ||||
| 			border-radius: 10px; | ||||
| 
 | ||||
| 		} | ||||
| 
 | ||||
| 		&:before, | ||||
| 		&:after { | ||||
| 			content: ""; | ||||
| 			background: rgba(0,0,0,0.2); | ||||
| 			z-index: 2; | ||||
| 			width: 100%; | ||||
| 			height: 100%; | ||||
| 			position: absolute; | ||||
| 			left: 0; | ||||
| 			top: 0; | ||||
| 			border-radius: 10px; | ||||
| 		} | ||||
| 
 | ||||
| 		.title { | ||||
| 			position: absolute; | ||||
| 			bottom: 5px; | ||||
| 			left: 10px; | ||||
| 			font-size: 40px; | ||||
| 			color: #fff; | ||||
| 			z-index: 3; | ||||
| 			font-weight: 700; | ||||
| 		} | ||||
| 
 | ||||
| 		&.big { | ||||
| 			img { | ||||
| 				height: 300px; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	.font-default { | ||||
| 		font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | ||||
| 		letter-spacing: -0.7px; | ||||
| 	} | ||||
| 
 | ||||
| 	.bg-stellar { | ||||
| 		background: #7474BF; | ||||
| 		background: -webkit-linear-gradient(to right, #348AC7, #7474BF); | ||||
| 		background: linear-gradient(to right, #348AC7, #7474BF); | ||||
| 	} | ||||
| 
 | ||||
| 	.bg-berry { | ||||
| 		background: #5433FF; | ||||
| 		background: -webkit-linear-gradient(to right, #acb6e5, #86fde8); | ||||
| 		background: linear-gradient(to right, #acb6e5, #86fde8); | ||||
| 	} | ||||
| 
 | ||||
| 	.bg-midnight { | ||||
| 		background: #232526; | ||||
| 		background: -webkit-linear-gradient(to right, #414345, #232526); | ||||
| 		background: linear-gradient(to right, #414345, #232526); | ||||
| 	} | ||||
| 
 | ||||
| 	.media-body { | ||||
| 		margin-right: 0.5rem; | ||||
| 	} | ||||
| 
 | ||||
| 	.avatar { | ||||
| 		border-radius: 15px; | ||||
| 	} | ||||
| 
 | ||||
| 	.username { | ||||
| 		font-size: 14px; | ||||
| 		line-height: 14px; | ||||
| 		margin-bottom: 2px; | ||||
| 		word-break: break-word !important; | ||||
| 		word-wrap: break-word !important; | ||||
| 	} | ||||
| 
 | ||||
| 	.display-name { | ||||
| 		margin-bottom: 0; | ||||
| 		font-size: 12px; | ||||
| 		word-break: break-word !important; | ||||
| 		word-wrap: break-word !important; | ||||
| 	} | ||||
| 
 | ||||
| 	.follower-count { | ||||
| 		margin-bottom: 0; | ||||
| 		font-size: 10px; | ||||
| 		word-break: break-word !important; | ||||
| 		word-wrap: break-word !important; | ||||
| 	} | ||||
| 
 | ||||
| 	.follow { | ||||
| 		background-color: var(--primary); | ||||
| 		border-radius: 18px; | ||||
| 		font-weight: 600; | ||||
| 		padding: 5px 15px; | ||||
| 	} | ||||
| </style> | ||||
|  | @ -0,0 +1,182 @@ | |||
| <template> | ||||
| 	<div class="discover-find-friends-component"> | ||||
| 		<div v-if="isLoaded" class="container-fluid mt-3"> | ||||
| 
 | ||||
| 			<div class="row"> | ||||
| 				<div class="col-md-4 col-lg-3"> | ||||
| 					<sidebar :user="profile" /> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="col-md-6 col-lg-6"> | ||||
| 					<b-breadcrumb class="font-default" :items="breadcrumbItems"></b-breadcrumb> | ||||
| 
 | ||||
| 					<h1 class="font-default">Find Friends</h1> | ||||
| 					<!-- <p class="font-default lead">Posts from hashtags you follow</p> --> | ||||
| 					<hr> | ||||
| 
 | ||||
| 					<b-spinner v-if="isLoading" /> | ||||
| 
 | ||||
| 					<div v-if="!isLoading" class="row justify-content-center"> | ||||
| 						<div class="col-12 col-lg-10 mb-3" v-for="(profile, index) in popularAccounts"> | ||||
| 							<div class="card shadow-sm border-0 rounded-px"> | ||||
| 								<div class="card-body p-2"> | ||||
| 									<profile-card | ||||
| 										:key="'pfc' + index" | ||||
| 										:profile="profile" | ||||
| 										class="w-100" | ||||
| 										v-on:follow="follow(index)" | ||||
| 										v-on:unfollow="unfollow(index)" | ||||
| 									/> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
| import Drawer from './../partials/drawer.vue'; | ||||
| import Sidebar from './../partials/sidebar.vue'; | ||||
| import StatusCard from './../partials/TimelineStatus.vue'; | ||||
| import ProfileCard from './../partials/profile/ProfileHoverCard.vue'; | ||||
| 
 | ||||
| export default { | ||||
| 	components: { | ||||
| 		"drawer": Drawer, | ||||
| 		"sidebar": Sidebar, | ||||
| 		"status-card": StatusCard, | ||||
| 		"profile-card": ProfileCard | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			isLoaded: true, | ||||
| 			isLoading: true, | ||||
| 			profile: window._sharedData.user, | ||||
| 			feed: [], | ||||
| 			popular: [], | ||||
| 			popularAccounts: [], | ||||
| 			popularLoaded: false, | ||||
| 			breadcrumbItems: [ | ||||
| 				{ | ||||
| 					text: 'Discover', | ||||
| 					href: '/i/web/discover' | ||||
| 				}, | ||||
| 				{ | ||||
| 					text: 'Find Friends', | ||||
| 					active: true | ||||
| 				} | ||||
| 			] | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.fetchConfig(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		fetchConfig() { | ||||
| 			axios.get('/api/pixelfed/v2/discover/meta') | ||||
| 			.then(res => { | ||||
| 				if(res.data.friends.enabled == false) { | ||||
| 					this.$router.push('/i/web/discover'); | ||||
| 				} else { | ||||
| 					this.fetchPopularAccounts(); | ||||
| 				} | ||||
| 			}) | ||||
| 			.catch(e => { | ||||
| 				this.isLoading = false; | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		fetchPopular() { | ||||
| 			axios.get('/api/pixelfed/v2/discover/account-insights') | ||||
| 			.then(res => { | ||||
| 				this.popular = res.data; | ||||
| 				this.popularLoaded = true; | ||||
| 				this.isLoading = false; | ||||
| 			}) | ||||
| 			.catch(e => { | ||||
| 				this.isLoading = false; | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		formatCount(val) { | ||||
| 			return App.util.format.count(val); | ||||
| 		}, | ||||
| 
 | ||||
| 		timeago(ts) { | ||||
| 			return App.util.format.timeAgo(ts); | ||||
| 		}, | ||||
| 
 | ||||
| 		fetchPopularAccounts() { | ||||
| 			axios.get('/api/pixelfed/discover/accounts/popular') | ||||
| 			.then(res => { | ||||
| 				this.popularAccounts = res.data; | ||||
| 				this.isLoading = false; | ||||
| 			}) | ||||
| 			.catch(e => { | ||||
| 				this.isLoading = false; | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		follow(index) { | ||||
| 			axios.post('/api/v1/accounts/' + this.popularAccounts[index].id + '/follow') | ||||
| 			.then(res => { | ||||
| 				this.newlyFollowed++; | ||||
| 				this.$store.commit('updateRelationship', [res.data]); | ||||
| 				this.$emit('update-profile', { | ||||
| 					'following_count': this.profile.following_count + 1 | ||||
| 				}) | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		unfollow(index) { | ||||
| 			axios.post('/api/v1/accounts/' + this.popularAccounts[index].id + '/unfollow') | ||||
| 			.then(res => { | ||||
| 				this.newlyFollowed--; | ||||
| 				this.$store.commit('updateRelationship', [res.data]); | ||||
| 				this.$emit('update-profile', { | ||||
| 					'following_count': this.profile.following_count - 1 | ||||
| 				}) | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
| 	.discover-find-friends-component { | ||||
| 		.bg-stellar { | ||||
| 			background: #7474BF; | ||||
| 			background: -webkit-linear-gradient(to right, #348AC7, #7474BF); | ||||
| 			background: linear-gradient(to right, #348AC7, #7474BF); | ||||
| 		} | ||||
| 
 | ||||
| 		.bg-midnight { | ||||
| 			background: #232526; | ||||
| 			background: -webkit-linear-gradient(to right, #414345, #232526); | ||||
| 			background: linear-gradient(to right, #414345, #232526); | ||||
| 		} | ||||
| 
 | ||||
| 		.font-default { | ||||
| 			font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | ||||
| 			letter-spacing: -0.7px; | ||||
| 		} | ||||
| 
 | ||||
| 		.active { | ||||
| 			font-weight: 700; | ||||
| 		} | ||||
| 
 | ||||
| 		.profile-hover-card-inner { | ||||
| 			width: 100%; | ||||
| 
 | ||||
| 			.d-flex { | ||||
| 				max-width: 100% !important; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -0,0 +1,384 @@ | |||
| <template> | ||||
| 	<div class="discover-my-hashtags-component"> | ||||
| 		<div v-if="isLoaded" class="container-fluid mt-3"> | ||||
| 
 | ||||
| 			<div class="row"> | ||||
| 				<div class="col-md-4 col-lg-3"> | ||||
| 					<sidebar :user="profile" /> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="col-md-6 col-lg-6"> | ||||
| 					<b-breadcrumb class="font-default" :items="breadcrumbItems"></b-breadcrumb> | ||||
| 
 | ||||
| 					<h1 class="font-default">My Hashtags</h1> | ||||
| 					<p class="font-default lead">Posts from hashtags you follow</p> | ||||
| 
 | ||||
| 					<hr> | ||||
| 
 | ||||
| 					<b-spinner v-if="isLoading" /> | ||||
| 
 | ||||
| 					<status-card | ||||
| 						v-if="!isLoading" | ||||
| 						v-for="(post, index) in feed" | ||||
| 						:key="'ti1:'+index+':'+post.id" | ||||
| 						:profile="profile" | ||||
| 						:status="post" | ||||
| 						@like="likeStatus(index)" | ||||
| 						@unlike="unlikeStatus(index)" | ||||
| 						@share="shareStatus(index)" | ||||
| 						@unshare="unshareStatus(index)" | ||||
| 						@menu="openContextMenu(index)" | ||||
| 						@mod-tools="handleModTools(index)" | ||||
| 						@likes-modal="openLikesModal(index)" | ||||
| 						@shares-modal="openSharesModal(index)" | ||||
| 						@bookmark="handleBookmark(index)" | ||||
| 						/> | ||||
| 
 | ||||
| 					<p v-if="!isLoading && tagsLoaded && feed.length == 0" class="lead">No hashtags found :(</p> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="col-md-2 col-lg-3"> | ||||
| 					<div class="nav flex-column nav-pills font-default"> | ||||
| 						<a | ||||
| 							v-for="(tag, idx) in tags" | ||||
| 							class="nav-link" | ||||
| 							:class="{ active: tagIndex == idx }" | ||||
| 							href="#" | ||||
| 							@click.prevent="toggleTag(idx)"> | ||||
| 							{{ tag }} | ||||
| 						</a> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 
 | ||||
| 		<context-menu | ||||
| 			v-if="showMenu" | ||||
| 			ref="contextMenu" | ||||
| 			:status="feed[postIndex]" | ||||
| 			:profile="profile" | ||||
| 			@moderate="commitModeration" | ||||
| 			@delete="deletePost" | ||||
| 			@report-modal="handleReport" | ||||
| 		/> | ||||
| 
 | ||||
| 		<likes-modal | ||||
| 			v-if="showLikesModal" | ||||
| 			ref="likesModal" | ||||
| 			:status="likesModalPost" | ||||
| 			:profile="profile" | ||||
| 		/> | ||||
| 
 | ||||
| 		<shares-modal | ||||
| 			v-if="showSharesModal" | ||||
| 			ref="sharesModal" | ||||
| 			:status="sharesModalPost" | ||||
| 			:profile="profile" | ||||
| 		/> | ||||
| 
 | ||||
| 		<report-modal | ||||
| 			ref="reportModal" | ||||
| 			:key="reportedStatusId" | ||||
| 			:status="reportedStatus" | ||||
| 		/> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
| import Drawer from './../partials/drawer.vue'; | ||||
| import Sidebar from './../partials/sidebar.vue'; | ||||
| import StatusCard from './../partials/TimelineStatus.vue'; | ||||
| import ContextMenu from './../partials/post/ContextMenu.vue'; | ||||
| import LikesModal from './../partials/post/LikeModal.vue'; | ||||
| import SharesModal from './../partials/post/ShareModal.vue'; | ||||
| import ReportModal from './../partials/modal/ReportPost.vue'; | ||||
| 
 | ||||
| export default { | ||||
| 	components: { | ||||
| 		"drawer": Drawer, | ||||
| 		"sidebar": Sidebar, | ||||
| 		"context-menu": ContextMenu, | ||||
|         "likes-modal": LikesModal, | ||||
|         "shares-modal": SharesModal, | ||||
|         "report-modal": ReportModal, | ||||
| 		"status-card": StatusCard | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			isLoaded: true, | ||||
| 			isLoading: true, | ||||
| 			profile: window._sharedData.user, | ||||
| 			tagIndex: 0, | ||||
| 			tags: [], | ||||
| 			feed: [], | ||||
| 			tagsLoaded: false, | ||||
| 			breadcrumbItems: [ | ||||
| 				{ | ||||
| 					text: 'Discover', | ||||
| 					href: '/i/web/discover' | ||||
| 				}, | ||||
| 				{ | ||||
| 					text: 'My Hashtags', | ||||
| 					active: true | ||||
| 				} | ||||
| 			], | ||||
| 				canLoadMore: true, | ||||
| 				isFetchingMore: false, | ||||
| 				endFeedReached: false, | ||||
| 			postIndex: 0, | ||||
| 			showMenu: false, | ||||
| 			showLikesModal: false, | ||||
| 			likesModalPost: {}, | ||||
| 			showReportModal: false, | ||||
| 			reportedStatus: {}, | ||||
| 			reportedStatusId: 0, | ||||
| 			showSharesModal: false, | ||||
| 			sharesModalPost: {}, | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.fetchHashtags(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		fetchHashtags() { | ||||
| 			axios.get('/api/local/discover/tag/list') | ||||
| 			.then(res => { | ||||
| 				this.tags = res.data; | ||||
| 				this.tagsLoaded = true; | ||||
| 				if(this.tags.length) { | ||||
| 					this.fetchTagFeed(this.tags[0]); | ||||
| 				} else { | ||||
| 					this.isLoading = false; | ||||
| 				} | ||||
| 			}) | ||||
| 			.catch(e => { | ||||
| 				this.isLoading = false; | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		fetchTagFeed(hashtag) { | ||||
| 			this.isLoading = true; | ||||
| 			axios.get('/api/v2/discover/tag', { | ||||
| 				params: { | ||||
| 					hashtag: hashtag | ||||
| 				} | ||||
| 			}) | ||||
| 			.then(res => { | ||||
| 				this.feed = res.data.tags.map(p => p.status); | ||||
| 				this.isLoading = false; | ||||
| 			}) | ||||
| 			.catch(e => { | ||||
| 				this.isLoading = false; | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		toggleTag(tag) { | ||||
| 			this.tagIndex = tag; | ||||
| 			this.fetchTagFeed(this.tags[tag]); | ||||
| 		}, | ||||
| 
 | ||||
| 		likeStatus(index) { | ||||
| 			let status = this.feed[index]; | ||||
| 			let state = status.favourited; | ||||
| 			let count = status.favourites_count; | ||||
| 			this.feed[index].favourites_count = count + 1; | ||||
| 			this.feed[index].favourited = !status.favourited; | ||||
| 
 | ||||
| 			axios.post('/api/v1/statuses/' + status.id + '/favourite') | ||||
| 			.then(res => { | ||||
| 				// | ||||
| 			}).catch(err => { | ||||
| 				this.feed[index].favourites_count = count; | ||||
| 				this.feed[index].favourited = false; | ||||
| 
 | ||||
| 				let el = document.createElement('p'); | ||||
| 				el.classList.add('text-left'); | ||||
| 				el.classList.add('mb-0'); | ||||
| 				el.innerHTML = '<span class="lead">We limit certain interactions to keep our community healthy and it appears that you have reached that limit. <span class="font-weight-bold">Please try again later.</span></span>'; | ||||
| 				let wrapper = document.createElement('div'); | ||||
| 				wrapper.appendChild(el); | ||||
| 
 | ||||
| 				if(err.response.status === 429) { | ||||
| 					swal({ | ||||
| 						title: 'Too many requests', | ||||
| 						content: wrapper, | ||||
| 						icon: 'warning', | ||||
| 						buttons: { | ||||
| 							// moreInfo: { | ||||
| 							// 	text: "Contact a human", | ||||
| 							// 	visible: true, | ||||
| 							// 	value: "more", | ||||
| 							// 	className: "text-lighter bg-transparent border" | ||||
| 							// }, | ||||
| 							confirm: { | ||||
| 								text: "OK", | ||||
| 								value: false, | ||||
| 								visible: true, | ||||
| 								className: "bg-transparent primary", | ||||
| 								closeModal: true | ||||
| 							} | ||||
| 						} | ||||
| 					}) | ||||
| 					.then((val) => { | ||||
| 						if(val == 'more') { | ||||
| 							location.href = '/site/contact' | ||||
| 						} | ||||
| 						return; | ||||
| 					}); | ||||
| 				} | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		unlikeStatus(index) { | ||||
| 			let status = this.feed[index]; | ||||
| 			let state = status.favourited; | ||||
| 			let count = status.favourites_count; | ||||
| 			this.feed[index].favourites_count = count - 1; | ||||
| 			this.feed[index].favourited = !status.favourited; | ||||
| 
 | ||||
| 			axios.post('/api/v1/statuses/' + status.id + '/unfavourite') | ||||
| 			.then(res => { | ||||
| 				// | ||||
| 			}).catch(err => { | ||||
| 				this.feed[index].favourites_count = count; | ||||
| 				this.feed[index].favourited = false; | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		shareStatus(index) { | ||||
| 			let status = this.feed[index]; | ||||
| 			let state = status.reblogged; | ||||
| 			let count = status.reblogs_count; | ||||
| 			this.feed[index].reblogs_count = count + 1; | ||||
| 			this.feed[index].reblogged = !status.reblogged; | ||||
| 
 | ||||
| 			axios.post('/api/v1/statuses/' + status.id + '/reblog') | ||||
| 			.then(res => { | ||||
| 				// | ||||
| 			}).catch(err => { | ||||
| 				this.feed[index].reblogs_count = count; | ||||
| 				this.feed[index].reblogged = false; | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		unshareStatus(index) { | ||||
| 			let status = this.feed[index]; | ||||
| 			let state = status.reblogged; | ||||
| 			let count = status.reblogs_count; | ||||
| 			this.feed[index].reblogs_count = count - 1; | ||||
| 			this.feed[index].reblogged = !status.reblogged; | ||||
| 
 | ||||
| 			axios.post('/api/v1/statuses/' + status.id + '/unreblog') | ||||
| 			.then(res => { | ||||
| 				// | ||||
| 			}).catch(err => { | ||||
| 				this.feed[index].reblogs_count = count; | ||||
| 				this.feed[index].reblogged = false; | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		openContextMenu(idx) { | ||||
| 			this.postIndex = idx; | ||||
| 			this.showMenu = true; | ||||
| 			this.$nextTick(() => { | ||||
| 				this.$refs.contextMenu.open(); | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		commitModeration(type) { | ||||
| 			let idx = this.postIndex; | ||||
| 
 | ||||
| 			switch(type) { | ||||
| 				case 'addcw': | ||||
| 					this.feed[idx].sensitive = true; | ||||
| 				break; | ||||
| 
 | ||||
| 				case 'remcw': | ||||
| 					this.feed[idx].sensitive = false; | ||||
| 				break; | ||||
| 
 | ||||
| 				case 'unlist': | ||||
| 					this.feed.splice(idx, 1); | ||||
| 				break; | ||||
| 
 | ||||
| 				case 'spammer': | ||||
| 					let id = this.feed[idx].account.id; | ||||
| 
 | ||||
| 					this.feed = this.feed.filter(post => { | ||||
| 						return post.account.id != id; | ||||
| 					}); | ||||
| 				break; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		deletePost() { | ||||
| 			this.feed.splice(this.postIndex, 1); | ||||
| 		}, | ||||
| 
 | ||||
| 		handleReport(post) { | ||||
| 			this.reportedStatusId = post.id; | ||||
| 			this.$nextTick(() => { | ||||
| 				this.reportedStatus = post; | ||||
| 				this.$refs.reportModal.open(); | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		openLikesModal(idx) { | ||||
| 			this.postIndex = idx; | ||||
| 			this.likesModalPost = this.feed[this.postIndex]; | ||||
| 			this.showLikesModal = true; | ||||
| 			this.$nextTick(() => { | ||||
| 				this.$refs.likesModal.open(); | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		openSharesModal(idx) { | ||||
| 			this.postIndex = idx; | ||||
| 			this.sharesModalPost = this.feed[this.postIndex]; | ||||
| 			this.showSharesModal = true; | ||||
| 			this.$nextTick(() => { | ||||
| 				this.$refs.sharesModal.open(); | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		handleBookmark(index) { | ||||
| 			let p = this.feed[index]; | ||||
| 
 | ||||
| 			axios.post('/i/bookmark', { | ||||
| 				item: p.id | ||||
| 			}) | ||||
| 			.then(res => { | ||||
| 				this.feed[index].bookmarked = !p.bookmarked; | ||||
| 			}) | ||||
| 			.catch(err => { | ||||
| 				this.$bvToast.toast('Cannot bookmark post at this time.', { | ||||
| 					title: 'Bookmark Error', | ||||
| 					variant: 'danger', | ||||
| 					autoHideDelay: 5000 | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| 	.discover-my-hashtags-component { | ||||
| 		.bg-stellar { | ||||
| 			background: #7474BF; | ||||
| 			background: -webkit-linear-gradient(to right, #348AC7, #7474BF); | ||||
| 			background: linear-gradient(to right, #348AC7, #7474BF); | ||||
| 		} | ||||
| 		.font-default { | ||||
| 			font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | ||||
| 			letter-spacing: -0.7px; | ||||
| 		} | ||||
| 
 | ||||
| 		.active { | ||||
| 			font-weight: 700; | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -0,0 +1,190 @@ | |||
| <template> | ||||
| 	<div class="discover-insights-component"> | ||||
| 		<div v-if="isLoaded" class="container-fluid mt-3"> | ||||
| 
 | ||||
| 			<div class="row"> | ||||
| 				<div class="col-md-4 col-lg-3"> | ||||
| 					<sidebar :user="profile" /> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="col-md-6 col-lg-6"> | ||||
| 					<b-breadcrumb class="font-default" :items="breadcrumbItems"></b-breadcrumb> | ||||
| 
 | ||||
| 					<h1 class="font-default">Account Insights</h1> | ||||
| 					<p class="font-default lead">A brief overview of your account</p> | ||||
| 					<hr> | ||||
| 
 | ||||
| 					<div class="row"> | ||||
| 						<div class="col-12 col-md-6 mb-3"> | ||||
| 							<div class="card bg-midnight"> | ||||
| 								<div class="card-body font-default text-white"> | ||||
| 									<h1 class="display-4 mb-n2">{{ formatCount(profile.statuses_count) }}</h1> | ||||
| 									<p class="primary lead mb-0 font-weight-bold">Posts</p> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 
 | ||||
| 						<div class="col-12 col-md-6 mb-3"> | ||||
| 							<div class="card bg-midnight"> | ||||
| 								<div class="card-body font-default text-white"> | ||||
| 									<h1 class="display-4 mb-n2">{{ formatCount(profile.followers_count) }}</h1> | ||||
| 									<p class="primary lead mb-0 font-weight-bold">Followers</p> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<div v-if="profile.statuses_count" class="card my-3 bg-midnight"> | ||||
| 						<div class="card-header bg-dark border-bottom border-primary text-white font-default lead">Popular Posts</div> | ||||
| 						<div v-if="!popularLoaded" class="card-body text-white"> | ||||
| 							<b-spinner/> | ||||
| 						</div> | ||||
| 
 | ||||
| 						<ul v-else class="list-group list-group-flush font-default text-white"> | ||||
| 							<li v-for="post in popular" class="list-group-item bg-midnight"> | ||||
| 								<div class="media align-items-center"> | ||||
| 									<img | ||||
| 										v-if="post.media_attachments.length" | ||||
| 										:src="post.media_attachments[0].url" | ||||
| 										onerror="this.onerror=null;this.src='/storage/no-preview.png?v=0'" | ||||
| 										class="media-photo shadow"> | ||||
| 
 | ||||
| 									<div class="media-body"> | ||||
| 										<p class="media-caption mb-0">{{ post.content_text.slice(0, 40) }}</p> | ||||
| 										<p class="mb-0"> | ||||
| 											<span class="font-weight-bold">{{ post.favourites_count }} Likes</span> | ||||
| 											<span class="mx-2">·</span> | ||||
| 											<span class="text-muted">Posted {{ timeago(post.created_at) }} ago</span> | ||||
| 										</p> | ||||
| 									</div> | ||||
| 
 | ||||
| 									<button class="btn btn-primary primary font-weight-bold rounded-pill" @click="gotoPost(post)">View</button> | ||||
| 								</div> | ||||
| 							</li> | ||||
| 						</ul> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
| import Drawer from './../partials/drawer.vue'; | ||||
| import Sidebar from './../partials/sidebar.vue'; | ||||
| import StatusCard from './../partials/TimelineStatus.vue'; | ||||
| 
 | ||||
| export default { | ||||
| 	components: { | ||||
| 		"drawer": Drawer, | ||||
| 		"sidebar": Sidebar, | ||||
| 		"status-card": StatusCard | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			isLoaded: true, | ||||
| 			isLoading: true, | ||||
| 			profile: window._sharedData.user, | ||||
| 			feed: [], | ||||
| 			popular: [], | ||||
| 			popularLoaded: false, | ||||
| 			breadcrumbItems: [ | ||||
| 				{ | ||||
| 					text: 'Discover', | ||||
| 					href: '/i/web/discover' | ||||
| 				}, | ||||
| 				{ | ||||
| 					text: 'Account Insights', | ||||
| 					active: true | ||||
| 				} | ||||
| 			] | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.fetchConfig(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		fetchConfig() { | ||||
| 			axios.get('/api/pixelfed/v2/discover/meta') | ||||
| 			.then(res => { | ||||
| 				if(res.data.insights.enabled == false) { | ||||
| 					this.$router.push('/i/web/discover'); | ||||
| 				} | ||||
| 				this.fetchPopular(); | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		fetchPopular() { | ||||
| 			axios.get('/api/pixelfed/v2/discover/account-insights') | ||||
| 			.then(res => { | ||||
| 				this.popular = res.data.filter(p => { | ||||
| 					return p.favourites_count; | ||||
| 				}); | ||||
| 				this.popularLoaded = true; | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		formatCount(val) { | ||||
| 			return App.util.format.count(val); | ||||
| 		}, | ||||
| 
 | ||||
| 		timeago(ts) { | ||||
| 			return App.util.format.timeAgo(ts); | ||||
| 		}, | ||||
| 
 | ||||
| 		gotoPost(status) { | ||||
| 			this.$router.push({ | ||||
| 				name: 'post', | ||||
| 				path: `/i/web/post/${status.id}`, | ||||
| 				params: { | ||||
| 					id: status.id, | ||||
| 					cachedStatus: status, | ||||
| 					cachedProfile: this.profile | ||||
| 				} | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| 	.discover-insights-component { | ||||
| 		.bg-stellar { | ||||
| 			background: #7474BF; | ||||
| 			background: -webkit-linear-gradient(to right, #348AC7, #7474BF); | ||||
| 			background: linear-gradient(to right, #348AC7, #7474BF); | ||||
| 		} | ||||
| 
 | ||||
| 		.bg-midnight { | ||||
| 			background: #232526; | ||||
| 			background: -webkit-linear-gradient(to right, #414345, #232526); | ||||
| 			background: linear-gradient(to right, #414345, #232526); | ||||
| 		} | ||||
| 
 | ||||
| 		.font-default { | ||||
| 			font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | ||||
| 			letter-spacing: -0.7px; | ||||
| 		} | ||||
| 
 | ||||
| 		.active { | ||||
| 			font-weight: 700; | ||||
| 		} | ||||
| 
 | ||||
| 		.media-photo { | ||||
| 			width: 70px; | ||||
| 			height: 70px; | ||||
| 			border-radius: 8px; | ||||
| 			margin-right: 2rem; | ||||
| 			object-fit: cover; | ||||
| 		} | ||||
| 
 | ||||
| 		.media-caption { | ||||
| 			letter-spacing: -0.3px; | ||||
| 			font-size: 17px; | ||||
| 			opacity: 0.7; | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -0,0 +1,172 @@ | |||
| <template> | ||||
| 	<div class="discover-my-memories web-wrapper"> | ||||
| 		<div v-if="isLoaded" class="container-fluid mt-3"> | ||||
| 			<div class="row"> | ||||
| 				<div class="col-md-4 col-lg-3"> | ||||
| 					<sidebar :user="profile" /> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div v-if="tabIndex === 0" class="col-md-6 col-lg-6"> | ||||
| 					<b-breadcrumb class="font-default" :items="breadcrumbItems"></b-breadcrumb> | ||||
| 
 | ||||
| 					<h1 class="font-default">My Memories</h1> | ||||
| 					<p class="font-default lead">Posts from this day in previous years</p> | ||||
| 
 | ||||
| 					<hr> | ||||
| 
 | ||||
| 					<b-spinner v-if="!feedLoaded" /> | ||||
| 
 | ||||
| 					<status-card | ||||
| 						v-for="(post, idx) in feed" | ||||
| 						:key="'ti0:'+idx+':'+post.id" | ||||
| 						:profile="profile" | ||||
| 						:status="post" | ||||
| 						/> | ||||
| 
 | ||||
| 					<p v-if="feedLoaded && feed.length == 0" class="lead">No memories found :(</p> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div v-else-if="tabIndex === 1" class="col-md-6 col-lg-6"> | ||||
| 					<b-breadcrumb class="font-default" :items="breadcrumbItems"></b-breadcrumb> | ||||
| 
 | ||||
| 					<h1 class="font-default">My Memories</h1> | ||||
| 					<p class="font-default lead">Posts I've liked from this day in previous years</p> | ||||
| 
 | ||||
| 					<hr> | ||||
| 
 | ||||
| 					<b-spinner v-if="!likedLoaded" /> | ||||
| 
 | ||||
| 					<status-card | ||||
| 						v-for="(post, idx) in liked" | ||||
| 						:key="'ti1:'+idx+':'+post.id" | ||||
| 						:profile="profile" | ||||
| 						:status="post" | ||||
| 						/> | ||||
| 
 | ||||
| 					<p v-if="likedLoaded && liked.length == 0" class="lead">No memories found :(</p> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="col-md-2 col-lg-3"> | ||||
| 					<div class="nav flex-column nav-pills font-default"> | ||||
| 						<a class="nav-link" :class="{ active: tabIndex == 0 }" href="#" @click.prevent="toggleTab(0)">My Posts</a> | ||||
| 						<a class="nav-link" :class="{ active: tabIndex == 1 }" href="#" @click.prevent="toggleTab(1)">Posts I've Liked</a> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
| 	import Drawer from './../partials/drawer.vue'; | ||||
| 	import Sidebar from './../partials/sidebar.vue'; | ||||
| 	import StatusCard from './../partials/TimelineStatus.vue'; | ||||
| 
 | ||||
| 	export default { | ||||
| 		components: { | ||||
| 		 	"drawer": Drawer, | ||||
|             "sidebar": Sidebar, | ||||
|             "status-card": StatusCard | ||||
|         }, | ||||
| 
 | ||||
|         data() { | ||||
|         	return { | ||||
|         		isLoaded: true, | ||||
|         		profile: window._sharedData.user, | ||||
|         		curDate: undefined, | ||||
|         		tabIndex: 0, | ||||
|         		feedLoaded: false, | ||||
|         		likedLoaded: false, | ||||
|         		feed: [], | ||||
|         		liked: [], | ||||
|         		breadcrumbItems: [ | ||||
| 					{ | ||||
| 						text: 'Discover', | ||||
| 						href: '/i/web/discover' | ||||
| 					}, | ||||
| 					{ | ||||
| 						text: 'My Memories', | ||||
| 						active: true | ||||
| 					} | ||||
| 				] | ||||
|         	} | ||||
|         }, | ||||
| 
 | ||||
|         mounted() { | ||||
|         	this.curDate = new Date(); | ||||
|         	this.fetchConfig(); | ||||
|         }, | ||||
| 
 | ||||
|         methods: { | ||||
|         	fetchConfig() { | ||||
|         		if( | ||||
|         			window._sharedData.hasOwnProperty('discoverMeta') && | ||||
|         			window._sharedData.discoverMeta | ||||
|         		) { | ||||
|         			this.config = window._sharedData.discoverMeta; | ||||
|         			this.isLoaded = true; | ||||
|         			if(this.config.memories.enabled == false) { | ||||
| 						this.$router.push('/i/web/discover'); | ||||
|         			} else { | ||||
|         				this.fetchMemories(); | ||||
|         			} | ||||
|         			return; | ||||
|         		} | ||||
| 				axios.get('/api/pixelfed/v2/discover/meta') | ||||
| 				.then(res => { | ||||
| 					this.config = res.data; | ||||
| 					this.isLoaded = true; | ||||
| 					window._sharedData.discoverMeta = res.data; | ||||
| 					if(res.data.memories.enabled == false) { | ||||
| 						this.$router.push('/i/web/discover'); | ||||
| 					} else { | ||||
| 						this.fetchMemories(); | ||||
| 					} | ||||
| 				}) | ||||
| 			}, | ||||
| 
 | ||||
| 			fetchMemories() { | ||||
| 				axios.get('/api/pixelfed/v2/discover/memories') | ||||
| 				.then(res => { | ||||
| 					this.feed = res.data; | ||||
| 					this.feedLoaded = true; | ||||
| 				}); | ||||
| 			}, | ||||
| 
 | ||||
| 			fetchLiked() { | ||||
| 				axios.get('/api/pixelfed/v2/discover/memories?type=liked') | ||||
| 				.then(res => { | ||||
| 					this.liked = res.data; | ||||
| 					this.likedLoaded = true; | ||||
| 				}); | ||||
| 			}, | ||||
| 
 | ||||
|         	toggleTab(idx) { | ||||
|         		if(idx == 1) { | ||||
|         			if(!this.likedLoaded) { | ||||
|         				this.fetchLiked(); | ||||
|         			} | ||||
|         		} | ||||
|         		this.tabIndex = idx; | ||||
|         	} | ||||
|         } | ||||
| 	} | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| 	.discover-my-memories { | ||||
| 		.bg-stellar { | ||||
| 			background: #7474BF; | ||||
| 			background: -webkit-linear-gradient(to right, #348AC7, #7474BF); | ||||
| 			background: linear-gradient(to right, #348AC7, #7474BF); | ||||
| 		} | ||||
| 		.font-default { | ||||
| 			font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | ||||
| 			letter-spacing: -0.7px; | ||||
| 		} | ||||
| 
 | ||||
| 		.active { | ||||
| 			font-weight: 700; | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -0,0 +1,149 @@ | |||
| <template> | ||||
| 	<div class="discover-serverfeeds-component"> | ||||
| 		<div class="container-fluid mt-3"> | ||||
| 
 | ||||
| 			<div class="row"> | ||||
| 				<div class="col-md-4 col-lg-3"> | ||||
| 					<sidebar :user="profile" /> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="col-md-6 col-lg-6"> | ||||
| 					<b-breadcrumb class="font-default" :items="breadcrumbItems"></b-breadcrumb> | ||||
| 
 | ||||
| 					<h1 class="font-default">Server Timelines</h1> | ||||
| 					<p class="font-default lead">Browse timelines of a specific instance</p> | ||||
| 
 | ||||
| 					<hr> | ||||
| 
 | ||||
| 					<b-spinner v-if="isLoading && !initialTab" /> | ||||
| 
 | ||||
| 					<status-card | ||||
| 						v-if="!isLoading" | ||||
| 						v-for="(post, idx) in feed" | ||||
| 						:key="'ti1:'+idx+':'+post.id" | ||||
| 						:profile="profile" | ||||
| 						:status="post" | ||||
| 						/> | ||||
| 
 | ||||
| 					<p v-if="!initialTab && !isLoading && feed.length == 0" class="lead">No posts found :(</p> | ||||
| 
 | ||||
| 					<div v-if="initialTab === true"> | ||||
| 						<p v-if="config.server.mode == 'allowlist'" class="lead">Select an instance from the menu</p> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="col-md-2 col-lg-3"> | ||||
| 					<div v-if="config.server.mode === 'allowlist'" class="nav flex-column nav-pills font-default"> | ||||
| 						<a | ||||
| 							v-for="(tag, idx) in domains" | ||||
| 							class="nav-link" | ||||
| 							:class="{ active: tagIndex == idx }" | ||||
| 							href="#" | ||||
| 							@click.prevent="toggleTag(idx)"> | ||||
| 							{{ tag }} | ||||
| 						</a> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
| import Drawer from './../partials/drawer.vue'; | ||||
| import Sidebar from './../partials/sidebar.vue'; | ||||
| import StatusCard from './../partials/TimelineStatus.vue'; | ||||
| 
 | ||||
| export default { | ||||
| 	components: { | ||||
| 		"drawer": Drawer, | ||||
| 		"sidebar": Sidebar, | ||||
| 		"status-card": StatusCard | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			isLoaded: false, | ||||
| 			isLoading: true, | ||||
| 			initialTab: true, | ||||
| 			config: {}, | ||||
| 			profile: window._sharedData.user, | ||||
| 			tagIndex: undefined, | ||||
| 			domains: [], | ||||
| 			feed: [], | ||||
| 			breadcrumbItems: [ | ||||
| 				{ | ||||
| 					text: 'Discover', | ||||
| 					href: '/i/web/discover' | ||||
| 				}, | ||||
| 				{ | ||||
| 					text: 'Server Timelines', | ||||
| 					active: true | ||||
| 				} | ||||
| 			] | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.fetchConfig(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		fetchConfig() { | ||||
| 			axios.get('/api/pixelfed/v2/discover/meta') | ||||
| 			.then(res => { | ||||
| 				this.config = res.data; | ||||
| 				if(this.config.server.enabled == false) { | ||||
| 					this.$router.push('/i/web/discover'); | ||||
| 				} | ||||
| 				if(this.config.server.mode === 'allowlist') { | ||||
| 					this.domains = this.config.server.domains.split(','); | ||||
| 				} | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		fetchFeed(domain) { | ||||
| 			this.isLoading = true; | ||||
| 			axios.get('/api/pixelfed/v2/discover/server-timeline', { | ||||
| 				params: { | ||||
| 					domain: domain | ||||
| 				} | ||||
| 			}).then(res => { | ||||
| 				this.feed = res.data; | ||||
| 				this.isLoading = false; | ||||
| 				this.isLoaded = true; | ||||
| 			}) | ||||
| 			.catch(err => { | ||||
| 				this.feed = []; | ||||
| 				this.tagIndex = null; | ||||
| 				this.isLoaded = true; | ||||
| 				this.isLoading = false; | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		toggleTag(tag) { | ||||
| 			this.initialTab = false; | ||||
| 			this.tagIndex = tag; | ||||
| 			this.fetchFeed(this.domains[tag]); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| 	.discover-serverfeeds-component { | ||||
| 		.bg-stellar { | ||||
| 			background: #7474BF; | ||||
| 			background: -webkit-linear-gradient(to right, #348AC7, #7474BF); | ||||
| 			background: linear-gradient(to right, #348AC7, #7474BF); | ||||
| 		} | ||||
| 		.font-default { | ||||
| 			font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | ||||
| 			letter-spacing: -0.7px; | ||||
| 		} | ||||
| 
 | ||||
| 		.active { | ||||
| 			font-weight: 700; | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -0,0 +1,280 @@ | |||
| <template> | ||||
| 	<div class="discover-admin-settings-component"> | ||||
| 		<div v-if="isLoaded" class="container-fluid mt-3"> | ||||
| 
 | ||||
| 			<div class="row"> | ||||
| 				<div class="col-md-4 col-lg-3"> | ||||
| 					<sidebar :user="profile" /> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="col-md-6 col-lg-6"> | ||||
| 					<b-breadcrumb class="font-default" :items="breadcrumbItems"></b-breadcrumb> | ||||
| 
 | ||||
| 					<h1 class="font-default">Discover Settings</h1> | ||||
| 					<!-- <p class="font-default lead">Browse timelines of a specific instance</p> --> | ||||
| 
 | ||||
| 					<hr> | ||||
| 
 | ||||
| 					<div class="card font-default shadow-none border"> | ||||
| 						<div class="card-header"> | ||||
| 							<p class="text-center font-weight-bold mb-0">Manage Features</p> | ||||
| 						</div> | ||||
| 						<div class="card-body"> | ||||
| 
 | ||||
| 							<div class="mb-2"> | ||||
| 								<b-form-checkbox size="lg" v-model="hashtags.enabled" name="check-button" switch class="font-weight-bold"> | ||||
| 									My Hashtags | ||||
| 								</b-form-checkbox> | ||||
| 								<p class="text-muted">Allow users to browse timelines of hashtags they follow</p> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<div class="mb-2"> | ||||
| 								<b-form-checkbox size="lg" v-model="memories.enabled" name="check-button" switch class="font-weight-bold"> | ||||
| 									My Memories | ||||
| 								</b-form-checkbox> | ||||
| 								<p class="text-muted">Allow users to access Memories, a timeline of posts they made or liked on this day in past years</p> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<div class="mb-2"> | ||||
| 								<b-form-checkbox size="lg" v-model="insights.enabled" name="check-button" switch class="font-weight-bold"> | ||||
| 									Account Insights | ||||
| 								</b-form-checkbox> | ||||
| 								<p class="text-muted">Allow users to access Account Insights, an overview of their account activity</p> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<div class="mb-2"> | ||||
| 								<b-form-checkbox size="lg" v-model="friends.enabled" name="check-button" switch class="font-weight-bold"> | ||||
| 									Find Friends | ||||
| 								</b-form-checkbox> | ||||
| 								<p class="text-muted">Allow users to access Find Friends, a directory of popular accounts</p> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<div> | ||||
| 								<b-form-checkbox size="lg" v-model="server.enabled" name="check-button" switch class="font-weight-bold"> | ||||
| 									Server Timelines | ||||
| 								</b-form-checkbox> | ||||
| 								<p class="text-muted">Allow users to access Server Timelines, a timeline of public posts from a specific instance</p> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<div v-if="server.enabled" class="card font-default shadow-none border my-3"> | ||||
| 						<div class="card-header"> | ||||
| 							<p class="text-center font-weight-bold mb-0">Manage Server Timelines</p> | ||||
| 						</div> | ||||
| 						<div class="card-body"> | ||||
| 							<div class="mb-2"> | ||||
| 								<b-form-group label="Server Mode"> | ||||
| 									<b-form-radio v-model="server.mode" value="all" disabled>Allow any instance (Not Recommended)</b-form-radio> | ||||
| 									<b-form-radio v-model="server.mode" value="allowlist">Limit by approved domains</b-form-radio> | ||||
| 								</b-form-group> | ||||
| 								<p class="text-muted">Set the allowed instances to browse</p> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<div v-if="server.mode == 'allowlist'"> | ||||
| 								<b-form-group label="Allowed Domains"> | ||||
| 									<b-form-textarea | ||||
| 										v-model="server.domains" | ||||
| 										placeholder="Add domains to allow here, separated by commas" | ||||
| 										rows="3" | ||||
| 										max-rows="6" | ||||
| 									></b-form-textarea> | ||||
| 								</b-form-group> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="col-md-2 col-lg-3"> | ||||
| 					<button v-if="hasChanged" class="btn btn-primary btn-block primary font-weight-bold" @click="saveFeatures">Save changes</button> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
| import Drawer from './../partials/drawer.vue'; | ||||
| import Sidebar from './../partials/sidebar.vue'; | ||||
| import StatusCard from './../partials/TimelineStatus.vue'; | ||||
| 
 | ||||
| export default { | ||||
| 	components: { | ||||
| 		"drawer": Drawer, | ||||
| 		"sidebar": Sidebar, | ||||
| 		"status-card": StatusCard | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			isLoaded: false, | ||||
| 			isLoading: true, | ||||
| 			profile: window._sharedData.user, | ||||
| 			breadcrumbItems: [ | ||||
| 				{ | ||||
| 					text: 'Discover', | ||||
| 					href: '/i/web/discover' | ||||
| 				}, | ||||
| 				{ | ||||
| 					text: 'Settings', | ||||
| 					active: true | ||||
| 				} | ||||
| 			], | ||||
| 			hasChanged: false, | ||||
| 			features: {}, | ||||
| 			original: undefined, | ||||
| 			hashtags: { enabled: undefined }, | ||||
| 			memories: { enabled: undefined }, | ||||
| 			insights: { enabled: undefined }, | ||||
| 			friends: { enabled: undefined }, | ||||
| 			server: { enabled: undefined, mode: 'allowlist', domains: '' }, | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	watch: { | ||||
| 		hashtags: { | ||||
| 			deep: true, | ||||
| 			handler: function(val, old) { | ||||
| 				this.updateFeatures('hashtags'); | ||||
| 			}, | ||||
| 		}, | ||||
| 
 | ||||
| 		memories: { | ||||
| 			deep: true, | ||||
| 			handler: function(val, old) { | ||||
| 				this.updateFeatures('memories'); | ||||
| 			}, | ||||
| 		}, | ||||
| 
 | ||||
| 		insights: { | ||||
| 			deep: true, | ||||
| 			handler: function(val, old) { | ||||
| 				this.updateFeatures('insights'); | ||||
| 			}, | ||||
| 		}, | ||||
| 
 | ||||
| 		friends: { | ||||
| 			deep: true, | ||||
| 			handler: function(val, old) { | ||||
| 				this.updateFeatures('friends'); | ||||
| 			}, | ||||
| 		}, | ||||
| 
 | ||||
| 		server: { | ||||
| 			deep: true, | ||||
| 			handler: function(val, old) { | ||||
| 				this.updateFeatures('server'); | ||||
| 			}, | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	beforeMount() { | ||||
| 		if(!this.profile.is_admin) { | ||||
| 			this.$router.push('/i/web/discover'); | ||||
| 		} | ||||
| 		this.fetchConfig(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		fetchConfig() { | ||||
| 			axios.get('/api/pixelfed/v2/discover/meta') | ||||
| 			.then(res => { | ||||
| 				this.original = res.data; | ||||
| 				this.storeOriginal(res.data); | ||||
| 			}) | ||||
| 		}, | ||||
| 
 | ||||
| 		storeOriginal(data) { | ||||
| 			this.friends.enabled = data.friends.enabled; | ||||
| 			this.hashtags.enabled = data.hashtags.enabled; | ||||
| 			this.insights.enabled = data.insights.enabled; | ||||
| 			this.memories.enabled = data.memories.enabled; | ||||
| 			this.server = { | ||||
| 				domains: data.server.domains, | ||||
| 				enabled: data.server.enabled, | ||||
| 				mode: data.server.mode | ||||
| 			}; | ||||
| 			this.isLoaded = true; | ||||
| 		}, | ||||
| 
 | ||||
| 		updateFeatures(id) { | ||||
| 			if(!this.isLoaded) { | ||||
| 				return; | ||||
| 			} | ||||
| 			let changed = false; | ||||
| 			if(this.friends.enabled !== this.original.friends.enabled) { | ||||
| 				changed = true; | ||||
| 			} | ||||
| 			if(this.hashtags.enabled !== this.original.hashtags.enabled) { | ||||
| 				changed = true; | ||||
| 			} | ||||
| 			if(this.insights.enabled !== this.original.insights.enabled) { | ||||
| 				changed = true; | ||||
| 			} | ||||
| 			if(this.memories.enabled !== this.original.memories.enabled) { | ||||
| 				changed = true; | ||||
| 			} | ||||
| 			if(this.server.enabled !== this.original.server.enabled) { | ||||
| 				changed = true; | ||||
| 			} | ||||
| 			if(this.server.domains !== this.original.server.domains) { | ||||
| 				changed = true; | ||||
| 			} | ||||
| 			if(this.server.mode !== this.original.server.mode) { | ||||
| 				changed = true; | ||||
| 			} | ||||
| 			// if(JSON.stringify(this.server) !== JSON.stringify(this.original.server)) { | ||||
| 			// 	changed = true; | ||||
| 			// } | ||||
| 			this.hasChanged = changed; | ||||
| 		}, | ||||
| 
 | ||||
| 		saveFeatures() { | ||||
| 			axios.post('/api/pixelfed/v2/discover/admin/features', { | ||||
| 				features: { | ||||
| 					friends: this.friends, | ||||
| 					hashtags: this.hashtags, | ||||
| 					insights: this.insights, | ||||
| 					memories: this.memories, | ||||
| 					server: this.server | ||||
| 				} | ||||
| 			}) | ||||
| 			.then(res => { | ||||
| 				// let data = { | ||||
| 				// 	friends: res.data.friends, | ||||
| 				// 	hashtags: res.data.hashtags, | ||||
| 				// 	insights: res.data.insights, | ||||
| 				// 	memories: res.data.memories, | ||||
| 				// 	server: res.data.server | ||||
| 				// } | ||||
| 				// this.original = data; | ||||
| 				this.server = res.data.server; | ||||
| 				this.$bvToast.toast('Successfully updated settings!', { | ||||
| 					title: 'Discover Settings', | ||||
| 					autoHideDelay: 5000, | ||||
| 					appendToast: true, | ||||
| 					variant: 'success' | ||||
| 				}) | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| 	.discover-admin-settings-component { | ||||
| 		.bg-stellar { | ||||
| 			background: #7474BF; | ||||
| 			background: -webkit-linear-gradient(to right, #348AC7, #7474BF); | ||||
| 			background: linear-gradient(to right, #348AC7, #7474BF); | ||||
| 		} | ||||
| 		.font-default { | ||||
| 			font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | ||||
| 			letter-spacing: -0.7px; | ||||
| 		} | ||||
| 
 | ||||
| 		.active { | ||||
| 			font-weight: 700; | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
		Ładowanie…
	
		Reference in New Issue
	
	 Daniel Supernault
						Daniel Supernault