Commit 72f499ab authored by ransome1's avatar ransome1
Browse files

Merge branch 'develop'

parents 079045a4 42e09b82
......@@ -29,4 +29,5 @@ System Volume Information/
Thumbs.db
src/css/
*.drawio*
*.provisionprofile
\ No newline at end of file
*.provisionprofile
out/
\ No newline at end of file
# sleek
## sleek is a free and open-source (FOSS) todo manager that makes use of the todo.txt format. Available for Linux, Windows and MacOS
## sleek is an open-source (FOSS) todo manager based on the todo.txt syntax. It's available for Windows, MacOS and Linux
+ [Screenshots](#screenshots)
+ [Support sleek](#support-sleek)
+ [Get it from Microsoft Store](#get-sleek-from-microsoft-store)
......@@ -13,13 +13,11 @@
+ [Features](#features)
+ [Used libraries](#used-libraries)
sleek is a free and open-source (FOSS) todo manager that makes use of the todo.txt format. sleeks GUI is modern and simple but still offers a decent set of functions which help users getting things done. sleek is available as a client for Windows, MacOS and Linux.
sleek is an open-source (FOSS) todo manager based on the todo.txt syntax. Stripped down to only the most necessary features, and with a clean and simple interface, sleek aims to help you focus on getting things done.
Users can add contexts, projects, priorities, due dates, recurrences or threshold dates to their todos. These todo.txt attributes can then be used in full-text search, as filters or to group and sort the todo list.
All classic todo.txt attributes are supported and enhanced by additional features. Creating todos is straightforward, and tag-based filtering in tandem with highly customisable grouping and smart full-text search allow for rapid information retrieval. Completed todos can be hidden or archived into separate done.txt files. Easy integration with other todo.txt apps is facilitated by continuously scanning todo.txt files for changes.
sleek manages and watches multiple todo.txt files continuously for changes, which makes it easy to integrate sleek with other todo.txt apps. Also users can switch to dark mode and choose from multiple languages.
Todos with due date or repeating todos will trigger notifications and completed todos can be hidden or archived into separate done.txt files. If users have tons of todos, a compact view can come in handy.
sleek is available for Windows, MacOS and Linux, and in several languages. For a detailed list of features, see below.
### Screenshots
![Alt text](assets/screenshots/mac/main.png?raw=true "Screenshot of sleek's todo list view")
......@@ -73,35 +71,32 @@ You can download sleek for Windows, MacOS and Linux from
4. Build sleek `yarn build:windows` or `yarn build:linux` or `yarn build:macos`
### Features
* sleek can use existing todo.txt files or create new ones
* Todos can be enriched and searched for by
* Uses existing todo.txt files or creates new ones
* Add and search for todos by
- priorities
- contexts
- projects
- due dates
- creation dates
- <a href="https://github.com/ransome1/sleek/wiki/Recurring-todos-(rec:)">recurrences</a>
- <a href="https://github.com/ransome1/sleek/wiki/Deferred-todos-(t:)">threshold dates</a>
* Todo list can be sorted by priorities, due dates, creation dates, contexts and projects or simply by file order
* Todos can be filtered by contexts, projects and priorities
* Todos can be looked up by a full-text search that is compatible with the todo.txt syntax
* Autocomplete function suggests available contexts and projects
* Dates and priorities can be selected by built-in picker elements
* <a href="https://github.com/ransome1/sleek/wiki/Keyboard-shortcuts">Keyboard shortcuts following todotxt.net</a>
- recurrences (repeating todos)
- threshold dates
* Sort and group todos by priority, due and creation date, context and project or as they occur in the text file
* Filter todos by context, project and priority
* Find todos using full-text search compatible with todo.txt syntax
* Inline autocomplete available
* Dates and priorities can be selected by built-in picker elements
* Navigable via keyboard shortcuts
* Tabindex available
* <a href="https://github.com/ransome1/sleek/wiki/Hidden-todos-(h:)">A todo can be hidden but its attributes will be available in the filter drawer and autocomplete function</a>
* Due dates trigger alarms and appear as badges in sleeks icon
* Dark and light mode can be toggled
* A compact view is available
* Completed todos can be bulk archived to a separate done.txt file
* Completed todos can be shown or hidden
* Options for due date reminders and notification badges
* Easily toggle between dark and light mode
* Compact view and zoom available
* Completed todos can be shown, hidden and archived
* Multi line todos can be created
* Filters can be renamed or deleted by right clicking on them
* Filters are sorted alphanummerically
* Hyperlinks are detected automatically and can be clicked using the icon
* A file watcher rereads the todo.txt file if it has been changed
* Multiple todo.txt files can be managed and switched between using a tab bar or keyboard shortcuts
* Multiple languages are either detected or can be set by hand to
* Filters are sorted alphanummerically and can be renamed or deleted
* Hyperlinks detected automatically
* File watcher scans todo.txt files for changes
* Simultaneously manage multiple todo.txt files
* Language options
- English
- German
- Italian
......@@ -114,9 +109,8 @@ You can download sleek for Windows, MacOS and Linux from
- Hungarian
- Czech
- Polish
* sleek can be minimized to tray
* Existing todos can be used as templates for new ones
* Todo list can be copied to clipboard
A more detailed documentation can be found in <a href="https://github.com/ransome1/sleek/wiki/">sleeks wiki</a>.
### Used libraries
- Electron: https://github.com/electron/electron
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>
......@@ -2,7 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
\ No newline at end of file
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key><true/>
<key>com.apple.application-identifier</key>
<string>8QSR3UZXP8.com.todotxt.sleek</string>
<key>com.apple.developer.team-identifier</key>
<string>8QSR3UZXP8</string>
<key>com.apple.security.application-groups</key>
<array>
<string>8QSR3UZXP8.com.todotxt.sleek</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>8QSR3UZXP8.com.todotxt.sleek</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
......@@ -23,5 +18,9 @@
<true/>
<key>com.apple.security.files.bookmarks.document-scope</key>
<true/>
</dict>
</plist>
\ No newline at end of file
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
\ No newline at end of file
#!/bin/bash
APP="sleek"
APP_PATH="dist/mas-universal/$APP.app"
PKG_PATH="dist/mas-universal/$APP-mas.pkg"
PARENT_PLIST="build/entitlements.mas.plist"
CHILD_PLIST="build/entitlements.mas.inherit.plist"
LOGINHELPER_PLIST="build/entitlements.mas.loginhelper.plist"
APP_KEY="Apple Distribution: Robin Ahle (8QSR3UZXP8)"
codesign --force --entitlements "$CHILD_PLIST" --deep --sign "$APP_KEY" "$APP_PATH"
codesign --force --entitlements "$PARENT_PLIST" --sign "$APP_KEY" "$APP_PATH"
codesign --force --entitlements "$LOGINHELPER_PLIST" --sign "$APP_KEY" "$APP_PATH/Contents/Library/LoginItems/$APP Login Helper.app/Contents/MacOS/$APP Login Helper"
codesign --force --entitlements "$LOGINHELPER_PLIST" --sign "$APP_KEY" "$APP_PATH/Contents/Library/LoginItems/$APP Login Helper.app/"
productbuild --component "$APP_PATH" /Applications --sign "3rd Party Mac Developer Installer: Robin Ahle (8QSR3UZXP8)" "$PKG_PATH"
\ No newline at end of file
......@@ -12,14 +12,14 @@
"tasks"
],
"author": "Robin Ahle <mail@robbfolio.de>",
"copyright": "Copyright © 2021 ${author}",
"copyright": "Copyright © 2022 ${author}",
"license": "MIT",
"repository": "https://github.com/ransome1/sleek/",
"icon": "assets/icons/sleek.png",
"main": "src/main.js",
"buildResources": "src",
"build": {
"buildVersion": "7",
"buildVersion": "14",
"files": [
"!flatpak-node${/*}",
"!snap${/*}",
......@@ -32,6 +32,7 @@
"!**.md",
"!**.gitignore",
"!yarn.lock",
"!yarn-error.log",
"!assets/screenshots${/*}",
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme,test,__tests__,tests,powered-test,example,examples,*.d.ts}",
"!**/node_modules/.bin",
......@@ -58,11 +59,14 @@
"arch": "universal"
}
],
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"icon": "build/icon.icns",
"category": "public.app-category.productivity",
"artifactName": "${productName}-${version}-mac.${ext}"
"artifactName": "${productName}-${version}-mac.${ext}",
"darkModeSupport": true
},
"dmg": {
"sign": false,
......@@ -73,24 +77,13 @@
}
},
"mas": {
"asar": true,
"asarUnpack": [
"node_modules/fsevents"
],
"hardenedRuntime": false,
"provisioningProfile": "build/distribution.provisionprofile",
"entitlements": "build/entitlements.mas.plist",
"entitlementsInherit": "build/entitlements.mas.plist",
"entitlementsLoginHelper": "build/entitlements.mas.loginhelper.plist"
},
"masDev": {
"asar": true,
"asarUnpack": [
"node_modules/fsevents"
],
"provisioningProfile": "build/development.provisionprofile",
"entitlements": "build/entitlements.mas.plist",
"entitlementsInherit": "build/entitlements.mas.inherit.plist",
"entitlementsLoginHelper": "build/entitlements.mas.loginhelper.plist"
"entitlementsInherit": "build/entitlements.mas.inherit.plist"
},
"win": {
"target": [
......@@ -126,28 +119,21 @@
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"scripts": {
"build:windows": "cross-env NODE_ENV=production yarn build:css && yarn build:pegjs && electron-builder -w --publish never",
"build:macos": "cross-env NODE_ENV=production yarn build:css && yarn build:pegjs && electron-builder -m --publish never",
"build:mas": "cross-env NODE_ENV=production yarn build:css && yarn build:pegjs && electron-builder --mac=mas --publish never",
"build:mas-dev": "cross-env NODE_ENV=production yarn build:css && yarn build:pegjs && electron-builder --mac=mas-dev --publish never",
"build:linux": "cross-env NODE_ENV=production yarn build:css && yarn build:pegjs && electron-builder -l --publish never",
"build:appx": "cross-env NODE_ENV=production yarn build:css && yarn build:pegjs && electron-builder -w appx --publish never",
"build:appimage": "cross-env NODE_ENV=production yarn build:css && yarn build:pegjs && electron-builder -l AppImage --publish never",
"build:mas": "yarn build:css && yarn build:pegjs && electron-builder -m mas --universal --publish never && bash build/resignPkg.sh",
"build:appx": "yarn build:css && yarn build:pegjs && electron-builder -w appx --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": "cross-env NODE_ENV=production yarn build:css && yarn build:pegjs && electron-builder --dir",
"lint": "eslint --ext .js, src --ext .mjs, src",
"sass": "sass -w src/scss/style.scss:src/css/style.css",
"start": "yarn sass & electron .",
"test": "cross-env PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn playwright test --config=test/playwright.config.js",
"test1": "cross-env PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn playwright test -g 'Input element switch' --config=test/playwright.config.js"
"test": "yarn playwright test --config=test/playwright.config.js",
"test1": "yarn playwright test -g 'Check if multi line items are displayed correctly' --config=test/playwright.config.js"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.3",
"bulma": "^0.9.2",
"chokidar": "^3.5.1",
"electron-notarize": "^1.1.1",
"electron-updater": "^4.3.9",
"electron-updater": "^4.6.5",
"electron-windows-badge": "^1.1.0",
"i18next": "^21.6.6",
"i18next-fs-backend": "^1.1.1",
......@@ -158,9 +144,9 @@
},
"devDependencies": {
"@playwright/test": "^1.17.2",
"cross-env": "^7.0.3",
"electron": "^16.0.5",
"electron": "^17.2.0",
"electron-builder": "23.0.2",
"electron-notarize": "^1.1.1",
"electron-reloader": "^1.2.2",
"eslint": "^8.6.0",
"peggy": "^1.2.0",
......
......@@ -35,6 +35,7 @@ apps:
- home
- browser-support
- network
- removable-media
parts:
sleek:
plugin: nil
......
......@@ -4,6 +4,7 @@ body.dark {
color: #f0f0f0;
}
body.dark code, body.dark pre {
color: #CCCDCF !important;
background-color: #212224 !important;
}
body.dark a {
......@@ -339,6 +340,14 @@ body.dark #filterContext .card #filterContextDelete,
body.dark #todoContext .card #filterContextDelete {
color: #ff3860 !important;
}
body.dark #filterContext .horizontal,
body.dark #todoContext .horizontal {
border-bottom: 1px solid #2d2d2d;
}
body.dark #filterContext .horizontal label,
body.dark #todoContext .horizontal label {
color: #ebebeb;
}
body.dark #fileTabBar ul {
background-color: #2d2d2d;
}
......@@ -747,8 +756,9 @@ strong {
code, pre {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace !important;
background-color: #ebebeb !important;
color: inherit !important;
background: #ccc !important;
color: #5a5a5a !important;
border-radius: 0.65em;
}
#noResultContainer {
......@@ -1314,8 +1324,8 @@ a.contexts:hover span.tag {
background: #247561 !important;
}
a.contexts:focus-visible,
a.contexts.is-dark:focus-visible {
a.button.contexts:focus-visible,
a.button.contexts.is-dark:focus-visible {
background: #247561 !important;
color: white !important;
}
......@@ -1338,10 +1348,10 @@ a.projects:hover span.tag {
background: #822c82;
}
a.projects:focus-visible,
a.projects.is-dark:focus-visible {
a.button.projects:focus-visible,
a.button.projects.is-dark:focus-visible {
background: #822c82 !important;
color: white;
color: white !important;
}
a.projects.is-dark,
......@@ -1567,12 +1577,6 @@ a.priority.C:focus-visible span.tag {
}
#drawerContainer .drawer section .todoFilterHint {
cursor: help;
opacity: 0.4;
}
#drawerContainer .drawer section .todoFilterHint code {
color: white !important;
background: #2d2d2d !important;
border-radius: 0.65em;
}
#drawerContainer .drawer table tr td:nth-child(odd) {
width: auto;
......@@ -1660,7 +1664,7 @@ a.priority.C:focus-visible span.tag {
}
.modal.content code {
background: transparent;
padding: 0;
padding: 0.25em 0.5em;
}
.modal.content .delete,
.modal.content .modal-close {
......@@ -2132,26 +2136,38 @@ a.priority.C:focus-visible span.tag {
border-radius: 0.65em;
box-shadow: 0 0 1em #ccc;
}
#todoContext a {
#todoContext a, #todoContext label {
font-size: 0.9em;
color: #3273dc;
padding: 0.75em 1.5em;
}
#todoContext a.dropdown-item:first-child {
#todoContext .dropdown-item:first-child {
border-top-left-radius: 0.65em;
border-top-right-radius: 0.65em;
}
#todoContext a.dropdown-item:last-child {
#todoContext .dropdown-item:last-child {
border-bottom-left-radius: 0.65em;
border-bottom-right-radius: 0.65em;
}
#todoContext a.dropdown-item:focus,
#todoContext a.dropdown-item:hover {
#todoContext .dropdown-item:focus,
#todoContext .dropdown-item:hover {
background-color: #f0f0f0 !important;
}
#todoContext .card-content {
padding: 0;
}
#todoContext .card-content .horizontal {
display: flex;
text-align: center;
border-bottom: 1px solid #f0f0f0;
}
#todoContext .card-content .horizontal label {
color: #5a5a5a;
}
#todoContext .card-content .horizontal .dropdown-item {
border-radius: 0;
border: none;
}
.toggle {
margin: 0;
......
This diff is collapsed.
......@@ -776,9 +776,14 @@
<div id="todoContext" role="menu" tabindex="0">
<div class="card">
<div class="card-content">
<a id="todoContextUseAsTemplate" class="dropdown-item" tabindex="0"></a>
<a id="todoContextCopy" href="#" class="dropdown-item" tabindex="0"></a>
<a id="todoContextDelete" class="dropdown-item" tabindex="0"></a>
<div class="horizontal">
<label>Priority</label>
<a href="#" id="todoContextPriorityIncrease" class="dropdown-item" tabindex="0"><i class="fas fa-plus"></i></a>
<a href="#" id="todoContextPriorityDecrease" class="dropdown-item" tabindex="0"><i class="fas fa-minus"></i></a>
</div>
<a href="#" id="todoContextUseAsTemplate" class="dropdown-item" tabindex="0"></a>
<a href="#" id="todoContextCopy" class="dropdown-item" tabindex="0"></a>
<a href="#" id="todoContextDelete" class="dropdown-item" tabindex="0"></a>
</div>
</div>
</div>
......
......@@ -435,6 +435,7 @@ function generateFilterButtons(category, autoCompletePrefix) {
todoFiltersItem.setAttribute("data-category", category);
todoFiltersItem.classList.add("button", category);
todoFiltersItem.setAttribute("tabindex", 0)
todoFiltersItem.setAttribute("href", "#")
todoFiltersItem.innerHTML = filter;
// configuration for filter drawer buttons
......
......@@ -461,7 +461,7 @@ async function show(todo, templated) {
// this is a new templated todo task
if(templated) {
// erase the original creation date and description
todo.date = new Date();
if(userData.appendStartDate) todo.date = new Date();
todo.text = "____________";
modalFormInput.value = todo.toString();
......
"use strict";
import { buildTable, translations } from "../render.js";
import { setupInterface, handleError } from "./helper.mjs";
import { showDrawer } from "./drawer.mjs";
import { resetFilters } from "./filters.mjs";
import { triggerToggle } from "./toggles.mjs";
//const helper = await import("./helper.mjs");
//const render = await import("../render.js");
// receives todo.txt data from main process as string and passes it to build function
window.api.receive("buildTable", async (args) => {
buildTable(...args).then(function(response) {
console.info(response);
}).catch(function(error) {
handleError(error);
});
});
window.api.receive("triggerFunction", async (name, args) => {
try {
const helper = await import("./helper.mjs");
if(!args) args = new Array;
switch(name) {
......@@ -13,7 +29,7 @@ window.api.receive("triggerFunction", async (name, args) => {
onboarding.showOnboarding(...args).then(function(response) {
console.info(response);
}).catch(function(error) {
helper.handleError(error);
handleError(error);
});
break;
case "showForm":
......@@ -21,29 +37,37 @@ window.api.receive("triggerFunction", async (name, args) => {
form.show(...args).then(function(response) {
console.info(response);
}).catch(function(error) {
helper.handleError(error);
handleError(error);
});
break;
case "showGenericMessage":
const messages = await import("../js/messages.mjs");
messages.showGenericMessage(...args).then(function(response) {
console.info(response);
}).catch(function(error) {
handleError(error);
});
break;
case "archiveTodos":
var prompt = await import("../js/prompt.mjs");
prompt.getConfirmation(todos.archiveTodos, await getTranslation("archivingPrompt"));
const prompt = await import("../js/prompt.mjs");
const todos = await import("../js/todos.mjs");
prompt.getConfirmation(todos.archiveTodos, translations.archivingPrompt);
break;
case "showDrawer":
var drawer = await import("../js/drawer.mjs");
drawer.show(...args).then(function(response) {
console.info(response);
showDrawer(document.getElementById("navBtnFilter")).then(function(result) {
console.log(result);
}).catch(function(error) {
helper.handleError(error);
handleError(error);
});
break;
case "handleError":
helper.handleError(...args);
handleError(...args);
break;
case "resetFilters":
filters.resetFilters(...args).then(function(response) {
resetFilters(true).then(function(response) {
console.info(response);
}).catch(function(error) {
helper.handleError(error);
handleError(error);
});
break;
case "showModal":
......@@ -51,29 +75,28 @@ window.api.receive("triggerFunction", async (name, args) => {
content.showModal(args[0]).then(function(response) {
console.info(response);
}).catch(function(error) {
helper.handleError(error);
handleError(error);
});
break;
case "setupInterface":
helper.setupInterface().then(function(response) {
setupInterface().then(function(response) {
console.info(response);
}).catch(function(error) {
helper.handleError(error);
handleError(error);
});
break;
case "toggleDarkmode":
helper.toggleDarkmode().then(function(response) {
triggerToggle(document.getElementById("darkmode"), true).then(function(response) {
console.info(response);
}).catch(function(error) {
helper.handleError(error);
handleError(error);
});
break;
case "toggle":
const toggles = await import("../js/toggles.mjs");
toggles.triggerToggle(document.getElementById(...args), true).then(function(response) {
triggerToggle(document.getElementById(...args), true).then(function(response) {
console.info(response);
}).catch(function(error) {
helper.handleError(error);
handleError(error);
});
break;
}
......@@ -81,15 +104,4 @@ window.api.receive("triggerFunction", async (name, args) => {
error.functionName = "triggerFunction";
return Promise.reject(error);
}
});
// receives todo.txt data from main process as string and passes it to build function
window.api.receive("buildTable", async (args) => {
const helper = await import("./helper.mjs");
const render = await import("../render.js");
render.buildTable(...args).then(function(response) {
console.info(response);
}).catch(function(error) {
helper.handleError(error);
});
});
\ No newline at end of file
......@@ -19,6 +19,7 @@ export function createModalJail(modal) {
// add focus on the first focusable element
firstFocusableElement.focus();