mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 11:39:01 +01:00 
			
		
		
		
	"smart" date values can now freely contain whitespaces
This commit is contained in:
		
							parent
							
								
									a6c79c934c
								
							
						
					
					
						commit
						46e373e822
					
				| @ -33,9 +33,6 @@ find $DIR/libraries -name "*.map" -type f -delete | |||||||
| 
 | 
 | ||||||
| rm -r $DIR/src/public/app | rm -r $DIR/src/public/app | ||||||
| 
 | 
 | ||||||
| rm -r $DIR/node_modules/sqlite3/build |  | ||||||
| rm -r $DIR/node_modules/sqlite3/deps |  | ||||||
| 
 |  | ||||||
| sed -i -e 's/app\/desktop.js/app-dist\/desktop.js/g' $DIR/src/views/desktop.ejs | sed -i -e 's/app\/desktop.js/app-dist\/desktop.js/g' $DIR/src/views/desktop.ejs | ||||||
| sed -i -e 's/app\/mobile.js/app-dist\/mobile.js/g' $DIR/src/views/mobile.ejs | sed -i -e 's/app\/mobile.js/app-dist\/mobile.js/g' $DIR/src/views/mobile.ejs | ||||||
| sed -i -e 's/app\/setup.js/app-dist\/setup.js/g' $DIR/src/views/setup.ejs | sed -i -e 's/app\/setup.js/app-dist\/setup.js/g' $DIR/src/views/setup.ejs | ||||||
|  | |||||||
| @ -65,6 +65,11 @@ describe("Lexer fulltext", () => { | |||||||
|             .toEqual(["what's", "u=p", "<b(r*t)h>"]); |             .toEqual(["what's", "u=p", "<b(r*t)h>"]); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     it("operator characters in expressions are separate tokens", () => { | ||||||
|  |         expect(lex("# abc+=-def**-+d").expressionTokens.map(t => t.token)) | ||||||
|  |             .toEqual(["#", "abc", "+=-", "def", "**-+", "d"]); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     it("escaping special characters", () => { |     it("escaping special characters", () => { | ||||||
|         expect(lex("hello \\#\\~\\'").fulltextTokens.map(t => t.token)) |         expect(lex("hello \\#\\~\\'").fulltextTokens.map(t => t.token)) | ||||||
|             .toEqual(["hello", "#~'"]); |             .toEqual(["hello", "#~'"]); | ||||||
|  | |||||||
| @ -219,7 +219,7 @@ describe("Invalid expressions", () => { | |||||||
|             parsingContext |             parsingContext | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         expect(parsingContext.error).toEqual(`Error near token "note" in "#first = note.relations.s...", it's possible to compare with constant only.`); |         expect(parsingContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`); | ||||||
| 
 | 
 | ||||||
|         const rootExp = parse({ |         const rootExp = parse({ | ||||||
|             fulltextTokens: [], |             fulltextTokens: [], | ||||||
|  | |||||||
| @ -121,11 +121,11 @@ describe("Search", () => { | |||||||
| 
 | 
 | ||||||
|         const parsingContext = new ParsingContext(); |         const parsingContext = new ParsingContext(); | ||||||
| 
 | 
 | ||||||
|         let searchResults = searchService.findNotesWithQuery('#established <= 1955-01-01', parsingContext); |         let searchResults = searchService.findNotesWithQuery('#established <= "1955-01-01"', parsingContext); | ||||||
|         expect(searchResults.length).toEqual(1); |         expect(searchResults.length).toEqual(1); | ||||||
|         expect(findNoteByTitle(searchResults, "Hungary")).toBeTruthy(); |         expect(findNoteByTitle(searchResults, "Hungary")).toBeTruthy(); | ||||||
| 
 | 
 | ||||||
|         searchResults = searchService.findNotesWithQuery('#established > 1955-01-01', parsingContext); |         searchResults = searchService.findNotesWithQuery('#established > "1955-01-01"', parsingContext); | ||||||
|         expect(searchResults.length).toEqual(2); |         expect(searchResults.length).toEqual(2); | ||||||
|         expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy(); |         expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy(); | ||||||
|         expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy(); |         expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy(); | ||||||
| @ -154,19 +154,29 @@ describe("Search", () => { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         test("#year = YEAR", 1); |         test("#year = YEAR", 1); | ||||||
|  |         test("#year = 'YEAR'", 0); | ||||||
|         test("#year >= YEAR", 1); |         test("#year >= YEAR", 1); | ||||||
|         test("#year <= YEAR", 1); |         test("#year <= YEAR", 1); | ||||||
|         test("#year < YEAR+1", 1); |         test("#year < YEAR+1", 1); | ||||||
|  |         test("#year < YEAR + 1", 1); | ||||||
|  |         test("#year < year + 1", 1); | ||||||
|         test("#year > YEAR+1", 0); |         test("#year > YEAR+1", 0); | ||||||
| 
 | 
 | ||||||
|         test("#month = MONTH", 1); |         test("#month = MONTH", 1); | ||||||
|  |         test("#month = month", 1); | ||||||
|  |         test("#month = 'MONTH'", 0); | ||||||
| 
 | 
 | ||||||
|         test("#date = TODAY", 1); |         test("#date = TODAY", 1); | ||||||
|  |         test("#date = today", 1); | ||||||
|  |         test("#date = 'today'", 0); | ||||||
|         test("#date > TODAY", 0); |         test("#date > TODAY", 0); | ||||||
|         test("#date > TODAY-1", 1); |         test("#date > TODAY-1", 1); | ||||||
|  |         test("#date > TODAY - 1", 1); | ||||||
|  |         test("#date < TODAY+1", 1); | ||||||
|         test("#date < TODAY + 1", 1); |         test("#date < TODAY + 1", 1); | ||||||
|         test("#date < 'TODAY + 1'", 1); |         test("#date < 'TODAY + 1'", 1); | ||||||
| 
 | 
 | ||||||
|  |         test("#dateTime <= NOW+10", 1); | ||||||
|         test("#dateTime <= NOW + 10", 1); |         test("#dateTime <= NOW + 10", 1); | ||||||
|         test("#dateTime < NOW-10", 0); |         test("#dateTime < NOW-10", 0); | ||||||
|         test("#dateTime >= NOW-10", 1); |         test("#dateTime >= NOW-10", 1); | ||||||
|  | |||||||
| @ -1,5 +1,3 @@ | |||||||
| const dayjs = require("dayjs"); |  | ||||||
| 
 |  | ||||||
| const stringComparators = { | const stringComparators = { | ||||||
|     "=": comparedValue => (val => val === comparedValue), |     "=": comparedValue => (val => val === comparedValue), | ||||||
|     "!=": comparedValue => (val => val !== comparedValue), |     "!=": comparedValue => (val => val !== comparedValue), | ||||||
| @ -19,52 +17,9 @@ const numericComparators = { | |||||||
|     "<=": comparedValue => (val => parseFloat(val) <= comparedValue) |     "<=": comparedValue => (val => parseFloat(val) <= comparedValue) | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i; |  | ||||||
| 
 |  | ||||||
| function calculateSmartValue(v) { |  | ||||||
|     const match = smartValueRegex.exec(v); |  | ||||||
|     if (match === null) { |  | ||||||
|         return v; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const keyword = match[1].toUpperCase(); |  | ||||||
|     const num = match[2] ? parseInt(match[2].replace(/ /g, "")) : 0; // can contain spaces between sign and digits
 |  | ||||||
| 
 |  | ||||||
|     let format, date; |  | ||||||
| 
 |  | ||||||
|     if (keyword === 'NOW') { |  | ||||||
|         date = dayjs().add(num, 'second'); |  | ||||||
|         format = "YYYY-MM-DD HH:mm:ss"; |  | ||||||
|     } |  | ||||||
|     else if (keyword === 'TODAY') { |  | ||||||
|         date = dayjs().add(num, 'day'); |  | ||||||
|         format = "YYYY-MM-DD"; |  | ||||||
|     } |  | ||||||
|     else if (keyword === 'WEEK') { |  | ||||||
|         // FIXME: this will always use sunday as start of the week
 |  | ||||||
|         date = dayjs().startOf('week').add(7 * num, 'day'); |  | ||||||
|         format = "YYYY-MM-DD"; |  | ||||||
|     } |  | ||||||
|     else if (keyword === 'MONTH') { |  | ||||||
|         date = dayjs().add(num, 'month'); |  | ||||||
|         format = "YYYY-MM"; |  | ||||||
|     } |  | ||||||
|     else if (keyword === 'YEAR') { |  | ||||||
|         date = dayjs().add(num, 'year'); |  | ||||||
|         format = "YYYY"; |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         throw new Error("Unrecognized keyword: " + keyword); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return date.format(format); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function buildComparator(operator, comparedValue) { | function buildComparator(operator, comparedValue) { | ||||||
|     comparedValue = comparedValue.toLowerCase(); |     comparedValue = comparedValue.toLowerCase(); | ||||||
| 
 | 
 | ||||||
|     comparedValue = calculateSmartValue(comparedValue); |  | ||||||
| 
 |  | ||||||
|     if (operator in numericComparators && !isNaN(comparedValue)) { |     if (operator in numericComparators && !isNaN(comparedValue)) { | ||||||
|         return numericComparators[operator](parseFloat(comparedValue)); |         return numericComparators[operator](parseFloat(comparedValue)); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ function lex(str) { | |||||||
|     let currentWord = ''; |     let currentWord = ''; | ||||||
| 
 | 
 | ||||||
|     function isSymbolAnOperator(chr) { |     function isSymbolAnOperator(chr) { | ||||||
|         return ['=', '*', '>', '<', '!'].includes(chr); |         return ['=', '*', '>', '<', '!', "-", "+"].includes(chr); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function isPreviousSymbolAnOperator() { |     function isPreviousSymbolAnOperator() { | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
|  | const dayjs = require("dayjs"); | ||||||
| const AndExp = require('../expressions/and.js'); | const AndExp = require('../expressions/and.js'); | ||||||
| const OrExp = require('../expressions/or.js'); | const OrExp = require('../expressions/or.js'); | ||||||
| const NotExp = require('../expressions/not.js'); | const NotExp = require('../expressions/not.js'); | ||||||
| @ -14,7 +15,7 @@ const NoteCacheFulltextExp = require('../expressions/note_cache_flat_text.js'); | |||||||
| const NoteContentProtectedFulltextExp = require('../expressions/note_content_protected_fulltext.js'); | const NoteContentProtectedFulltextExp = require('../expressions/note_content_protected_fulltext.js'); | ||||||
| const NoteContentUnprotectedFulltextExp = require('../expressions/note_content_unprotected_fulltext.js'); | const NoteContentUnprotectedFulltextExp = require('../expressions/note_content_unprotected_fulltext.js'); | ||||||
| const OrderByAndLimitExp = require('../expressions/order_by_and_limit.js'); | const OrderByAndLimitExp = require('../expressions/order_by_and_limit.js'); | ||||||
| const comparatorBuilder = require('./build_comparator.js'); | const buildComparator = require('./build_comparator.js'); | ||||||
| const ValueExtractor = require('../value_extractor.js'); | const ValueExtractor = require('../value_extractor.js'); | ||||||
| 
 | 
 | ||||||
| function getFulltext(tokens, parsingContext) { | function getFulltext(tokens, parsingContext) { | ||||||
| @ -41,7 +42,7 @@ function getFulltext(tokens, parsingContext) { | |||||||
| 
 | 
 | ||||||
|     return new AndExp([ |     return new AndExp([ | ||||||
|         textSearchExpression, |         textSearchExpression, | ||||||
|         new PropertyComparisonExp("isarchived", comparatorBuilder("=", "false")) |         new PropertyComparisonExp("isarchived", buildComparator("=", "false")) | ||||||
|     ]); |     ]); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -69,6 +70,55 @@ function getExpression(tokens, parsingContext, level = 0) { | |||||||
|             + (endIndex !== parsingContext.originalQuery.length ? "..." : "") + '"'; |             + (endIndex !== parsingContext.originalQuery.length ? "..." : "") + '"'; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     function resolveConstantOperand() { | ||||||
|  |         const operand = tokens[i]; | ||||||
|  | 
 | ||||||
|  |         if (!operand.inQuotes | ||||||
|  |             && (operand.token.startsWith('#') || operand.token.startsWith('~') || operand.token === 'note')) { | ||||||
|  |             parsingContext.addError(`Error near token "${operand.token}" in ${context(i)}, it's possible to compare with constant only.`); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (operand.inQuotes || !["now", "today", "month", "year"].includes(operand.token)) { | ||||||
|  |             return operand.token; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let delta = 0; | ||||||
|  | 
 | ||||||
|  |         if (i + 2 < tokens.length) { | ||||||
|  |             if (tokens[i + 1].token === '+') { | ||||||
|  |                 delta += parseInt(tokens[i + 2].token); | ||||||
|  |             } | ||||||
|  |             else if (tokens[i + 1].token === '-') { | ||||||
|  |                 delta -= parseInt(tokens[i + 2].token); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let format, date; | ||||||
|  | 
 | ||||||
|  |         if (operand.token === 'now') { | ||||||
|  |             date = dayjs().add(delta, 'second'); | ||||||
|  |             format = "YYYY-MM-DD HH:mm:ss"; | ||||||
|  |         } | ||||||
|  |         else if (operand.token === 'today') { | ||||||
|  |             date = dayjs().add(delta, 'day'); | ||||||
|  |             format = "YYYY-MM-DD"; | ||||||
|  |         } | ||||||
|  |         else if (operand.token === 'month') { | ||||||
|  |             date = dayjs().add(delta, 'month'); | ||||||
|  |             format = "YYYY-MM"; | ||||||
|  |         } | ||||||
|  |         else if (operand.token === 'year') { | ||||||
|  |             date = dayjs().add(delta, 'year'); | ||||||
|  |             format = "YYYY"; | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             throw new Error("Unrecognized keyword: " + operand.token); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return date.format(format); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     function parseNoteProperty() { |     function parseNoteProperty() { | ||||||
|         if (tokens[i].token !== '.') { |         if (tokens[i].token !== '.') { | ||||||
|             parsingContext.addError('Expected "." to separate field path'); |             parsingContext.addError('Expected "." to separate field path'); | ||||||
| @ -150,7 +200,7 @@ function getExpression(tokens, parsingContext, level = 0) { | |||||||
|             const propertyName = tokens[i].token; |             const propertyName = tokens[i].token; | ||||||
|             const operator = tokens[i + 1].token; |             const operator = tokens[i + 1].token; | ||||||
|             const comparedValue = tokens[i + 2].token; |             const comparedValue = tokens[i + 2].token; | ||||||
|             const comparator = comparatorBuilder(operator, comparedValue); |             const comparator = buildComparator(operator, comparedValue); | ||||||
| 
 | 
 | ||||||
|             if (!comparator) { |             if (!comparator) { | ||||||
|                 parsingContext.addError(`Can't find operator '${operator}' in ${context(i)}`); |                 parsingContext.addError(`Can't find operator '${operator}' in ${context(i)}`); | ||||||
| @ -186,11 +236,12 @@ function getExpression(tokens, parsingContext, level = 0) { | |||||||
| 
 | 
 | ||||||
|         if (i < tokens.length - 2 && isOperator(tokens[i + 1].token)) { |         if (i < tokens.length - 2 && isOperator(tokens[i + 1].token)) { | ||||||
|             let operator = tokens[i + 1].token; |             let operator = tokens[i + 1].token; | ||||||
|             const comparedValue = tokens[i + 2].token; |  | ||||||
| 
 | 
 | ||||||
|             if (!tokens[i + 2].inQuotes |             i += 2; | ||||||
|                 && (comparedValue.startsWith('#') || comparedValue.startsWith('~') || comparedValue === 'note')) { | 
 | ||||||
|                 parsingContext.addError(`Error near token "${comparedValue}" in ${context(i)}, it's possible to compare with constant only.`); |             const comparedValue = resolveConstantOperand(); | ||||||
|  | 
 | ||||||
|  |             if (comparedValue === null) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @ -200,13 +251,11 @@ function getExpression(tokens, parsingContext, level = 0) { | |||||||
|                 operator = '*=*'; |                 operator = '*=*'; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const comparator = comparatorBuilder(operator, comparedValue); |             const comparator = buildComparator(operator, comparedValue); | ||||||
| 
 | 
 | ||||||
|             if (!comparator) { |             if (!comparator) { | ||||||
|                 parsingContext.addError(`Can't find operator '${operator}' in ${context(i)}`); |                 parsingContext.addError(`Can't find operator '${operator}' in ${context(i - 1)}`); | ||||||
|             } else { |             } else { | ||||||
|                 i += 2; |  | ||||||
| 
 |  | ||||||
|                 return new LabelComparisonExp('label', labelName, comparator); |                 return new LabelComparisonExp('label', labelName, comparator); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam