1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
# my-publisher — Design Spec
**Date:** 2026-05-01
**Status:** Approved
---
## Context
Workflow attuale per pubblicare articoli sul blog danix.xyz è frammentato: git manuale da terminale, hugo server lanciato a mano, traduzione via script CLI, Typora aperto separatamente, frontmatter editato a mano. L'obiettivo è centralizzare tutto in una GUI desktop Linux che automatizzi le operazioni ripetitive e riduca il friction nel publishing workflow.
Blog: Hugo bilingual IT/EN, repo `danix.xyz-hacker-theme`, due branch (`master` = staging, `production` = live via post-receive hook). Script traduzione: `/home/danix/bin/transart.py` (RunPod + TranslateGemma 27b).
---
## Architettura
**Stack:** Python 3 + PyQt6. Processo singolo con `QThread`/`QProcess` per operazioni async — UI non blocca mai.
```
my-publisher/
├── main.py
├── ui/
│ ├── main_window.py # finestra principale, sidebar + QStackedWidget
│ ├── articles_view.py # lista articoli tab IT/EN + voce "Senza Traduzione"
│ ├── article_detail.py # pannello dettaglio (frontmatter + preview + azioni)
│ ├── frontmatter_editor.py # dialog editing TOML frontmatter
│ ├── translation_view.py # progress log + preview + azioni post-traduzione
│ ├── git_view.py # stato repo + operazioni git
│ ├── media_view.py # upload file in static/uppies/YYYY/MM/
│ └── hugo_panel.py # start/stop hugo server + log
├── workers/
│ ├── git_worker.py # QThread: pull, push master, push production, git rm, restore
│ ├── hugo_worker.py # QProcess: hugo server start/stop, streaming log
│ └── translation_worker.py # QProcess: transart.py, streaming stdout
├── core/
│ ├── article_scanner.py # scansiona content/it/ e content/en/, costruisce ArticleModel
│ ├── frontmatter.py # parse/write TOML frontmatter (tomlkit)
│ └── config.py # percorsi configurabili
└── docs/superpowers/specs/
```
**Configurazione:** `~/.config/my-publisher/config.toml`
```toml
blog_repo = "/home/danix/Programming/GIT/danix.xyz-hacker-theme"
transart_script = "/home/danix/bin/transart.py"
typora_bin = "typora"
```
Prima apertura → dialog setup se config mancante.
---
## Layout UI
**Struttura:** sidebar sinistra con gruppi + area contenuto destra (QStackedWidget).
### Sidebar
```
📰 my-publisher [🔄]
CONTENUTO
📋 Articoli
⚠️ Senza Traduzione [4]
➕ Nuovo articolo
🏷 Tassonomia
🖼 Media
WORKFLOW
🌍 Traduzioni
🔧 Git ops
🚀 Hugo server
DEPLOY
🧪 Test (master)
🚢 Production
```
Icona 🔄 in cima → refresh manuale `article_scanner`. `QFileSystemWatcher` su `content/it/` e `content/en/` → re-scan silenzioso automatico su modifiche esterne (Typora, terminale).
---
## Componenti
### Lista Articoli (`articles_view`)
Tab IT / EN. In ogni tab: lista articoli con badge stato traduzione.
- Articolo con traduzione: `🇬🇧 ✓` verde
- Articolo senza traduzione: `🇬🇧 ✗` rosso + bordo sinistro rosso
Voce sidebar "Senza Traduzione" → lista cross-lingua di tutti gli articoli privi di traduzione, con badge "manca 🇬🇧" / "manca 🇮🇹" + pulsante "Traduci" diretto.
Click articolo → emette `article_selected(Article)` → carica `article_detail`.
### Pannello Dettaglio (`article_detail`)
Layout: header + split colonne.
**Header:** path articolo + stato traduzione + 5 pulsanti:
| Pulsante | Azione |
|---|---|
| ✏️ Typora | `subprocess.Popen(["typora", path])` |
| 🔧 Frontmatter | apre `frontmatter_editor` dialog |
| 🌍 Traduci | apre `translation_view` |
| 🧪 Push master | `git_worker` → push master |
| 🚢 Pubblica | `git_worker` → push production |
**Colonna sinistra:** frontmatter leggibile (campi + valori) + bottone "Modifica frontmatter".
**Colonna destra:** preview markdown (`QTextBrowser`, renderer Qt built-in). Shortcodes Hugo strippati via regex prima del render.
### Editor Frontmatter (`frontmatter_editor`)
Dialog modale. Form generato dinamicamente dai campi TOML dell'articolo. Salva con `tomlkit` per preservare commenti e formato originale.
Il campo `type` è un `QComboBox` con i 5 tipi predefiniti del sito (non editabile liberamente):
```python
ARTICLE_TYPES = ["Life", "Photo", "Link", "Quote", "Tech"]
```
Tutti gli altri campi sono field testuali liberi. Tags e categorie mostrano i valori esistenti come chip con possibilità di aggiungere/rimuovere — solo termini già presenti nella tassonomia sono accettati (autocompletamento dal file `tags-{lang}.txt`).
### Vista Traduzione (`translation_view`)
Sostituisce `article_detail` durante e dopo la traduzione.
1. **In corso:** `QProcess` lancia `transart.py`, ogni riga stdout → append in `QPlainTextEdit` (log streaming in tempo reale).
2. **Completata:** preview markdown della traduzione + 4 pulsanti:
- ✏️ Apri in Typora
- 🔄 Rigenera traduzione
- 🧪 Push master
- 🚢 Pubblica
### Git ops (`git_view`)
Stato repo: branch corrente, `git status` (file modificati), `git log --oneline -5`.
Pulsanti: Pull, Push master, Push production. Output in `QPlainTextEdit`.
**Eliminazione articoli:** pulsante "Elimina" in `article_detail` → `git rm` + commit automatico `"remove: <slug> (<lang>)"`. Sezione "Articoli eliminati" in `git_view` → `git log --diff-filter=D --pretty=...` → pulsante "Ripristina" → `git checkout <hash> -- <path>`.
Refresh automatico all'apertura della scheda.
Le voci sidebar "🧪 Test (master)" e "🚢 Production" nel gruppo DEPLOY aprono `git_view` con focus sul branch corrispondente e pulsante azione primario evidenziato. Equivalenti ai pulsanti "Push master" / "Pubblica" nell'`article_detail` — shortcut rapidi senza dover aprire un articolo.
### Tassonomia (`taxonomy_view`)
Scheda dedicata nella sidebar sotto CONTENUTO. Gestisce tag e categorie in modo bilingue.
**Visualizzazione:** due tab — Tags / Categorie. Ogni tab mostra lista di termini con colonne IT | EN. Evidenziati in rosso i termini privi di corrispondenza nell'altra lingua (non presenti in `docs/tags-it.txt` / `docs/tags-en.txt`).
**Operazioni:**
- Aggiungere nuovo termine (IT + EN insieme)
- Modificare termine esistente
- Proposta traduzione automatica: pulsante "Traduci mancanti" → lancia `transart.py` o chiamata diretta all'API Ollama per tradurre i termini orfani in batch
- Salva → aggiorna `docs/tags-it.txt` e `docs/tags-en.txt` mantenendo ordine alfabetico e allineamento 1:1 tra i due file
**Integrità:** `article_scanner` segnala nel dettaglio articolo se un tag usato non è presente nel file tassonomia.
### Media (`media_view`)
Upload file in `static/uppies/YYYY/MM/` (anno/mese corrente automatico).
- Drag & drop nella finestra
- Pulsante "Aggiungi file" → file dialog
- Post-copia: path relativo copiato in clipboard automaticamente (`/uppies/YYYY/MM/file.ext`)
- Lista file presenti nella cartella del mese corrente
### Hugo Server (`hugo_panel`)
`QProcess` per `hugo server -D`. Start/stop dalla sidebar con icona status (🟢 running / 🔴 stopped). Log accessibile nel pannello. URL `http://localhost:1313` cliccabile.
---
## Modello Dati
```python
@dataclass
class Article:
slug: str
lang: str # "it" | "en"
path: Path # path assoluto a index.md
frontmatter: dict
has_translation: bool
translation_path: Path | None
```
`ArticleModel` = lista di `Article`. `article_scanner` scansiona `content/{it,en}/articles/*/index.md`, costruisce pairs per slug, determina `has_translation`.
```python
ARTICLE_TYPES = ["Life", "Photo", "Link", "Quote", "Tech"]
```
`TaxonomyModel`: dizionario `{term_it: term_en}` caricato da `docs/tags-it.txt` e `docs/tags-en.txt` (file allineati riga per riga). Stesso schema per categorie.
---
## Gestione Errori
Ogni worker emette `error(str)` → statusbar + dialog non-bloccante. Git push fallito → stderr visibile. Traduzione fallita → log completo + pulsante "Riprova".
---
## Dipendenze Python
```
PyQt6
tomlkit # parse/write TOML preservando formato
mistune # markdown → HTML per preview (fallback a QTextBrowser nativo)
```
---
## Verifica (test end-to-end)
1. Avviare app → sidebar popolata con articoli del blog reale
2. Click articolo → frontmatter corretto + preview markdown visibile
3. Modificare frontmatter → salvare → rileggere file su disco con `tomlkit`
4. Aprire Typora → modificare articolo → tornare in app → refresh automatico aggiorna preview
5. Tradurre articolo → log streaming visibile → preview traduzione al termine
6. Push master → `git log` sul repo mostra commit
7. Upload media → file in `static/uppies/YYYY/MM/` → path in clipboard
8. Eliminare articolo → scompare dalla lista → ripristino da Git ops funziona
|