mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
added ancestor
This commit is contained in:
parent
8ce2afff8a
commit
9ede77aead
@ -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]}"`);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user