浏览代码

Search results + suggestions

NGPixel 9 年之前
父节点
当前提交
dca6b71610
共有 12 个文件被更改,包括 145 次插入37 次删除
  1. 1 1
      README.md
  2. 0 0
      assets/css/app.css
  3. 0 0
      assets/js/app.js
  4. 0 0
      assets/js/libs.js
  5. 4 20
      client/js/app.js
  6. 78 0
      client/js/components/search.js
  7. 5 1
      client/scss/layout/_base.scss
  8. 10 1
      client/scss/layout/_header.scss
  9. 0 1
      gulpfile.js
  10. 31 4
      models/search.js
  11. 4 3
      package.json
  12. 12 6
      views/common/header.pug

+ 1 - 1
README.md

@@ -27,6 +27,6 @@
 - [ ] Markdown Editor
 - [ ] Markdown Editor
 - [x] Navigation
 - [x] Navigation
 - [x] Parsing / Tree / Metadata
 - [x] Parsing / Tree / Metadata
-- [ ] Search
+- [x] Search
 - [x] UI
 - [x] UI
 - [x] View Entry Source
 - [x] View Entry Source

文件差异内容过多而无法显示
+ 0 - 0
assets/css/app.css


文件差异内容过多而无法显示
+ 0 - 0
assets/js/app.js


文件差异内容过多而无法显示
+ 0 - 0
assets/js/libs.js


+ 4 - 20
client/js/app.js

@@ -58,25 +58,7 @@ jQuery( document ).ready(function( $ ) {
 
 
 	var socket = io(ioHost);
 	var socket = io(ioHost);
 
 
-	var vueHeader = new Vue({
-		el: '#header-container',
-		data: {
-			searchq: '',
-			searchres: []
-		},
-		watch: {
-			searchq: (val, oldVal) => {
-				if(val.length >= 3) {
-					socket.emit('search', { terms: val }, (data) => {
-						vueHeader.$set('searchres', data);
-					});
-				}
-			}
-		},
-		methods: {
-			
-		}
-	});
+	//=include components/search.js
 
 
 	// ====================================
 	// ====================================
 	// Pages logic
 	// Pages logic
@@ -90,4 +72,6 @@ jQuery( document ).ready(function( $ ) {
 });
 });
 
 
 //=include helpers/form.js
 //=include helpers/form.js
-//=include helpers/pages.js
+//=include helpers/pages.js
+
+//=include components/alerts.js

+ 78 - 0
client/js/components/search.js

@@ -0,0 +1,78 @@
+"use strict";
+
+jQuery( document ).ready(function( $ ) {
+
+	if($('#search-input').length) {
+
+		$('#search-input').focus();
+
+		Vue.transition('slide', {
+			enterClass: 'slideInDown',
+			leaveClass: 'fadeOutUp'
+		});
+
+		$('.searchresults').css('display', 'block');
+
+		var vueHeader = new Vue({
+			el: '#header-container',
+			data: {
+				searchq: '',
+				searchres: [],
+				searchsuggest: [],
+				searchload: 0,
+				searchactive: false,
+				searchmoveidx: 0,
+				searchmovekey: '',
+				searchmovearr: []
+			},
+			watch: {
+				searchq: (val, oldVal) => {
+					searchmoveidx: 0;
+					if(val.length >= 3) {
+						vueHeader.searchactive = true;
+						vueHeader.searchload++;
+						socket.emit('search', { terms: val }, (data) => {
+							vueHeader.searchres = data.match;
+							vueHeader.searchsuggest = data.suggest;
+							if(vueHeader.searchload > 0) { vueHeader.searchload--; }
+						});
+					} else {
+						vueHeader.searchactive = false;
+						vueHeader.searchres = [];
+						vueHeader.searchsuggest = [];
+						vueHeader.searchload = 0;
+					}
+				},
+				searchmoveidx: (val, oldVal) => {
+
+				}
+			},
+			methods: {
+				useSuggestion: (sug) => {
+					vueHeader.searchq = sug;
+				},
+				closeSearch: () => {
+					vueHeader.searchq = '';
+					vueHeader.searchactive = false;
+				},
+				moveSelectSearch: () => {
+
+				},
+				moveDownSearch: () => {
+					if(vueHeader.searchmoveidx < vueHeader.searchmovearr) {
+						vueHeader.searchmoveidx++;
+					}
+				},
+				moveUpSearch: () => {
+					if(vueHeader.searchmoveidx > 0) {
+						vueHeader.searchmoveidx--;
+					}
+				}
+			}
+		});
+
+		$('main').on('click', vueHeader.closeSearch);
+
+	}
+
+});

+ 5 - 1
client/scss/layout/_base.scss

@@ -6,4 +6,8 @@ html {
 }
 }
 
 
 //$family-sans-serif: "Roboto", "Helvetica", "Arial", sans-serif;
 //$family-sans-serif: "Roboto", "Helvetica", "Arial", sans-serif;
-$family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+$family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+
+[v-cloak] {
+  display: none;
+}

+ 10 - 1
client/scss/layout/_header.scss

@@ -27,6 +27,11 @@ h2.nav-item {
 
 
 }
 }
 
 
