Commit 564a1564 authored by ransome1's avatar ransome1
Browse files

Added priority quick changer (@M4he), fixed sorting bug (350), adjusted test...

Added priority quick changer (@M4he), fixed sorting bug (350), adjusted test cases to new context options
parent 574919da
<?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/>
</dict>
</plist>
\ No newline at end of file
......@@ -3,7 +3,15 @@
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<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>
......@@ -16,10 +24,11 @@
<true/>
<key>com.apple.security.files.bookmarks.document-scope</key>
<true/>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>com.apple.locationd.desktop.registration</string>
<string>com.apple.CoreLocation.agent</string>
</array>
</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/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>
\ No newline at end of file
{
"name": "sleek",
"productName": "sleek",
"version": "1.1.7",
"version": "1.1.8-rc.1",
"description": "todo.txt manager for Linux, Windows and MacOS, free and open-source (FOSS)",
"synopsis": "todo.txt manager for Linux, Windows and MacOS, free and open-source (FOSS)",
"category": "ProjectManagement",
......@@ -19,28 +19,8 @@
"main": "src/main.js",
"buildResources": "src",
"build": {
"buildVersion": "7",
"files": [
"!flatpak-node${/*}",
"!snap${/*}",
"!flatpak${/*}",
"!.git${/*}",
"!.github${/*}",
"!dist${/*}",
"!**.pfx",
"!**.bak",
"!**.md",
"!**.gitignore",
"!yarn.lock",
"!test",
"!assets",
"!FUNDING.yml",
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme,test,__tests__,tests,powered-test,example,examples,*.d.ts}",
"!**/*.{o,hprof,orig,pyc,pyo,rbc}",
"!**/._*",
"!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,thumbs.db,.gitignore,.gitattributes,.editorconfig,.flowconfig,.yarn-metadata.json,.idea,appveyor.yml,.travis.yml,circle.yml,npm-debug.log,.nyc_output,yarn.lock,.yarn-integrity}",
"!**/node_modules/search-index/si${/*}"
],
"appId": "com.todotxt.sleek",
"buildVersion": "8",
"linux": {
"icon": "assets/icons/sleek.png",
"category": "ProjectManagement",
......@@ -59,41 +39,31 @@
"arch": "universal"
}
],
"gatekeeperAssess": false,
"hardenedRuntime": true,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"icon": "build/icon.icns",
"category": "public.app-category.productivity",
"artifactName": "${productName}-${version}-mac.${ext}"
},
"dmg": {
"sign": false,
"background": "build/background.tiff",
"window": {
"width": 720,
"height": 380
}
"artifactName": "${productName}-${version}-mac.${ext}",
"darkModeSupport": true
},
"mas": {
"asarUnpack": [
"node_modules/fsevents"
],
"hardenedRuntime": false,
"provisioningProfile": "build/distribution.provisionprofile",
"entitlements": "build/entitlements.mas.plist",
"entitlementsInherit": "build/entitlements.mas.inherit.plist",
"entitlementsLoginHelper": "build/entitlements.mas.loginhelper.plist"
"entitlementsInherit": "build/entitlements.mas.inherit.plist"
},
"masDev": {
"asarUnpack": [
"node_modules/fsevents"
],
"hardenedRuntime": false,
"provisioningProfile": "build/development.provisionprofile",
"entitlements": "build/entitlements.mas.plist",
"entitlementsInherit": "build/entitlements.mas.inherit.plist"
},
"dmg": {
"sign": false,
"background": "build/background.tiff",
"window": {
"width": 720,
"height": 380
}
},
"win": {
"target": [
"zip",
......@@ -128,21 +98,16 @@
"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:mas": "cross-env NODE_ENV=production yarn build:css && yarn build:pegjs && electron-builder -m mas --universal --publish never",
"build:mas-dev": "cross-env NODE_ENV=production yarn build:css && yarn build:pegjs && electron-builder -m mas-dev --universal --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: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"
"test1": "cross-env PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn playwright test -g 'Check if multi line items are displayed correctly' --config=test/playwright.config.js"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.3",
......@@ -161,8 +126,8 @@
"devDependencies": {
"@playwright/test": "^1.17.2",
"cross-env": "^7.0.3",
"electron": "^16.0.5",
"electron-builder": "23.0.2",
"electron": "^17.2.0",
"electron-builder": "^22.14.13",
"electron-reloader": "^1.2.2",
"eslint": "^8.6.0",
"peggy": "^1.2.0",
......
......@@ -339,6 +339,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;
}
......@@ -2132,26 +2140,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.
......@@ -777,12 +777,13 @@
<div class="card">
<div class="card-content">
<div class="horizontal">
<a id="todoContextPriorityIncrease" class="dropdown-item" tabindex="0"><i class="fas fa-arrow-up"></i></a>
<a id="todoContextPriorityDecrease" class="dropdown-item" tabindex="0"><i class="fas fa-arrow-down"></i></a>
<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 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>
<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>
......
......@@ -19,6 +19,7 @@ export function createModalJail(modal) {
// add focus on the first focusable element
firstFocusableElement.focus();
modal.onkeydown = function(event) {
// if arrow down key is pressed
......
......@@ -498,10 +498,16 @@ function generateTableRow(todo) {
return Promise.reject(error);
}
}
function createTodoContext(todoTableRow) {
async function createTodoContext(todoTableRow) {
try {
// get index of todo
let index = await items.objects.map(function(object) {return object.toString(); }).indexOf(todoTableRow.getAttribute("data-item"));
// retrieve todo object
const todo = items.objects[index]
const useAsTemplate = function() {
show(todoContext.getAttribute("data-item"), true).then(response => {
show(todo.toString(), true).then(response => {
console.log(response);
}).catch(error => {
handleError(error);
......@@ -514,14 +520,7 @@ function createTodoContext(todoTableRow) {
if(userData.matomoEvents) _paq.push(["trackEvent", "Todo-Table-Context", "Click on Use as template"]);
}
const copyTodo = async function() {
const todoObject = await generateTodoTxtObject(todoTableRow.getAttribute("data-item")).then(response => {
return response;
}).catch(error => {
handleError(error);
});
pasteItemToClipboard(todoObject).then(response => {
pasteItemToClipboard(todo).then(response => {
console.log(response);
}).catch(error => {
handleError(error);
......@@ -534,9 +533,6 @@ function createTodoContext(todoTableRow) {
if(userData.matomoEvents) _paq.push(["trackEvent", "Todo-Table-Context", "Click on Copy"]);
}
const deleteTodo = async function() {
// get index of todo
const index = await items.objects.map(function(object) {return object.toString(); }).indexOf(todoTableRow.getAttribute("data-item"));
// remove item at index
items.objects.splice(index, 1);
......@@ -549,68 +545,54 @@ function createTodoContext(todoTableRow) {
// trigger matomo event
if(userData.matomoEvents) _paq.push(["trackEvent", "Todo-Table-Context", "Click on Delete"]);
}
const changePriority = async function(direction) {
// get index of todo
const index = await items.objects.map(function(object) {return object.toString(); }).indexOf(todoTableRow.getAttribute("data-item"));
// retrieve todo object
const todo = items.objects[index]
const changePriority = function(direction) {
// abort if todo has no priority set
if (!todo.priority) return false;
let nextIndex = 97;
const currentPriority = todo.priority
if (direction < 0) {
if (currentPriority !== "Z") {
todo.priority = String.fromCharCode(currentPriority.charCodeAt(0) + 1);
} else {
return false;
}
} else if (direction > 0) {
if (currentPriority !== "A") {
todo.priority = String.fromCharCode(currentPriority.charCodeAt(0) - 1);
} else {
return false;
}
// in case a todo has no priority and the 1st grouping method is priority
if(!items.objects[index].priority && userData.sortBy[0] === "priority") {
const index = items.grouped.length - 2;
// this receives the lowest available priority group
(index >= 0) ? nextIndex = items.grouped[index][0].toLowerCase().charCodeAt(0) : nextIndex = 97
// change priority based on current priority
} else if(items.objects[index].priority) {
const currentPriority = items.objects[index].priority.toLowerCase().charCodeAt(0)
nextIndex = currentPriority + direction;
}
if(nextIndex <= 96 || nextIndex >= 123) return false
items.objects[index].priority = String.fromCharCode(nextIndex).toUpperCase();
//write the data to the file
window.api.send("writeToFile", [items.objects.join("\n").toString() + "\n"]);
todoContext.classList.toggle("is-active");
todoContext.classList.remove("is-active");
todoContext.removeAttribute("data-item");
// trigger matomo event
if(userData.matomoEvents) _paq.push(["trackEvent", "Todo-Table-Context", "Click on Priority changer"]);
}
todoContext.setAttribute("data-item", todoTableRow.getAttribute("data-item"))
todoContext.setAttribute("data-item", todoTableRow.getAttribute("data-item"))
// get index of todo
const index = items.objects.map(function(object) {return object.toString(); }).indexOf(todoTableRow.getAttribute("data-item"));
// show/hide priority change buttons
if (items.objects[index].priority) {
todoContextPriorityIncrease.classList.remove("is-hidden")
todoContextPriorityDecrease.classList.remove("is-hidden")
} else {
todoContextPriorityIncrease.classList.add("is-hidden")
todoContextPriorityDecrease.classList.add("is-hidden")
}
// click on increse priority option
todoContextPriorityIncrease.onclick = function() {
changePriority(1);
changePriority(-1);
}
todoContextPriorityIncrease.onkeypress = function(event) {
if(event.key !== "Enter") return false;
changePriority(1);
changePriority(-1);
}
// click on decrease priority option
todoContextPriorityDecrease.onclick = function() {
changePriority(-1);
changePriority(1);
}
todoContextPriorityDecrease.onkeypress = function(event) {
if(event.key !== "Enter") return false;
changePriority(-1);
changePriority(1);
}
// click on use as template option
todoContextUseAsTemplate.onclick = function() {
......@@ -685,8 +667,6 @@ function sortTodosInGroup(group) {
if(!item1 || (item1 > item2)) return 1;
// if second item is empty it will be sorted before first item
if(!item2 || (item1 < item2)) return -1;
//return item1.toString().localeCompare(item2.toString())
});
}
......
......@@ -377,6 +377,12 @@ body.dark {
color: $has-text-danger!important;
}
}
.horizontal {
label {
color: $light-grey;
}
border-bottom: 1px solid $darker-grey;
}
}
#fileTabBar {
ul {
......
......@@ -4,38 +4,39 @@
z-index: 40;
border-radius: $radius;
box-shadow: 0 0 1em $mid-grey;
a {
a, label {
font-size: 0.9em;
color: $has-text-link;
padding: 0.75em 1.5em;
}
a.dropdown-item:first-child {
.dropdown-item {
}
.dropdown-item:first-child {
border-top-left-radius: $radius;
border-top-right-radius: $radius;
}
a.dropdown-item:last-child {
.dropdown-item:last-child {
border-bottom-left-radius: $radius;
border-bottom-right-radius: $radius;
}
a.dropdown-item:focus,
a.dropdown-item:hover {
.dropdown-item:focus,
.dropdown-item:hover {
background-color: $almost-white!important;
}
.card-content {
padding: 0;
.horizontal {
display: flex;
text-align: center;
a.dropdown-item {
border-radius: 0;
border-bottom: 1px solid $almost-white;
label {
color: $dark-grey;
}
a.dropdown-item:first-child {
border-top-left-radius: $radius;
border-top-right-radius: 0;
}
a.dropdown-item:last-child {
border-top-left-radius: 0;
border-top-right-radius: $radius;
.dropdown-item {
border-radius: 0;
border: none;
}
}
}
......
......@@ -15,3 +15,5 @@ x 2022-03-09 This is a test todo that needs to be archived
x 2022-03-14 This is a test todo that needs to be archived
x 2022-03-16 This is a test todo that needs to be archived
x 2022-03-22 This is a test todo that needs to be archived
x 2022-03-28 sadasd
x 2022-03-28 This is a test todo that needs to be archived
const { _electron: electron } = require("playwright");
const { test, expect } = require("@playwright/test");
const waitForAppToLoad = 2000;
const waitForAppToLoad = 1000;
let
app,
page;
......@@ -89,9 +89,9 @@ test.describe("Todo table", () => {
});
test("Fourth row can be clicked and shows modal, which input field contains a specfic value", async () => {
await page.locator(":nth-match(#todoTable .todo, 3) > .text").click();
await page.locator(":nth-match(#todoTable .todo, 4) > .text").click();
const value = await page.inputValue("#modalFormInput");
await expect(value).toBe("(A) 2021-02-28 A task list with all possible task types comments due:2023-03-30 +todotxt @test");
await expect(value).toBe("(B) Call Mom 2011-03-02 this task has no creation date Call Mom multiple projects and contexts Email SoAndSo at soandso@example.com this task has no context due:2023-04-09 +Family +PeaceLoveAndHappiness @iphone @phone");
});
});
......@@ -131,21 +131,21 @@ test.describe("Todo context", () => {
await expect(todoContext).toBeVisible();
});
test("Number of buttons is 3", async () => {
test("Number of buttons is 5", async () => {
// count items in context
const rows = await page.locator("#todoContext .dropdown-item").count();
await expect(rows).toBe(3);
await expect(rows).toBe(5);
});
test("Context container has the correct 'data-item' attribute", async () => {
// check if data item has desired value
const todoContext = await page.locator("#todoContext");
const dataItem = await todoContext.getAttribute("data-item");
await expect(dataItem).toBe("(A) Call Mom 2011-03-02 this task has no creation date Call Mom multiple projects and contexts Email SoAndSo at soandso@example.com this task has no context due:2023-04-09 +Family +PeaceLoveAndHappiness @iphone @phone");
await expect(dataItem).toBe("(A) Thank Mom for the meatballs x 2021-04-07 (B) Schedule Goodwill pickup Eskimo pies Really gotta call Mom this task has no priority (A) (b) Get back to the boss this task has no priority due:2023-06-10 +GarageSale @phone @phone @GroceryStore @phone @someday");
});
test("Click on 'Use as template' shows desired input value", async () => {
await page.locator(":nth-match(#todoContext .dropdown-item, 1)").click();
await page.locator(":nth-match(#todoContext .dropdown-item, 3)").click();
//const value = await page.inputValue("#modalFormInput");
await expect(page.locator("#modalFormInput")).toHaveValue(/____________/);
......@@ -163,7 +163,7 @@ test.describe("Todo context", () => {
"y": 10
}
});
await page.locator(":nth-match(#todoContext .dropdown-item, 2)").click();
await page.locator(":nth-match(#todoContext .dropdown-item, 4)").click();
await expect(page.locator("#messageGenericContainer")).toBeVisible();
});
......@@ -202,7 +202,7 @@ test.describe("Creating todos", () => {
"y": 10
}
});
await page.locator(":nth-match(#todoContext .dropdown-item, 3)").click();
await page.locator(":nth-match(#todoContext .dropdown-item, 5)").click();
await page.waitForSelector("#btnAddTodoContainer");
});
......@@ -222,7 +222,7 @@ test.describe("Creating todos", () => {
test("Add new todo and use it as a template to create another one", async () => {
await page.locator("#btnAddTodoContainer").click();
await page.locator("#modalFormInput").fill("This is a test todo that will be used as a template +testing @testing");
await page.locator("#btnSave").click();
await page.keyboard.press("Enter");
const row = await page.locator(":nth-match(#todoTable .todo, 1) > .text");
await row.click({
......@@ -232,7 +232,7 @@ test.describe("Creating todos", () => {
"y": 10
}
});
await page.locator(":nth-match(#todoContext .dropdown-item, 1)").click();
await page.locator(":nth-match(#todoContext .dropdown-item, 3)").click();
await expect(page.locator("#modalFormInput")).toHaveValue(/____________/);
// close modal
await page.locator("#btnCancel").click();
......@@ -243,7 +243,7 @@ test.describe("Creating todos", () => {
"y": 10
}
});
await page.locator(":nth-match(#todoContext .dropdown-item, 3)").click();
await page.locator(":nth-match(#todoContext .dropdown-item, 5)").click();
await page.waitForSelector("#btnAddTodoContainer");
});
......@@ -408,13 +408,14 @@ test.describe("Input element switch", () => {
});
test("Check if multi line items are displayed correctly", async () => {
const row = page.locator(":nth-match(#todoTable .todo, 8) > .text");
const row = page.locator(":nth-match(#todoTable .todo, 7) > .text");
await row.click({
"position": {
"x": 50,
"y": 10
}
});
await expect(await page.locator("#modalFormInput").inputValue()).toBe("This is a multi line taskcreated withinSleek due:2023-05-01 +todotxt @test");
await page.locator("#modalFormInputResize").click();
await expect(await page.locator("#modalFormInput").inputValue()).toBe(`This is a
......@@ -509,7 +510,7 @@ test.describe("Filter drawer", () => {
await page.waitForSelector("#filterDrawer");
const filterButton = await page.locator(":nth-match(#filterDrawer section.projects button, 2)");
const filterButton = await page.locator(":nth-match(#filterDrawer section.projects a.button, 2)");
await filterButton.click();
......@@ -526,11 +527,11 @@ test.describe("Filter drawer", () => {
test("Filter sidebar is being opened and a context and a project is being selected and filter reset button removes selection", async () => {
await page.locator("#navBtnFilter").click();
await page.waitForSelector("#filterDrawer");
const priorityButton = await page.locator(":nth-match(#filterDrawer #todoFilters button, 1)");
const priorityButton = await page.locator(":nth-match(#filterDrawer #todoFilters a.button, 1)");
await priorityButton.click();
const contextButton = await page.locator(":nth-match(#filterDrawer #todoFilters button, 7)");
const contextButton = await page.locator(":nth-match(#filterDrawer #todoFilters a.button, 7)");
await contextButton.click();
const projectButton = await page.locator(":nth-match(#filterDrawer #todoFilters button, 11)");
const projectButton = await page.locator(":nth-match(#filterDrawer #todoFilters a.button, 11)");
await projectButton.click();
rows = await page.locator("#todoTable .todo").count();
await