new-doxa-monorepo
No files matched your search
@@ -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
|
||||
|
||||
@@ -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."
|
||||
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
@@ -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)
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,15 @@
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||

|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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, ¬es)
|
||||
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)
|
||||
}
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 314 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -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",
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 127 KiB |
@@ -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 © 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>
|
||||