浏览代码

Merge branch 'search'

sbkwgh 8 年之前
父节点
当前提交
679a0b6617

+ 23 - 4
src/App.vue

@@ -106,9 +106,22 @@
 						Login
 					</div>
 				</template>
-				<div class='search' tabindex='0'>
-					<input class='search__field' placeholder='Search this forum'>
-					<button class='search__button'><span class='fa fa-search'></span></button>
+				<div
+					class='search'
+					tabindex='0'
+					@keydown.enter='goToSearch'
+				>
+					<input
+						class='search__field'
+						placeholder='Search this forum'
+						v-model='searchField'
+					>
+					<button
+						class='search__button'
+						@click='goToSearch'
+					>
+						<span class='fa fa-search'></span>
+					</button>
 				</div>
 			</div>
 		</header>
@@ -161,7 +174,8 @@
 					}
 				},
 				loadingLogout: false,
-				ajaxErrorHandler: AjaxErrorHandler(this.$store)
+				ajaxErrorHandler: AjaxErrorHandler(this.$store),
+				searchField: ''
 			}
 		},
 		computed: {
@@ -187,6 +201,11 @@
 			}
 		},
 		methods: {
+			goToSearch () {
+				if(this.searchField.trim().length) {
+					this.$router.push("/search/" + this.searchField)
+				}
+			},
 			showAccountModalTab (index) {
 				this.showAccountModal = true
 				this.showAccountTab = index

+ 18 - 0
src/assets/scss/elementStyles.scss

@@ -199,4 +199,22 @@ b, strong {
 			font-size: 1.25rem;
 		}
 	}
+}
+
+.overlay_message {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	padding-right: 5rem;
+	font-size: 2rem;
+	user-select: none;
+	cursor: default;
+	transition: none;
+	color: $color__gray--darkest;
+
+	span {
+		font-size: 4rem;
+		color: $color__gray--darker;
+	}
 }

+ 2 - 2
src/components/AvatarIcon.vue

@@ -11,7 +11,7 @@
 						{{userLetter}}
 					</div>
 					<div class='avatar_icon__header_info'>
-						<span class='avatar_icon__username' @click='goToUser'>{{ajaxUser.username}}</span>
+						<span class='avatar_icon__username' @click.stop='goToUser'>{{ajaxUser.username}}</span>
 						<span class='avatar_icon__date'>Created: {{ajaxUser.createdAt | formatDate('date') }}</span>
 					</div>
 				</div>
@@ -26,7 +26,7 @@
 			class='avatar_icon__icon'
 			:class='{"avatar_icon__icon--small": size === "small"}'
 			:style='{ "background-color": userColor }'
-			@click='goToUser'
+			@click.stop='goToUser'
 		>
 			{{userLetter}}
 		</div>

+ 2 - 2
src/components/ReportPostModal.vue

@@ -14,8 +14,8 @@
 				<div class='report_post_modal--margin'>Select a reason for reporting this post below:</div>
 				<select-button :options='reportOptions' v-model='selectedOption' class='report_post_modal--margin'></select-button>
 				<div >
-					<button class='button button--modal' @click='setShowModal(false)'>Cancel</button>
-					<button class='button button--modal' @click='submitReport'>Submit</button>
+					<button class='button button--modal' @click.stop='setShowModal(false)'>Cancel</button>
+					<button class='button button--modal' @click.stop='submitReport'>Submit</button>
 				</div>
 			</div>
 		</modal-window>

+ 29 - 10
src/components/ThreadPost.vue

@@ -7,18 +7,20 @@
 		}'
 		@mouseenter='setPostFooterState(true)'
 		@mouseleave='setPostFooterState(false)'
+
+		@click='goToPost'
 	>
 		<span
 			class='post__remove_icon fa fa-check'
 			:class='{"post__remove_icon--show": showSelect && !post.removed}'
-			@click='toggleSelected'
+			@click.stop='toggleSelected'
 		></span>
 
 		<modal-window v-model='showShareModal'>
 			<div style='padding: 0rem 1rem 1rem 1rem;'>
 				<p>Copy this URL to share the post</p>
 				<fancy-input placeholder='Post URL' :value='postURL' width='100%'></fancy-input>
-				<button class='button button--modal' @click='setShareModalState(false)'>OK</button>
+				<button class='button button--modal' @click.stop='setShareModalState(false)'>OK</button>
 			</div>
 		</modal-window>
 
@@ -26,14 +28,14 @@
 
 		<div class='post__meta_data'>
 			<avatar-icon :user='post.User' class='post__avatar'></avatar-icon>
-			<div class='post__thread' v-if='showThread' @click='goToThread'>{{post.Thread.name}}</div>
+			<div class='post__thread' v-if='showThread' @click.stop='goToThread'>{{post.Thread.name}}</div>
 			<div class='post__user' v-else>{{username}}</div>
 			<replying-to
 				style='margin-right: 0.5rem;'
 				v-if='post.replyingToUsername'
 				:replyId='post.replyId'
 				:username='post.replyingToUsername'
