From 8746dfdae4e0afd6c1a36f7dfa6a99481177520e Mon Sep 17 00:00:00 2001 From: ransome1 Date: Thu, 28 Dec 2023 10:31:54 +0100 Subject: [PATCH] on macOS files can now be dopped on dock icon, on macOS sleek is now available in native "Open With" context menu, lot's of refactoring --- .erb/configs/webpack.config.renderer.dev.ts | 6 +- .erb/configs/webpack.config.renderer.prod.ts | 8 +- .github/ISSUE_TEMPLATE/feature_request.md | 6 +- .github/workflows/prepare-release.yml | 3 - .gitignore | 3 +- flatpak/com.github.ransome1.sleek.appdata.xml | 2 +- flatpak/com.github.ransome1.sleek.desktop | 2 +- package.json | 25 +++- release/app/package.json | 2 +- snap/snapcraft.yaml | 2 +- src/__tests__/__mock__/recurrence.txt | 28 ++-- src/__tests__/main/Archive.tsx | 2 +- src/__tests__/main/ChangeCompleteState.tsx | 1 - src/__tests__/main/CreateRecurringTodo.tsx | 33 ++--- src/__tests__/main/CreateTodoObjects.tsx | 4 - src/__tests__/main/Dialog.tsx | 1 - src/__tests__/main/File.tsx | 1 - src/__tests__/main/ProcessTodoObjects.tsx | 2 - src/__tests__/main/Write.tsx | 1 - src/locales/cs.json | 3 +- src/locales/de.json | 3 +- src/locales/en-gb.json | 3 +- src/locales/en.json | 3 +- src/locales/es.json | 3 +- src/locales/fr.json | 3 +- src/locales/hi.json | 3 +- src/locales/hu.json | 3 +- src/locales/it.json | 3 +- src/locales/jp.json | 3 +- src/locales/ko.json | 3 +- src/locales/pl.json | 3 +- src/locales/pt.json | 3 +- src/locales/ru.json | 3 +- src/locales/tr.json | 3 +- src/locales/zh.json | 3 +- src/main/config.tsx | 12 +- src/main/main.tsx | 8 +- src/main/modules/File/Archive.tsx | 14 +- src/main/modules/File/Dialog.tsx | 66 ++++----- src/main/modules/File/File.tsx | 2 +- src/main/modules/Filters/FilterQuery.js | 8 +- src/main/modules/Ipc.tsx | 9 +- src/main/modules/Menu.tsx | 7 +- .../CreateRecurringTodo.tsx | 2 - .../ProcessDataRequest/CreateTodoObjects.tsx | 3 +- .../ProcessDataRequest/ProcessDataRequest.tsx | 6 +- src/main/modules/Theme.tsx | 2 - src/main/modules/Tray.tsx | 2 +- src/renderer/App.tsx | 105 +++++++------- src/renderer/Archive.tsx | 22 +-- src/renderer/ContextMenu.tsx | 56 ++------ src/renderer/DataGrid/DatePickerInline.tsx | 8 +- src/renderer/DataGrid/Elements.tsx | 7 +- src/renderer/DataGrid/Grid.tsx | 60 ++++---- src/renderer/DataGrid/Group.tsx | 4 +- src/renderer/DataGrid/Row.tsx | 106 +++++++------- src/renderer/Drawer/Attributes.tsx | 4 +- src/renderer/Drawer/Drawer.tsx | 3 +- src/renderer/Drawer/Filters.tsx | 2 +- src/renderer/Drawer/Sorting/DraggableList.tsx | 2 +- .../Drawer/Sorting/DraggableListItem.tsx | 2 +- src/renderer/Drawer/Sorting/Sorting.tsx | 4 +- src/renderer/Header/FileTabs.tsx | 86 +++++++----- src/renderer/Header/Search.tsx | 6 +- src/renderer/Ipc.tsx | 37 +++-- src/renderer/Navigation.tsx | 4 +- src/renderer/Prompt.tsx | 16 +-- src/renderer/Settings/LanguageSelector.tsx | 19 ++- src/renderer/Settings/Settings.tsx | 25 ++-- src/renderer/Shared.tsx | 6 +- src/renderer/SplashScreen.tsx | 35 +++-- src/renderer/Themes.tsx | 7 - src/renderer/TodoDialog/AutoSuggest.tsx | 23 ++-- src/renderer/TodoDialog/DueDatePicker.tsx | 4 +- src/renderer/TodoDialog/PomodoroPicker.tsx | 4 +- src/renderer/TodoDialog/PriorityPicker.tsx | 8 +- src/renderer/TodoDialog/RecurrencePicker.tsx | 19 +-- .../TodoDialog/ThresholdDatePicker.tsx | 4 +- src/renderer/TodoDialog/TodoDialog.tsx | 37 +++-- src/types.tsx | 89 ++++++------ yarn.lock | 129 +++++++++--------- 81 files changed, 647 insertions(+), 617 deletions(-) diff --git a/.erb/configs/webpack.config.renderer.dev.ts b/.erb/configs/webpack.config.renderer.dev.ts index 1854e079..6df3ffdf 100644 --- a/.erb/configs/webpack.config.renderer.dev.ts +++ b/.erb/configs/webpack.config.renderer.dev.ts @@ -63,7 +63,7 @@ const configuration: webpack.Configuration = { module: { rules: [ { - test: /\.s?(c|a)ss$/, + test: /\.s?([ca])ss$/, use: [ 'style-loader', { @@ -76,12 +76,12 @@ const configuration: webpack.Configuration = { }, 'sass-loader', ], - include: /\.module\.s?(c|a)ss$/, + include: /\.module\.s?([ca])ss$/, }, { test: /\.s?css$/, use: ['style-loader', 'css-loader', 'sass-loader'], - exclude: /\.module\.s?(c|a)ss$/, + exclude: /\.module\.s?([ca])ss$/, }, // Fonts { diff --git a/.erb/configs/webpack.config.renderer.prod.ts b/.erb/configs/webpack.config.renderer.prod.ts index 3cebf30d..f9603da6 100644 --- a/.erb/configs/webpack.config.renderer.prod.ts +++ b/.erb/configs/webpack.config.renderer.prod.ts @@ -39,7 +39,7 @@ const configuration: webpack.Configuration = { module: { rules: [ { - test: /\.s?(a|c)ss$/, + test: /\.s?([ac])ss$/, use: [ MiniCssExtractPlugin.loader, { @@ -52,12 +52,12 @@ const configuration: webpack.Configuration = { }, 'sass-loader', ], - include: /\.module\.s?(c|a)ss$/, + include: /\.module\.s?([ca])ss$/, }, { - test: /\.s?(a|c)ss$/, + test: /\.s?([ac])ss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], - exclude: /\.module\.s?(c|a)ss$/, + exclude: /\.module\.s?([ca])ss$/, }, // Fonts { diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bd751dfb..8f8fff6b 100755 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -11,8 +11,8 @@ labels: 'feature request' ### Important ### If your feature request is more of a **loose idea** and you are **unsure about its implementation or potential impact**, please discuss it first in the [GitHub Discussions section](https://github.com/ransome1/sleek/discussions). -### Description ### -[_Provide a clear and detailed description of the feature you are requesting. Explain what the feature should do and how it would enhance the functionality or user experience of the app._] +### What problem does it solve? ### +Offer a comprehensive and detailed explanation of the problem addressed by this feature and its target audience. Describe the intended functionality and how it contributes to improving the overall app experience. ### Impact on the interface ### -[_What specific alterations will this feature make to the interface?_] \ No newline at end of file +How does this feature impact the interface in terms of interactive elements? Where should these elements be positioned, and do they require specific labels? Provide a detailed description of how you envision incorporating this feature into sleek. \ No newline at end of file diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 141b94b4..e4e38d73 100755 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -66,9 +66,6 @@ jobs: yarn run build - name: Run Electron Builder uses: samuelmeuli/action-electron-builder@v1 - env: - CSC_LINK: ${{ secrets.CSC_LINK }} - CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} with: max_attempts: 5 github_token: ${{ secrets.github_token }} diff --git a/.gitignore b/.gitignore index 4bd895b5..483a32c8 100755 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,5 @@ npm-debug.log.* *.drawio *.snap *FilterLang.js -*package-lock.json \ No newline at end of file +*package-lock.json +*qodana* \ No newline at end of file diff --git a/flatpak/com.github.ransome1.sleek.appdata.xml b/flatpak/com.github.ransome1.sleek.appdata.xml index 85f58a22..49e9cbed 100755 --- a/flatpak/com.github.ransome1.sleek.appdata.xml +++ b/flatpak/com.github.ransome1.sleek.appdata.xml @@ -9,7 +9,7 @@ Robin Ahle - + https://github.com/ransome1/sleek https://github.com/ransome1/sleek/issues diff --git a/flatpak/com.github.ransome1.sleek.desktop b/flatpak/com.github.ransome1.sleek.desktop index 41256a90..d2c647e5 100755 --- a/flatpak/com.github.ransome1.sleek.desktop +++ b/flatpak/com.github.ransome1.sleek.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=2.0.4 +Version=2.0.4-rc.5 Name=sleek Exec=sleek Type=Application diff --git a/package.json b/package.json index 0aff782a..516ee2d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sleek", - "version": "2.0.4", + "version": "2.0.4-rc.5", "main": "./src/main/main.tsx", "scripts": { "build": "concurrently \"yarn run peggy\" \"yarn run build:main\" \"yarn run build:renderer\"", @@ -97,6 +97,7 @@ "@teamsupercell/typings-for-css-modules-loader": "^2.5.2", "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.0.0", + "@types/react-autosuggest": "^10.1.10", "@types/jest": "^29.5.2", "@types/node": "20.10.5", "@types/react": "^18.2.8", @@ -110,7 +111,7 @@ "css-minimizer-webpack-plugin": "^5.0.0", "depcheck": "^1.4.7", "detect-port": "^1.5.1", - "electron": "^27.0.0", + "electron": "27.2.0", "electron-builder": "^24.2.1", "electron-devtools-installer": "^3.2.0", "electronmon": "^2.0.2", @@ -166,7 +167,7 @@ "isPackage": false, "rank": "Owner" } - ], + ], "icon": "assets/icons/icon.icns", "type": "distribution", "hardenedRuntime": true, @@ -212,7 +213,15 @@ "nsis" ], "icon": "assets/icons/sleek.ico", - "artifactName": "${productName}-${version}-win.${ext}" + "artifactName": "${productName}-${version}-win.${ext}", + "fileAssociations": [ + { + "ext": [ + "txt", + "md" + ] + } + ] }, "nsis": { "artifactName": "${productName}-${version}-win-Setup.${ext}" @@ -232,6 +241,14 @@ "freebsd", "rpm", "AppImage" + ], + "fileAssociations": [ + { + "ext": [ + "txt", + "md" + ] + } ] }, "directories": { diff --git a/release/app/package.json b/release/app/package.json index 4208b8fc..7d218ec5 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -1,6 +1,6 @@ { "name": "sleek", - "version": "2.0.4", + "version": "2.0.4-rc.5", "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)", "keywords": [ diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 945cca66..e3a17548 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: sleek base: core20 -version: "2.0.4" +version: "2.0.4-rc.5" summary: todo.txt manager for Linux, free and open-source (FOSS) description: | 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. diff --git a/src/__tests__/__mock__/recurrence.txt b/src/__tests__/__mock__/recurrence.txt index a16ffaac..db5cff66 100644 --- a/src/__tests__/__mock__/recurrence.txt +++ b/src/__tests__/__mock__/recurrence.txt @@ -1,15 +1,15 @@ -2023-12-26 Line 1 rec:1d due:2023-12-27 -2023-12-26 Line 1 rec:w due:2024-01-02 -2023-12-26 Line 1 rec:2m due:2024-02-26 -2023-12-26 Line 1 rec:+1d due:2023-12-28 -2023-12-26 Line 1 rec:7w due:2024-02-13 -2023-12-26 Line 1 due:2023-07-24 rec:+1b -2023-12-26 taxes are due in one year t:2022-03-30 due:2022-04-30 rec:+1y -2023-12-26 Water plants @home +quick due:2024-01-02 t:2023-12-23 rec:1w -2023-12-26 Line 1 rec:+1d t:2023-09-20 -2023-12-26 Line 1 rec:1d pri:A due:2023-12-27 -2023-12-26 (A) Do something rec:d t:2023-12-27 @SomeContext -2023-12-26 Do something rec:0d -2023-12-26 Do something rec:0d due:2023-12-26 -2023-12-26 Do something rec:0d due:2023-12-26 t:2023-12-26 \ No newline at end of file +2023-12-28 Line 1 rec:1d due:2023-12-29 +2023-12-28 Line 1 rec:w due:2024-01-04 +2023-12-28 Line 1 rec:2m due:2024-02-28 +2023-12-28 Line 1 rec:+1d due:2023-12-30 +2023-12-28 Line 1 rec:7w due:2024-02-15 +2023-12-28 Line 1 due:2023-07-24 rec:+1b +2023-12-28 taxes are due in one year t:2022-03-30 due:2022-04-30 rec:+1y +2023-12-28 Water plants @home +quick due:2024-01-04 t:2023-12-25 rec:1w +2023-12-28 Line 1 rec:+1d t:2023-09-20 +2023-12-28 Line 1 rec:1d pri:A due:2023-12-29 +2023-12-28 (A) Do something rec:d t:2023-12-29 @SomeContext +2023-12-28 Do something rec:0d +2023-12-28 Do something rec:0d due:2023-12-28 +2023-12-28 Do something rec:0d due:2023-12-28 t:2023-12-28 \ No newline at end of file diff --git a/src/__tests__/main/Archive.tsx b/src/__tests__/main/Archive.tsx index 04533ea2..35e8d291 100644 --- a/src/__tests__/main/Archive.tsx +++ b/src/__tests__/main/Archive.tsx @@ -1,4 +1,4 @@ -import archiveTodos from '../../main/modules/File/Archive'; +import { archiveTodos } from '../../main/modules/File/Archive'; import fs from 'fs/promises'; jest.mock('electron', () => ({ diff --git a/src/__tests__/main/ChangeCompleteState.tsx b/src/__tests__/main/ChangeCompleteState.tsx index 41cdf7ec..f8ef72c3 100644 --- a/src/__tests__/main/ChangeCompleteState.tsx +++ b/src/__tests__/main/ChangeCompleteState.tsx @@ -1,5 +1,4 @@ import { changeCompleteState } from '../../main/modules/ProcessDataRequest/ChangeCompleteState'; -import { Item } from 'jstodotxt'; import dayjs from 'dayjs'; const date: string = dayjs(new Date()).format('YYYY-MM-DD'); diff --git a/src/__tests__/main/CreateRecurringTodo.tsx b/src/__tests__/main/CreateRecurringTodo.tsx index 2d300d8f..739f2f6d 100644 --- a/src/__tests__/main/CreateRecurringTodo.tsx +++ b/src/__tests__/main/CreateRecurringTodo.tsx @@ -1,8 +1,5 @@ import fs from 'fs/promises'; -import { writeTodoObjectToFile } from '../../main/modules/File/Write'; import { createRecurringTodo } from '../../main/modules/ProcessDataRequest/CreateRecurringTodo'; -import { getActiveFile } from '../../main/modules/File/Active'; -import { lines } from '../../main/modules/ProcessDataRequest/CreateTodoObjects'; import dayjs from 'dayjs'; jest.mock('../../main/modules/ProcessDataRequest/CreateTodoObjects', () => ({ @@ -37,89 +34,89 @@ const dateTodayInSevenWeeks = dateToday.add(7, 'week').format('YYYY-MM-DD'); describe('Create recurring todos', () => { beforeEach(async () => { jest.clearAllMocks(); - fs.writeFile('./src/__tests__/__mock__/recurrence.txt', ''); + await fs.writeFile('./src/__tests__/__mock__/recurrence.txt', ''); }); test('Should add a new todo with due date set to tomorrow', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:1d`, '1d'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:1d`, '1d'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(dateTodayString + ' Line 1 rec:1d due:' + dateTomorrowString); }); test('Should add a new todo with due date set to next week', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:w`, 'w'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:w`, 'w'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(dateTodayString + ' Line 1 rec:w due:' + dateInOneWeekString); }); test('Should add a new todo with due date set to today in 2 months', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:2m`, '2m'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:2m`, '2m'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(dateTodayString + ' Line 1 rec:2m due:' + dateTodayInTwoMonths); }); test('Should add a new todo with due date set to day after tomorrow', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:+1d due:${dateTomorrowString}`, '+1d'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:+1d due:${dateTomorrowString}`, '+1d'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(dateTodayString + ' Line 1 rec:+1d due:' + dateDayAfterTomorrowString); }); test('Should add a new todo with due date set to today in 7 weeks', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:7w due:${dateTomorrowString}`, '7w'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:7w due:${dateTomorrowString}`, '7w'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(dateTodayString + ' Line 1 rec:7w due:' + dateTodayInSevenWeeks); }); test('Should add a new todo with due date set to next possible business day, based off a todo which already contains a due date', async () => { - const recurringTodo = await createRecurringTodo(`x 2023-07-21 2023-07-21 Line 1 due:2023-07-21 rec:+1b`, '+1b'); + await createRecurringTodo(`x 2023-07-21 2023-07-21 Line 1 due:2023-07-21 rec:+1b`, '+1b'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(`${dateTodayString} Line 1 due:2023-07-24 rec:+1b`); }); test('Should add a new todo adding a strict recurrence of one year to due date and threshold date', async () => { - const recurringTodo = await createRecurringTodo(`x 2021-01-01 2021-01-01 taxes are due in one year t:2021-03-30 due:2021-04-30 rec:+1y`, '+1y'); + await createRecurringTodo(`x 2021-01-01 2021-01-01 taxes are due in one year t:2021-03-30 due:2021-04-30 rec:+1y`, '+1y'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(`${dateTodayString} taxes are due in one year t:2022-03-30 due:2022-04-30 rec:+1y`); }); test('Should add a new todo adding a non-strict recurrence of one week to due date and threshold date', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Water plants @home +quick due:2021-07-19 t:2021-07-09 rec:1w`, '1w'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Water plants @home +quick due:2021-07-19 t:2021-07-09 rec:1w`, '1w'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(`${dateTodayString} Water plants @home +quick due:${dateInOneWeekString} t:${dateInOneWeekMinus10String} rec:1w`); }); test('Should add a new todo adding a strict recurrence of one day to threshold date. No due date should be created.', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:+1d t:2023-09-19`, '+1d'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:+1d t:2023-09-19`, '+1d'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(`${dateTodayString} Line 1 rec:+1d t:2023-09-20`); }); test('Should add a new todo and preserve the priority in the pri extension, but should not set the priority (A) for the task.', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:1d pri:A`, '1d'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Line 1 rec:1d pri:A`, '1d'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(`${dateTodayString} Line 1 rec:1d pri:A due:${dateTomorrowString}`); }); test('Should add a new todo based on a daily recurrence and a threshold date set for today, without unwanted due date and priority labels', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} (A) Do something rec:d t:${dateTodayString} @SomeContext`, 'd'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} (A) Do something rec:d t:${dateTodayString} @SomeContext`, 'd'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(`${dateTodayString} (A) Do something rec:d t:${dateTomorrowString} @SomeContext`); }); test('Should add a new todo based on a zero daily recurrence and no due date and no threshold date are set', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Do something rec:0d`, '0d'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Do something rec:0d`, '0d'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(`${dateTodayString} Do something rec:0d`); }); test('Should add a new todo based on a zero daily recurrence and a due date of today is set', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Do something rec:0d due:1999-11-11`, '0d'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Do something rec:0d due:1999-11-11`, '0d'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(`${dateTodayString} Do something rec:0d due:${dateTodayString}`); }); test('Should add a new todo based on a zero daily recurrence and a due date and a threshold date of today are set', async () => { - const recurringTodo = await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Do something rec:0d due:1999-11-11 t:2010-03-05`, '0d'); + await createRecurringTodo(`x ${dateTodayString} ${dateTodayString} Do something rec:0d due:1999-11-11 t:2010-03-05`, '0d'); const fileContent = await fs.readFile('./src/__tests__/__mock__/recurrence.txt', 'utf8'); expect(fileContent.split('\n').pop()).toEqual(`${dateTodayString} Do something rec:0d due:${dateTodayString} t:${dateTodayString}`); }); diff --git a/src/__tests__/main/CreateTodoObjects.tsx b/src/__tests__/main/CreateTodoObjects.tsx index 1cfb5d4a..049d665c 100644 --- a/src/__tests__/main/CreateTodoObjects.tsx +++ b/src/__tests__/main/CreateTodoObjects.tsx @@ -1,8 +1,4 @@ -import dayjs from 'dayjs'; import { createTodoObjects } from '../../main/modules/ProcessDataRequest/CreateTodoObjects'; -import { configStorage } from '../../main/config'; - -const dateTodayString: string = dayjs(new Date()).format('YYYY-MM-DD'); jest.mock('electron', () => ({ app: { diff --git a/src/__tests__/main/Dialog.tsx b/src/__tests__/main/Dialog.tsx index f1bc8949..4d9c21af 100644 --- a/src/__tests__/main/Dialog.tsx +++ b/src/__tests__/main/Dialog.tsx @@ -1,7 +1,6 @@ import { dialog } from 'electron'; import { openFile, createFile } from '../../main/modules/File/Dialog'; import { addFile } from '../../main/modules/File/File'; -import { configStorage } from '../../main/config'; import fs from 'fs/promises'; jest.mock('../../main/main', () => ({ diff --git a/src/__tests__/main/File.tsx b/src/__tests__/main/File.tsx index d91b48c0..bb4f04a0 100644 --- a/src/__tests__/main/File.tsx +++ b/src/__tests__/main/File.tsx @@ -1,6 +1,5 @@ import path from 'path' import { configStorage } from '../../main/config'; -import { createMenu } from '../../main/modules/Menu'; import { addFile, removeFile, setFile } from '../../main/modules/File/File'; jest.mock('../../main/main', () => ({ diff --git a/src/__tests__/main/ProcessTodoObjects.tsx b/src/__tests__/main/ProcessTodoObjects.tsx index a914804e..9f754420 100644 --- a/src/__tests__/main/ProcessTodoObjects.tsx +++ b/src/__tests__/main/ProcessTodoObjects.tsx @@ -169,8 +169,6 @@ const todoObjects: any = } ]; -let groupedTodoObjects: any; - describe('Process todo.txt objects', () => { beforeEach(() => { diff --git a/src/__tests__/main/Write.tsx b/src/__tests__/main/Write.tsx index 5603bbdf..67a8c964 100644 --- a/src/__tests__/main/Write.tsx +++ b/src/__tests__/main/Write.tsx @@ -1,6 +1,5 @@ import fs from 'fs/promises'; import { writeTodoObjectToFile, removeLineFromFile } from '../../main/modules/File/Write'; -import { lines } from '../../main/modules/ProcessDataRequest/CreateTodoObjects'; import { configStorage } from '../../main/config'; import dayjs from 'dayjs'; diff --git a/src/locales/cs.json b/src/locales/cs.json index bb95c5e3..b0d58a5d 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -5,7 +5,8 @@ "openFile": "Otevřít soubor", "createFile": "Vytvořit soubor", "fileTabs.changeLocation": "Změnit soubor pro archivaci", - "fileTabs.revealFile": "Zobrazit v správci souborů", + "fileTabs.revealTodoFile": "Zobrazit soubor s úkoly v průzkumníku souborů", + "fileTabs.revealArchivingFile": "Zobrazit archivační soubor v průzkumníku souborů", "fileTabs.removeFileHeadline": "Odstranit soubor ze sleek?", "fileTabs.removeFileText": "Nebude smazán z pevného disku.", "fileTabs.removeFileLabel": "Odstranit", diff --git a/src/locales/de.json b/src/locales/de.json index 28edff93..f2ed1343 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -5,7 +5,8 @@ "openFile": "Datei öffnen", "createFile": "Datei erstellen", "fileTabs.changeLocation": "Datei für die Archivierung ändern", - "fileTabs.revealFile": "Im Dateimanager anzeigen", + "fileTabs.revealTodoFile": "Todo-Datei im Dateimanager anzeigen", + "fileTabs.revealArchivingFile": "Archivdatei im Dateimanager anzeigen", "fileTabs.removeFileHeadline": "Datei aus sleek entfernen?", "fileTabs.removeFileText": "Die Datei wird nicht von Ihrer Festplatte gelöscht.", "fileTabs.removeFileLabel": "Entfernen", diff --git a/src/locales/en-gb.json b/src/locales/en-gb.json index ceeed85e..7e112524 100644 --- a/src/locales/en-gb.json +++ b/src/locales/en-gb.json @@ -5,7 +5,8 @@ "openFile": "Open file", "createFile": "Create file", "fileTabs.changeLocation": "Change file for archiving", - "fileTabs.revealFile": "Reveal in file manager", + "fileTabs.revealTodoFile": "Show todo file in file manager", + "fileTabs.revealArchivingFile": "Show archiving file in file manager", "fileTabs.removeFileHeadline": "Remove file from sleek?", "fileTabs.removeFileText": "It will not be deleted from your hard drive.", "fileTabs.removeFileLabel": "Remove", diff --git a/src/locales/en.json b/src/locales/en.json index ceeed85e..7e112524 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -5,7 +5,8 @@ "openFile": "Open file", "createFile": "Create file", "fileTabs.changeLocation": "Change file for archiving", - "fileTabs.revealFile": "Reveal in file manager", + "fileTabs.revealTodoFile": "Show todo file in file manager", + "fileTabs.revealArchivingFile": "Show archiving file in file manager", "fileTabs.removeFileHeadline": "Remove file from sleek?", "fileTabs.removeFileText": "It will not be deleted from your hard drive.", "fileTabs.removeFileLabel": "Remove", diff --git a/src/locales/es.json b/src/locales/es.json index 0727c185..96a99f61 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -5,7 +5,8 @@ "openFile": "Abrir archivo", "createFile": "Crear archivo", "fileTabs.changeLocation": "Cambiar archivo para archivar", - "fileTabs.revealFile": "Mostrar en el administrador de archivos", + "fileTabs.revealTodoFile": "Mostrar archivo de tareas en el administrador de archivos", + "fileTabs.revealArchivingFile": "Mostrar archivo de archivo en el administrador de archivos", "fileTabs.removeFileHeadline": "¿Eliminar archivo de sleek?", "fileTabs.removeFileText": "No se eliminará de su disco duro.", "fileTabs.removeFileLabel": "Eliminar", diff --git a/src/locales/fr.json b/src/locales/fr.json index 187289aa..418a9412 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -5,7 +5,8 @@ "openFile": "Ouvrir un fichier", "createFile": "Créer un fichier", "fileTabs.changeLocation": "Changer le fichier pour archiver", - "fileTabs.revealFile": "Révéler dans le gestionnaire de fichiers", + "fileTabs.revealTodoFile": "Afficher le fichier de tâches dans le gestionnaire de fichiers", + "fileTabs.revealArchivingFile": "Afficher le fichier d'archivage dans le gestionnaire de fichiers", "fileTabs.removeFileHeadline": "Supprimer le fichier de sleek?", "fileTabs.removeFileText": "Il ne sera pas supprimé de votre disque dur.", "fileTabs.removeFileLabel": "Supprimer", diff --git a/src/locales/hi.json b/src/locales/hi.json index 50c3c98f..6481beed 100644 --- a/src/locales/hi.json +++ b/src/locales/hi.json @@ -5,7 +5,8 @@ "openFile": "फ़ाइल खोलें", "createFile": "फ़ाइल बनाएं", "fileTabs.changeLocation": "आर्काइव के लिए फ़ाइल बदलें", - "fileTabs.revealFile": "फ़ाइल प्रबंधक में दिखाएँ", + "fileTabs.revealTodoFile": "टोडो फ़ाइल को फ़ाइल प्रबंधक में दिखाएं", + "fileTabs.revealArchivingFile": "आर्काइविंग फ़ाइल को फ़ाइल प्रबंधक में दिखाएं", "fileTabs.removeFileHeadline": "sleek से फ़ाइल हटाएं?", "fileTabs.removeFileText": "यह आपके हार्ड ड्राइव से हटाएं नहीं होगा।", "fileTabs.removeFileLabel": "हटाएं", diff --git a/src/locales/hu.json b/src/locales/hu.json index 0a556171..6226be68 100644 --- a/src/locales/hu.json +++ b/src/locales/hu.json @@ -5,7 +5,8 @@ "openFile": "Fájl megnyitása", "createFile": "Fájl létrehozása", "fileTabs.changeLocation": "Fájl megváltoztatása az archiváláshoz", - "fileTabs.revealFile": "Megmutatás a fájlkezelőben", + "fileTabs.revealTodoFile": "Mutassa meg a teendő fájlt a fájlkezelőben", + "fileTabs.revealArchivingFile": "Mutassa meg az archiválási fájlt a fájlkezelőben", "fileTabs.removeFileHeadline": "Fájl eltávolítása a sleek?", "fileTabs.removeFileText": "Nem kerül törlésre a merevlemezről.", "fileTabs.removeFileLabel": "Eltávolítás", diff --git a/src/locales/it.json b/src/locales/it.json index e05e5a44..320392a8 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -5,7 +5,8 @@ "openFile": "Apri file", "createFile": "Crea file", "fileTabs.changeLocation": "Cambia file per l'archiviazione", - "fileTabs.revealFile": "Mostra nel gestore di file", + "fileTabs.revealTodoFile": "Mostra il file delle attività nel gestore dei file", + "fileTabs.revealArchivingFile": "Mostra il file di archiviazione nel gestore dei file", "fileTabs.removeFileHeadline": "Rimuovi il file da sleek?", "fileTabs.removeFileText": "Non verrà eliminato dal tuo disco rigido.", "fileTabs.removeFileLabel": "Rimuovi", diff --git a/src/locales/jp.json b/src/locales/jp.json index 905dec91..cc26543f 100644 --- a/src/locales/jp.json +++ b/src/locales/jp.json @@ -5,7 +5,8 @@ "openFile": "ファイルを開く", "createFile": "ファイルを作成", "fileTabs.changeLocation": "アーカイブ用のファイルを変更", - "fileTabs.revealFile": "ファイルマネージャーで表示", + "fileTabs.revealTodoFile": "ファイルマネージャーでToDoファイルを表示する", + "fileTabs.revealArchivingFile": "ファイルマネージャーでアーカイブファイルを表示する", "fileTabs.removeFileHeadline": "sleek からファイルを削除しますか?", "fileTabs.removeFileText": "ハードディスクからは削除されません。", "fileTabs.removeFileLabel": "削除", diff --git a/src/locales/ko.json b/src/locales/ko.json index b3e4ab4e..7cbbed82 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -5,7 +5,8 @@ "openFile": "파일 열기", "createFile": "파일 생성", "fileTabs.changeLocation": "아카이브를 위한 파일 변경", - "fileTabs.revealFile": "파일 매니저에서 보기", + "fileTabs.revealTodoFile": "파일 관리자에서 To-do 파일 표시", + "fileTabs.revealArchivingFile": "파일 관리자에서 아카이브 파일 표시", "fileTabs.removeFileHeadline": "sleek에서 파일 삭제?", "fileTabs.removeFileText": "하드 드라이브에서는 삭제되지 않습니다.", "fileTabs.removeFileLabel": "삭제", diff --git a/src/locales/pl.json b/src/locales/pl.json index 0afabf24..850551d0 100644 --- a/src/locales/pl.json +++ b/src/locales/pl.json @@ -5,7 +5,8 @@ "openFile": "Otwórz plik", "createFile": "Utwórz plik", "fileTabs.changeLocation": "Zmień plik do archiwizacji", - "fileTabs.revealFile": "Pokaż w menedżerze plików", + "fileTabs.revealTodoFile": "Pokaż plik zadań w menedżerze plików", + "fileTabs.revealArchivingFile": "Pokaż plik archiwizacyjny w menedżerze plików", "fileTabs.removeFileHeadline": "Usunąć plik z sleek?", "fileTabs.removeFileText": "Nie zostanie usunięty z dysku twardego.", "fileTabs.removeFileLabel": "Usuń", diff --git a/src/locales/pt.json b/src/locales/pt.json index 49183a94..a3ab73b9 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -5,7 +5,8 @@ "openFile": "Abrir ficheiro", "createFile": "Criar ficheiro", "fileTabs.changeLocation": "Alterar arquivo para arquivamento", - "fileTabs.revealFile": "Revelar no gerenciador de arquivos", + "fileTabs.revealTodoFile": "Mostrar arquivo de tarefas no gerenciador de arquivos", + "fileTabs.revealArchivingFile": "Mostrar arquivo de arquivamento no gerenciador de arquivos", "fileTabs.removeFileHeadline": "Remover ficheiro do sleek?", "fileTabs.removeFileText": "Não será eliminado do seu disco rígido.", "fileTabs.removeFileLabel": "Remover", diff --git a/src/locales/ru.json b/src/locales/ru.json index 27ad641e..6e0b387f 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -5,7 +5,8 @@ "openFile": "Открыть файл", "createFile": "Создать файл", "fileTabs.changeLocation": "Изменить файл для архивации", - "fileTabs.revealFile": "Показать в файловом менеджере", + "fileTabs.revealTodoFile": "Показать файл задач в файловом менеджере", + "fileTabs.revealArchivingFile": "Показать архивный файл в файловом менеджере", "fileTabs.removeFileHeadline": "Удалить файл из sleek?", "fileTabs.removeFileText": "Он не будет удален с вашего жесткого диска.", "fileTabs.removeFileLabel": "Удалить", diff --git a/src/locales/tr.json b/src/locales/tr.json index ad64321e..a7453008 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -5,7 +5,8 @@ "openFile": "Dosyasını aç", "createFile": "Dosyası oluştur", "fileTabs.changeLocation": "Arşivleme için dosya değiştir", - "fileTabs.revealFile": "Dosya yöneticisinde göster", + "fileTabs.revealTodoFile": "Görev dosyasını dosya yöneticisinde göster", + "fileTabs.revealArchivingFile": "Arşiv dosyasını dosya yöneticisinde göster", "fileTabs.removeFileHeadline": "Dosyayı sleek'ten kaldırmak istiyor musunuz?", "fileTabs.removeFileText": "Sabit diskinizden silinmeyecektir.", "fileTabs.removeFileLabel": "Kaldır", diff --git a/src/locales/zh.json b/src/locales/zh.json index 00d93e9f..d8830e1f 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -5,7 +5,8 @@ "openFile": "打开 文件", "createFile": "创建 文件", "fileTabs.changeLocation": "更改文件以进行归档", - "fileTabs.revealFile": "在文件管理器中显示", + "fileTabs.revealTodoFile": "在文件管理器中显示待办文件", + "fileTabs.revealArchivingFile": "在文件管理器中显示归档文件", "fileTabs.removeFileHeadline": "从 sleek 中删除文件?", "fileTabs.removeFileText": "它不会从您的硬盘驱动器中删除。", "fileTabs.removeFileLabel": "删除", diff --git a/src/main/config.tsx b/src/main/config.tsx index b5ebecb9..5e18f5f0 100644 --- a/src/main/config.tsx +++ b/src/main/config.tsx @@ -9,15 +9,19 @@ import processDataRequest from './modules/ProcessDataRequest/ProcessDataRequest' import handleTheme from './modules/Theme'; import crypto from 'crypto'; -const anonymousUserId = crypto.randomUUID() || null; +Store.initRenderer(); + +const anonymousUserId: string = crypto.randomUUID() || null; + +const userDataDirectory: string = path.join(app.getPath('userData'), 'userData'); -const userDataDirectory = path.join(app.getPath('userData'), 'userData'); if(!fs.existsSync(userDataDirectory)) fs.mkdirSync(userDataDirectory) + console.log('config.ts: sleek userdata is located at: ' + userDataDirectory); -const customStylesPath = path.join(userDataDirectory, 'customStyles.css'); +const customStylesPath: string = path.join(userDataDirectory, 'customStyles.css'); -const configStorage = new Store({ +const configStorage: Store = new Store({ cwd: userDataDirectory, name: 'config', beforeEachMigration: context => { diff --git a/src/main/main.tsx b/src/main/main.tsx index 2d7ec616..e43ebcd2 100644 --- a/src/main/main.tsx +++ b/src/main/main.tsx @@ -6,6 +6,7 @@ import { createMenu } from './modules/Menu'; import handleTheme from './modules/Theme'; import { getAssetPath, resolveHtmlPath } from './util'; import { createFileWatcher, watcher } from './modules/File/Watcher'; +import { addFile } from './modules/File/File'; import { createTray } from './modules/Tray'; import './modules/Ipc'; @@ -92,8 +93,6 @@ const handleWindowSizeAndPosition = () => { } const createMainWindow = () => { - const startTime = performance.now(); - mainWindow = new BrowserWindow({ width: 1280, height: 1000, @@ -171,10 +170,15 @@ const handleBeforeQuit = () => { app.releaseSingleInstanceLock(); } +const handleOpenFile = (event, path) => { + if(path) addFile(path, null); +}; + app .on('window-all-closed', handleWindowAllClosed) .on('before-quit', handleBeforeQuit) .on('activate', handleCreateWindow) + .on('open-file', handleOpenFile) .whenReady() .then(() => { diff --git a/src/main/modules/File/Archive.tsx b/src/main/modules/File/Archive.tsx index 7a215e74..dd8129f1 100644 --- a/src/main/modules/File/Archive.tsx +++ b/src/main/modules/File/Archive.tsx @@ -2,10 +2,16 @@ import { app } from 'electron'; import fs from 'fs/promises'; import { getActiveFile } from './Active'; import { replaceFileContent } from './Write'; +import { mainWindow } from '../../main'; import { createTodoObjects } from '../ProcessDataRequest/CreateTodoObjects'; let stopAccessingSecurityScopedResource: any; +function handleRequestArchive(): void { + const activeFile = getActiveFile(); + mainWindow!.webContents.send('triggerArchiving', Boolean(activeFile?.doneFilePath)); +} + async function extractTodoStringsFromFile(filePath: string, complete: boolean | null, bookmark: string | null): Promise { if(bookmark) stopAccessingSecurityScopedResource = app.startAccessingSecurityScopedResource(bookmark); @@ -24,17 +30,17 @@ async function archiveTodos(): Promise { const activeFile: FileObject | null = getActiveFile(); if(activeFile === null) { - throw new Error('No active file defined'); + return 'No active file defined'; } if(activeFile.doneFilePath === null) { - throw new Error('Archiving file is not defined'); + return 'Archiving file is not defined'; } const completedTodos = await extractTodoStringsFromFile(activeFile.todoFilePath, true, activeFile.todoFileBookmark); if(completedTodos.length === 0) { - throw new Error(`No completed todos found`); + return 'No completed todos found'; } const uncompletedTodos = await extractTodoStringsFromFile(activeFile.todoFilePath, false, activeFile.todoFileBookmark); @@ -49,4 +55,4 @@ async function archiveTodos(): Promise { return 'Successfully archived'; } -export default archiveTodos; +export { archiveTodos, handleRequestArchive }; diff --git a/src/main/modules/File/Dialog.tsx b/src/main/modules/File/Dialog.tsx index 0c6da80a..7bdcce64 100644 --- a/src/main/modules/File/Dialog.tsx +++ b/src/main/modules/File/Dialog.tsx @@ -15,55 +15,47 @@ const dialogFilters = [ ] async function openFile(setDoneFile: boolean): Promise { - try { - const result: OpenDialogReturnValue = await dialog.showOpenDialog({ - properties: ['openFile'], - filters: dialogFilters, - securityScopedBookmarks: true, - }); - if(!result.canceled && result.filePaths.length > 0) { - const filePath: string = result.filePaths[0]; - const securityScopedBookmark: string | null = result.bookmarks?.[0] || null; + const result: OpenDialogReturnValue = await dialog.showOpenDialog({ + properties: ['openFile'], + filters: dialogFilters, + securityScopedBookmarks: true, + }); + if(!result.canceled && result.filePaths.length > 0) { + const filePath: string = result.filePaths[0]; + const securityScopedBookmark: string | null = result.bookmarks?.[0] || null; - if(setDoneFile) { - addDoneFile(filePath, securityScopedBookmark); - } else { - addFile(filePath, securityScopedBookmark); - } + if(setDoneFile) { + addDoneFile(filePath, securityScopedBookmark); + } else { + addFile(filePath, securityScopedBookmark); } - return; - } catch (error: any) { - console.error('FileDialog.ts:', error); } + return; } async function createFile(setDoneFile: boolean): Promise { - try { - const defaultFileName = setDoneFile ? 'done.txt' : 'todo.txt'; + const defaultFileName = setDoneFile ? 'done.txt' : 'todo.txt'; - const result: SaveDialogReturnValue = await dialog.showSaveDialog({ - defaultPath: path.join(app.getPath('documents'), defaultFileName), - filters: dialogFilters, - securityScopedBookmarks: true, - }); + const result: SaveDialogReturnValue = await dialog.showSaveDialog({ + defaultPath: path.join(app.getPath('documents'), defaultFileName), + filters: dialogFilters, + securityScopedBookmarks: true, + }); - if(!result.canceled && result.filePath) { - const filePath: string = result.filePath; - const securityScopedBookmark: string | null = result.bookmark || null; + if(!result.canceled && result.filePath) { + const filePath: string = result.filePath; + const securityScopedBookmark: string | null = result.bookmark || null; - await fs.writeFile(filePath, ''); + await fs.writeFile(filePath, ''); - if(setDoneFile) { - addDoneFile(filePath, securityScopedBookmark); - } else { - addFile(filePath, securityScopedBookmark); - } + if(setDoneFile) { + addDoneFile(filePath, securityScopedBookmark); + } else { + addFile(filePath, securityScopedBookmark); } - - return; - } catch (error: any) { - console.error('FileDialog.ts:', error); } + + return; } export { diff --git a/src/main/modules/File/File.tsx b/src/main/modules/File/File.tsx index 7d98bdba..6d29b3ea 100644 --- a/src/main/modules/File/File.tsx +++ b/src/main/modules/File/File.tsx @@ -23,7 +23,7 @@ async function readFileContent(filePath: string, bookmark: string | null) { } function addFile(filePath: string, bookmark: string | null) { - if(process.mas) throw new Error('This release does not support the drag and drop function, please use the file dialog'); + if(process.mas && !bookmark) throw new Error('The Mac App Store release requires you to open files using the file dialog'); const files: FileObject[] = configStorage.get('files'); const existingFileIndex = files.findIndex((file) => file.todoFilePath === filePath); diff --git a/src/main/modules/Filters/FilterQuery.js b/src/main/modules/Filters/FilterQuery.js index 321929fe..0dfa9676 100644 --- a/src/main/modules/Filters/FilterQuery.js +++ b/src/main/modules/Filters/FilterQuery.js @@ -80,12 +80,12 @@ function runQuery(todoObject, compiledQuery) { case "==": operand2 = stack.pop(); operand1 = stack.pop(); - stack.push(operand1 == operand2); + stack.push(operand1 === operand2); break; case "!=": operand2 = stack.pop(); operand1 = stack.pop(); - stack.push(operand1 != operand2); + stack.push(operand1 !== operand2); break; case "<": operand2 = stack.pop(); @@ -109,7 +109,7 @@ function runQuery(todoObject, compiledQuery) { break; case "++": next = q.shift(); - if(next == "*") { + if(next === "*") { stack.push(todoObject.projects ? true : false); } else if(next.startsWith('"')) { stack.push(todoObject.projects && todoObject.projects.includes(next.slice(1,-1))); @@ -123,7 +123,7 @@ function runQuery(todoObject, compiledQuery) { break; case "@@": next = q.shift(); - if(next == "*") { + if(next === "*") { stack.push(todoObject.contexts ? true : false); } else if(next.startsWith('"')) { stack.push(todoObject.contexts && todoObject.contexts.includes(next.slice(1,-1))); diff --git a/src/main/modules/Ipc.tsx b/src/main/modules/Ipc.tsx index d2c5e20a..e4149fc5 100644 --- a/src/main/modules/Ipc.tsx +++ b/src/main/modules/Ipc.tsx @@ -3,7 +3,7 @@ import { ipcMain, app, IpcMainEvent, clipboard } from 'electron'; import processDataRequest from './ProcessDataRequest/ProcessDataRequest'; import { changeCompleteState } from './ProcessDataRequest/ChangeCompleteState'; import { writeTodoObjectToFile, removeLineFromFile } from './File/Write'; -import archiveTodos from './File/Archive'; +import { archiveTodos, handleRequestArchive } from './File/Archive'; import { configStorage, filterStorage, notifiedTodoObjectsStorage } from '../config'; import { addFile, setFile, removeFile } from './File/File'; import { openFile, createFile } from './File/Dialog'; @@ -28,7 +28,7 @@ async function handleUpdateAttributeFields(event: IpcMainEvent, index: number, s } } -function handleUpdateTodoObject(event: IpcMainEvent, index, string, attributeType, attributeValue): Promise { +function handleUpdateTodoObject(event: IpcMainEvent, index: number, string: string, attributeType: string, attributeValue: string): void { try { const todoObject = createTodoObject(index, string, attributeType, attributeValue); event.reply('updateTodoObject', todoObject); @@ -115,7 +115,7 @@ function handleAddFile(event: IpcMainEvent, filePath: string): void { } } -async function handleDroppedFile(event: IpcMainEvent, filePath: string): Promise { +function handleDroppedFile(event: IpcMainEvent, filePath: string): void { try { addFile(filePath, null); } catch (error: any) { @@ -199,6 +199,7 @@ function removeEventListeners(): void { ipcMain.off('revealInFileManager', handleRevealInFileManager); ipcMain.off('removeLineFromFile', handleRemoveLineFromFile); ipcMain.off('updateTodoObject', handleUpdateTodoObject); + ipcMain.off('requestArchive', handleRequestArchive); } app.on('before-quit', removeEventListeners); @@ -221,4 +222,4 @@ ipcMain.on('saveToClipboard', handleSaveToClipboard); ipcMain.on('revealInFileManager', handleRevealInFileManager); ipcMain.on('removeLineFromFile', handleRemoveLineFromFile); ipcMain.on('updateTodoObject', handleUpdateTodoObject); - +ipcMain.on('requestArchive', handleRequestArchive); \ No newline at end of file diff --git a/src/main/modules/Menu.tsx b/src/main/modules/Menu.tsx index d9c18e54..aa007919 100644 --- a/src/main/modules/Menu.tsx +++ b/src/main/modules/Menu.tsx @@ -2,7 +2,7 @@ import { app, Menu, dialog, shell } from 'electron'; import { setFile } from './File/File'; import { mainWindow } from '../main'; import { openFile, createFile } from './File/Dialog'; -import { getActiveFile } from './File/Active'; +import { handleRequestArchive } from './File/Archive'; import { configStorage, filterStorage } from '../config'; import appPackage from '../../../release/app/package.json'; @@ -48,7 +48,7 @@ function createMenu(files: FileObject[]) { label: 'Hide', accelerator: 'Cmd+H', role: 'hide', - }, + } ] : []), { type: 'separator' }, @@ -175,8 +175,7 @@ function createMenu(files: FileObject[]) { label: 'Archive completed todos', accelerator: 'Ctrl+Alt+A', click: () => { - const activeFile = getActiveFile(); - mainWindow!.webContents.send('triggerArchiving', Boolean(activeFile?.doneFilePath)); + handleRequestArchive(); }, }, { diff --git a/src/main/modules/ProcessDataRequest/CreateRecurringTodo.tsx b/src/main/modules/ProcessDataRequest/CreateRecurringTodo.tsx index e2de5c1c..143110e1 100644 --- a/src/main/modules/ProcessDataRequest/CreateRecurringTodo.tsx +++ b/src/main/modules/ProcessDataRequest/CreateRecurringTodo.tsx @@ -82,8 +82,6 @@ const createRecurringTodo = async (string: string, recurrence: string): Promise< JsTodoTxtObject.setComplete(false); JsTodoTxtObject.setCompleted(null); - updatedString = JsTodoTxtObject.toString().replaceAll(` ${String.fromCharCode(16)} `, String.fromCharCode(16)); - await writeTodoObjectToFile(-1, JsTodoTxtObject.toString()); return 'Recurring todo created'; diff --git a/src/main/modules/ProcessDataRequest/CreateTodoObjects.tsx b/src/main/modules/ProcessDataRequest/CreateTodoObjects.tsx index 3e85c16c..5530af0d 100644 --- a/src/main/modules/ProcessDataRequest/CreateTodoObjects.tsx +++ b/src/main/modules/ProcessDataRequest/CreateTodoObjects.tsx @@ -2,7 +2,6 @@ import { app } from 'electron'; import { Item } from 'jstodotxt'; import { handleNotification } from '../Notifications'; import { extractSpeakingDates } from '../Date'; -import { configStorage } from '../../config'; import dayjs from 'dayjs'; let lines: string[]; @@ -29,7 +28,7 @@ function createTodoObject(index: number, string: string, attributeType?: string, const tString = speakingDates['t:']?.string || null; const extensions = JsTodoTxtObject.extensions(); const hidden = extensions.some(extension => extension.key === 'h' && extension.value === '1'); - const pm = extensions.find(extension => extension.key === 'pm')?.value || null; + const pm: string | number | null = extensions.find(extension => extension.key === 'pm')?.value || null; const rec = extensions.find(extension => extension.key === 'rec')?.value || null; const creation = dayjs(JsTodoTxtObject.created()).isValid() ? dayjs(JsTodoTxtObject.created()).format('YYYY-MM-DD') : null; const completed = dayjs(JsTodoTxtObject.completed()).isValid() ? dayjs(JsTodoTxtObject.completed()).format('YYYY-MM-DD') : null; diff --git a/src/main/modules/ProcessDataRequest/ProcessDataRequest.tsx b/src/main/modules/ProcessDataRequest/ProcessDataRequest.tsx index f6c44201..e7258c30 100644 --- a/src/main/modules/ProcessDataRequest/ProcessDataRequest.tsx +++ b/src/main/modules/ProcessDataRequest/ProcessDataRequest.tsx @@ -13,9 +13,9 @@ const headers: HeadersObject = { completedTodoObjects: 0, }; -let searchString; +let searchString: string; -async function processDataRequest(search: string): Promise { +async function processDataRequest(search?: string): Promise { if(search) { searchString = search; @@ -49,8 +49,6 @@ async function processDataRequest(search: string): Promise { updateAttributes(todoObjects, sorting, false); - let flattenedTodoObjects: TodoObject[]; - if(fileSorting) { todoObjects = flattenTodoObjects(todoObjects, ''); } else { diff --git a/src/main/modules/Theme.tsx b/src/main/modules/Theme.tsx index 0158c859..f2afc982 100644 --- a/src/main/modules/Theme.tsx +++ b/src/main/modules/Theme.tsx @@ -1,5 +1,4 @@ import { nativeTheme } from 'electron'; -import { mainWindow } from '../main'; import { configStorage } from '../config'; function handleTheme() { @@ -14,7 +13,6 @@ function handleTheme() { } else { shouldUseDarkColors = false; } - nativeTheme.themeSource = colorTheme; configStorage.set('shouldUseDarkColors', shouldUseDarkColors); } diff --git a/src/main/modules/Tray.tsx b/src/main/modules/Tray.tsx index 77a79218..0f7f5c23 100644 --- a/src/main/modules/Tray.tsx +++ b/src/main/modules/Tray.tsx @@ -16,7 +16,7 @@ function createMenuTemplate(files: FileObject[]): Electron.MenuItemConstructorOp }, { type: 'separator' }, ...(files?.length > 0 - ? files.map((file: File, index: number) => ({ + ? files.map((file: FileObject, index: number) => ({ label: file.todoFileName!, accelerator: `CommandOrControl+${index + 1}`, click: () => { diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index d2a6ed47..6b9bf8c2 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { ThemeProvider } from '@mui/material/styles'; import IpcComponent from './Ipc'; import MatomoComponent from './Matomo'; -import { CssBaseline, Snackbar, Alert, Box } from '@mui/material'; +import { CssBaseline, Snackbar, Alert, Box, AlertColor } from '@mui/material'; import NavigationComponent from './Navigation'; import TodoDataGrid from './DataGrid/Grid'; import SplashScreen from './SplashScreen'; @@ -35,53 +35,42 @@ const translatedAttributes = (t: typeof i18n.t) => ({ }); const App = () => { - const [settings, setSettings] = useState(store.get()); + const [settings, setSettings] = useState(store.get()); const [splashScreen, setSplashScreen] = useState(null); const [snackBarOpen, setSnackBarOpen] = useState(false); const [snackBarContent, setSnackBarContent] = useState(null); - const [snackBarSeverity, setSnackBarSeverity] = useState(null); + const [snackBarSeverity, setSnackBarSeverity] = useState(); const [dialogOpen, setDialogOpen] = useState(false); const [searchString, setSearchString] = useState(''); const [todoObjects, setTodoObjects] = useState(null); const [todoObject, setTodoObject] = useState(null); const [attributeFields, setAttributeFields] = useState(null); const [headers, setHeaders] = useState(null); - const [filters, setFilters] = useState(null); + const [filters, setFilters] = useState([]); const [attributes, setAttributes] = useState(null); - const [isSettingsOpen, setIsSettingsOpen] = useState(false); - const [contextMenuItems, setContextMenuItems] = useState(null); - const [textFieldValue, setTextFieldValue] = useState(''); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + const [contextMenu, setContextMenu] = useState(null); + const [textFieldValue, setTextFieldValue] = useState(''); const [promptItem, setPromptItem] = useState(null); const [triggerArchiving, setTriggerArchiving] = useState(false); - const [showPromptDoneFile, setShowPromptDoneFile] = useState(false); - const searchFieldRef = useRef(null); - - const [attributeMapping, setAttributeMapping] = useState(translatedAttributes(i18n.t) || {}); + const searchFieldRef = useRef(null); + const [attributeMapping] = useState(translatedAttributes(i18n.t) || {}); + const [visibleRowCount, setVisibleRowCount] = useState(30); + const [loadMoreRows, setLoadMoreRows] = useState(true); useEffect(() => { - if(!headers) { - return; - } else if(headers.availableObjects === 0) { - setSplashScreen('noTodosAvailable'); - } else if(headers.visibleObjects === 0) { - setSplashScreen('noTodosVisible'); - } else { - setSplashScreen(null); - } - }, [headers]); + setSnackBarOpen(Boolean(snackBarContent)); + }, [snackBarContent]); useEffect(() => { + setVisibleRowCount(30); + setLoadMoreRows(true); if(settings.files?.length === 0) { - setTodoObjects(null); - setHeaders(null); setSplashScreen('noFiles'); + setTodoObjects(null); } }, [settings.files]); - useEffect(() => { - setSnackBarOpen((!snackBarContent) ? false : true); - }, [snackBarContent]); - useEffect(() => { ipcRenderer.send('requestData'); }, []); @@ -98,30 +87,26 @@ const App = () => { setSnackBarSeverity={setSnackBarSeverity} setSnackBarContent={setSnackBarContent} setSettings={setSettings} - setTriggerArchiving={setTriggerArchiving} setSplashScreen={setSplashScreen} setIsSettingsOpen={setIsSettingsOpen} /> + /> - + - + - {settings.files?.length > 0 && ( + {settings?.files?.length > 0 && ( { {settings.showFileTabs ? : null} - {headers?.availableObjects > 0 ? + {headers && headers.availableObjects > 0 ? <> { : null } )} - + {todoObjects?.length > 0 && ( + <> + + + )} @@ -195,10 +188,10 @@ const App = () => { settings={settings} attributeMapping={attributeMapping} /> - {contextMenuItems && ( + {contextMenu && ( )} @@ -211,7 +204,7 @@ const App = () => { {snackBarContent} - {settings.files?.length > 0 && ( + {settings?.files?.length > 0 && ( { onClose={() => setPromptItem(null)} promptItem={promptItem} setPromptItem={setPromptItem} - setContextMenuItems={setContextMenuItems} + setContextMenu={setContextMenu} /> )} diff --git a/src/renderer/Archive.tsx b/src/renderer/Archive.tsx index 50309c23..3db3515f 100644 --- a/src/renderer/Archive.tsx +++ b/src/renderer/Archive.tsx @@ -1,6 +1,5 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { withTranslation, WithTranslation } from 'react-i18next'; -import Prompt from './Prompt'; import { i18n } from './Settings/LanguageSelector'; const { ipcRenderer} = window.api; @@ -9,21 +8,18 @@ interface Props extends WithTranslation { triggerArchiving: boolean; setTriggerArchiving: React.Dispatch>; settings: Settings, - setPromptItem: React.Dispatch>; - headers: HeadersObject; + setPromptItem: React.Dispatch>; + headers: HeadersObject | null; t: typeof i18n.t; } const Archive: React.FC = ({ triggerArchiving, - setTriggerArchiving, - settings, setPromptItem, - headers, t }) => { - const handleTriggerArchiving = (doneFileAvailable) => { + const handleTriggerArchiving = (doneFileAvailable: boolean) => { setPromptItem((doneFileAvailable) ? promptItemArchiving : promptItemChooseChangeFile); }; @@ -40,7 +36,7 @@ const Archive: React.FC = ({ }; const promptItemArchiving = { - id: 'delete', + id: 'archive', headline: t('prompt.archive.headline'), text: t('prompt.archive.text'), button1: t('prompt.archive.button'), @@ -48,7 +44,7 @@ const Archive: React.FC = ({ } const promptItemChooseChangeFile = { - id: 'delete', + id: 'changeFile', headline: t('prompt.archive.changeFile.headline'), text: t('prompt.archive.changeFile.text'), button1: t('openFile'), @@ -57,6 +53,12 @@ const Archive: React.FC = ({ onButton2: handleCreateDoneFile, } + useEffect(() => { + if(triggerArchiving) { + setPromptItem(null) + } + }, [triggerArchiving]); + useEffect(() => { ipcRenderer.on('triggerArchiving', handleTriggerArchiving); return () => { diff --git a/src/renderer/ContextMenu.tsx b/src/renderer/ContextMenu.tsx index ddd12b25..9996a8ea 100644 --- a/src/renderer/ContextMenu.tsx +++ b/src/renderer/ContextMenu.tsx @@ -1,65 +1,35 @@ -import React, { useState, useEffect, memo } from 'react'; -import { Menu, MenuItem, Button, Tooltip } from '@mui/material'; -import FileOpenIcon from '@mui/icons-material/FileOpen'; +import React, { memo } from 'react'; +import { Menu, MenuItem } from '@mui/material'; interface Props { - contextMenuItems: ContextMenuItem[]; - setContextMenuItems: React.Dispatch>; - setPromptItem; + contextMenu: ContextMenu; + setContextMenu: React.Dispatch>; + setPromptItem: React.Dispatch>; } -const { ipcRenderer } = window.api; - const ContextMenu: React.FC = memo(({ - contextMenuItems, - setContextMenuItems, + contextMenu, + setContextMenu, setPromptItem, }) => { - //const [contextMenuPosition, setContextMenuPosition] = useState(null); - // const handleContextMenuClick = (item: ContextMenuItem) => { - // const { id, todoObject, pathToReveal} = item; - // switch (id) { - // case 'delete': - // setPromptItem(item); - // break; - // case 'copy': - // setContextMenuItems(null); - // ipcRenderer.send('saveToClipboard', todoObject?.string); - // break; - // case 'removeFile': - // setPromptItem(item); - // break; - // case 'revealInFileManager': - // setContextMenuItems(null); - // ipcRenderer.send('revealInFileManager', pathToReveal); - // break; - // default: - // setContextMenuItems(null); - // } - // }; - - // const handleChangeDoneFilePath = () => { - // setShowPromptDoneFile(true); - // }; - - const onClick = (contextMenuItem) => { + const onClick = (contextMenuItem: ContextMenuItem) => { if(contextMenuItem.promptItem) { setPromptItem(contextMenuItem.promptItem); } else if(contextMenuItem.function) { contextMenuItem.function(); - setContextMenuItems(null); + setContextMenu(null); } }; return ( <> setContextMenuItems(null)} + open={Boolean(contextMenu)} + onClose={() => setContextMenu(null)} anchorReference="anchorPosition" - anchorPosition={{ top: contextMenuItems.event.clientY, left: contextMenuItems.event.clientX }} + anchorPosition={{ top: contextMenu.event.clientY, left: contextMenu.event.clientX }} > - {contextMenuItems && contextMenuItems.items.map((contextMenuItem) => ( + {contextMenu && contextMenu.items.map((contextMenuItem: ContextMenuItem) => ( onClick(contextMenuItem)}> {contextMenuItem.label} diff --git a/src/renderer/DataGrid/DatePickerInline.tsx b/src/renderer/DataGrid/DatePickerInline.tsx index 89b205fd..f56dd1d7 100644 --- a/src/renderer/DataGrid/DatePickerInline.tsx +++ b/src/renderer/DataGrid/DatePickerInline.tsx @@ -11,7 +11,7 @@ const { ipcRenderer } = window.api; interface Props { type: string; todoObject: TodoObject; - date: Date; + date: string | null; filters: Filters; settings: Settings; } @@ -30,7 +30,7 @@ const DatePickerInline: React.FC = ({ if(!date || !dayjs(date).isValid() || !todoObject.id) return; const validDate = dayjs(date).format('YYYY-MM-DD'); - const string = todoObject.string.replaceAll(/[\x10\r\n]/g, ` ${String.fromCharCode(16)} `); + const string: string = todoObject?.string?.replaceAll(/[\x10\r\n]/g, ` ${String.fromCharCode(16)} `) || ''; const JsTodoTxtObject = new Item(string); JsTodoTxtObject.setExtension(type, validDate); @@ -42,10 +42,10 @@ const DatePickerInline: React.FC = ({ setOpen(false); }; - const DatePickerInline = ({ date, ...props }) => { + const DatePickerInline = ({ ...props }) => { const parsedDate = dayjs(date); - const ButtonField = ({ date, ...props }) => { + const ButtonField = ({ ...props }) => { const { setOpen, disabled, InputProps: { ref } = {}, inputProps: { 'aria-label': ariaLabel } = {} } = props; const mustNotify = (type === 'due') ? !todoObject?.notify : true; diff --git a/src/renderer/DataGrid/Elements.tsx b/src/renderer/DataGrid/Elements.tsx index 711d0281..0305664b 100644 --- a/src/renderer/DataGrid/Elements.tsx +++ b/src/renderer/DataGrid/Elements.tsx @@ -5,7 +5,7 @@ import DatePickerInline from './DatePickerInline'; interface Props { todoObject: TodoObject; - filters: Filters; + filters: Filters | null; handleButtonClick: (key: string, value: string) => void; settings: Settings; } @@ -59,7 +59,7 @@ const Elements: React.FC = memo(({ {value} ), - hidden: () => null, + hidden: () => null as React.ReactNode, }; const matches = () => { @@ -73,7 +73,8 @@ const Elements: React.FC = memo(({ { pattern: /rec:([^ ]+)/, type: 'rec', key: 'rec:' }, ]; - let body = todoObject.body?.replaceAll(String.fromCharCode(16), ' ') ?? ''; + //let body = todoObject.body?.replaceAll(String.fromCharCode(16), ' ') ?? ''; + let body = todoObject.body; let substrings = []; let index = 0; diff --git a/src/renderer/DataGrid/Grid.tsx b/src/renderer/DataGrid/Grid.tsx index f2d1f413..a9d77db1 100644 --- a/src/renderer/DataGrid/Grid.tsx +++ b/src/renderer/DataGrid/Grid.tsx @@ -1,4 +1,4 @@ -import React, { useState, KeyboardEvent, memo } from 'react'; +import React, { KeyboardEvent, memo } from 'react'; import { List } from '@mui/material'; import Row from './Row'; import './Grid.scss'; @@ -7,14 +7,15 @@ interface TodoDataGridProps { todoObjects: TodoObject[] | null; attributes: Attributes | null; filters: Filters | null; - setDialogOpen: (open: boolean) => void; - contextMenuPosition: { top: number; left: number } | null; - setContextMenuPosition: (position: { top: number; left: number } | null) => void; - contextMenuItems: ContextMenuItem[]; - setContextMenuItems: (items: any[]) => void; - setTodoObject: (todoObject: TodoObject) => void; - setPromptItem: PromptItem; + setDialogOpen: React.Dispatch>; + setContextMenu: React.Dispatch>; + setTodoObject: React.Dispatch>; + setPromptItem: React.Dispatch>; settings: Settings; + visibleRowCount: number; + setVisibleRowCount: React.Dispatch>; + loadMoreRows: boolean; + setLoadMoreRows: React.Dispatch>; } const TodoDataGrid: React.FC = memo(({ @@ -22,16 +23,18 @@ const TodoDataGrid: React.FC = memo(({ attributes, filters, setDialogOpen, - contextMenuPosition, - setContextMenuPosition, - contextMenuItems, - setContextMenuItems, + setContextMenu, setTodoObject, setPromptItem, settings, + visibleRowCount, + setVisibleRowCount, + loadMoreRows, + setLoadMoreRows, }) => { - const [visibleRowCount, setVisibleRowCount] = useState(50); - const [loadMoreRows, setLoadMoreRows] = useState(true); + + const list = document.getElementById('dataGrid'); + const totalRowCount = todoObjects?.length; const handleKeyUp = (event: KeyboardEvent) => { if(event.key === 'ArrowDown') { @@ -44,9 +47,9 @@ const TodoDataGrid: React.FC = memo(({ } } else if(event.key === 'ArrowUp') { const listItems = document.querySelectorAll('li:not(.group)'); - const currentIndex = Array.from(listItems).indexOf(document.activeElement); - const prevIndex = currentIndex - 1; - const prevElement = listItems[prevIndex]; + const currentIndex: number = Array.from(listItems).indexOf(document.activeElement); + const prevIndex: number = currentIndex - 1; + const prevElement: Element = listItems[prevIndex]; if(prevElement) { prevElement.focus(); } @@ -64,15 +67,13 @@ const TodoDataGrid: React.FC = memo(({ } }; - const handleScroll = () => { - const list = document.getElementById('dataGrid'); - if(list && loadMoreRows) { + const handleScroll = () => { + if(list && loadMoreRows) { const scrollPos = list.scrollTop; const totalHeight = list.scrollHeight; const clientHeight = list.clientHeight; if(totalHeight - scrollPos <= clientHeight * 3) { - const remainingRows: TodoObject[] | null = todoObjects?.slice(visibleRowCount, visibleRowCount + 30); - if(remainingRows?.length === 0) { + if(visibleRowCount >= totalRowCount) { setLoadMoreRows(false); } else { setVisibleRowCount((prevVisibleRowCount) => prevVisibleRowCount + 30); @@ -81,24 +82,19 @@ const TodoDataGrid: React.FC = memo(({ } }; - if(!todoObjects || Object.keys(todoObjects).length === 0) return null; - - const rows = todoObjects.slice(0, visibleRowCount); + const visibleTodoObjects = todoObjects?.slice(0, visibleRowCount); return ( - {rows.map((row, index) => ( + {visibleTodoObjects?.map((row, index) => ( diff --git a/src/renderer/DataGrid/Group.tsx b/src/renderer/DataGrid/Group.tsx index ce87baa9..b0ec314e 100644 --- a/src/renderer/DataGrid/Group.tsx +++ b/src/renderer/DataGrid/Group.tsx @@ -4,8 +4,8 @@ import { ListItem, Box, Button, Divider } from '@mui/material'; interface Props { value: string; group: string; - filters: Filters; - onClick: (key: string, value: string) => void; + filters: Filters | null; + onClick: Function; } const Group: React.FC = memo(({ diff --git a/src/renderer/DataGrid/Row.tsx b/src/renderer/DataGrid/Row.tsx index 645d498f..c85e085a 100644 --- a/src/renderer/DataGrid/Row.tsx +++ b/src/renderer/DataGrid/Row.tsx @@ -13,64 +13,64 @@ import { i18n } from '../Settings/LanguageSelector'; const { ipcRenderer } = window.api; interface Props extends WithTranslation { - row: TodoObject; - filters: Filters; + todoObject: TodoObject; + filters: Filters | null; setDialogOpen: React.Dispatch>; - setTodoObject: React.Dispatch>; + setTodoObject: React.Dispatch>; + setContextMenu: React.Dispatch>; + setPromptItem: React.Dispatch>; settings: Settings, t: typeof i18n.t; } const Row: React.FC = memo(({ - row, + todoObject, filters, setDialogOpen, setTodoObject, - setContextMenuItems, + setContextMenu, + setPromptItem, settings, t, }) => { - const handleContextMenu = (event: React.MouseEvent) => { - - const confirmDelete = () => { - if(row) ipcRenderer.send('removeLineFromFile', row?.id); - }; + const handleConfirmDelete = () => { + if(todoObject) ipcRenderer.send('removeLineFromFile', todoObject?.id); + }; - const saveToClipboard = () => { - if(row) ipcRenderer.send('saveToClipboard', row?.string); - }; + const handleSaveToClipboard = () => { + if(todoObject) ipcRenderer.send('saveToClipboard', todoObject?.string); + }; - const contextMenuItems = [ - { - id: 'copy', - label: t('copy'), - function: saveToClipboard, - }, - { - id: 'delete', - label: t('delete'), - promptItem: { + const handleContextMenu = (event: React.MouseEvent) => { + setContextMenu({ + event: event, + items: [ + { + id: 'copy', + label: t('copy'), + function: handleSaveToClipboard, + }, + { id: 'delete', - headline: t('prompt.delete.headline'), - text: t('prompt.delete.text'), - button1: t('delete'), - onButton1: confirmDelete, + label: t('delete'), + promptItem: { + id: 'delete', + headline: t('prompt.delete.headline'), + text: t('prompt.delete.text'), + button1: t('delete'), + onButton1: handleConfirmDelete, + } } - } - ] - - setContextMenuItems({ - event: event, - items: contextMenuItems + ] }); }; const handleCheckboxChange = (event: React.ChangeEvent) => { - ipcRenderer.send('writeTodoToFile', row.id, row.string, event.target.checked, false); + ipcRenderer.send('writeTodoToFile', todoObject.id, todoObject.string, event.target.checked, false); }; - const handleRowClick = (event: React.KeyboardEvent | React.MouseEvent) => { + const handleRowClick = (event: KeyboardEvent | MouseEvent) => { const clickedElement = event.target as HTMLElement; if((event.type === 'keydown' && event.key === 'Enter') || event.type === 'click') { if( @@ -84,13 +84,21 @@ const Row: React.FC = memo(({ clickedElement.tagName === 'SPAN' || clickedElement.tagName === 'LI' ) { - if(row) { - setTodoObject(row); + if(todoObject) { + setTodoObject(todoObject); setDialogOpen(true); } } } else if((event.metaKey || event.ctrlKey) && (event.key === 'Delete' || event.key === 'Backspace')) { - setPromptItem(itemDelete); + setPromptItem({ + id: 'delete', + headline: t('prompt.delete.headline'), + text: t('prompt.delete.text'), + button1: t('delete'), + onButton1: handleConfirmDelete, + }); + } else if((event.metaKey || event.ctrlKey) && (event.key === 'c')) { + handleSaveToClipboard(); } }; @@ -98,11 +106,11 @@ const Row: React.FC = memo(({ handleFilterSelect(key, value, filters, false); }; - if(row.group) { + if(todoObject.group) { return ( @@ -113,28 +121,28 @@ const Row: React.FC = memo(({ <> handleRowClick(event)} onKeyDown={handleRowClick} onContextMenu={handleContextMenu} data-todotxt-attribute="priority" - data-todotxt-value={row.priority} + data-todotxt-value={todoObject.priority} > } checkedIcon={} tabIndex={0} - checked={row.complete} + checked={todoObject.complete} onChange={handleCheckboxChange} /> - {row.hidden && } + {todoObject.hidden && } = memo(({ filters, t, }) => { - const mustNotify = (items) => items.some((item) => item.notify); + const mustNotify = (todoObjects: TodoObject[]) => todoObjects.some((todoObject) => todoObject.notify); const [isCtrlKeyPressed, setIsCtrlKeyPressed] = useState(false); const [settings, setSettings] = useState({ accordionOpenState: store.get('accordionOpenState'), diff --git a/src/renderer/Drawer/Drawer.tsx b/src/renderer/Drawer/Drawer.tsx index 87c29ec0..f83008e4 100644 --- a/src/renderer/Drawer/Drawer.tsx +++ b/src/renderer/Drawer/Drawer.tsx @@ -17,7 +17,7 @@ interface Props extends WithTranslation { attributes: Attributes | null; filters: Filters | null; searchFieldRef: React.RefObject; - attributeMapping; + attributeMapping: TranslatedAttributes; t: typeof i18n.t; } @@ -93,7 +93,6 @@ const DrawerComponent: React.FC = memo(({ {settings.isDrawerOpen && activeTab === 'attributes' && ( = ({ key={settingName} control={ store.set(settingName, event.target.checked)} name={settingName} /> diff --git a/src/renderer/Drawer/Sorting/DraggableList.tsx b/src/renderer/Drawer/Sorting/DraggableList.tsx index b20ae06d..8b023262 100644 --- a/src/renderer/Drawer/Sorting/DraggableList.tsx +++ b/src/renderer/Drawer/Sorting/DraggableList.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import DraggableListItem from './DraggableListItem'; import { Box } from '@mui/material'; import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; diff --git a/src/renderer/Drawer/Sorting/DraggableListItem.tsx b/src/renderer/Drawer/Sorting/DraggableListItem.tsx index 825c1838..9f11afe7 100644 --- a/src/renderer/Drawer/Sorting/DraggableListItem.tsx +++ b/src/renderer/Drawer/Sorting/DraggableListItem.tsx @@ -21,7 +21,7 @@ const DraggableListItem: React.FC = ({ attributeMapping, }) => { const handleButtonClick = () => { - const updatedSorting = settings.sorting.map((sortingItem) => { + const updatedSorting = settings.sorting.map((sortingItem: Sorting) => { if(sortingItem.id === item.id) { return { ...sortingItem, invert: !item.invert }; } diff --git a/src/renderer/Drawer/Sorting/Sorting.tsx b/src/renderer/Drawer/Sorting/Sorting.tsx index 69579835..f38d0f43 100644 --- a/src/renderer/Drawer/Sorting/Sorting.tsx +++ b/src/renderer/Drawer/Sorting/Sorting.tsx @@ -9,7 +9,7 @@ const { store } = window.api; interface Props extends WithTranslation { settings: Settings; - attributeMapping; + attributeMapping: TranslatedAttributes; t: typeof i18n.t; } @@ -33,7 +33,7 @@ const DrawerSorting: React.FC = ({ key={settingName} control={ store.set(settingName, event.target.checked)} name={settingName} /> diff --git a/src/renderer/Header/FileTabs.tsx b/src/renderer/Header/FileTabs.tsx index 46728cc4..28789e69 100644 --- a/src/renderer/Header/FileTabs.tsx +++ b/src/renderer/Header/FileTabs.tsx @@ -9,56 +9,72 @@ const { ipcRenderer } = window.api; interface Props extends WithTranslation { settings: Settings; - setContextMenuPosition: (position: { top: number; left: number }) => void; - setContextMenuItems: (items: any[]) => void; + setContextMenu: React.Dispatch>; t: typeof i18n.t; } const FileTabs: React.FC = memo(({ settings, - setContextMenuPosition, - setContextMenuItems, + setContextMenu, t, }) => { const handleContextMenu = (event: React.MouseEvent, index: number) => { - const revealFile = (index) => { - const file = settings.files[index]; - if(file) ipcRenderer.send('revealInFileManager', file.todoFilePath); + const fileObject = settings.files[index]; + + const handleOpenDoneFile = () => { + ipcRenderer.send('openFile', true); }; - const removeFile = (index) => { - if(index >= 0) ipcRenderer.send('removeFile', index); + const handleCreateDoneFile = () => { + ipcRenderer.send('createFile', true); }; - const contextMenuItems = [ - { - id: 'changeLocation', - label: t('fileTabs.changeLocation'), - index: index, - doneFilePath: settings.files[index].doneFilePath, - }, - { - id: 'revealFile', - label: t('fileTabs.revealFile'), - function: () => revealFile(index), - }, - { - id: 'removeFile', - label: t('fileTabs.removeFileLabel'), - promptItem: { - headline: t('fileTabs.removeFileHeadline'), - text: t('fileTabs.removeFileText'), - button1: t('fileTabs.removeFileLabel'), - onButton1: () => removeFile(index), - }, - }, - ]; + const handleRevealFile = (path: string | null) => { + if(path) ipcRenderer.send('revealInFileManager', path); + }; + + const handleRemoveFile = (index: number) => { + if(index >= 0) ipcRenderer.send('removeFile', index); + }; - setContextMenuItems({ + setContextMenu({ event: event, - index: index, - items: contextMenuItems, + items: [ + { + id: 'changeLocation', + label: t('fileTabs.changeLocation'), + promptItem: { + id: 'changeFile', + headline: t('prompt.archive.changeFile.headline'), + text: t('prompt.archive.changeFile.text'), + button1: t('openFile'), + onButton1: handleOpenDoneFile, + button2: t('createFile'), + onButton2: handleCreateDoneFile, + }, + }, + fileObject.todoFilePath && { + id: 'revealFile', + label: t('fileTabs.revealTodoFile'), + function: () => handleRevealFile(fileObject.todoFilePath), + }, + fileObject.doneFilePath && { + id: 'revealArchivingFile', + label: t('fileTabs.revealArchivingFile'), + function: () => handleRevealFile(fileObject.doneFilePath), + }, + { + id: 'removeFile', + label: t('fileTabs.removeFileLabel'), + promptItem: { + headline: t('fileTabs.removeFileHeadline'), + text: t('fileTabs.removeFileText'), + button1: t('fileTabs.removeFileLabel'), + onButton1: () => handleRemoveFile(index), + }, + }, + ].filter(Boolean) }); }; diff --git a/src/renderer/Header/Search.tsx b/src/renderer/Header/Search.tsx index 7915cf48..d3b6b31f 100644 --- a/src/renderer/Header/Search.tsx +++ b/src/renderer/Header/Search.tsx @@ -8,10 +8,10 @@ import './Search.scss'; const { ipcRenderer, store } = window.api; interface Props extends WithTranslation { - headers: HeadersObject; + headers: HeadersObject | null; settings: Settings, searchString: string; - setSearchString: (searchString: string) => void; + setSearchString: React.Dispatch>; searchFieldRef: React.RefObject; t: typeof i18n.t; } @@ -80,7 +80,7 @@ const Search: React.FC = memo(({ >; + setAttributes: React.Dispatch>; + setFilters: React.Dispatch>; + setTodoObjects: React.Dispatch>; + setTodoObject: React.Dispatch>; + setAttributeFields: React.Dispatch>; + setSnackBarSeverity: React.Dispatch>; + setSnackBarContent: React.Dispatch>; + setSettings: React.Dispatch>; + setSplashScreen: React.Dispatch>; + setIsSettingsOpen: React.Dispatch>; } const IpcComponent: React.FC = ({ @@ -27,7 +27,6 @@ const IpcComponent: React.FC = ({ setSnackBarSeverity, setSnackBarContent, setSettings, - setTriggerArchiving, setSplashScreen, setIsSettingsOpen, }) => { @@ -65,19 +64,19 @@ const IpcComponent: React.FC = ({ useEffect(() => { ipcRenderer.on('requestData', handleRequestedData); ipcRenderer.on('updateAttributeFields', handleUpdateAttributeFields); - ipcRenderer.on('updateTodoObject', (todoObject) => setTodoObject(todoObject)); + ipcRenderer.on('updateTodoObject', (todoObject: TodoObject) => setTodoObject(todoObject)); ipcRenderer.on('responseFromMainProcess', handleResponse); - ipcRenderer.on('settingsChanged', (settings) => setSettings(settings)); - ipcRenderer.on('isSettingsOpen', (isSettingsOpen) => setIsSettingsOpen(isSettingsOpen)); + ipcRenderer.on('settingsChanged', (settings: Settings) => setSettings(settings)); + ipcRenderer.on('isSettingsOpen', (isSettingsOpen: boolean) => setIsSettingsOpen(isSettingsOpen)); window.addEventListener('drop', handleDrop); window.addEventListener('dragover', handleDragOver); return () => { ipcRenderer.off('requestData', handleRequestedData); ipcRenderer.off('updateAttributeFields', handleUpdateAttributeFields); - ipcRenderer.off('updateTodoObject', (todoObject) => setTodoObject(todoObject)); + ipcRenderer.off('updateTodoObject', (todoObject: TodoObject) => setTodoObject(todoObject)); ipcRenderer.off('responseFromMainProcess', handleResponse); - ipcRenderer.off('settingsChanged', (settings) => setSettings(settings)); - ipcRenderer.off('isSettingsOpen', (isSettingsOpen) => setIsSettingsOpen(isSettingsOpen)); + ipcRenderer.off('settingsChanged', (settings: Settings) => setSettings(settings)); + ipcRenderer.off('isSettingsOpen', (isSettingsOpen: boolean) => setIsSettingsOpen(isSettingsOpen)); window.removeEventListener('drop', handleDrop); window.removeEventListener('dragover', handleDragOver); }; diff --git a/src/renderer/Navigation.tsx b/src/renderer/Navigation.tsx index 4430b0e0..eb2f95a2 100644 --- a/src/renderer/Navigation.tsx +++ b/src/renderer/Navigation.tsx @@ -15,7 +15,6 @@ interface Props { setIsSettingsOpen: React.Dispatch>; setDialogOpen: React.Dispatch>; settings: Settings; - setTriggerArchiving: React.Dispatch>; headers: HeadersObject | null; setTodoObject: React.Dispatch>; } @@ -24,7 +23,6 @@ const NavigationComponent: React.FC = memo(({ setIsSettingsOpen, setDialogOpen, settings, - setTriggerArchiving, headers, setTodoObject, }) => { @@ -60,7 +58,7 @@ const NavigationComponent: React.FC = memo(({ {headers && headers.completedTodoObjects > 0 && ( <> - diff --git a/src/renderer/Prompt.tsx b/src/renderer/Prompt.tsx index b5075f15..77ded334 100644 --- a/src/renderer/Prompt.tsx +++ b/src/renderer/Prompt.tsx @@ -3,14 +3,12 @@ import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/ import { withTranslation, WithTranslation } from 'react-i18next'; import { i18n } from './Settings/LanguageSelector'; -const { ipcRenderer } = window.api; - interface Props extends WithTranslation { open: boolean; onClose: () => void; - promptItem: PromptItem; - setPromptItem: React.Dispatch>; - setContextMenuItems: React.Dispatch>; + promptItem: PromptItem | null; + setPromptItem: React.Dispatch>; + setContextMenu: React.Dispatch>; t: typeof i18n.t; } @@ -19,18 +17,18 @@ const Prompt: React.FC = ({ onClose, promptItem, setPromptItem, - setContextMenuItems, + setContextMenu, t }) => { - const onClick = (functionToExecute) => { + const onClick = (functionToExecute: Function) => { functionToExecute(); setPromptItem(null); }; useEffect(() => { if(promptItem) { - setContextMenuItems(null); + setContextMenu(null); } }, [promptItem]); @@ -43,7 +41,7 @@ const Prompt: React.FC = ({ {promptItem?.button1 && } {promptItem?.button2 && } - + ); diff --git a/src/renderer/Settings/LanguageSelector.tsx b/src/renderer/Settings/LanguageSelector.tsx index 923f7a6c..4931eccb 100644 --- a/src/renderer/Settings/LanguageSelector.tsx +++ b/src/renderer/Settings/LanguageSelector.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from 'react'; -import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'; +import React, { useEffect } from 'react'; +import { FormControl, InputLabel, MenuItem, Select, SelectChangeEvent } from '@mui/material'; import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import de from '../../locales/de.json'; @@ -38,7 +38,7 @@ const options: i18n.InitOptions = { resources: { de: { translation: de }, en: { translation: en }, - en_GB: { translation: en_GB }, + 'en-gb': { translation: en_GB }, it: { translation: it }, es: { translation: es }, fr: { translation: fr }, @@ -54,7 +54,7 @@ const options: i18n.InitOptions = { hi: { translation: hi }, }, fallbackLng: 'en', - supportedLngs: ['de', 'en', 'en-GB', 'it', 'es', 'fr', 'zh', 'pt', 'jp', 'tr', 'hu', 'cs', 'pl', 'ru', 'ko', 'hi'], + supportedLngs: ['de', 'en', 'en-gb', 'it', 'es', 'fr', 'zh', 'pt', 'jp', 'tr', 'hu', 'cs', 'pl', 'ru', 'ko', 'hi'], interpolation: { escapeValue: false, }, @@ -66,7 +66,7 @@ i18n .init(options) .then(() => { if(!store.get('language')) { - store.set('language', navigator.language); + store.set('language', navigator.language.toLowerCase()); } // TODO: check if this is still working i18n.on('missingKey', (key: string) => { @@ -80,7 +80,7 @@ i18n const friendlyLanguageName: Record = { de: 'Deutsch', en: 'English', - 'en-GB': 'English (UK)', + 'en-gb': 'English (UK)', it: 'Italiano', es: 'Español', fr: 'Français', @@ -98,16 +98,14 @@ const friendlyLanguageName: Record = { interface Props { settings: Settings; - attributeMapping; } const LanguageSelector: React.FC = ({ settings, - attributeMapping, }) => { const supportedLanguages: false | readonly string[] | undefined = i18n.options.supportedLngs; - const changeLanguage = (event: React.ChangeEvent<{ value: string }>) => { + const changeLanguage = (event: SelectChangeEvent) => { const language = event.target.value; store.set('language', language); }; @@ -116,7 +114,6 @@ const LanguageSelector: React.FC = ({ i18n.changeLanguage(settings.language) .then(() => { store.set('language', settings.language); - //setAttributeMapping(attributeMapping(i18n.t)); }) .catch((error) => { console.error('Error loading translation:', error); @@ -132,7 +129,7 @@ const LanguageSelector: React.FC = ({ label='Language' value={settings.language || navigator.language} name='language' - onChange={(event) => changeLanguage(event)} + onChange={(event: SelectChangeEvent) => changeLanguage(event)} > {Array.isArray(supportedLanguages) ? ( supportedLanguages.map((languageCode: string) => ( diff --git a/src/renderer/Settings/Settings.tsx b/src/renderer/Settings/Settings.tsx index e9c12543..f361ca6a 100644 --- a/src/renderer/Settings/Settings.tsx +++ b/src/renderer/Settings/Settings.tsx @@ -10,11 +10,11 @@ interface Props extends WithTranslation { isOpen: boolean; onClose: () => void; settings: Settings; - attributeMapping; + attributeMapping: TranslatedAttributes; t: typeof i18n.t; } -const visibleSettings = { +const visibleSettings: VisibleSettings = { appendCreationDate: { style: 'toggle', }, @@ -60,7 +60,6 @@ const Settings: React.FC = memo(({ isOpen, onClose, settings, - attributeMapping, t, }) => { @@ -79,7 +78,7 @@ const Settings: React.FC = memo(({ key={settingName} control={ store.set(settingName, event.target.checked)} name={settingName} /> @@ -93,10 +92,10 @@ const Settings: React.FC = memo(({