]> danix's work - publisher.git/commitdiff
feat: add Categories tab to TaxonomyView
authorDanilo M. <redacted>
Sun, 3 May 2026 13:44:00 +0000 (15:44 +0200)
committerDanilo M. <redacted>
Sun, 3 May 2026 13:44:00 +0000 (15:44 +0200)
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 <redacted>
core/taxonomy.py
ui/taxonomy_view.py

index 8aee0aac9b5d664f3cf9ab25262b82b5ffad5ba0..e72d4866b61572dc3f315e02e43fbb2a060f5edc 100644 (file)
@@ -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")
index f7b450bddd49b85efdd21c206b4e6dc7be748c9b..6efbffd630cd943d39c4340d965779a4cd389665 100644 (file)
@@ -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