Unverified Commit d14b0ec0 authored by zerodat's avatar zerodat Committed by GitHub
Browse files

Filter tweaks (#186)

* restore missing build:css rule

* allow case-insensitive matches in project, context names

* allow more alternate syntaxes in filter expressions
- allow the syntax "(A)" to mean "pri == A"
- allow the operator "=" as an alternate form of "=="
- let quoted project or context names include ( or ).
- allow short forms YYYY or YYYY-MM to mean first day of year or month
  in date expressions, eg "due < 2021" means before 2021-01-01.
- allow the syntax "due:DATE" to mean string match due date
  against prefix DATE,e eg: "due:2021" means due date begins with 2021.

* simple text color feedback for query validity

* bug fix to the due:DATESTR feature
parent d2c07460
......@@ -97,6 +97,7 @@
"build:linux": "yarn build:css && yarn build:pegjs && electron-builder -l --publish never",
"build:appx": "yarn build:css && yarn build:pegjs && electron-builder -w appx --publish never",
"build:appimage": "yarn build:css && yarn build:pegjs && electron-builder -l AppImage --publish never",
"build:css": "sass src/scss/style.scss:src/css/style.css",
"build:pegjs": "peggy --format es --output src/js/filterlang.mjs src/js/filterlang.pegjs",
"pack": "yarn build:css && yarn build:pegjs && electron-builder --dir",
"lint": "eslint --ext .js, src --ext .mjs, src",
......
......@@ -144,6 +144,12 @@ body.dark #todoTableSearchContainer #todoTableSearchAddTodo:focus-visible {
color: #212224 !important;
background: #f5f5f5;
}
body.dark #todoTableSearchContainer #todoTableSearch.is-valid-query {
color: #10cf10 !important;
}
body.dark #todoTableSearchContainer #todoTableSearch.is-previous-query {
color: #cfa010 !important;
}
body.dark #todoTableSearchContainer #btnToggleViewContainer {
background: transparent !important;
}
......@@ -930,6 +936,12 @@ nav ul:nth-child(2) {
cursor: pointer;
margin-top: -0.7em;
}
#todoTableSearchContainer #todoTableSearch.is-valid-query {
color: #09aa41;
}
#todoTableSearchContainer #todoTableSearch.is-previous-query {
color: #cfa010;
}
#todoTableSearchContainer label {
left: 5.9em;
}
......
This diff is collapsed.
......@@ -52,8 +52,9 @@ comparison
/ left:dueComparison { return left; }
priorityComparison
= priorityKeyword _ op:compareOp _ right:priorityLiteral { return ["priority", right, op]; }
/ priorityKeyword { return ["priority"]; }
= priorityKeyword _ op:compareOp _ right:priorityLiteral { return ["pri", right, op]; }
/ priorityKeyword { return ["pri"]; }
/ "(" right:priorityLiteral ")" { return ["pri", right, "=="]; }
priorityLiteral
= [A-Z] { return text(); }
......@@ -63,6 +64,7 @@ priorityKeyword
dueComparison
= "due" _ op:compareOp _ right:dateExpr { return ["due"].concat(right, [op]); }
/ "due:" right:dateStr { return ["duestr", right]; }
/ "due" { return ["due"]; }
dateExpr
......@@ -85,17 +87,29 @@ dateOp
compareOp
= "==" { return text(); }
/ "=" { return "=="; }
/ "!=" { return text(); }
/ ">=" { return text(); }
/ "<=" { return text(); }
/ ">" { return text(); }
/ "<" { return text(); }
dateStr
= [0-9]+ ("-" [0-9]+ ("-" [0-9]+)?)? { return text(); }
dateLiteral
= year:number4 "-" month: number2 "-" day:number2 {
let d = new Date(year, month-1, day);
return d.getTime();
}
/ year:number4 "-" month: number2 {
let d = new Date(year, month-1, 1);
return d.getTime();
}
/ year:number4 {
let d = new Date(year, 0, 1);
return d.getTime();
}
/ "today" {
let d = new Date(); // now, w current time of day
d = new Date(d.getFullYear(), d.getMonth(), d.getDate());
......@@ -153,11 +167,14 @@ SourceCharacter
= .
name
= '"' nonblank+ '"' { return text(); }
/ nonblank+ '"' { return '"' + text(); }
/ nonblank+ { return text(); }
= '"' nonblank+ '"' { return text(); }
/ nonblankparen+ '"' { return '"' + text(); }
/ nonblankparen+ { return text(); }
nonblank
= [^ \t\n\r"]
nonblankparen
= [^ \t\n\r"()]
_ "whitespace"
......
......@@ -15,17 +15,31 @@ function runQuery(item, compiledQuery) {
while (q.length > 0) {
const opcode = q.shift();
switch(opcode) {
case "priority":
case "pri":
stack.push(item.priority);
break;
case "due":
let d = item.due;
if (d) {
if (item.due) {
// normalize date to have time of midnight in local zone
// we represent dates as millisec from epoch to simplify comparison
d = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
let d = item.due;
stack.push(new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime());
} else {
stack.push(undefined); // all comparisons will return false
}
break;
case "duestr":
// match next value (a string) as prefix of ISO date string of due date
next = q.shift(); // the string to compare
if (item.due) {
// normalize date to have time of midnight in local zone
// we represent dates as millisec from epoch to simplify comparison
let d = item.due;
d = new Date(d.getFullYear(), d.getMonth(), d.getDate());
stack.push(d.toISOString().slice(0, 10).startsWith(next));
} else {
stack.push(false); // no due date
}
stack.push(d);
break;
case "complete":
stack.push(item.complete);
......@@ -75,9 +89,10 @@ function runQuery(item, compiledQuery) {
} else if (next.startsWith('"')) {
stack.push(item.projects && item.projects.includes(next.slice(1,-1)));
} else {
// match for next as a substring of the project name
// case-insensitive match for next as a substring of the project name
let pattern = next.toLowerCase();
stack.push(item.projects && item.projects.findIndex(function(p) {
return p.indexOf(next) > -1;
return p.toLowerCase().indexOf(pattern) > -1;
}) > -1);
}
break;
......@@ -88,9 +103,10 @@ function runQuery(item, compiledQuery) {
} else if (next.startsWith('"')) {
stack.push(item.contexts && item.contexts.includes(next.slice(1,-1)));
} else {
// match for next as a substring of the context name
// case-insensitive match for next as a substring of the context name
let pattern = next.toLowerCase();
stack.push(item.contexts && item.contexts.findIndex(function(c) {
return c.indexOf(next) > -1;
return c.toLowerCase().indexOf(pattern) > -1;
}) > -1);
}
break;
......
......@@ -130,13 +130,17 @@ function filterItems(items) {
});
lastFilterQueryString = queryString;
lastFilterItems = items;
todoTableSearch.classList.add("is-valid-query");
todoTableSearch.classList.remove("is-previous-query");
}
} catch(e) {
// oops, that wasn't a syntactically correct search expression
todoTableSearch.classList.remove("is-valid-query");
if (lastFilterQueryString && queryString.startsWith(lastFilterQueryString)) {
// keep table more stable by using the previous valid query while
// user continues to type additional query syntax.
items = lastFilterItems;
todoTableSearch.classList.add("is-previous-query");
} else {
// the query is not syntactically correct and isn't a longer version
// of the last working query, so let's assume that it is a
......@@ -144,6 +148,7 @@ function filterItems(items) {
items = items.filter(function(item) {
return item.toString().toLowerCase().indexOf(queryString.toLowerCase()) !== -1;
});
todoTableSearch.classList.remove("is-previous-query");
}
}
}
......
......@@ -159,6 +159,12 @@ body.dark {
color: $almost-black!important;
background: $almost-white;
}
#todoTableSearch.is-valid-query {
color: #10cf10!important;
}
#todoTableSearch.is-previous-query {
color: #cfa010!important;
}
#btnToggleViewContainer {
background: transparent!important;
}
......
......@@ -43,6 +43,12 @@
cursor: pointer;
margin-top: -0.7em;
}
#todoTableSearch.is-valid-query {
color: hsl(141, 90%, 35%);
}
#todoTableSearch.is-previous-query {
color: #cfa010;
}
label {
left: 5.9em;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment