mirror of
https://codeberg.org/Kankys/doxa.git
synced 2026-11-11 06:44:36 +01:00
habit+timer
This commit is contained in:
1 parent
6356981825
commit
f19af63d91
31 files changed
+2767
-106
No files matched your search
+16
-53
@@ -103,31 +103,9 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
|
||||
|
||||
---
|
||||
|
||||
### 5. ✅ DOXA Habit Tracker 🎯
|
||||
**Popis**: Tracker návyků pro terminál
|
||||
|
||||
- **Priorita**: Střední
|
||||
- **Integrace**: Motivace a sledování osobního růstu
|
||||
- **Cílová platforma**: Linux, FreeBSD
|
||||
- **Technologie**: Go, Bubble Tea
|
||||
|
||||
**Plánované funkce**:
|
||||
- [ ] Vytváření návyků s cíli (denní, týdenní)
|
||||
- [ ] Denní sledování splnění
|
||||
- [ ] Statistiky a grafy (ASCII)
|
||||
- [ ] Motivující zprávy a upozornění
|
||||
- [ ] Export dat do CSV
|
||||
- [ ] Přehled streaku (počet po sobě jdoucích dnů)
|
||||
- [ ] Dvoujazyčné rozhraní (CZ/EN)
|
||||
|
||||
**Odhadovaná složitost**: Střední
|
||||
**Odhadovaný čas**: 2 týdny
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Bonusové nápady
|
||||
|
||||
### 6. DOXA Finance 💰
|
||||
### 5. DOXA Finance 💰
|
||||
**Popis**: Správa výdajů a příjmů
|
||||
|
||||
- **Funkce**: Evidence transakcí, kategorie, statistiky, rozpočty
|
||||
@@ -137,7 +115,7 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
|
||||
|
||||
---
|
||||
|
||||
### 7. DOXA Bookmarks 🔖
|
||||
### 6. DOXA Bookmarks 🔖
|
||||
**Popis**: Správce záložek z příkazové řádky
|
||||
|
||||
- **Funkce**: Ukládání URL, tagování, vyhledávání, otevírání v prohlížeči
|
||||
@@ -147,17 +125,7 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
|
||||
|
||||
---
|
||||
|
||||
### 8. DOXA Timer ⏱️
|
||||
**Popis**: Pomodoro timer pro terminál
|
||||
|
||||
- **Funkce**: Nastavení časových intervalů, sledování produktivity, statistiky
|
||||
- **Technologie**: Go, Bubble Tea
|
||||
- **Složitost**: Nízká
|
||||
- **Integrace**: Spuštění společně s doxa-todo
|
||||
|
||||
---
|
||||
|
||||
### 9. DOXA Sync 🔄
|
||||
### 7. DOXA Sync 🔄
|
||||
**Popis**: Lokální synchronizace mezi zařízeními
|
||||
|
||||
- **Funkce**: Synchronizace dat přes SSH/SCP, konflikt resolution
|
||||
@@ -167,7 +135,7 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
|
||||
|
||||
---
|
||||
|
||||
### 10. DOXA Backup 💾
|
||||
### 8. DOXA Backup 💾
|
||||
**Popis**: Nástroj pro zálohování DOXA dat
|
||||
|
||||
- **Funkce**: Automatické zálohování, obnovení, verze, komprese
|
||||
@@ -180,17 +148,15 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
|
||||
## 📊 Prioritizace
|
||||
|
||||
| Pořadí | Aplikace | Priorita | Složitost | Čas | Integrace s existujícími |
|
||||
|--------|----------|----------|-----------|------|--------------------------|
|
||||
|--------|----------|----------|-----------|------|-----------------------------|
|
||||
| 1 | DOXA Calendar | Vysoká | Střední | 2-3 týdny | ✅ Vysoká (doxa-todo) |
|
||||
| 2 | DOXA Contacts | Vysoká | Nízká | 1-2 týdny | ✅ Střední |
|
||||
| 3 | DOXA Password | Vysoká | Vysoká | 3-4 týdny | ✅ Střední (doxa-secret) |
|
||||
| 4 | DOXA Journal | Střední | Nízká | 1-2 týdny | ⚠️ Nízká |
|
||||
| 5 | DOXA Habit Tracker | Střední | Střední | 2 týdny | ⚠️ Nízká |
|
||||
| 6 | DOXA Finance | Nízká | Vysoká | 3-4 týdny | ❌ Žádná |
|
||||
| 7 | DOXA Bookmarks | Nízká | Nízká | 1 týden | ❌ Žádná |
|
||||
| 8 | DOXA Timer | Nízká | Nízká | 1 týden | ✅ Střední (doxa-todo) |
|
||||
| 9 | DOXA Sync | Nízká | Vysoká | 4+ týdny | ✅ Vysoká (všechny) |
|
||||
| 10 | DOXA Backup | Nízká | Střední | 2-3 týdny | ✅ Vysoká (všechny) |
|
||||
| 5 | DOXA Finance | Nízká | Vysoká | 3-4 týdny | ❌ Žádná |
|
||||
| 6 | DOXA Bookmarks | Nízká | Nízká | 1 týden | ❌ Žádná |
|
||||
| 7 | DOXA Sync | Nízká | Vysoká | 4+ týdny | ✅ Vysoká (všechny) |
|
||||
| 8 | DOXA Backup | Nízká | Střední | 2-3 týdny | ✅ Vysoká (všechny) |
|
||||
|
||||
---
|
||||
|
||||
@@ -204,13 +170,9 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
|
||||
3. **DOXA Password** - Bezpečný správce hesel
|
||||
4. **DOXA Journal** - Osobní deník
|
||||
|
||||
### Fáze 3: Pokročilé funkce (3-4 měsíce)
|
||||
5. **DOXA Habit Tracker** - Sledování návyků
|
||||
6. **DOXA Timer** - Pomodoro timer
|
||||
|
||||
### Fáze 4: Systémové nástroje (4+ měsíce)
|
||||
7. **DOXA Sync** - Synchronizace mezi zařízeními
|
||||
8. **DOXA Backup** - Zálohovací nástroj
|
||||
### Fáze 3: Systémové nástroje (3+ měsíce)
|
||||
5. **DOXA Sync** - Synchronizace mezi zařízeními
|
||||
6. **DOXA Backup** - Zálohovací nástroj
|
||||
|
||||
---
|
||||
|
||||
@@ -232,12 +194,13 @@ Všechny nové aplikace by měly:
|
||||
- [x] DOXA Notes - ✅ Kompletní
|
||||
- [x] DOXA CLI - ✅ Kompletní
|
||||
- [x] DOXA Secret - ✅ Kompletní
|
||||
- [x] DOXA TODO - ✅ Kompletní (nově přidáno označování splněných úkolů)
|
||||
- [x] DOXA TODO - ✅ Kompletní
|
||||
- [x] DOXA Timer - ✅ Kompletní (nově přidáno)
|
||||
- [x] DOXA Habit - ✅ Kompletní (nově přidáno)
|
||||
- [ ] DOXA Calendar - ⏳ Plánováno
|
||||
- [ ] DOXA Contacts - ⏳ Plánováno
|
||||
- [ ] DOXA Password - ⏳ Plánováno
|
||||
- [ ] DOXA Journal - ⏳ Plánováno
|
||||
- [ ] DOXA Habit Tracker - ⏳ Plánováno
|
||||
|
||||
---
|
||||
|
||||
@@ -247,4 +210,4 @@ Pokud máte nápady na další aplikace nebo chcete upřednostnit některý z n
|
||||
|
||||
---
|
||||
|
||||
*Poslední aktualizace: 9. srpna 2026*
|
||||
*Poslední aktualizace: 16. srpna 2026*
|
||||
@@ -4,7 +4,7 @@
|
||||
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT license">
|
||||
<img src="https://img.shields.io/badge/language-Go-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/language-JavaScript-F7DF1E?style=flat-square&logo=javascript&logoColor=black" alt="JavaScript">
|
||||
<img src="https://img.shields.io/badge/platform-Linux%20%7C%20Windows%20%7C%20FreeBSD-6DA55F?style=flat-square" alt="Platforms">
|
||||
<img src="https://img.shields.io/badge/platform-Linux%20%7C%20MacOS%20%7C%20FreeBSD-6DA55F?style=flat-square" alt="Platforms">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
## 🌍 DOXA
|
||||
|
||||
**DOXA** is a collection of three minimalist, locally-focused applications for note management, message encryption, and command-line text processing. All projects share a common philosophy: **complete independence, privacy, and control over your data**. No cloud services, no tracking, no external dependencies. Your data belongs only to you.
|
||||
**DOXA** is a collection of six minimalist, locally-focused applications for note management, message encryption, task management, Pomodoro productivity and habit tracking. All projects share a common philosophy: **complete independence, privacy, and control over your data**. No cloud services, no tracking, no external dependencies. Your data belongs only to you.
|
||||
|
||||
---
|
||||
|
||||
@@ -101,20 +101,66 @@
|
||||
|
||||
---
|
||||
|
||||
### 5. ⏱️ [DOXA Timer](doxa-timer/)
|
||||
|
||||
**Pomodoro timer for the terminal with productivity statistics**
|
||||
|
||||
- **Description**: A minimalist Pomodoro timer for the terminal. Helps you focus using the Pomodoro technique — alternating work sessions with short and long breaks. Tracks your daily and total completed Pomodoros.
|
||||
- **Philosophy**: Local-first productivity. No cloud apps, no subscriptions — just clean focus. Data stored in `~/.doxa-timer/`.
|
||||
- **Technologies**: Go (Golang), Bubble Tea (TUI framework), Lip Gloss (styling).
|
||||
- **Platforms**: Linux, FreeBSD.
|
||||
- **Features**:
|
||||
- Pomodoro technique: work session (25 min), short break (5 min), long break (15 min).
|
||||
- Live countdown timer with ASCII progress bar.
|
||||
- Configurable session lengths.
|
||||
- Daily and total Pomodoro statistics.
|
||||
- Bilingual interface (Czech/English) with `l` key toggle.
|
||||
- Data stored in `~/.doxa-timer/doxa_timer.json`.
|
||||
|
||||
📖 [Read detailed documentation](doxa-timer/README.md)
|
||||
|
||||
---
|
||||
|
||||
### 6. 🎯 [DOXA Habit](doxa-habit/)
|
||||
|
||||
**Habit tracker for the terminal with streak tracking**
|
||||
|
||||
- **Description**: A minimalist terminal application for building and tracking habits. Monitor your daily and weekly habits, track streaks and visualize progress with ASCII heatmaps.
|
||||
- **Philosophy**: Local-first personal growth. No gamification traps, no notification manipulation — just your data and your progress. Data stored in `~/.doxa-habit/`.
|
||||
- **Technologies**: Go (Golang), Bubble Tea (TUI framework), Lip Gloss (styling).
|
||||
- **Platforms**: Linux, FreeBSD.
|
||||
- **Features**:
|
||||
- Daily and weekly habits with completion tracking.
|
||||
- Streak counter (consecutive days completed).
|
||||
- ASCII heatmap for the last 7 days.
|
||||
- Success rate statistics (7/30 days).
|
||||
- Export data to CSV format.
|
||||
- Search habits by name.
|
||||
- Bilingual interface (Czech/English) with `l` key toggle.
|
||||
- Data stored in `~/.doxa-habit/doxa_habit.json`.
|
||||
|
||||
📖 [Read detailed documentation](doxa-habit/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Common Features of All DOXA Projects
|
||||
|
||||
| Feature | DOXA Notes | DOXA CLI | DOXA Secret | DOXA TODO |
|
||||
|---------|------------|----------|--------------|-----------|
|
||||
| **Local Data Storage** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Open Format (JSON)** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **No Cloud Dependencies** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Open Source (MIT)** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Minimalist Design** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Tag Support** | ✅ | ✅ | ❌ | ✅ |
|
||||
| **Search and Filtering** | ✅ | ✅ | ❌ | ✅ |
|
||||
| **Encryption** | ❌ | ❌ | ✅ | ❌ |
|
||||
| **Priority Levels** | ❌ | ❌ | ❌ | ✅ |
|
||||
| **Categories** | ❌ | ❌ | ❌ | ✅ |
|
||||
| Feature | DOXA Notes | DOXA CLI | DOXA Secret | DOXA TODO | DOXA Timer | DOXA Habit |
|
||||
|---------|------------|----------|-------------|-----------|------------|------------|
|
||||
| **Local Data Storage** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Open Format (JSON)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **No Cloud Dependencies** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Open Source (MIT)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Minimalist Design** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Bilingual (CZ/EN)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Tag Support** | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ |
|
||||
| **Search & Filtering** | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
|
||||
| **Encryption** | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| **Priority Levels** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
||||
| **Categories** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
||||
| **Pomodoro Timer** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
||||
| **Streak / Habit Stats** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
||||
| **Data Export** | ✅ JSON | ✅ TXT | ✅ TXT | ✅ TXT | ✅ JSON | ✅ CSV |
|
||||
|
||||
---
|
||||
|
||||
@@ -126,6 +172,8 @@ Each project has its own installation process. Detailed guides can be found in t
|
||||
- [DOXA CLI – Installation](doxa-cli/README.md#installation)
|
||||
- [DOXA Secret – Installation](doxa-secret/README_EN.md#installation)
|
||||
- [DOXA TODO – Installation](doxa-todo/README.md#installation)
|
||||
- [DOXA Timer – Installation](doxa-timer/README.md#installation)
|
||||
- [DOXA Habit – Installation](doxa-habit/README.md#installation)
|
||||
|
||||
---
|
||||
|
||||
@@ -170,7 +218,7 @@ DOXA believes in:
|
||||
|
||||
## 🌍 DOXA
|
||||
|
||||
**DOXA** je sbírka tří minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv a práci s textem z příkazové řádky. Všechny projekty sdílejí společnou filozofii: **úplná nezávislost, soukromí a kontrola nad vašimi daty**. Žádné cloudové služby, žádné sledování, žádné externí závislosti. Vaše data patří pouze vám.
|
||||
**DOXA** je sbírka šesti minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv, správu úkolů, Pomodoro produktivitu a sledování návyků. Všechny projekty sdílejí společnou filozofii: **úplná nezávislost, soukromí a kontrola nad vašimi daty**. Žádné cloudové služby, žádné sledování, žádné externí závislosti. Vaše data patří pouze vám.
|
||||
|
||||
---
|
||||
|
||||
@@ -255,20 +303,66 @@ DOXA believes in:
|
||||
|
||||
---
|
||||
|
||||
### 5. ⏱️ [DOXA Timer](doxa-timer/)
|
||||
|
||||
**Pomodoro timer pro terminál se statistikami produktivity**
|
||||
|
||||
- **Popis**: Minimalistický Pomodoro timer pro terminál. Pomáhá vám se soustředit pomocí Pomodoro techniky — střídání pracovních sezení s krátkými a dlouhými přestávkami. Sleduje denní a celkový počet dokončených Pomodorů.
|
||||
- **Filozofie**: Lokální produktivita. Žádné cloudové aplikace, žádné předplatné — jen čistý fokus na vaši práci. Data uložena v `~/.doxa-timer/`.
|
||||
- **Technologie**: Go (Golang), Bubble Tea (TUI framework), Lip Gloss (stylování).
|
||||
- **Platformy**: Linux, FreeBSD.
|
||||
- **Vlastnosti**:
|
||||
- Pomodoro technika: pracovní sezení (25 min), krátká přestávka (5 min), dlouhá přestávka (15 min).
|
||||
- Live odpočítávání s ASCII progress barem.
|
||||
- Konfigurovatelné délky sezení.
|
||||
- Denní a celkové statistiky Pomodorů.
|
||||
- Dvoujazyčné rozhraní (česky/anglicky) s přepínáním klávesou `l`.
|
||||
- Data uložena v `~/.doxa-timer/doxa_timer.json`.
|
||||
|
||||
📖 [Přečtěte si podrobnější dokumentaci](doxa-timer/README_CZ.md)
|
||||
|
||||
---
|
||||
|
||||
### 6. 🎯 [DOXA Habit](doxa-habit/)
|
||||
|
||||
**Tracker návyků pro terminál se sledováním streaku**
|
||||
|
||||
- **Popis**: Minimalistická terminálová aplikace pro budování a sledování návyků. Sledujte denní a týdenní návyky, streaky a vizualizujte pokrok pomocí ASCII heatmap.
|
||||
- **Filozofie**: Lokální osobní rozvoj. Žádné gamifikační pasti, žádná manipulace s notifikacemi — jen vaše data a váš pokrok. Data uložena v `~/.doxa-habit/`.
|
||||
- **Technologie**: Go (Golang), Bubble Tea (TUI framework), Lip Gloss (stylování).
|
||||
- **Platformy**: Linux, FreeBSD.
|
||||
- **Vlastnosti**:
|
||||
- Denní a týdenní návyky se sledováním splnění.
|
||||
- Streak čítač (po sobě jdoucí dny splnění).
|
||||
- ASCII heatmap posledních 7 dní.
|
||||
- Statistiky úspěšnosti (7/30 dní).
|
||||
- Export dat do CSV formátu.
|
||||
- Vyhledávání návyků dle názvu.
|
||||
- Dvoujazyčné rozhraní (česky/anglicky) s přepínáním klávesou `l`.
|
||||
- Data uložena v `~/.doxa-habit/doxa_habit.json`.
|
||||
|
||||
📖 [Přečtěte si podrobnější dokumentaci](doxa-habit/README_CZ.md)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Společné vlastnosti všech projektů DOXA
|
||||
|
||||
| Vlastnost | DOXA Notes | DOXA CLI | DOXA Secret | DOXA TODO |
|
||||
|-----------|------------|----------|--------------|-----------|
|
||||
| **Lokální uložení dat** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Otevřený formát (JSON)** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Žádné cloudové závislosti** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Open Source (MIT)** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Minimalistický design** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Podpora tagů** | ✅ | ✅ | ❌ | ✅ |
|
||||
| **Vyhledávání a filtrování** | ✅ | ✅ | ❌ | ✅ |
|
||||
| **Šifrování** | ❌ | ❌ | ✅ | ❌ |
|
||||
| **Úrovně priority** | ❌ | ❌ | ❌ | ✅ |
|
||||
| **Kategorie** | ❌ | ❌ | ❌ | ✅ |
|
||||
| Vlastnost | DOXA Notes | DOXA CLI | DOXA Secret | DOXA TODO | DOXA Timer | DOXA Habit |
|
||||
|-----------|------------|----------|-------------|-----------|------------|------------|
|
||||
| **Lokální uložení dat** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Otevřený formát (JSON)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Žádné cloudové závislosti** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Open Source (MIT)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Minimalistický design** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Dvoujazyčné rozhraní** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Podpora tagů** | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ |
|
||||
| **Vyhledávání a filtrování** | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
|
||||
| **Šifrování** | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| **Úrovně priority** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
||||
| **Kategorie** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
|
||||
| **Pomodoro timer** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
||||
| **Streak / Statistiky návyků** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
||||
| **Export dat** | ✅ JSON | ✅ TXT | ✅ TXT | ✅ TXT | ✅ JSON | ✅ CSV |
|
||||
|
||||
---
|
||||
|
||||
@@ -280,6 +374,8 @@ Každý projekt má vlastní instalační postup. Podrobné návody naleznete v
|
||||
- [DOXA CLI – Instalace](doxa-cli/README_CZ.md#instalace)
|
||||
- [DOXA Secret – Instalace](doxa-secret/README.md#instalace)
|
||||
- [DOXA TODO – Instalace](doxa-todo/README_CZ.md#instalace)
|
||||
- [DOXA Timer – Instalace](doxa-timer/README_CZ.md#instalace)
|
||||
- [DOXA Habit – Instalace](doxa-habit/README_CZ.md#instalace)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Kankys
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,41 @@
|
||||
# DOXA Habit User Manual 🎯
|
||||
|
||||
This manual describes how to navigate and manage habits in the DOXA Habit tracker.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Start the application by running:
|
||||
```bash
|
||||
doxa-habit
|
||||
```
|
||||
Press `Enter` on the splash screen to enter the main interface.
|
||||
|
||||
## Interface States
|
||||
|
||||
1. **MAIN VIEW**: A list showing all your habits, their current streaks (e.g., `🔥3` for 3 consecutive days, or `❄️0`), and a mini calendar heatmap showing the last 7 days (`▓` for completed, `░` for missed).
|
||||
2. **DETAIL MODAL**: Displays description, frequency, stats for last 7/30 days, and a detailed heatmap.
|
||||
3. **STATS VIEW**: Table summarizing all your habits' statistics.
|
||||
4. **ADD / EDIT FORM**: Text inputs to add or modify habits.
|
||||
|
||||
## Controls
|
||||
|
||||
### Main Screen
|
||||
- `1`: Add a new habit.
|
||||
- `2`: Edit the selected habit.
|
||||
- `3`: Export completion history to `~/.doxa-habit/export/<habit_name>_history.csv`.
|
||||
- `4`: Delete selected habit.
|
||||
- `t`: Toggle today's status (Completed ✅ vs Not Completed ⬜).
|
||||
- `s`: Show statistics of all habits.
|
||||
- `l`: Switch UI language (English/Czech).
|
||||
- `/`: Filter habits by search term.
|
||||
- `Enter`: Open details modal.
|
||||
- `q` / `Esc`: Exit the program.
|
||||
|
||||
### Form Fields (Add/Edit)
|
||||
- `Tab`: Jump between the "Name" and "Description" fields.
|
||||
- `f`: Toggle habit frequency between "Daily" and "Weekly".
|
||||
- `Enter`: Save and create/modify the habit.
|
||||
- `q` / `Esc`: Go back to the main list without saving.
|
||||
|
||||
### Modal & Stats Views
|
||||
- `q` / `Esc`: Return to the main list view.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Uživatelský manuál DOXA Habit 🎯
|
||||
|
||||
Tento manuál popisuje použití aplikace DOXA Habit pro sledování návyků.
|
||||
|
||||
## Spuštění programu
|
||||
|
||||
Spusťte aplikaci příkazem:
|
||||
```bash
|
||||
doxa-habit
|
||||
```
|
||||
Na úvodní obrazovce stiskněte `Enter` pro přechod do hlavního rozhraní.
|
||||
|
||||
## Rozhraní aplikace
|
||||
|
||||
1. **SEZNAM NÁVYKŮ**: Zobrazuje všechny vaše návyky, aktuální streak (např. `🔥3` nebo `❄️0`) a mini heatmapu posledních 7 dní (`▓` splněno, `░` nesplněno).
|
||||
2. **DETAIL MODAL**: Zobrazí podrobnější popis návyku, frekvenci a úspěšnost v procentech za 7/30 dní.
|
||||
3. **STATISTIKY**: Tabulkový přehled všech vašich návyků a jejich úspěšnosti.
|
||||
4. **PŘIDAT / UPRAVIT**: Formulář pro zakládání či editaci návyků.
|
||||
|
||||
## Ovládání
|
||||
|
||||
### Hlavní obrazovka
|
||||
- `1`: Přidat nový návyk.
|
||||
- `2`: Upravit vybraný návyk.
|
||||
- `3`: Exportovat historii splnění do souboru `~/.doxa-habit/export/<název>_history.csv`.
|
||||
- `4`: Smazat vybraný návyk.
|
||||
- `t`: Označit návyk jako dnes splněný (střídá ✅ a ⬜).
|
||||
- `s`: Zobrazit celkové statistiky všech návyků.
|
||||
- `l`: Přepne jazyk rozhraní (čeština/angličtina).
|
||||
- `/`: Vyhledat/filtrovat návyky podle názvu.
|
||||
- `Enter`: Otevřít detail návyku v modálním okně.
|
||||
- `q` / `Esc`: Ukončit program.
|
||||
|
||||
### Formulář (Přidat / Upravit)
|
||||
- `Tab`: Přechází mezi poli "Název" a "Popis".
|
||||
- `f`: Přepíná frekvenci návyku (Denní / Týdenní).
|
||||
- `Enter`: Uloží data.
|
||||
- `q` / `Esc`: Zruší změny a vrátí se zpět na hlavní seznam.
|
||||
|
||||
### Okna detailu a statistik
|
||||
- `q` / `Esc`: Vrátí se na hlavní seznam návyků.
|
||||
@@ -0,0 +1,36 @@
|
||||
# DOXA Habit 🎯
|
||||
|
||||
**Minimalist habit tracker for the terminal with streak tracking**
|
||||
|
||||
DOXA Habit is a terminal-based tool to organize, record, and maintain daily and weekly habits. It calculates streaks and offers a visual 7-day mini heatmap of your progress.
|
||||
|
||||
## Features
|
||||
|
||||
- **Daily & Weekly Habits**: Setup name, description, and execution frequency.
|
||||
- **Completion toggle**: Mark today as completed or not.
|
||||
- **Streak & Heatmap**: Displays your current streak (🔥/❄️) and a mini calendar (▓/░) of the last 7 days.
|
||||
- **Overall Statistics**: Success rates over 7 and 30 days.
|
||||
- **Local JSON Storage**: Kept securely offline at `~/.doxa-habit/doxa_habit.json`.
|
||||
- **Export to CSV**: Export your habit completion log history.
|
||||
- **Bilingual**: CZ/EN language support via the `l` key.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cd doxa-habit
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
## Hotkeys
|
||||
|
||||
- `1` - Add Habit
|
||||
- `2` - Edit selected Habit
|
||||
- `3` - Export selected Habit history to CSV
|
||||
- `4` - Delete Habit
|
||||
- `t` - Toggle completed state for today
|
||||
- `s` - View Overall Statistics
|
||||
- `l` - Toggle language (EN/CS)
|
||||
- `/` - Search habits by name
|
||||
- `Enter` - View Habit Details Modal
|
||||
- `q` / `Esc` - Go back / Quit
|
||||
@@ -0,0 +1,36 @@
|
||||
# DOXA Habit 🎯
|
||||
|
||||
**Minimalistický tracker návyků pro terminál se sledováním streaku**
|
||||
|
||||
DOXA Habit je terminálová aplikace pro organizaci, zaznamenávání a udržování denních i týdenních návyků. Počítá aktuální sérii splnění (streak) a vizualizuje pokrok.
|
||||
|
||||
## Vlastnosti
|
||||
|
||||
- **Denní a týdenní návyky**: Volitelný název, popis a frekvence plnění.
|
||||
- **Označení splnění**: Rychlé označení dnešního splnění (toggle).
|
||||
- **Streak & Heatmap**: Zobrazení aktuální série (🔥/❄️) a 7denního mini kalendáře (▓/░).
|
||||
- **Celkové statistiky**: Úspěšnost plnění za posledních 7 a 30 dní.
|
||||
- **Local-first**: Data jsou uložena v offline JSON databázi `~/.doxa-habit/doxa_habit.json`.
|
||||
- **Export do CSV**: Uložení kompletní historie plnění návyku.
|
||||
- **Dvojjazyčnost**: Podpora češtiny a angličtiny přepínatelná klávesou `l`.
|
||||
|
||||
## Instalace
|
||||
|
||||
```bash
|
||||
cd doxa-habit
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
## Klávesové zkratky
|
||||
|
||||
- `1` - Přidat návyk
|
||||
- `2` - Upravit vybraný návyk
|
||||
- `3` - Exportovat historii návyku do CSV
|
||||
- `4` - Smazat návyk
|
||||
- `t` - Změnit stav splnění pro dnešní den
|
||||
- `s` - Zobrazit celkové statistiky
|
||||
- `l` - Přepnout jazyk (CZ/EN)
|
||||
- `/` - Vyhledávání návyků podle názvu
|
||||
- `Enter` - Zobrazit detail návyku v modálním okně
|
||||
- `q` / `Esc` - Zpět / Konec
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
module doxa-habit
|
||||
|
||||
go 1.26.5
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbles v1.0.0
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.9.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
|
||||
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
||||
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DOXA-HABIT Installation Script
|
||||
|
||||
# --- Nastavení ---
|
||||
|
||||
APP_NAME="doxa-habit"
|
||||
INSTALL_DIR="$HOME/.local/bin"
|
||||
CONFIG_DIR="$HOME/.doxa-habit"
|
||||
CONFIG_FILE="doxa_habit.json"
|
||||
REPO_URL="https://codeberg.org/Kankys/doxa.git"
|
||||
|
||||
# --- Funkce ---
|
||||
|
||||
error_exit() {
|
||||
echo -e "\033[1;31mERROR: $1\033[0m" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
success_message() {
|
||||
echo -e "\033[1;32mSUCCESS: $1\033[0m"
|
||||
}
|
||||
|
||||
info_message() {
|
||||
echo -e "\033[1;34mINFO: $1\033[0m"
|
||||
}
|
||||
|
||||
# --- Hlavní část skriptu ---
|
||||
|
||||
info_message "Kontrola Go instalace..."
|
||||
if ! command -v go &> /dev/null
|
||||
then
|
||||
error_exit "Go není nainstalováno. Prosím, nainstalujte Go před spuštěním tohoto skriptu."
|
||||
fi
|
||||
success_message "Go je nainstalováno."
|
||||
|
||||
info_message "Kontrola aktuálního adresáře..."
|
||||
if [ -f "main.go" ] && [ -f "go.mod" ] && grep -q "module doxa-habit" go.mod; then
|
||||
info_message "Jste již v adresáři $APP_NAME, používám místní verzi."
|
||||
else
|
||||
error_exit "Tento skript spouštějte z adresáře doxa-habit."
|
||||
fi
|
||||
|
||||
info_message "Stažení závislostí projektu..."
|
||||
if ! go mod tidy
|
||||
then
|
||||
error_exit "Nepodařilo se stáhnout závislosti Go."
|
||||
fi
|
||||
success_message "Závislosti projektu úspěšně staženy."
|
||||
|
||||
info_message "Kompilace aplikace..."
|
||||
APP_BINARY="$APP_NAME"
|
||||
if ! go build -o "$APP_BINARY" .
|
||||
then
|
||||
error_exit "Nepodařilo se zkompilovat aplikaci."
|
||||
fi
|
||||
success_message "Aplikace úspěšně zkompilována do './$APP_BINARY'."
|
||||
|
||||
info_message "Instalace aplikace..."
|
||||
|
||||
if ! mkdir -p "$INSTALL_DIR"
|
||||
then
|
||||
error_exit "Nepodařilo se vytvořit cílový adresář '$INSTALL_DIR'."
|
||||
fi
|
||||
|
||||
if ! cp "$APP_BINARY" "$INSTALL_DIR/"
|
||||
then
|
||||
error_exit "Nepodařilo se zkopírovat binárku do '$INSTALL_DIR'."
|
||||
fi
|
||||
|
||||
chmod +x "$INSTALL_DIR/$APP_NAME"
|
||||
success_message "Aplikace '$APP_NAME' úspěšně nainstalována do '$INSTALL_DIR'."
|
||||
|
||||
info_message "Nastavení konfiguračního adresáře..."
|
||||
|
||||
if ! mkdir -p "$CONFIG_DIR"
|
||||
then
|
||||
error_exit "Nepodařilo se vytvořit konfigurační adresář '$CONFIG_DIR'."
|
||||
fi
|
||||
|
||||
if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]
|
||||
then
|
||||
echo "[]" > "$CONFIG_DIR/$CONFIG_FILE"
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
error_exit "Nepodařilo se vytvořit konfigurační soubor '$CONFIG_DIR/$CONFIG_FILE'."
|
||||
fi
|
||||
success_message "Konfigurační soubor '$CONFIG_DIR/$CONFIG_FILE' vytvořen."
|
||||
else
|
||||
info_message "Konfigurační soubor '$CONFIG_DIR/$CONFIG_FILE' již existuje."
|
||||
fi
|
||||
|
||||
info_message "Instalace dokončena."
|
||||
success_message "Nyní můžete spustit aplikaci '$APP_NAME' z jakéhokoli místa v terminálu."
|
||||
echo ""
|
||||
echo -e "\033[1;33mPRO TIP:\nPokud \`$APP_NAME\` nefunguje z jakéhokoli místa, přidejte '$INSTALL_DIR' do vašeho PATH.\nexport PATH=\$PATH:$INSTALL_DIR\033[0m"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func main() {
|
||||
m := NewModel()
|
||||
|
||||
p := tea.NewProgram(m)
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Printf("Chyba při spuštění programu: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func getStoragePath() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "doxa_habit.json"
|
||||
}
|
||||
configDir := filepath.Join(home, ".doxa-habit")
|
||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(configDir, 0755)
|
||||
}
|
||||
return filepath.Join(configDir, "doxa_habit.json")
|
||||
}
|
||||
|
||||
func loadHabits() []Habit {
|
||||
path := getStoragePath()
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return []Habit{}
|
||||
}
|
||||
var habits []Habit
|
||||
if err := json.Unmarshal(file, &habits); err != nil {
|
||||
return []Habit{}
|
||||
}
|
||||
return habits
|
||||
}
|
||||
|
||||
func saveHabits(habits []Habit) {
|
||||
path := getStoragePath()
|
||||
data, err := json.MarshalIndent(habits, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("Chyba při ukládání dat:", err)
|
||||
return
|
||||
}
|
||||
os.WriteFile(path, data, 0644)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package main
|
||||
|
||||
type Translation struct {
|
||||
Title string
|
||||
MenuAdd string
|
||||
MenuEdit string
|
||||
MenuExport string
|
||||
MenuDelete string
|
||||
MenuToggle string
|
||||
MenuStats string
|
||||
MenuLang string
|
||||
MenuQuit string
|
||||
ListTitle string
|
||||
NoHabits string
|
||||
AddTitle string
|
||||
EditTitle string
|
||||
LabelName string
|
||||
LabelDesc string
|
||||
LabelFreq string
|
||||
PlaceholderName string
|
||||
PlaceholderDesc string
|
||||
FreqDaily string
|
||||
FreqWeekly string
|
||||
HelpForm string
|
||||
HelpList string
|
||||
HelpModal string
|
||||
StatusSaved string
|
||||
StatusEdited string
|
||||
StatusDeleted string
|
||||
StatusExported string
|
||||
ConfirmDelete string
|
||||
ConfirmYes string
|
||||
ConfirmNo string
|
||||
StatsTitle string
|
||||
LabelStreak string
|
||||
LabelSuccess7 string
|
||||
LabelSuccess30 string
|
||||
LabelCreated string
|
||||
LabelCompleted string
|
||||
LabelNotCompleted string
|
||||
ErrNameRequired string
|
||||
HelpStats string
|
||||
}
|
||||
|
||||
var Translations = map[string]Translation{
|
||||
"cs": {
|
||||
Title: "DOXA-HABIT 🦅",
|
||||
MenuAdd: "1: Přidat",
|
||||
MenuEdit: "2: Upravit",
|
||||
MenuExport: "3: Export CSV",
|
||||
MenuDelete: "4: Smazat",
|
||||
MenuToggle: "t: Splnit dnes",
|
||||
MenuStats: "s: Statistiky",
|
||||
MenuLang: "l: EN",
|
||||
MenuQuit: "q: Konec",
|
||||
ListTitle: "--- SEZNAM NÁVYKŮ ---",
|
||||
NoHabits: " (Žádné návyky...)",
|
||||
AddTitle: "PŘIDAT NÁVYK",
|
||||
EditTitle: "UPRAVIT NÁVYK",
|
||||
LabelName: "Název:",
|
||||
LabelDesc: "Popis:",
|
||||
LabelFreq: "Frekvence:",
|
||||
PlaceholderName: "Název (povinné)",
|
||||
PlaceholderDesc: "Popis (nepovinný)",
|
||||
FreqDaily: "Denní",
|
||||
FreqWeekly: "Týdenní",
|
||||
HelpForm: "[Tab] Další pole | [Enter] Uložit | [f] Změnit frekvenci | [q] Zrušit",
|
||||
HelpList: "↑/↓: Vybrat | Enter: Detail | t: Splnit | s: Statistiky | /: Hledat | q: Konec",
|
||||
HelpModal: "q: Zavřít | 2: Editovat | ↑/↓: Posun",
|
||||
StatusSaved: "Návyk byl uložen.",
|
||||
StatusEdited: "Změny byly uloženy.",
|
||||
StatusDeleted: "Návyk byl smazán.",
|
||||
StatusExported: "Statistiky exportovány do CSV.",
|
||||
ConfirmDelete: "Opravdu chcete smazat tento návyk?",
|
||||
ConfirmYes: "y: Ano",
|
||||
ConfirmNo: "n: Ne / Esc",
|
||||
StatsTitle: "CELKOVÉ STATISTIKY",
|
||||
LabelStreak: "Aktuální streak:",
|
||||
LabelSuccess7: "Úspěšnost 7 dní:",
|
||||
LabelSuccess30: "Úspěšnost 30 dní:",
|
||||
LabelCreated: "Vytvořeno:",
|
||||
LabelCompleted: "Splněno",
|
||||
LabelNotCompleted: "Nesplněno",
|
||||
ErrNameRequired: "Chyba: Název je povinný!",
|
||||
HelpStats: "q/Esc: Zpět",
|
||||
},
|
||||
"en": {
|
||||
Title: "DOXA-HABIT 🦅",
|
||||
MenuAdd: "1: Add",
|
||||
MenuEdit: "2: Edit",
|
||||
MenuExport: "3: Export CSV",
|
||||
MenuDelete: "4: Delete",
|
||||
MenuToggle: "t: Complete today",
|
||||
MenuStats: "s: Stats",
|
||||
MenuLang: "l: CS",
|
||||
MenuQuit: "q: Quit",
|
||||
ListTitle: "--- HABITS LIST ---",
|
||||
NoHabits: " (No habits...)",
|
||||
AddTitle: "ADD HABIT",
|
||||
EditTitle: "EDIT HABIT",
|
||||
LabelName: "Name:",
|
||||
LabelDesc: "Description:",
|
||||
LabelFreq: "Frequency:",
|
||||
PlaceholderName: "Name (required)",
|
||||
PlaceholderDesc: "Description (optional)",
|
||||
FreqDaily: "Daily",
|
||||
FreqWeekly: "Weekly",
|
||||
HelpForm: "[Tab] Next field | [Enter] Save | [f] Change frequency | [q] Cancel",
|
||||
HelpList: "↑/↓: Select | Enter: Detail | t: Toggle Done | s: Stats | /: Search | q: Quit",
|
||||
HelpModal: "q: Close | 2: Edit | ↑/↓: Scroll",
|
||||
StatusSaved: "Habit saved.",
|
||||
StatusEdited: "Changes saved.",
|
||||
StatusDeleted: "Habit deleted.",
|
||||
StatusExported: "Stats exported to CSV.",
|
||||
ConfirmDelete: "Are you sure you want to delete this habit?",
|
||||
ConfirmYes: "y: Yes",
|
||||
ConfirmNo: "n: No / Esc",
|
||||
StatsTitle: "OVERALL STATISTICS",
|
||||
LabelStreak: "Current streak:",
|
||||
LabelSuccess7: "Success 7 days:",
|
||||
LabelSuccess30: "Success 30 days:",
|
||||
LabelCreated: "Created:",
|
||||
LabelCompleted: "Done",
|
||||
LabelNotCompleted: "Not done",
|
||||
ErrNameRequired: "Error: Name is required!",
|
||||
HelpStats: "q/Esc: Back",
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,846 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// ─── Styles ──────────────────────────────────────────────────────────────────
|
||||
|
||||
var (
|
||||
appStyle = lipgloss.NewStyle().
|
||||
Padding(1, 2).
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("62"))
|
||||
|
||||
titleStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#FAFAFA")).
|
||||
Background(lipgloss.Color("#7D56F4")).
|
||||
Padding(0, 1).
|
||||
MarginBottom(1)
|
||||
|
||||
selectedStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#7D56F4")).
|
||||
Bold(true)
|
||||
|
||||
statusStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("205"))
|
||||
|
||||
completedStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#2ECC71"))
|
||||
|
||||
streakStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#F39C12"))
|
||||
|
||||
labelStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("241"))
|
||||
|
||||
valueStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#FAFAFA"))
|
||||
|
||||
focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
|
||||
cursorStyle = focusedStyle.Copy()
|
||||
|
||||
confirmBoxStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("205")).
|
||||
Padding(1, 2).Align(lipgloss.Center)
|
||||
|
||||
helpStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("241")).
|
||||
MarginTop(1)
|
||||
)
|
||||
|
||||
// ─── Data Structure ──────────────────────────────────────────────────────────
|
||||
|
||||
type Habit struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Frequency string `json:"frequency"` // "daily" or "weekly"
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ModifiedAt time.Time `json:"modified_at"`
|
||||
CompletedDates []string `json:"completed_dates"` // format "2006-01-02"
|
||||
}
|
||||
|
||||
type AppState int
|
||||
|
||||
const (
|
||||
StateStartup AppState = iota
|
||||
StateViewHabits
|
||||
StateViewHabitModal
|
||||
StateAddHabit
|
||||
StateEditHabit
|
||||
StateStats
|
||||
StateConfirmDelete
|
||||
StateSearch
|
||||
)
|
||||
|
||||
// ─── Model ────────────────────────────────────────────────────────────────────
|
||||
|
||||
type model struct {
|
||||
habits []Habit
|
||||
filteredHabits []Habit
|
||||
cursor int
|
||||
state AppState
|
||||
filterText string
|
||||
textInputs []textinput.Model // 0=name, 1=desc
|
||||
searchInput textinput.Model
|
||||
viewport viewport.Model
|
||||
ready bool
|
||||
focusIndex int
|
||||
selectedHabitID int64
|
||||
statusMsg string
|
||||
lang string
|
||||
width int
|
||||
height int
|
||||
editFrequency string // "daily" or "weekly"
|
||||
}
|
||||
|
||||
func NewModel() model {
|
||||
inputs := make([]textinput.Model, 2)
|
||||
for i := range inputs {
|
||||
t := textinput.New()
|
||||
t.Cursor.Style = cursorStyle
|
||||
inputs[i] = t
|
||||
}
|
||||
inputs[0].Focus()
|
||||
inputs[0].PromptStyle = focusedStyle
|
||||
inputs[0].TextStyle = focusedStyle
|
||||
|
||||
si := textinput.New()
|
||||
si.Prompt = "🔍 "
|
||||
si.Placeholder = "Hledat..."
|
||||
|
||||
m := model{
|
||||
habits: loadHabits(),
|
||||
cursor: 0,
|
||||
state: StateStartup,
|
||||
textInputs: inputs,
|
||||
searchInput: si,
|
||||
focusIndex: 0,
|
||||
lang: "cs",
|
||||
editFrequency: "daily",
|
||||
}
|
||||
m.applyFilter()
|
||||
m.updatePlaceholders()
|
||||
return m
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return tea.Batch(textinput.Blink, tea.EnterAltScreen)
|
||||
}
|
||||
|
||||
func (m *model) applyFilter() {
|
||||
if m.filterText == "" {
|
||||
m.filteredHabits = m.habits
|
||||
} else {
|
||||
m.filteredHabits = []Habit{}
|
||||
query := strings.ToLower(m.filterText)
|
||||
for _, h := range m.habits {
|
||||
if strings.Contains(strings.ToLower(h.Name), query) ||
|
||||
strings.Contains(strings.ToLower(h.Description), query) {
|
||||
m.filteredHabits = append(m.filteredHabits, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
if m.cursor >= len(m.filteredHabits) {
|
||||
m.cursor = 0
|
||||
if len(m.filteredHabits) > 0 {
|
||||
m.cursor = len(m.filteredHabits) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) updatePlaceholders() {
|
||||
tr := Translations[m.lang]
|
||||
m.textInputs[0].Placeholder = tr.PlaceholderName
|
||||
m.textInputs[1].Placeholder = tr.PlaceholderDesc
|
||||
m.searchInput.Placeholder = "..."
|
||||
}
|
||||
|
||||
// ─── Update ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
tr := Translations[m.lang]
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
|
||||
headerHeight := 8
|
||||
footerHeight := 3
|
||||
vpHeight := msg.Height - headerHeight - footerHeight
|
||||
if vpHeight < 1 {
|
||||
vpHeight = 1
|
||||
}
|
||||
|
||||
if !m.ready {
|
||||
m.viewport = viewport.New(msg.Width-6, vpHeight)
|
||||
m.ready = true
|
||||
} else {
|
||||
m.viewport.Width = msg.Width - 6
|
||||
m.viewport.Height = vpHeight
|
||||
}
|
||||
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c":
|
||||
return m, tea.Quit
|
||||
|
||||
case "esc":
|
||||
if m.state == StateConfirmDelete {
|
||||
m.state = StateViewHabits
|
||||
m.statusMsg = ""
|
||||
return m, nil
|
||||
}
|
||||
if m.state == StateViewHabits {
|
||||
if m.filterText != "" {
|
||||
m.filterText = ""
|
||||
m.applyFilter()
|
||||
return m, nil
|
||||
}
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.resetToView()
|
||||
return m, nil
|
||||
|
||||
case "q":
|
||||
if m.state == StateStartup {
|
||||
return m, tea.Quit
|
||||
}
|
||||
if m.state == StateConfirmDelete {
|
||||
m.state = StateViewHabits
|
||||
m.statusMsg = ""
|
||||
return m, nil
|
||||
}
|
||||
if m.state == StateViewHabitModal {
|
||||
m.state = StateViewHabits
|
||||
return m, nil
|
||||
}
|
||||
if m.state == StateViewHabits {
|
||||
if m.filterText != "" {
|
||||
m.filterText = ""
|
||||
m.applyFilter()
|
||||
return m, nil
|
||||
}
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.resetToView()
|
||||
return m, nil
|
||||
|
||||
case "l":
|
||||
if m.state == StateViewHabits {
|
||||
if m.lang == "cs" {
|
||||
m.lang = "en"
|
||||
} else {
|
||||
m.lang = "cs"
|
||||
}
|
||||
m.updatePlaceholders()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
case "/":
|
||||
if m.state == StateViewHabits {
|
||||
m.state = StateSearch
|
||||
m.searchInput.Focus()
|
||||
m.searchInput.SetValue("")
|
||||
return m, nil
|
||||
}
|
||||
|
||||
case "tab", "shift+tab":
|
||||
if m.state == StateAddHabit || m.state == StateEditHabit {
|
||||
if msg.String() == "shift+tab" {
|
||||
m.focusIndex--
|
||||
} else {
|
||||
m.focusIndex++
|
||||
}
|
||||
if m.focusIndex > 1 {
|
||||
m.focusIndex = 0
|
||||
}
|
||||
if m.focusIndex < 0 {
|
||||
m.focusIndex = 1
|
||||
}
|
||||
return m.updateFocus()
|
||||
}
|
||||
|
||||
case "up", "k":
|
||||
if m.state == StateViewHabits && m.cursor > 0 {
|
||||
m.cursor--
|
||||
m.viewport.LineUp(1)
|
||||
}
|
||||
if m.state == StateViewHabitModal {
|
||||
m.viewport.LineUp(1)
|
||||
}
|
||||
|
||||
case "down", "j":
|
||||
if m.state == StateViewHabits {
|
||||
max := len(m.filteredHabits)
|
||||
if m.cursor < max-1 {
|
||||
m.cursor++
|
||||
m.viewport.LineDown(1)
|
||||
}
|
||||
}
|
||||
if m.state == StateViewHabitModal {
|
||||
m.viewport.LineDown(1)
|
||||
}
|
||||
|
||||
case "f":
|
||||
if m.state == StateAddHabit || m.state == StateEditHabit {
|
||||
if m.editFrequency == "daily" {
|
||||
m.editFrequency = "weekly"
|
||||
} else {
|
||||
m.editFrequency = "daily"
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
case "enter":
|
||||
switch m.state {
|
||||
case StateStartup:
|
||||
m.state = StateViewHabits
|
||||
case StateViewHabits:
|
||||
if len(m.filteredHabits) > 0 {
|
||||
m.state = StateViewHabitModal
|
||||
m.selectedHabitID = m.filteredHabits[m.cursor].ID
|
||||
m.updateModalContent()
|
||||
}
|
||||
case StateSearch:
|
||||
m.filterText = m.searchInput.Value()
|
||||
m.applyFilter()
|
||||
m.state = StateViewHabits
|
||||
case StateAddHabit:
|
||||
m.handleAddHabit()
|
||||
case StateEditHabit:
|
||||
m.handleEditHabit()
|
||||
case StateConfirmDelete:
|
||||
if m.focusIndex == 0 {
|
||||
m.handleDeleteHabit()
|
||||
m.state = StateViewHabits
|
||||
m.statusMsg = tr.StatusDeleted
|
||||
return m, nil
|
||||
} else {
|
||||
m.state = StateViewHabits
|
||||
m.statusMsg = ""
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
case "1":
|
||||
if m.state == StateViewHabits {
|
||||
m.state = StateAddHabit
|
||||
m.resetInputs()
|
||||
m.editFrequency = "daily"
|
||||
return m, nil
|
||||
}
|
||||
case "2":
|
||||
if m.state == StateViewHabits && len(m.filteredHabits) > 0 {
|
||||
m.state = StateEditHabit
|
||||
m.selectedHabitID = m.filteredHabits[m.cursor].ID
|
||||
m.populateInputs(m.filteredHabits[m.cursor])
|
||||
return m, nil
|
||||
}
|
||||
if m.state == StateViewHabitModal {
|
||||
for _, h := range m.habits {
|
||||
if h.ID == m.selectedHabitID {
|
||||
m.state = StateEditHabit
|
||||
m.populateInputs(h)
|
||||
break
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
case "3":
|
||||
if m.state == StateViewHabits && len(m.filteredHabits) > 0 {
|
||||
m.exportCSV(m.filteredHabits[m.cursor])
|
||||
}
|
||||
case "4":
|
||||
if m.state == StateViewHabits && len(m.filteredHabits) > 0 {
|
||||
m.state = StateConfirmDelete
|
||||
m.selectedHabitID = m.filteredHabits[m.cursor].ID
|
||||
m.focusIndex = 0
|
||||
}
|
||||
case "t":
|
||||
if m.state == StateViewHabits && len(m.filteredHabits) > 0 {
|
||||
m.toggleHabitComplete(m.filteredHabits[m.cursor].ID)
|
||||
}
|
||||
case "s":
|
||||
if m.state == StateViewHabits {
|
||||
m.state = StateStats
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.state == StateSearch {
|
||||
m.searchInput, cmd = m.searchInput.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
if m.state == StateAddHabit || m.state == StateEditHabit {
|
||||
m.textInputs[m.focusIndex], cmd = m.textInputs[m.focusIndex].Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
if m.state == StateConfirmDelete {
|
||||
if msg, ok := msg.(tea.KeyMsg); ok {
|
||||
switch msg.String() {
|
||||
case "y":
|
||||
m.handleDeleteHabit()
|
||||
m.state = StateViewHabits
|
||||
m.statusMsg = tr.StatusDeleted
|
||||
return m, nil
|
||||
case "n", "esc":
|
||||
m.state = StateViewHabits
|
||||
m.statusMsg = ""
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *model) updateFocus() (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
for i := range m.textInputs {
|
||||
m.textInputs[i].Blur()
|
||||
m.textInputs[i].PromptStyle = lipgloss.NewStyle()
|
||||
}
|
||||
cmds = append(cmds, m.textInputs[m.focusIndex].Focus())
|
||||
m.textInputs[m.focusIndex].PromptStyle = focusedStyle
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *model) resetToView() {
|
||||
m.state = StateViewHabits
|
||||
m.statusMsg = ""
|
||||
m.filterText = ""
|
||||
m.applyFilter()
|
||||
m.resetInputs()
|
||||
}
|
||||
|
||||
func (m *model) resetInputs() {
|
||||
for i := range m.textInputs {
|
||||
m.textInputs[i].SetValue("")
|
||||
m.textInputs[i].Blur()
|
||||
}
|
||||
m.focusIndex = 0
|
||||
m.updateFocus()
|
||||
}
|
||||
|
||||
func (m *model) populateInputs(h Habit) {
|
||||
m.textInputs[0].SetValue(h.Name)
|
||||
m.textInputs[1].SetValue(h.Description)
|
||||
m.editFrequency = h.Frequency
|
||||
m.focusIndex = 0
|
||||
m.updateFocus()
|
||||
}
|
||||
|
||||
func (m *model) toggleHabitComplete(id int64) {
|
||||
today := time.Now().Format("2006-01-02")
|
||||
for i, h := range m.habits {
|
||||
if h.ID == id {
|
||||
found := -1
|
||||
for idx, d := range h.CompletedDates {
|
||||
if d == today {
|
||||
found = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
if found != -1 {
|
||||
// Remove completion
|
||||
m.habits[i].CompletedDates = append(h.CompletedDates[:found], h.CompletedDates[found+1:]...)
|
||||
} else {
|
||||
// Complete
|
||||
m.habits[i].CompletedDates = append(h.CompletedDates, today)
|
||||
}
|
||||
m.habits[i].ModifiedAt = time.Now()
|
||||
break
|
||||
}
|
||||
}
|
||||
saveHabits(m.habits)
|
||||
m.applyFilter()
|
||||
}
|
||||
|
||||
func (m *model) handleAddHabit() {
|
||||
tr := Translations[m.lang]
|
||||
name := m.textInputs[0].Value()
|
||||
desc := m.textInputs[1].Value()
|
||||
if name == "" {
|
||||
m.statusMsg = tr.ErrNameRequired
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
m.habits = append(m.habits, Habit{
|
||||
ID: now.UnixMilli(),
|
||||
Name: name,
|
||||
Description: desc,
|
||||
Frequency: m.editFrequency,
|
||||
CreatedAt: now,
|
||||
ModifiedAt: now,
|
||||
CompletedDates: []string{},
|
||||
})
|
||||
saveHabits(m.habits)
|
||||
m.applyFilter()
|
||||
m.statusMsg = tr.StatusSaved
|
||||
m.state = StateViewHabits
|
||||
}
|
||||
|
||||
func (m *model) handleEditHabit() {
|
||||
tr := Translations[m.lang]
|
||||
name := m.textInputs[0].Value()
|
||||
desc := m.textInputs[1].Value()
|
||||
if name == "" {
|
||||
m.statusMsg = tr.ErrNameRequired
|
||||
return
|
||||
}
|
||||
for i, h := range m.habits {
|
||||
if h.ID == m.selectedHabitID {
|
||||
m.habits[i].Name = name
|
||||
m.habits[i].Description = desc
|
||||
m.habits[i].Frequency = m.editFrequency
|
||||
m.habits[i].ModifiedAt = time.Now()
|
||||
break
|
||||
}
|
||||
}
|
||||
saveHabits(m.habits)
|
||||
m.applyFilter()
|
||||
m.statusMsg = tr.StatusEdited
|
||||
m.state = StateViewHabits
|
||||
}
|
||||
|
||||
func (m *model) handleDeleteHabit() {
|
||||
found := -1
|
||||
for i, h := range m.habits {
|
||||
if h.ID == m.selectedHabitID {
|
||||
found = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if found != -1 {
|
||||
m.habits = append(m.habits[:found], m.habits[found+1:]...)
|
||||
saveHabits(m.habits)
|
||||
}
|
||||
m.applyFilter()
|
||||
}
|
||||
|
||||
func (m *model) updateModalContent() {
|
||||
tr := Translations[m.lang]
|
||||
var h Habit
|
||||
for _, habit := range m.habits {
|
||||
if habit.ID == m.selectedHabitID {
|
||||
h = habit
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
freqLabel := tr.FreqDaily
|
||||
if h.Frequency == "weekly" {
|
||||
freqLabel = tr.FreqWeekly
|
||||
}
|
||||
|
||||
streak := getCurrentStreak(h)
|
||||
success7 := getSuccessRate(h, 7)
|
||||
success30 := getSuccessRate(h, 30)
|
||||
|
||||
modalContent := fmt.Sprintf("%s\n---\n[%d] %s\n\n%s\n\n%s %s\n%s %d\n%s %.0f%%\n%s %.0f%%\n%s %s\n\n Heatmap (Last 7 Days):\n %s",
|
||||
tr.HelpModal,
|
||||
h.ID, h.Name, h.Description,
|
||||
tr.LabelFreq, freqLabel,
|
||||
tr.LabelStreak, streak,
|
||||
tr.LabelSuccess7, success7,
|
||||
tr.LabelSuccess30, success30,
|
||||
tr.LabelCreated, h.CreatedAt.Format("2006-01-02 15:04"),
|
||||
renderMiniCalendar(h),
|
||||
)
|
||||
|
||||
m.viewport.Width = m.width - 20
|
||||
m.viewport.Height = m.height - 10
|
||||
m.viewport.SetContent(modalContent)
|
||||
}
|
||||
|
||||
func (m *model) exportCSV(h Habit) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
m.statusMsg = "Chyba zjištění domovského adresáře"
|
||||
return
|
||||
}
|
||||
exportDir := filepath.Join(home, ".doxa-habit", "export")
|
||||
os.MkdirAll(exportDir, 0755)
|
||||
|
||||
safeName := strings.ReplaceAll(h.Name, " ", "_")
|
||||
filename := filepath.Join(exportDir, fmt.Sprintf("%s_history.csv", safeName))
|
||||
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
m.statusMsg = "Chyba při zápisu souboru"
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
file.WriteString("Date,Completed\n")
|
||||
// Write all dates
|
||||
for _, d := range h.CompletedDates {
|
||||
file.WriteString(fmt.Sprintf("%s,1\n", d))
|
||||
}
|
||||
m.statusMsg = fmt.Sprintf("Exportováno do %s", filename)
|
||||
}
|
||||
|
||||
// ─── Helper Functions ────────────────────────────────────────────────────────
|
||||
|
||||
func isCompletedToday(h Habit) bool {
|
||||
today := time.Now().Format("2006-01-02")
|
||||
for _, d := range h.CompletedDates {
|
||||
if d == today {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getCurrentStreak(h Habit) int {
|
||||
if len(h.CompletedDates) == 0 {
|
||||
return 0
|
||||
}
|
||||
streak := 0
|
||||
checkDate := time.Now()
|
||||
// Parse completed dates into a map for fast lookup
|
||||
completedMap := make(map[string]bool)
|
||||
for _, d := range h.CompletedDates {
|
||||
completedMap[d] = true
|
||||
}
|
||||
|
||||
// If not completed today, check if it was completed yesterday to maintain streak
|
||||
todayStr := checkDate.Format("2006-01-02")
|
||||
if !completedMap[todayStr] {
|
||||
checkDate = checkDate.AddDate(0, 0, -1)
|
||||
}
|
||||
|
||||
for {
|
||||
dateStr := checkDate.Format("2006-01-02")
|
||||
if completedMap[dateStr] {
|
||||
streak++
|
||||
checkDate = checkDate.AddDate(0, 0, -1)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return streak
|
||||
}
|
||||
|
||||
func getSuccessRate(h Habit, days int) float64 {
|
||||
if len(h.CompletedDates) == 0 {
|
||||
return 0
|
||||
}
|
||||
count := 0
|
||||
now := time.Now()
|
||||
completedMap := make(map[string]bool)
|
||||
for _, d := range h.CompletedDates {
|
||||
completedMap[d] = true
|
||||
}
|
||||
|
||||
for i := 0; i < days; i++ {
|
||||
dateStr := now.AddDate(0, 0, -i).Format("2006-01-02")
|
||||
if completedMap[dateStr] {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return (float64(count) / float64(days)) * 100
|
||||
}
|
||||
|
||||
func renderProgressBar(percent float64, width int) string {
|
||||
filled := int(percent / 100 * float64(width))
|
||||
if filled > width {
|
||||
filled = width
|
||||
}
|
||||
if filled < 0 {
|
||||
filled = 0
|
||||
}
|
||||
return "[" + strings.Repeat("█", filled) + strings.Repeat("░", width-filled) + "]"
|
||||
}
|
||||
|
||||
func renderMiniCalendar(h Habit) string {
|
||||
var sb strings.Builder
|
||||
now := time.Now()
|
||||
completedMap := make(map[string]bool)
|
||||
for _, d := range h.CompletedDates {
|
||||
completedMap[d] = true
|
||||
}
|
||||
|
||||
// Last 7 days in order (oldest to newest)
|
||||
for i := 6; i >= 0; i-- {
|
||||
day := now.AddDate(0, 0, -i)
|
||||
dateStr := day.Format("2006-01-02")
|
||||
if completedMap[dateStr] {
|
||||
sb.WriteString("▓")
|
||||
} else {
|
||||
sb.WriteString("░")
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// ─── View ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
func (m model) View() string {
|
||||
tr := Translations[m.lang]
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(titleStyle.Render(tr.Title) + "\n")
|
||||
|
||||
if m.statusMsg != "" {
|
||||
b.WriteString(statusStyle.Render(m.statusMsg) + "\n\n")
|
||||
}
|
||||
|
||||
switch m.state {
|
||||
case StateStartup:
|
||||
eagle := `
|
||||
_
|
||||
( ` + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("`") + `
|
||||
\ ` + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("\\") + `
|
||||
/ / _
|
||||
__ / / ( \
|
||||
( "---" / / /
|
||||
\ / / /
|
||||
\ /-----' /
|
||||
\ / /
|
||||
\ / /
|
||||
'-------'
|
||||
`
|
||||
b.WriteString(lipgloss.Place(m.width-12, 14, lipgloss.Center, lipgloss.Center,
|
||||
eagle+"\n"+
|
||||
lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#7D56F4")).Render("DOXA-HABIT 🦅")+"\n\n"+
|
||||
"Local-First Habit Tracker\n\n"+
|
||||
"Enter: Start | q: Quit\n\n"+
|
||||
"License: MIT | © 2026 Kankys"))
|
||||
|
||||
case StateSearch:
|
||||
b.WriteString("HLEDAT: \n" + m.searchInput.View() + "\n\n" + tr.HelpForm)
|
||||
|
||||
case StateViewHabits:
|
||||
filterInfo := ""
|
||||
if m.filterText != "" {
|
||||
filterInfo = fmt.Sprintf(" [Filtr: %s]", m.filterText)
|
||||
}
|
||||
|
||||
var listBuilder strings.Builder
|
||||
if len(m.filteredHabits) == 0 {
|
||||
listBuilder.WriteString(tr.NoHabits + "\n")
|
||||
} else {
|
||||
for i, h := range m.filteredHabits {
|
||||
status := "⬜"
|
||||
if isCompletedToday(h) {
|
||||
status = "✅"
|
||||
}
|
||||
streak := getCurrentStreak(h)
|
||||
streakStr := fmt.Sprintf("🔥%d", streak)
|
||||
if streak == 0 {
|
||||
streakStr = "❄️0"
|
||||
}
|
||||
|
||||
cursor := " "
|
||||
if m.cursor == i {
|
||||
cursor = "> "
|
||||
}
|
||||
|
||||
line := fmt.Sprintf("%s%s %-20s %-5s %s",
|
||||
cursor,
|
||||
status,
|
||||
h.Name,
|
||||
streakStyle.Render(streakStr),
|
||||
renderMiniCalendar(h),
|
||||
)
|
||||
if m.cursor == i {
|
||||
listBuilder.WriteString(selectedStyle.Render(line) + "\n")
|
||||
} else {
|
||||
listBuilder.WriteString(line + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.ready {
|
||||
m.viewport.SetContent(listBuilder.String())
|
||||
}
|
||||
|
||||
b.WriteString(fmt.Sprintf("%s | %s | %s | %s | %s | %s | %s\n\n",
|
||||
tr.MenuAdd, tr.MenuEdit, tr.MenuExport, tr.MenuDelete, tr.MenuToggle, tr.MenuStats, tr.MenuLang))
|
||||
b.WriteString(tr.ListTitle + filterInfo + "\n")
|
||||
|
||||
if m.ready {
|
||||
b.WriteString(m.viewport.View() + "\n")
|
||||
} else {
|
||||
b.WriteString(listBuilder.String())
|
||||
}
|
||||
b.WriteString("\n" + tr.HelpList)
|
||||
|
||||
case StateAddHabit, StateEditHabit:
|
||||
title := tr.AddTitle
|
||||
if m.state == StateEditHabit {
|
||||
title = fmt.Sprintf("%s (%d)", tr.EditTitle, m.selectedHabitID)
|
||||
}
|
||||
b.WriteString(title + "\n\n")
|
||||
|
||||
b.WriteString(fmt.Sprintf("%-12s %s\n", tr.LabelName, m.textInputs[0].View()))
|
||||
b.WriteString(fmt.Sprintf("%-12s %s\n", tr.LabelDesc, m.textInputs[1].View()))
|
||||
|
||||
freqLabel := tr.FreqDaily
|
||||
if m.editFrequency == "weekly" {
|
||||
freqLabel = tr.FreqWeekly
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("%-12s %s\n", tr.LabelFreq, valueStyle.Render(freqLabel)))
|
||||
b.WriteString("\n" + tr.HelpForm)
|
||||
|
||||
case StateViewHabitModal:
|
||||
if m.ready {
|
||||
b.WriteString(confirmBoxStyle.Render(m.viewport.View()))
|
||||
} else {
|
||||
b.WriteString(confirmBoxStyle.Render("Načítání..."))
|
||||
}
|
||||
|
||||
case StateConfirmDelete:
|
||||
var hToDelete Habit
|
||||
for _, h := range m.habits {
|
||||
if h.ID == m.selectedHabitID {
|
||||
hToDelete = h
|
||||
break
|
||||
}
|
||||
}
|
||||
confirmText := fmt.Sprintf("%s\n\n[%d] %s\n\n%s | %s",
|
||||
tr.ConfirmDelete,
|
||||
hToDelete.ID,
|
||||
hToDelete.Name,
|
||||
tr.ConfirmYes, tr.ConfirmNo,
|
||||
)
|
||||
b.WriteString(confirmBoxStyle.Render(confirmText))
|
||||
|
||||
case StateStats:
|
||||
b.WriteString(lipgloss.NewStyle().Bold(true).Render(tr.StatsTitle) + "\n\n")
|
||||
b.WriteString(fmt.Sprintf(" %-25s %-8s %-12s\n", "Návyk", "Streak", "Úspěšnost (30 dní)"))
|
||||
b.WriteString(" " + strings.Repeat("-", 50) + "\n")
|
||||
for _, h := range m.habits {
|
||||
streak := getCurrentStreak(h)
|
||||
success30 := getSuccessRate(h, 30)
|
||||
b.WriteString(fmt.Sprintf(" %-25s 🔥%-7d %.0f%%\n", h.Name, streak, success30))
|
||||
}
|
||||
b.WriteString("\n" + helpStyle.Render(tr.HelpStats))
|
||||
}
|
||||
|
||||
content := b.String()
|
||||
if m.width > 0 && m.height > 0 {
|
||||
return appStyle.Width(m.width - 6).Height(m.height - 4).Render(content)
|
||||
}
|
||||
return appStyle.Render(content)
|
||||
}
|
||||
@@ -6,11 +6,11 @@
|
||||
"nav.about": "O projektu",
|
||||
"nav.themeToggle": "Přepnout motiv",
|
||||
"footer.copyright": "Doxa Notes © 2026 | Local-First & Svoboda 🦅",
|
||||
"index.pageTitle": "DOXA Notes — Local-First poznámky a minimalistický Markdown editor",
|
||||
"index.pageTitle": "DOXA Notes — Local-First poznámky and minimalistický Markdown editor",
|
||||
"index.pageDescription": "DOXA Notes je minimalistická, local‑first poznámková aplikace s podporou Markdownu a otevřeným formátem JSON. Absolutní soukromí pro vaše myšlenky.",
|
||||
"index.ogTitle": "DOXA Notes — Local-First poznámky a minimalistický Markdown editor",
|
||||
"index.ogTitle": "DOXA Notes — Local-First poznámky and minimalistický Markdown editor",
|
||||
"index.ogDescription": "DOXA Notes je minimalistická, local‑first poznámková aplikace založená na otevřeném JSON formátu. Data zůstávají plně pod vaší kontrolou.",
|
||||
"index.twitterTitle": "DOXA Notes — Local-First poznámky a minimalistický Markdown editor",
|
||||
"index.twitterTitle": "DOXA Notes — Local-First poznámky and minimalistický Markdown editor",
|
||||
"index.twitterDescription": "DOXA Notes je minimalistická, local‑first poznámková aplikace založená na otevřeném JSON formátu.",
|
||||
"index.mainTitle": "DOXA Notes",
|
||||
"index.subtitle": "Local-First Notes & Sovereignty",
|
||||
@@ -81,14 +81,14 @@
|
||||
"manifest.quote.author": "— Stoický odkaz pro svobodné myslitele",
|
||||
"nav.doxa": "Kolekce DOXA",
|
||||
"doxa.pageTitle": "Kolekce DOXA — Celý přehled projektů",
|
||||
"doxa.pageDescription": "DOXA je sbírka čtyř minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv a práci s textem z příkazové řádky. Všechny projekty sdílejí společnou filozofii: úplná nezávislost, soukromí a kontrola nad vašimi daty.",
|
||||
"doxa.pageDescription": "DOXA je sbírka šesti minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv, správu úkolů, Pomodoro timer a sledování návyků. Žádný cloud, žádné sledování – vaše data patří pouze vám.",
|
||||
"doxa.ogTitle": "Kolekce DOXA — Celý přehled projektů",
|
||||
"doxa.ogDescription": "Přehled všech projektů DOXA: Notes, CLI, Secret a TODO. Úplná lokalita, otevřené formáty a digitální suverenita.",
|
||||
"doxa.ogDescription": "Přehled všech projektů DOXA: Notes, CLI, Secret, TODO, Timer a Habit. Úplná lokalita, otevřené formáty a digitální suverenita.",
|
||||
"doxa.twitterTitle": "Kolekce DOXA",
|
||||
"doxa.twitterDescription": "Přehled všech projektů DOXA pro správu poznámek, šifrování a organizaci úkolů.",
|
||||
"doxa.twitterDescription": "Přehled všech projektů DOXA pro správu poznámek, šifrování, úkolů, Pomodoro časovače a návyků.",
|
||||
"doxa.mainTitle": "Kolekce DOXA",
|
||||
"doxa.subtitle": "Úplná nezávislost, soukromí a kontrola nad daty",
|
||||
"doxa.intro": "DOXA je sbírka čtyř minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv a práci s textem z příkazové řádky. Všechny projekty sdílejí společnou filozofii: úplná nezávislost, soukromí a kontrola nad vašimi daty. Žádná cloudová služba, žádné sledování, žádné externí závislosti. Vaše data patří pouze vám.",
|
||||
"doxa.intro": "DOXA je sbírka šesti minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv, správu úkolů, Pomodoro timer a sledování návyků. Všechny projekty sdílejí společnou filozofii: úplná nezávislost, soukromí a kontrola nad vašimi daty. Žádná cloudová služba, žádné sledování, žádné externí závislosti. Vaše data patří pouze vám.",
|
||||
"doxa.philosophy": "Základní myšlenka zůstává nekompromisní: Radikální Local-First přístup, čistá data v JSON formátu a absolutní digitální soukromí bez korporátního cloudu a API.",
|
||||
"doxa.projects.title": "Projekty v kolekci DOXA",
|
||||
"doxa.notes.title": "📝 DOXA Notes",
|
||||
@@ -121,28 +121,49 @@
|
||||
"doxa.todo.philosophy": "Filozofie: Lokální správa úkolů. Vaše úkoly jsou uloženy lokálně v JSON formátu.",
|
||||
"doxa.todo.dataLocation": "Data uložena v ~/.doxa-todo/doxa_todo.json.",
|
||||
"doxa.todo.link": "Přečtěte si podrobnější dokumentaci",
|
||||
"doxa.timer.title": "⏱️ DOXA Timer",
|
||||
"doxa.timer.description": "Pomodoro timer pro terminál se statistikami produktivity",
|
||||
"doxa.timer.philosophy": "Local-first produktivita. Žádné cloudové aplikace, žádné předplatné – jen čistý fokus na vaši práci. Data uložena v ~/.doxa-timer/.",
|
||||
"doxa.timer.features": "Funkce: Pomodoro technika (25/5/15 min) · Live countdown · Konfigurovatelné délky sessions · Statistiky pomodorů · Dvoujazyčné rozhraní (CZ/EN)",
|
||||
"doxa.timer.tech": "Technologie: Go (Golang), Bubble Tea, Lip Gloss",
|
||||
"doxa.timer.platforms": "Platformy: Linux, FreeBSD",
|
||||
"doxa.timer.link": "Přečtěte si podrobnější dokumentaci",
|
||||
"doxa.habit.title": "🎯 DOXA Habit",
|
||||
"doxa.habit.description": "Tracker návyků pro terminál se sledováním streaku",
|
||||
"doxa.habit.philosophy": "Sledujte svůj osobní růst lokálně. Bez notifikačních manipulací, bez gamifikačního tlaku – jen vaše data a váš pokrok. Data uložena v ~/.doxa-habit/.",
|
||||
"doxa.habit.features": "Funkce: Denní/týdenní návyky · Streak tracker · ASCII heatmap · Statistiky úspěšnosti (7/30 dní) · Export do CSV · Dvoujazyčné rozhraní (CZ/EN)",
|
||||
"doxa.habit.tech": "Technologie: Go (Golang), Bubble Tea, Lip Gloss",
|
||||
"doxa.habit.platforms": "Platformy: Linux, FreeBSD",
|
||||
"doxa.habit.link": "Přečtěte si podrobnější dokumentaci",
|
||||
"doxa.commonFeatures.title": "Společné vlastnosti všech projektů DOXA",
|
||||
"doxa.commonFeatures.table.header.feature": "Vlastnost",
|
||||
"doxa.commonFeatures.table.header.notes": "DOXA Notes",
|
||||
"doxa.commonFeatures.table.header.cli": "DOXA CLI",
|
||||
"doxa.commonFeatures.table.header.secret": "DOXA Secret",
|
||||
"doxa.commonFeatures.table.header.todo": "DOXA TODO",
|
||||
"doxa.commonFeatures.table.header.timer": "DOXA Timer",
|
||||
"doxa.commonFeatures.table.header.habit": "DOXA Habit",
|
||||
"doxa.commonFeatures.table.row.localStorage": "Lokální uložení dat",
|
||||
"doxa.commonFeatures.table.row.openFormat": "Otevřený formát (JSON)",
|
||||
"doxa.commonFeatures.table.row.noCloud": "Žádné cloudové závislosti",
|
||||
"doxa.commonFeatures.table.row.openSource": "Open Source (MIT)",
|
||||
"doxa.commonFeatures.table.row.minimalist": "Minimalistický design",
|
||||
"doxa.commonFeatures.table.row.bilingual": "Dvoujazyčné rozhraní (CZ/EN)",
|
||||
"doxa.commonFeatures.table.row.tagSupport": "Podpora tagů",
|
||||
"doxa.commonFeatures.table.row.search": "Vyhledávání a filtrování",
|
||||
"doxa.commonFeatures.table.row.encryption": "Šifrování",
|
||||
"doxa.commonFeatures.table.row.priority": "Úrovně priority",
|
||||
"doxa.commonFeatures.table.row.categories": "Kategorie",
|
||||
"doxa.commonFeatures.table.row.timer": "Pomodoro timer",
|
||||
"doxa.commonFeatures.table.row.streak": "Streak / Statistiky návyků",
|
||||
"doxa.commonFeatures.table.row.export": "Export dat",
|
||||
"doxa.installation.title": "Instalace",
|
||||
"doxa.installation.text": "Každý projekt má svůj vlastní instalační postup. Podrobné návody naleznete v příslušných souborech README:",
|
||||
"doxa.installation.notes": "DOXA Notes — Instalace",
|
||||
"doxa.installation.cli": "DOXA CLI — Instalace",
|
||||
"doxa.installation.secret": "DOXA Secret — Instalace",
|
||||
"doxa.installation.todo": "DOXA TODO — Instalace",
|
||||
"doxa.installation.timer": "DOXA Timer — Instalace",
|
||||
"doxa.installation.habit": "DOXA Habit — Instalace",
|
||||
"doxa.license.title": "Licence",
|
||||
"doxa.license.text": "Všechny projekty v kolekci DOXA jsou open-source a licencovány pod MIT licencí. Podrobnosti naleznete v souborech LICENSE v jednotlivých projektech.",
|
||||
"doxa.philosophy.title": "Filozofie DOXA",
|
||||
|
||||
@@ -81,14 +81,14 @@
|
||||
"manifest.quote.author": "— A Stoic message for free thinkers",
|
||||
"nav.doxa": "DOXA Collection",
|
||||
"doxa.pageTitle": "DOXA Collection — Complete Project Overview",
|
||||
"doxa.pageDescription": "DOXA is a collection of four minimalist, locally-focused applications for note management, message encryption, and command-line text processing. All projects share a common philosophy: complete independence, privacy, and control over your data.",
|
||||
"doxa.pageDescription": "DOXA is a collection of six minimalist, locally-focused applications for note management, message encryption, task management, Pomodoro timer and habit tracking. No cloud, no tracking – your data belongs only to you.",
|
||||
"doxa.ogTitle": "DOXA Collection — Complete Project Overview",
|
||||
"doxa.ogDescription": "Overview of all DOXA projects: Notes, CLI, Secret, and TODO. Full locality, open formats, and digital sovereignty.",
|
||||
"doxa.ogDescription": "Overview of all DOXA projects: Notes, CLI, Secret, TODO, Timer and Habit. Full locality, open formats, and digital sovereignty.",
|
||||
"doxa.twitterTitle": "DOXA Collection",
|
||||
"doxa.twitterDescription": "Overview of all DOXA projects for note management, encryption, and task organization.",
|
||||
"doxa.twitterDescription": "Overview of all DOXA projects for note management, encryption, tasks, Pomodoro timer and habit tracking.",
|
||||
"doxa.mainTitle": "DOXA Collection",
|
||||
"doxa.subtitle": "Complete Independence, Privacy & Data Control",
|
||||
"doxa.intro": "DOXA is a collection of four minimalist, locally-focused applications for note management, message encryption, and command-line text processing. All projects share a common philosophy: complete independence, privacy, and control over your data. No cloud services, no tracking, no external dependencies. Your data belongs only to you.",
|
||||
"doxa.intro": "DOXA is a collection of six minimalist, locally-focused applications for note management, message encryption, task management, Pomodoro timer and habit tracking. All projects share a common philosophy: complete independence, privacy, and control over your data. No cloud services, no tracking, no external dependencies. Your data belongs only to you.",
|
||||
"doxa.philosophy": "The core idea remains uncompromising: A radical Local-First approach, clean data in JSON format, and absolute digital privacy without corporate clouds and APIs.",
|
||||
"doxa.projects.title": "Projects in the DOXA Collection",
|
||||
"doxa.notes.title": "📝 DOXA Notes",
|
||||
@@ -121,28 +121,49 @@
|
||||
"doxa.todo.philosophy": "Philosophy: Local task management. Your todos are stored locally in JSON format.",
|
||||
"doxa.todo.dataLocation": "Data stored in ~/.doxa-todo/doxa_todo.json.",
|
||||
"doxa.todo.link": "Read detailed documentation",
|
||||
"doxa.timer.title": "⏱️ DOXA Timer",
|
||||
"doxa.timer.description": "Pomodoro timer for terminal with productivity statistics",
|
||||
"doxa.timer.philosophy": "Local-first productivity. No cloud apps, no subscriptions – just clean focus on your work. Data stored in ~/.doxa-timer/.",
|
||||
"doxa.timer.features": "Features: Pomodoro technique (25/5/15 min) · Live countdown · Configurable session lengths · Pomodoro statistics · Bilingual interface (CZ/EN)",
|
||||
"doxa.timer.tech": "Technologies: Go (Golang), Bubble Tea, Lip Gloss",
|
||||
"doxa.timer.platforms": "Platforms: Linux, FreeBSD",
|
||||
"doxa.timer.link": "Read detailed documentation",
|
||||
"doxa.habit.title": "🎯 DOXA Habit",
|
||||
"doxa.habit.description": "Habit tracker for terminal with streak tracking",
|
||||
"doxa.habit.philosophy": "Track your personal growth locally. No notification manipulations, no gamification pressure – just your data and your progress. Data stored in ~/.doxa-habit/.",
|
||||
"doxa.habit.features": "Features: Daily/weekly habits · Streak tracker · ASCII heatmap · Success rate statistics (7/30 days) · Export to CSV · Bilingual interface (CZ/EN)",
|
||||
"doxa.habit.tech": "Technologies: Go (Golang), Bubble Tea, Lip Gloss",
|
||||
"doxa.habit.platforms": "Platforms: Linux, FreeBSD",
|
||||
"doxa.habit.link": "Read detailed documentation",
|
||||
"doxa.commonFeatures.title": "Common Features of All DOXA Projects",
|
||||
"doxa.commonFeatures.table.header.feature": "Feature",
|
||||
"doxa.commonFeatures.table.header.notes": "DOXA Notes",
|
||||
"doxa.commonFeatures.table.header.cli": "DOXA CLI",
|
||||
"doxa.commonFeatures.table.header.secret": "DOXA Secret",
|
||||
"doxa.commonFeatures.table.header.todo": "DOXA TODO",
|
||||
"doxa.commonFeatures.table.header.timer": "DOXA Timer",
|
||||
"doxa.commonFeatures.table.header.habit": "DOXA Habit",
|
||||
"doxa.commonFeatures.table.row.localStorage": "Local Data Storage",
|
||||
"doxa.commonFeatures.table.row.openFormat": "Open Format (JSON)",
|
||||
"doxa.commonFeatures.table.row.noCloud": "No Cloud Dependencies",
|
||||
"doxa.commonFeatures.table.row.openSource": "Open Source (MIT)",
|
||||
"doxa.commonFeatures.table.row.minimalist": "Minimalist Design",
|
||||
"doxa.commonFeatures.table.row.bilingual": "Bilingual Interface (CZ/EN)",
|
||||
"doxa.commonFeatures.table.row.tagSupport": "Tag Support",
|
||||
"doxa.commonFeatures.table.row.search": "Search and Filtering",
|
||||
"doxa.commonFeatures.table.row.encryption": "Encryption",
|
||||
"doxa.commonFeatures.table.row.priority": "Priority Levels",
|
||||
"doxa.commonFeatures.table.row.categories": "Categories",
|
||||
"doxa.commonFeatures.table.row.timer": "Pomodoro timer",
|
||||
"doxa.commonFeatures.table.row.streak": "Streak / Habit Statistics",
|
||||
"doxa.commonFeatures.table.row.export": "Data Export",
|
||||
"doxa.installation.title": "Installation",
|
||||
"doxa.installation.text": "Each project has its own installation process. Detailed guides can be found in the respective README files:",
|
||||
"doxa.installation.notes": "DOXA Notes — Installation",
|
||||
"doxa.installation.cli": "DOXA CLI — Installation",
|
||||
"doxa.installation.secret": "DOXA Secret — Installation",
|
||||
"doxa.installation.todo": "DOXA TODO — Installation",
|
||||
"doxa.installation.timer": "DOXA Timer — Installation",
|
||||
"doxa.installation.habit": "DOXA Habit — Installation",
|
||||
"doxa.license.title": "License",
|
||||
"doxa.license.text": "All projects in the DOXA collection are open-source and licensed under the MIT License. See the LICENSE files in each project for details.",
|
||||
"doxa.philosophy.title": "DOXA Philosophy",
|
||||
|
||||
+89
-13
@@ -6,20 +6,20 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title data-i18n-key="doxa.pageTitle">Kolekce DOXA — Celý přehled projektů</title>
|
||||
<meta name="description" data-i18n-key="doxa.pageDescription" data-i18n-attr="content"
|
||||
content="DOXA je sbírka čtyř minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv a práci s textem z příkazové řádky. Všechny projekty sdílejí společnou filozofii: úplná nezávislost, soukromí a kontrola nad vašimi daty." />
|
||||
content="DOXA je sbírka šesti minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv, správu úkolů, Pomodoro timer a sledování návyků. Žádný cloud, žádné sledování – vaše data patří pouze vám." />
|
||||
<link rel="canonical" href="https://doxa-notes.org/doxa.html" />
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://doxa-notes.org/doxa.html" />
|
||||
<meta property="og:title" data-i18n-key="doxa.ogTitle" data-i18n-attr="content" content="Kolekce DOXA — Celý přehled projektů" />
|
||||
<meta property="og:description" data-i18n-key="doxa.ogDescription" data-i18n-attr="content" content="Přehled všech projektů DOXA: Notes, CLI, Secret a TODO. Úplná lokalita, otevřené formáty a digitální suverenita." />
|
||||
<meta property="og:description" data-i18n-key="doxa.ogDescription" data-i18n-attr="content" content="Přehled všech projektů DOXA: Notes, CLI, Secret, TODO, Timer a Habit. Úplná lokalita, otevřené formáty a digitální suverenita." />
|
||||
<meta property="og:image" content="https://doxa-notes.org/assets/img/doxa.png" />
|
||||
<meta property="og:image:alt" content="DOXA Collection - Complete Project Overview" />
|
||||
<meta property="og:locale" data-i18n-key="ogLocale" data-i18n-attr="content" content="cs_CZ" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" data-i18n-key="doxa.twitterTitle" data-i18n-attr="content" content="Kolekce DOXA" />
|
||||
<meta name="twitter:description" data-i18n-key="doxa.twitterDescription" data-i18n-attr="content" content="Přehled všech projektů DOXA pro správu poznámek, šifrování a organizaci úkolů." />
|
||||
<meta name="twitter:description" data-i18n-key="doxa.twitterDescription" data-i18n-attr="content" content="Přehled všech projektů DOXA pro správu poznámek, šifrování, organizaci úkolů, Pomodoro timer a sledování návyků." />
|
||||
<meta name="twitter:image" content="https://doxa-notes.org/assets/img/doxa.png" />
|
||||
<meta name="twitter:image:alt" content="DOXA Collection - Complete Project Overview" />
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "DOXA Collection",
|
||||
"description": "A collection of four minimalist, locally-focused applications for note management, message encryption, and command-line text processing.",
|
||||
"operatingSystem": "Windows, Linux, FreeBSD, Web",
|
||||
"description": "A collection of six minimalist, locally-focused applications for note management, message encryption, task management, Pomodoro timer and habit tracking.",
|
||||
"operatingSystem": "Linux, FreeBSD, Web",
|
||||
"applicationCategory": "UtilitiesApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
@@ -65,13 +65,13 @@
|
||||
<p style="color: var(--muted); margin-bottom: 30px; margin-top: 2px; font-size: 0.95rem;" data-i18n-key="doxa.subtitle">Úplná nezávislost, soukromí a kontrola nad daty</p>
|
||||
|
||||
<p data-i18n-key="doxa.intro">
|
||||
DOXA je sbírka čtyř minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv a práci s textem z příkazové řádky.
|
||||
DOXA je sbírka šesti minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv, správu úkolů, Pomodoro timer a sledování návyků.
|
||||
Všechny projekty sdílejí společnou filozofii: úplná nezávislost, soukromí a kontrola nad vašimi daty.
|
||||
Žádná cloudová služba, žádné sledování, žádné externí závislosti. Vaše data patří pouze vám.
|
||||
</p>
|
||||
|
||||
<div class="philosophy" data-i18n-key="doxa.philosophy">
|
||||
„Základní myšlenka zůstává nekompromisní: Radikální Local-First přístup, čistá data v JSON formátu a absolutní digitální soukromí bez korporátního cloudu a API.“
|
||||
„Základní myšlenka zůstává nekompromisní: Radikální Local-First přístup, čistá data v JSON formátu a absolutní digitální soukromí bez korporátního cloudu a API."
|
||||
</div>
|
||||
|
||||
<hr id="lineHR" style="margin: 30px 0;">
|
||||
@@ -130,10 +130,37 @@
|
||||
<p><a id="links" href="https://codeberg.org/Kankys/doxa-todo" target="_blank" rel="noopener noreferrer" data-i18n-key="doxa.todo.link">Přečtěte si podrobnější dokumentaci</a></p>
|
||||
</section>
|
||||
|
||||
<hr id="lineHR" style="margin: 20px 0;">
|
||||
|
||||
<!-- DOXA Timer -->
|
||||
<section style="margin-bottom: 40px; text-align: left;">
|
||||
<h3 data-i18n-key="doxa.timer.title">⏱️ DOXA Timer</h3>
|
||||
<p><strong data-i18n-key="doxa.timer.description">Pomodoro timer pro terminál se statistikami produktivity</strong></p>
|
||||
<p><em data-i18n-key="doxa.timer.philosophy">Local-first produktivita. Žádné cloudové aplikace, žádné předplatné – jen čistý fokus na vaši práci. Data uložena v <code>~/.doxa-timer/</code>.</em></p>
|
||||
<p><strong data-i18n-key="doxa.timer.features">Funkce: Pomodoro technika (25/5/15 min) · Live countdown · Konfigurovatelné délky sessions · Statistiky pomodorů · Dvoujazyčné rozhraní (CZ/EN)</strong></p>
|
||||
<p><strong data-i18n-key="doxa.timer.tech">Technologie: Go (Golang), Bubble Tea, Lip Gloss</strong></p>
|
||||
<p><strong data-i18n-key="doxa.timer.platforms">Platformy: Linux, FreeBSD</strong></p>
|
||||
<p><a id="links" href="https://codeberg.org/Kankys/doxa-timer" target="_blank" rel="noopener noreferrer" data-i18n-key="doxa.timer.link">Přečtěte si podrobnější dokumentaci</a></p>
|
||||
</section>
|
||||
|
||||
<hr id="lineHR" style="margin: 20px 0;">
|
||||
|
||||
<!-- DOXA Habit -->
|
||||
<section style="margin-bottom: 40px; text-align: left;">
|
||||
<h3 data-i18n-key="doxa.habit.title">🎯 DOXA Habit</h3>
|
||||
<p><strong data-i18n-key="doxa.habit.description">Tracker návyků pro terminál se sledováním streaku</strong></p>
|
||||
<p><em data-i18n-key="doxa.habit.philosophy">Sledujte svůj osobní růst lokálně. Bez notifikačních manipulací, bez gamifikačního tlaku – jen vaše data a váš pokrok. Data uložena v <code>~/.doxa-habit/</code>.</em></p>
|
||||
<p><strong data-i18n-key="doxa.habit.features">Funkce: Denní/týdenní návyky · Streak tracker · ASCII heatmap · Statistiky úspěšnosti (7/30 dní) · Export do CSV · Dvoujazyčné rozhraní (CZ/EN)</strong></p>
|
||||
<p><strong data-i18n-key="doxa.habit.tech">Technologie: Go (Golang), Bubble Tea, Lip Gloss</strong></p>
|
||||
<p><strong data-i18n-key="doxa.habit.platforms">Platformy: Linux, FreeBSD</strong></p>
|
||||
<p><a id="links" href="https://codeberg.org/Kankys/doxa-habit" target="_blank" rel="noopener noreferrer" data-i18n-key="doxa.habit.link">Přečtěte si podrobnější dokumentaci</a></p>
|
||||
</section>
|
||||
|
||||
<hr id="lineHR" style="margin: 30px 0;">
|
||||
|
||||
<!-- Common Features Table -->
|
||||
<h2 data-i18n-key="doxa.commonFeatures.title">Společné vlastnosti všech projektů DOXA</h2>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -142,6 +169,8 @@
|
||||
<th style="border: 1px solid var(--note-border); padding: 12px; text-align: center; background: var(--button-bg);" data-i18n-key="doxa.commonFeatures.table.header.cli">DOXA CLI</th>
|
||||
<th style="border: 1px solid var(--note-border); padding: 12px; text-align: center; background: var(--button-bg);" data-i18n-key="doxa.commonFeatures.table.header.secret">DOXA Secret</th>
|
||||
<th style="border: 1px solid var(--note-border); padding: 12px; text-align: center; background: var(--button-bg);" data-i18n-key="doxa.commonFeatures.table.header.todo">DOXA TODO</th>
|
||||
<th style="border: 1px solid var(--note-border); padding: 12px; text-align: center; background: var(--button-bg);" data-i18n-key="doxa.commonFeatures.table.header.timer">DOXA Timer</th>
|
||||
<th style="border: 1px solid var(--note-border); padding: 12px; text-align: center; background: var(--button-bg);" data-i18n-key="doxa.commonFeatures.table.header.habit">DOXA Habit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -151,6 +180,8 @@
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.openFormat">Otevřený formát (JSON)</td>
|
||||
@@ -158,6 +189,8 @@
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.noCloud">Žádné cloudové závislosti</td>
|
||||
@@ -165,6 +198,8 @@
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.openSource">Open Source (MIT)</td>
|
||||
@@ -172,6 +207,8 @@
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.minimalist">Minimalistický design</td>
|
||||
@@ -179,6 +216,17 @@
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.bilingual">Dvoujazyčné rozhraní (CZ/EN)</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.tagSupport">Podpora tagů</td>
|
||||
@@ -186,6 +234,8 @@
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.search">Vyhledávání a filtrování</td>
|
||||
@@ -193,6 +243,8 @@
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.encryption">Šifrování</td>
|
||||
@@ -200,6 +252,8 @@
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.priority">Úrovně priority</td>
|
||||
@@ -207,26 +261,50 @@
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.categories">Kategorie</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.timer">Pomodoro timer</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.streak">Streak / Statistiky návyků</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">❌</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px;" data-i18n-key="doxa.commonFeatures.table.row.export">Export dat</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅ JSON</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅ TXT</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅ TXT</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅ TXT</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅ JSON</td>
|
||||
<td style="border: 1px solid var(--note-border); padding: 12px; text-align: center;">✅ CSV</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<hr id="lineHR" style="margin: 30px 0;">
|
||||
|
||||
<h2 data-i18n-key="doxa.installation.title">Instalace</h2>
|
||||
<p data-i18n-key="doxa.installation.text"></p>
|
||||
<ul style="text-align: left;">
|
||||
<!--<li><a id="links" href="../doxa-notes/03_install.md" target="_blank" rel="noopener noreferrer" data-i18n-key="doxa.installation.notes">DOXA Notes — Instalace</a></li>-->
|
||||
<li><a id="links" href="https://codeberg.org/Kankys/doxa-cli" target="_blank" rel="noopener noreferrer" data-i18n-key="doxa.installation.cli">DOXA CLI — Instalace</a></li>
|
||||
<li><a id="links" href="https://codeberg.org/Kankys/doxa-secret" target="_blank" rel="noopener noreferrer" data-i18n-key="doxa.installation.secret">DOXA Secret — Instalace</a></li>
|
||||
<li><a id="links" href="https://codeberg.org/Kankys/doxa-todo" target="_blank" rel="noopener noreferrer" data-i18n-key="doxa.installation.todo">DOXA TODO — Instalace</a></li>
|
||||
<li><a id="links" href="https://codeberg.org/Kankys/doxa-timer" target="_blank" rel="noopener noreferrer" data-i18n-key="doxa.installation.timer">DOXA Timer — Instalace</a></li>
|
||||
<li><a id="links" href="https://codeberg.org/Kankys/doxa-habit" target="_blank" rel="noopener noreferrer" data-i18n-key="doxa.installation.habit">DOXA Habit — Instalace</a></li>
|
||||
</ul>
|
||||
|
||||
<hr id="lineHR" style="margin: 30px 0;">
|
||||
@@ -256,18 +334,16 @@
|
||||
|
||||
<h3 data-i18n-key="doxa.roadmap.mainTitle">Hlavní návrhy</h3>
|
||||
<ul style="text-align: left;">
|
||||
<li><strong>📅 DOXA Calendar</strong> - <span data-i18n-key="doxa.roadmap.calendar">Termínový kalendář s událostmi a integrací s DOXA TODO</span></li>
|
||||
<li><strong>📅 DOXA Calendar</strong> - <span data-i18n-key="doxa.roadmap.calendar">Terminálový kalendář s událostmi a integrací s DOXA TODO</span></li>
|
||||
<li><strong>📇 DOXA Contacts</strong> - <span data-i18n-key="doxa.roadmap.contacts">Správce kontaktů v terminálu</span></li>
|
||||
<li><strong>🔑 DOXA Password</strong> - <span data-i18n-key="doxa.roadmap.password">Bezpečný správce hesel s místním šifrováním</span></li>
|
||||
<li><strong>📖 DOXA Journal</strong> - <span data-i18n-key="doxa.roadmap.journal">Deníkový systém pro terminál</span></li>
|
||||
<li><strong>🎯 DOXA Habit Tracker</strong> - <span data-i18n-key="doxa.roadmap.habit">Tracker návyků pro sledování osobního růstu</span></li>
|
||||
</ul>
|
||||
|
||||
<h3 data-i18n-key="doxa.roadmap.bonusTitle">Bonusové nápady</h3>
|
||||
<ul style="text-align: left;">
|
||||
<li><strong>💰 DOXA Finance</strong> - <span data-i18n-key="doxa.roadmap.finance">Správa výdajů a příjmů</span></li>
|
||||
<li><strong>🔖 DOXA Bookmarks</strong> - <span data-i18n-key="doxa.roadmap.bookmarks">Správce záložek z příkazové řádky</span></li>
|
||||
<li><strong>⏱️ DOXA Timer</strong> - <span data-i18n-key="doxa.roadmap.timer">Pomodoro timer pro terminál</span></li>
|
||||
<li><strong>🔄 DOXA Sync</strong> - <span data-i18n-key="doxa.roadmap.sync">Lokální synchronizace mezi zařízeními</span></li>
|
||||
<li><strong>💾 DOXA Backup</strong> - <span data-i18n-key="doxa.roadmap.backup">Nástroj pro zálohování DOXA dat</span></li>
|
||||
</ul>
|
||||
@@ -296,7 +372,7 @@
|
||||
|
||||
</div>
|
||||
<footer>
|
||||
<p class="copyright" data-i18n-key="footer.copyright">© 2026 Doxa Notes | Local-First & Freedom 🦅</p>
|
||||
<p class="copyright" data-i18n-key="footer.copyright">© 2026 Doxa Notes | Local-First & Freedom 🦅</p>
|
||||
</footer>
|
||||
<script src="assets/js/i18n.js"></script>
|
||||
<script src="assets/js/script.js"></script>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Kankys
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,43 @@
|
||||
# DOXA Timer User Manual ⏱️
|
||||
|
||||
This manual explains how to use the DOXA Timer application.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Run the program with:
|
||||
```bash
|
||||
doxa-timer
|
||||
```
|
||||
|
||||
When you first launch the app, you will see a startup screen. Press `Enter` to access the main interface.
|
||||
|
||||
## Interface States
|
||||
|
||||
1. **READY**: The timer is set and waiting for you to start.
|
||||
2. **FOCUS**: Work interval. Focus on your tasks.
|
||||
3. **SHORT BREAK / LONG BREAK**: Relax. The countdown signals the break duration.
|
||||
4. **SETTINGS**: Configuration menu to set durations.
|
||||
5. **STATS**: Historical data.
|
||||
|
||||
## Controls
|
||||
|
||||
### Main Screen
|
||||
- `Space`: Starts or pauses the countdown.
|
||||
- `r`: Resets the countdown back to the configured focus or break time.
|
||||
- `s`: Opens Settings.
|
||||
- `t`: Opens Stats.
|
||||
- `l`: Toggles UI translation (English/Czech).
|
||||
- `q` / `Esc`: Exits the application.
|
||||
|
||||
### Settings Screen
|
||||
- `up`/`down` or `k`/`j`: Navigate between different settings options:
|
||||
- Focus time
|
||||
- Short break time
|
||||
- Long break time
|
||||
- Long break after X pomodoros
|
||||
- `+` / `-`: Increase or decrease the value of the active option.
|
||||
- `Enter`: Save settings and return to the main screen.
|
||||
- `q` / `Esc`: Go back to the main screen without saving changes.
|
||||
|
||||
### Stats Screen
|
||||
- `q` / `Esc`: Return to the main screen.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Uživatelský manuál DOXA Timer ⏱️
|
||||
|
||||
Tento manuál popisuje použití aplikace DOXA Timer.
|
||||
|
||||
## Spuštění programu
|
||||
|
||||
Spusťte aplikaci příkazem:
|
||||
```bash
|
||||
doxa-timer
|
||||
```
|
||||
|
||||
Při prvním spuštění se zobrazí úvodní logo a text. Stiskněte `Enter` pro přechod do hlavního rozhraní.
|
||||
|
||||
## Stavy aplikace
|
||||
|
||||
1. **PŘIPRAVEN (READY)**: Časovač čeká na spuštění.
|
||||
2. **FOKUS (FOCUS)**: Pracovní interval. Soustřeďte se na práci.
|
||||
3. **KRÁTKÁ / DLOUHÁ PŘESTÁVKA**: Čas na relaxaci a odpočinek.
|
||||
4. **NASTAVENÍ (SETTINGS)**: Konfigurace délek časovačů.
|
||||
5. **STATISTIKY (STATS)**: Přehled o dokončených sezeních.
|
||||
|
||||
## Ovládání
|
||||
|
||||
### Hlavní obrazovka
|
||||
- `Mezerník (Space)`: Spustí nebo pozastaví aktuální odpočet.
|
||||
- `r`: Obnoví (resetuje) aktuální časovač zpět na plnou délku.
|
||||
- `s`: Přejde do Nastavení.
|
||||
- `t`: Přejde do Statistik.
|
||||
- `l`: Přepne jazyk mezi češtinou a angličtinou.
|
||||
- `q` / `Esc`: Ukončí program.
|
||||
|
||||
### Obrazovka Nastavení
|
||||
- `šipka nahoru/dolů` nebo `k`/`j`: Pohyb mezi položkami nastavení:
|
||||
- Délka fokusu
|
||||
- Krátká přestávka
|
||||
- Dlouhá přestávka
|
||||
- Dlouhá přestávka po X pomodorech
|
||||
- `+` / `-`: Zvýší nebo sníží hodnotu zvolené položky.
|
||||
- `Enter`: Uloží konfiguraci a vrátí se na hlavní obrazovku.
|
||||
- `q` / `Esc`: Vrátí se na hlavní obrazovku bez uložení změn.
|
||||
|
||||
### Obrazovka Statistiky
|
||||
- `q` / `Esc`: Vrátí se na hlavní obrazovku.
|
||||
@@ -0,0 +1,32 @@
|
||||
# DOXA Timer ⏱️
|
||||
|
||||
**Minimalist Pomodoro timer for the terminal with local statistics**
|
||||
|
||||
DOXA Timer is a terminal-based Pomodoro app. It alternates focus work sessions with short and long breaks to optimize concentration, while tracking your sessions locally.
|
||||
|
||||
## Features
|
||||
|
||||
- **Pomodoro Technique**: Standard Focus (25m), Short Break (5m), and Long Break (15m).
|
||||
- **Live Countdown & Progress**: Interactive ASCII progress bar.
|
||||
- **Persistent Local Stats**: Tracking of today's and total Pomodoros.
|
||||
- **Customizable Intervals**: Adjust times inside the app settings.
|
||||
- **Sovereign & Offline**: Everything is saved locally at `~/.doxa-timer/doxa_timer.json`.
|
||||
- **Bilingual TUI**: Switch between English and Czech using the `l` key.
|
||||
|
||||
## Installation
|
||||
|
||||
Go to the `doxa-timer` folder and run the install script:
|
||||
|
||||
```bash
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
## Hotkeys
|
||||
|
||||
- `Space` - Start / Pause / Resume
|
||||
- `r` - Reset current timer
|
||||
- `s` - Open Settings (press `+`/`-` to adjust times, `Enter` to Save, `q` to Cancel)
|
||||
- `t` - Show Statistics
|
||||
- `l` - Toggle language (EN/CS)
|
||||
- `q` or `Esc` - Go back / Quit
|
||||
@@ -0,0 +1,32 @@
|
||||
# DOXA Timer ⏱️
|
||||
|
||||
**Minimalistický Pomodoro časovač do terminálu s lokální statistikou**
|
||||
|
||||
DOXA Timer je terminálová Pomodoro aplikace. Střídá intervaly plného soustředění na práci s krátkými a dlouhými přestávkami pro zvýšení produktivity.
|
||||
|
||||
## Vlastnosti
|
||||
|
||||
- **Metoda Pomodoro**: Fokus (25 min), Krátká přestávka (5 min) a Dlouhá přestávka (15 min).
|
||||
- **Živý odpočet**: ASCII progress bar s procentuálním vyjádřením pokroku.
|
||||
- **Lokální statistika**: Počítadlo celkových a dnešních dokončených Pomodorů.
|
||||
- **Nastavitelnost**: Změna délek jednotlivých fází v menu Nastavení.
|
||||
- **Local-first**: Data se ukládají lokálně do `~/.doxa-timer/doxa_timer.json`.
|
||||
- **Dvojjazyčnost**: Přepínání češtiny a angličtiny pomocí klávesy `l`.
|
||||
|
||||
## Instalace
|
||||
|
||||
Přejděte do složky `doxa-timer` a spusťte instalační skript:
|
||||
|
||||
```bash
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
## Klávesové zkratky
|
||||
|
||||
- `Space` - Start / Pauza / Pokračovat
|
||||
- `r` - Reset aktuálního odpočtu
|
||||
- `s` - Otevřít Nastavení (klávesy `+`/`-` mění hodnotu, `Enter` uloží, `q` zruší změny)
|
||||
- `t` - Zobrazit Statistiky
|
||||
- `l` - Přepnutí jazyka (CZ/EN)
|
||||
- `q` nebo `Esc` - Zpět / Konec
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,31 @@
|
||||
module doxa-timer
|
||||
|
||||
go 1.26.5
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.9.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
||||
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DOXA-TIMER Installation Script
|
||||
|
||||
# --- Nastavení ---
|
||||
|
||||
APP_NAME="doxa-timer"
|
||||
INSTALL_DIR="$HOME/.local/bin"
|
||||
CONFIG_DIR="$HOME/.doxa-timer"
|
||||
CONFIG_FILE="doxa_timer.json"
|
||||
REPO_URL="https://codeberg.org/Kankys/doxa.git"
|
||||
|
||||
# --- Funkce ---
|
||||
|
||||
error_exit() {
|
||||
echo -e "\033[1;31mERROR: $1\033[0m" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
success_message() {
|
||||
echo -e "\033[1;32mSUCCESS: $1\033[0m"
|
||||
}
|
||||
|
||||
info_message() {
|
||||
echo -e "\033[1;34mINFO: $1\033[0m"
|
||||
}
|
||||
|
||||
# --- Hlavní část skriptu ---
|
||||
|
||||
info_message "Kontrola Go instalace..."
|
||||
if ! command -v go &> /dev/null
|
||||
then
|
||||
error_exit "Go není nainstalováno. Prosím, nainstalujte Go před spuštěním tohoto skriptu."
|
||||
fi
|
||||
success_message "Go je nainstalováno."
|
||||
|
||||
info_message "Kontrola aktuálního adresáře..."
|
||||
if [ -f "main.go" ] && [ -f "go.mod" ] && grep -q "module doxa-timer" go.mod; then
|
||||
info_message "Jste již v adresáři $APP_NAME, používám místní verzi."
|
||||
else
|
||||
error_exit "Tento skript spouštějte z adresáře doxa-timer."
|
||||
fi
|
||||
|
||||
info_message "Stažení závislostí projektu..."
|
||||
if ! go mod tidy
|
||||
then
|
||||
error_exit "Nepodařilo se stáhnout závislosti Go."
|
||||
fi
|
||||
success_message "Závislosti projektu úspěšně staženy."
|
||||
|
||||
info_message "Kompilace aplikace..."
|
||||
APP_BINARY="$APP_NAME"
|
||||
if ! go build -o "$APP_BINARY" .
|
||||
then
|
||||
error_exit "Nepodařilo se zkompilovat aplikaci."
|
||||
fi
|
||||
success_message "Aplikace úspěšně zkompilována do './$APP_BINARY'."
|
||||
|
||||
info_message "Instalace aplikace..."
|
||||
|
||||
if ! mkdir -p "$INSTALL_DIR"
|
||||
then
|
||||
error_exit "Nepodařilo se vytvořit cílový adresář '$INSTALL_DIR'."
|
||||
fi
|
||||
|
||||
if ! cp "$APP_BINARY" "$INSTALL_DIR/"
|
||||
then
|
||||
error_exit "Nepodařilo se zkopírovat binárku do '$INSTALL_DIR'."
|
||||
fi
|
||||
|
||||
chmod +x "$INSTALL_DIR/$APP_NAME"
|
||||
success_message "Aplikace '$APP_NAME' úspěšně nainstalována do '$INSTALL_DIR'."
|
||||
|
||||
info_message "Nastavení konfiguračního adresáře..."
|
||||
|
||||
if ! mkdir -p "$CONFIG_DIR"
|
||||
then
|
||||
error_exit "Nepodařilo se vytvořit konfigurační adresář '$CONFIG_DIR'."
|
||||
fi
|
||||
|
||||
if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]
|
||||
then
|
||||
echo "{}" > "$CONFIG_DIR/$CONFIG_FILE"
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
error_exit "Nepodařilo se vytvořit konfigurační soubor '$CONFIG_DIR/$CONFIG_FILE'."
|
||||
fi
|
||||
success_message "Konfigurační soubor '$CONFIG_DIR/$CONFIG_FILE' vytvořen."
|
||||
else
|
||||
info_message "Konfigurační soubor '$CONFIG_DIR/$CONFIG_FILE' již existuje."
|
||||
fi
|
||||
|
||||
info_message "Instalace dokončena."
|
||||
success_message "Nyní můžete spustit aplikaci '$APP_NAME' z jakéhokoli místa v terminálu."
|
||||
echo ""
|
||||
echo -e "\033[1;33mPRO TIP:\nPokud \`$APP_NAME\` nefunguje z jakéhokoli místa, přidejte '$INSTALL_DIR' do vašeho PATH.\nexport PATH=\$PATH:$INSTALL_DIR\033[0m"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func main() {
|
||||
m := NewModel()
|
||||
|
||||
p := tea.NewProgram(m)
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Printf("Chyba při spuštění programu: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimerStats holds persistent statistics and settings
|
||||
type TimerStats struct {
|
||||
TotalPomodoros int `json:"total_pomodoros"`
|
||||
TodayPomodoros int `json:"today_pomodoros"`
|
||||
LastDate string `json:"last_date"`
|
||||
FocusDuration int `json:"focus_duration"` // minutes
|
||||
ShortBreak int `json:"short_break"` // minutes
|
||||
LongBreak int `json:"long_break"` // minutes
|
||||
LongBreakAfter int `json:"long_break_after"` // after how many pomodoros
|
||||
}
|
||||
|
||||
func getStoragePath() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "doxa_timer.json"
|
||||
}
|
||||
configDir := filepath.Join(home, ".doxa-timer")
|
||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(configDir, 0755)
|
||||
}
|
||||
return filepath.Join(configDir, "doxa_timer.json")
|
||||
}
|
||||
|
||||
func loadStats() TimerStats {
|
||||
path := getStoragePath()
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return defaultStats()
|
||||
}
|
||||
var stats TimerStats
|
||||
if err := json.Unmarshal(file, &stats); err != nil {
|
||||
return defaultStats()
|
||||
}
|
||||
// Reset today's count if it's a new day
|
||||
today := time.Now().Format("2006-01-02")
|
||||
if stats.LastDate != today {
|
||||
stats.TodayPomodoros = 0
|
||||
stats.LastDate = today
|
||||
}
|
||||
// Ensure sane defaults if values are zero
|
||||
if stats.FocusDuration == 0 {
|
||||
stats.FocusDuration = 25
|
||||
}
|
||||
if stats.ShortBreak == 0 {
|
||||
stats.ShortBreak = 5
|
||||
}
|
||||
if stats.LongBreak == 0 {
|
||||
stats.LongBreak = 15
|
||||
}
|
||||
if stats.LongBreakAfter == 0 {
|
||||
stats.LongBreakAfter = 4
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
func saveStats(stats TimerStats) {
|
||||
path := getStoragePath()
|
||||
data, err := json.MarshalIndent(stats, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("Chyba při ukládání dat:", err)
|
||||
return
|
||||
}
|
||||
os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
func defaultStats() TimerStats {
|
||||
return TimerStats{
|
||||
TotalPomodoros: 0,
|
||||
TodayPomodoros: 0,
|
||||
LastDate: time.Now().Format("2006-01-02"),
|
||||
FocusDuration: 25,
|
||||
ShortBreak: 5,
|
||||
LongBreak: 15,
|
||||
LongBreakAfter: 4,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package main
|
||||
|
||||
// Translation holds all UI strings for one language
|
||||
type Translation struct {
|
||||
Title string
|
||||
MenuStart string
|
||||
MenuPause string
|
||||
MenuReset string
|
||||
MenuSettings string
|
||||
MenuStats string
|
||||
MenuLang string
|
||||
MenuQuit string
|
||||
StateIdle string
|
||||
StateRunning string
|
||||
StatePaused string
|
||||
StateBreak string
|
||||
StateLongBreak string
|
||||
StateDone string
|
||||
LabelFocus string
|
||||
LabelShortBreak string
|
||||
LabelLongBreak string
|
||||
LabelLongAfter string
|
||||
LabelTodayCount string
|
||||
LabelTotalCount string
|
||||
LabelNextBreak string
|
||||
HelpIdle string
|
||||
HelpRunning string
|
||||
HelpSettings string
|
||||
HelpStats string
|
||||
SettingsTitle string
|
||||
StatsTitle string
|
||||
SessionComplete string
|
||||
BreakComplete string
|
||||
PomodorosToday string
|
||||
PomodorosTotal string
|
||||
NextLongBreakIn string
|
||||
MinutesShort string
|
||||
}
|
||||
|
||||
var Translations = map[string]Translation{
|
||||
"cs": {
|
||||
Title: "DOXA-TIMER 🦅",
|
||||
MenuStart: "Space: Start",
|
||||
MenuPause: "Space: Pauza",
|
||||
MenuReset: "r: Reset",
|
||||
MenuSettings: "s: Nastavení",
|
||||
MenuStats: "t: Statistiky",
|
||||
MenuLang: "l: EN",
|
||||
MenuQuit: "q: Konec",
|
||||
StateIdle: "PŘIPRAVEN",
|
||||
StateRunning: "FOKUS",
|
||||
StatePaused: "PAUZA",
|
||||
StateBreak: "KRÁTKÁ PŘESTÁVKA",
|
||||
StateLongBreak: "DLOUHÁ PŘESTÁVKA",
|
||||
StateDone: "DOKONČENO!",
|
||||
LabelFocus: "Fokus:",
|
||||
LabelShortBreak: "Krátká přestávka:",
|
||||
LabelLongBreak: "Dlouhá přestávka:",
|
||||
LabelLongAfter: "Dlouhá přestávka po:",
|
||||
LabelTodayCount: "Dnes:",
|
||||
LabelTotalCount: "Celkem:",
|
||||
LabelNextBreak: "Příští dlouhá přestávka za:",
|
||||
HelpIdle: "Space: Spustit | r: Reset | s: Nastavení | t: Statistiky | l: EN | q: Konec",
|
||||
HelpRunning: "Space: Pauza | r: Reset | q: Konec",
|
||||
HelpSettings: "+/-: Změnit fokus | b/n: Změnit přestávky | Enter: Uložit | q: Zpět",
|
||||
HelpStats: "q/Esc: Zpět",
|
||||
SettingsTitle: "NASTAVENÍ ČASOVAČŮ",
|
||||
StatsTitle: "STATISTIKY",
|
||||
SessionComplete: "🎉 Sezení dokončeno! Čas na přestávku.",
|
||||
BreakComplete: "⏰ Přestávka skončila! Čas na práci.",
|
||||
PomodorosToday: "Dnes dokončeno:",
|
||||
PomodorosTotal: "Celkem dokončeno:",
|
||||
NextLongBreakIn: "Dlouhá přestávka za:",
|
||||
MinutesShort: "min",
|
||||
},
|
||||
"en": {
|
||||
Title: "DOXA-TIMER 🦅",
|
||||
MenuStart: "Space: Start",
|
||||
MenuPause: "Space: Pause",
|
||||
MenuReset: "r: Reset",
|
||||
MenuSettings: "s: Settings",
|
||||
MenuStats: "t: Stats",
|
||||
MenuLang: "l: CS",
|
||||
MenuQuit: "q: Quit",
|
||||
StateIdle: "READY",
|
||||
StateRunning: "FOCUS",
|
||||
StatePaused: "PAUSED",
|
||||
StateBreak: "SHORT BREAK",
|
||||
StateLongBreak: "LONG BREAK",
|
||||
StateDone: "DONE!",
|
||||
LabelFocus: "Focus:",
|
||||
LabelShortBreak: "Short break:",
|
||||
LabelLongBreak: "Long break:",
|
||||
LabelLongAfter: "Long break after:",
|
||||
LabelTodayCount: "Today:",
|
||||
LabelTotalCount: "Total:",
|
||||
LabelNextBreak: "Next long break in:",
|
||||
HelpIdle: "Space: Start | r: Reset | s: Settings | t: Stats | l: CS | q: Quit",
|
||||
HelpRunning: "Space: Pause | r: Reset | q: Quit",
|
||||
HelpSettings: "+/-: Change focus | b/n: Change breaks | Enter: Save | q: Back",
|
||||
HelpStats: "q/Esc: Back",
|
||||
SettingsTitle: "TIMER SETTINGS",
|
||||
StatsTitle: "STATISTICS",
|
||||
SessionComplete: "🎉 Session complete! Time for a break.",
|
||||
BreakComplete: "⏰ Break over! Time to focus.",
|
||||
PomodorosToday: "Completed today:",
|
||||
PomodorosTotal: "Completed total:",
|
||||
NextLongBreakIn: "Long break in:",
|
||||
MinutesShort: "min",
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// ─── Styles ──────────────────────────────────────────────────────────────────
|
||||
|
||||
var (
|
||||
appStyle = lipgloss.NewStyle().
|
||||
Padding(1, 2).
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("62"))
|
||||
|
||||
titleStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#FAFAFA")).
|
||||
Background(lipgloss.Color("#7D56F4")).
|
||||
Padding(0, 1).
|
||||
MarginBottom(1)
|
||||
|
||||
statusStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("205"))
|
||||
|
||||
timerFocusStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#7D56F4"))
|
||||
|
||||
timerPauseStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("205"))
|
||||
|
||||
timerBreakStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#2ECC71"))
|
||||
|
||||
timerDoneStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#F39C12"))
|
||||
|
||||
labelStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("241"))
|
||||
|
||||
valueStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#FAFAFA"))
|
||||
|
||||
settingActiveStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#7D56F4"))
|
||||
|
||||
helpStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("241")).
|
||||
MarginTop(1)
|
||||
)
|
||||
|
||||
// ─── State ────────────────────────────────────────────────────────────────────
|
||||
|
||||
type AppState int
|
||||
|
||||
const (
|
||||
StateStartup AppState = iota
|
||||
StateIdle
|
||||
StateRunning
|
||||
StatePaused
|
||||
StateBreak
|
||||
StateLongBreak
|
||||
StateSettings
|
||||
StateStats
|
||||
)
|
||||
|
||||
// ─── Messages ─────────────────────────────────────────────────────────────────
|
||||
|
||||
type tickMsg time.Time
|
||||
|
||||
func tickCmd() tea.Cmd {
|
||||
return tea.Tick(time.Second, func(t time.Time) tea.Msg {
|
||||
return tickMsg(t)
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Model ────────────────────────────────────────────────────────────────────
|
||||
|
||||
type model struct {
|
||||
state AppState
|
||||
stats TimerStats
|
||||
lang string
|
||||
width int
|
||||
height int
|
||||
statusMsg string
|
||||
// Timer countdown
|
||||
remaining int // seconds remaining
|
||||
totalSeconds int // total seconds for current session
|
||||
// Settings edit fields (temporary values while editing)
|
||||
editFocus int
|
||||
editShort int
|
||||
editLong int
|
||||
editAfter int
|
||||
settingCursor int // 0=focus,1=short,2=long,3=after
|
||||
}
|
||||
|
||||
func NewModel() model {
|
||||
stats := loadStats()
|
||||
return model{
|
||||
state: StateStartup,
|
||||
stats: stats,
|
||||
lang: "cs",
|
||||
remaining: stats.FocusDuration * 60,
|
||||
totalSeconds: stats.FocusDuration * 60,
|
||||
editFocus: stats.FocusDuration,
|
||||
editShort: stats.ShortBreak,
|
||||
editLong: stats.LongBreak,
|
||||
editAfter: stats.LongBreakAfter,
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return tea.EnterAltScreen
|
||||
}
|
||||
|
||||
// ─── Update ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
tr := Translations[m.lang]
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
|
||||
case tickMsg:
|
||||
if m.state == StateRunning || m.state == StateBreak || m.state == StateLongBreak {
|
||||
m.remaining--
|
||||
if m.remaining <= 0 {
|
||||
return m.handleTimerComplete()
|
||||
}
|
||||
return m, tickCmd()
|
||||
}
|
||||
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c":
|
||||
return m, tea.Quit
|
||||
|
||||
case "q", "esc":
|
||||
switch m.state {
|
||||
case StateStartup:
|
||||
return m, tea.Quit
|
||||
case StateIdle:
|
||||
return m, tea.Quit
|
||||
case StateRunning, StatePaused:
|
||||
return m, tea.Quit
|
||||
case StateSettings, StateStats:
|
||||
m.state = StateIdle
|
||||
m.statusMsg = ""
|
||||
return m, nil
|
||||
default:
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
case "l":
|
||||
if m.state != StateSettings {
|
||||
if m.lang == "cs" {
|
||||
m.lang = "en"
|
||||
} else {
|
||||
m.lang = "cs"
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case "enter":
|
||||
switch m.state {
|
||||
case StateStartup:
|
||||
m.state = StateIdle
|
||||
m.remaining = m.stats.FocusDuration * 60
|
||||
m.totalSeconds = m.stats.FocusDuration * 60
|
||||
return m, nil
|
||||
case StateSettings:
|
||||
// Save settings
|
||||
m.stats.FocusDuration = m.editFocus
|
||||
m.stats.ShortBreak = m.editShort
|
||||
m.stats.LongBreak = m.editLong
|
||||
m.stats.LongBreakAfter = m.editAfter
|
||||
saveStats(m.stats)
|
||||
m.state = StateIdle
|
||||
m.remaining = m.stats.FocusDuration * 60
|
||||
m.totalSeconds = m.stats.FocusDuration * 60
|
||||
m.statusMsg = ""
|
||||
return m, nil
|
||||
}
|
||||
|
||||
case " ":
|
||||
switch m.state {
|
||||
case StateIdle:
|
||||
m.state = StateRunning
|
||||
m.statusMsg = ""
|
||||
return m, tickCmd()
|
||||
case StateRunning:
|
||||
m.state = StatePaused
|
||||
return m, nil
|
||||
case StatePaused:
|
||||
m.state = StateRunning
|
||||
return m, tickCmd()
|
||||
case StateBreak, StateLongBreak:
|
||||
m.state = StateRunning
|
||||
return m, tickCmd()
|
||||
}
|
||||
|
||||
case "r":
|
||||
if m.state == StateRunning || m.state == StatePaused || m.state == StateIdle {
|
||||
m.state = StateIdle
|
||||
m.remaining = m.stats.FocusDuration * 60
|
||||
m.totalSeconds = m.stats.FocusDuration * 60
|
||||
m.statusMsg = ""
|
||||
return m, nil
|
||||
}
|
||||
if m.state == StateBreak || m.state == StateLongBreak {
|
||||
m.state = StateIdle
|
||||
m.remaining = m.stats.FocusDuration * 60
|
||||
m.totalSeconds = m.stats.FocusDuration * 60
|
||||
m.statusMsg = ""
|
||||
return m, nil
|
||||
}
|
||||
|
||||
case "s":
|
||||
if m.state == StateIdle || m.state == StatePaused {
|
||||
m.state = StateSettings
|
||||
m.editFocus = m.stats.FocusDuration
|
||||
m.editShort = m.stats.ShortBreak
|
||||
m.editLong = m.stats.LongBreak
|
||||
m.editAfter = m.stats.LongBreakAfter
|
||||
m.settingCursor = 0
|
||||
return m, nil
|
||||
}
|
||||
|
||||
case "t":
|
||||
if m.state == StateIdle || m.state == StatePaused {
|
||||
m.state = StateStats
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Settings navigation
|
||||
case "up", "k":
|
||||
if m.state == StateSettings {
|
||||
m.settingCursor--
|
||||
if m.settingCursor < 0 {
|
||||
m.settingCursor = 3
|
||||
}
|
||||
}
|
||||
case "down", "j":
|
||||
if m.state == StateSettings {
|
||||
m.settingCursor++
|
||||
if m.settingCursor > 3 {
|
||||
m.settingCursor = 0
|
||||
}
|
||||
}
|
||||
case "+", "=":
|
||||
if m.state == StateSettings {
|
||||
switch m.settingCursor {
|
||||
case 0:
|
||||
if m.editFocus < 60 { m.editFocus++ }
|
||||
case 1:
|
||||
if m.editShort < 30 { m.editShort++ }
|
||||
case 2:
|
||||
if m.editLong < 60 { m.editLong++ }
|
||||
case 3:
|
||||
if m.editAfter < 10 { m.editAfter++ }
|
||||
}
|
||||
}
|
||||
case "-":
|
||||
if m.state == StateSettings {
|
||||
switch m.settingCursor {
|
||||
case 0:
|
||||
if m.editFocus > 1 { m.editFocus-- }
|
||||
case 1:
|
||||
if m.editShort > 1 { m.editShort-- }
|
||||
case 2:
|
||||
if m.editLong > 1 { m.editLong-- }
|
||||
case 3:
|
||||
if m.editAfter > 1 { m.editAfter-- }
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = tr
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model) handleTimerComplete() (tea.Model, tea.Cmd) {
|
||||
tr := Translations[m.lang]
|
||||
|
||||
if m.state == StateRunning {
|
||||
// Pomodoro complete
|
||||
m.stats.TotalPomodoros++
|
||||
m.stats.TodayPomodoros++
|
||||
m.stats.LastDate = time.Now().Format("2006-01-02")
|
||||
saveStats(m.stats)
|
||||
m.statusMsg = tr.SessionComplete
|
||||
|
||||
// Decide: long break or short break
|
||||
if m.stats.TodayPomodoros%m.stats.LongBreakAfter == 0 {
|
||||
m.state = StateLongBreak
|
||||
m.remaining = m.stats.LongBreak * 60
|
||||
m.totalSeconds = m.stats.LongBreak * 60
|
||||
} else {
|
||||
m.state = StateBreak
|
||||
m.remaining = m.stats.ShortBreak * 60
|
||||
m.totalSeconds = m.stats.ShortBreak * 60
|
||||
}
|
||||
return m, tickCmd()
|
||||
}
|
||||
|
||||
// Break complete → go idle
|
||||
m.statusMsg = tr.BreakComplete
|
||||
m.state = StateIdle
|
||||
m.remaining = m.stats.FocusDuration * 60
|
||||
m.totalSeconds = m.stats.FocusDuration * 60
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ─── View ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
func (m model) View() string {
|
||||
tr := Translations[m.lang]
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(titleStyle.Render(tr.Title) + "\n")
|
||||
|
||||
if m.statusMsg != "" {
|
||||
b.WriteString(statusStyle.Render(m.statusMsg) + "\n\n")
|
||||
}
|
||||
|
||||
switch m.state {
|
||||
case StateStartup:
|
||||
eagle := `
|
||||
_
|
||||
( ` + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("`") + `
|
||||
\ ` + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("\\") + `
|
||||
/ / _
|
||||
__ / / ( \
|
||||
( "---" / / /
|
||||
\ / / /
|
||||
\ /-----' /
|
||||
\ / /
|
||||
\ / /
|
||||
'-------'
|
||||
`
|
||||
b.WriteString(lipgloss.Place(m.width-12, 14, lipgloss.Center, lipgloss.Center,
|
||||
eagle+"\n"+
|
||||
lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#7D56F4")).Render("DOXA-TIMER 🦅")+"\n\n"+
|
||||
"Local-First Pomodoro Timer\n\n"+
|
||||
"Enter: Start | q: Quit\n\n"+
|
||||
"License: MIT | © 2026 Kankys"))
|
||||
|
||||
case StateIdle, StateRunning, StatePaused, StateBreak, StateLongBreak:
|
||||
// State label
|
||||
stateLabel := m.getStateLabel()
|
||||
b.WriteString(lipgloss.NewStyle().Bold(true).Render(stateLabel) + "\n\n")
|
||||
|
||||
// Big timer
|
||||
timerStr := formatDuration(m.remaining)
|
||||
timerRendered := m.renderTimer(timerStr)
|
||||
b.WriteString(lipgloss.PlaceHorizontal(m.width-10, lipgloss.Center, timerRendered) + "\n\n")
|
||||
|
||||
// Progress bar
|
||||
progressBar := renderProgressBar(m.remaining, m.totalSeconds, 40)
|
||||
b.WriteString(lipgloss.PlaceHorizontal(m.width-10, lipgloss.Center, progressBar) + "\n\n")
|
||||
|
||||
// Stats row
|
||||
pomLine := fmt.Sprintf(" %s %s %s %s",
|
||||
labelStyle.Render(tr.LabelTodayCount),
|
||||
valueStyle.Render(fmt.Sprintf("%d", m.stats.TodayPomodoros)),
|
||||
labelStyle.Render(tr.LabelTotalCount),
|
||||
valueStyle.Render(fmt.Sprintf("%d", m.stats.TotalPomodoros)),
|
||||
)
|
||||
b.WriteString(pomLine + "\n\n")
|
||||
|
||||
// Next long break
|
||||
remaining := m.stats.LongBreakAfter - (m.stats.TodayPomodoros % m.stats.LongBreakAfter)
|
||||
if remaining == m.stats.LongBreakAfter {
|
||||
remaining = 0
|
||||
}
|
||||
if remaining > 0 {
|
||||
b.WriteString(labelStyle.Render(fmt.Sprintf(" %s %d pomodoro(s)", tr.LabelNextBreak, remaining)) + "\n\n")
|
||||
}
|
||||
|
||||
// Help
|
||||
helpText := tr.HelpIdle
|
||||
if m.state == StateRunning {
|
||||
helpText = tr.HelpRunning
|
||||
} else if m.state == StatePaused {
|
||||
helpText = tr.HelpIdle
|
||||
}
|
||||
b.WriteString(helpStyle.Render(helpText))
|
||||
|
||||
case StateSettings:
|
||||
b.WriteString(lipgloss.NewStyle().Bold(true).Render(tr.SettingsTitle) + "\n\n")
|
||||
|
||||
settings := []struct {
|
||||
label string
|
||||
value int
|
||||
unit string
|
||||
}{
|
||||
{tr.LabelFocus, m.editFocus, tr.MinutesShort},
|
||||
{tr.LabelShortBreak, m.editShort, tr.MinutesShort},
|
||||
{tr.LabelLongBreak, m.editLong, tr.MinutesShort},
|
||||
{tr.LabelLongAfter, m.editAfter, "pomodorů"},
|
||||
}
|
||||
if m.lang == "en" {
|
||||
settings[3].unit = "pomodoros"
|
||||
}
|
||||
|
||||
for i, s := range settings {
|
||||
cursor := " "
|
||||
lStyle := labelStyle
|
||||
vStyle := valueStyle
|
||||
if i == m.settingCursor {
|
||||
cursor = "> "
|
||||
lStyle = settingActiveStyle
|
||||
vStyle = settingActiveStyle
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("%s%-22s %s %s\n",
|
||||
cursor,
|
||||
lStyle.Render(s.label),
|
||||
vStyle.Render(fmt.Sprintf("%d", s.value)),
|
||||
labelStyle.Render(s.unit),
|
||||
))
|
||||
}
|
||||
b.WriteString("\n" + helpStyle.Render(tr.HelpSettings))
|
||||
|
||||
case StateStats:
|
||||
b.WriteString(lipgloss.NewStyle().Bold(true).Render(tr.StatsTitle) + "\n\n")
|
||||
b.WriteString(fmt.Sprintf(" %-28s %s\n",
|
||||
labelStyle.Render(tr.PomodorosToday),
|
||||
valueStyle.Render(fmt.Sprintf("%d 🍅", m.stats.TodayPomodoros)),
|
||||
))
|
||||
b.WriteString(fmt.Sprintf(" %-28s %s\n",
|
||||
labelStyle.Render(tr.PomodorosTotal),
|
||||
valueStyle.Render(fmt.Sprintf("%d 🍅", m.stats.TotalPomodoros)),
|
||||
))
|
||||
b.WriteString(fmt.Sprintf("\n %-28s %d %s\n",
|
||||
labelStyle.Render(tr.LabelFocus),
|
||||
m.stats.FocusDuration,
|
||||
tr.MinutesShort,
|
||||
))
|
||||
b.WriteString(fmt.Sprintf(" %-28s %d %s\n",
|
||||
labelStyle.Render(tr.LabelShortBreak),
|
||||
m.stats.ShortBreak,
|
||||
tr.MinutesShort,
|
||||
))
|
||||
b.WriteString(fmt.Sprintf(" %-28s %d %s\n",
|
||||
labelStyle.Render(tr.LabelLongBreak),
|
||||
m.stats.LongBreak,
|
||||
tr.MinutesShort,
|
||||
))
|
||||
b.WriteString("\n" + helpStyle.Render(tr.HelpStats))
|
||||
}
|
||||
|
||||
content := b.String()
|
||||
if m.width > 0 && m.height > 0 {
|
||||
return appStyle.Width(m.width - 6).Height(m.height - 4).Render(content)
|
||||
}
|
||||
return appStyle.Render(content)
|
||||
}
|
||||
|
||||
func (m model) getStateLabel() string {
|
||||
tr := Translations[m.lang]
|
||||
switch m.state {
|
||||
case StateIdle:
|
||||
return tr.StateIdle
|
||||
case StateRunning:
|
||||
return tr.StateRunning
|
||||
case StatePaused:
|
||||
return tr.StatePaused
|
||||
case StateBreak:
|
||||
return tr.StateBreak
|
||||
case StateLongBreak:
|
||||
return tr.StateLongBreak
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m model) renderTimer(s string) string {
|
||||
large := lipgloss.NewStyle().Bold(true).PaddingLeft(1).PaddingRight(1)
|
||||
switch m.state {
|
||||
case StateRunning:
|
||||
return timerFocusStyle.Copy().Inherit(large).Render(s)
|
||||
case StatePaused:
|
||||
return timerPauseStyle.Copy().Inherit(large).Render(s)
|
||||
case StateBreak, StateLongBreak:
|
||||
return timerBreakStyle.Copy().Inherit(large).Render(s)
|
||||
default:
|
||||
return timerPauseStyle.Copy().Inherit(large).Render(s)
|
||||
}
|
||||
}
|
||||
|
||||
// renderProgressBar renders an ASCII progress bar
|
||||
func renderProgressBar(remaining, total, width int) string {
|
||||
if total == 0 {
|
||||
return ""
|
||||
}
|
||||
elapsed := total - remaining
|
||||
filled := int(float64(elapsed) / float64(total) * float64(width))
|
||||
if filled > width {
|
||||
filled = width
|
||||
}
|
||||
bar := "[" + strings.Repeat("█", filled) + strings.Repeat("░", width-filled) + "]"
|
||||
pct := int(float64(elapsed) / float64(total) * 100)
|
||||
return fmt.Sprintf("%s %3d%%", bar, pct)
|
||||
}
|
||||
|
||||
// formatDuration formats seconds as MM:SS
|
||||
func formatDuration(secs int) string {
|
||||
if secs < 0 {
|
||||
secs = 0
|
||||
}
|
||||
m := secs / 60
|
||||
s := secs % 60
|
||||
return fmt.Sprintf("%02d:%02d", m, s)
|
||||
}
|
||||
Reference in new issue
Block a user