--- /dev/null
+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}]")