mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	added ancestor
This commit is contained in:
		| @@ -3,6 +3,7 @@ const Note = require('../src/services/note_cache/entities/note'); | |||||||
| const Branch = require('../src/services/note_cache/entities/branch'); | const Branch = require('../src/services/note_cache/entities/branch'); | ||||||
| const Attribute = require('../src/services/note_cache/entities/attribute'); | const Attribute = require('../src/services/note_cache/entities/attribute'); | ||||||
| const ParsingContext = require('../src/services/search/parsing_context'); | const ParsingContext = require('../src/services/search/parsing_context'); | ||||||
|  | const dateUtils = require('../src/services/date_utils'); | ||||||
| const noteCache = require('../src/services/note_cache/note_cache'); | const noteCache = require('../src/services/note_cache/note_cache'); | ||||||
| const randtoken = require('rand-token').generator({source: 'crypto'}); | const randtoken = require('rand-token').generator({source: 'crypto'}); | ||||||
|  |  | ||||||
| @@ -131,6 +132,48 @@ describe("Search", () => { | |||||||
|         expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy(); |         expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     it("smart date comparisons", async () => { | ||||||
|  |         // dates should not be coerced into numbers which would then give wrong numbers | ||||||
|  |  | ||||||
|  |         rootNote | ||||||
|  |             .child(note("My note") | ||||||
|  |                 .label('year', new Date().getFullYear().toString()) | ||||||
|  |                 .label('month', dateUtils.localNowDate().substr(0, 7)) | ||||||
|  |                 .label('date', dateUtils.localNowDate()) | ||||||
|  |                 .label('dateTime', dateUtils.localNowDateTime()) | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |         const parsingContext = new ParsingContext(); | ||||||
|  |  | ||||||
|  |         async function test(query, expectedResultCount) { | ||||||
|  |             const searchResults = await searchService.findNotesWithQuery(query, parsingContext); | ||||||
|  |             expect(searchResults.length).toEqual(expectedResultCount); | ||||||
|  |  | ||||||
|  |             if (expectedResultCount === 1) { | ||||||
|  |                 expect(findNoteByTitle(searchResults, "My note")).toBeTruthy(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         await test("#year = YEAR", 1); | ||||||
|  |         await test("#year >= YEAR", 1); | ||||||
|  |         await test("#year <= YEAR", 1); | ||||||
|  |         await test("#year < YEAR+1", 1); | ||||||
|  |         await test("#year > YEAR+1", 0); | ||||||
|  |  | ||||||
|  |         await test("#month = MONTH", 1); | ||||||
|  |  | ||||||
|  |         await test("#date = TODAY", 1); | ||||||
|  |         await test("#date > TODAY", 0); | ||||||
|  |         await test("#date > TODAY-1", 1); | ||||||
|  |         await test("#date < TODAY+1", 1); | ||||||
|  |         await test("#date < 'TODAY + 1'", 1); | ||||||
|  |  | ||||||
|  |         await test("#dateTime <= NOW+10", 1); | ||||||
|  |         await test("#dateTime < NOW-10", 0); | ||||||
|  |         await test("#dateTime >= NOW-10", 1); | ||||||
|  |         await test("#dateTime < NOW-10", 0); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     it("logical or", async () => { |     it("logical or", async () => { | ||||||
|         rootNote |         rootNote | ||||||
|             .child(note("Europe") |             .child(note("Europe") | ||||||
| @@ -217,6 +260,29 @@ describe("Search", () => { | |||||||
|         expect(findNoteByTitle(searchResults, "Prague")).toBeTruthy(); |         expect(findNoteByTitle(searchResults, "Prague")).toBeTruthy(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     it("filter by note's ancestor", async () => { | ||||||
|  |         rootNote | ||||||
|  |             .child(note("Europe") | ||||||
|  |                 .child(note("Austria")) | ||||||
|  |                 .child(note("Czech Republic") | ||||||
|  |                     .child(note("Prague").label('city'))) | ||||||
|  |             ) | ||||||
|  |             .child(note("Asia") | ||||||
|  |                 .child(note('Taiwan') | ||||||
|  |                     .child(note('Taipei').label('city'))) | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |         const parsingContext = new ParsingContext(); | ||||||
|  |  | ||||||
|  |         let searchResults = await searchService.findNotesWithQuery('#city AND note.ancestors.title = Europe', parsingContext); | ||||||
|  |         expect(searchResults.length).toEqual(1); | ||||||
|  |         expect(findNoteByTitle(searchResults, "Prague")).toBeTruthy(); | ||||||
|  |  | ||||||
|  |         searchResults = await searchService.findNotesWithQuery('#city AND note.ancestors.title = Asia', parsingContext); | ||||||
|  |         expect(searchResults.length).toEqual(1); | ||||||
|  |         expect(findNoteByTitle(searchResults, "Taipei")).toBeTruthy(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     it("filter by note's child", async () => { |     it("filter by note's child", async () => { | ||||||
|         rootNote |         rootNote | ||||||
|             .child(note("Europe") |             .child(note("Europe") | ||||||
| @@ -411,7 +477,7 @@ class NoteBuilder { | |||||||
|         this.note = note; |         this.note = note; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     label(name, value, isInheritable = false) { |     label(name, value = '', isInheritable = false) { | ||||||
|         new Attribute(noteCache, { |         new Attribute(noteCache, { | ||||||
|             attributeId: id(), |             attributeId: id(), | ||||||
|             noteId: this.note.noteId, |             noteId: this.note.noteId, | ||||||
|   | |||||||
| @@ -53,6 +53,9 @@ class Note { | |||||||
|         if (protectedSessionService.isProtectedSessionAvailable()) { |         if (protectedSessionService.isProtectedSessionAvailable()) { | ||||||
|             this.decrypt(); |             this.decrypt(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         /** @param {Note[]|null} */ | ||||||
|  |         this.ancestorCache = null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @return {Attribute[]} */ |     /** @return {Attribute[]} */ | ||||||
| @@ -164,6 +167,7 @@ class Note { | |||||||
|  |  | ||||||
|         this.attributeCache = null; |         this.attributeCache = null; | ||||||
|         this.inheritableAttributeCache = null; |         this.inheritableAttributeCache = null; | ||||||
|  |         this.ancestorCache = null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     invalidateSubtreeCaches() { |     invalidateSubtreeCaches() { | ||||||
| @@ -258,6 +262,29 @@ class Note { | |||||||
|         return this.attributes.length; |         return this.attributes.length; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     get ancestors() { | ||||||
|  |         if (!this.ancestorCache) { | ||||||
|  |             const noteIds = new Set(); | ||||||
|  |             this.ancestorCache = []; | ||||||
|  |  | ||||||
|  |             for (const parent of this.parents) { | ||||||
|  |                 if (!noteIds.has(parent.noteId)) { | ||||||
|  |                     this.ancestorCache.push(parent); | ||||||
|  |                     noteIds.add(parent.noteId); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 for (const ancestorNote of parent.ancestors) { | ||||||
|  |                     if (!noteIds.has(ancestorNote.noteId)) { | ||||||
|  |                         this.ancestorCache.push(ancestorNote); | ||||||
|  |                         noteIds.add(ancestorNote.noteId); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return this.ancestorCache; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** @return {Note[]} - returns only notes which are templated, does not include their subtrees |     /** @return {Note[]} - returns only notes which are templated, does not include their subtrees | ||||||
|      *                     in effect returns notes which are influenced by note's non-inheritable attributes */ |      *                     in effect returns notes which are influenced by note's non-inheritable attributes */ | ||||||
|     get templatedNotes() { |     get templatedNotes() { | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								src/services/search/expressions/descendant_of.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/services/search/expressions/descendant_of.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | const Expression = require('./expression'); | ||||||
|  | const NoteSet = require('../note_set'); | ||||||
|  |  | ||||||
|  | class DescendantOfExp extends Expression { | ||||||
|  |     constructor(subExpression) { | ||||||
|  |         super(); | ||||||
|  |  | ||||||
|  |         this.subExpression = subExpression; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     execute(inputNoteSet, searchContext) { | ||||||
|  |         const resNoteSet = new NoteSet(); | ||||||
|  |  | ||||||
|  |         for (const note of inputNoteSet.notes) { | ||||||
|  |             const subInputNoteSet = new NoteSet(note.ancestors); | ||||||
|  |  | ||||||
|  |             const subResNoteSet = this.subExpression.execute(subInputNoteSet, searchContext); | ||||||
|  |  | ||||||
|  |             if (subResNoteSet.notes.length > 0) { | ||||||
|  |                 resNoteSet.add(note); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return resNoteSet; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = DescendantOfExp; | ||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| class NoteSet { | class NoteSet { | ||||||
|     constructor(notes = []) { |     constructor(notes = []) { | ||||||
|  |         /** @type {Note[]} */ | ||||||
|         this.notes = notes; |         this.notes = notes; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ const AndExp = require('./expressions/and'); | |||||||
| const OrExp = require('./expressions/or'); | const OrExp = require('./expressions/or'); | ||||||
| const NotExp = require('./expressions/not'); | const NotExp = require('./expressions/not'); | ||||||
| const ChildOfExp = require('./expressions/child_of'); | const ChildOfExp = require('./expressions/child_of'); | ||||||
|  | const DescendantOfExp = require('./expressions/descendant_of'); | ||||||
| const ParentOfExp = require('./expressions/parent_of'); | const ParentOfExp = require('./expressions/parent_of'); | ||||||
| const RelationWhereExp = require('./expressions/relation_where'); | const RelationWhereExp = require('./expressions/relation_where'); | ||||||
| const PropertyComparisonExp = require('./expressions/property_comparison'); | const PropertyComparisonExp = require('./expressions/property_comparison'); | ||||||
| @@ -64,6 +65,12 @@ function getExpression(tokens, parsingContext) { | |||||||
|             return new ParentOfExp(parseNoteProperty()); |             return new ParentOfExp(parseNoteProperty()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (tokens[i] === 'ancestors') { | ||||||
|  |             i += 1; | ||||||
|  |  | ||||||
|  |             return new DescendantOfExp(parseNoteProperty()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (tokens[i] === 'labels') { |         if (tokens[i] === 'labels') { | ||||||
|             if (tokens[i + 1] !== '.') { |             if (tokens[i + 1] !== '.') { | ||||||
|                 parsingContext.addError(`Expected "." to separate field path, god "${tokens[i + 1]}"`); |                 parsingContext.addError(`Expected "." to separate field path, god "${tokens[i + 1]}"`); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user