]> danix's work - publisher.git/commitdiff
feat: article detail panel with frontmatter display and markdown preview
authorDanilo M. <redacted>
Sun, 3 May 2026 08:32:57 +0000 (10:32 +0200)
committerDanilo M. <redacted>
Sun, 3 May 2026 08:32:57 +0000 (10:32 +0200)
ui/article_detail.py [new file with mode: 0644]

diff --git a/ui/article_detail.py b/ui/article_detail.py
new file mode 100644 (file)
index 0000000..77dadb7
--- /dev/null
@@ -0,0 +1,132 @@
+from __future__ import annotations
+import subprocess
+import re
+from PyQt6.QtWidgets import (
+    QWidget, QVBoxLayout, QHBoxLayout, QLabel,
+    QPushButton, QTextBrowser, QScrollArea, QSizePolicy, QFrame,
+)
+from PyQt6.QtCore import pyqtSignal, Qt
+from core.models import Article
+from core.frontmatter import parse_frontmatter
+
+def _strip_shortcodes(text: str) -> str:
+    return re.sub(r'\{\{[^}]+\}\}', '[shortcode]', text)
+
+class ArticleDetailView(QWidget):
+    open_typora = pyqtSignal(object)       # Article
+    open_frontmatter = pyqtSignal(object)  # Article
+    translate = pyqtSignal(object)         # Article
+    push_master = pyqtSignal(object)       # Article
+    publish = pyqtSignal(object)           # Article
+    delete_article = pyqtSignal(object)    # Article
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self._article: Article | None = None
+        self._build_ui()
+
+    def _build_ui(self):
+        layout = QVBoxLayout(self)
+        layout.setContentsMargins(0, 0, 0, 0)
+
+        # Header
+        header = QWidget()
+        header.setStyleSheet("background: #0f0f1a; border-bottom: 1px solid #2a2a4e;")
+        h_layout = QHBoxLayout(header)
+        h_layout.setContentsMargins(12, 8, 12, 8)
+
+        self._title_label = QLabel("")
+        self._title_label.setStyleSheet("color: #fff; font-weight: bold; font-size: 13px;")
+        self._path_label = QLabel("")
+        self._path_label.setStyleSheet("color: #555; font-size: 10px;")
+        info = QVBoxLayout()
+        info.addWidget(self._title_label)
+        info.addWidget(self._path_label)
+        h_layout.addLayout(info, stretch=1)
+
+        for (icon, signal_name, style) in [
+            ("โœ๏ธ Typora", "open_typora", "color:#a855f7;border:1px solid #a855f7;"),
+            ("๐Ÿ”ง Frontmatter", "open_frontmatter", "color:#888;border:1px solid #444;"),
+            ("๐ŸŒ Traduci", "translate", "color:#f59e0b;border:1px solid #f59e0b;"),
+            ("๐Ÿงช Push master", "push_master", "color:#60a5fa;border:1px solid #60a5fa;"),
+            ("๐Ÿšข Pubblica", "publish", "color:#00ff88;border:1px solid #00ff88;"),
+            ("๐Ÿ—‘ Elimina", "delete_article", "color:#ff6b6b;border:1px solid #ff6b6b;"),
+        ]:
+            btn = QPushButton(icon)
+            btn.setStyleSheet(f"background:#1a1a2e;{style}padding:4px 10px;border-radius:4px;font-size:10px;")
+            btn.clicked.connect(lambda _, s=signal_name: getattr(self, s).emit(self._article))
+            h_layout.addWidget(btn)
+
+        layout.addWidget(header)
+
+        # Body: split columns
+        body = QHBoxLayout()
+        body.setContentsMargins(0, 0, 0, 0)
+        body.setSpacing(0)
+
+        # Frontmatter column
+        self._fm_widget = QWidget()
+        self._fm_widget.setFixedWidth(230)
+        self._fm_widget.setStyleSheet("background:#0a0a12;")
+        self._fm_layout = QVBoxLayout(self._fm_widget)
+        self._fm_layout.setContentsMargins(10, 10, 10, 10)
+        fm_title = QLabel("Frontmatter")
+        fm_title.setStyleSheet("color:#a855f7;font-size:9px;text-transform:uppercase;letter-spacing:1px;")
+        self._fm_layout.addWidget(fm_title)
+        self._fm_content = QVBoxLayout()
+        self._fm_layout.addLayout(self._fm_content)
+        self._fm_layout.addStretch()
+        body.addWidget(self._fm_widget)
+
+        divider = QFrame()
+        divider.setFrameShape(QFrame.Shape.VLine)
+        divider.setStyleSheet("color:#1a1a2e;")
+        body.addWidget(divider)
+
+        # Preview column
+        preview_container = QWidget()
+        preview_layout = QVBoxLayout(preview_container)
+        preview_layout.setContentsMargins(12, 10, 12, 10)
+        preview_title = QLabel("Preview markdown")
+        preview_title.setStyleSheet("color:#a855f7;font-size:9px;text-transform:uppercase;letter-spacing:1px;")
+        preview_layout.addWidget(preview_title)
+        self._preview = QTextBrowser()
+        self._preview.setStyleSheet("background:#0d0d1a;color:#ccc;border:none;")
+        self._preview.setOpenExternalLinks(True)
+        preview_layout.addWidget(self._preview)
+        body.addWidget(preview_container, stretch=1)
+
+        layout.addLayout(body, stretch=1)
+
+    def set_article(self, article: Article):
+        self._article = article
+        self._title_label.setText(article.slug)
+        rel = str(article.path).replace(str(article.path.parent.parent.parent.parent), "")
+        self._path_label.setText(rel)
+
+        # Populate frontmatter
+        while self._fm_content.count():
+            item = self._fm_content.takeAt(0)
+            if item.widget():
+                item.widget().deleteLater()
+
+        for key, val in article.frontmatter.items():
+            row = QVBoxLayout()
+            key_lbl = QLabel(key)
+            key_lbl.setStyleSheet("color:#555;font-size:9px;")
+            val_lbl = QLabel(str(val) if not isinstance(val, list) else ", ".join(str(v) for v in val))
+            val_lbl.setStyleSheet("color:#e2e8f0;font-size:11px;")
+            val_lbl.setWordWrap(True)
+            row.addWidget(key_lbl)
+            row.addWidget(val_lbl)
+            container = QWidget()
+            container.setLayout(row)
+            self._fm_content.addWidget(container)
+
+        # Load and render markdown preview
+        try:
+            _, body = parse_frontmatter(article.path)
+            clean = _strip_shortcodes(body)
+            self._preview.setMarkdown(clean)
+        except Exception as e:
+            self._preview.setPlainText(f"[Errore caricamento: {e}]")