Browse Source

Search results + suggestions

pull/1/head
NGPixel 8 years ago
parent
commit
dca6b71610
12 changed files with 171 additions and 65 deletions
  1. 2
      README.md
  2. 2
      assets/css/app.css
  3. 2
      assets/js/app.js
  4. 50
      assets/js/libs.js
  5. 24
      client/js/app.js
  6. 78
      client/js/components/search.js
  7. 6
      client/scss/layout/_base.scss
  8. 11
      client/scss/layout/_header.scss
  9. 1
      gulpfile.js
  10. 35
      models/search.js
  11. 7
      package.json
  12. 18
      views/common/header.pug

2
README.md

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

2
assets/css/app.css
File diff suppressed because it is too large
View File

2
assets/js/app.js
File diff suppressed because it is too large
View File

50
assets/js/libs.js
File diff suppressed because it is too large
View File

24
client/js/app.js

@ -58,25 +58,7 @@ jQuery( document ).ready(function( $ ) {
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
@ -90,4 +72,6 @@ jQuery( document ).ready(function( $ ) {
});
//=include helpers/form.js
//=include helpers/pages.js
//=include helpers/pages.js
//=include components/alerts.js

78
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);
}
});

6
client/scss/layout/_base.scss

@ -6,4 +6,8 @@ html {
}
//$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;
}

11
client/scss/layout/_header.scss

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

1
gulpfile.js

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

35
models/search.js

@ -50,16 +50,43 @@ module.exports = {
.toLower()
.trim()
.replace(/[^a-z0-9 ]/g, '')
.split(' ')
.filter((f) => { return !_.isEmpty(f); })
.value();
let arrTerms = _.chain(terms)
.split(' ')
.filter((f) => { return !_.isEmpty(f); })
.value();
return self._si.searchAsync({
query: {
AND: [{ '*': terms }]
AND: [{ '*': arrTerms }]
},
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: []
};
}
});
},

7
package.json

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

18
views/common/header.pug

@ -9,8 +9,9 @@
h1.title Wiki
.nav-center
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
span
@ -32,14 +33,19 @@
i.fa.fa-plus
span Create
.box.searchresults
.box.searchresults.animated(v-show='searchactive', transition='slide', v-cloak, style={'display':'none'})
.menu
p.menu-label
| Search Results
ul.menu-list
li(v-if="searchres.length === 0")
a: em No results matching your query
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 }}
Loading…
Cancel
Save