diff --git a/src/public/app/widgets/search_options/ancestor.js b/src/public/app/widgets/search_options/ancestor.js
index add442990..9372e52fa 100644
--- a/src/public/app/widgets/search_options/ancestor.js
+++ b/src/public/app/widgets/search_options/ancestor.js
@@ -3,12 +3,43 @@ import noteAutocompleteService from "../../services/note_autocomplete.js";
const TPL = `
-
- Ancestor:
- |
-
-
-
+
+
+ Ancestor:
+
+
+
+
+ depth:
+
+
|
@@ -27,6 +58,7 @@ export default class Ancestor extends AbstractSearchOption {
doRender() {
const $option = $(TPL);
const $ancestor = $option.find('.ancestor');
+ const $ancestorDepth = $option.find('.ancestor-depth');
noteAutocompleteService.initNoteAutocomplete($ancestor);
$ancestor.on('autocomplete:closed', async () => {
@@ -37,12 +69,35 @@ export default class Ancestor extends AbstractSearchOption {
}
});
+ $ancestorDepth.on('change', async () => {
+ const ancestorDepth = $ancestorDepth.val();
+
+ if (ancestorDepth) {
+ await this.setAttribute('label', 'ancestorDepth', ancestorDepth);
+ }
+ else {
+ await this.deleteAttribute('label', 'ancestorDepth');
+ }
+ });
+
const ancestorNoteId = this.note.getRelationValue('ancestor');
if (ancestorNoteId !== 'root') {
$ancestor.setNote(ancestorNoteId);
}
+ const ancestorDepth = this.note.getLabelValue('ancestorDepth');
+
+ if (ancestorDepth) {
+ $ancestorDepth.val(ancestorDepth);
+ }
+
return $option;
}
+
+ async deleteOption() {
+ await this.deleteAttribute('label', 'ancestorDepth');
+
+ await super.deleteOption();
+ }
}
diff --git a/src/routes/api/search.js b/src/routes/api/search.js
index 572518a0a..d15916c10 100644
--- a/src/routes/api/search.js
+++ b/src/routes/api/search.js
@@ -18,6 +18,7 @@ async function search(note) {
const searchContext = new SearchContext({
fastSearch: note.hasLabel('fastSearch'),
ancestorNoteId: note.getRelationValue('ancestor'),
+ ancestorDepth: note.getLabelValue('ancestorDepth'),
includeArchivedNotes: note.hasLabel('includeArchivedNotes'),
orderBy: note.getLabelValue('orderBy'),
orderDirection: note.getLabelValue('orderDirection'),
diff --git a/src/services/note_cache/entities/note.js b/src/services/note_cache/entities/note.js
index 4ea6dca0e..2ff63f715 100644
--- a/src/services/note_cache/entities/note.js
+++ b/src/services/note_cache/entities/note.js
@@ -368,6 +368,20 @@ class Note {
return arr;
}
+ getDistanceToAncestor(ancestorNoteId) {
+ if (this.noteId === ancestorNoteId) {
+ return 0;
+ }
+
+ let minDistance = 999_999;
+
+ for (const parent of this.parents) {
+ minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1);
+ }
+
+ return minDistance;
+ }
+
decrypt() {
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
this.title = protectedSessionService.decryptString(this.title);
diff --git a/src/services/search/expressions/ancestor.js b/src/services/search/expressions/ancestor.js
index 360b71a40..086da4833 100644
--- a/src/services/search/expressions/ancestor.js
+++ b/src/services/search/expressions/ancestor.js
@@ -6,10 +6,11 @@ const log = require('../../log');
const noteCache = require('../../note_cache/note_cache');
class AncestorExp extends Expression {
- constructor(ancestorNoteId) {
+ constructor(ancestorNoteId, ancestorDepth) {
super();
this.ancestorNoteId = ancestorNoteId;
+ this.ancestorDepthComparator = this.getComparator(ancestorDepth);
}
execute(inputNoteSet, executionContext) {
@@ -21,7 +22,45 @@ class AncestorExp extends Expression {
return new NoteSet([]);
}
- return new NoteSet(ancestorNote.subtreeNotes).intersection(inputNoteSet);
+ const subTreeNoteSet = new NoteSet(ancestorNote.subtreeNotes).intersection(inputNoteSet);
+
+ if (!this.ancestorDepthComparator) {
+ return subTreeNoteSet;
+ }
+
+ const depthConformingNoteSet = new NoteSet([]);
+
+ for (const note of subTreeNoteSet.notes) {
+ const distance = note.getDistanceToAncestor(ancestorNote.noteId);
+
+ if (this.ancestorDepthComparator(distance)) {
+ depthConformingNoteSet.add(note);
+ }
+ }
+
+ return depthConformingNoteSet;
+ }
+
+ getComparator(depthCondition) {
+ if (!depthCondition) {
+ return null;
+ }
+
+ const comparedDepth = parseInt(depthCondition.substr(2));
+
+ if (depthCondition.startsWith("eq")) {
+ return depth => depth === comparedDepth;
+ }
+ else if (depthCondition.startsWith("gt")) {
+ return depth => depth > comparedDepth;
+ }
+ else if (depthCondition.startsWith("lt")) {
+ return depth => depth < comparedDepth;
+ }
+ else {
+ log.error(`Unrecognized depth condition value ${depthCondition}`);
+ return null;
+ }
}
}
diff --git a/src/services/search/search_context.js b/src/services/search/search_context.js
index d64c4edb3..01691e472 100644
--- a/src/services/search/search_context.js
+++ b/src/services/search/search_context.js
@@ -4,6 +4,7 @@ class SearchContext {
constructor(params = {}) {
this.fastSearch = !!params.fastSearch;
this.ancestorNoteId = params.ancestorNoteId;
+ this.ancestorDepth = params.ancestorDepth;
this.includeArchivedNotes = !!params.includeArchivedNotes;
this.orderBy = params.orderBy;
this.orderDirection = params.orderDirection;
diff --git a/src/services/search/services/parse.js b/src/services/search/services/parse.js
index a8aa2a32f..1cf0982ac 100644
--- a/src/services/search/services/parse.js
+++ b/src/services/search/services/parse.js
@@ -410,7 +410,7 @@ function getExpression(tokens, searchContext, level = 0) {
function parse({fulltextTokens, expressionTokens, searchContext}) {
let exp = AndExp.of([
searchContext.includeArchivedNotes ? null : new PropertyComparisonExp(searchContext, "isarchived", buildComparator("=", "false")),
- searchContext.ancestorNoteId ? new AncestorExp(searchContext.ancestorNoteId) : null,
+ searchContext.ancestorNoteId ? new AncestorExp(searchContext.ancestorNoteId, searchContext.ancestorDepth) : null,
getFulltext(fulltextTokens, searchContext),
getExpression(expressionTokens, searchContext)
]);
| |