--- /dev/null
+from __future__ import annotations
+from PyQt6.QtWidgets import (
+ QMainWindow, QWidget, QHBoxLayout, QVBoxLayout,
+ QLabel, QPushButton, QStackedWidget, QFrame, QSizePolicy,
+)
+from PyQt6.QtCore import Qt, QFileSystemWatcher
+from PyQt6.QtGui import QFont
+from pathlib import Path
+from core.config import Config
+from core.article_scanner import scan_articles
+from core.models import Article
+
+class SidebarButton(QPushButton):
+ def __init__(self, text: str, parent=None):
+ super().__init__(text, parent)
+ self.setFlat(True)
+ self.setCheckable(True)
+ self.setStyleSheet("""
+ QPushButton { text-align: left; padding: 5px 10px; border-radius: 4px; color: #888; }
+ QPushButton:checked { background: #2a2a4e; color: #a855f7; }
+ QPushButton:hover:!checked { background: #1a1a2e; color: #ccc; }
+ """)
+
+class MainWindow(QMainWindow):
+ def __init__(self, config: Config, parent=None):
+ super().__init__(parent)
+ self.config = config
+ self._articles: list[Article] = []
+ self._watcher = QFileSystemWatcher(self)
+ self._watcher.directoryChanged.connect(self._on_fs_change)
+ self.setWindowTitle("my-publisher")
+ self.setMinimumSize(1100, 700)
+ self._build_ui()
+ self._refresh_articles()
+ self._setup_watcher()
+
+ def _build_ui(self):
+ central = QWidget()
+ self.setCentralWidget(central)
+ root = QHBoxLayout(central)
+ root.setContentsMargins(0, 0, 0, 0)
+ root.setSpacing(0)
+
+ # Sidebar
+ sidebar = self._build_sidebar()
+ root.addWidget(sidebar)
+
+ # Divider
+ line = QFrame()
+ line.setFrameShape(QFrame.Shape.VLine)
+ line.setStyleSheet("color: #2a2a4e;")
+ root.addWidget(line)
+
+ # Content stack
+ 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"]:
+ w = QLabel(f"[{name}]")
+ w.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ self._stack.addWidget(w)
+ setattr(self, f"_page_{name}", self._stack.count() - 1)
+
+ def _build_sidebar(self) -> QWidget:
+ w = QWidget()
+ w.setFixedWidth(210)
+ w.setStyleSheet("background: #1a1a2e;")
+ layout = QVBoxLayout(w)
+ layout.setContentsMargins(8, 12, 8, 12)
+ layout.setSpacing(2)
+
+ header = QHBoxLayout()
+ title = QLabel("๐ฐ my-publisher")
+ title.setStyleSheet("color: #fff; font-weight: bold; font-size: 13px; padding: 4px 8px;")
+ header.addWidget(title)
+ self._refresh_btn = QPushButton("๐")
+ self._refresh_btn.setFlat(True)
+ self._refresh_btn.setFixedSize(28, 28)
+ self._refresh_btn.setStyleSheet("color: #555; border: none;")
+ self._refresh_btn.clicked.connect(self._refresh_articles)
+ header.addWidget(self._refresh_btn)
+ layout.addLayout(header)
+
+ def section(text):
+ lbl = QLabel(text)
+ lbl.setStyleSheet("color: #a855f7; font-size: 9px; text-transform: uppercase; letter-spacing: 1px; padding: 8px 8px 2px;")
+ layout.addWidget(lbl)
+
+ self._btn_group: list[SidebarButton] = []
+
+ def btn(icon_text: str, page_attr: str) -> SidebarButton:
+ b = SidebarButton(icon_text)
+ b.clicked.connect(lambda: self._show_page(page_attr, b))
+ layout.addWidget(b)
+ self._btn_group.append(b)
+ return b
+
+ section("CONTENUTO")
+ btn("๐ Articoli", "_page_articles")
+ self._btn_no_trans = btn("โ ๏ธ Senza Traduzione", "_page_no_translation")
+ self._badge_no_trans = QLabel("0")
+ self._badge_no_trans.setStyleSheet("background:#3a1a1a;color:#ff6b6b;border-radius:8px;padding:1px 6px;font-size:10px;")
+ btn("โ Nuovo articolo", "_page_new_article")
+ btn("๐ท Tassonomia", "_page_taxonomy")
+ btn("๐ผ Media", "_page_media")
+
+ section("WORKFLOW")
+ btn("๐ Traduzioni", "_page_translations")
+ btn("๐ง Git ops", "_page_git")
+ btn("๐ Hugo server", "_page_hugo")
+
+ section("DEPLOY")
+ btn("๐งช Test (master)", "_page_git")
+ btn("๐ข Production", "_page_git")
+
+ layout.addStretch()
+ return w
+
+ def _show_page(self, page_attr: str, active_btn: SidebarButton):
+ for b in self._btn_group:
+ b.setChecked(False)
+ active_btn.setChecked(True)
+ self._stack.setCurrentIndex(getattr(self, page_attr))
+
+ def _refresh_articles(self):
+ if not self.config.blog_repo:
+ return
+ self._articles = scan_articles(Path(self.config.blog_repo))
+ missing = [a for a in self._articles if not a.has_translation]
+ self._badge_no_trans.setText(str(len(missing)))
+
+ def _setup_watcher(self):
+ if not self.config.blog_repo:
+ return
+ root = Path(self.config.blog_repo)
+ for lang in ("it", "en"):
+ d = root / "content" / lang / "articles"
+ if d.exists():
+ self._watcher.addPath(str(d))
+
+ def _on_fs_change(self, _path: str):
+ self._refresh_articles()
--- /dev/null
+from __future__ import annotations
+from PyQt6.QtWidgets import (
+ QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
+ QPushButton, QFileDialog, QFormLayout,
+)
+from core.config import Config
+
+class SetupDialog(QDialog):
+ def __init__(self, config: Config, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle("my-publisher โ Setup")
+ self.setMinimumWidth(500)
+ self._config = config
+ self._build_ui()
+
+ def _build_ui(self):
+ layout = QVBoxLayout(self)
+ layout.addWidget(QLabel("Configura my-publisher prima di iniziare."))
+
+ form = QFormLayout()
+
+ self._blog_repo = QLineEdit(self._config.blog_repo)
+ browse_btn = QPushButton("Sfoglia...")
+ browse_btn.clicked.connect(self._browse_repo)
+ row = QHBoxLayout()
+ row.addWidget(self._blog_repo)
+ row.addWidget(browse_btn)
+ form.addRow("Blog repo:", row)
+
+ self._transart = QLineEdit(self._config.transart_script)
+ form.addRow("transart.py:", self._transart)
+
+ self._typora = QLineEdit(self._config.typora_bin)
+ form.addRow("Typora bin:", self._typora)
+
+ layout.addLayout(form)
+
+ btns = QHBoxLayout()
+ save_btn = QPushButton("Salva")
+ save_btn.clicked.connect(self._save)
+ btns.addStretch()
+ btns.addWidget(save_btn)
+ layout.addLayout(btns)
+
+ def _browse_repo(self):
+ path = QFileDialog.getExistingDirectory(self, "Seleziona blog repo")
+ if path:
+ self._blog_repo.setText(path)
+
+ def _save(self):
+ self._config.blog_repo = self._blog_repo.text().strip()
+ self._config.transart_script = self._transart.text().strip()
+ self._config.typora_bin = self._typora.text().strip()
+ self._config.save()
+ self.accept()