mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 09:58:32 +02: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,20 +154,30 @@ 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);
|
||||||
test("#dateTime < NOW-10", 0);
|
test("#dateTime < NOW-10", 0);
|
||||||
|
@ -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