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
|
||||
- **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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user