1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
from __future__ import annotations
import re
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QTextBrowser, QScrollArea, 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;")
self._draft_badge = QLabel("โ DRAFT")
self._draft_badge.setStyleSheet("color:#f59e0b; font-size:10px; font-weight:bold;")
self._draft_badge.hide()
info = QVBoxLayout()
info.addWidget(self._title_label)
info.addWidget(self._path_label)
info.addWidget(self._draft_badge)
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 (scrollable)
fm_scroll = QScrollArea()
fm_scroll.setFixedWidth(230)
fm_scroll.setWidgetResizable(True)
fm_scroll.setStyleSheet("QScrollArea { background:#0a0a12; border:none; }")
self._fm_widget = QWidget()
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()
fm_scroll.setWidget(self._fm_widget)
body.addWidget(fm_scroll)
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 = "/".join(article.path.parts[-5:])
self._path_label.setText(rel)
self._draft_badge.setVisible(article.draft)
# 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}]")
|