Search results picker + create/update index update
This commit is contained in:
parent
dca6b71610
commit
48e2afa5c0
22
agent.js
22
agent.js
@ -68,6 +68,7 @@ var job = new cron({
|
|||||||
winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)');
|
winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
winston.info('[AGENT] Running all jobs...');
|
||||||
jobIsBusy = true;
|
jobIsBusy = true;
|
||||||
|
|
||||||
// Prepare async job collector
|
// Prepare async job collector
|
||||||
@ -87,6 +88,10 @@ var job = new cron({
|
|||||||
//-> Stream all documents
|
//-> Stream all documents
|
||||||
|
|
||||||
let cacheJobs = [];
|
let cacheJobs = [];
|
||||||
|
let jobCbStreamDocs_resolve = null,
|
||||||
|
jobCbStreamDocs = new Promise((resolve, reject) => {
|
||||||
|
jobCbStreamDocs_resolve = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
fs.walk(repoPath).on('data', function (item) {
|
fs.walk(repoPath).on('data', function (item) {
|
||||||
if(path.extname(item.path) === '.md') {
|
if(path.extname(item.path) === '.md') {
|
||||||
@ -113,15 +118,10 @@ var job = new cron({
|
|||||||
|
|
||||||
}).then((fileStatus) => {
|
}).then((fileStatus) => {
|
||||||
|
|
||||||
//-> Update search index
|
//-> Update cache and search index
|
||||||
|
|
||||||
if(fileStatus !== 'active') {
|
if(fileStatus !== 'active') {
|
||||||
return entries.fetchIndexableVersion(entryPath).then((content) => {
|
return entries.updateCache(entryPath);
|
||||||
ws.emit('searchAdd', {
|
|
||||||
auth: WSInternalKey,
|
|
||||||
content
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -131,9 +131,11 @@ var job = new cron({
|
|||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}).on('end', () => {
|
||||||
|
jobCbStreamDocs_resolve(Promise.all(cacheJobs));
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all(cacheJobs);
|
return jobCbStreamDocs;
|
||||||
|
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
@ -143,7 +145,7 @@ var job = new cron({
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
Promise.all(jobs).then(() => {
|
Promise.all(jobs).then(() => {
|
||||||
winston.info('[AGENT] All jobs completed successfully! Going to sleep for now...');
|
winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.');
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
winston.error('[AGENT] One or more jobs have failed: ', err);
|
winston.error('[AGENT] One or more jobs have failed: ', err);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
@ -161,8 +163,8 @@ var job = new cron({
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
ws.on('connect', function () {
|
ws.on('connect', function () {
|
||||||
job.start();
|
|
||||||
winston.info('[AGENT] Background Agent started successfully! [RUNNING]');
|
winston.info('[AGENT] Background Agent started successfully! [RUNNING]');
|
||||||
|
job.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('connect_error', function () {
|
ws.on('connect_error', function () {
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -27,24 +27,32 @@ jQuery( document ).ready(function( $ ) {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchq: (val, oldVal) => {
|
searchq: (val, oldVal) => {
|
||||||
searchmoveidx: 0;
|
vueHeader.searchmoveidx = 0;
|
||||||
if(val.length >= 3) {
|
if(val.length >= 3) {
|
||||||
vueHeader.searchactive = true;
|
vueHeader.searchactive = true;
|
||||||
vueHeader.searchload++;
|
vueHeader.searchload++;
|
||||||
socket.emit('search', { terms: val }, (data) => {
|
socket.emit('search', { terms: val }, (data) => {
|
||||||
vueHeader.searchres = data.match;
|
vueHeader.searchres = data.match;
|
||||||
vueHeader.searchsuggest = data.suggest;
|
vueHeader.searchsuggest = data.suggest;
|
||||||
|
vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest);
|
||||||
if(vueHeader.searchload > 0) { vueHeader.searchload--; }
|
if(vueHeader.searchload > 0) { vueHeader.searchload--; }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
vueHeader.searchactive = false;
|
vueHeader.searchactive = false;
|
||||||
vueHeader.searchres = [];
|
vueHeader.searchres = [];
|
||||||
vueHeader.searchsuggest = [];
|
vueHeader.searchsuggest = [];
|
||||||
|
vueHeader.searchmovearr = [];
|
||||||
vueHeader.searchload = 0;
|
vueHeader.searchload = 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
searchmoveidx: (val, oldVal) => {
|
searchmoveidx: (val, oldVal) => {
|
||||||
|
if(val > 0) {
|
||||||
|
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1].document) ?
|
||||||
|
'res.' + vueHeader.searchmovearr[val - 1].document.entryPath :
|
||||||
|
'sug.' + vueHeader.searchmovearr[val - 1];
|
||||||
|
} else {
|
||||||
|
vueHeader.searchmovekey = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -53,13 +61,20 @@ jQuery( document ).ready(function( $ ) {
|
|||||||
},
|
},
|
||||||
closeSearch: () => {
|
closeSearch: () => {
|
||||||
vueHeader.searchq = '';
|
vueHeader.searchq = '';
|
||||||
vueHeader.searchactive = false;
|
|
||||||
},
|
},
|
||||||
moveSelectSearch: () => {
|
moveSelectSearch: () => {
|
||||||
|
if(vueHeader.searchmoveidx < 1) { return; }
|
||||||
|
let i = vueHeader.searchmoveidx - 1;
|
||||||
|
|
||||||
|
if(vueHeader.searchmovearr[i].document) {
|
||||||
|
window.location.assign('/' + vueHeader.searchmovearr[i].document.entryPath);
|
||||||
|
} else {
|
||||||
|
vueHeader.searchq = vueHeader.searchmovearr[i];
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
moveDownSearch: () => {
|
moveDownSearch: () => {
|
||||||
if(vueHeader.searchmoveidx < vueHeader.searchmovearr) {
|
if(vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
|
||||||
vueHeader.searchmoveidx++;
|
vueHeader.searchmoveidx++;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
top: 60px;
|
top: 60px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
width: 350px;
|
width: 350px;
|
||||||
z-index: 2;
|
z-index: 10;
|
||||||
text-shadow: 1px 1px 0 rgba(0,0,0,0.1);
|
text-shadow: 1px 1px 0 rgba(0,0,0,0.1);
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
|
@ -71,7 +71,7 @@ router.get('/create/*', (req, res, next) => {
|
|||||||
|
|
||||||
entries.exists(safePath).then((docExists) => {
|
entries.exists(safePath).then((docExists) => {
|
||||||
if(!docExists) {
|
if(!docExists) {
|
||||||
entries.getStarter(safePath).then((contents) => {
|
return entries.getStarter(safePath).then((contents) => {
|
||||||
|
|
||||||
let pageData = {
|
let pageData = {
|
||||||
markdown: contents,
|
markdown: contents,
|
||||||
@ -80,7 +80,9 @@ router.get('/create/*', (req, res, next) => {
|
|||||||
path: safePath
|
path: safePath
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return res.render('pages/create', { pageData });
|
res.render('pages/create', { pageData });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
throw new Error('Could not load starter content!');
|
throw new Error('Could not load starter content!');
|
||||||
|
@ -156,7 +156,7 @@ module.exports = {
|
|||||||
// Cache to disk
|
// Cache to disk
|
||||||
|
|
||||||
if(options.cache) {
|
if(options.cache) {
|
||||||
let cacheData = BSON.serialize(pageData, false, false, false);
|
let cacheData = BSON.serialize(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false);
|
||||||
return fs.writeFileAsync(cpath, cacheData).catch((err) => {
|
return fs.writeFileAsync(cpath, cacheData).catch((err) => {
|
||||||
winston.error('Unable to write to cache! Performance may be affected.');
|
winston.error('Unable to write to cache! Performance may be affected.');
|
||||||
return true;
|
return true;
|
||||||
@ -177,34 +177,6 @@ module.exports = {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a text version of a Markdown-formatted document
|
|
||||||
*
|
|
||||||
* @param {String} entryPath The entry path
|
|
||||||
* @return {String} Text-only version
|
|
||||||
*/
|
|
||||||
fetchIndexableVersion(entryPath) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
return self.fetchOriginal(entryPath, {
|
|
||||||
parseMarkdown: false,
|
|
||||||
parseMeta: true,
|
|
||||||
parseTree: false,
|
|
||||||
includeMarkdown: true,
|
|
||||||
includeParentInfo: true,
|
|
||||||
cache: false
|
|
||||||
}).then((pageData) => {
|
|
||||||
return {
|
|
||||||
entryPath,
|
|
||||||
meta: pageData.meta,
|
|
||||||
parent: pageData.parent || {},
|
|
||||||
text: mark.removeMarkdown(pageData.markdown)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse raw url path and make it safe
|
* Parse raw url path and make it safe
|
||||||
*
|
*
|
||||||
@ -314,13 +286,48 @@ module.exports = {
|
|||||||
return fs.statAsync(fpath).then((st) => {
|
return fs.statAsync(fpath).then((st) => {
|
||||||
if(st.isFile()) {
|
if(st.isFile()) {
|
||||||
return self.makePersistent(entryPath, contents).then(() => {
|
return self.makePersistent(entryPath, contents).then(() => {
|
||||||
return self.fetchOriginal(entryPath, {});
|
return self.updateCache(entryPath);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error('Entry does not exist!'));
|
return Promise.reject(new Error('Entry does not exist!'));
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return Promise.reject(new Error('Entry does not exist!'));
|
winston.error(err);
|
||||||
|
return Promise.reject(new Error('Failed to save document.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update local cache and search index
|
||||||
|
*
|
||||||
|
* @param {String} entryPath The entry path
|
||||||
|
* @return {Promise} Promise of the operation
|
||||||
|
*/
|
||||||
|
updateCache(entryPath) {
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
return self.fetchOriginal(entryPath, {
|
||||||
|
parseMarkdown: true,
|
||||||
|
parseMeta: true,
|
||||||
|
parseTree: true,
|
||||||
|
includeMarkdown: true,
|
||||||
|
includeParentInfo: true,
|
||||||
|
cache: true
|
||||||
|
}).then((pageData) => {
|
||||||
|
return {
|
||||||
|
entryPath,
|
||||||
|
meta: pageData.meta,
|
||||||
|
parent: pageData.parent || {},
|
||||||
|
text: mark.removeMarkdown(pageData.markdown)
|
||||||
|
};
|
||||||
|
}).then((content) => {
|
||||||
|
ws.emit('searchAdd', {
|
||||||
|
auth: WSInternalKey,
|
||||||
|
content
|
||||||
|
});
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -339,7 +346,7 @@ module.exports = {
|
|||||||
return self.exists(entryPath).then((docExists) => {
|
return self.exists(entryPath).then((docExists) => {
|
||||||
if(!docExists) {
|
if(!docExists) {
|
||||||
return self.makePersistent(entryPath, contents).then(() => {
|
return self.makePersistent(entryPath, contents).then(() => {
|
||||||
return self.fetchOriginal(entryPath, {});
|
return self.updateCache(entryPath);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error('Entry already exists!'));
|
return Promise.reject(new Error('Entry already exists!'));
|
||||||
|
@ -195,7 +195,9 @@ module.exports = {
|
|||||||
commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath;
|
commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath;
|
||||||
return self._git.add(gitFilePath);
|
return self._git.add(gitFilePath);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return self._git.commit(commitMsg);
|
return self._git.commit(commitMsg).catch((err) => {
|
||||||
|
if(_.includes(err.stdout, 'nothing to commit')) { return true; }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,21 @@ module.exports = {
|
|||||||
|
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
|
return self._si.searchAsync({
|
||||||
|
query: {
|
||||||
|
AND: [{ 'entryPath': [content.entryPath] }]
|
||||||
|
}
|
||||||
|
}).then((results) => {
|
||||||
|
|
||||||
|
if(results.totalHits > 0) {
|
||||||
|
let delIds = _.map(results.hits, 'id');
|
||||||
|
return self._si.delAsync(delIds);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}).then(() => {
|
||||||
|
|
||||||
return self._si.addAsync({
|
return self._si.addAsync({
|
||||||
entryPath: content.entryPath,
|
entryPath: content.entryPath,
|
||||||
title: content.meta.title,
|
title: content.meta.title,
|
||||||
@ -125,7 +140,7 @@ module.exports = {
|
|||||||
store: false
|
store: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'subtitle',
|
fieldName: 'parent',
|
||||||
searchable: false,
|
searchable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -135,7 +150,12 @@ module.exports = {
|
|||||||
store: false
|
store: false
|
||||||
}]
|
}]
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
winston.info('Entry ' + content.entryPath + ' added to index.');
|
winston.info('Entry ' + content.entryPath + ' added/updated to index.');
|
||||||
|
return true;
|
||||||
|
}).catch((err) => {
|
||||||
|
winston.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
winston.error(err);
|
winston.error(err);
|
||||||
});
|
});
|
||||||
|
27
server.js
27
server.js
@ -206,13 +206,32 @@ server.on('listening', () => {
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
var fork = require('child_process').fork,
|
var fork = require('child_process').fork,
|
||||||
libInternalAuth = require('./lib/internalAuth'),
|
libInternalAuth = require('./lib/internalAuth');
|
||||||
internalAuthKey = libInternalAuth.generateKey();
|
|
||||||
|
|
||||||
var wsSrv = fork('ws-server.js', [internalAuthKey]),
|
global.WSInternalKey = libInternalAuth.generateKey();
|
||||||
bgAgent = fork('agent.js', [internalAuthKey]);
|
|
||||||
|
var wsSrv = fork('ws-server.js', [WSInternalKey]),
|
||||||
|
bgAgent = fork('agent.js', [WSInternalKey]);
|
||||||
|
|
||||||
process.on('exit', (code) => {
|
process.on('exit', (code) => {
|
||||||
wsSrv.disconnect();
|
wsSrv.disconnect();
|
||||||
bgAgent.disconnect();
|
bgAgent.disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Connect to local WebSocket server
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
var wsClient = require('socket.io-client');
|
||||||
|
global.ws = wsClient('http://localhost:' + appconfig.wsPort, { reconnectionAttempts: 10 });
|
||||||
|
|
||||||
|
ws.on('connect', function () {
|
||||||
|
winston.info('[SERVER] Connected to WebSocket server successfully!');
|
||||||
|
});
|
||||||
|
ws.on('connect_error', function () {
|
||||||
|
winston.warn('[SERVER] Unable to connect to WebSocket server! Retrying...');
|
||||||
|
});
|
||||||
|
ws.on('reconnect_failed', function () {
|
||||||
|
winston.error('[SERVER] Failed to reconnect to WebSocket server too many times! Stopping...');
|
||||||
|
process.exit(1);
|
||||||
|
});
|
@ -11,7 +11,7 @@
|
|||||||
block rootNavCenter
|
block rootNavCenter
|
||||||
.nav-item
|
.nav-item
|
||||||
p.control(v-bind:class="{ 'is-loading': searchload > 0 }")
|
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...')
|
input.input#search-input(type='text', v-model='searchq', @keyup.esc='closeSearch', @keyup.down='moveDownSearch', @keyup.up='moveUpSearch', @keyup.enter='moveSelectSearch', debounce='400', placeholder='Search...')
|
||||||
span.nav-toggle
|
span.nav-toggle
|
||||||
span
|
span
|
||||||
span
|
span
|
||||||
@ -46,6 +46,6 @@
|
|||||||
| Did you mean...?
|
| Did you mean...?
|
||||||
ul.menu-list(v-if='searchsuggest.length > 0')
|
ul.menu-list(v-if='searchsuggest.length > 0')
|
||||||
li(v-for='sug in searchsuggest')
|
li(v-for='sug in searchsuggest')
|
||||||
a(v-on:click="useSuggestion(sug)") {{ sug }}
|
a(v-on:click="useSuggestion(sug)", v-bind:class="{ 'is-active': searchmovekey === 'sug.' + sug }") {{ sug }}
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,10 +31,10 @@ html
|
|||||||
|
|
||||||
body
|
body
|
||||||
#root
|
#root
|
||||||
include ./common/header
|
include ./common/header.pug
|
||||||
include ./common/alerts
|
include ./common/alerts.pug
|
||||||
main
|
main
|
||||||
block content
|
block content
|
||||||
include ./common/footer
|
include ./common/footer.pug
|
||||||
|
|
||||||
block outside
|
block outside
|
@ -1,4 +1,4 @@
|
|||||||
extends ../layout
|
extends ../layout.pug
|
||||||
|
|
||||||
block rootNavCenter
|
block rootNavCenter
|
||||||
h2.nav-item Create New Document
|
h2.nav-item Create New Document
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
extends ../layout
|
extends ../layout.pug
|
||||||
|
|
||||||
block rootNavCenter
|
block rootNavCenter
|
||||||
h2.nav-item= pageData.meta.title
|
h2.nav-item= pageData.meta.title
|
||||||
@ -24,4 +24,4 @@ block content
|
|||||||
#page-type-edit(data-entrypath=pageData.meta.path)
|
#page-type-edit(data-entrypath=pageData.meta.path)
|
||||||
textarea#mk-editor= pageData.markdown
|
textarea#mk-editor= pageData.markdown
|
||||||
|
|
||||||
include ../modals/edit
|
include ../modals/edit.pug
|
@ -1,4 +1,4 @@
|
|||||||
extends ../layout
|
extends ../layout.pug
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
extends ../layout
|
extends ../layout.pug
|
||||||
|
|
||||||
mixin tocMenu(ti)
|
mixin tocMenu(ti)
|
||||||
each node in ti
|
each node in ti
|
||||||
@ -61,4 +61,4 @@ block content
|
|||||||
.content.mkcontent
|
.content.mkcontent
|
||||||
!= pageData.html
|
!= pageData.html
|
||||||
|
|
||||||
include ../modals/create
|
include ../modals/create.pug
|
||||||
|
Loading…
Reference in New Issue
Block a user