summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-05-03 15:44:00 +0200
committerDanilo M. <danix@danix.xyz>2026-05-03 15:44:00 +0200
commitab830bf7b489339d6d13ec673fd539a0023eb97d (patch)
treea5874da620fcbb68b551b183dd450912084d00d2
parent273915dafd51c0ef7aa1ac3f74cb6192497b9b09 (diff)
downloadpublisher-ab830bf7b489339d6d13ec673fd539a0023eb97d.tar.gz
publisher-ab830bf7b489339d6d13ec673fd539a0023eb97d.zip
feat: add Categories tab to TaxonomyView
Single-list editor for docs/categories.txt with article usage counts scanned from frontmatter. Supports multi-select delete. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--core/taxonomy.py8
-rw-r--r--ui/taxonomy_view.py106
2 files changed, 109 insertions, 5 deletions
diff --git a/core/taxonomy.py b/core/taxonomy.py
index 8aee0aa..e72d486 100644
--- a/core/taxonomy.py
+++ b/core/taxonomy.py
@@ -22,3 +22,11 @@ def save_taxonomy(model: TaxonomyModel, it_path: Path, en_path: Path) -> None:
pairs = sorted(model.it_to_en.items(), key=lambda x: x[0])
it_path.write_text("\n".join(k for k, _ in pairs) + "\n")
en_path.write_text("\n".join(v for _, v in pairs) + "\n")
+
+def load_categories(path: Path) -> list[str]:
+ if not path.exists():
+ return []
+ return sorted(l.strip() for l in path.read_text().splitlines() if l.strip())
+
+def save_categories(path: Path, categories: list[str]) -> None:
+ path.write_text("\n".join(sorted(categories)) + "\n")
diff --git a/ui/taxonomy_view.py b/ui/taxonomy_view.py
index f7b450b..6efbffd 100644
--- a/ui/taxonomy_view.py
+++ b/ui/taxonomy_view.py
@@ -3,11 +3,12 @@ from pathlib import Path
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTabWidget,
QTableWidget, QTableWidgetItem, QPushButton,
- QLabel, QDialog, QFormLayout, QLineEdit, QMessageBox,
+ QLabel, QDialog, QFormLayout, QLineEdit, QMessageBox, QHeaderView,
)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QColor
-from core.taxonomy import TaxonomyModel, load_taxonomy, save_taxonomy
+from core.taxonomy import TaxonomyModel, load_taxonomy, save_taxonomy, load_categories, save_categories
+from core.article_scanner import scan_articles
class AddTermDialog(QDialog):
def __init__(self, parent=None):
@@ -44,7 +45,9 @@ class TaxonomyView(QWidget):
self._blog_root = blog_root
self._tags_it = blog_root / "docs" / "tags-it.txt"
self._tags_en = blog_root / "docs" / "tags-en.txt"
+ self._categories_path = blog_root / "docs" / "categories.txt"
self._model: TaxonomyModel | None = None
+ self._categories: list[str] = []
self._build_ui()
self.load()
@@ -70,6 +73,29 @@ class TaxonomyView(QWidget):
tl.addLayout(tag_btns)
tabs.addTab(tags_tab, "🏷 Tags")
+ cats_tab = QWidget()
+ cl = QVBoxLayout(cats_tab)
+ self._cats_table = QTableWidget(0, 2)
+ self._cats_table.setHorizontalHeaderLabels(["Categoria", "Articoli"])
+ self._cats_table.setSelectionMode(QTableWidget.SelectionMode.ExtendedSelection)
+ self._cats_table.horizontalHeader().setStretchLastSection(False)
+ self._cats_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
+ self._cats_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
+ cl.addWidget(self._cats_table)
+ cat_btns = QHBoxLayout()
+ add_cat_btn = QPushButton("➕ Aggiungi")
+ add_cat_btn.clicked.connect(self._add_category)
+ remove_cat_btn = QPushButton("🗑 Rimuovi")
+ remove_cat_btn.clicked.connect(self._remove_category)
+ save_cats_btn = QPushButton("💾 Salva")
+ save_cats_btn.clicked.connect(self._save_categories)
+ cat_btns.addWidget(add_cat_btn)
+ cat_btns.addWidget(remove_cat_btn)
+ cat_btns.addStretch()
+ cat_btns.addWidget(save_cats_btn)
+ cl.addLayout(cat_btns)
+ tabs.addTab(cats_tab, "📂 Categorie")
+
layout.addWidget(tabs)
self._status = QLabel("")
@@ -79,9 +105,11 @@ class TaxonomyView(QWidget):
def load(self):
if not self._tags_it.exists():
self._status.setText("tags-it.txt non trovato.")
- return
- self._model = load_taxonomy(self._tags_it, self._tags_en)
- self._populate_table()
+ else:
+ self._model = load_taxonomy(self._tags_it, self._tags_en)
+ self._populate_table()
+ self._categories = load_categories(self._categories_path)
+ self._populate_cats_table()
def _populate_table(self):
if not self._model:
@@ -114,6 +142,74 @@ class TaxonomyView(QWidget):
self._model.it_to_en[dlg.it_term] = dlg.en_term
self._populate_table()
+ def _populate_cats_table(self):
+ articles = scan_articles(self._blog_root)
+ counts: dict[str, int] = {}
+ for a in articles:
+ for c in a.frontmatter.get("categories", []):
+ key = str(c).strip()
+ counts[key] = counts.get(key, 0) + 1
+
+ self._cats_table.setRowCount(0)
+ for cat in sorted(self._categories):
+ row = self._cats_table.rowCount()
+ self._cats_table.insertRow(row)
+ self._cats_table.setItem(row, 0, QTableWidgetItem(cat))
+ count_item = QTableWidgetItem(str(counts.get(cat, 0)))
+ count_item.setFlags(count_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
+ count_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
+ self._cats_table.setItem(row, 1, count_item)
+
+ def _add_category(self):
+ dlg = QDialog(self)
+ dlg.setWindowTitle("Aggiungi categoria")
+ layout = QVBoxLayout(dlg)
+ form = QFormLayout()
+ name_edit = QLineEdit()
+ form.addRow("Nome:", name_edit)
+ layout.addLayout(form)
+ btns = QHBoxLayout()
+ ok = QPushButton("Aggiungi")
+ ok.clicked.connect(dlg.accept)
+ cancel = QPushButton("Annulla")
+ cancel.clicked.connect(dlg.reject)
+ btns.addStretch()
+ btns.addWidget(cancel)
+ btns.addWidget(ok)
+ layout.addLayout(btns)
+ if dlg.exec() and name_edit.text().strip():
+ name = name_edit.text().strip()
+ if name not in self._categories:
+ self._categories.append(name)
+ self._populate_cats_table()
+
+ def _remove_category(self):
+ rows = {idx.row() for idx in self._cats_table.selectedIndexes()}
+ if not rows:
+ return
+ names = [self._cats_table.item(r, 0).text() for r in rows if self._cats_table.item(r, 0)]
+ if not names:
+ return
+ reply = QMessageBox.question(
+ self, "Rimuovi categorie",
+ f"Rimuovere {len(names)} categorie?",
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
+ )
+ if reply == QMessageBox.StandardButton.Yes:
+ to_remove = set(names)
+ self._categories = [c for c in self._categories if c not in to_remove]
+ self._populate_cats_table()
+
+ def _save_categories(self):
+ cats: list[str] = []
+ for row in range(self._cats_table.rowCount()):
+ item = self._cats_table.item(row, 0)
+ if item and item.text().strip():
+ cats.append(item.text().strip())
+ self._categories = sorted(set(cats))
+ save_categories(self._categories_path, self._categories)
+ self._status.setText("Categorie salvate.")
+
def _save(self):
if not self._model:
return