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
42d48a4a
Commit
42d48a4a
authored
May 30, 2021
by
ransome1
Browse files
Added cluster based rendering of todos to improve performance
parent
7b7cb513
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
.github/workflows/github-ci.yml
View file @
42d48a4a
...
...
@@ -95,27 +95,10 @@ jobs:
uses
:
github/codeql-action/autobuild@v1
-
name
:
Perform CodeQL Analysis
uses
:
github/codeql-action/analyze@v1
# mirror:
# needs: codeql
# name: Mirror code to opencode.net
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v1
# - name: Mirror + trigger CI
# uses: SvanBoxel/gitlab-mirror-and-ci-action@master
# with:
# args: "https://www.opencode.net/ransome/sleek.git"
# env:
# GITLAB_HOSTNAME: "opencode.net"
# GITLAB_USERNAME: "ransome"
# GITLAB_PASSWORD: ${{ secrets.gitlab_password }}
# GITLAB_PROJECT_ID: "https://www.opencode.net/ransome/sleek/edit"
# GITHUB_TOKEN: ${{ secrets.github_token }}
mirror
:
name
:
Mirror code to opencode.net
runs-on
:
ubuntu-latest
#
needs: codeql
needs
:
codeql
steps
:
# <-- must use actions/checkout@v1 before mirroring!
-
uses
:
actions/checkout@v1
-
uses
:
pixta-dev/repository-mirroring-action@v1
...
...
@@ -124,21 +107,3 @@ jobs:
git@www.opencode.net:ransome/sleek.git
ssh_private_key
:
# <-- use 'secrets' to pass credential information.
${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
# publish-aur-package:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
#
# - name: Publish AUR package
# uses: KSXGitHub/github-actions-deploy-aur@v2
# with:
# pkgname: sleek
# pkgbuild: ./PKGBUILD
# commit_username: ${{ secrets.AUR_USERNAME }}
# commit_email: ${{ secrets.AUR_EMAIL }}
# ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
# commit_message: Update to latest sleek release
package.json
View file @
42d48a4a
{
"name"
:
"sleek"
,
"productName"
:
"sleek"
,
"version"
:
"1.0.5-
3
"
,
"version"
:
"1.0.5-
4
"
,
"description"
:
"Todo app based on todo.txt for Linux, Windows and MacOS, free and open-source"
,
"synopsis"
:
"Todo app based on todo.txt for Linux, Windows and MacOS, free and open-source"
,
"category"
:
"ProjectManagement"
,
...
...
@@ -53,7 +53,10 @@
"mac"
:
{
"target"
:
{
"target"
:
"default"
,
"arch"
:
[
"arm64"
,
"x64"
]
"arch"
:
[
"arm64"
,
"x64"
]
},
"icon"
:
"assets/icons/sleek.icns"
,
"category"
:
"public.app-category.productivity"
...
...
src/css/style.css
View file @
42d48a4a
...
...
@@ -157,7 +157,7 @@ svg {
code
,
pre
{
font-family
:
SFMono-Regular
,
Consolas
,
Liberation
Mono
,
Menlo
,
monospace
!important
;
background-color
:
transparent
!important
;
background-color
:
#ebebeb
;
color
:
inherit
!important
;
}
...
...
src/css/style.css.map
View file @
42d48a4a
This diff is collapsed.
Click to expand it.
src/index.html
View file @
42d48a4a
...
...
@@ -155,7 +155,7 @@
</div>
</nav>
</div>
<div
class=
"column content"
>
<div
id=
"todoTableWrapper"
class=
"column content"
>
<div
id=
"todoTableSearchContainer"
class=
"control has-icons-left"
>
<div
class=
"column"
>
<input
id=
"todoTableSearch"
class=
"input is-medium"
type=
"search"
placeholder=
"Search"
tabindex=
"10"
>
...
...
src/js/todos.mjs
View file @
42d48a4a
"
use strict
"
;
import
{
userData
,
appData
,
handleError
,
translations
,
setUserData
,
_paq
}
from
"
../render.js
"
;
import
{
userData
,
appData
,
handleError
,
translations
,
setUserData
,
_paq
,
startBuilding
}
from
"
../render.js
"
;
import
{
RecExtension
}
from
"
./todotxtExtensions.mjs
"
;
import
{
categories
}
from
"
./filters.mjs
"
;
import
{
generateRecurrence
}
from
"
./recurrences.mjs
"
;
import
{
convertDate
,
isToday
,
isTomorrow
,
isPast
}
from
"
./date.mjs
"
;
import
{
show
}
from
"
./form.mjs
"
;
const
modalForm
=
document
.
getElementById
(
"
modalForm
"
);
const
todoTableWrapper
=
document
.
getElementById
(
"
todoTableWrapper
"
);
const
todoTableContainer
=
document
.
getElementById
(
"
todoTableContainer
"
);
// ########################################################################################################################
// CONFIGURE MARKDOWN PARSER
...
...
@@ -44,8 +45,67 @@ const todoTableBodyCellSpacerTemplate = document.createElement("div");
const
todoTableBodyCellDueDateTemplate
=
document
.
createElement
(
"
span
"
);
const
todoTableBodyCellRecurrenceTemplate
=
document
.
createElement
(
"
span
"
);
const
item
=
{
previous
:
""
}
let
items
;
let
visibleRows
;
let
items
,
clusterCounter
,
clusterSize
=
Math
.
ceil
(
window
.
innerHeight
/
35
),
// 35 being the pixel height of one todo in compact mode
clusterThreshold
=
0
,
stopBuilding
=
false
,
visibleRows
=
0
;
todoTableWrapper
.
addEventListener
(
"
scroll
"
,
function
(
event
)
{
if
((
event
.
target
.
scrollHeight
-
event
.
target
.
scrollTop
===
event
.
target
.
clientHeight
)
&&
visibleRows
<
items
.
filtered
.
length
)
{
stopBuilding
=
false
;
startBuilding
(
null
,
true
);
}
});
function
configureTodoTableTemplate
(
append
)
{
try
{
if
(
!
append
)
{
todoTableContainer
.
innerHTML
=
""
;
visibleRows
=
0
;
clusterThreshold
=
0
;
stopBuilding
=
false
;
}
todoTableBodyCellMoreTemplate
.
setAttribute
(
"
class
"
,
"
flex-row todoTableItemMore
"
);
todoTableBodyCellMoreTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellMoreTemplate
.
innerHTML
=
`
<div class="dropdown is-right">
<div class="dropdown-trigger">
<a href="#"><i class="fas fa-ellipsis-v"></i></a>
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item">`
+
translations
.
useAsTemplate
+
`</a>
<a href="#" class="dropdown-item">`
+
translations
.
edit
+
`</a>
<a class="dropdown-item">`
+
translations
.
delete
+
`</a>
</div>
</div>
</div>
`
;
todoTableBodyRowTemplate
.
setAttribute
(
"
role
"
,
"
rowgroup
"
);
todoTableBodyRowTemplate
.
setAttribute
(
"
class
"
,
"
flex-table
"
);
todoTableBodyCellCheckboxTemplate
.
setAttribute
(
"
class
"
,
"
flex-row checkbox
"
);
todoTableBodyCellCheckboxTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellTextTemplate
.
setAttribute
(
"
class
"
,
"
flex-row text
"
);
todoTableBodyCellTextTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellTextTemplate
.
setAttribute
(
"
tabindex
"
,
0
);
todoTableBodyCellTextTemplate
.
setAttribute
(
"
href
"
,
"
#
"
);
todoTableBodyCellTextTemplate
.
setAttribute
(
"
title
"
,
translations
.
editTodo
);
tableContainerCategoriesTemplate
.
setAttribute
(
"
class
"
,
"
categories
"
);
todoTableBodyCellPriorityTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellSpacerTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellDueDateTemplate
.
setAttribute
(
"
class
"
,
"
flex-row itemDueDate
"
);
todoTableBodyCellDueDateTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellRecurrenceTemplate
.
setAttribute
(
"
class
"
,
"
flex-row recurrence
"
);
todoTableBodyCellRecurrenceTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
return
Promise
.
resolve
(
"
Success: Table templates set up
"
);
}
catch
(
error
)
{
error
.
functionName
=
configureTodoTableTemplate
.
name
;
return
Promise
.
reject
(
error
);
}
}
function
generateItems
(
content
)
{
try
{
items
=
{
objects
:
TodoTxt
.
parse
(
content
,
[
new
DueExtension
(),
new
RecExtension
(),
new
HiddenExtension
()
])
}
...
...
@@ -99,42 +159,50 @@ function generateGroups(items) {
return
0
;
});
}
// sort the items within the groups
items
.
forEach
((
group
)
=>
{
group
[
1
]
=
sortTodoData
(
group
[
1
]);
});
return
Promise
.
resolve
(
items
)
}
function
generateTable
(
groups
)
{
function
generateTable
(
groups
,
append
)
{
// prepare the templates for the table
return
configureTodoTableTemplate
().
then
(
function
(
response
)
{
return
configureTodoTableTemplate
(
append
).
then
(
function
(
response
)
{
clusterCounter
=
0
;
console
.
info
(
response
);
visibleRows
=
0
;
for
(
let
group
in
groups
)
{
if
(
stopBuilding
)
{
stopBuilding
=
false
;
break
;
}
// create a divider row
let
dividerRow
;
// completed todos
if
(
userData
.
sortCompletedLast
&&
groups
[
group
][
0
]
===
"
completed
"
)
{
tableContainerContent
.
appendChild
(
document
.
createRange
().
createContextualFragment
(
"
<div class=
\"
flex-table group
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row
\"
role=
\"
cell
\"
></div></div>
"
)
)
dividerRow
=
document
.
createRange
().
createContextualFragment
(
"
<div
id=
\"
"
+
userData
.
sortBy
+
groups
[
group
][
0
]
+
"
\"
class=
\"
flex-table group
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row
\"
role=
\"
cell
\"
></div></div>
"
)
// for priority, context and project
}
else
if
(
groups
[
group
][
0
]
!=
"
null
"
&&
userData
.
sortBy
!=
"
dueString
"
)
{
tableContainerContent
.
appendChild
(
document
.
createRange
().
createContextualFragment
(
"
<div class=
\"
flex-table
"
+
userData
.
sortBy
+
"
group
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row
\"
role=
\"
cell
\"
><span class=
\"
button
"
+
groups
[
group
][
0
]
+
"
\"
>
"
+
groups
[
group
][
0
].
replace
(
/,/g
,
'
,
'
)
+
"
</span></div></div>
"
)
)
dividerRow
=
document
.
createRange
().
createContextualFragment
(
"
<div
id=
\"
"
+
userData
.
sortBy
+
groups
[
group
][
0
]
+
"
\"
class=
\"
flex-table
"
+
userData
.
sortBy
+
"
group
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row
\"
role=
\"
cell
\"
><span class=
\"
button
"
+
groups
[
group
][
0
]
+
"
\"
>
"
+
groups
[
group
][
0
].
replace
(
/,/g
,
'
,
'
)
+
"
</span></div></div>
"
)
// if sorting is by due date
}
else
if
(
userData
.
sortBy
===
"
dueString
"
&&
groups
[
group
][
1
][
0
].
due
)
{
if
(
isToday
(
groups
[
group
][
1
][
0
].
due
))
{
tableContainerContent
.
appendChild
(
document
.
createRange
().
createContextualFragment
(
"
<div class=
\"
flex-table group due
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row isToday
\"
role=
\"
cell
\"
><span class=
\"
button
\"
>
"
+
translations
.
today
+
"
</span></div></div>
"
)
);
dividerRow
=
document
.
createRange
().
createContextualFragment
(
"
<div
id=
\"
"
+
userData
.
sortBy
+
groups
[
group
][
0
]
+
"
\"
class=
\"
flex-table group due
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row isToday
\"
role=
\"
cell
\"
><span class=
\"
button
\"
>
"
+
translations
.
today
+
"
</span></div></div>
"
)
}
else
if
(
isTomorrow
(
groups
[
group
][
1
][
0
].
due
))
{
tableContainerContent
.
appendChild
(
document
.
createRange
().
createContextualFragment
(
"
<div class=
\"
flex-table group due
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row isTomorrow
\"
role=
\"
cell
\"
><span class=
\"
button
\"
>
"
+
translations
.
tomorrow
+
"
</span></div></div>
"
)
);
dividerRow
=
document
.
createRange
().
createContextualFragment
(
"
<div
id=
\"
"
+
userData
.
sortBy
+
groups
[
group
][
0
]
+
"
\"
class=
\"
flex-table group due
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row isTomorrow
\"
role=
\"
cell
\"
><span class=
\"
button
\"
>
"
+
translations
.
tomorrow
+
"
</span></div></div>
"
)
}
else
if
(
isPast
(
groups
[
group
][
1
][
0
].
due
))
{
tableContainerContent
.
appendChild
(
document
.
createRange
().
createContextualFragment
(
"
<div class=
\"
flex-table group due
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row isPast
\"
role=
\"
cell
\"
><span class=
\"
button
\"
>
"
+
groups
[
group
][
0
]
+
"
</span></div></div>
"
)
);
dividerRow
=
document
.
createRange
().
createContextualFragment
(
"
<div
id=
\"
"
+
userData
.
sortBy
+
groups
[
group
][
0
]
+
"
\"
class=
\"
flex-table group due
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row isPast
\"
role=
\"
cell
\"
><span class=
\"
button
\"
>
"
+
groups
[
group
][
0
]
+
"
</span></div></div>
"
)
}
else
{
tableContainerContent
.
appendChild
(
document
.
createRange
().
createContextualFragment
(
"
<div class=
\"
flex-table group due
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row
\"
role=
\"
cell
\"
><span class=
\"
button
\"
>
"
+
groups
[
group
][
0
]
+
"
</span></div></div>
"
)
)
dividerRow
=
document
.
createRange
().
createContextualFragment
(
"
<div
id=
\"
"
+
userData
.
sortBy
+
groups
[
group
][
0
]
+
"
\"
class=
\"
flex-table group due
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row
\"
role=
\"
cell
\"
><span class=
\"
button
\"
>
"
+
groups
[
group
][
0
]
+
"
</span></div></div>
"
)
}
// create an empty divider row
}
else
{
tableContainerContent
.
appendChild
(
document
.
createRange
().
createContextualFragment
(
"
<div class=
\"
flex-table group
\"
role=
\"
rowgroup
\"
><div class=
\"
flex-row
\"
role=
\"
cell
\"
></div></div>
"
))
}
// sort items within this group
const
sortedGroup
=
sortTodoData
(
groups
[
group
][
1
]);
//const sortedGroup = groups[group][1];
// build the fragments per group
for
(
let
item
in
sortedGroup
)
{
let
todo
=
sortedGroup
[
item
];
}
/*else {
dividerRow = document.createRange().createContextualFragment("<div class=\"flex-table group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"></div></div>")
}*/
// add divider row only if it doesn't exist yet
if
(
!
document
.
getElementById
(
userData
.
sortBy
+
groups
[
group
][
0
])
&&
dividerRow
)
tableContainerContent
.
appendChild
(
dividerRow
);
for
(
let
item
in
groups
[
group
][
1
])
{
let
todo
=
groups
[
group
][
1
][
item
];
// if this todo is not a recurring one the rec value will be set to null
if
(
!
todo
.
rec
)
{
todo
.
rec
=
null
;
...
...
@@ -163,6 +231,14 @@ function generateTable(groups) {
});
}
}
if
(
clusterCounter
<
clusterThreshold
)
{
clusterCounter
++
;
continue
;
}
else
if
((
visibleRows
===
clusterSize
+
clusterThreshold
)
||
visibleRows
===
items
.
filtered
.
length
)
{
clusterThreshold
=
visibleRows
;
stopBuilding
=
true
;
break
;
}
tableContainerContent
.
appendChild
(
generateTableRow
(
todo
));
}
// TODO add a catch
...
...
@@ -177,6 +253,7 @@ function generateTable(groups) {
}
function
generateTableRow
(
todo
)
{
try
{
clusterCounter
++
;
visibleRows
++
;
// create nodes from templates
let
todoTableBodyRow
=
todoTableBodyRowTemplate
.
cloneNode
(
true
);
...
...
@@ -494,49 +571,6 @@ function checkIsTodoVisible(todo) {
}
return
true
;
}
function
configureTodoTableTemplate
()
{
try
{
todoTableContainer
.
innerHTML
=
""
;
todoTableBodyCellMoreTemplate
.
setAttribute
(
"
class
"
,
"
flex-row todoTableItemMore
"
);
todoTableBodyCellMoreTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellMoreTemplate
.
innerHTML
=
`
<div class="dropdown is-right">
<div class="dropdown-trigger">
<a href="#"><i class="fas fa-ellipsis-v"></i></a>
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item">`
+
translations
.
useAsTemplate
+
`</a>
<a href="#" class="dropdown-item">`
+
translations
.
edit
+
`</a>
<a class="dropdown-item">`
+
translations
.
delete
+
`</a>
</div>
</div>
</div>
`
;
todoTableBodyRowTemplate
.
setAttribute
(
"
role
"
,
"
rowgroup
"
);
todoTableBodyRowTemplate
.
setAttribute
(
"
class
"
,
"
flex-table
"
);
todoTableBodyCellCheckboxTemplate
.
setAttribute
(
"
class
"
,
"
flex-row checkbox
"
);
todoTableBodyCellCheckboxTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellTextTemplate
.
setAttribute
(
"
class
"
,
"
flex-row text
"
);
todoTableBodyCellTextTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellTextTemplate
.
setAttribute
(
"
tabindex
"
,
0
);
todoTableBodyCellTextTemplate
.
setAttribute
(
"
href
"
,
"
#
"
);
todoTableBodyCellTextTemplate
.
setAttribute
(
"
title
"
,
translations
.
editTodo
);
tableContainerCategoriesTemplate
.
setAttribute
(
"
class
"
,
"
categories
"
);
todoTableBodyCellPriorityTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellSpacerTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellDueDateTemplate
.
setAttribute
(
"
class
"
,
"
flex-row itemDueDate
"
);
todoTableBodyCellDueDateTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
todoTableBodyCellRecurrenceTemplate
.
setAttribute
(
"
class
"
,
"
flex-row recurrence
"
);
todoTableBodyCellRecurrenceTemplate
.
setAttribute
(
"
role
"
,
"
cell
"
);
return
Promise
.
resolve
(
"
Success: Table templates set up
"
);
}
catch
(
error
)
{
error
.
functionName
=
configureTodoTableTemplate
.
name
;
return
Promise
.
reject
(
error
);
}
}
function
generateNotification
(
todo
,
offset
)
{
try
{
let
notifications
=
userData
.
notifications
;
...
...
src/render.js
View file @
42d48a4a
...
...
@@ -56,6 +56,7 @@ const todoTableSearch = document.getElementById("todoTableSearch");
const
todoTableSearchContainer
=
document
.
getElementById
(
"
todoTableSearchContainer
"
);
const
welcomeToSleek
=
document
.
getElementById
(
"
welcomeToSleek
"
);
let
append
=
false
,
_paq
,
a0
,
a1
,
appData
,
...
...
@@ -861,9 +862,9 @@ function showOnboarding(variable) {
function
showResultStats
()
{
try
{
// we show some information on filters if any are set
if
(
todos
.
visibleRows
!=
todos
.
items
.
objects
.
length
)
{
if
(
todos
.
items
.
filtered
.
length
!=
todos
.
items
.
objects
.
length
)
{
resultStats
.
classList
.
add
(
"
is-active
"
);
resultStats
.
firstElementChild
.
innerHTML
=
translations
.
visibleTodos
+
"
<strong>
"
+
todos
.
visibleRows
+
"
</strong>
"
+
translations
.
of
+
"
<strong>
"
+
todos
.
items
.
objects
.
length
+
"
</strong>
"
;
resultStats
.
firstElementChild
.
innerHTML
=
translations
.
visibleTodos
+
"
<strong>
"
+
todos
.
items
.
filtered
.
length
+
"
</strong>
"
+
translations
.
of
+
"
<strong>
"
+
todos
.
items
.
objects
.
length
+
"
</strong>
"
;
return
Promise
.
resolve
(
"
Info: Result box is shown
"
);
}
else
{
resultStats
.
classList
.
remove
(
"
is-active
"
);
...
...
@@ -939,7 +940,7 @@ function showFiles() {
return
Promise
.
reject
(
error
);
}
}
async
function
startBuilding
(
searchString
)
{
async
function
startBuilding
(
searchString
,
append
)
{
try
{
t0
=
performance
.
now
();
...
...
@@ -950,7 +951,7 @@ async function startBuilding(searchString) {
const
groups
=
await
todos
.
generateGroups
(
todos
.
items
.
filtered
);
await
todos
.
generateTable
(
groups
);
await
todos
.
generateTable
(
groups
,
append
);
userData
=
await
getUserData
();
...
...
@@ -958,14 +959,14 @@ async function startBuilding(searchString) {
showResultStats
();
t1
=
performance
.
now
();
console
.
info
(
"
Table build:
"
,
t1
-
t0
,
"
ms
"
);
console
.
info
(
"
Table build:
"
,
performance
.
now
()
-
t0
,
"
ms
"
);
}
catch
(
error
)
{
error
.
functionName
=
startBuilding
.
name
;
return
Promise
.
reject
(
error
);
}
}
window
.
onload
=
async
function
()
{
a0
=
performance
.
now
();
...
...
src/scss/style.scss
View file @
42d48a4a
...
...
@@ -140,7 +140,7 @@ svg {
}
code
,
pre
{
font-family
:
SFMono-Regular
,
Consolas
,
Liberation
Mono
,
Menlo
,
monospace
!
important
;
background-color
:
transparent
!
important
;
background-color
:
$light-grey
;
color
:
inherit
!
important
;
}
nav
{
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment