new-doxa-monorepo

This commit is contained in:
kankys committed 2026-08-07 09:48:59 +02:00
1 parent b4401527a3
commit 5eacbb0182
162 files changed
+21820 -2

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
# **🇬🇧 DOXA-CLI PROJECT**
##### **🇨🇿 [Czech Version](About_CZ.md)**
#### **Technical and architectural manifesto of a minimalist terminal-based note-taking application**
**Project goal:** To create a companion to the web-based [DOXA Notes](https://doxa-notes.org/), bringing the exact same philosophy into the focused environment of the command line. Built with the Go language, DOXA-CLI operates entirely locally, utilizing the identical open JSON data standard without relying on external clouds or complex frameworks.
## **1\. TECHNICAL ARCHITECTURE**
To maintain maximum speed, raw performance, and absolute minimalism, DOXA-CLI is built on solid foundations:
Language: Pure Go (Golang) ensuring native compilation, high execution speed, and zero heavy runtime dependencies.
Interface: Terminal User Interface (CLI) designed to keep the mind free from graphical clutter and focused purely on text.
Data layer: Fully compatible JSON format shared with the web version. Notes contain unique timestamps, identifiers, content, tags.
## **2\. DATA FORMAT STRUCTURE (JSON)**
Ensuring seamless interoperability with the web version, DOXA-CLI reads and writes data using the standard shared schema:
JSON
\[
{
"id":
"name": " ",
"content": " ",
"tags": \[
" "
\],
"folder": " ", // unused/empty
"created\_at": " ",
"modified\_at": " "
}
\]
## **3\. LOCAL EXECUTION & PHILOSOPHY**
**Architecture (Purely local execution)**
**Technical storage solution** \- Data resides directly on your local disk in a standardized JSON file (doxa\_notes.json), giving you absolute control with zero network latency.
**Security aspect** \- Total self-reliance. Your thoughts remain entirely on your machine.
## **License**
The application is open-source and licensed under the MIT license.
\--
"The terminal asks for nothing, distracts by nothing, and offers total control. Bring order to your thoughts through pure code." — Stoic reference from Kankys
+43
View File
@@ -0,0 +1,43 @@
# **🇨🇿 PROJEKT DOXA-CLI**
#### **Technický a architektonický manifest minimalistické terminálové poznámkové aplikace**
**Záměr projektu:** Vytvořit věrnou sestru webové verze [DOXA Notes](https://doxa-notes.org/), která přenáší stejnou myšlenku do soustředěného prostředí příkazové řádky. Aplikace je napsaná v jazyce Go, funguje zcela lokálně a sdílí identický otevřený formát JSON bez závislosti na cloudu či složitých knihovnách.
## **1\. TECHNICKÁ ARCHITEKTURA**
Pro zachování maximální rychlosti, surového výkonu a absolutního minimalismu je DOXA-CLI postavena na pevných základech:
Jazyk: Čisté Go (Golang), které zaručuje nativní kompilaci, vysokou rychlost provedení a žádné zbytečné externí závislosti.
Rozhraní: Terminálové rozhraní (CLI), které zbavuje mysl grafického chaosu a nechává vyniknout čistému textu.
Datová vrstva: Plně kompatibilní formát JSON sdílený s webovou verzí. Poznámky obsahují přesná časová razítka, identifikátory, obsah, štítky.
## **2\. STRUKTURA DATOVÉHO FORMÁTU (JSON)**
Pro zajištění dokonalé propojenosti s webovou verzí čte a zapisuje DOXA-CLI data ve stejném sdíleném schématu:
JSON
\[
{
"id":
"name": " ",
"content": " ",
"tags": \[
" "
\],
"folder": " ", // nepoužito/prázdné
"created\_at": " ",
"modified\_at": " "
}
\]
## **3\. LOKÁLNÍ SPUŠTĚNÍ A FILOZOFIE**
**Architektura (Čistě lokální běh)**
**Technické řešení úložiště** \- Data sídlí přímo na tvém lokálním disku ve standardizovaném souboru JSON (doxa\_notes.json), což ti dává absolutní kontrolu bez jakékoliv závislosti na síti.
**Bezpečnostní aspekt** \- Úplná soběstačnost. Tvoje myšlenky zůstávají pevně na tvém stroji.
## **Licence**
Aplikace je open-source a licencována pod MIT licencí.
\--
"Terminál nežádá nic, neodvádí pozornost ničím a nabízí úplnou kontrolu. Vnes řád do svých myšlenek skrze čistý kód."
+18
View File
@@ -0,0 +1,18 @@
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.
+223
View File
@@ -0,0 +1,223 @@
# 🦅 USER MANUAL (DOXA-CLI)
**DOXA-CLI** is a minimalist, local-first command-line notes management tool. It is designed to bring the same philosophy of focus and utility from the web-based [DOXA Notes](https://doxa-notes.org/) into your terminal, utilizing the exact same open JSON data standard without relying on external clouds. Written in **Go**, it offers a modern Terminal User Interface (**TUI**) built with Charm's Bubble Tea and Lip Gloss.
<p align="center">
<img src="https://img.shields.io/badge/go-1.22+-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go 1.22+">
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT license">
<img src="https://img.shields.io/badge/tested-Linux-brightgreen?style=flat-square" alt="Tested on Linux">
</p>
<p align="center">
<img src="https://img.shields.io/badge/freebsd-AB2B28?style=for-the-badge&logo=freebsd&logoColor=white" alt="freeBSD">
</p>
---
## TABLE OF CONTENTS
1. [Installation & Execution](#1-installation--execution)
2. [Storage Architecture & Schema](#2-storage-architecture--schema)
3. [Interactive Interface & States](#3-interactive-interface--states)
4. [Keyboard Shortcuts Reference](#4-keyboard-shortcuts-reference)
5. [Export System Details](#5-export-system-details)
6. [Uninstallation](#6-uninstallation)
---
## 1. INSTALLATION & EXECUTION
### Prerequisites
Before installing, make sure you have **Go** installed on your system (version **1.20** or higher is recommended).
### Installation Steps (Linux / macOS)
1. **Clone the repository:**
```bash
git clone https://codeberg.org/Kankys/doxa-cli.git
cd doxa-cli
```
2. **Run the installation script:**
```bash
chmod +x install.sh
./install.sh
```
*The script checks your Go installation, downloads all Go modules, compiles the binary, installs the executable `doxa-cli` to `~/.local/bin/`, and prepares an empty JSON database file at `~/.doxa-cli/doxa_notes.json`.*
3. **Add to PATH (if necessary):**
If the `doxa-cli` command is not recognized, add the following line to your shell profile (e.g., `~/.bashrc` or `~/.zshrc`):
```bash
export PATH=$PATH:$HOME/.local/bin
```
Then restart your terminal or reload your profile using `source ~/.bashrc`.
### Post-Installation Cleanup
Once completed, you can safely remove the cloned repository directory:
```bash
rm -rf doxa-cli
```
# **Operation and installation of doxa-cli on FreeBSD**
Building the application for FreeBSD does not always work correctly, if the instructions below do not help you, you can run it directly from the repository. And in .bashrc, make an alias for `doxa-cli`. You can run the application from the repository directly from the directory where it is cloned with the command `go run .`.
The FreeBSD system has its own specifics - it uses different default shells, kernel module management and interpreter paths than Linux. For a problem-free operation of the application, proceed as follows:
## **1\. Editing the installation script (install.sh)**
Before running the installation, it is necessary to check the header (shebang) of the install.sh script, because FreeBSD strictly requires the correct path to the command interpreter:
> * Open install.sh and make sure that the first line refers to the universal path, not hard-coded to the Linux bash:
```bash
\#\!/usr/bin/env bash
```
## **2\. Running the installation**
Standard cloning and running procedure:
```bash
git clone https://codeberg.org/Kankys/doxa-cli.git
cd doxa-cli
chmod \+x install.sh
./install.sh
rm \-rf doxa-cli
```
*The script will automatically compile the application and place the binary file in the \~/.local/bin/ directory.*
## **3\. Setting the PATH environment variable for FreeBSD (sh shell)**
FreeBSD uses /bin/sh as the default shell, which does not support the source command. To permanently and immediately enable the doxa-cli command, follow these steps:
> 1. **Add the path to the profile:**
```bash
echo 'export PATH="$PATH:$HOME/.local/bin"' \>\> \~/.profile
```
> 2. **Load the changes into the current terminal (using a dot, not the source command):**
```bash
. ~/.profile
```
---
## 2. STORAGE ARCHITECTURE & SCHEMA
The application runs **strictly locally**, keeping all your ideas entirely on your machine.
- **Storage Location:** `~/.doxa-cli/doxa_notes.json` (falls back to a local `doxa_notes.json` in the current working directory if the user home directory cannot be resolved).
- **Backups:** To backup your notes, simply copy `doxa_notes.json` to a backup location regularly.
### JSON Data Structure
Notes are written using a standardized JSON schema, ensuring perfect interoperability with the DOXA web client:
```json
[
{
"id": 1722240000000,
"name": "My Note",
"content": "Note content with support for multi-line strings.",
"tags": ["personal", "work"],
"folder": "", // unused/empty
"created_at": "2026-07-29T14:30:00Z",
"modified_at": "2026-07-29T14:35:00Z"
}
]
```
---
## 3. INTERACTIVE INTERFACE & STATES
The application automatically scales and adjusts to your active terminal window size:
### A. Notes List (Main View) (`StateViewNotes`)
The dashboard displays your list of notes.
- Scroll through notes using the arrow keys or `j`/`k`.
- The selected note expands to display its full content, ID, and tags.
- The bottom bar contains available key shortcuts and a colored status bar showing operation confirmations.
### B. Add / Edit Note Forms (`StateAddNote` / `StateEditNote`)
An interactive form with three inputs:
1. **Name:** (Required) Name of the note.
2. **Content:** (Required) Large multi-line text input.
3. **Tags:** (Optional) Comma-separated list of tags.
*Use the `Tab` or `Shift+Tab` keys to traverse between form inputs.*
### C. Search Field (`StateSearch`)
Allows you to perform a case-insensitive full-text filter on the notes database. Matches are evaluated across names, contents, and tags.
### D. Browse Tags Screen (`StateBrowse`)
A specialized menu that displays a unique collection of all tags currently in use.
- Press `t` to list all tags.
- Select an item and press `Enter` to filter the main note list by your choice.
### E. Delete Confirmation Dialogue (`StateConfirmDelete`)
A safety box showcasing the name and a snippet of the note selected for deletion, requiring a strict `y` (Yes) or `n` (No) keypress to prevent accidental loss of data.
---
## 4. KEYBOARD SHORTCUTS REFERENCE
### Global Commands
| Key | Action |
| :--- | :--- |
| `ctrl + c` | Instantly exit the application from any screen |
| `q` / `esc` | Go back to the previous screen / Cancel current action / Quit application (from the main view) |
### Notes List (Main View) Commands (`StateViewNotes`)
| Key | Action |
| :--- | :--- |
| `↑` / `k` | Move selection up |
| `↓` / `j` | Move selection down |
| `1` | Create a new note |
| `2` / `Enter` | Edit the selected note |
| `3` | Export the selected note to a raw text file |
| `4` | Delete the selected note (requires confirmation) |
| `/` | Start searching |
| `b` | Browse by Tag |
| `l` | Toggle user interface language (CS <=> EN) |
### Form Inputs (Add / Edit Note)
| Key | Action |
| :--- | :--- |
| `Tab` | Move cursor focus to the next field (Name -> Content -> Tags -> Name...) |
| `Shift + Tab` | Move cursor focus to the previous field |
| `Enter` | **Inside Content field:** Inserts a new line (carriage return).<br>**Inside any other field:** Saves/submits the form. |
### Browse Mode (`StateBrowse`)
| Key | Action |
| :--- | :--- |
| `t` | Display **Tags** list |
| `↑` / `k` / `↓` / `j` | Scroll through the items |
| `Enter` | Filter main note list by the selected tag |
---
## 5. EXPORT SYSTEM DETAILS
Pressing key `3` on a note in the main list outputs its contents to a plain `.txt` file:
- **Export Directory:** `~/doxa-cli-txt/` (automatically created).
- **Output Filename:** `<Note_Name>_<ID>.txt` (spaces are automatically escaped into underscores).
- **Exported File Schema:**
*(Note: The exported header fields are written using Czech labels as hardcoded in the Go export engine).*
```text
Název: <Note Name>
Tagy: <tag1, tag2, ...>
Vytvořeno: YYYY-MM-DD HH:MM
<Full Text Note Content>
```
---
## 6. UNINSTALLATION
To fully uninstall DOXA-CLI from your machine:
1. **Remove the executable binary:**
```bash
rm ~/.local/bin/doxa-cli
```
2. **Remove the configuration and local database (Warning: This will delete all of your notes!):**
```bash
rm -rf ~/.doxa-cli
```
+224
View File
@@ -0,0 +1,224 @@
# 🦅 NÁVOD K POUŽITÍ (DOXA-CLI)
**DOXA-CLI** je minimalistický, lokálně orientovaný nástroj pro správu poznámek z příkazové řádky. Slouží jako přímý ekvivalent k webové verzi [DOXA Notes](https://doxa-notes.org/), se kterou sdílí filozofii soustředění a identický datový formát JSON. Aplikace je napsaná v jazyce **Go** a využívá moderní terminálové rozhraní (**TUI**) postavené na knihovnách Bubble Tea a Lip Gloss od Charm.
<p align="center">
<img src="https://img.shields.io/badge/go-1.22+-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go 1.22+">
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT license">
<img src="https://img.shields.io/badge/tested-Linux-brightgreen?style=flat-square" alt="Tested on Linux">
</p>
<p align="center">
<img src="https://img.shields.io/badge/freebsd-AB2B28?style=for-the-badge&logo=freebsd&logoColor=white" alt="freeBSD">
</p>
---
## OBSAH
1. [Instalace a spuštění](#1-instalace-a-spuštění)
2. [Chování a struktura úložiště](#2-chování-a-struktura-úložiště)
3. [Uživatelské rozhraní a stavy](#3-uživatelské-rozhraní-a-stavy)
4. [Klávesové zkratky a ovládání](#4-klávesové-zkratky-a-ovládání)
5. [Detaily o exportu](#5-detaily-o-exportu)
6. [Odinstalace](#6-odinstalace)
---
## 1. INSTALACE A SPUŠTĚNÍ
### Systémové předpoklady
Před instalací je nutné mít na systému nainstalované vývojové prostředí **Go** (verze **1.20** nebo vyšší).
### Postup instalace (Linux / macOS)
1. **Stažení repozitáře:**
```bash
git clone https://codeberg.org/Kankys/doxa-cli.git
cd doxa-cli
```
2. **Spuštění instalačního skriptu:**
```bash
chmod +x install.sh
./install.sh
```
*Instalační skript automaticky stáhne závislosti Go, zkompiluje aplikaci, přesune binární soubor `doxa-cli` do adresáře `~/.local/bin/` a vytvoří výchozí prázdnou databázi v `~/.doxa-cli/doxa_notes.json`.*
3. **Přidání do PATH (pokud je potřeba):**
Pokud po instalaci nelze příkaz `doxa-cli` spustit, přidejte do svého profilu (např. `~/.bashrc` nebo `~/.zshrc`) následující řádek:
```bash
export PATH=$PATH:$HOME/.local/bin
```
Následně restartujte terminál nebo načtěte změny pomocí `source ~/.bashrc`.
### Úklid po instalaci
Jakmile skript doběhne, složku s klonovaným repozitářem můžete bezpečně odstranit:
```bash
rm -rf doxa-cli
```
# **Provoz a instalace doxa-cli na FreeBSD**
Build aplikace pro FreeBSD nefunguje vždy korektně, pokud vám nepomůže návod níže je možné ji spouštět přímo z repozitáře. A v .bashrc si udělat alias pro `doxa-cli`. Alikaci z repozitáře spustíte přímo z adresáře, kde je klonován příkazem `go run .`.
Systém FreeBSD má svá specifika – používá odlišné výchozí shelly, správu modulů jádra a cesty k interpretům než Linux. Pro bezproblémový chod aplikace postupuj následovně:
## **1\. Úprava instalačního skriptu (install.sh)**
Před spuštěním instalace je nutné zkontrolovat hlavičku (shebang) skriptu install.sh, protože FreeBSD striktně vyžaduje správnou cestu k interpretu příkazů:
> * Otevři install.sh a ujisti se, že první řádek odkazuje na univerzální cestu, nikoli natvrdo na linuxový bash:
```bash
\#\!/usr/bin/env bash
```
## **2\. Spuštění instalace**
Standardní postup klonování a spuštění:
```bash
git clone https://codeberg.org/Kankys/doxa-cli.git
cd doxa-cli
chmod \+x install.sh
./install.sh
rm \-rf doxa-cli
```
*Skript automaticky zkompiluje aplikaci a umístí binární soubor do adresáře \~/.local/bin/.*
## **3\. Nastavení proměnné prostředí PATH pro FreeBSD (shell sh)**
FreeBSD používá ve výchozím stavu jako standardní shell /bin/sh, který nepodporuje příkaz source. Pro trvalé i okamžité zprístupnění příkazu doxa-cli proveď tyto kroky:
> 1. **Přidej cestu do profilu:**
```bash
echo 'export PATH="$PATH:$HOME/.local/bin"' \>\> \~/.profile
```
> 2. **Načti změny v aktuálním terminálu (pomocí tečky, nikoliv příkazu source):**
```bash
. ~/.profile
```
---
## 2. CHOVÁNÍ A STRUKTURA ÚLOŽIŠTĚ
Aplikace funguje **zcela offline** a data ukládá na váš lokální disk.
- **Umístění databáze:** `~/.doxa-cli/doxa_notes.json` (na systémech, kde nelze detekovat domovský adresář, se vytvoří `doxa_notes.json` v aktuálním pracovním adresáři).
- **Záloha:** Pro zálohu stačí zkopírovat soubor `doxa_notes.json` na bezpečné místo.
### Formát dat (JSON)
Každá poznámka v JSON poli obsahuje následující strukturu kompatibilní s webovou verzí DOXA Notes:
```json
[
{
"id": 1722240000000,
"name": "Moje Poznámka",
"content": "Obsah poznámky s podporou více řádků.",
"tags": ["osobní", "práce"],
"folder": "", // nepoužito/prázdné
"created_at": "2026-07-29T14:30:00Z",
"modified_at": "2026-07-29T14:35:00Z"
}
]
```
---
## 3. UŽIVATELSKÉ ROZHRANÍ A STAVY
Aplikace se dynamicky přizpůsobuje velikosti terminálu a nabízí několik stavů:
### A. Hlavní seznam poznámek (`StateViewNotes`)
Zde vidíte přehled všech uložených poznámek.
- Pomocí šipek vybíráte aktivní poznámku.
- U vybrané poznámky se zobrazí její kompletní obsah, ID a přiřazené tagy.
- Ve spodní části obrazovky se nachází menu rychlých akcí a stavový řádek (ukazuje potvrzení o uložení, úpravě či exportu).
### B. Formulář pro Přidání / Úpravu poznámky (`StateAddNote` / `StateEditNote`)
Interaktivní rozhraní se 3 vstupními poli:
1. **Název:** (Povinné) Název vaší poznámky.
2. **Obsah:** (Povinné) Víceřádkové pole pro text poznámky.
3. **Tagy:** (Nepovinné) Štítky oddělené čárkou.
*Pohyb mezi poli se provádí klávesou `Tab` (dopředu) nebo `Shift+Tab` (dozadu).*
### C. Vyhledávání (`StateSearch`)
Umožňuje fulltextově filtrovat seznam poznámek. Vyhledává se shoda v názvu, obsahu i v jednotlivých tazích (velikost písmen se ignoruje).
### D. Procházení Tagů (`StateBrowse`)
Umožňuje rychle vyfiltrovat poznámky na základě existujících tagů.
- Klávesou `t` zobrazíte seznam všech existujících tagů.
- Výběrem položky a stisknutím `Enter` aplikujete filtr na hlavní seznam poznámek.
### E. Potvrzení smazání (`StateConfirmDelete`)
Bezpečnostní mezikrok, který zobrazí detail odstraňované poznámky a vyžaduje explicitní potvrzení stisknutím klávesy `y` (Ano) nebo `n` (Ne).
---
## 4. KLÁVESOVÉ ZKRATKY A OVLÁDÁNÍ
### Globální ovládání
| Klávesa | Akce |
| :--- | :--- |
| `ctrl + c` | Okamžité ukončení aplikace z jakéhokoliv stavu |
| `q` / `esc` | Návrat do předchozího stavu / Zrušení akce / Ukončení aplikace (z hlavního seznamu) |
### Ovládání v Hlavním seznamu poznámek (`StateViewNotes`)
| Klávesa | Akce |
| :--- | :--- |
| `↑` / `k` | Posunout výběr nahoru |
| `↓` / `j` | Posunout výběr dolů |
| `1` | Přidat novou poznámku (otevře formulář) |
| `2` / `Enter` | Upravit vybranou poznámku (otevře formulář) |
| `3` | Exportovat vybranou poznámku |
| `4` | Smazat vybranou poznámku (otevře potvrzovací dialog) |
| `/` | Otevřít vyhledávání |
| `l` | Přepnout jazyk rozhraní (CS <=> EN) |
### Ovládání ve formulářích (Přidání / Úprava)
| Klávesa | Akce |
| :--- | :--- |
| `Tab` | Přechod na další pole (Název -> Obsah -> Tagy -> Název...) |
| `Shift + Tab` | Přechod na předchozí pole |
| `Enter` | **Pokud jste v poli Obsah:** Vloží nový řádek.<br>**Pokud jste v jakémkoliv jiném poli:** Uloží poznámku. |
### Ovládání v režimu Procházení (`StateBrowse`)
| Klávesa | Akce |
| :--- | :--- |
| `t` | Přepnout na seznam **Tagů** |
| `↑` / `k` / `↓` / `j` | Pohyb v seznamu |
| `Enter` | Filtrovat hlavní seznam podle vybrané tagu |
---
## 5. DETAILY O EXPORTU
Při stisknutí klávesy `3` na vybrané poznámce v hlavním seznamu dojde k exportu poznámky do čistého textového souboru.
- **Adresář exportu:** `~/doxa-cli-txt/` (bude automaticky vytvořen v domovském adresáři).
- **Název exportovaného souboru:** `<Nazev_Poznamky>_<ID>.txt` (mezery v názvu jsou nahrazeny podtržítky).
- **Vnitřní struktura exportovaného souboru:**
*(Poznámka: Hlavička exportu je generována s českými popisky bez ohledu na zvolený jazyk rozhraní).*
```text
Název: <Název poznámky>
Tagy: <tag1, tag2, ...>
Vytvořeno: RRRR-MM-DD HH:MM
<Kompletní textový obsah poznámky>
```
---
## 6. ODINSTALACE
Pokud se rozhodnete aplikaci ze systému odstranit, postupujte takto:
1. **Smazání spustitelného souboru:**
```bash
rm ~/.local/bin/doxa-cli
```
2. **Smazání databáze a konfigurace (Upozornění: Smaže všechny vaše poznámky!):**
```bash
rm -rf ~/.doxa-cli
```
+91
View File
@@ -0,0 +1,91 @@
DOXA-CLI
DOXA-CLI is a command-line tool for managing your notes. It allows you to add, view, edit, delete, export notes, as well as filter and search them. The application is written in Go and offers a modern Terminal User Interface (TUI).
<p align="center">
<img src="https://img.shields.io/badge/go-1.22+-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go 1.22+">
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT license">
<img src="https://img.shields.io/badge/tested-Linux-brightgreen?style=flat-square" alt="Tested on Linux">
</p>
<p align="center">
<img src="https://img.shields.io/badge/freebsd-AB2B28?style=for-the-badge&logo=freebsd&logoColor=white" alt="freeBSD">
</p>
## Prerequisites
Before installing DOXA-CLI, it is assumed that you have **Go installed on your system. The recommended version is 1.20 or higher.
You can download Go from the official website: [https://go.dev/doc/install](https://go.dev/doc/install)
![dashboard](screen/dashboard.png)
## Installation
To install DOXA-CLI on your system, follow these steps:
1. **Clone the repository**:
Get the latest version of the code from Git:
``` bash
git clone https://codeberg.org/Kankys/doxa-cli.git
cd doxa-cli # Go to the project directory
```
2. **Run the installation script**:
``` bash
chmod +x install.sh
./install.sh
```
The installation script will do the following:
* Check if you have Go installed.
* Download all necessary Go modules and project dependencies.
* Compile the application for your operating system.
* Install the `doxa-cli` executable file in the `$HOME/.local/bin/` directory.
* Creates the `~/doxa-cli` configuration directory and the `doxa_notes.json` file if they do not exist.
3. **Add to PATH (if necessary)**:
After successful installation, you should be able to run `doxa-cli` from another location in the terminal. If the `doxa-cli` command is not recognized, it is assumed that the `$HOME/.local/bin` directory is not in your `PATH` system variable.
You can fix this by adding the following line to your shell profile (e.g. `~/.bashrc` for Bash, `~/.zshrc` for Zsh, or `~/.profile` for other systems):
``` bash
export PATH=$PATH:$HOME/.local/bin
```
After editing the profile file, **restart the terminal** or load the profile (e.g. `source ~/.bashrc`).
## Post-installation cleanup
Once the installation is successfully completed, the source files in the project directory are no longer needed. You can delete the entire folder with the cloned repository:
``` bash
rm -rf doxa-cli
```
## Usage
After successful installation, you can run the application from another directory by typing:
``` bash
doxa-cli
```
For detailed control in the TUI interface, follow the help displayed directly in the application.
## Backup
To backup your notes, you must save the `doxa_notes.json` file from the `~/.doxa-cli` configuration directory anywhere in your backup directory.
**Note**: The `doxa_notes.json` file contains all your notes and it is important to save it regularly.
## Uninstall
To uninstall DOXA-CLI, follow these steps:
1. **Remove the executable**:
Remove the binary from your PATH:
``` bash
rm ~/.local/bin/doxa-cli
```
*(If you installed the binary in a different directory, use its path.)*
2. **Remove the configuration directory (optional)**:
If you want to remove all application data and configuration, the configuration directory:
``` bash
rm -rf ~/.doxa-cli
```
**WARNING**: This step will delete ALL your notes. If you want to keep your data, skip this step.
+93
View File
@@ -0,0 +1,93 @@
DOXA-CLI
DOXA-CLI je nástroj pro správu vašich poznámek z příkazové řádky. Umožňuje přidávat, zobrazovat, upravovat, mazat, exportovat poznámky a také je filtrovat a vyhledávat. Aplikace je napsána v Go a nabízí moderní terminálové uživatelské rozhraní (TUI).
<p align="center">
<img src="https://img.shields.io/badge/go-1.22+-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go 1.22+">
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT license">
<img src="https://img.shields.io/badge/tested-Linux-brightgreen?style=flat-square" alt="Tested on Linux">
</p>
<p align="center">
<img src="https://img.shields.io/badge/freebsd-AB2B28?style=for-the-badge&logo=freebsd&logoColor=white" alt="freeBSD">
</p>
## Předpoklady
Před instalací DOXA-CLI se ujistěte, že máte na svém systému nainstalovaný **Go**. Doporučená verze je 1.20 nebo vyšší.
Stáhnout Go můžete z oficiálního webu: [https://go.dev/doc/install](https://go.dev/doc/install)
![dashboard](screen/dashboard.png)
## Instalace
Pro instalaci DOXA-CLI na váš systém postupujte podle následujících kroků:
1. **Naklonujte repozitář**:
Získejte nejnovější verzi kódu z Gitea:
```bash
git clone https://codeberg.org/Kankys/doxa-cli.git
cd doxa-cli # Přejděte do adresáře projektu
```
2. **Spusťte instalační skript**:
```bash
chmod +x install.sh
./install.sh
```
Instalační skript provede následující:
* Zkontroluje, zda máte nainstalované Go.
* Stáhne všechny potřebné Go moduly a závislosti projektu.
* Zkompiluje aplikaci pro váš operační systém.
* Nainstaluje spustitelný soubor `doxa-cli` do adresáře `$HOME/.local/bin/`.
* Vytvoří konfigurační adresář `~/.doxa-cli` a soubor `doxa_notes.json`, pokud neexistují.
3. **Přidání do PATH (pokud je nutné)**:
Po úspěšné instalaci byste měli být schopni spustit `doxa-cli` z jakéhokoli místa v terminálu. Pokud příkaz `doxa-cli` není rozpoznán, je pravděpodobné, že adresář `$HOME/.local/bin` není ve vaší systémové proměnné `PATH`.
Můžete to opravit přidáním následujícího řádku do vašeho shell profilu (např. `~/.bashrc` pro Bash, `~/.zshrc` pro Zsh, nebo `~/.profile` pro jiné systémy):
```bash
export PATH=$PATH:$HOME/.local/bin
```
Po úpravě souboru profilu **restartujte terminál** nebo načtěte profil (např. `source ~/.bashrc`).
## Úklid po instalaci
Jakmile je instalace úspěšně dokončena, zdrojové soubory v adresáři projektu již nejsou potřeba. Celou složku s klonovaným repozitářem můžete smazat:
```bash
rm -rf doxa-cli
```
## Použití
Po úspěšné instalaci můžete aplikaci spustit z jakéhokoliv adresáře zadáním:
```bash
doxa-cli
```
Pro detailní ovládání v TUI rozhraní se řiďte nápovědou zobrazovanou přímo v aplikaci.
## Záloha
Pro zálohu vašich poznámek je nutné si uložit soubor `doxa_notes.json` z konfiguračního adresáře `~/.doxa-cli` kamkoliv do vašeho záložního adresáře.
**Poznámka**: Soubor `doxa_notes.json` obsahuje všechny vaše poznámky a je důležité si jej pravidelně uložit.
## Odinstalace
Pro odinstalaci DOXA-CLI postupujte takto:
1. **Odstraňte spustitelný soubor**:
Odstraňte binárku aplikace z vašeho PATH:
```bash
rm ~/.local/bin/doxa-cli
```
*(Pokud jste binárku nainstalovali do jiného adresáře, použijte jeho cestu.)*
2. **Odstraňte konfigurační adresář (volitelné)**:
Pokud chcete odstranit i veškerá data a konfiguraci aplikace, odstraňte konfigurační adresář:
```bash
rm -rf ~/.doxa-cli
```
**UPOZORNĚNÍ**: Tento krok smaže VŠECHNY vaše poznámky. Pokud si chcete data ponechat, tento krok vynechejte.
+15
View File
@@ -0,0 +1,15 @@
![install](screen/install.png)
![dashboard](screen/dashboard.png)
![home](screen/home.png)
![new-note](screen/new-note.png)
![add-note](screen/add-note.png)
![delete-note](screen/delete.png)
![search](screen/search.png)
![tags](screen/tags.png)
+9
View File
@@ -0,0 +1,9 @@
### 🇬🇧 If you want to support this project or my future ones, I would be very grateful. All support goes towards my education or directly to the infrastructure as payments for VPS or payments for development-related services.
#### 🇨🇿 Pokud chcete podpořit tento projekt, nebo mé dálší budu vám velmi vděčný. Veškerá podpora jde na má vzdělání nebo přímo do ifrastruktury jako platby za VPS či platby za služby spojené s vývojem.
---
![podpora](screen/podpora.png)
+30
View File
@@ -0,0 +1,30 @@
module doxa-cli
go 1.26.5
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/bubbles v1.0.0 // indirect
github.com/charmbracelet/bubbletea v1.3.10 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/lipgloss v1.1.0 // 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
)
+65
View File
@@ -0,0 +1,65 @@
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.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
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.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
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.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
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.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
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.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
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.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
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/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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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=
+94
View File
@@ -0,0 +1,94 @@
#!/bin/bash
# DOXA-CLI Installation Script
# --- Nastavení ---
APP_NAME="doxa-cli"
INSTALL_DIR="$HOME/.local/bin"
CONFIG_DIR="$HOME/.doxa-cli"
CONFIG_FILE="doxa_notes.json"
# --- 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 "Stažení závislostí projektu..."
if ! go mod download
then
error_exit "Nepodařilo se stáhnout závislosti Go. Zkontrolujte GO moduly a připojení k internetu."
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. Zkontrolujte chyby v kódu."
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'. Zkontrolujte oprávnění."
fi
if ! cp "$APP_BINARY" "$INSTALL_DIR/"
then
error_exit "Nepodařilo se zkopírovat binárku do '$INSTALL_DIR'. Zkontrolujte oprávnění."
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'. Zkontrolujte oprávnění."
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, možná budete muset přidat '$INSTALL_DIR' do vašeho PATH.\nČasto se to dělá přidáním následujícího řádku do vašich shell profilů (~/.bashrc, ~/.zshrc, ~/.profile):\n\nexport PATH=\$PATH:$INSTALL_DIR\n\nPoté restartujte terminál nebo načtěte profil (např. \`source ~/.bashrc\`).\033[0m"
echo ""
# Vyčištění
rm "$APP_BINARY"
exit 0
+20
View File
@@ -0,0 +1,20 @@
package main
import (
"fmt"
"os"
tea "github.com/charmbracelet/bubbletea"
)
func main() {
// Inicializace Bubble Tea modelu pomocí NewModel() z tui.go
m := NewModel()
// Spuštění Bubble Tea programu
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)
}
}
+12
View File
@@ -0,0 +1,12 @@
package main
import "testing"
func TestTruncate(t *testing.T) {
result := truncate("Ahoj světe", 5)
expected := "Ahoj..."
if result != expected {
t.Errorf("Očekáváno %q, ale dostali jsme %q", expected, result)
}
}
+55
View File
@@ -0,0 +1,55 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
)
type Note struct {
ID int64 `json:"id"`
Name string `json:"name"`
Content string `json:"content"`
Tags []string `json:"tags"`
Folder string `json:"folder"`
CreatedAt time.Time `json:"created_at"`
ModifiedAt time.Time `json:"modified_at"`
}
func getStoragePath() string {
home, err := os.UserHomeDir()
if err != nil {
return "doxa_notes.json" // fallback do aktuálního adresáře
}
configDir := filepath.Join(home, ".doxa-cli")
// Vytvoření složky, pokud neexistuje
if _, err := os.Stat(configDir); os.IsNotExist(err) {
os.MkdirAll(configDir, 0755)
}
return filepath.Join(configDir, "doxa_notes.json")
}
func loadNotes() []Note {
path := getStoragePath()
file, err := os.ReadFile(path)
if err != nil {
return []Note{}
}
var notes []Note
json.Unmarshal(file, &notes)
return notes
}
func saveNotes(notes []Note) {
path := getStoragePath()
data, err := json.MarshalIndent(notes, "", " ")
if err != nil {
fmt.Println("Chyba při ukládání dat:", err)
return
}
os.WriteFile(path, data, 0644)
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+110
View File
@@ -0,0 +1,110 @@
package main
type Translation struct {
Title string
MenuAdd string
MenuEdit string
MenuExport string
MenuDelete string
MenuQuit string
MenuLang string
MenuSearch string
MenuBrowse string
ListTitle string
NoNotes string
AddTitle string
EditTitle string
LabelName string
LabelContent string
LabelTags string
PlaceholderName string
PlaceholderContent string
PlaceholderTags string
HelpForm string
HelpList string
StatusSaved string
StatusEdited string
StatusDeleted string
StatusExported string
ErrRequired string
SearchTitle string
BrowseTags string
AllNotes string
ConfirmDelete string
ConfirmYes string
ConfirmNo string
HelpModal string
}
var Translations = map[string]Translation{
"cs": {
Title: "DOXA-CLI 🦅",
MenuAdd: "1: Přidat",
MenuEdit: "2: Upravit",
MenuExport: "3: Export",
MenuDelete: "4: Smazat",
MenuQuit: "q: Konec",
MenuLang: "l: EN",
MenuSearch: "/: Hledat",
MenuBrowse: "b: Tagy",
ListTitle: "--- SEZNAM POZNÁMEK ---",
NoNotes: " (Prázdno...)",
AddTitle: "PŘIDAT POZNÁMKU",
EditTitle: "UPRAVIT POZNÁMKU",
LabelName: "Název:",
LabelContent: "Obsah:",
LabelTags: "Tagy:",
PlaceholderName: "Název (povinné)",
PlaceholderContent: "Enter: Nový řádek | Tab: Další pole",
PlaceholderTags: "Tagy (nepovinné, oddělené čárkou)",
HelpForm: "[Tab] Další pole | [Enter] Uložit (mimo obsah) | [q] Zrušit",
HelpList: "↑/↓: Vybrat | Enter: Upravit | /: Hledat | b: Procházet",
StatusSaved: "Poznámka uložena.",
StatusEdited: "Změny uloženy.",
StatusDeleted: "Poznámka smazána.",
StatusExported: "Poznámka vyexportována.",
ErrRequired: "Chyba: Název a obsah jsou povinné!",
SearchTitle: "HLEDAT: ",
BrowseTags: "TAGY",
AllNotes: "Všechny poznámky",
ConfirmDelete: "Opravdu chcete smazat tuto poznámku?",
ConfirmYes: "y: Ano",
ConfirmNo: "n: Ne / Esc",
HelpModal: "q: Zavřít | 2: Editovat | ↑/↓: Posun",
},
"en": {
Title: "DOXA-CLI 🦅",
MenuAdd: "1: Add",
MenuEdit: "2: Edit",
MenuExport: "3: Export",
MenuDelete: "4: Delete",
MenuQuit: "q: Quit",
MenuLang: "l: CS",
MenuSearch: "/: Search",
MenuBrowse: "b: Tags",
ListTitle: "--- NOTES LIST ---",
NoNotes: " (Empty...)",
AddTitle: "ADD NOTE",
EditTitle: "EDIT NOTE",
LabelName: "Name:",
LabelContent: "Content:",
LabelTags: "Tags:",
PlaceholderName: "Name (required)",
PlaceholderContent: "Enter: New line | Tab: Next field",
PlaceholderTags: "Tags (optional, comma separated)",
HelpForm: "[Tab] Next field | [Enter] Save (outside content) | [q] Cancel",
HelpList: "↑/↓: Select | Enter: Edit | /: Search | b: Browse",
StatusSaved: "Note saved.",
StatusEdited: "Changes saved.",
StatusDeleted: "Note deleted.",
StatusExported: "Note exported.",
ErrRequired: "Error: Name and content are required!",
SearchTitle: "SEARCH: ",
BrowseTags: "TAGS",
AllNotes: "All notes",
ConfirmDelete: "Are you sure you want to delete this note?",
ConfirmYes: "y: Yes",
ConfirmNo: "n: No / Esc",
HelpModal: "q: Close | 2: Edit | ↑/↓: Scroll",
},
}
+749
View File
@@ -0,0 +1,749 @@
package main
import (
"fmt"
"os"
"strings"
"time"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/textinput"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
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"))
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)
)
type AppState int
const (
StateStartup AppState = iota
StateViewNotes
StateViewNoteModal
StateAddNote
StateEditNote
StateSearch
StateBrowseTags
StateConfirmDelete
)
type model struct {
notes []Note
filteredNotes []Note
cursor int
state AppState
browseTags []string
filterText string
textInputs []textinput.Model
contentInput textarea.Model
searchInput textinput.Model
viewport viewport.Model
ready bool
focusIndex int
selectedNoteID int64
statusMsg string
lang string
width int
height int
}
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
ta := textarea.New()
ta.SetWidth(60)
ta.SetHeight(8)
ta.FocusedStyle.CursorLine = lipgloss.NewStyle().Background(lipgloss.Color("62"))
si := textinput.New()
si.Prompt = "🔍 "
si.Placeholder = "Hledat..."
m := model{
notes: loadNotes(),
cursor: 0,
state: StateStartup,
textInputs: inputs,
contentInput: ta,
searchInput: si,
focusIndex: 0,
lang: "cs",
}
m.applyFilter()
m.updatePlaceholders()
return m
}
func (m model) Init() tea.Cmd {
return tea.Batch(textinput.Blink, textarea.Blink, tea.EnterAltScreen)
}
func (m *model) applyFilter() {
if m.filterText == "" {
m.filteredNotes = m.notes
} else {
m.filteredNotes = []Note{}
query := strings.ToLower(m.filterText)
for _, n := range m.notes {
if strings.Contains(strings.ToLower(n.Name), query) ||
strings.Contains(strings.ToLower(n.Content), query) ||
strings.Contains(strings.ToLower(strings.Join(n.Tags, " ")), query) {
m.filteredNotes = append(m.filteredNotes, n)
}
}
}
if m.cursor >= len(m.filteredNotes) {
m.cursor = 0
if len(m.filteredNotes) > 0 {
m.cursor = len(m.filteredNotes) - 1
}
}
}
func (m *model) getBrowseTags() {
set := make(map[string]bool)
for _, n := range m.notes {
for _, t := range n.Tags {
if t != "" {
set[t] = true
}
}
}
m.browseTags = []string{}
for k := range set {
m.browseTags = append(m.browseTags, k)
}
// Manual sort
for i := 0; i < len(m.browseTags); i++ {
for j := i + 1; j < len(m.browseTags); j++ {
if m.browseTags[i] > m.browseTags[j] {
m.browseTags[i], m.browseTags[j] = m.browseTags[j], m.browseTags[i]
}
}
}
m.cursor = 0
}
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
m.contentInput.SetWidth(msg.Width - 25)
m.contentInput.SetHeight(m.height/2 - 5)
headerHeight := 6
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 = StateViewNotes
m.statusMsg = ""
return m, nil
}
if m.state == StateViewNotes {
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 = StateViewNotes
m.statusMsg = ""
return m, nil
}
if m.state == StateViewNoteModal {
m.state = StateViewNotes
// Reset viewport to full list size
headerHeight := 6
footerHeight := 3
vpHeight := m.height - headerHeight - footerHeight
if vpHeight < 1 { vpHeight = 1 }
m.viewport.Width = m.width - 6
m.viewport.Height = vpHeight
return m, nil
}
if m.state == StateViewNotes {
if m.filterText != "" {
m.filterText = ""
m.applyFilter()
return m, nil
}
return m, tea.Quit
}
m.resetToView()
return m, nil
case "l":
if m.state == StateViewNotes {
if m.lang == "cs" {
m.lang = "en"
} else {
m.lang = "cs"
}
m.updatePlaceholders()
return m, nil
}
case "/":
if m.state == StateViewNotes {
m.state = StateSearch
m.searchInput.Focus()
m.searchInput.SetValue("")
return m, nil
}
case "tab", "shift+tab":
if m.state == StateAddNote || m.state == StateEditNote {
if msg.String() == "shift+tab" {
m.focusIndex--
} else {
m.focusIndex++
}
if m.focusIndex > 2 {
m.focusIndex = 0
}
if m.focusIndex < 0 {
m.focusIndex = 2
}
return m.updateFocus()
}
case "up", "k":
if m.state == StateViewNotes && m.cursor > 0 {
m.cursor--
m.viewport.LineUp(1)
}
if m.state == StateViewNoteModal {
m.viewport.LineUp(1)
}
if m.state == StateBrowseTags && m.cursor > 0 {
m.cursor--
}
case "down", "j":
if m.state == StateViewNotes {
max := len(m.filteredNotes)
if m.cursor < max-1 {
m.cursor++
m.viewport.LineDown(1)
}
}
if m.state == StateViewNoteModal {
m.viewport.LineDown(1)
}
if m.state == StateBrowseTags {
max := len(m.browseTags)
if m.cursor < max-1 {
m.cursor++
}
}
case "enter":
switch m.state {
case StateStartup:
m.state = StateViewNotes
case StateViewNotes:
if len(m.filteredNotes) > 0 {
m.state = StateViewNoteModal
m.selectedNoteID = m.filteredNotes[m.cursor].ID
var n Note
for _, note := range m.notes {
if note.ID == m.selectedNoteID {
n = note
break
}
}
modalContent := fmt.Sprintf("%s\n---\n[%d] %s\n\n%s\n\nTagy: %s",
tr.HelpModal,
n.ID, n.Name, n.Content, strings.Join(n.Tags, ", "),
)
// Set viewport size for modal (smaller than full screen)
modalWidth := m.width - 20
if modalWidth < 40 { modalWidth = 40 }
modalHeight := m.height - 10
if modalHeight < 10 { modalHeight = 10 }
m.viewport.Width = modalWidth
m.viewport.Height = modalHeight
m.viewport.SetContent(modalContent)
m.viewport.YOffset = 0
}
case StateSearch:
m.filterText = m.searchInput.Value()
m.applyFilter()
m.state = StateViewNotes
case StateBrowseTags:
if len(m.browseTags) > 0 {
m.filterText = m.browseTags[m.cursor]
m.applyFilter()
}
m.state = StateViewNotes
case StateAddNote:
if m.focusIndex != 1 {
m.handleAddNote()
}
case StateEditNote:
if m.focusIndex != 1 {
m.handleEditNote()
}
case StateConfirmDelete:
if m.focusIndex == 0 {
m.handleDeleteNote()
m.state = StateViewNotes
m.statusMsg = tr.StatusDeleted
return m, nil
} else {
m.state = StateViewNotes
m.statusMsg = ""
return m, nil
}
}
case "b":
if m.state == StateViewNotes {
m.state = StateBrowseTags
m.getBrowseTags()
return m, nil
}
case "1":
if m.state == StateViewNotes {
m.state = StateAddNote
m.resetInputs()
m.textInputs[0].SetValue("")
m.updatePlaceholders()
return m, nil
}
case "2":
if m.state == StateViewNotes && len(m.filteredNotes) > 0 {
m.state = StateEditNote
m.selectedNoteID = m.filteredNotes[m.cursor].ID
m.populateInputs(m.filteredNotes[m.cursor])
return m, nil
}
if m.state == StateViewNoteModal {
for _, n := range m.notes {
if n.ID == m.selectedNoteID {
m.state = StateEditNote
m.populateInputs(n)
break
}
}
return m, nil
}
case "3":
if m.state == StateViewNotes && len(m.filteredNotes) > 0 {
exportNote(m.notes, m.filteredNotes[m.cursor].ID)
m.statusMsg = tr.StatusExported
}
case "4":
if m.state == StateViewNotes && len(m.filteredNotes) > 0 {
m.state = StateConfirmDelete
m.selectedNoteID = m.filteredNotes[m.cursor].ID
m.focusIndex = 0
}
}
}
if m.state == StateSearch {
m.searchInput, cmd = m.searchInput.Update(msg)
return m, cmd
}
if m.state == StateAddNote || m.state == StateEditNote {
if m.focusIndex == 1 {
m.contentInput, cmd = m.contentInput.Update(msg)
} else {
idx := m.focusIndex
if idx > 1 {
idx--
}
m.textInputs[idx], cmd = m.textInputs[idx].Update(msg)
}
return m, cmd
}
if m.state == StateConfirmDelete {
if msg, ok := msg.(tea.KeyMsg); ok {
switch msg.String() {
case "y":
m.handleDeleteNote()
m.state = StateViewNotes
m.statusMsg = tr.StatusDeleted
return m, nil
case "n", "esc":
m.state = StateViewNotes
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()
}
m.contentInput.Blur()
m.searchInput.Blur()
switch m.focusIndex {
case 0:
cmds = append(cmds, m.textInputs[0].Focus())
m.textInputs[0].PromptStyle = focusedStyle
case 1:
cmds = append(cmds, m.contentInput.Focus())
case 2:
cmds = append(cmds, m.textInputs[1].Focus())
m.textInputs[1].PromptStyle = focusedStyle
}
return m, tea.Batch(cmds...)
}
func (m model) View() string {
tr := Translations[m.lang]
var b strings.Builder
b.WriteString(titleStyle.Render(tr.Title))
b.WriteString("\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-CLI 🦅") + "\n\n" +
"Local-First Notes & Sovereignty\n\n" +
"Enter: Start | q: Quit\n\n" +
"License: MIT | © 2026 Kankys"))
case StateSearch:
b.WriteString(tr.SearchTitle + "\n" + m.searchInput.View() + "\n\n" + tr.HelpForm)
case StateBrowseTags:
b.WriteString(fmt.Sprintf("--- %s ---\n", tr.BrowseTags))
b.WriteString("Enter: Filtrovat | q: Zpět\n\n")
for i, item := range m.browseTags {
cursor := " "
if m.cursor == i {
cursor = "> "
}
b.WriteString(fmt.Sprintf("%s%s\n", cursor, item))
}
case StateViewNotes:
filterInfo := ""
if m.filterText != "" {
filterInfo = fmt.Sprintf(" [Filtr: %s]", m.filterText)
}
var listBuilder strings.Builder
if len(m.filteredNotes) == 0 {
listBuilder.WriteString(tr.NoNotes + "\n")
} else {
for i, n := range m.filteredNotes {
if m.cursor == i {
listBuilder.WriteString(selectedStyle.Render(fmt.Sprintf("> [%d] %s", n.ID, n.Name)) + "\n")
} else {
listBuilder.WriteString(fmt.Sprintf(" [%d] %s\n", n.ID, n.Name))
}
}
}
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.MenuSearch, tr.MenuBrowse, 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 StateAddNote, StateEditNote:
title := tr.AddTitle
if m.state == StateEditNote {
title = fmt.Sprintf("%s (%d)", tr.EditTitle, m.selectedNoteID)
}
b.WriteString(title + "\n\n")
b.WriteString(fmt.Sprintf("%-8s %s\n", tr.LabelName, m.textInputs[0].View()))
b.WriteString(fmt.Sprintf("%-8s\n%s\n\n", tr.LabelContent, m.contentInput.View()))
b.WriteString(fmt.Sprintf("%-8s %s\n", tr.LabelTags, m.textInputs[1].View()))
b.WriteString("\n" + tr.HelpForm)
case StateViewNoteModal:
if m.ready {
b.WriteString(confirmBoxStyle.Render(m.viewport.View()))
} else {
b.WriteString(confirmBoxStyle.Render("Načítání..."))
}
case StateConfirmDelete:
var noteToDelete Note
found := false
for _, n := range m.notes {
if n.ID == m.selectedNoteID {
noteToDelete = n
found = true
break
}
}
if !found {
m.state = StateViewNotes
m.statusMsg = "Chyba: Poznámka k smazání nebyla nalezena!"
return ""
}
confirmText := fmt.Sprintf("%s\n\n[%d] %s\nObsah: %s\n\n%s | %s",
tr.ConfirmDelete,
noteToDelete.ID,
noteToDelete.Name,
truncate(noteToDelete.Content, 40),
tr.ConfirmYes, tr.ConfirmNo,
)
b.WriteString(confirmBoxStyle.Render(confirmText))
}
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) updatePlaceholders() {
tr := Translations[m.lang]
m.textInputs[0].Placeholder = tr.PlaceholderName
m.contentInput.Placeholder = tr.PlaceholderContent
m.textInputs[1].Placeholder = tr.PlaceholderTags
}
func (m *model) resetToView() {
m.state = StateViewNotes
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.contentInput.SetValue("")
m.searchInput.SetValue("")
m.focusIndex = 0
m.updateFocus()
}
func (m *model) populateInputs(n Note) {
m.textInputs[0].SetValue(n.Name)
m.contentInput.SetValue(n.Content)
m.textInputs[1].SetValue(strings.Join(n.Tags, ", "))
m.focusIndex = 0
m.updateFocus()
}
func (m *model) handleAddNote() {
tr := Translations[m.lang]
name := m.textInputs[0].Value()
content := m.contentInput.Value()
if name == "" || content == "" {
m.statusMsg = tr.ErrRequired
return
}
tags := []string{}
if m.textInputs[1].Value() != "" {
tags = strings.Split(m.textInputs[1].Value(), ",")
for i := range tags {
tags[i] = strings.TrimSpace(tags[i])
}
}
now := time.Now()
m.notes = append(m.notes, Note{
ID: now.UnixMilli(), Name: name, Content: content, Folder: "", Tags: tags, CreatedAt: now, ModifiedAt: now,
})
saveNotes(m.notes)
m.applyFilter()
m.statusMsg = tr.StatusSaved
m.state = StateViewNotes
}
func (m *model) handleEditNote() {
tr := Translations[m.lang]
for i, n := range m.notes {
if n.ID == m.selectedNoteID {
m.notes[i].Name = m.textInputs[0].Value()
m.notes[i].Content = m.contentInput.Value()
m.notes[i].Folder = ""
tags := []string{}
if m.textInputs[1].Value() != "" {
tags = strings.Split(m.textInputs[1].Value(), ",")
for j := range tags {
tags[j] = strings.TrimSpace(tags[j])
}
}
m.notes[i].Tags = tags
m.notes[i].ModifiedAt = time.Now()
break
}
}
saveNotes(m.notes)
m.applyFilter()
m.statusMsg = tr.StatusEdited
m.state = StateViewNotes
}
func (m *model) handleDeleteNote() {
noteIndex := -1
for i, n := range m.notes {
if n.ID == m.selectedNoteID {
noteIndex = i
break
}
}
if noteIndex != -1 {
m.notes = append(m.notes[:noteIndex], m.notes[noteIndex+1:]...)
saveNotes(m.notes)
}
m.applyFilter()
}
func exportNote(notes []Note, id int64) {
for _, note := range notes {
if note.ID == id {
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Printf("Chyba při zjišťování domovského adresáře: %v\n", err)
return
}
exportDir := fmt.Sprintf("%s/doxa-cli-txt", homeDir)
if err := os.MkdirAll(exportDir, 0755); err != nil {
fmt.Printf("Chyba při vytváření adresáře pro export: %v\n", err)
return
}
fileName := fmt.Sprintf("%s_%d.txt", strings.ReplaceAll(note.Name, " ", "_"), note.ID)
fullPath := fmt.Sprintf("%s/%s", exportDir, fileName)
file, err := os.Create(fullPath)
if err != nil {
fmt.Printf("Chyba při vytváření souboru pro export: %v\n", err)
return
}
defer file.Close()
content := fmt.Sprintf("Název: %s\nTagy: %s\nVytvořeno: %s\n\n%s",
note.Name, strings.Join(note.Tags, ", "), note.CreatedAt.Format("2006-01-02 15:04"), note.Content)
_, _ = file.WriteString(content)
return
}
}
}
func truncate(s string, max int) string {
if len(s) > max {
return strings.TrimSpace(s[:max]) + "..."
}
return s
}
type errMsg error
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

+335
View File
@@ -0,0 +1,335 @@
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOXA cli</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: #0d1117;
color: #c9d1d9;
font-family: 'Courier New', Courier, monospace;
line-height: 1.6;
padding: 2rem 1rem;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
overflow: hidden;
}
.terminal-header {
background-color: #21262d;
padding: 0.75rem 1rem;
border-bottom: 1px solid #30363d;
display: flex;
align-items: center;
justify-content: space-between;
}
.terminal-header-left {
display: flex;
align-items: center;
}
.terminal-buttons {
display: flex;
gap: 8px;
}
.btn-circle {
width: 12px;
height: 12px;
border-radius: 50%;
}
.btn-close { background-color: #ff5f56; }
.btn-minimize { background-color: #ffbd2e; }
.btn-maximize { background-color: #27c93f; }
.terminal-title {
margin-left: 1rem;
font-size: 0.85rem;
color: #8b949e;
}
.lang-switch {
background-color: #30363d;
border: 1px solid #8b949e;
color: #f0f6fc;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.8rem;
cursor: pointer;
font-family: inherit;
transition: all 0.2s ease;
}
.lang-switch:hover {
background-color: #8b949e;
color: #0d1117;
}
.terminal-body {
padding: 2rem;
}
h1 {
color: #58a6ff;
font-size: 1.5rem;
margin-bottom: 1rem;
}
p {
margin-bottom: 1.5rem;
color: #8b949e;
}
.prompt {
color: #7ee787;
}
.command {
color: #f0f6fc;
}
pre {
background-color: #0d1117;
border: 1px solid #30363d;
padding: 1rem;
border-radius: 4px;
overflow-x: auto;
margin-bottom: 1.5rem;
color: #a5d6ff;
}
code {
font-family: 'Courier New', Courier, monospace;
}
.section-title {
color: #ffa657;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
font-size: 1.1rem;
}
ul {
list-style-type: none;
margin-bottom: 1.5rem;
padding-left: 1rem;
}
ul li::before {
content: "> ";
color: #7ee787;
}
ul li {
margin-bottom: 0.5rem;
}
a {
color: #58a6ff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.manual-highlight {
background-color: rgba(255, 166, 87, 0.1);
border-left: 4px solid #ffa657;
padding: 1rem;
margin: 1.5rem 0;
border-radius: 0 4px 4px 0;
}
.manual-highlight a {
font-weight: bold;
color: #ffa657;
text-decoration: underline;
}
.footer {
margin-top: 2rem;
border-top: 1px solid #30363d;
padding-top: 1rem;
font-size: 0.85rem;
color: #8b949e;
text-align: center;
font-style: italic;
}
/* Lang visibility styles */
[data-lang] {
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="terminal-header">
<div class="terminal-header-left">
<div class="terminal-buttons">
<div class="btn-circle btn-close"></div>
<div class="btn-circle btn-minimize"></div>
<div class="btn-circle btn-maximize"></div>
</div>
<div class="terminal-title">bash - doxa-cli</div>
</div>
<button class="lang-switch" onclick="toggleLang()" id="langBtn">EN</button>
</div>
<div class="terminal-body">
<h1>DOXA cli</h1>
<!-- CZ Mutation -->
<div data-lang="cs">
<p>DOXA-CLI je nástroj pro správu vašich poznámek z příkazové řádky. Umožňuje přidávat, zobrazovat, upravovat, mazat, exportovat poznámky a také je filtrovat a vyhledávat. Aplikace je napsána v Go a nabízí moderní terminálové uživatelské rozhraní (TUI).</p>
</div>
<!-- EN Mutation -->
<div data-lang="en">
<p>DOXA-CLI is a command-line tool for managing your notes. It allows you to add, view, edit, delete, export notes, as well as filter and search them. The application is written in Go and offers a modern Terminal User Interface (TUI).</p>
</div>
<p align="center">
<img src="https://img.shields.io/badge/go-1.22+-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go 1.22+">
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT license">
<img src="https://img.shields.io/badge/tested-Linux-brightgreen?style=flat-square" alt="Tested on Linux">
</p>
<p align="center">
<img src="https://img.shields.io/badge/freebsd-AB2B28?style=for-the-badge&logo=freebsd&logoColor=white" alt="freeBSD">
</p>
<p align="center">
<img src="./img/dashboard.png" width="100%" alt="Dashboard">
</p>
<!-- CZ Mutation -->
<div data-lang="cs">
<div class="manual-highlight">
🔥 <strong>Důležité:</strong> Podrobný návod k použití, instalaci a přehled klávesových zkratek naleznete v oficiálním manuálu:
<a href="https://codeberg.org/Kankys/doxa-cli/src/branch/master/MANUAL_CZ.md" target="_blank">Český uživatelský manuál (MANUAL_CZ.md)</a>
</div>
<div class="section-title">$ instalace</div>
<pre><code>git clone https://codeberg.org/Kankys/doxa-cli.git
cd doxa-cli
chmod +x install.sh
./install.sh</code></pre>
<div class="section-title">$ vlastnosti</div>
<ul>
<li>Jazyk: Čisté Go (Golang), které zaručuje nativní kompilaci, vysokou rychlost provedení a žádné zbytečné externí závislosti.</li>
<li>Rozhraní: Terminálové rozhraní (CLI), které zbavuje mysl grafického chaosu a nechává vyniknout čistému textu.</li>
<li>Datová vrstva: Plně kompatibilní formát JSON sdílený s webovou verzí. Poznámky obsahují přesná časová razítka, identifikátory, obsah a štítky.</li>
</ul>
<div class="section-title">$ použití</div>
<pre><code># Spuštění aplikace
doxa-cli</code></pre>
<div class="section-title">$ odkazy</div>
<p>Zdrojový kód, dokumentaci, návody a další informace naleznete na <a href="https://codeberg.org/Kankys/doxa-cli" target="_blank">Codebergu</a>.</p>
<p>Web DOXA Notes naleznete na <a href="https://doxa-notes.org" target="_blank">oficálním webu</a>.</p>
<p>Web doxa-secret naleznete na <a href="https://secret.doxa-notes.org" target="_blank">oficálním webu</a>.</p>
<div class="footer">
"Terminál nežádá nic, neodvádí pozornost ničím a nabízí úplnou kontrolu. Vnes řád do svých myšlenek skrze čistý kód."
</div>
</div>
<!-- EN Mutation -->
<div data-lang="en">
<div class="manual-highlight">
🔥 <strong>Important:</strong> A detailed guide for installation, and keyboard shortcuts can be found in the official manual:
<a href="https://codeberg.org/Kankys/doxa-cli/src/branch/master/MANUAL.md" target="_blank">English User Manual (MANUAL.md)</a>
</div>
<div class="section-title">$ installation</div>
<pre><code>git clone https://codeberg.org/Kankys/doxa-cli.git
cd doxa-cli
chmod +x install.sh
./install.sh</code></pre>
<div class="section-title">$ features</div>
<ul>
<li>Language: Clean Go (Golang) ensuring native compilation, fast execution speeds, and no bloated external dependencies.</li>
<li>Interface: Terminal-based interface (CLI) to strip away graphical noise and let pure text stand out.</li>
<li>Data Layer: Fully compatible JSON format shared with the web version. Notes include accurate timestamps, IDs, content, and tags.</li>
</ul>
<div class="section-title">$ usage</div>
<pre><code># Start the application
doxa-cli</code></pre>
<div class="section-title">$ links</div>
<p>Source code, documentation, guides, and more can be found on <a href="https://codeberg.org/Kankys/doxa-cli" target="_blank">Codeberg</a>.</p>
<p>DOXA Notes web can be found on the <a href="https://doxa-notes.org" target="_blank">official website</a>.</p>
<p>doxa-secret web can be found on the <a href="https://secret.doxa-notes.org" target="_blank">official website</a>.</p>
<div class="footer">
"The terminal requests nothing, distracts with nothing, and offers absolute control. Bring order to your thoughts through clean code."
</div>
</div>
</div>
<footer>
<p class="copyright" style="text-align: center;">Doxa-cli &copy; 2026 | Local-First & Freedom 🦅</p>
</footer>
</div>
<script>
let currentLang = 'cs';
function applyLang() {
document.querySelectorAll('[data-lang]').forEach(el => {
if (el.getAttribute('data-lang') === currentLang) {
el.style.display = 'block';
} else {
el.style.display = 'none';
}
});
document.getElementById('langBtn').innerText = currentLang === 'cs' ? 'EN' : 'CS';
document.documentElement.lang = currentLang;
}
function toggleLang() {
currentLang = currentLang === 'cs' ? 'en' : 'cs';
localStorage.setItem('doxa-cli-lang', currentLang);
applyLang();
}
// Initialize language
(function initLanguage() {
// 1. Check local storage
let savedLang = localStorage.getItem('doxa-cli-lang');
if (savedLang) {
currentLang = savedLang;
} else {
// 2. Check system language
let sysLang = navigator.language || navigator.userLanguage || 'en';
sysLang = sysLang.toLowerCase();
if (sysLang.startsWith('cs') || sysLang.startsWith('sk')) {
currentLang = 'cs';
} else {
currentLang = 'en';
}
}
applyLang();
})();
</script>
</body>
</html>