rozšíření-completeTask

This commit is contained in:
kankys committed 2026-08-09 10:33:11 +02:00
1 parent 6230b7895d
commit b447cc8de7
6 files changed
+178 -5

No files matched your search

+23
View File
@@ -33,6 +33,7 @@ The main interface displays:
- **/: Search** - Search todos by title, description, or tags
- **b: Tags** - Browse all tags
- **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
- **q: Quit** - Exit the application
@@ -138,6 +139,27 @@ The exported file contains:
3. Press `y` to confirm deletion
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
### Navigation
@@ -154,6 +176,7 @@ The exported file contains:
- `/`: Search
- `b`: Browse tags
- `c`: Filter by category
- `t`: Toggle todo completion status
- `l`: Toggle language
- `+`: Increase priority (when adding/editing)
- `Tab`: Switch between fields
+23
View File
@@ -33,6 +33,7 @@ Hlavní rozhraní zobrazuje:
- **/: Hledat** - Vyhledat úkoly podle názvu, popisu nebo štítků
- **b: Tagy** - Procházet všechny štítky
- **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
- **q: Konec** - Ukončit aplikaci
@@ -138,6 +139,27 @@ Exportovaný soubor obsahuje:
3. Stiskněte `y` pro potvrzení smazání
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
### Navigace
@@ -154,6 +176,7 @@ Exportovaný soubor obsahuje:
- `/`: Vyhledat
- `b`: Procházet štítky
- `c`: Filtr podle kategorie
- `t`: Přepnout stav splnění úkolu
- `l`: Přepnout jazyk
- `+`: Zvýšit prioritu (při přidávání/úpravě)
- `Tab`: Přepínat mezi poli
+11
View File
@@ -12,6 +12,7 @@
- **Persistent Storage**: Todos are saved to `~/.doxa-todo/doxa_todo.json`
- **Search & Filter**: Search by text, browse by tags, filter by category
- **Export**: Export individual todos to text files
- **Completed Todos**: Mark todos as completed/incomplete with automatic sorting to the end of the list
## Installation
@@ -69,6 +70,7 @@ export PATH=$PATH:~/.local/bin
| `b` | Browse tags |
| `c` | Filter by category |
| `l` | Toggle language (Czech/English) |
| `t` | Toggle todo completion status |
| `+` | Increase priority (when adding/editing) |
| `↑/↓` or `j/k` | Navigate through list |
| `Tab` | Switch between fields |
@@ -83,6 +85,7 @@ Each todo has:
- **Tags**: Comma-separated list
- **Created At**: Automatic timestamp
- **Modified At**: Automatic timestamp on edits
- **Completed**: Completion status (toggle with `t` key)
### Storage
@@ -96,6 +99,14 @@ Exported todos are saved to:
~/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
The application automatically creates the configuration directory and file on first run.
+11
View File
@@ -12,6 +12,7 @@
- **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
- **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
@@ -69,6 +70,7 @@ export PATH=$PATH:~/.local/bin
| `b` | Procházet štítky |
| `c` | Filtr podle kategorie |
| `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ě) |
| `↑/↓` nebo `j/k` | Procházet seznamem |
| `Tab` | Přepínat mezi poli |
@@ -83,6 +85,7 @@ Každý úkol má:
- **Štítky**: Seznam oddělený čárkami
- **Vytvořeno**: Automatické časové razítko
- **Upraveno**: Automatické časové razítko při úpravách
- **Splněno**: Stav splnění (přepíná se klávesou `t`)
### Uložení
@@ -96,6 +99,14 @@ Exportované úkoly jsou uloženy v:
~/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
Aplikace automaticky vytvoří konfigurační adresář a soubor při prvním spuštění.
+11 -2
View File
@@ -11,6 +11,7 @@ type Translation struct {
MenuSearch string
MenuBrowse string
MenuFilter string
MenuToggleComplete string
ListTitle string
NoTodos string
AddTitle string
@@ -30,6 +31,8 @@ type Translation struct {
StatusEdited string
StatusDeleted string
StatusExported string
StatusCompleted string
StatusUncompleted string
ErrRequired string
SearchTitle string
BrowseTags string
@@ -55,6 +58,7 @@ var Translations = map[string]Translation{
MenuSearch: "/: Hledat",
MenuBrowse: "b: Tagy",
MenuFilter: "c: Filtr",
MenuToggleComplete: "t: Splněno",
ListTitle: "--- SEZNAM ÚKOLŮ ---",
NoTodos: " (Prázdno...)",
AddTitle: "PŘIDAT ÚKOL",
@@ -68,12 +72,14 @@ var Translations = map[string]Translation{
PlaceholderDescription: "Popis (nepovinný)",
PlaceholderTags: "Tagy (nepovinné, oddělené čárkou)",
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",
StatusSaved: "Úkol uložen.",
StatusEdited: "Změny uloženy.",
StatusDeleted: "Úkol smazá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ý!",
SearchTitle: "HLEDAT: ",
BrowseTags: "TAGY",
@@ -97,6 +103,7 @@ var Translations = map[string]Translation{
MenuSearch: "/: Search",
MenuBrowse: "b: Tags",
MenuFilter: "c: Filter",
MenuToggleComplete: "t: Toggle Complete",
ListTitle: "--- TODOS LIST ---",
NoTodos: " (Empty...)",
AddTitle: "ADD TODO",
@@ -110,12 +117,14 @@ var Translations = map[string]Translation{
PlaceholderDescription: "Description (optional)",
PlaceholderTags: "Tags (optional, comma separated)",
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",
StatusSaved: "Todo saved.",
StatusEdited: "Changes saved.",
StatusDeleted: "Todo deleted.",
StatusExported: "Todo exported.",
StatusCompleted: "Todo marked as completed.",
StatusUncompleted: "Todo marked as incomplete.",
ErrRequired: "Error: Title is required!",
SearchTitle: "SEARCH: ",
BrowseTags: "TAGS",
+99 -3
View File
@@ -54,6 +54,10 @@ var (
categoryWork = lipgloss.NewStyle().
Foreground(lipgloss.Color("#9B59B6")) // Fialová
completedStyle = lipgloss.NewStyle().
Strikethrough(true).
Foreground(lipgloss.Color("#95A5A6")) // Šedá pro splněné úkoly
)
// Priority levels
@@ -116,6 +120,7 @@ type Todo struct {
Tags []string `json:"tags"`
CreatedAt time.Time `json:"created_at"`
ModifiedAt time.Time `json:"modified_at"`
Completed bool `json:"completed"`
}
type model struct {
@@ -161,6 +166,7 @@ func NewModel() model {
newTodoPriority: PriorityMedium,
newTodoCategory: CategoryPersonal,
}
m.sortTodos()
m.applyFilter()
m.updatePlaceholders()
return m
@@ -170,6 +176,28 @@ func (m model) Init() tea.Cmd {
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() {
if m.filterText == "" && m.currentCategoryFilter == -1 {
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) {
m.cursor = 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.focusIndex = 0
}
case "t":
if m.state == StateViewTodos && len(m.filteredTodos) > 0 {
m.toggleTodoCompleted()
}
case "+":
if m.state == StateAddTodo || m.state == StateEditTodo {
// Cycle priority up
@@ -735,7 +786,12 @@ func (m model) View() string {
if m.cursor == i {
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())
}
b.WriteString(fmt.Sprintf("%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))
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.MenuToggleComplete, tr.MenuLang))
b.WriteString(tr.ListTitle + filterInfo + categoryInfo + "\n")
if m.ready {
@@ -911,8 +967,10 @@ func (m *model) handleAddTodo() {
Tags: tags,
CreatedAt: now,
ModifiedAt: now,
Completed: false,
})
saveTodos(m.todos)
m.sortTodos()
m.applyFilter()
m.statusMsg = tr.StatusSaved
m.state = StateViewTodos
@@ -940,6 +998,7 @@ func (m *model) handleEditTodo() {
}
}
saveTodos(m.todos)
m.sortTodos()
m.applyFilter()
m.statusMsg = tr.StatusEdited
m.state = StateViewTodos
@@ -958,9 +1017,46 @@ func (m *model) handleDeleteTodo() {
m.todos = append(m.todos[:todoIndex], m.todos[todoIndex+1:]...)
saveTodos(m.todos)
}
m.sortTodos()
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) {
for _, todo := range todos {
if todo.ID == id {