-				@click='$emit("goToPost", post.replyId, true)'
+				@click.stop='$emit("goToPost", post.replyId, true)'
 			></replying-to>
 			<div class='post__date'>{{post.createdAt | formatDate('time|date', ', ')}}</div>
 		</div>
@@ -43,7 +45,7 @@
 				class='post__footer_group'
 			>
 				<div class='post__footer_sub_group'>
-					<heart-button :post='post'></heart-button>
+					<heart-button :post='post' v-if='showReply'></heart-button>
 				</div>
 				<div class='post__footer_sub_group' v-if='post.Replies.length'>
 					<span class='post__footer_sub_group__text post__footer_sub_group__text--replies'>replies</span>
@@ -52,17 +54,17 @@
 						:post='reply'
 						:hover='hover'
 						:first='index === 0'
-						@click='$emit("goToPost", reply.postNumber)'
+						@click.stop='$emit("goToPost", reply.postNumber)'
 					></post-reply>
 				</div>
 				
 			</div>
 			<div
 				class='post__footer_group'>
-				<div class='post__action post__share' @click='setShareModalState(true)'>Share</div>
+				<div class='post__action post__share' @click.stop='setShareModalState(true)'>Share</div>
 				<div
 					class='post__action'
-					@click='setShowReportPostModal(true)'
+					@click.stop='setShowReportPostModal(true)'
 					v-if='$store.state.username && !post.removed'
 				>
 					Report
@@ -70,7 +72,7 @@
 				<div
 					class='post__action post__reply'
 					v-if='$store.state.username && showReply'
-					@click='$emit("reply", post.id, username)'
+					@click.stop='$emit("reply", post.id, username)'
 				>
 					Reply
 				</div>
@@ -92,7 +94,14 @@
 
 	export default {
 		name: 'ThreadPost',
-		props: ['post', 'highlight', 'showReply', 'showThread', 'showSelect'],
+		props: [
+			'post',
+			'highlight',
+			'showReply',
+			'showThread',
+			'showSelect',
+			'clickForPost'
+		],
 		components: {
 			PostReply,
 			ModalWindow,
@@ -135,6 +144,16 @@
 			goToThread () {
 				this.$router.push(`/thread/${this.post.Thread.slug}/${this.post.Thread.id}`)
 			},
+			goToPost () {
+				if(this.clickForPost) {
+					this.$router.push(
+						'/thread/' +
+						this.post.Thread.slug + '/' +
+						this.post.Thread.id + '/' +
+						this.post.postNumber
+					)
+				}
+			},
 			toggleSelected () {
 				this.selected = !this.selected
 

+ 1 - 0
src/components/ThreadPostPlaceholder.vue

@@ -24,6 +24,7 @@
 	.post_placeholder {
 		position: relative;
 		border-bottom: thin solid $color__gray--primary;
+		border-radius: 0.25rem;
 		transition: background-color 0.5s;
 		margin: 0.5rem 0;
 

+ 1 - 19
src/components/routes/AdminModerationBannedUsers.vue

@@ -32,7 +32,7 @@
 			</tr>
 		</table>
 
-		<div class='admin_moderation__no_bans' v-else>
+		<div class='overlay_message' v-else>
 			<span class='fa fa-thumbs-up'></span>
 			No banned users
 		</div>
@@ -251,23 +251,5 @@
 			}
 			
 		}
-
-		@at-root #{&}__no_bans {
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			justify-content: center;
-			padding-top: 5rem;
-			font-size: 2rem;
-			user-select: none;
-			cursor: default;
-			transition: none;
-			color: $color__gray--darkest;
-
-			span {
-				font-size: 4rem;
-				color: $color__gray--darker;
-			}
-		}
 	}
 </style>

+ 1 - 19
src/components/routes/AdminModerationReports.vue

@@ -49,7 +49,7 @@
 				</div>
 			</div>
 		</div>
-		<div class='admin_moderation__no_reports' v-else>
+		<div class='overlay_message' v-else>
 			<span class='fa fa-thumbs-up'></span>
 			No user reports
 		</div>
@@ -165,24 +165,6 @@
 				border-radius: 0 0 0.25rem 0.25rem;
 			}
 		}
-		@at-root #{&}__no_reports {
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			justify-content: center;
-			padding-top: 5rem;
-			font-size: 2rem;
-			user-select: none;
-			cursor: default;
-			transition: none;
-			color: $color__gray--darkest;
-
-			span {
-				font-size: 4rem;
-				color: $color__gray--darker;
-			}
-		}
-
 
 		@at-root #{&}__report {
 			display: flex;

+ 1 - 19
src/components/routes/Index.vue

@@ -47,7 +47,7 @@
 				<thread-display v-for='thread in filteredThreads' :thread='thread'></thread-display>
 				<thread-display-placeholder v-for='n in nextThreadsCount' v-if='loading'></thread-display-placeholder>
 			</scroll-load>
