From: Danilo M. Date: Sun, 3 May 2026 08:32:47 +0000 (+0200) Subject: feat: articles view with IT/EN tabs and missing translation highlight X-Git-Tag: v1.0~17 X-Git-Url: https://git.danix.xyz/?a=commitdiff_plain;h=19468b56aa60c0b36ae2e2adabf6f2f3088b47cc;p=publisher.git feat: articles view with IT/EN tabs and missing translation highlight --- diff --git a/ui/articles_view.py b/ui/articles_view.py new file mode 100644 index 0000000..d3fac93 --- /dev/null +++ b/ui/articles_view.py @@ -0,0 +1,82 @@ +from __future__ import annotations +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, + QListWidget, QListWidgetItem, QLabel, QPushButton, +) +from PyQt6.QtCore import Qt, pyqtSignal +from PyQt6.QtGui import QColor +from core.models import Article + +class ArticleItem(QListWidgetItem): + def __init__(self, article: Article): + super().__init__() + self.article = article + lang_other = "EN" if article.lang == "it" else "IT" + if article.has_translation: + badge = f"🇬🇧 ✓" if article.lang == "it" else "🇮🇹 ✓" + status = f"{article.slug} [{badge}]" + else: + badge = f"🇬🇧 ✗" if article.lang == "it" else "🇮🇹 ✗" + status = f"{article.slug} [{badge}]" + self.setText(status) + if not article.has_translation: + self.setForeground(QColor("#ff6b6b")) + +class ArticlesView(QWidget): + article_selected = pyqtSignal(object) # Article + translate_requested = pyqtSignal(object) # Article + + def __init__(self, parent=None): + super().__init__(parent) + self._articles: list[Article] = [] + self._build_ui() + + def _build_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + self._tabs = QTabWidget() + self._tabs.setStyleSheet("QTabBar::tab { padding: 6px 16px; }") + self._list_it = QListWidget() + self._list_en = QListWidget() + self._tabs.addTab(self._list_it, "🇮🇹 Italiano") + self._tabs.addTab(self._list_en, "🇬🇧 English") + layout.addWidget(self._tabs) + self._list_it.itemClicked.connect(lambda item: self.article_selected.emit(item.article)) + self._list_en.itemClicked.connect(lambda item: self.article_selected.emit(item.article)) + + def set_articles(self, articles: list[Article]): + self._articles = articles + self._list_it.clear() + self._list_en.clear() + for a in articles: + item = ArticleItem(a) + if a.lang == "it": + self._list_it.addItem(item) + else: + self._list_en.addItem(item) + +class MissingTranslationView(QWidget): + article_selected = pyqtSignal(object) # Article + translate_requested = pyqtSignal(object) # Article + + def __init__(self, parent=None): + super().__init__(parent) + self._build_ui() + + def _build_ui(self): + layout = QVBoxLayout(self) + header = QLabel("⚠️ Articoli senza traduzione") + header.setStyleSheet("color: #ff6b6b; font-weight: bold; font-size: 13px; padding: 10px;") + layout.addWidget(header) + self._list = QListWidget() + layout.addWidget(self._list) + self._list.itemClicked.connect(lambda item: self.article_selected.emit(item.article)) + + def set_articles(self, articles: list[Article]): + self._list.clear() + missing = [a for a in articles if not a.has_translation] + for a in missing: + lang_other = "🇬🇧" if a.lang == "it" else "🇮🇹" + item = ArticleItem(a) + item.setText(f"{a.slug} [manca {lang_other}] ({a.lang.upper()})") + self._list.addItem(item) diff --git a/ui/main_window.py b/ui/main_window.py index cbefa5f..01e4cf2 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -1,4 +1,5 @@ from __future__ import annotations +import subprocess from PyQt6.QtWidgets import ( QMainWindow, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QPushButton, QStackedWidget, QFrame, @@ -54,9 +55,32 @@ class MainWindow(QMainWindow): self._stack = QStackedWidget() root.addWidget(self._stack, stretch=1) - # Placeholder pages (replaced in later tasks) - for name in ["articles", "no_translation", "new_article", "taxonomy", - "media", "translations", "git", "hugo"]: + from ui.articles_view import ArticlesView, MissingTranslationView + + self._articles_view = ArticlesView() + self._articles_view.article_selected.connect(self._on_article_selected) + self._stack.addWidget(self._articles_view) + self._page_articles = self._stack.count() - 1 + + self._missing_view = MissingTranslationView() + self._missing_view.article_selected.connect(self._on_article_selected) + self._stack.addWidget(self._missing_view) + self._page_no_translation = self._stack.count() - 1 + + from ui.article_detail import ArticleDetailView + + self._detail_view = ArticleDetailView() + self._detail_view.open_typora.connect(self._do_open_typora) + self._detail_view.open_frontmatter.connect(self._do_open_frontmatter) + self._detail_view.translate.connect(self._do_translate) + self._detail_view.push_master.connect(lambda a: self._do_git_push("master")) + self._detail_view.publish.connect(lambda a: self._do_git_push("production")) + self._detail_view.delete_article.connect(self._do_delete_article) + self._stack.addWidget(self._detail_view) + self._page_detail = self._stack.count() - 1 + + # Remaining placeholders + for name in ["new_article", "taxonomy", "media", "translations", "git", "hugo"]: w = QLabel(f"[{name}]") w.setAlignment(Qt.AlignmentFlag.AlignCenter) self._stack.addWidget(w) @@ -136,9 +160,33 @@ class MainWindow(QMainWindow): if not self.config.blog_repo: return self._articles = scan_articles(Path(self.config.blog_repo)) + self._articles_view.set_articles(self._articles) + self._missing_view.set_articles(self._articles) missing = [a for a in self._articles if not a.has_translation] self._badge_no_trans.setText(str(len(missing))) + def _on_article_selected(self, article: Article): + self._detail_view.set_article(article) + self._stack.setCurrentIndex(self._page_detail) + + def _do_open_typora(self, article: Article): + subprocess.Popen([self.config.typora_bin, str(article.path)]) + + def _do_open_frontmatter(self, article: Article): + from ui.frontmatter_editor import FrontmatterEditor + dlg = FrontmatterEditor(article, Path(self.config.blog_repo), self) + if dlg.exec(): + self._detail_view.set_article(article) + + def _do_translate(self, article: Article): + pass # implemented in Task 15 + + def _do_git_push(self, branch: str): + pass # implemented in Task 16 + + def _do_delete_article(self, article: Article): + pass # implemented in Task 16 + def _setup_watcher(self): if not self.config.blog_repo: return