mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 21:19:01 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/develop' into renovate/jsdom-26.x
This commit is contained in:
		
						commit
						f12057f799
					
				@ -1,12 +1,274 @@
 | 
				
			|||||||
// @ts-nocheck
 | 
					import AndExp from "../../src/services/search/expressions/and.js";
 | 
				
			||||||
// There are many issues with the types of the parser e.g. "parse" function returns "Expression"
 | 
					import AttributeExistsExp from "../../src/services/search/expressions/attribute_exists.js";
 | 
				
			||||||
// but we access properties like "subExpressions" which is not defined in the "Expression" class.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import Expression from "../../src/services/search/expressions/expression.js";
 | 
					import Expression from "../../src/services/search/expressions/expression.js";
 | 
				
			||||||
 | 
					import LabelComparisonExp from "../../src/services/search/expressions/label_comparison.js";
 | 
				
			||||||
 | 
					import NotExp from "../../src/services/search/expressions/not.js";
 | 
				
			||||||
 | 
					import NoteContentFulltextExp from "../../src/services/search/expressions/note_content_fulltext.js";
 | 
				
			||||||
 | 
					import NoteFlatTextExp from "../../src/services/search/expressions/note_flat_text.js";
 | 
				
			||||||
 | 
					import OrExp from "../../src/services/search/expressions/or.js";
 | 
				
			||||||
 | 
					import OrderByAndLimitExp from "../../src/services/search/expressions/order_by_and_limit.js";
 | 
				
			||||||
 | 
					import PropertyComparisonExp from "../../src/services/search/expressions/property_comparison.js";
 | 
				
			||||||
import SearchContext from "../../src/services/search/search_context.js";
 | 
					import SearchContext from "../../src/services/search/search_context.js";
 | 
				
			||||||
