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