+#search-input {
+	max-width: 300px;
+	width: 33vw;
+}
+
 .searchresults {
 .searchresults {
 	position: fixed;
 	position: fixed;
 	top: 45px;
 	top: 45px;
@@ -35,5 +40,9 @@ h2.nav-item {
 	margin: 0 auto;
 	margin: 0 auto;
 	width: 500px;
 	width: 500px;
 	z-index: 1;
 	z-index: 1;
-	//display: none;
+
+	&.slideInDown {
+		@include prefix(animation-duration, .6s);
+	}
+
 }
 }

+ 0 - 1
gulpfile.js

@@ -31,7 +31,6 @@ var paths = {
 		'./node_modules/lodash/lodash.min.js'
 		'./node_modules/lodash/lodash.min.js'
 	],
 	],
 	scriptapps: [
 	scriptapps: [
-		'./client/js/components/*.js',
 		'./client/js/app.js'
 		'./client/js/app.js'
 	],
 	],
 	scriptapps_watch: [
 	scriptapps_watch: [

+ 31 - 4
models/search.js

@@ -50,16 +50,43 @@ module.exports = {
 							.toLower()
 							.toLower()
 							.trim()
 							.trim()
 							.replace(/[^a-z0-9 ]/g, '')
 							.replace(/[^a-z0-9 ]/g, '')
-							.split(' ')
-							.filter((f) => { return !_.isEmpty(f); })
 							.value();
 							.value();
 
 
+		let arrTerms = _.chain(terms)
+										.split(' ')
+										.filter((f) => { return !_.isEmpty(f); })
+										.value();
+
+
 		return self._si.searchAsync({
 		return self._si.searchAsync({
 			query: {
 			query: {
-				AND: [{ '*': terms }]
+				AND: [{ '*': arrTerms }]
 			},
 			},
 			pageSize: 10
 			pageSize: 10
-		}).get('hits');
+		}).get('hits').then((hits) => {
+
+			if(hits.length < 5) {
+				return self._si.matchAsync({
+					beginsWith: terms,
+					threshold: 3,
+					limit: 5,
+					type: 'simple'
+				}).then((matches) => {
+
+					return {
+						match: hits,
+						suggest: matches
+					};
+
+				});
+			} else {
+				return {
+					match: hits,
+					suggest: []
+				};
+			}
+
+		});
 
 
 	},
 	},
 
 

+ 4 - 3
package.json

@@ -32,7 +32,7 @@
   "dependencies": {
   "dependencies": {
     "auto-load": "^2.1.0",
     "auto-load": "^2.1.0",
     "bcryptjs-then": "^1.0.1",
     "bcryptjs-then": "^1.0.1",
-    "bluebird": "^3.4.5",
+    "bluebird": "^3.4.6",
     "body-parser": "^1.15.2",
     "body-parser": "^1.15.2",
     "bson": "^0.5.4",
     "bson": "^0.5.4",
     "cheerio": "^0.22.0",
     "cheerio": "^0.22.0",
@@ -48,7 +48,7 @@
     "express-brute-loki": "^1.0.0",
     "express-brute-loki": "^1.0.0",
     "express-session": "^1.14.1",
     "express-session": "^1.14.1",
     "express-validator": "^2.20.8",
     "express-validator": "^2.20.8",
-    "farmhash": "^1.2.0",
+    "farmhash": "^1.2.1",
     "fs-extra": "^0.30.0",
     "fs-extra": "^0.30.0",
     "git-wrapper2-promise": "^0.2.9",
     "git-wrapper2-promise": "^0.2.9",
     "highlight.js": "^9.6.0",
     "highlight.js": "^9.6.0",
@@ -77,7 +77,8 @@
     "serve-favicon": "^2.3.0",
     "serve-favicon": "^2.3.0",
     "simplemde": "^1.11.2",
     "simplemde": "^1.11.2",
     "socket.io": "^1.4.8",
     "socket.io": "^1.4.8",
-    "validator": "^5.5.0",
+    "sticky-js": "^1.0.7",
+    "validator": "^5.6.0",
     "validator-as-promised": "^1.0.2",
     "validator-as-promised": "^1.0.2",
     "winston": "^2.2.0"
     "winston": "^2.2.0"
   },
   },

+ 12 - 6
views/common/header.pug

@@ -9,8 +9,9 @@
 					h1.title Wiki
 					h1.title Wiki
 		.nav-center
 		.nav-center
 			block rootNavCenter
 			block rootNavCenter
-				p.nav-item
-					input.input(type='text', v-model='searchq', debounce='500' placeholder='Search...', style= { 'max-width': '300px', width: '33vw' })
+				.nav-item
+					p.control(v-bind:class="{ 'is-loading': searchload > 0 }")
+						input.input#search-input(type='text', v-model='searchq', @keyup.esc='closeSearch', @keyup.down='moveDownSearch', @keyup.up='moveUpSearch', debounce='400', placeholder='Search...')
 		span.nav-toggle
 		span.nav-toggle
 			span
 			span
 			span
 			span
@@ -32,14 +33,19 @@
 							i.fa.fa-plus
 							i.fa.fa-plus
 						span Create
 						span Create
 
 
-	.box.searchresults
+	.box.searchresults.animated(v-show='searchactive', transition='slide', v-cloak, style={'display':'none'})
 		.menu
 		.menu
 			p.menu-label
 			p.menu-label
 				| Search Results
 				| Search Results
 			ul.menu-list
 			ul.menu-list
+				li(v-if="searchres.length === 0")
+					a: em No results matching your query
 				li(v-for='sres in searchres')
 				li(v-for='sres in searchres')
-					a(href='#') {{ sres.document.title }}
-			p.menu-label
-				| Do you mean...?
+					a(href='/{{ sres.document.entryPath }}', v-bind:class="{ 'is-active': searchmovekey === 'res.' + sres.document.entryPath }") {{ sres.document.title }}
+			p.menu-label(v-if='searchsuggest.length > 0')
+				| Did you mean...?
+			ul.menu-list(v-if='searchsuggest.length > 0')
+				li(v-for='sug in searchsuggest')
+					a(v-on:click="useSuggestion(sug)") {{ sug }}
 
 
 
 

部分文件因为文件数量过多而无法显示