Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ransome
sleek
Commits
d2c07460
Commit
d2c07460
authored
Jun 30, 2021
by
ransome1
Browse files
GUI refresh
parent
fe080b66
Changes
31
Expand all
Hide whitespace changes
Inline
Side-by-side
package.json
View file @
d2c07460
...
...
@@ -92,13 +92,13 @@
"artifactName"
:
"${productName}-${version}-${arch}.${ext}"
},
"scripts"
:
{
"pack"
:
"yarn build:css && electron-builder --dir"
,
"build:windows"
:
"yarn build:css && electron-builder -w --publish never"
,
"build:macos"
:
"yarn build:css && electron-builder -m --publish never"
,
"build:linux"
:
"yarn build:css && electron-builder -l --publish never"
,
"build:appx"
:
"yarn build:css && electron-builder -w appx --publish never"
,
"build:windows"
:
"yarn build:css && yarn build:pegjs && electron-builder -w --publish never"
,
"build:macos"
:
"yarn build:css && yarn build:pegjs && electron-builder -m --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: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"
,
"test"
:
"mocha --timeout 10000"
,
"test1"
:
"mocha ./test/onboarding.js --timeout 10000"
,
...
...
src/css/style.css
View file @
d2c07460
This diff is collapsed.
Click to expand it.
src/css/style.css.map
View file @
d2c07460
This diff is collapsed.
Click to expand it.
src/index.html
View file @
d2c07460
...
...
@@ -27,7 +27,7 @@
<li><a
href=
"#"
id=
"navBtnAddTodo"
tabindex=
"0"
><i
class=
"fas fa-plus"
></i></a></li>
<li><a
href=
"#"
id=
"navBtnFilter"
class=
"drawerTrigger"
data-drawer=
"filterDrawer"
tabindex=
"0"
><i
class=
"fas fa-filter"
></i></a></li>
<li><a
href=
"#"
id=
"navBtnView"
class=
"drawerTrigger"
data-drawer=
"viewDrawer"
tabindex=
"0"
><i
class=
"fas fa-sliders-h"
></i></a></li>
<li><a
href=
"#"
id=
"btnOpenTodoFile"
tabindex=
"
-1
"
><i
class=
"fas fa-folder-open"
></i></a></li>
<li><a
href=
"#"
id=
"btnOpenTodoFile"
tabindex=
"
0
"
><i
class=
"fas fa-folder-open"
></i></a></li>
<li><a
href=
"#"
id=
"btnTheme"
tabindex=
"-1"
><i
class=
"fas fa-adjust"
></i></a></li>
</ul>
<ul>
...
...
@@ -38,7 +38,7 @@
<section
id=
"filterDrawer"
class=
"drawer dropdown"
tabindex=
"0"
>
<div
class=
"container"
>
<div
id=
"todoFilters"
></div>
<button
id=
"btnFiltersResetFilters"
class=
"
button
btnResetFilters"
tabindex=
"0"
><i
class=
"fas fa-ban"
></i>
<span></span></button>
<button
id=
"btnFiltersResetFilters"
class=
"btnResetFilters"
tabindex=
"0"
><i
class=
"fas fa-ban"
></i>
<span></span></button>
</div>
</section>
<section
id=
"viewDrawer"
class=
"drawer dropdown"
tabindex=
"0"
>
...
...
@@ -189,14 +189,14 @@
<section
id=
"todoTableSearchContainer"
class=
"inputWrapper"
>
<i
class=
"fas fa-search"
></i>
<label
id=
"todoTableSearchLabel"
for=
"todoTableSearch"
>
Search by todo.txt syntax
</label>
<label
id=
"todoTableSearchLabel"
for=
"todoTableSearch"
></label>
<input
id=
"todoTableSearch"
class=
"input is-medium"
type=
"search"
tabindex=
"1"
placeholder=
"(A) Todo text +project @context due:2020-12-12 rec:d"
>
<a
href=
"#"
id=
"todoTableSearchAddTodo"
class=
"tag"
tabindex=
"5"
><i
class=
"fas fa-plus"
></i>
Add as new todo
</a>
<section
id=
"resultStats"
>
<span
class=
"tag"
></span>
</section>
</section>
<section
id=
"resultStats"
>
<span
class=
"tag"
></span>
</section>
<section
id=
"todoTable"
tabindex=
"-1"
></section>
...
...
@@ -219,7 +219,7 @@
<h1
id=
"addTodoContainerHeadline"
class=
"title is-1"
></h1>
<p
id=
"addTodoContainerSubtitle"
class=
"subtitle"
></p>
<p
class=
"file is-boxed is-centered"
>
<a
href=
"#"
id=
"btnAddTodoContainer"
class=
"btnOnboarding"
>
<a
href=
"#"
id=
"btnAddTodoContainer"
class=
"btnOnboarding"
tabindex=
"0"
>
<i
class=
"fas fa-plus"
></i>
<span
id=
"addTodoContainerButton"
class=
"file-label"
></span>
</a>
...
...
@@ -229,25 +229,23 @@
<section
id=
"noResultContainer"
class=
"contentContainer"
>
<h1
id=
"noResultContainerHeadline"
class=
"title is-1"
></h1>
<p
id=
"noResultContainerSubtitle"
class=
"subtitle"
></p>
<p><button
id=
"btnNoResultContainerResetFilters"
class=
"btnResetFilters
button
"
tabindex=
"0"
><i
class=
"fas fa-ban"
></i>
<span></span></button></p>
<p><button
id=
"btnNoResultContainerResetFilters"
class=
"btnResetFilters"
tabindex=
"0"
><i
class=
"fas fa-ban"
></i>
<span></span></button></p>
</section>
</div>
</div>
<
form
id=
"modalForm"
class=
"modal"
>
<
section
id=
"modalForm"
class=
"modal"
>
<div
class=
"modal-background"
></div>
<div
class=
"modal-content"
>
<div
class=
"card"
>
<header
id=
"modalTitle"
class=
"card-header-title"
></header>
<div
class=
"card-content"
>
<div
class=
"inputWrapper"
>
<label
id=
"modalFormInputLabel"
for=
"modalFormInput"
>
Search by todo.txt syntax
</label>
<label
id=
"modalFormInputLabel"
for=
"modalFormInput"
></label>
<input
id=
"modalFormInput"
class=
"input is-medium"
type=
"text"
tabindex=
"0"
placeholder=
"(A) Todo text +project @context due:2020-12-12 rec:d"
>
<a
href=
"#"
id=
"modalFormInputResize"
class=
"icon is-right"
tabindex=
"-1"
data-input-type=
"input"
><i
class=
"fas fa-expand-alt"
></i></a>
</div>
<div
id=
"autoCompleteContainer"
class=
"card"
></div>
<section
id=
"autoCompleteContainer"
class=
"card"
></section>
<article
class=
"message"
>
<div
id=
"modalFormAlert"
class=
"message-body"
></div>
</article>
...
...
@@ -342,13 +340,13 @@
</div>
<footer
class=
"card-footer"
>
<button
id=
"btnSave"
type=
"submit"
class=
"card-footer-item"
tabindex=
"0"
></button>
<
a
href=
"#"
id=
"btnItemStatus"
class=
"card-footer-item"
tabindex=
"0"
></
a
>
<
a
href=
"#"
class=
"card-footer-item
btnModalC
ancel"
tabindex=
"0"
></
a
>
<button
id=
"btnSave"
class=
"card-footer-item"
tabindex=
"0"
></button>
<
button
id=
"btnItemStatus"
class=
"card-footer-item"
tabindex=
"0"
></
button
>
<
button
class=
"card-footer-item
"
role=
"c
ancel"
tabindex=
"0"
></
button
>
</footer>
</div>
</div>
</
form
>
</
section
>
<div
id=
"modalSettings"
class=
"modal content"
tabindex=
"0"
>
<div
class=
"modal-background"
></div>
...
...
@@ -662,11 +660,11 @@
<div
id=
"filterContext"
>
<div
class=
"card"
>
<div
class=
"card-content"
>
<input
id=
"filterContextInput"
class=
"input"
type=
"text"
>
<input
id=
"filterContextInput"
class=
"input"
type=
"text"
tabindex=
"0"
>
</div>
<footer
class=
"card-footer"
>
<
a
href=
"#"
id=
"filterContextSave"
class=
"card-footer-item"
></a
>
<
a
href=
"#"
id=
"filterContextDelete"
class=
"card-footer-item"
></a>
<
button
id=
"filterContextSave"
class=
"card-footer-item"
tabindex=
"0"
></button
>
<
button
id=
"filterContextDelete"
class=
"card-footer-item"
tabindex=
"0"
></button
</footer
>
</div>
</div>
...
...
@@ -675,16 +673,13 @@
<div
class=
"modal-background"
></div>
<div
class=
"modal-content"
>
<div
class=
"card"
>
<header>
<p
id=
"modalChangeFileTitle"
class=
"card-header-title"
></p>
</header>
<div
class=
"card-content"
>
<table
id=
"modalChangeFileTable"
></table>
</div>
<footer
class=
"card-footer"
>
<
a
href=
"#"
id=
"btnFilesOpenTodoFile"
class=
"card-footer-item"
tabindex=
"0"
><i
class=
"fas fa-folder-open"
></i>
<span
id=
"modalChangeFileOpen"
></span></
a
>
<
a
href=
"#"
id=
"btnFilesCreateTodoFile"
class=
"card-footer-item"
tabindex=
"0"
><i
class=
"fas fa-plus-circle"
></i>
<span
id=
"modalChangeFileCreate"
></span></
a
>
<
a
href=
"#"
class=
"card-footer-item
btnModalC
ancel"
tabindex=
"0"
></
a
>
<
button
id=
"btnFilesOpenTodoFile"
class=
"card-footer-item"
tabindex=
"0"
><i
class=
"fas fa-folder-open"
></i>
<span
id=
"modalChangeFileOpen"
></span></
button
>
<
button
id=
"btnFilesCreateTodoFile"
class=
"card-footer-item"
tabindex=
"0"
><i
class=
"fas fa-plus-circle"
></i>
<span
id=
"modalChangeFileCreate"
></span></
button
>
<
button
class=
"card-footer-item
"
role=
"c
ancel"
tabindex=
"0"
></
button
>
</footer>
</div>
</div>
...
...
@@ -696,8 +691,8 @@
<div
class=
"card"
>
<div
id=
"modalPromptContent"
class=
"card-content"
></div>
<footer
class=
"card-footer"
>
<
a
href=
"#"
id=
"modalPromptConfirm"
class=
"card-footer-item"
tabindex=
"0"
>
Confirm
</a>
<
a
href=
"#"
id=
"modalPromptCancel"
class=
"card-footer-item"
tabindex=
"0"
>
Cancel
</a>
<
button
id=
"modalPromptConfirm"
class=
"card-footer-item"
tabindex=
"0"
></a>
<
button
id=
"modalPromptCancel"
role=
"cancel"
class=
"card-footer-item"
tabindex=
"0"
></a>
</footer>
</div>
</div>
...
...
@@ -708,25 +703,25 @@
<article
class=
"message fixed"
data=
"logging"
>
<div
class=
"message-header"
>
<p><span
id=
"messageLoggingTitle"
></span></p>
<button
class=
"delete close"
aria-label=
"delete"
data-message=
"logging"
></button>
<button
class=
"delete close"
aria-label=
"delete"
data-message=
"logging"
tabindex=
"-1"
></button>
</div>
<div
class=
"message-body"
>
<p><span
id=
"messageLoggingBody"
></span></p>
<p><button
id=
"btnMessageLogging"
class=
"button"
tabindex=
"
0
"
><i
class=
"fas fa-cog"
></i>
<span
id=
"messageLoggingButton"
></span></button></p>
<p><button
id=
"btnMessageLogging"
tabindex=
"
-1
"
><i
class=
"fas fa-cog"
></i>
<span
id=
"messageLoggingButton"
></span></button></p>
</div>
</article>
<article
class=
"message fixed"
data=
"share"
>
<div
class=
"message-header"
>
<p><span
id=
"messageShareTitle"
></span></p>
<button
class=
"delete close"
aria-label=
"delete"
data-message=
"share"
></button>
<button
class=
"delete close"
aria-label=
"delete"
data-message=
"share"
tabindex=
"-1"
></button>
</div>
<div
class=
"message-body"
>
<p><span
id=
"messageShareBody"
></span></p>
<p
class=
"brands"
>
<a
href=
"https://twitter.com/intent/tweet?text=Check%20out%20sleek,%20a%20todo%20app%20based%20on%20todo.txt,%20free%20and%20open-source.%20Available%20for%20Linux,%20Windows%20and%20MacOS.%20https://github.com/ransome1/sleek"
target=
"_blank"
><i
class=
"fab fa-twitter"
></i></a>
<a
href=
"https://www.facebook.com/sharer/sharer.php?u=https://github.com/ransome1/sleek"
target=
"_blank"
><i
class=
"fab fa-facebook-square"
></i></a>
<a
href=
"https://www.linkedin.com/shareArticle?mini=true&url=https://github.com/ransome1/sleek"
target=
"_blank"
><i
class=
"fab fa-linkedin"
></i></a>
<a
tabindex=
"-1"
href=
"https://twitter.com/intent/tweet?text=Check%20out%20sleek,%20a%20todo%20app%20based%20on%20todo.txt,%20free%20and%20open-source.%20Available%20for%20Linux,%20Windows%20and%20MacOS.%20https://github.com/ransome1/sleek"
target=
"_blank"
><i
class=
"fab fa-twitter"
></i></a>
<a
tabindex=
"-1"
href=
"https://www.facebook.com/sharer/sharer.php?u=https://github.com/ransome1/sleek"
target=
"_blank"
><i
class=
"fab fa-facebook-square"
></i></a>
<a
tabindex=
"-1"
href=
"https://www.linkedin.com/shareArticle?mini=true&url=https://github.com/ransome1/sleek"
target=
"_blank"
><i
class=
"fab fa-linkedin"
></i></a>
</p>
</div>
</article>
...
...
src/js/files.mjs
View file @
d2c07460
"
use strict
"
;
import
{
resetModal
,
handleError
,
userData
,
setUserData
,
translations
}
from
"
../render.js
"
;
import
{
resetFilters
,
resetModal
,
handleError
,
userData
,
setUserData
,
translations
}
from
"
../render.js
"
;
import
{
_paq
}
from
"
./matomo.mjs
"
;
import
{
createModalJail
}
from
"
../configs/modal.config.mjs
"
;
...
...
@@ -22,11 +22,15 @@ function showFiles() {
let
cell3
=
row
.
insertCell
(
2
);
row
.
setAttribute
(
"
data-path
"
,
files
[
file
][
1
]);
if
(
files
[
file
][
0
]
===
1
)
{
cell1
.
innerHTML
=
"
<button
class=
\"
button
\"
disabled>
"
+
translations
.
selected
+
"
</button>
"
;
cell1
.
innerHTML
=
"
<button disabled>
"
+
translations
.
selected
+
"
</button>
"
;
}
else
{
cell1
.
innerHTML
=
"
<button
class=
\"
button is-link
\"
tabindex=
\"
0
\"
>
"
+
translations
.
select
+
"
</button>
"
;
cell1
.
innerHTML
=
"
<button tabindex=
\"
0
\"
>
"
+
translations
.
select
+
"
</button>
"
;
cell1
.
onclick
=
function
()
{
setUserData
(
"
selectedFilters
"
,
[]);
resetFilters
().
then
(
function
(
response
)
{
console
.
info
(
response
);
}).
catch
(
function
(
error
)
{
handleError
(
error
);
});
resetModal
().
then
(
response
=>
{
window
.
api
.
send
(
"
startFileWatcher
"
,
this
.
parentElement
.
getAttribute
(
"
data-path
"
));
console
.
info
(
response
);
...
...
src/js/filterlang.mjs
0 → 100644
View file @
d2c07460
This diff is collapsed.
Click to expand it.
src/js/filterlang.pegjs
0 → 100644
View file @
d2c07460
{{
import { addIntervalToDate } from "./recurrences.mjs";
}}
filterQuery
= _ left:orExpr _ { return left; }
/ _ { return []; }
orExpr
= left:andExpr _ OrOp _ right:orExpr { return left.concat(right, ["||"]); }
/ left:andExpr { return left; }
andExpr
= left:notExpr _ AndOp _ right:andExpr { return left.concat(right, ["&&"]); }
/ left:notExpr { return left; }
notExpr
= NotOp _ left:notExpr { return left.concat(["!!"]); }
/ left:boolExpr { return left; }
boolExpr
= left:project { return left; }
/ left:context { return left; }
/ "(" _ left:orExpr _ ")" { return left; }
/ left:comparison { return left; }
/ "complete" { return ["complete"]; }
/ left:StringLiteral { return ["string", left]; }
/ left:RegexLiteral { return ["regex", left]; }
project
= "+" left:name { return ["++", left]; }
/ "+" { return ["++", "*"]; }
context
= "@" left:name { return ["@@", left]; }
/ "@" { return ["@@", "*"]; }
OrOp
= "||"
/ "or"i
AndOp
= "&&"
/ "and"i
NotOp
= "!"
/ "not"i
comparison
= left:priorityComparison { return left; }
/ left:dueComparison { return left; }
priorityComparison
= priorityKeyword _ op:compareOp _ right:priorityLiteral { return ["priority", right, op]; }
/ priorityKeyword { return ["priority"]; }
priorityLiteral
= [A-Z] { return text(); }
priorityKeyword
= "pri" ("o" ("r" ("i" ("t" "y"?)?)?)?)?
dueComparison
= "due" _ op:compareOp _ right:dateExpr { return ["due"].concat(right, [op]); }
/ "due" { return ["due"]; }
dateExpr
= left:dateLiteral _ op:dateOp _ count:number unit:[dbwmy] {
if (op == "-") {
count = count * -1;
}
// we do our date math with the same code as we use for
// recurrence calculations. All dates are returned from
// the parser as millisec since epoch (getTime()) to
// simplify comparisons in the filter lang execution engine.
let d = addIntervalToDate(new Date(left), count, unit);
return d.getTime();
}
/ left:dateLiteral { return left; }
dateOp
= "+" { return text(); }
/ "-" { return text(); }
compareOp
= "==" { return text(); }
/ "!=" { return text(); }
/ ">=" { return text(); }
/ "<=" { return text(); }
/ ">" { return text(); }
/ "<" { return text(); }
dateLiteral
= year:number4 "-" month: number2 "-" day:number2 {
let d = new Date(year, month-1, day);
return d.getTime();
}
/ "today" {
let d = new Date(); // now, w current time of day
d = new Date(d.getFullYear(), d.getMonth(), d.getDate());
return d.getTime();
}
/ "tomorrow" {
let d = new Date(); // now, w current time of day
d = new Date(d.getFullYear(), d.getMonth(), d.getDate());
return d.getTime() + 24*60*60*1000;
}
/ "yesterday" {
let d = new Date(); // now, w current time of day
d = new Date(d.getFullYear(), d.getMonth(), d.getDate());
return d.getTime() - 24*60*60*1000;
}
number4
= [0-9][0-9][0-9][0-9] { return text(); }
number2
= [0-9][0-9] { return text(); }
number
= [0-9]+ { return text(); }
StringLiteral "string"
= '"' chars:DoubleStringCharacter* '"'? {
return chars.join("");
}
/ "'" chars:SingleStringCharacter* "'"? {
return chars.join("");
}
DoubleStringCharacter
= '\\' '"' { return '"'; }
/ !'"' SourceCharacter { return text(); }
SingleStringCharacter
= '\\' "'" { return "'"; }
/ !"'" SourceCharacter { return text(); }
RegexLiteral "regex"
= "/" chars:RegexCharacter* "/" "i" {
return new RegExp(chars.join(""), "i");
}
/ "/" chars:RegexCharacter* "/"? {
return new RegExp(chars.join(""));
}
RegexCharacter
= "\\" "/" { return "/"; }
/ !"/" SourceCharacter { return text(); }
SourceCharacter
= .
name
= '"' nonblank+ '"' { return text(); }
/ nonblank+ '"' { return '"' + text(); }
/ nonblank+ { return text(); }
nonblank
= [^ \t\n\r"()]
_ "whitespace"
= [ \t\n\r]*
src/js/filterquery.mjs
0 → 100644
View file @
d2c07460
// This is a simple stack machine that executes a filter language query
// compiled by filterlang.pegjs (which generates filterlang.mjs).
// The compiled query consists of a list of postfix opcodes designed
// specifically for todo.txt searching and filtering.
function
runQuery
(
item
,
compiledQuery
)
{
if
(
!
compiledQuery
)
{
return
true
;
// a null query matches everything
}
let
stack
=
[];
let
operand1
=
false
;
let
operand2
=
false
;
let
next
=
0
;
let
q
=
compiledQuery
.
slice
();
// shallow copy
while
(
q
.
length
>
0
)
{
const
opcode
=
q
.
shift
();
switch
(
opcode
)
{
case
"
priority
"
:
stack
.
push
(
item
.
priority
);
break
;
case
"
due
"
:
let
d
=
item
.
due
;
if
(
d
)
{
// 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
();
}
stack
.
push
(
d
);
break
;
case
"
complete
"
:
stack
.
push
(
item
.
complete
);
break
;
case
"
string
"
:
next
=
q
.
shift
();
// the string value to match
stack
.
push
(
item
.
toString
().
toLowerCase
().
indexOf
(
next
.
toLowerCase
())
!==
-
1
);
break
;
case
"
regex
"
:
next
=
q
.
shift
();
// the regex to match
stack
.
push
(
next
.
test
(
item
.
toString
()));
break
;
case
"
==
"
:
operand2
=
stack
.
pop
();
operand1
=
stack
.
pop
();
stack
.
push
(
operand1
==
operand2
);
break
;
case
"
!=
"
:
operand2
=
stack
.
pop
();
operand1
=
stack
.
pop
();
stack
.
push
(
operand1
!=
operand2
);
break
;
case
"
<
"
:
operand2
=
stack
.
pop
();
operand1
=
stack
.
pop
();
stack
.
push
(
operand1
<
operand2
);
break
;
case
"
<=
"
:
operand2
=
stack
.
pop
();
operand1
=
stack
.
pop
();
stack
.
push
(
operand1
<=
operand2
);
break
;
case
"
>
"
:
operand2
=
stack
.
pop
();
operand1
=
stack
.
pop
();
stack
.
push
(
operand1
>
operand2
);
break
;
case
"
>=
"
:
operand2
=
stack
.
pop
();
operand1
=
stack
.
pop
();
stack
.
push
(
operand1
>=
operand2
);
break
;
case
"
++
"
:
next
=
q
.
shift
();
if
(
next
==
"
*
"
)
{
stack
.
push
(
item
.
projects
?
true
:
false
);
}
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
stack
.
push
(
item
.
projects
&&
item
.
projects
.
findIndex
(
function
(
p
)
{
return
p
.
indexOf
(
next
)
>
-
1
;
})
>
-
1
);
}
break
;
case
"
@@
"
:
next
=
q
.
shift
();
if
(
next
==
"
*
"
)
{
stack
.
push
(
item
.
contexts
?
true
:
false
);
}
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
stack
.
push
(
item
.
contexts
&&
item
.
contexts
.
findIndex
(
function
(
c
)
{
return
c
.
indexOf
(
next
)
>
-
1
;
})
>
-
1
);
}
break
;
case
"
||
"
:
operand2
=
stack
.
pop
();
operand1
=
stack
.
pop
();
stack
.
push
(
operand1
||
operand2
);
break
;
case
"
&&
"
:
operand2
=
stack
.
pop
();
operand1
=
stack
.
pop
();
stack
.
push
(
operand1
&&
operand2
);
break
;
case
"
!!
"
:
operand1
=
stack
.
pop
();
stack
.
push
(
!
operand1
);
break
;
default
:
// should be a data item like a string or date in millisec, ...
stack
.
push
(
opcode
);
break
;
}
}
return
stack
.
pop
();
}
export
{
runQuery
};
src/js/filters.mjs
View file @
d2c07460
"
use strict
"
;
import
{
userData
,
handleError
,
translations
,
setUserData
,
startBuilding
,
getConfirmation
}
from
"
../render.js
"
;
import
{
createModalJail
}
from
"
../configs/modal.config.mjs
"
;
import
{
_paq
}
from
"
./matomo.mjs
"
;
import
{
items
}
from
"
./todos.mjs
"
;
import
{
isToday
,
isPast
,
isFuture
}
from
"
./date.mjs
"
;
import
*
as
filterlang
from
"
./filterlang.mjs
"
;
import
{
runQuery
}
from
"
./filterquery.mjs
"
;
const
todoTableSearch
=
document
.
getElementById
(
"
todoTableSearch
"
);
const
autoCompleteContainer
=
document
.
getElementById
(
"
autoCompleteContainer
"
);
...
...
@@ -17,6 +20,8 @@ let categories,
filtersCounted
,
filtersCountedReduced
,
selectedFilters
,
lastFilterQueryString
=
null
,
lastFilterItems
=
null
,
container
,
headline
;
...
...
@@ -115,25 +120,36 @@ function filterItems(items) {
});
});
}
if
(
todoTableSearch
.
value
&&
todoTableSearch
.
value
.
startsWith
(
"
?
"
))
{
// if search starts with "?", parse it with filter query language grammar
if
(
todoTableSearch
.
value
)
{
// assume that this is an advanced search expr
let
queryString
=
todoTableSearch
.
value
;
try
{
let
query
=
filterlang
.
parse
(
todoTableSearch
.
value
.
slice
(
1
)
);
let
query
=
filterlang
.
parse
(
queryString
);
if
(
query
.
length
>
0
)
{
items
=
items
.
filter
(
function
(
item
)
{
return
runQuery
(
item
,
query
);
});
lastFilterQueryString
=
queryString
;
lastFilterItems
=
items
;
}
}
catch
(
e
)
{
// if query is malformed, don't match anything, so user can tell that
// query is busted.
items
=
[];
// oops, that wasn't a syntactically correct search expression
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
;
}
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
// plain-text query.
items
=
items
.
filter
(
function
(
item
)
{
return
item
.
toString
().
toLowerCase
().
indexOf
(
queryString
.
toLowerCase
())
!==
-
1
;
});
}
}
}
// apply filters
or filter by search string
// apply filters
items
=
items
.
filter
(
function
(
item
)
{
if
(
!
item
.
text
)
return
false
if
(
todoTableSearch
.
value
&&
!
todoTableSearch
.
value
.
startsWith
(
"
?
"
)
&&
item
.
toString
().
toLowerCase
().
indexOf
(
todoTableSearch
.
value
.
toLowerCase
())
===
-
1
)
return
false
;
if
(
!
userData
.
showHidden
&&
item
.
h
)
return
false
;
if
(
!
userData
.
showCompleted
&&
item
.
complete
)
return
false
;
if
(
!
userData
.
showDueIsToday
&&
item
.
due
&&
isToday
(
item
.
due
))
return
false
;
...
...
@@ -366,12 +382,10 @@ function generateFilterButtons(category, autoCompleteValue, autoCompletePrefix,
for
(
let
filter
in
filtersCounted
)
{
// skip this loop if no filters are present
if
(
!
filter
)
continue
;
let
todoFiltersItem
=
document
.
createElement
(
"
a
"
);
todoFiltersItem
.
setAttribute
(
"
class
"
,
"
button
"
);
let
todoFiltersItem
=
document
.
createElement
(
"
button
"
);
if
(
category
===
"
priority
"
)
todoFiltersItem
.
classList
.
add
(
filter
);
todoFiltersItem
.
setAttribute
(
"
data-filter
"
,
filter
);
todoFiltersItem
.
setAttribute
(
"
data-category
"
,
category
);
todoFiltersItem
.
setAttribute
(
"
href
"
,
"
#
"
);
if
(
autoCompletePrefix
===
undefined
)
{
todoFiltersItem
.
setAttribute
(
"
tabindex
"
,
0
)
}
else
{
todoFiltersItem
.
setAttribute
(
"
tabindex
"
,
0
)
}
todoFiltersItem
.
innerHTML
=
filter
;
if
(
autoCompletePrefix
==
undefined
)
{
...
...
@@ -381,6 +395,9 @@ function generateFilterButtons(category, autoCompleteValue, autoCompletePrefix,
});
// add context menu
todoFiltersItem
.
addEventListener
(
"
contextmenu
"
,
event
=>
{
// jail the modal
createModalJail
(
filterContext
);
filterContext
.
style
.
left
=
event
.
x
+
"
px
"
;
filterContext
.
style
.
top
=
event
.
y
+
"
px
"
;
filterContext
.
classList
.
add
(
"
is-active
"
);
...
...
@@ -432,14 +449,17 @@ function generateFilterButtons(category, autoCompleteValue, autoCompletePrefix,
// autocomplete container
}
else
{
// add filter to input
todoFiltersItem
.
addEventListener
(
"
click
"
,
()
=>
{
if
(
autoCompleteValue
)
{
todoFiltersItem
.
addEventListener
(
"
click
"
,
(
event
)
=>
{
if
(
autoCompletePrefix
&&
autoCompleteValue
)
{
// remove composed filter first, then add selected filter
document
.
getElementById
(
"
modalFormInput
"
).
value
=
document
.
getElementById
(
"
modalFormInput
"
).
value
.
replace
(
autoCompletePrefix
+
autoCompleteValue
,
autoCompletePrefix
+
todoFiltersItem
.
getAttribute
(
"
data-filter
"
)
+
"
"
);
}
else
{
}
else
if
(
autoCompletePrefix
)
{
// add button data value to the exact caret position
document
.
getElementById
(
"
modalFormInput
"
).
value
=
[
document
.
getElementById
(
"
modalFormInput
"
).
value
.
slice
(
0
,
caretPosition
),
todoFiltersItem
.
getAttribute
(
'
data-filter
'
),
document
.
getElementById
(
"
modalFormInput
"
).
value
.
slice
(
caretPosition
)].
join
(
''
)
+
"
"
;