import parse from "../../src/services/search/services/parse.js";
 | 
					import { default as parseInternal, type ParseOpts } from "../../src/services/search/services/parse.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function tokens(toks: Array<string>, cur = 0): Array<any> {
 | 
					describe("Parser", () => {
 | 
				
			||||||
 | 
					    it("fulltext parser without content", () => {
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: tokens(["hello", "hi"]),
 | 
				
			||||||
 | 
					            expressionTokens: [],
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expectExpression(rootExp.subExpressions[0], PropertyComparisonExp);
 | 
				
			||||||
 | 
					        const orExp = expectExpression(rootExp.subExpressions[2], OrExp);
 | 
				
			||||||
 | 
					        const flatTextExp = expectExpression(orExp.subExpressions[0], NoteFlatTextExp);
 | 
				
			||||||
 | 
					        expect(flatTextExp.tokens).toEqual(["hello", "hi"]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("fulltext parser with content", () => {
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: tokens(["hello", "hi"]),
 | 
				
			||||||
 | 
					            expressionTokens: [],
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertIsArchived(rootExp.subExpressions[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const orExp = expectExpression(rootExp.subExpressions[2], OrExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const firstSub = expectExpression(orExp.subExpressions[0], NoteFlatTextExp);
 | 
				
			||||||
 | 
					        expect(firstSub.tokens).toEqual(["hello", "hi"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const secondSub = expectExpression(orExp.subExpressions[1], NoteContentFulltextExp);
 | 
				
			||||||
 | 
					        expect(secondSub.tokens).toEqual(["hello", "hi"]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("simple label comparison", () => {
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#mylabel", "=", "text"]),
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertIsArchived(rootExp.subExpressions[0]);
 | 
				
			||||||
 | 
					        const labelComparisonExp = expectExpression(rootExp.subExpressions[2], LabelComparisonExp);
 | 
				
			||||||
 | 
					        expect(labelComparisonExp.attributeType).toEqual("label");
 | 
				
			||||||
 | 
					        expect(labelComparisonExp.attributeName).toEqual("mylabel");
 | 
				
			||||||
 | 
					        expect(labelComparisonExp.comparator).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("simple attribute negation", () => {
 | 
				
			||||||
 | 
					        let rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#!mylabel"]),
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertIsArchived(rootExp.subExpressions[0]);
 | 
				
			||||||
 | 
					        let notExp = expectExpression(rootExp.subExpressions[2], NotExp);
 | 
				
			||||||
 | 
					        let attributeExistsExp = expectExpression(notExp.subExpression, AttributeExistsExp);
 | 
				
			||||||
 | 
					        expect(attributeExistsExp.attributeType).toEqual("label");
 | 
				
			||||||
 | 
					        expect(attributeExistsExp.attributeName).toEqual("mylabel");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["~!myrelation"]),
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertIsArchived(rootExp.subExpressions[0]);
 | 
				
			||||||
 | 
					        notExp = expectExpression(rootExp.subExpressions[2], NotExp);
 | 
				
			||||||
 | 
					        attributeExistsExp = expectExpression(notExp.subExpression, AttributeExistsExp);
 | 
				
			||||||
 | 
					        expect(attributeExistsExp.attributeType).toEqual("relation");
 | 
				
			||||||
 | 
					        expect(attributeExistsExp.attributeName).toEqual("myrelation");
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("simple label AND", () => {
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]),
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertIsArchived(rootExp.subExpressions[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const andExp = expectExpression(rootExp.subExpressions[2], AndExp);
 | 
				
			||||||
 | 
					        const [firstSub, secondSub] = expectSubexpressions(andExp, LabelComparisonExp, LabelComparisonExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(firstSub.attributeName).toEqual("first");
 | 
				
			||||||
 | 
					        expect(secondSub.attributeName).toEqual("second");
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("simple label AND without explicit AND", () => {
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]),
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertIsArchived(rootExp.subExpressions[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const andExp = expectExpression(rootExp.subExpressions[2], AndExp);
 | 
				
			||||||
 | 
					        const [firstSub, secondSub] = expectSubexpressions(andExp, LabelComparisonExp, LabelComparisonExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(firstSub.attributeName).toEqual("first");
 | 
				
			||||||
 | 
					        expect(secondSub.attributeName).toEqual("second");
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("simple label OR", () => {
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]),
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertIsArchived(rootExp.subExpressions[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const orExp = expectExpression(rootExp.subExpressions[2], OrExp);
 | 
				
			||||||
 | 
					        const [firstSub, secondSub] = expectSubexpressions(orExp, LabelComparisonExp, LabelComparisonExp);
 | 
				
			||||||
 | 
					        expect(firstSub.attributeName).toEqual("first");
 | 
				
			||||||
 | 
					        expect(secondSub.attributeName).toEqual("second");
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("fulltext and simple label", () => {
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: tokens(["hello"]),
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#mylabel", "=", "text"]),
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const [firstSub, _, thirdSub, fourth] = expectSubexpressions(rootExp, PropertyComparisonExp, undefined, OrExp, LabelComparisonExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(firstSub.propertyName).toEqual("isArchived");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const noteFlatTextExp = expectExpression(thirdSub.subExpressions[0], NoteFlatTextExp);
 | 
				
			||||||
 | 
					        expect(noteFlatTextExp.tokens).toEqual(["hello"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(fourth.attributeName).toEqual("mylabel");
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("label sub-expression", () => {
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]),
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertIsArchived(rootExp.subExpressions[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const orExp = expectExpression(rootExp.subExpressions[2], OrExp);
 | 
				
			||||||
 | 
					        const [firstSub, secondSub] = expectSubexpressions(orExp, LabelComparisonExp, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(firstSub.attributeName).toEqual("first");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const [firstSubSub, secondSubSub] = expectSubexpressions(secondSub, LabelComparisonExp, LabelComparisonExp);
 | 
				
			||||||
 | 
					        expect(firstSubSub.attributeName).toEqual("second");
 | 
				
			||||||
 | 
					        expect(secondSubSub.attributeName).toEqual("third");
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("label sub-expression without explicit operator", () => {
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]),
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertIsArchived(rootExp.subExpressions[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const andExp = expectExpression(rootExp.subExpressions[2], AndExp);
 | 
				
			||||||
 | 
					        const [firstSub, secondSub, thirdSub] = expectSubexpressions(andExp, AttributeExistsExp, OrExp, AttributeExistsExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(firstSub.attributeName).toEqual("first");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const [firstSubSub, secondSubSub] = expectSubexpressions(secondSub, AttributeExistsExp, AttributeExistsExp);
 | 
				
			||||||
 | 
					        expect(firstSubSub.attributeName).toEqual("second");
 | 
				
			||||||
 | 
					        expect(secondSubSub.attributeName).toEqual("third");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(thirdSub.attributeName).toEqual("fourth");
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("parses limit without order by", () => {
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: tokens(["hello", "hi"]),
 | 
				
			||||||
 | 
					            expressionTokens: [],
 | 
				
			||||||
 | 
					            searchContext: new SearchContext({ limit: 2 })
 | 
				
			||||||
 | 
					        }, OrderByAndLimitExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(rootExp.limit).toBe(2);
 | 
				
			||||||
 | 
					        expect(rootExp.subExpression).toBeInstanceOf(AndExp);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("Invalid expressions", () => {
 | 
				
			||||||
 | 
					    it("incomplete comparison", () => {
 | 
				
			||||||
 | 
					        const searchContext = new SearchContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parseInternal({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#first", "="]),
 | 
				
			||||||
 | 
					            searchContext
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(searchContext.error).toEqual('Misplaced or incomplete expression "="');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("comparison between labels is impossible", () => {
 | 
				
			||||||
 | 
					        let searchContext = new SearchContext();
 | 
				
			||||||
 | 
					        searchContext.originalQuery = "#first = #second";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parseInternal({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#first", "=", "#second"]),
 | 
				
			||||||
 | 
					            searchContext
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(searchContext.error).toEqual(`Error near token "#second" in "#first = #second", it's possible to compare with constant only.`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        searchContext = new SearchContext();
 | 
				
			||||||
 | 
					        searchContext.originalQuery = "#first = note.relations.second";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parseInternal({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["#first", "=", "note", ".", "relations", "second"]),
 | 
				
			||||||
 | 
					            searchContext
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(searchContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const rootExp = parse({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: [
 | 
				
			||||||
 | 
					                { token: "#first", inQuotes: false },
 | 
				
			||||||
 | 
					                { token: "=", inQuotes: false },
 | 
				
			||||||
 | 
					                { token: "#second", inQuotes: true }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            searchContext: new SearchContext()
 | 
				
			||||||
 | 
					        }, AndExp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertIsArchived(rootExp.subExpressions[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const labelComparisonExp = expectExpression(rootExp.subExpressions[2], LabelComparisonExp);
 | 
				
			||||||
 | 
					        expect(labelComparisonExp.attributeType).toEqual("label");
 | 
				
			||||||
 | 
					        expect(labelComparisonExp.attributeName).toEqual("first");
 | 
				
			||||||
 | 
					        expect(labelComparisonExp.comparator).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("searching by relation without note property", () => {
 | 
				
			||||||
 | 
					        const searchContext = new SearchContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parseInternal({
 | 
				
			||||||
 | 
					            fulltextTokens: [],
 | 
				
			||||||
 | 
					            expressionTokens: tokens(["~first", "=", "text", "-", "abc"]),
 | 
				
			||||||
 | 
					            searchContext
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(searchContext.error).toEqual('Relation can be compared only with property, e.g. ~relation.title=hello in ""');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ClassType<T extends Expression> = new (...args: any[]) => T;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function tokens(toks: (string | string[])[], cur = 0): Array<any> {
 | 
				
			||||||
    return toks.map((arg) => {
 | 
					    return toks.map((arg) => {
 | 
				
			||||||
        if (Array.isArray(arg)) {
 | 
					        if (Array.isArray(arg)) {
 | 
				
			||||||
            return tokens(arg, cur);
 | 
					            return tokens(arg, cur);
 | 
				
			||||||
@ -23,293 +285,71 @@ function tokens(toks: Array<string>, cur = 0): Array<any> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function assertIsArchived(exp: Expression) {
 | 
					function assertIsArchived(_exp: Expression) {
 | 
				
			||||||
    expect(exp.constructor.name).toEqual("PropertyComparisonExp");
 | 
					    const exp = expectExpression(_exp, PropertyComparisonExp);
 | 
				
			||||||
    expect(exp.propertyName).toEqual("isArchived");
 | 
					    expect(exp.propertyName).toEqual("isArchived");
 | 
				
			||||||
    expect(exp.operator).toEqual("=");
 | 
					    expect(exp.operator).toEqual("=");
 | 
				
			||||||
    expect(exp.comparedValue).toEqual("false");
 | 
					    expect(exp.comparedValue).toEqual("false");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Parser", () => {
 | 
					/**
 | 
				
			||||||
    it("fulltext parser without content", () => {
 | 
					 * Parses the corresponding {@link Expression} from plain text, while also expecting the resulting expression to be of the given type.
 | 
				
			||||||
        const rootExp = parse({
 | 
					 *
 | 
				
			||||||
            fulltextTokens: tokens(["hello", "hi"]),
 | 
					 * @param opts the options for parsing.
 | 
				
			||||||
            expressionTokens: [],
 | 
					 * @param type the expected type of the expression.
 | 
				
			||||||
            searchContext: new SearchContext({ excludeArchived: true })
 | 
					 * @returns the expression typecasted to the expected type.
 | 
				
			||||||
        });
 | 
					 */
 | 
				
			||||||
 | 
					function parse<T extends Expression>(opts: ParseOpts, type: ClassType<T>) {
 | 
				
			||||||
 | 
					    return expectExpression(parseInternal(opts), type);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					/**
 | 
				
			||||||
        expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
 | 
					 * Expects the given {@link Expression} to be of the given type.
 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
 | 
					 *
 | 
				
			||||||
        expect(rootExp.subExpressions[2].subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
 | 
					 * @param exp an instance of an {@link Expression}.
 | 
				
			||||||
        expect(rootExp.subExpressions[2].subExpressions[0].tokens).toEqual(["hello", "hi"]);
 | 
					 * @param type a type class such as {@link AndExp}, {@link OrExp}, etc.
 | 
				
			||||||
    });
 | 
					 * @returns the same expression typecasted to the expected type.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function expectExpression<T extends Expression>(exp: Expression, type: ClassType<T>) {
 | 
				
			||||||
 | 
					    expect(exp).toBeInstanceOf(type);
 | 
				
			||||||
 | 
					    return exp as T;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("fulltext parser with content", () => {
 | 
					/**
 | 
				
			||||||
        const rootExp = parse({
 | 
					 * For an {@link AndExp}, it goes through all its subexpressions (up to fourth) and checks their type and returns them as a typecasted array.
 | 
				
			||||||
            fulltextTokens: tokens(["hello", "hi"]),
 | 
					 * Each subexpression can have their own type.
 | 
				
			||||||
            expressionTokens: [],
 | 
					 *
 | 
				
			||||||
            searchContext: new SearchContext()
 | 
					 * @param exp the expression containing one or more subexpressions.
 | 
				
			||||||
        });
 | 
					 * @param firstType the type of the first subexpression.
 | 
				
			||||||
 | 
					 * @param secondType the type of the second subexpression.
 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					 * @param thirdType the type of the third subexpression.
 | 
				
			||||||
        assertIsArchived(rootExp.subExpressions[0]);
 | 
					 * @param fourthType the type of the fourth subexpression.
 | 
				
			||||||
 | 
					 * @returns an array of all the subexpressions (in order) typecasted to their expected type.
 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
 | 
					 */
 | 
				
			||||||
 | 
					function expectSubexpressions<FirstT extends Expression,
 | 
				
			||||||
        const subs = rootExp.subExpressions[2].subExpressions;
 | 
					                              SecondT extends Expression,
 | 
				
			||||||
 | 
					                              ThirdT extends Expression,
 | 
				
			||||||
        expect(subs[0].constructor.name).toEqual("NoteFlatTextExp");
 | 
					                              FourthT extends Expression>(
 | 
				
			||||||
        expect(subs[0].tokens).toEqual(["hello", "hi"]);
 | 
					                                exp: AndExp,
 | 
				
			||||||
 | 
					                                firstType: ClassType<FirstT>,
 | 
				
			||||||
        expect(subs[1].constructor.name).toEqual("NoteContentFulltextExp");
 | 
					                                secondType?: ClassType<SecondT>,
 | 
				
			||||||
        expect(subs[1].tokens).toEqual(["hello", "hi"]);
 | 
					                                thirdType?: ClassType<ThirdT>,
 | 
				
			||||||
    });
 | 
					                                fourthType?: ClassType<FourthT>): [ FirstT, SecondT, ThirdT, FourthT ]
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
    it("simple label comparison", () => {
 | 
					    expectExpression(exp.subExpressions[0], firstType);
 | 
				
			||||||
        const rootExp = parse({
 | 
					    if (secondType) {
 | 
				
			||||||
            fulltextTokens: [],
 | 
					        expectExpression(exp.subExpressions[1], secondType);
 | 
				
			||||||
            expressionTokens: tokens(["#mylabel", "=", "text"]),
 | 
					    }
 | 
				
			||||||
            searchContext: new SearchContext()
 | 
					    if (thirdType) {
 | 
				
			||||||
        });
 | 
					        expectExpression(exp.subExpressions[2], thirdType);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					    if (fourthType) {
 | 
				
			||||||
        assertIsArchived(rootExp.subExpressions[0]);
 | 
					        expectExpression(exp.subExpressions[3], fourthType);
 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("LabelComparisonExp");
 | 
					    }
 | 
				
			||||||
        expect(rootExp.subExpressions[2].attributeType).toEqual("label");
 | 
					    return [
 | 
				
			||||||
        expect(rootExp.subExpressions[2].attributeName).toEqual("mylabel");
 | 
					        exp.subExpressions[0] as FirstT,
 | 
				
			||||||
        expect(rootExp.subExpressions[2].comparator).toBeTruthy();
 | 
					        exp.subExpressions[1] as SecondT,
 | 
				
			||||||
    });
 | 
					        exp.subExpressions[2] as ThirdT,
 | 
				
			||||||
 | 
					        exp.subExpressions[3] as FourthT
 | 
				
			||||||
    it("simple attribute negation", () => {
 | 
					    ]
 | 
				
			||||||
        let rootExp = parse({
 | 
					}
 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["#!mylabel"]),
 | 
					 | 
				
			||||||
            searchContext: new SearchContext()
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        assertIsArchived(rootExp.subExpressions[0]);
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("NotExp");
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].subExpression.constructor.name).toEqual("AttributeExistsExp");
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].subExpression.attributeType).toEqual("label");
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].subExpression.attributeName).toEqual("mylabel");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        rootExp = parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["~!myrelation"]),
 | 
					 | 
				
			||||||
            searchContext: new SearchContext()
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        assertIsArchived(rootExp.subExpressions[0]);
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("NotExp");
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].subExpression.constructor.name).toEqual("AttributeExistsExp");
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].subExpression.attributeType).toEqual("relation");
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].subExpression.attributeName).toEqual("myrelation");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("simple label AND", () => {
 | 
					 | 
				
			||||||
        const rootExp = parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]),
 | 
					 | 
				
			||||||
            searchContext: new SearchContext(true)
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        assertIsArchived(rootExp.subExpressions[0]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(firstSub.attributeName).toEqual("first");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(secondSub.attributeName).toEqual("second");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("simple label AND without explicit AND", () => {
 | 
					 | 
				
			||||||
        const rootExp = parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]),
 | 
					 | 
				
			||||||
            searchContext: new SearchContext()
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        assertIsArchived(rootExp.subExpressions[0]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(firstSub.attributeName).toEqual("first");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(secondSub.attributeName).toEqual("second");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("simple label OR", () => {
 | 
					 | 
				
			||||||
        const rootExp = parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]),
 | 
					 | 
				
			||||||
            searchContext: new SearchContext()
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        assertIsArchived(rootExp.subExpressions[0]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
 | 
					 | 
				
			||||||
        const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(firstSub.attributeName).toEqual("first");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(secondSub.attributeName).toEqual("second");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("fulltext and simple label", () => {
 | 
					 | 
				
			||||||
        const rootExp = parse({
 | 
					 | 
				
			||||||
            fulltextTokens: tokens(["hello"]),
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["#mylabel", "=", "text"]),
 | 
					 | 
				
			||||||
            searchContext: new SearchContext({ excludeArchived: true })
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        const [firstSub, secondSub, thirdSub, fourth] = rootExp.subExpressions;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(firstSub.constructor.name).toEqual("PropertyComparisonExp");
 | 
					 | 
				
			||||||
        expect(firstSub.propertyName).toEqual("isArchived");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(thirdSub.constructor.name).toEqual("OrExp");
 | 
					 | 
				
			||||||
        expect(thirdSub.subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
 | 
					 | 
				
			||||||
        expect(thirdSub.subExpressions[0].tokens).toEqual(["hello"]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(fourth.constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(fourth.attributeName).toEqual("mylabel");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("label sub-expression", () => {
 | 
					 | 
				
			||||||
        const rootExp = parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]),
 | 
					 | 
				
			||||||
            searchContext: new SearchContext()
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        assertIsArchived(rootExp.subExpressions[0]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
 | 
					 | 
				
			||||||
        const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(firstSub.attributeName).toEqual("first");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        const [firstSubSub, secondSubSub] = secondSub.subExpressions;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(firstSubSub.constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(firstSubSub.attributeName).toEqual("second");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(secondSubSub.constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(secondSubSub.attributeName).toEqual("third");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("label sub-expression without explicit operator", () => {
 | 
					 | 
				
			||||||
        const rootExp = parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]),
 | 
					 | 
				
			||||||
            searchContext: new SearchContext()
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        assertIsArchived(rootExp.subExpressions[0]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        const [firstSub, secondSub, thirdSub] = rootExp.subExpressions[2].subExpressions;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(firstSub.constructor.name).toEqual("AttributeExistsExp");
 | 
					 | 
				
			||||||
        expect(firstSub.attributeName).toEqual("first");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("OrExp");
 | 
					 | 
				
			||||||
        const [firstSubSub, secondSubSub] = secondSub.subExpressions;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(firstSubSub.constructor.name).toEqual("AttributeExistsExp");
 | 
					 | 
				
			||||||
        expect(firstSubSub.attributeName).toEqual("second");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(secondSubSub.constructor.name).toEqual("AttributeExistsExp");
 | 
					 | 
				
			||||||
        expect(secondSubSub.attributeName).toEqual("third");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(thirdSub.constructor.name).toEqual("AttributeExistsExp");
 | 
					 | 
				
			||||||
        expect(thirdSub.attributeName).toEqual("fourth");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe("Invalid expressions", () => {
 | 
					 | 
				
			||||||
    it("incomplete comparison", () => {
 | 
					 | 
				
			||||||
        const searchContext = new SearchContext();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["#first", "="]),
 | 
					 | 
				
			||||||
            searchContext
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(searchContext.error).toEqual('Misplaced or incomplete expression "="');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("comparison between labels is impossible", () => {
 | 
					 | 
				
			||||||
        let searchContext = new SearchContext();
 | 
					 | 
				
			||||||
        searchContext.originalQuery = "#first = #second";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["#first", "=", "#second"]),
 | 
					 | 
				
			||||||
            searchContext
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(searchContext.error).toEqual(`Error near token "#second" in "#first = #second", it's possible to compare with constant only.`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        searchContext = new SearchContext();
 | 
					 | 
				
			||||||
        searchContext.originalQuery = "#first = note.relations.second";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["#first", "=", "note", ".", "relations", "second"]),
 | 
					 | 
				
			||||||
            searchContext
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(searchContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const rootExp = parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: [
 | 
					 | 
				
			||||||
                { token: "#first", inQuotes: false },
 | 
					 | 
				
			||||||
                { token: "=", inQuotes: false },
 | 
					 | 
				
			||||||
                { token: "#second", inQuotes: true }
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            searchContext: new SearchContext()
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					 | 
				
			||||||
        assertIsArchived(rootExp.subExpressions[0]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].constructor.name).toEqual("LabelComparisonExp");
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].attributeType).toEqual("label");
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].attributeName).toEqual("first");
 | 
					 | 
				
			||||||
        expect(rootExp.subExpressions[2].comparator).toBeTruthy();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("searching by relation without note property", () => {
 | 
					 | 
				
			||||||
        const searchContext = new SearchContext();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        parse({
 | 
					 | 
				
			||||||
            fulltextTokens: [],
 | 
					 | 
				
			||||||
            expressionTokens: tokens(["~first", "=", "text", "-", "abc"]),
 | 
					 | 
				
			||||||
            searchContext
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(searchContext.error).toEqual('Relation can be compared only with property, e.g. ~relation.title=hello in ""');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -704,7 +704,7 @@ body.layout-horizontal .tab-row-widget-container {
 | 
				
			|||||||
    overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
body.desktop #root-widget.horizontal-layout {
 | 
					body.desktop:not(.background-effects.platform-win32) #root-widget.horizontal-layout {
 | 
				
			||||||
    background-color: var(--root-background) !important;
 | 
					    background-color: var(--root-background) !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import Expression from "./expression.js";
 | 
				
			|||||||
import TrueExp from "./true.js";
 | 
					import TrueExp from "./true.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AndExp extends Expression {
 | 
					class AndExp extends Expression {
 | 
				
			||||||
    private subExpressions: Expression[];
 | 
					    subExpressions: Expression[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static of(_subExpressions: (Expression | null | undefined)[]) {
 | 
					    static of(_subExpressions: (Expression | null | undefined)[]) {
 | 
				
			||||||
        const subExpressions = _subExpressions.filter((exp) => !!exp) as Expression[];
 | 
					        const subExpressions = _subExpressions.filter((exp) => !!exp) as Expression[];
 | 
				
			||||||
 | 
				
			|||||||
@ -7,8 +7,8 @@ import becca from "../../../becca/becca.js";
 | 
				
			|||||||
import Expression from "./expression.js";
 | 
					import Expression from "./expression.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AttributeExistsExp extends Expression {
 | 
					class AttributeExistsExp extends Expression {
 | 
				
			||||||
    private attributeType: string;
 | 
					    attributeType: string;
 | 
				
			||||||
    private attributeName: string;
 | 
					    attributeName: string;
 | 
				
			||||||
    private isTemplateLabel: boolean;
 | 
					    private isTemplateLabel: boolean;
 | 
				
			||||||
    private prefixMatch: boolean;
 | 
					    private prefixMatch: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import NoteSet from "../note_set.js";
 | 
					import type NoteSet from "../note_set.js";
 | 
				
			||||||
import SearchContext from "../search_context.js";
 | 
					import type SearchContext from "../search_context.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
abstract class Expression {
 | 
					export default abstract class Expression {
 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
@ -12,5 +12,3 @@ abstract class Expression {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    abstract execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext): NoteSet;
 | 
					    abstract execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext): NoteSet;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Expression;
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -8,9 +8,9 @@ import SearchContext from "../search_context.js";
 | 
				
			|||||||
type Comparator = (value: string) => boolean;
 | 
					type Comparator = (value: string) => boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LabelComparisonExp extends Expression {
 | 
					class LabelComparisonExp extends Expression {
 | 
				
			||||||
    private attributeType: string;
 | 
					    attributeType: string;
 | 
				
			||||||
    private attributeName: string;
 | 
					    attributeName: string;
 | 
				
			||||||
    private comparator: Comparator;
 | 
					    comparator: Comparator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(attributeType: string, attributeName: string, comparator: Comparator) {
 | 
					    constructor(attributeType: string, attributeName: string, comparator: Comparator) {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@ import SearchContext from "../search_context.js";
 | 
				
			|||||||
import Expression from "./expression.js";
 | 
					import Expression from "./expression.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NotExp extends Expression {
 | 
					class NotExp extends Expression {
 | 
				
			||||||
    private subExpression: Expression;
 | 
					    subExpression: Expression;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(subExpression: Expression) {
 | 
					    constructor(subExpression: Expression) {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
 | 
				
			|||||||
@ -34,7 +34,7 @@ type SearchRow = Pick<NoteRow, "noteId" | "type" | "mime" | "content" | "isProte
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class NoteContentFulltextExp extends Expression {
 | 
					class NoteContentFulltextExp extends Expression {
 | 
				
			||||||
    private operator: string;
 | 
					    private operator: string;
 | 
				
			||||||
    private tokens: string[];
 | 
					    tokens: string[];
 | 
				
			||||||
    private raw: boolean;
 | 
					    private raw: boolean;
 | 
				
			||||||
    private flatText: boolean;
 | 
					    private flatText: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ import { normalize } from "../../utils.js";
 | 
				
			|||||||
import beccaService from "../../../becca/becca_service.js";
 | 
					import beccaService from "../../../becca/becca_service.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NoteFlatTextExp extends Expression {
 | 
					class NoteFlatTextExp extends Expression {
 | 
				
			||||||
    private tokens: string[];
 | 
					    tokens: string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(tokens: string[]) {
 | 
					    constructor(tokens: string[]) {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import TrueExp from "./true.js";
 | 
				
			|||||||
import SearchContext from "../search_context.js";
 | 
					import SearchContext from "../search_context.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OrExp extends Expression {
 | 
					class OrExp extends Expression {
 | 
				
			||||||
    private subExpressions: Expression[];
 | 
					    subExpressions: Expression[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static of(subExpressions: Expression[]) {
 | 
					    static of(subExpressions: Expression[]) {
 | 
				
			||||||
        subExpressions = subExpressions.filter((exp) => !!exp);
 | 
					        subExpressions = subExpressions.filter((exp) => !!exp);
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,7 @@ interface OrderDefinition {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class OrderByAndLimitExp extends Expression {
 | 
					class OrderByAndLimitExp extends Expression {
 | 
				
			||||||
    private orderDefinitions: OrderDefinition[];
 | 
					    private orderDefinitions: OrderDefinition[];
 | 
				
			||||||
    private limit: number;
 | 
					    limit: number;
 | 
				
			||||||
    subExpression: Expression | null;
 | 
					    subExpression: Expression | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(orderDefinitions: Pick<OrderDefinition, "direction" | "valueExtractor">[], limit?: number) {
 | 
					    constructor(orderDefinitions: Pick<OrderDefinition, "direction" | "valueExtractor">[], limit?: number) {
 | 
				
			||||||
 | 
				
			|||||||
@ -41,9 +41,9 @@ interface SearchContext {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PropertyComparisonExp extends Expression {
 | 
					class PropertyComparisonExp extends Expression {
 | 
				
			||||||
    private propertyName: string;
 | 
					    propertyName: string;
 | 
				
			||||||
    private operator: string;
 | 
					    operator: string;
 | 
				
			||||||
    private comparedValue: string;
 | 
					    comparedValue: string;
 | 
				
			||||||
    private comparator;
 | 
					    private comparator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static isProperty(name: string) {
 | 
					    static isProperty(name: string) {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ class SearchContext {
 | 
				
			|||||||
    originalQuery: string;
 | 
					    originalQuery: string;
 | 
				
			||||||
    fulltextQuery: string;
 | 
					    fulltextQuery: string;
 | 
				
			||||||
    dbLoadNeeded: boolean;
 | 
					    dbLoadNeeded: boolean;
 | 
				
			||||||
    private error: string | null;
 | 
					    error: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(params: SearchParams = {}) {
 | 
					    constructor(params: SearchParams = {}) {
 | 
				
			||||||
        this.fastSearch = !!params.fastSearch;
 | 
					        this.fastSearch = !!params.fastSearch;
 | 
				
			||||||
 | 
				
			|||||||
@ -423,7 +423,14 @@ function getExpression(tokens: TokenData[], searchContext: SearchContext, level
 | 
				
			|||||||
    return getAggregateExpression();
 | 
					    return getAggregateExpression();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function parse({ fulltextTokens, expressionTokens, searchContext }: { fulltextTokens: TokenData[]; expressionTokens: TokenStructure; searchContext: SearchContext; originalQuery: string }) {
 | 
					export interface ParseOpts {
 | 
				
			||||||
 | 
					    fulltextTokens: TokenData[];
 | 
				
			||||||
 | 
					    expressionTokens: TokenStructure;
 | 
				
			||||||
 | 
					    searchContext: SearchContext;
 | 
				
			||||||
 | 
					    originalQuery?: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function parse({ fulltextTokens, expressionTokens, searchContext }: ParseOpts) {
 | 
				
			||||||
    let expression: Expression | undefined | null;
 | 
					    let expression: Expression | undefined | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
@ -441,6 +448,12 @@ function parse({ fulltextTokens, expressionTokens, searchContext }: { fulltextTo
 | 
				
			|||||||
        expression
 | 
					        expression
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (searchContext.limit && !searchContext.orderBy) {
 | 
				
			||||||
 | 
					        const filterExp = exp;
 | 
				
			||||||
 | 
					        exp = new OrderByAndLimitExp([], searchContext.limit || undefined );
 | 
				
			||||||
 | 
					        (exp as any).subExpression = filterExp;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (searchContext.orderBy && searchContext.orderBy !== "relevancy") {
 | 
					    if (searchContext.orderBy && searchContext.orderBy !== "relevancy") {
 | 
				
			||||||
        const filterExp = exp;
 | 
					        const filterExp = exp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user