Commit 7b7cb513 authored by ransome1's avatar ransome1
Browse files

Merge branch 'develop'

parents 9b36258d ce736871
......@@ -95,22 +95,38 @@ 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:
needs: codeql
name: Mirror code to opencode.net
runs-on: ubuntu-latest
steps:
# needs: codeql
steps: # <-- must use actions/checkout@v1 before mirroring!
- uses: actions/checkout@v1
- name: Mirror + trigger CI
uses: SvanBoxel/gitlab-mirror-and-ci-action@master
- uses: pixta-dev/repository-mirroring-action@v1
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 }}
target_repo_url:
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
......
......@@ -92,13 +92,14 @@ A prioritized backlog of new features and known issues can be found <a href="htt
- "rec:y" (annually)
- also more specific recurrences are possible: "rec:2d" (every 2nd day)
* Available contexts and projects will be suggested according to your input
* Todos can be filtered by contexts and projects
* Todos can be filtered by contexts, projects and priorities
* Filters can be renamed or deleted by right clicking on them
* Filters are sorted alphanummerically
* Todos can be sorted and grouped by
- Priorites
- Due dates
- Projects
- Contexts
* Filters are sorted alphanummerically
* Todos can be looked up using full-text search
* Hyperlinks are detected automatically and can be clicked using the icon
* Alarms will be triggered when a todo is due tomorrow or today
......@@ -126,3 +127,4 @@ A prioritized backlog of new features and known issues can be found <a href="htt
- vanillajs-datepicker: https://github.com/mymth/vanillajs-datepicker
- i18next: https://github.com/i18next/i18next
- Matomo: https://github.com/matomo-org/matomo
- chokidar: https://github.com/paulmillr/chokidar
{
"name": "sleek",
"productName": "sleek",
"version": "1.0.4",
"version": "1.0.5-3",
"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,7 @@
"mac": {
"target": {
"target": "default",
"arch": "universal"
"arch": ["arm64", "x64"]
},
"icon": "assets/icons/sleek.icns",
"category": "public.app-category.productivity"
......@@ -63,6 +63,7 @@
},
"win": {
"target": [
"zip",
"portable",
"nsis"
],
......@@ -107,6 +108,7 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.3",
"bulma": "^0.9.2",
"chokidar": "^3.5.1",
"i18next": "^20.2.2",
"i18next-browser-languagedetector": "^6.1.0",
"i18next-fs-backend": "^1.1.1",
......
......@@ -6,10 +6,25 @@ class Store {
constructor(opts) {
// Renderer process has to get `app` module via `remote`, whereas the main process can get it directly
// app.getPath('userData') will return a string of the user's app data directory path.
const userDataPath = (electron.app || electron.remote.app).getPath('userData');
let userDataPath;
//userDataPath = path.dirname(app.getPath('exe'));
//fs.mkdirSync(userDataPath + '\config\sleek');
//this.path = path.join(userDataPath, opts.configName + '.json');
//console.log(path.join(path.dirname(process.execPath), 'config', 'sleek'));
if(process.env.PORTABLE_EXECUTABLE_FILE) {
userDataPath = path.join(path.dirname(process.env.PORTABLE_EXECUTABLE_FILE), 'config', 'sleek');
if(!fs.existsSync(userDataPath)) fs.mkdirSync(userDataPath, {recursive: true});
} else if(!process.env.PORTABLE_EXECUTABLE_FILE && process.platform==="win32") {
userDataPath = path.dirname(process.execPath);
if(!fs.existsSync(userDataPath)) fs.mkdirSync(userDataPath, {recursive: true});
} else {
userDataPath = (electron.app || electron.remote.app).getPath('userData');
}
// We'll use the `configName` property to set the file name and path.join to bring it all together as a string
this.path = path.join(userDataPath, opts.configName + '.json');
this.data = parseDataFile(this.path, opts.defaults);
}
......
......@@ -265,6 +265,17 @@ nav ul li.is-highlighted a {
background: #212224 !important;
}
#filterMenu .card {
background: #3B3B3B;
}
#filterMenu .card #filterMenuSave {
background-color: transparent !important;
border-right: 1px solid #212224 !important;
}
#filterMenu .card-footer {
border-top: 1px solid #212224 !important;
}
.contexts .button {
color: #c5ede3;
background: #247561;
......
{"version":3,"sourceRoot":"","sources":["../scss/dark.scss","../scss/variables.scss"],"names":[],"mappings":"AAGA;EACE,kBCOa;EDNb;;;AAEF;EACE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;AAAA;EAGE,OCZa;;;ADcf;AAAA;AAAA;EAGE;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAOE;EACA;;;AAEF;EACE;;;AAEF;AAAA;EAEE;EACA;;;AAEF;EACE;;;AAEF;AAAA;EAEE;;AACA;AAAA;EACE;;;AAGJ;AAAA;EAEE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE,kBC3EY;;AD4EZ;EACE,OCjFW;;ADoFX;EACE;;AAGA;EACE,OCzFO;ED0FP;;AAEF;AAAA;EAEE,OC9FO;ED+FP;EACA,kBC3FW;;AD8Ff;EACE,OCpGS;EDqGT;EACA,kBCjGa;;;ADuGf;EACE;;AAEF;EACE;;;AAIN;EACE,kBC/Ga;;ADgHb;EACE;;;AAIF;EACE;EACA;;;AAIF;EACE,kBC7He;;AD+HjB;EACE,OCrIW;;ADuIb;EACE;;AAGA;EACE,OC7IS;;ADgJb;EACE,cC3Ie;;AD4If;EACE,OClJS;;ADoJX;EACE;;AAEF;EACE,OCxJS;;AD0JX;EACE;;AACA;EACE,OCxJW;;AD2Jf;AAAA;EAEE,OC1JY;;AD2JZ;AAAA;EACE;;AAIF;EACE;;AAIF;EACE;EACA;;AAEF;EACE;EACA;;AAEF;EACE,OCtLO;EDuLP;;AAGJ;EACE,OC3LS;;AD6LX;EACE,YCzLa;ED0Lb;;AAEF;EACE,YC9LQ;;;ADmMZ;EACE;EACA;EACA;;AAEF;EACE;;AAGA;AAAA;AAAA;AAAA;EAIE;;AAEF;EACE,YClNa;;;ADuNjB;AAAA;EAEE;EACA;EACA;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE,YCrOU;;ADsOV;EACE,cCrOS;;;ADyOf;EACE,YC3OiB;;AD4OjB;EACE,OClPW;;;ADsPb;EACE;;AACA;EACE;;AAIJ;EACE;;AAEF;EACE;;AAEF;EACE;;;AAKF;EACE,OC7PoB;ED8PpB;;AACA;EACE;EACA;;AAGJ;EACE;EACA;;;AAIF;EACE,OC1QoB;ED2QpB;;AACA;EACE;EACA;;AAGJ;EACE;EACA;;;AAIF;EACE;;;AAKA;EACE,kBCzSM;;AD2SR;EACE;;AAEF;EACE;;AAEF;EACE;;;AAMF;EACE,YCvTa;EDwTb,OC7TS;;AD8TT;EACE;;AAEF;AACE;AAAA;AAAA;;AAIF;AAAA;EAEE,cCpUM;;;ADyUd;EACE;;AACA;EACE;;AAEF;EACE;;AAEF;AAAA;AAAA;EAGE;EACA;;AAEF;EACE;;AAEF;AAAA;EAEE;EACA;;AAEF;AAAA;EAEE,YChWe;EDiWf;;AAEF;AAAA;EAEE;;AAEF;AAAA;AAAA;EAGE;;AAEF;EACE;;AAEF;EACE;EACA,cCnXQ;;ADqXV;EACE,cCzXW;;AD2Xb;EACE;EACA,cC7XW;;;ADgYf;EACE;;AACA;EACE,OCnYW;EDoYX;;AACA;EACE;;;AAKJ;AAAA;AAAA;EAGE;EACA;EACA;;;AAGJ;EACE;;AACA;EACE;;AAEF;AAAA;EAEE;;AAEF;AAAA;EAEE;;AAEF;EACE,YC7ZU;ED8ZV;;AACA;AAAA;AAAA;EAGE;EACA;;AAEF;EACE;;AAEF;AAAA;EAEE,OC1aQ;ED2aR;;AAEF;AAAA;EAEE,OC/aQ;EDgbR,YCpbS;;;ADybb;EACE;;AAEF;AAAA;EAEE,OC9bW","file":"dark.css"}
\ No newline at end of file
{"version":3,"sourceRoot":"","sources":["../scss/dark.scss","../scss/variables.scss"],"names":[],"mappings":"AAGA;EACE,kBCOa;EDNb;;;AAEF;EACE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;AAAA;EAGE,OCZa;;;ADcf;AAAA;AAAA;EAGE;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAOE;EACA;;;AAEF;EACE;;;AAEF;AAAA;EAEE;EACA;;;AAEF;EACE;;;AAEF;AAAA;EAEE;;AACA;AAAA;EACE;;;AAGJ;AAAA;EAEE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE,kBC3EY;;AD4EZ;EACE,OCjFW;;ADoFX;EACE;;AAGA;EACE,OCzFO;ED0FP;;AAEF;AAAA;EAEE,OC9FO;ED+FP;EACA,kBC3FW;;AD8Ff;EACE,OCpGS;EDqGT;EACA,kBCjGa;;;ADuGf;EACE;;AAEF;EACE;;;AAIN;EACE,kBC/Ga;;ADgHb;EACE;;;AAIF;EACE;EACA;;;AAIF;EACE,kBC7He;;AD+HjB;EACE,OCrIW;;ADuIb;EACE;;AAGA;EACE,OC7IS;;ADgJb;EACE,cC3Ie;;AD4If;EACE,OClJS;;ADoJX;EACE;;AAEF;EACE,OCxJS;;AD0JX;EACE;;AACA;EACE,OCxJW;;AD2Jf;AAAA;EAEE,OC1JY;;AD2JZ;AAAA;EACE;;AAIF;EACE;;AAIF;EACE;EACA;;AAEF;EACE;EACA;;AAEF;EACE,OCtLO;EDuLP;;AAGJ;EACE,OC3LS;;AD6LX;EACE,YCzLa;ED0Lb;;AAEF;EACE,YC9LQ;;;ADmMZ;EACE;EACA;EACA;;AAEF;EACE;;AAGA;AAAA;AAAA;AAAA;EAIE;;AAEF;EACE,YClNa;;;ADuNjB;AAAA;EAEE;EACA;EACA;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE,YCrOU;;ADsOV;EACE,cCrOS;;;ADyOf;EACE,YC3OiB;;AD4OjB;EACE,OClPW;;;ADsPb;EACE;;AACA;EACE;;AAIJ;EACE;;AAEF;EACE;;AAEF;EACE;;;AAIF;EACE,YCpQe;;ADqQf;EACE;EACA;;AAGJ;EACE;;;AAIF;EACE,OCxQoB;EDyQpB;;AACA;EACE;EACA;;AAGJ;EACE;EACA;;;AAIF;EACE,OCrRoB;EDsRpB;;AACA;EACE;EACA;;AAGJ;EACE;EACA;;;AAIF;EACE;;;AAKA;EACE,kBCpTM;;ADsTR;EACE;;AAEF;EACE;;AAEF;EACE;;;AAMF;EACE,YClUa;EDmUb,OCxUS;;ADyUT;EACE;;AAEF;AACE;AAAA;AAAA;;AAIF;AAAA;EAEE,cC/UM;;;ADoVd;EACE;;AACA;EACE;;AAEF;EACE;;AAEF;AAAA;AAAA;EAGE;EACA;;AAEF;EACE;;AAEF;AAAA;EAEE;EACA;;AAEF;AAAA;EAEE,YC3We;ED4Wf;;AAEF;AAAA;EAEE;;AAEF;AAAA;AAAA;EAGE;;AAEF;EACE;;AAEF;EACE;EACA,cC9XQ;;ADgYV;EACE,cCpYW;;ADsYb;EACE;EACA,cCxYW;;;AD2Yf;EACE;;AACA;EACE,OC9YW;ED+YX;;AACA;EACE;;;AAKJ;AAAA;AAAA;EAGE;EACA;EACA;;;AAGJ;EACE;;AACA;EACE;;AAEF;AAAA;EAEE;;AAEF;AAAA;EAEE;;AAEF;EACE,YCxaU;EDyaV;;AACA;AAAA;AAAA;EAGE;EACA;;AAEF;EACE;;AAEF;AAAA;EAEE,OCrbQ;EDsbR;;AAEF;AAAA;EAEE,OC1bQ;ED2bR,YC/bS;;;ADocb;EACE;;AAEF;AAAA;EAEE,OCzcW","file":"dark.css"}
\ No newline at end of file
......@@ -155,7 +155,8 @@ svg {
color: #3273dc;
}
code {
code, pre {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace !important;
background-color: transparent !important;
color: inherit !important;
}
......@@ -348,6 +349,10 @@ nav ul:nth-child(2) {
overflow-x: hidden;
padding: 2em;
background: #ebebeb;
/*.dropdown-item.is-greyed-out {
opacity: .75;
-webkit-filter: grayscale(100%);
}*/
/*#btnResetAllFilters {
display: none;
}
......@@ -373,6 +378,13 @@ nav ul:nth-child(2) {
top: -0.75em;
z-index: 35;
}
#drawerContainer .drawer .is-greyed-out {
opacity: 0.75;
-webkit-filter: grayscale(100%);
}
#drawerContainer .drawer .button.is-greyed-out {
cursor: default;
}
#drawerContainer .drawer h4.is-4 {
font-size: 1.35em;
color: #4a4a4a;
......@@ -385,10 +397,6 @@ nav ul:nth-child(2) {
padding: 0;
margin: 0 0 1.5em 0;
}
#drawerContainer .drawer .dropdown-item.is-greyed-out {
opacity: 0.75;
-webkit-filter: grayscale(100%);
}
#drawerContainer .drawer table tr td:nth-child(odd) {
width: auto;
vertical-align: middle;
......@@ -769,6 +777,19 @@ nav ul:nth-child(2) {
display: block;
}
#filterMenu {
display: none;
position: fixed;
z-index: 60;
}
#filterMenu #filterMenuDelete {
color: #ff3860;
}
#filterMenu.is-active {
display: block;
}
.contentContainer {
width: 100%;
height: 90%;
......@@ -1138,6 +1159,9 @@ body.compact #todoTable .flex-table .flex-row.itemDueDate svg.fa-sort-down {
right: 0.225em;
bottom: 1.75em;
}
body.compact #todoTable .flex-table.group {
font-size: 0.8em;
}
body.compact .modal.content .modal-card {
width: 90% !important;
padding: 0.5em;
......@@ -1159,6 +1183,17 @@ body.compact nav ul li.logo {
body.compact .drawer {
padding: 1.5em !important;
}
body.compact .drawer .button {
font-size: 0.9em;
}
body.compact .drawer .button .tag {
padding: 0 0.5em;
width: auto;
height: auto;
min-width: 1.5em;
min-height: 1.5em;
font-size: 0.9em;
}
body.compact #autoCompleteContainer.drawer {
padding: 0 !important;
}
......
This diff is collapsed.
......@@ -479,7 +479,7 @@
</tr>
<tr>
<td><a href="https://github.com/matomo-org/matomo" target="_blank">Matomo</a></td>
<td></td>
<td><a href="https://github.com/paulmillr/chokidar" target="_blank">chokidar</a></td>
<td></td>
<td></td>
</tr>
......@@ -637,6 +637,17 @@
</div>
</div>
<div id="autoCompleteContainer" class="drawer card"></div>
<div id="filterMenu" class="flex-row ">
<div class="card">
<div class="card-content">
<input id="filterMenuInput" class="input" type="text">
</div>
<footer class="card-footer">
<a href="#" id="filterMenuSave" class="card-footer-item"></a>
<a href="#" id="filterMenuDelete" class="card-footer-item"></a>
</footer>
</div>
</div>
<div id="modalChangeFile" class="modal" tabindex="0">
<div class="modal-background"></div>
<div class="modal-content">
......
......@@ -2,11 +2,57 @@
import { userData, handleError, translations, setUserData, startBuilding, _paq } from "../render.js";
import { items, generateGroups, generateTable } from "./todos.mjs";
import { isToday, isPast, isFuture } from "./date.mjs";
const modalFormInput = document.getElementById("modalFormInput");
//const modalFormInput = document.getElementById("modalFormInput");
const todoTableSearch = document.getElementById("todoTableSearch");
const autoCompleteContainer = document.getElementById("autoCompleteContainer");
const todoFilters = document.getElementById("todoFilters");
let categories, filtersCounted, selectedFilters, container, headline;
const filterMenu = document.getElementById("filterMenu");
const filterMenuInput = document.getElementById("filterMenuInput");
const filterMenuSave = document.getElementById("filterMenuSave");
const filterMenuDelete = document.getElementById("filterMenuDelete");
let categories,
filtersCounted,
filtersCountedReduced,
selectedFilters,
container,
headline;
filterMenuSave.innerHTML = translations.save;
filterMenuDelete.innerHTML = translations.delete;
function saveFilter(newFilter, oldFilter, category) {
items.objects.forEach((item) => {
if(category!=="priority" && item[category]) {
const index = item[category].findIndex((el) => el === oldFilter);
item[category][index] = newFilter;
} else if(category==="priority" && item[category]===oldFilter) {
item[category] = newFilter.toUpperCase();
}
});
// persisted filters will be removed
setUserData("selectedFilters", []);
//write the data to the file
// a newline character is added to prevent other todo.txt apps to append new todos to the last line
window.api.send("writeToFile", [items.objects.join("\n").toString() + "\n", userData.file]);
}
function deleteFilter(filter, category) {
items.objects.forEach((item) => {
if(category!=="priority" && item[category]) {
const index = item[category].indexOf(filter);
if(index!==-1) item[category].splice(index, 1);
if(item[category].length===0) item[category] = null;
} else if(category==="priority" && item[category]===filter) {
item[category] = null;
}
});
// persisted filters will be removed
setUserData("selectedFilters", []);
//write the data to the file
// a newline character is added to prevent other todo.txt apps to append new todos to the last line
window.api.send("writeToFile", [items.objects.join("\n").toString() + "\n", userData.file]);
}
function filterItems(items, searchString) {
try {
// selected filters are empty, unless they were persisted
......@@ -80,7 +126,7 @@ function generateFilterData(autoCompleteCategory, autoCompleteValue, autoComplet
// array to collect all the available filters in the data
let filters = new Array();
// run the array and collect all possible filters, duplicates included
items.filtered.forEach((item) => {
items.objects.forEach((item) => {
// check if the object has values in either the project or contexts field
if(item[category]) {
// push all filters found so far into an array
......@@ -126,7 +172,7 @@ function generateFilterData(autoCompleteCategory, autoCompleteValue, autoComplet
// https://wsvincent.com/javascript-remove-duplicates-array/
filters = [...new Set(filters.join(",").split(","))];
// filter persisted filters
if(userData.selectedFilters && userData.selectedFilters.length>0) {
/*if(userData.selectedFilters && userData.selectedFilters.length>0) {
selectedFilters = JSON.parse(userData.selectedFilters);
// check if selected filters is still part of all available filters
selectedFilters.forEach(function(selectedFilter,index){
......@@ -140,7 +186,45 @@ function generateFilterData(autoCompleteCategory, autoCompleteValue, autoComplet
}
}
});
}
}*/
// TODO: basically a duplicate
// count reduced filter when persisted filters are present
let filtersReduced = new Array();
items.filtered.forEach((item) => {
// check if the object has values in either the project or contexts field
if(item[category]) {
// push all filters found so far into an array
for (let filter in item[category]) {
// if user has not opted for showComplete we skip the filter of this particular item
if(userData.showCompleted==false && item.complete==true) {
continue;
// if task is hidden the filter will be marked
} else if(item.h) {
filtersReduced.push([item[category][filter],0]);
} else {
filtersReduced.push([item[category][filter],1]);
}
}
}
});
filtersCountedReduced = filtersReduced.reduce(function(filters, filter) {
// if filter is already in object and should be counted
if (filter[1] && (filter[0] in filters)) {
filters[filter[0]]++;
// new filter in object and should be counted
} else if(filter[1]) {
filters[filter[0]] = 1;
// do not count if filter is suppose to be hidden
// only overwrite value with 0 if the filter doesn't already exist in object
} else if(!filter[1] && !(filter[0] in filters)) {
filters[filter[0]] = 0;
}
if(filters!=null) {
return filters;
}
}, {});
// build the filter buttons
if(filters[0]!="" && filters.length>0) {
generateFilterButtons(category, autoCompleteValue, autoCompletePrefix, caretPosition).then(response => {
......@@ -242,7 +326,8 @@ function generateFilterButtons(category, autoCompleteValue, autoCompletePrefix,
// skip this loop if no filters are present
if(!filter) continue;
let todoFiltersItem = document.createElement("a");
todoFiltersItem.setAttribute("class", "button " + filter);
todoFiltersItem.setAttribute("class", "button");
if(category==="priority") todoFiltersItem.classList.add(filter);
todoFiltersItem.setAttribute("data-filter", filter);
todoFiltersItem.setAttribute("data-category", category);
if(autoCompletePrefix===undefined) { todoFiltersItem.setAttribute("tabindex", 0) } else { todoFiltersItem.setAttribute("tabindex", 301) }
......@@ -253,32 +338,62 @@ function generateFilterButtons(category, autoCompleteValue, autoCompletePrefix,
selectedFilters.forEach(function(item) {
if(JSON.stringify(item) === '["'+filter+'","'+category+'"]') todoFiltersItem.classList.toggle("is-dark")
});
todoFiltersItem.innerHTML += " <span class=\"tag is-rounded\">" + filtersCounted[filter] + "</span>";
// create the event listener for filter selection by user
todoFiltersItem.addEventListener("click", () => {
selectFilter(todoFiltersItem.getAttribute('data-filter'), todoFiltersItem.getAttribute('data-category'))
// trigger matomo event
if(userData.matomoEvents) _paq.push(["trackEvent", "Filter-Drawer", "Click on filter tag", category]);
// add context menu
todoFiltersItem.addEventListener("contextmenu", event => {
filterMenu.style.left = event.x + "px";
filterMenu.style.top = event.y + "px";
filterMenu.classList.add("is-active");
filterMenuInput.value = filter;
filterMenuInput.focus();
filterMenuInput.addEventListener("keyup", function(event) {
if(event.key === "Escape") filterMenu.classList.remove("is-active");
if(event.key === "Enter") {
if(filterMenuInput.value!==filter && filterMenuInput.value) {
saveFilter(filterMenuInput.value, filter, category);
} else {
filterMenu.classList.remove("is-active");
}
}
});
filterMenuSave.onclick = function() {
if(filterMenuInput.value!==filter && filterMenuInput.value) {
saveFilter(filterMenuInput.value, filter, category);
} else {
filterMenu.classList.remove("is-active");
}
}
filterMenuDelete.onclick = function() {
deleteFilter(filter, category);
}
});
if(filtersCountedReduced[filter]) {
todoFiltersItem.innerHTML += " <span class=\"tag is-rounded\">" + filtersCountedReduced[filter] + "</span>";
// create the event listener for filter selection by user
todoFiltersItem.addEventListener("click", () => {
selectFilter(todoFiltersItem.getAttribute('data-filter'), todoFiltersItem.getAttribute('data-category'))
// trigger matomo event
if(userData.matomoEvents) _paq.push(["trackEvent", "Filter-Drawer", "Click on filter tag", category]);
});
} else {
todoFiltersItem.classList.add("is-greyed-out");
todoFiltersItem.innerHTML += " <span class=\"tag is-rounded\">0</span>";
}
// autocomplete container
} else {
// add filter to input
todoFiltersItem.addEventListener("click", () => {
// remove composed filter first, as it is going to be replaced with a filter from suggestion box
if(autoCompleteValue) {
// only if input is not only the prefix, otherwise all existing prefixes will be removed
modalFormInput.value = modalFormInput.value.replace(" " + autoCompletePrefix+autoCompleteValue, "");
// add filter from suggestion box
modalFormInput.value += " " + autoCompletePrefix+todoFiltersItem.getAttribute("data-filter") + " ";
// remove composed filter first, then add selected filter
document.getElementById("modalFormInput").value = document.getElementById("modalFormInput").value.slice(0, caretPosition-autoCompleteValue.length-1) + autoCompletePrefix + todoFiltersItem.getAttribute("data-filter") + document.getElementById("modalFormInput").value.slice(caretPosition) + " ";
} else {
// add button data value to the exact caret position
modalFormInput.value = [modalFormInput.value.slice(0, caretPosition), todoFiltersItem.getAttribute('data-filter'), modalFormInput.value.slice(caretPosition)].join('') + " ";
document.getElementById("modalFormInput").value = [document.getElementById("modalFormInput").value.slice(0, caretPosition), todoFiltersItem.getAttribute('data-filter'), document.getElementById("modalFormInput").value.slice(caretPosition)].join('') + " ";
}
// hide the suggestion container after the filter has been selected
autoCompleteContainer.blur();
autoCompleteContainer.classList.remove("is-active");
// put focus back into input so user can continue writing
modalFormInput.focus();
document.getElementById("modalFormInput").focus();
// trigger matomo event
if(userData.matomoEvents) _paq.push(["trackEvent", "Suggestion-box", "Click on filter tag", category]);
});
......@@ -291,4 +406,5 @@ function generateFilterButtons(category, autoCompleteValue, autoCompletePrefix,
return Promise.reject(error);
}
}
export { filterItems, generateFilterData, selectFilter, categories };
......@@ -14,7 +14,6 @@ const modalForm = document.getElementById("modalForm");
const modalFormInputResize = document.getElementById("modalFormInputResize");
const modalBackground = document.querySelectorAll('.modal-background');
const modalClose = document.querySelectorAll('.close');
const modalFormInput = document.getElementById("modalFormInput");
const priorityPicker = document.getElementById("priorityPicker");
const btnItemStatus = document.getElementById("btnItemStatus");
......@@ -55,37 +54,6 @@ modalForm.addEventListener("submit", function(event) {
handleError(error);
});
});
/*modalForm.addEventListener ("keydown", function(event) {
if(event.ctrlKey && event.shiftKey && event.key.length===1 && event.key.match(/[a-z]/i)) {
event.preventDefault();
setPriority(event.key.substr(0,1)).then(response => {
console.log(response);
}).catch(error => {
handleError(error);
});
} else if(event.ctrlKey && event.shiftKey && event.key.length===1 && event.key.match(/[_]/i)) {
setPriority(null).then(response => {
console.log(response);
}).catch(error => {
handleError(error);
});
} else if(event.key==="Enter" && event.ctrlKey) {
submitForm().then(response => {
console.log(response);
}).catch(error => {
handleError(error);
});
} else if(event.key === "Escape" && !autoCompleteContainer.classList.contains("is-active")) {
resetModal().then(function(result) {
console.log(result);
}).catch(function(error) {
handleError(error);
});
//this.classList.remove("is-active");
} else if(event.key === "Escape" && autoCompleteContainer.classList.contains("is-active")) {
autoCompleteContainer.classList.remove("is-active");
}
});*/
modalForm.addEventListener ("click", function() {
// close recurrence picker if click is outside of recurrence container
if(!event.target.closest("#recurrencePickerContainer") && event.target!=recurrencePickerInput) document.getElementById("recurrencePickerContainer").classList.remove("is-active")
......@@ -97,7 +65,6 @@ priorityPicker.addEventListener("change", e => {
handleError(error);
});
});
priorityPicker.onfocus = function() {
// close suggestion box if focus comes to priority picker
autoCompleteContainer.classList.remove("is-active");
......@@ -165,7 +132,7 @@ function modalFormInputEvent() {
}
let autoCompleteValue ="";
let autoCompletePrefix = "";
let caretPosition = getCaretPosition(modalFormInput);
let caretPosition = getCaretPosition(document.getElementById("modalFormInput"));
let autoCompleteCategory = "";
if((document.getElementById("modalFormInput").value.charAt(caretPosition-2) === " " || document.getElementById("modalFormInput").value.charAt(caretPosition-2) === "\n") && (document.getElementById("modalFormInput").value.charAt(caretPosition-1) === "@" || document.getElementById("modalFormInput").value.charAt(caretPosition-1) === "+")) {
autoCompleteValue = document.getElementById("modalFormInput").value.substr(caretPosition, document.getElementById("modalFormInput").value.lastIndexOf(" ")).split(" ").shift();
......@@ -454,7 +421,7 @@ function toggleInputSize(type) {
document.getElementById("modalFormInput").addEventListener("keyup", e => {
modalFormInputEvent();
// do not show suggestion container if Escape has been pressed
if(e.key==="Escape") return false;
//if(e.key==="Escape") return false;
});
document.getElementById("modalFormInput").focus();
}
......
......@@ -246,19 +246,19 @@ function generateTableRow(todo) {
if(userData.matomoEvents) _paq.push(["trackEvent", "Todo-Table", "Click on Todo item"]);
}
}
// cell for the categories
categories.forEach(category => {
if(todo[category] && category!="priority") {
todo[category].forEach(el => {
let todoTableBodyCellCategory = document.createElement("span");
todoTableBodyCellCategory.setAttribute("class", "tag " + category);
todoTableBodyCellCategory.innerHTML = el;
tableContainerCategories.appendChild(todoTableBodyCellCategory);
});
}
});
// only add the categories to text cell if it has child nodes
if(tableContainerCategories.hasChildNodes()) todoTableBodyCellText.appendChild(tableContainerCategories);
// cell for the categories
categories.forEach(category => {
if(todo[category] && category!="priority") {
todo[category].forEach(el => {
let todoTableBodyCellCategory = document.createElement("span");
todoTableBodyCellCategory.setAttribute("class", "tag " + category);
todoTableBodyCellCategory.innerHTML = el;
tableContainerCategories.appendChild(todoTableBodyCellCategory);
});
}
});
// only add the categories to text cell if it has child nodes
if(tableContainerCategories.hasChildNodes()) todoTableBodyCellText.appendChild(tableContainerCategories);
// check for and add a given due date
if(todo.due) {
var tag = convertDate(todo.due);
......
......@@ -2,6 +2,7 @@ let userData, defaultPath, _paq, fileWatcher, translations;
const { Tray, app, Notification, clipboard, Menu, ipcMain, BrowserWindow } = require("electron");
const path = require("path");
const fs = require("fs");