-			<div v-else class='threads_main__threads thread--empty'>
+			<div v-else class='threads_main__threads overlay_message'>
 				<span class='fa fa-exclamation-circle'></span>
 				No threads or posts.
 			</div>
@@ -358,24 +358,6 @@
 			}
 		}
 
-		@at-root #{&}--empty {
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			justify-content: center;
-			padding-right: 5rem;
-			font-size: 2rem;
-			user-select: none;
-			cursor: default;
-			transition: none;
-			color: $color__gray--darkest;
-
-			span {
-				font-size: 4rem;
-				color: $color__gray--darker;
-			}
-		}
-
 		@at-root #{&}__section {
 			padding: 0 0.5rem;
 		}

+ 136 - 0
src/components/routes/Search.vue

@@ -0,0 +1,136 @@
+<template>
+	<div class='route_container'>
+		<h1>Search results for '{{$route.params.q}}'</h1>
+		<div class='search__results' v-if='posts && posts.length'>
+			<scroll-load
+				:loading='loading'
+				@loadNext='loadNextPage'
+			>
+				<thread-post
+					class='search__post'
+					v-for='post in posts'
+					:post='post'
+					:show-thread='true'
+					:click-for-post='true'
+				></thread-post>
+				<thread-post-placeholder
+					class='search__post'
+					v-if='loading'
+					v-for='n in next'
+				>
+			</scroll-load>
+		</div>
+		<div
+			class='overlay_message search__overlay_message'
+			v-else-if='posts && !posts.length'
+		>
+			<span class='fa fa-exclamation-circle'></span>
+			No results found
+		</div>
+		<div
+			class='overlay_message search__overlay_message search__overlay_message--loading'
+			v-else
+		>
+			<span>
+				<loading-icon dark='true'></loading-icon>
+			</span>
+			Loading...
+		</div>
+	</div>
+</template>
+
+<script>
+	import LoadingIcon from '../LoadingIcon'
+	import ScrollLoad from '../ScrollLoad'
+	import ThreadPost from '../ThreadPost'
+	import ThreadPostPlaceholder from '../ThreadPostPlaceholder'
+
+	import AjaxErrorHandler from '../../assets/js/errorHandler'
+
+	export default {
+		name: 'Search',
+		components: {
+			LoadingIcon,
+			ThreadPost,
+			ScrollLoad,
+			ThreadPostPlaceholder
+		},
+		data () {
+			return {
+				posts: null,
+				next: 0,
+				offset: 0,
+
+				loading: false
+			}
+		},
+		methods: {
+			getResults () {
+				this.axios
+					.get('/api/v1/search?q=' + this.$route.params.q)
+					.then(res => {
+						this.posts = res.data.posts
+						this.next = res.data.next
+						this.offset = res.data.offset
+					})
+					.catch(AjaxErrorHandler(this.$store))
+			},
+			loadNextPage () {
+				if(this.next === 0) return
+
+				this.loading = true
+
+				this.axios
+					.get(
+						`/api/v1/search?q=${this.$route.params.q}&offset=${this.offset}`
+					)
+					.then(res => {
+						this.posts.push(...res.data.posts)
+						this.next = res.data.next
+						this.offset = res.data.offset
+
+						this.loading = false
+					})
+					.catch(e => {
+						this.loading = false
+						AjaxErrorHandler(this.$store)(e)
+					})
+			}
+		},
+		watch: {
+			'$route.params': 'getResults'
+		},
+		mounted () {
+			this.$store.dispatch('setTitle', 'Search | ' + this.$route.params.q)
+			this.getResults()
+		}
+	}
+</script>
+
+<style lang='scss' scoped>
+	@import '../../assets/scss/variables.scss';
+
+	.search {
+		@at-root #{&}__post {
+			background-color: #fff;
+			padding-left: 0.75rem;
+			margin-bottom: 1rem;
+			border: none;
+			transition: box-shadow 0.2s;
+			@extend .shadow_border;
+
+			&:hover {
+				@extend .shadow_border--hover;
+			}
+
+		}
+
+		@at-root #{&}__overlay_message {
+			margin-top: 5rem;
+
+			@at-root #{&}--loading span {
+				margin-bottom: 1rem;
+			}
+		}
+	}
+</style>

+ 2 - 0
src/main.js

@@ -16,6 +16,7 @@ import P from './components/routes/P'
 import Start from './components/routes/Start'
 import Thread from './components/routes/Thread'
 import ThreadNew from './components/routes/ThreadNew'
+import Search from './components/routes/Search'
 
 import User from './components/routes/User'
 import UserPosts from './components/routes/UserPosts'
@@ -44,6 +45,7 @@ const router = new VueRouter({
 		{ path: '/thread/:slug/:id', component: Thread },
 		{ path: '/thread/:slug/:id/:post_number', name: 'thread-post', component: Thread },
 		{ path: '/thread/new', component: ThreadNew },
+		{ path: '/search/:q', component: Search },
 		{ path: '/user/:username', redirect: '/user/:username/posts', component: User, children: [
 			{ path: 'posts', component: UserPosts },
 			{ path: 'threads', component: UserThreads }