mirror of
https://codeberg.org/Kankys/doxa.git
synced 2026-11-11 06:44:36 +01:00
rozšíření-completeTask
This commit is contained in:
1 parent
6230b7895d
commit
b447cc8de7
6 files changed
+178
-5
No files matched your search
@@ -33,6 +33,7 @@ The main interface displays:
|
|||||||
- **/: Search** - Search todos by title, description, or tags
|
- **/: Search** - Search todos by title, description, or tags
|
||||||
- **b: Tags** - Browse all tags
|
- **b: Tags** - Browse all tags
|
||||||
- **c: Filter** - Filter todos by category
|
- **c: Filter** - Filter todos by category
|
||||||
|
- **t: Toggle Complete** - Toggle completion status of the selected todo
|
||||||
- **l: EN/CS** - Toggle language between English and Czech
|
- **l: EN/CS** - Toggle language between English and Czech
|
||||||
- **q: Quit** - Exit the application
|
- **q: Quit** - Exit the application
|
||||||
|
|
||||||
@@ -138,6 +139,27 @@ The exported file contains:
|
|||||||
3. Press `y` to confirm deletion
|
3. Press `y` to confirm deletion
|
||||||
4. Press `n` or `Esc` to cancel
|
4. Press `n` or `Esc` to cancel
|
||||||
|
|
||||||
|
## Completed Todos
|
||||||
|
|
||||||
|
The application allows you to mark todos as completed, helping you track your progress.
|
||||||
|
|
||||||
|
### Marking Todos as Completed
|
||||||
|
|
||||||
|
1. Navigate to the todo you want to mark as completed/incomplete
|
||||||
|
2. Press `t` to toggle its completion status
|
||||||
|
3. Completed todos will:
|
||||||
|
- Be displayed with strikethrough and gray color
|
||||||
|
- Be automatically moved to the end of the list
|
||||||
|
- Be saved with the new status
|
||||||
|
|
||||||
|
### Completed Todos Features
|
||||||
|
|
||||||
|
- **Automatic Sorting**: Completed todos are always displayed last in the list
|
||||||
|
- **Visualization**: Completed todos are shown with strikethrough and gray color for easy identification
|
||||||
|
- **Persistent Storage**: Completion status is saved in the JSON file along with the todo
|
||||||
|
- **Filtering**: Completed todos are included in all filters and searches
|
||||||
|
- **Export**: When exporting a completed todo, its status is included in the exported data
|
||||||
|
|
||||||
## Keyboard Shortcuts Summary
|
## Keyboard Shortcuts Summary
|
||||||
|
|
||||||
### Navigation
|
### Navigation
|
||||||
@@ -154,6 +176,7 @@ The exported file contains:
|
|||||||
- `/`: Search
|
- `/`: Search
|
||||||
- `b`: Browse tags
|
- `b`: Browse tags
|
||||||
- `c`: Filter by category
|
- `c`: Filter by category
|
||||||
|
- `t`: Toggle todo completion status
|
||||||
- `l`: Toggle language
|
- `l`: Toggle language
|
||||||
- `+`: Increase priority (when adding/editing)
|
- `+`: Increase priority (when adding/editing)
|
||||||
- `Tab`: Switch between fields
|
- `Tab`: Switch between fields
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Hlavní rozhraní zobrazuje:
|
|||||||
- **/: Hledat** - Vyhledat úkoly podle názvu, popisu nebo štítků
|
- **/: Hledat** - Vyhledat úkoly podle názvu, popisu nebo štítků
|
||||||
- **b: Tagy** - Procházet všechny štítky
|
- **b: Tagy** - Procházet všechny štítky
|
||||||
- **c: Filtr** - Filtr úkolů podle kategorie
|
- **c: Filtr** - Filtr úkolů podle kategorie
|
||||||
|
- **t: Splněno** - Přepnout stav splnění vybraného úkolu
|
||||||
- **l: EN/CS** - Přepnout jazyk mezi angličtinou a češtinou
|
- **l: EN/CS** - Přepnout jazyk mezi angličtinou a češtinou
|
||||||
- **q: Konec** - Ukončit aplikaci
|
- **q: Konec** - Ukončit aplikaci
|
||||||
|
|
||||||
@@ -138,6 +139,27 @@ Exportovaný soubor obsahuje:
|
|||||||
3. Stiskněte `y` pro potvrzení smazání
|
3. Stiskněte `y` pro potvrzení smazání
|
||||||
4. Stiskněte `n` nebo `Esc` pro zrušení
|
4. Stiskněte `n` nebo `Esc` pro zrušení
|
||||||
|
|
||||||
|
## Splněné úkoly
|
||||||
|
|
||||||
|
Aplikace umožňuje označovat úkoly jako splněné, což vám pomůže sledovat váš pokrok.
|
||||||
|
|
||||||
|
### Označování úkolů jako splněných
|
||||||
|
|
||||||
|
1. Navigujte k úkolu, který chcete označit jako splněný/nesplněný
|
||||||
|
2. Stiskněte `t` pro přepnutí stavu
|
||||||
|
3. Splněné úkoly budou:
|
||||||
|
- Zobrazeny přeškrtlé a šedě
|
||||||
|
- Automaticky přesunuty na konec seznamu
|
||||||
|
- Uloženy s novým stavem
|
||||||
|
|
||||||
|
### Vlastnosti splněných úkolů
|
||||||
|
|
||||||
|
- **Automatické řazení**: Splněné úkoly jsou vždy zobrazeny jako poslední v seznamu
|
||||||
|
- **Vizualizace**: Splněné úkoly jsou přeškrtlé a zobrazují se šedou barvou pro snadnou identifikaci
|
||||||
|
- **Trvalé uložení**: Stav splnění je uložen v JSON souboru spolu s úkolem
|
||||||
|
- **Filtrace**: Splněné úkoly jsou zahrnuty ve všech filtrech a vyhledáváních
|
||||||
|
- **Export**: Při exportu splněného úkolu je jeho stav zahrnut v exportovaných datech
|
||||||
|
|
||||||
## Přehled klávesových zkratek
|
## Přehled klávesových zkratek
|
||||||
|
|
||||||
### Navigace
|
### Navigace
|
||||||
@@ -154,6 +176,7 @@ Exportovaný soubor obsahuje:
|
|||||||
- `/`: Vyhledat
|
- `/`: Vyhledat
|
||||||
- `b`: Procházet štítky
|
- `b`: Procházet štítky
|
||||||
- `c`: Filtr podle kategorie
|
- `c`: Filtr podle kategorie
|
||||||
|
- `t`: Přepnout stav splnění úkolu
|
||||||
- `l`: Přepnout jazyk
|
- `l`: Přepnout jazyk
|
||||||
- `+`: Zvýšit prioritu (při přidávání/úpravě)
|
- `+`: Zvýšit prioritu (při přidávání/úpravě)
|
||||||
- `Tab`: Přepínat mezi poli
|
- `Tab`: Přepínat mezi poli
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
- **Persistent Storage**: Todos are saved to `~/.doxa-todo/doxa_todo.json`
|
- **Persistent Storage**: Todos are saved to `~/.doxa-todo/doxa_todo.json`
|
||||||
- **Search & Filter**: Search by text, browse by tags, filter by category
|
- **Search & Filter**: Search by text, browse by tags, filter by category
|
||||||
- **Export**: Export individual todos to text files
|
- **Export**: Export individual todos to text files
|
||||||
|
- **Completed Todos**: Mark todos as completed/incomplete with automatic sorting to the end of the list
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@ export PATH=$PATH:~/.local/bin
|
|||||||
| `b` | Browse tags |
|
| `b` | Browse tags |
|
||||||
| `c` | Filter by category |
|
| `c` | Filter by category |
|
||||||
| `l` | Toggle language (Czech/English) |
|
| `l` | Toggle language (Czech/English) |
|
||||||
|
| `t` | Toggle todo completion status |
|
||||||
| `+` | Increase priority (when adding/editing) |
|
| `+` | Increase priority (when adding/editing) |
|
||||||
| `↑/↓` or `j/k` | Navigate through list |
|
| `↑/↓` or `j/k` | Navigate through list |
|
||||||
| `Tab` | Switch between fields |
|
| `Tab` | Switch between fields |
|
||||||
@@ -83,6 +85,7 @@ Each todo has:
|
|||||||
- **Tags**: Comma-separated list
|
- **Tags**: Comma-separated list
|
||||||
- **Created At**: Automatic timestamp
|
- **Created At**: Automatic timestamp
|
||||||
- **Modified At**: Automatic timestamp on edits
|
- **Modified At**: Automatic timestamp on edits
|
||||||
|
- **Completed**: Completion status (toggle with `t` key)
|
||||||
|
|
||||||
### Storage
|
### Storage
|
||||||
|
|
||||||
@@ -96,6 +99,14 @@ Exported todos are saved to:
|
|||||||
~/doxa-todo-export/
|
~/doxa-todo-export/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Completed Todos
|
||||||
|
|
||||||
|
The application supports marking todos as completed:
|
||||||
|
- **Toggle Status**: Select a todo and press `t` to toggle between completed/incomplete
|
||||||
|
- **Visualization**: Completed todos are displayed with strikethrough and gray color
|
||||||
|
- **Automatic Sorting**: Completed todos are always displayed at the end of the list (after incomplete ones)
|
||||||
|
- **Persistent Storage**: Completion status is saved with the todo
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The application automatically creates the configuration directory and file on first run.
|
The application automatically creates the configuration directory and file on first run.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
- **Trvalé uložení**: Úkoly se ukládají do `~/.doxa-todo/doxa_todo.json`
|
- **Trvalé uložení**: Úkoly se ukládají do `~/.doxa-todo/doxa_todo.json`
|
||||||
- **Vyhledávání a filtrace**: Vyhledávání podle textu, procházení podle štítků, filtrace podle kategorie
|
- **Vyhledávání a filtrace**: Vyhledávání podle textu, procházení podle štítků, filtrace podle kategorie
|
||||||
- **Export**: Export jednotlivých úkolů do textových souborů
|
- **Export**: Export jednotlivých úkolů do textových souborů
|
||||||
|
- **Splněné úkoly**: Označování úkolů jako splněné/nesplněné s automatickým řazením na konec seznamu
|
||||||
|
|
||||||
## Instalace
|
## Instalace
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@ export PATH=$PATH:~/.local/bin
|
|||||||
| `b` | Procházet štítky |
|
| `b` | Procházet štítky |
|
||||||
| `c` | Filtr podle kategorie |
|
| `c` | Filtr podle kategorie |
|
||||||
| `l` | Přepnout jazyk (Čeština/Angličtina) |
|
| `l` | Přepnout jazyk (Čeština/Angličtina) |
|
||||||
|
| `t` | Přepnout stav splnění úkolu |
|
||||||
| `+` | Zvýšit prioritu (při přidávání/úpravě) |
|
| `+` | Zvýšit prioritu (při přidávání/úpravě) |
|
||||||
| `↑/↓` nebo `j/k` | Procházet seznamem |
|
| `↑/↓` nebo `j/k` | Procházet seznamem |
|
||||||
| `Tab` | Přepínat mezi poli |
|
| `Tab` | Přepínat mezi poli |
|
||||||
@@ -83,6 +85,7 @@ Každý úkol má:
|
|||||||
- **Štítky**: Seznam oddělený čárkami
|
- **Štítky**: Seznam oddělený čárkami
|
||||||
- **Vytvořeno**: Automatické časové razítko
|
- **Vytvořeno**: Automatické časové razítko
|
||||||
- **Upraveno**: Automatické časové razítko při úpravách
|
- **Upraveno**: Automatické časové razítko při úpravách
|
||||||
|
- **Splněno**: Stav splnění (přepíná se klávesou `t`)
|
||||||
|
|
||||||
### Uložení
|
### Uložení
|
||||||
|
|
||||||
@@ -96,6 +99,14 @@ Exportované úkoly jsou uloženy v:
|
|||||||
~/doxa-todo-export/
|
~/doxa-todo-export/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Splněné úkoly
|
||||||
|
|
||||||
|
Aplikace podporuje označování úkolů jako splněné:
|
||||||
|
- **Přepnutí stavu**: Vyberte úkol a stiskněte `t` pro přepnutí mezi splněným/nesplněným
|
||||||
|
- **Vizualizace**: Splněné úkoly jsou zobrazovány přeškrtlé a šedě
|
||||||
|
- **Automatické řazení**: Splněné úkoly jsou vždy zobrazeny na konci seznamu (za nesplněnými)
|
||||||
|
- **Trvalé uložení**: Stav splnění se ukládá spolu s úkolem
|
||||||
|
|
||||||
## Konfigurace
|
## Konfigurace
|
||||||
|
|
||||||
Aplikace automaticky vytvoří konfigurační adresář a soubor při prvním spuštění.
|
Aplikace automaticky vytvoří konfigurační adresář a soubor při prvním spuštění.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type Translation struct {
|
|||||||
MenuSearch string
|
MenuSearch string
|
||||||
MenuBrowse string
|
MenuBrowse string
|
||||||
MenuFilter string
|
MenuFilter string
|
||||||
|
MenuToggleComplete string
|
||||||
ListTitle string
|
ListTitle string
|
||||||
NoTodos string
|
NoTodos string
|
||||||
AddTitle string
|
AddTitle string
|
||||||
@@ -30,6 +31,8 @@ type Translation struct {
|
|||||||
StatusEdited string
|
StatusEdited string
|
||||||
StatusDeleted string
|
StatusDeleted string
|
||||||
StatusExported string
|
StatusExported string
|
||||||
|
StatusCompleted string
|
||||||
|
StatusUncompleted string
|
||||||
ErrRequired string
|
ErrRequired string
|
||||||
SearchTitle string
|
SearchTitle string
|
||||||
BrowseTags string
|
BrowseTags string
|
||||||
@@ -55,6 +58,7 @@ var Translations = map[string]Translation{
|
|||||||
MenuSearch: "/: Hledat",
|
MenuSearch: "/: Hledat",
|
||||||
MenuBrowse: "b: Tagy",
|
MenuBrowse: "b: Tagy",
|
||||||
MenuFilter: "c: Filtr",
|
MenuFilter: "c: Filtr",
|
||||||
|
MenuToggleComplete: "t: Splněno",
|
||||||
ListTitle: "--- SEZNAM ÚKOLŮ ---",
|
ListTitle: "--- SEZNAM ÚKOLŮ ---",
|
||||||
NoTodos: " (Prázdno...)",
|
NoTodos: " (Prázdno...)",
|
||||||
AddTitle: "PŘIDAT ÚKOL",
|
AddTitle: "PŘIDAT ÚKOL",
|
||||||
@@ -68,12 +72,14 @@ var Translations = map[string]Translation{
|
|||||||
PlaceholderDescription: "Popis (nepovinný)",
|
PlaceholderDescription: "Popis (nepovinný)",
|
||||||
PlaceholderTags: "Tagy (nepovinné, oddělené čárkou)",
|
PlaceholderTags: "Tagy (nepovinné, oddělené čárkou)",
|
||||||
HelpForm: "[Tab] Další pole | [Enter] Uložit | [+] Zvýšit prioritu | [-] Změnit kategorii | [q] Zrušit",
|
HelpForm: "[Tab] Další pole | [Enter] Uložit | [+] Zvýšit prioritu | [-] Změnit kategorii | [q] Zrušit",
|
||||||
HelpList: "↑/↓: Vybrat | Enter: Zobrazit | /: Hledat | b: Procházet tagy | c: Filtr kategorie | q: Konec",
|
HelpList: "↑/↓: Vybrat | Enter: Zobrazit | /: Hledat | b: Procházet tagy | c: Filtr kategorie | t: Splněno | q: Konec",
|
||||||
HelpFormExtended: "[Tab] Další pole | [Enter] Uložit | [+] Priorita | [-] Kategorie | [q] Zrušit",
|
HelpFormExtended: "[Tab] Další pole | [Enter] Uložit | [+] Priorita | [-] Kategorie | [q] Zrušit",
|
||||||
StatusSaved: "Úkol uložen.",
|
StatusSaved: "Úkol uložen.",
|
||||||
StatusEdited: "Změny uloženy.",
|
StatusEdited: "Změny uloženy.",
|
||||||
StatusDeleted: "Úkol smazán.",
|
StatusDeleted: "Úkol smazán.",
|
||||||
StatusExported: "Úkol vyexportován.",
|
StatusExported: "Úkol vyexportován.",
|
||||||
|
StatusCompleted: "Úkol označen jako splněný.",
|
||||||
|
StatusUncompleted: "Úkol označen jako nesplněný.",
|
||||||
ErrRequired: "Chyba: Název je povinný!",
|
ErrRequired: "Chyba: Název je povinný!",
|
||||||
SearchTitle: "HLEDAT: ",
|
SearchTitle: "HLEDAT: ",
|
||||||
BrowseTags: "TAGY",
|
BrowseTags: "TAGY",
|
||||||
@@ -97,6 +103,7 @@ var Translations = map[string]Translation{
|
|||||||
MenuSearch: "/: Search",
|
MenuSearch: "/: Search",
|
||||||
MenuBrowse: "b: Tags",
|
MenuBrowse: "b: Tags",
|
||||||
MenuFilter: "c: Filter",
|
MenuFilter: "c: Filter",
|
||||||
|
MenuToggleComplete: "t: Toggle Complete",
|
||||||
ListTitle: "--- TODOS LIST ---",
|
ListTitle: "--- TODOS LIST ---",
|
||||||
NoTodos: " (Empty...)",
|
NoTodos: " (Empty...)",
|
||||||
AddTitle: "ADD TODO",
|
AddTitle: "ADD TODO",
|
||||||
@@ -110,12 +117,14 @@ var Translations = map[string]Translation{
|
|||||||
PlaceholderDescription: "Description (optional)",
|
PlaceholderDescription: "Description (optional)",
|
||||||
PlaceholderTags: "Tags (optional, comma separated)",
|
PlaceholderTags: "Tags (optional, comma separated)",
|
||||||
HelpForm: "[Tab] Next field | [Enter] Save | [+] Priority | [-] Category | [q] Cancel",
|
HelpForm: "[Tab] Next field | [Enter] Save | [+] Priority | [-] Category | [q] Cancel",
|
||||||
HelpList: "↑/↓: Select | Enter: View | /: Search | b: Browse tags | c: Filter category | q: Quit",
|
HelpList: "↑/↓: Select | Enter: View | /: Search | b: Browse tags | c: Filter category | t: Toggle Complete | q: Quit",
|
||||||
HelpFormExtended: "[Tab] Next field | [Enter] Save | [+] Increase priority | [-] Change category | [q] Cancel",
|
HelpFormExtended: "[Tab] Next field | [Enter] Save | [+] Increase priority | [-] Change category | [q] Cancel",
|
||||||
StatusSaved: "Todo saved.",
|
StatusSaved: "Todo saved.",
|
||||||
StatusEdited: "Changes saved.",
|
StatusEdited: "Changes saved.",
|
||||||
StatusDeleted: "Todo deleted.",
|
StatusDeleted: "Todo deleted.",
|
||||||
StatusExported: "Todo exported.",
|
StatusExported: "Todo exported.",
|
||||||
|
StatusCompleted: "Todo marked as completed.",
|
||||||
|
StatusUncompleted: "Todo marked as incomplete.",
|
||||||
ErrRequired: "Error: Title is required!",
|
ErrRequired: "Error: Title is required!",
|
||||||
SearchTitle: "SEARCH: ",
|
SearchTitle: "SEARCH: ",
|
||||||
BrowseTags: "TAGS",
|
BrowseTags: "TAGS",
|
||||||
|
|||||||
+99
-3
@@ -54,6 +54,10 @@ var (
|
|||||||
|
|
||||||
categoryWork = lipgloss.NewStyle().
|
categoryWork = lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("#9B59B6")) // Fialová
|
Foreground(lipgloss.Color("#9B59B6")) // Fialová
|
||||||
|
|
||||||
|
completedStyle = lipgloss.NewStyle().
|
||||||
|
Strikethrough(true).
|
||||||
|
Foreground(lipgloss.Color("#95A5A6")) // Šedá pro splněné úkoly
|
||||||
)
|
)
|
||||||
|
|
||||||
// Priority levels
|
// Priority levels
|
||||||
@@ -116,6 +120,7 @@ type Todo struct {
|
|||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
ModifiedAt time.Time `json:"modified_at"`
|
ModifiedAt time.Time `json:"modified_at"`
|
||||||
|
Completed bool `json:"completed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type model struct {
|
type model struct {
|
||||||
@@ -161,6 +166,7 @@ func NewModel() model {
|
|||||||
newTodoPriority: PriorityMedium,
|
newTodoPriority: PriorityMedium,
|
||||||
newTodoCategory: CategoryPersonal,
|
newTodoCategory: CategoryPersonal,
|
||||||
}
|
}
|
||||||
|
m.sortTodos()
|
||||||
m.applyFilter()
|
m.applyFilter()
|
||||||
m.updatePlaceholders()
|
m.updatePlaceholders()
|
||||||
return m
|
return m
|
||||||
@@ -170,6 +176,28 @@ func (m model) Init() tea.Cmd {
|
|||||||
return tea.Batch(textinput.Blink, tea.EnterAltScreen)
|
return tea.Batch(textinput.Blink, tea.EnterAltScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *model) sortTodos() {
|
||||||
|
// Sort todos: completed ones always last, then by ID (newest first)
|
||||||
|
for i := 0; i < len(m.todos)-1; i++ {
|
||||||
|
for j := i + 1; j < len(m.todos); j++ {
|
||||||
|
// If i is completed and j is not, swap
|
||||||
|
if m.todos[i].Completed && !m.todos[j].Completed {
|
||||||
|
m.todos[i], m.todos[j] = m.todos[j], m.todos[i]
|
||||||
|
} else if !m.todos[i].Completed && !m.todos[j].Completed {
|
||||||
|
// Both not completed, sort by ID (newest first)
|
||||||
|
if m.todos[i].ID < m.todos[j].ID {
|
||||||
|
m.todos[i], m.todos[j] = m.todos[j], m.todos[i]
|
||||||
|
}
|
||||||
|
} else if m.todos[i].Completed && m.todos[j].Completed {
|
||||||
|
// Both completed, sort by ID (newest first)
|
||||||
|
if m.todos[i].ID < m.todos[j].ID {
|
||||||
|
m.todos[i], m.todos[j] = m.todos[j], m.todos[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *model) applyFilter() {
|
func (m *model) applyFilter() {
|
||||||
if m.filterText == "" && m.currentCategoryFilter == -1 {
|
if m.filterText == "" && m.currentCategoryFilter == -1 {
|
||||||
m.filteredTodos = m.todos
|
m.filteredTodos = m.todos
|
||||||
@@ -196,6 +224,25 @@ func (m *model) applyFilter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Sort filtered todos: completed ones always last
|
||||||
|
for i := 0; i < len(m.filteredTodos)-1; i++ {
|
||||||
|
for j := i + 1; j < len(m.filteredTodos); j++ {
|
||||||
|
// If i is completed and j is not, swap
|
||||||
|
if m.filteredTodos[i].Completed && !m.filteredTodos[j].Completed {
|
||||||
|
m.filteredTodos[i], m.filteredTodos[j] = m.filteredTodos[j], m.filteredTodos[i]
|
||||||
|
} else if !m.filteredTodos[i].Completed && !m.filteredTodos[j].Completed {
|
||||||
|
// Both not completed, sort by ID (newest first)
|
||||||
|
if m.filteredTodos[i].ID < m.filteredTodos[j].ID {
|
||||||
|
m.filteredTodos[i], m.filteredTodos[j] = m.filteredTodos[j], m.filteredTodos[i]
|
||||||
|
}
|
||||||
|
} else if m.filteredTodos[i].Completed && m.filteredTodos[j].Completed {
|
||||||
|
// Both completed, sort by ID (newest first)
|
||||||
|
if m.filteredTodos[i].ID < m.filteredTodos[j].ID {
|
||||||
|
m.filteredTodos[i], m.filteredTodos[j] = m.filteredTodos[j], m.filteredTodos[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if m.cursor >= len(m.filteredTodos) {
|
if m.cursor >= len(m.filteredTodos) {
|
||||||
m.cursor = 0
|
m.cursor = 0
|
||||||
if len(m.filteredTodos) > 0 {
|
if len(m.filteredTodos) > 0 {
|
||||||
@@ -492,6 +539,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.selectedTodoID = m.filteredTodos[m.cursor].ID
|
m.selectedTodoID = m.filteredTodos[m.cursor].ID
|
||||||
m.focusIndex = 0
|
m.focusIndex = 0
|
||||||
}
|
}
|
||||||
|
case "t":
|
||||||
|
if m.state == StateViewTodos && len(m.filteredTodos) > 0 {
|
||||||
|
m.toggleTodoCompleted()
|
||||||
|
}
|
||||||
case "+":
|
case "+":
|
||||||
if m.state == StateAddTodo || m.state == StateEditTodo {
|
if m.state == StateAddTodo || m.state == StateEditTodo {
|
||||||
// Cycle priority up
|
// Cycle priority up
|
||||||
@@ -735,7 +786,12 @@ func (m model) View() string {
|
|||||||
if m.cursor == i {
|
if m.cursor == i {
|
||||||
cursor = selectedStyle.Render("> ")
|
cursor = selectedStyle.Render("> ")
|
||||||
}
|
}
|
||||||
listBuilder.WriteString(fmt.Sprintf("%s%s %s %s%s\n", cursor, priorityIcon, categoryIcon, t.Title, tagStr))
|
|
||||||
|
title := t.Title
|
||||||
|
if t.Completed {
|
||||||
|
title = completedStyle.Render(t.Title)
|
||||||
|
}
|
||||||
|
listBuilder.WriteString(fmt.Sprintf("%s%s %s %s%s\n", cursor, priorityIcon, categoryIcon, title, tagStr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -743,8 +799,8 @@ func (m model) View() string {
|
|||||||
m.viewport.SetContent(listBuilder.String())
|
m.viewport.SetContent(listBuilder.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteString(fmt.Sprintf("%s | %s | %s | %s | %s | %s | %s | %s\n\n",
|
b.WriteString(fmt.Sprintf("%s | %s | %s | %s | %s | %s | %s | %s | %s\n\n",
|
||||||
tr.MenuAdd, tr.MenuEdit, tr.MenuExport, tr.MenuDelete, tr.MenuSearch, tr.MenuBrowse, tr.MenuFilter, tr.MenuLang))
|
tr.MenuAdd, tr.MenuEdit, tr.MenuExport, tr.MenuDelete, tr.MenuSearch, tr.MenuBrowse, tr.MenuFilter, tr.MenuToggleComplete, tr.MenuLang))
|
||||||
b.WriteString(tr.ListTitle + filterInfo + categoryInfo + "\n")
|
b.WriteString(tr.ListTitle + filterInfo + categoryInfo + "\n")
|
||||||
|
|
||||||
if m.ready {
|
if m.ready {
|
||||||
@@ -911,8 +967,10 @@ func (m *model) handleAddTodo() {
|
|||||||
Tags: tags,
|
Tags: tags,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
ModifiedAt: now,
|
ModifiedAt: now,
|
||||||
|
Completed: false,
|
||||||
})
|
})
|
||||||
saveTodos(m.todos)
|
saveTodos(m.todos)
|
||||||
|
m.sortTodos()
|
||||||
m.applyFilter()
|
m.applyFilter()
|
||||||
m.statusMsg = tr.StatusSaved
|
m.statusMsg = tr.StatusSaved
|
||||||
m.state = StateViewTodos
|
m.state = StateViewTodos
|
||||||
@@ -940,6 +998,7 @@ func (m *model) handleEditTodo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveTodos(m.todos)
|
saveTodos(m.todos)
|
||||||
|
m.sortTodos()
|
||||||
m.applyFilter()
|
m.applyFilter()
|
||||||
m.statusMsg = tr.StatusEdited
|
m.statusMsg = tr.StatusEdited
|
||||||
m.state = StateViewTodos
|
m.state = StateViewTodos
|
||||||
@@ -958,9 +1017,46 @@ func (m *model) handleDeleteTodo() {
|
|||||||
m.todos = append(m.todos[:todoIndex], m.todos[todoIndex+1:]...)
|
m.todos = append(m.todos[:todoIndex], m.todos[todoIndex+1:]...)
|
||||||
saveTodos(m.todos)
|
saveTodos(m.todos)
|
||||||
}
|
}
|
||||||
|
m.sortTodos()
|
||||||
m.applyFilter()
|
m.applyFilter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *model) toggleTodoCompleted() {
|
||||||
|
tr := Translations[m.lang]
|
||||||
|
selectedID := m.filteredTodos[m.cursor].ID
|
||||||
|
|
||||||
|
for i, t := range m.todos {
|
||||||
|
if t.ID == selectedID {
|
||||||
|
m.todos[i].Completed = !m.todos[i].Completed
|
||||||
|
m.todos[i].ModifiedAt = time.Now()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveTodos(m.todos)
|
||||||
|
m.sortTodos()
|
||||||
|
m.applyFilter()
|
||||||
|
|
||||||
|
// Find the toggled todo in filtered list to update cursor
|
||||||
|
for i, t := range m.filteredTodos {
|
||||||
|
if t.ID == selectedID {
|
||||||
|
m.cursor = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the todo to check its completed status for status message
|
||||||
|
for _, t := range m.todos {
|
||||||
|
if t.ID == selectedID {
|
||||||
|
if t.Completed {
|
||||||
|
m.statusMsg = tr.StatusCompleted
|
||||||
|
} else {
|
||||||
|
m.statusMsg = tr.StatusUncompleted
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func exportTodo(todos []Todo, id int64) {
|
func exportTodo(todos []Todo, id int64) {
|
||||||
for _, todo := range todos {
|
for _, todo := range todos {
|
||||||
if todo.ID == id {
|
if todo.ID == id {
|
||||||
|
|||||||
Reference in new issue
Block a user