diff --git a/assets/fonts/FreeSans.otf b/assets/fonts/FreeSans.otf deleted file mode 100755 index 0b04a5d5..00000000 Binary files a/assets/fonts/FreeSans.otf and /dev/null differ diff --git a/assets/fonts/FreeSans.ttf b/assets/fonts/FreeSans.ttf deleted file mode 100755 index 9db95853..00000000 Binary files a/assets/fonts/FreeSans.ttf and /dev/null differ diff --git a/assets/fonts/FreeSansBold.otf b/assets/fonts/FreeSansBold.otf deleted file mode 100755 index 17865b10..00000000 Binary files a/assets/fonts/FreeSansBold.otf and /dev/null differ diff --git a/assets/fonts/FreeSansBold.ttf b/assets/fonts/FreeSansBold.ttf deleted file mode 100755 index 63644e74..00000000 Binary files a/assets/fonts/FreeSansBold.ttf and /dev/null differ diff --git a/flatpak/com.github.ransome1.sleek.appdata.xml b/flatpak/com.github.ransome1.sleek.appdata.xml index d8b838e0..5332067b 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 07527a75..ef01419e 100755 --- a/flatpak/com.github.ransome1.sleek.desktop +++ b/flatpak/com.github.ransome1.sleek.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=2.0.7-rc.3 +Version=2.0.7-rc.4 Name=sleek Exec=sleek Type=Application diff --git a/package.json b/package.json index 4c8a2a98..86d8115b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sleek", - "version": "2.0.7-rc.3", + "version": "2.0.7-rc.4", "main": "./src/main/main.tsx", "scripts": { "build": "concurrently \"yarn run peggy\" \"yarn run build:main\" \"yarn run build:renderer\"", diff --git a/release/app/package.json b/release/app/package.json index 8dea3b3d..3e8479ee 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -1,6 +1,6 @@ { "name": "sleek", - "version": "2.0.7-rc.3", + "version": "2.0.7-rc.4", "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 6647aa0d..8fbe16d9 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: sleek base: core20 -version: "2.0.7-rc.3" +version: "2.0.7-rc.4" 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/main/config.tsx b/src/main/config.tsx index 2da9145b..59db0a65 100644 --- a/src/main/config.tsx +++ b/src/main/config.tsx @@ -118,13 +118,13 @@ filter.onDidChange('attributes', async () => { } }); -filter.onDidChange('search', async () => { - try { - await processDataRequest(searchString); - } catch(error: any) { - console.error(error); - } -}); +// filter.onDidChange('search', async () => { +// try { +// await processDataRequest(searchString); +// } catch(error: any) { +// console.error(error); +// } +// }); config.onDidAnyChange(async(settings) => { try { diff --git a/src/main/modules/Notifications.tsx b/src/main/modules/Notifications.tsx index bbf68158..50472f2b 100644 --- a/src/main/modules/Notifications.tsx +++ b/src/main/modules/Notifications.tsx @@ -30,6 +30,20 @@ function createSpeakingDifference(dueDate: Dayjs) { return 'Due'; } +function isNotificationSuppressed(searchFilters, body) { + let suppressNotification = false; + for (const searchFilter of searchFilters) { + if (searchFilter.label && searchFilter.suppress) { + const match = checkForSearchMatches(body, searchFilter.label); + if (match) { + suppressNotification = true; + break; + } + } + } + return suppressNotification; +} + function handleNotification(due: string | null, body: string, badge: Badge) { const notificationAllowed = config.get('notificationsAllowed'); @@ -40,35 +54,18 @@ function handleNotification(due: string | null, body: string, badge: Badge) { const notificationThreshold: number = config.get('notificationThreshold'); const hash = todayString + crypto.createHash('sha256').update(body).digest('hex'); const searchFilters: SearchFilter[] = filter.get('search') || []; - - let searchFilterMatch; - for (const searchFilter of searchFilters) { - if (searchFilter.notify && searchFilter.label) { - const match = checkForSearchMatches(body, searchFilter.label); - if (match) { - searchFilterMatch = { - label: searchFilter.label, - }; - break; - } - } - } + + if(isNotificationSuppressed(searchFilters, body)) return; - let title: string; if(dueDate.isToday() || dueDate.isBetween(today, today.add(notificationThreshold, 'day'))) { - title = createSpeakingDifference(dueDate) - } else if(searchFilterMatch && searchFilterMatch.label) { - title = `Matched "${searchFilterMatch.label}"`; - } else { - return; - } - - badge.count += 1; - const notifiedTodoObjects = new Set(notifiedTodoObjectsStorage.get('notifiedTodoObjects', [])); - if(!notifiedTodoObjects.has(hash)) { - sendNotification(title, body); - notifiedTodoObjects.add(hash); - notifiedTodoObjectsStorage.set('notifiedTodoObjects', Array.from(notifiedTodoObjects)); + badge.count += 1; + const title = createSpeakingDifference(dueDate); + const notifiedTodoObjects = new Set(notifiedTodoObjectsStorage.get('notifiedTodoObjects', [])); + if(!notifiedTodoObjects.has(hash)) { + sendNotification(title, body); + notifiedTodoObjects.add(hash); + notifiedTodoObjectsStorage.set('notifiedTodoObjects', Array.from(notifiedTodoObjects)); + } } } } diff --git a/src/renderer/App.scss b/src/renderer/App.scss index ab896018..c1aa2dba 100644 --- a/src/renderer/App.scss +++ b/src/renderer/App.scss @@ -1,26 +1,20 @@ @import "Variables.scss"; @import "Coloring.scss"; -@font-face { - font-family: "FreeSans"; - src: url("../../assets/fonts/FreeSans.otf") format("opentype"); - src: url("../../assets/fonts/FreeSans.ttf") format("truetype"); -} -@font-face { - font-family: "FreeSansBold"; - src: url("../../assets/fonts/FreeSansBold.otf") format("opentype"); - src: url("../../assets/fonts/FreeSansBold.ttf") format("truetype"); -} - body { overflow: hidden; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; + font-family: "Helvetica, Arial, Sans-Serif"; code { + font-size: 0.9em; background: $lighter-grey; - color: $mid-grey; + color: $dark-grey; + border-radius: 0.25em; + padding: 0.25em; + white-space: nowrap; } #root { .flexContainer { @@ -62,7 +56,7 @@ body { } h1, h2, h3, h4, h5 { - font-family: $font-family-bold; + font-weight: bold; } *::-webkit-scrollbar { diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index c8e84e79..ef4fb8e6 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -134,6 +134,7 @@ const App = () => { setSearchString={setSearchString} settings={settings} searchFieldRef={searchFieldRef} + setPromptItem={setPromptItem} /> = memo(({ const handleKeyDown = useCallback( (event: KeyboardEvent) => { const isSearchFocused = document.activeElement === searchFieldRef.current; - if ((event.metaKey || event.ctrlKey) && event.key === 'f' && settings.isSearchOpen && !isSearchFocused) { + if ((event.metaKey || event.ctrlKey) && !event.shiftKey && event.key === 'f' && settings.isSearchOpen && !isSearchFocused) { event.preventDefault(); searchFieldRef.current?.focus(); } diff --git a/src/renderer/Header/Search.scss b/src/renderer/Header/Search.scss index 8396ce29..8794654f 100644 --- a/src/renderer/Header/Search.scss +++ b/src/renderer/Header/Search.scss @@ -4,11 +4,6 @@ display: flex; position: relative; margin-bottom: 0.25em; - .MuiButton-root { - position: absolute; - top: 0.5em; - right: 3.5em; - } .MuiAutocomplete-root { flex-direction: row; flex: 1; diff --git a/src/renderer/Header/Search.tsx b/src/renderer/Header/Search.tsx index ac4a1691..9273bb16 100644 --- a/src/renderer/Header/Search.tsx +++ b/src/renderer/Header/Search.tsx @@ -1,8 +1,14 @@ import React, { useState, useEffect, useCallback, memo, MouseEvent } from 'react'; import TextField from '@mui/material/TextField'; import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; +import InputAdornment from '@mui/material/InputAdornment'; +import IconButton from '@mui/material/IconButton'; +import ClearIcon from '@mui/icons-material/Clear'; +import AddIcon from '@mui/icons-material/Add'; import Button from '@mui/material/Button'; import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import ArrowDropUpIcon from '@mui/icons-material/ArrowDropUp'; import AddCircleIcon from '@mui/icons-material/AddCircle'; import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive'; import NotificationsOffIcon from '@mui/icons-material/NotificationsOff'; @@ -18,6 +24,7 @@ interface SearchProps extends WithTranslation { searchString: string; setSearchString: React.Dispatch>; searchFieldRef: React.RefObject; + setPromptItem: React.Dispatch>; t: typeof i18n.t; } @@ -27,33 +34,45 @@ const Search: React.FC = memo(({ searchString, setSearchString, searchFieldRef, + setPromptItem, t, }) => { const [searchFilters, setSearchFilters] = useState(store.getFilters('search')); + const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false); - const toggleNotify = (option) => { + const toggleSuppress = (option) => { const updatedFilters = searchFilters.map(searchFilter => { if (searchFilter.label === option.label) { - return { ...searchFilter, notify: !option.notify }; // Set notify to true when removing + return { ...searchFilter, suppress: !option.suppress }; } return searchFilter; }); setSearchFilters(updatedFilters); } - const handleRemoveFilter = useCallback((event: MouseEvent, option: SearchFilter) => { - event.stopPropagation(); - event.preventDefault(); + const handleDeleteFilterConfirm = (option) => { const updatedFilters = searchFilters.filter(searchFilter => searchFilter.label !== option.label); setSearchFilters(updatedFilters); + }; + + const handleDeleteFilter = useCallback((event: MouseEvent, option: SearchFilter) => { + event.stopPropagation(); + event.preventDefault(); + setPromptItem({ + id: 'confirmSearchFilterDelete', + headline: 'Delete search filter', + text: `This will delete search filter ${option.label}`, + button1: 'Delete', + onButton1: () => handleDeleteFilterConfirm(option), + }); }, [searchFilters]); const handleAddNewFilter = useCallback((event: React.SyntheticEvent, value: string) => { event.stopPropagation(); event.preventDefault(); const updatedFilters = [ - { label: value, notify: false }, + { label: value, suppress: false }, ...searchFilters.filter(searchFilter => searchFilter.label !== value) ]; setSearchFilters(updatedFilters); @@ -65,18 +84,6 @@ const Search: React.FC = memo(({ } }, [searchString]); - const handleKeyDown = useCallback((event: KeyboardEvent) => { - const isSearchFocused = document.activeElement === searchFieldRef.current; - if (searchString && isSearchFocused && event.key === 'Escape') { - setSearchString(''); - } else if (!searchString && isSearchFocused && event.key === 'Escape') { - const isSearchOpen = !settings.isSearchOpen; - store.setConfig('isSearchOpen', isSearchOpen); - } else if (isSearchFocused && searchString && (event.metaKey || event.ctrlKey) && event.key === 'Enter') { - handleAddTodo(); - } - }, [searchFieldRef, searchString, settings.isSearchOpen]); - useEffect(() => { const handleSearch = () => { ipcRenderer.send('requestData', searchString); @@ -99,9 +106,34 @@ const Search: React.FC = memo(({ } }, [settings.isSearchOpen, searchFieldRef]); + const handleKeyDown = useCallback((event: KeyboardEvent) => { + const isSearchFocused = document.activeElement === searchFieldRef.current; + if(!isAutocompleteOpen && isSearchFocused && event.key === 'ArrowDown') { + setIsAutocompleteOpen(true); + } else if (searchString && isSearchFocused && event.key === 'Escape') { + setSearchString(''); + } else if (!searchString && isSearchFocused && event.key === 'Escape') { + const isSearchOpen = !settings.isSearchOpen; + store.setConfig('isSearchOpen', isSearchOpen); + } else if (isSearchFocused && searchString && (event.metaKey || event.ctrlKey) && event.key === 'Enter') { + handleAddTodo(); + } else if((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'f') { + searchFieldRef?.current?.focus(); + setIsAutocompleteOpen(!isAutocompleteOpen); + } + }, [searchFieldRef, searchString, settings.isSearchOpen, isAutocompleteOpen]); + + const handleKeyUp = useCallback((event: KeyboardEvent) => { + if(isAutocompleteOpen && (event.key === 'Escape' || event.key === 'Enter')) { + setIsAutocompleteOpen(false); + } + }, [isAutocompleteOpen]); + useEffect(() => { + document.addEventListener('keyup', handleKeyUp); document.addEventListener('keydown', handleKeyDown); return () => { + document.removeEventListener('keyup', handleKeyUp); document.removeEventListener('keydown', handleKeyDown); }; }, [handleKeyDown]); @@ -112,12 +144,17 @@ const Search: React.FC = memo(({ )} diff --git a/src/renderer/Navigation.scss b/src/renderer/Navigation.scss index a8c0f5b7..f8d60d4f 100644 --- a/src/renderer/Navigation.scss +++ b/src/renderer/Navigation.scss @@ -26,7 +26,7 @@ flex-direction: column; transition: left 0.3s ease; div { - font-family: $font-family-bold; + font-weight: bold; line-height: 5em; text-align: center; color: white; diff --git a/src/renderer/Prompt.tsx b/src/renderer/Prompt.tsx index fca398f0..eedf5830 100644 --- a/src/renderer/Prompt.tsx +++ b/src/renderer/Prompt.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, Fragment } from 'react'; import Dialog from '@mui/material/Dialog'; import DialogTitle from '@mui/material/DialogTitle'; import DialogContent from '@mui/material/DialogContent'; @@ -39,7 +39,7 @@ const Prompt: React.FC = ({ return ( {promptItem?.headline && {promptItem.headline}} - {promptItem?.text &&

{promptItem.text}

} + {promptItem?.text &&

}