feat: Add preview dialog for documentation updates
- New DocsPreviewDialog shows side-by-side comparison before applying changes - Tabs for each doc file (overview, goals, milestones, todos) - "Show Differences" toggle marks changed lines with +/- prefixes - Stats show count of new/modified/unchanged files - Accept saves changes, Cancel restores original files from backup - Deployment is now a separate step after accepting changes This makes update-docs much safer - you can review all AI-generated content before it overwrites your carefully formatted documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fe6a4d4429
commit
ac24c56534
|
|
@ -18,6 +18,7 @@ from PyQt6.QtWidgets import (
|
||||||
QListWidget,
|
QListWidget,
|
||||||
QListWidgetItem,
|
QListWidgetItem,
|
||||||
QTextEdit,
|
QTextEdit,
|
||||||
|
QPlainTextEdit,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QLabel,
|
QLabel,
|
||||||
QProgressBar,
|
QProgressBar,
|
||||||
|
|
@ -25,6 +26,8 @@ from PyQt6.QtWidgets import (
|
||||||
QGroupBox,
|
QGroupBox,
|
||||||
QScrollArea,
|
QScrollArea,
|
||||||
QWidget,
|
QWidget,
|
||||||
|
QTabWidget,
|
||||||
|
QSplitter,
|
||||||
)
|
)
|
||||||
|
|
||||||
from development_hub.settings import Settings
|
from development_hub.settings import Settings
|
||||||
|
|
@ -815,3 +818,298 @@ class StandupDialog(QDialog):
|
||||||
f"Daily progress saved to:\n{path}"
|
f"Daily progress saved to:\n{path}"
|
||||||
)
|
)
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
|
|
||||||
|
class DocsPreviewDialog(QDialog):
|
||||||
|
"""Dialog for previewing documentation changes before applying them.
|
||||||
|
|
||||||
|
Shows side-by-side comparison of old vs new content for each file,
|
||||||
|
with optional diff highlighting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, changes: dict[str, tuple[str, str]], parent=None):
|
||||||
|
"""Initialize the preview dialog.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
changes: Dict mapping filename to (old_content, new_content) tuples
|
||||||
|
parent: Parent widget
|
||||||
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
|
self.changes = changes
|
||||||
|
self.accepted_changes = False
|
||||||
|
self._highlight_enabled = False
|
||||||
|
self._setup_ui()
|
||||||
|
|
||||||
|
def _setup_ui(self):
|
||||||
|
"""Set up the dialog UI."""
|
||||||
|
self.setWindowTitle("Review Documentation Changes")
|
||||||
|
self.resize(1400, 800)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
|
||||||
|
# Header with instructions
|
||||||
|
header = QLabel(
|
||||||
|
"Review the proposed changes below. Old content is on the left, "
|
||||||
|
"new content is on the right."
|
||||||
|
)
|
||||||
|
header.setStyleSheet("color: #b0b0b0; font-size: 13px; padding: 8px;")
|
||||||
|
layout.addWidget(header)
|
||||||
|
|
||||||
|
# Toolbar with options
|
||||||
|
toolbar = QHBoxLayout()
|
||||||
|
|
||||||
|
self.highlight_btn = QPushButton("Show Differences")
|
||||||
|
self.highlight_btn.setCheckable(True)
|
||||||
|
self.highlight_btn.toggled.connect(self._toggle_highlighting)
|
||||||
|
self.highlight_btn.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
QPushButton:checked {
|
||||||
|
background-color: #3d6a99;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
toolbar.addWidget(self.highlight_btn)
|
||||||
|
|
||||||
|
toolbar.addStretch()
|
||||||
|
|
||||||
|
# Stats label
|
||||||
|
self.stats_label = QLabel()
|
||||||
|
self._update_stats()
|
||||||
|
toolbar.addWidget(self.stats_label)
|
||||||
|
|
||||||
|
layout.addLayout(toolbar)
|
||||||
|
|
||||||
|
# Tab widget for each file
|
||||||
|
self.tab_widget = QTabWidget()
|
||||||
|
self.tab_widget.setStyleSheet("""
|
||||||
|
QTabWidget::pane {
|
||||||
|
border: 1px solid #3d3d3d;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
}
|
||||||
|
QTabBar::tab {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border: 1px solid #3d3d3d;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
QTabBar::tab:selected {
|
||||||
|
background-color: #3d6a99;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Create a tab for each file
|
||||||
|
self._text_widgets = {} # Store references for highlighting
|
||||||
|
for filename, (old_content, new_content) in self.changes.items():
|
||||||
|
tab = self._create_comparison_tab(filename, old_content, new_content)
|
||||||
|
|
||||||
|
# Determine tab label with change indicator
|
||||||
|
if old_content == new_content:
|
||||||
|
label = f"{filename} (no changes)"
|
||||||
|
elif not old_content:
|
||||||
|
label = f"{filename} (new)"
|
||||||
|
else:
|
||||||
|
label = f"{filename} (modified)"
|
||||||
|
|
||||||
|
self.tab_widget.addTab(tab, label)
|
||||||
|
|
||||||
|
layout.addWidget(self.tab_widget)
|
||||||
|
|
||||||
|
# Button row
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
button_layout.addStretch()
|
||||||
|
|
||||||
|
cancel_btn = QPushButton("Cancel")
|
||||||
|
cancel_btn.clicked.connect(self.reject)
|
||||||
|
cancel_btn.setStyleSheet("padding: 8px 24px;")
|
||||||
|
button_layout.addWidget(cancel_btn)
|
||||||
|
|
||||||
|
accept_btn = QPushButton("Accept Changes")
|
||||||
|
accept_btn.clicked.connect(self._accept_changes)
|
||||||
|
accept_btn.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
padding: 8px 24px;
|
||||||
|
background-color: #2e7d32;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #388e3c;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
button_layout.addWidget(accept_btn)
|
||||||
|
|
||||||
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
def _create_comparison_tab(self, filename: str, old_content: str, new_content: str) -> QWidget:
|
||||||
|
"""Create a tab with side-by-side comparison."""
|
||||||
|
widget = QWidget()
|
||||||
|
layout = QVBoxLayout(widget)
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
# Splitter for side-by-side
|
||||||
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
||||||
|
|
||||||
|
# Left side - old content
|
||||||
|
left_container = QWidget()
|
||||||
|
left_layout = QVBoxLayout(left_container)
|
||||||
|
left_layout.setContentsMargins(4, 4, 4, 4)
|
||||||
|
|
||||||
|
left_label = QLabel("Current")
|
||||||
|
left_label.setStyleSheet("font-weight: bold; color: #888888; padding: 4px;")
|
||||||
|
left_layout.addWidget(left_label)
|
||||||
|
|
||||||
|
old_text = QPlainTextEdit()
|
||||||
|
old_text.setPlainText(old_content or "(file does not exist)")
|
||||||
|
old_text.setReadOnly(True)
|
||||||
|
old_text.setStyleSheet("""
|
||||||
|
QPlainTextEdit {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #b0b0b0;
|
||||||
|
border: none;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
left_layout.addWidget(old_text)
|
||||||
|
|
||||||
|
splitter.addWidget(left_container)
|
||||||
|
|
||||||
|
# Right side - new content
|
||||||
|
right_container = QWidget()
|
||||||
|
right_layout = QVBoxLayout(right_container)
|
||||||
|
right_layout.setContentsMargins(4, 4, 4, 4)
|
||||||
|
|
||||||
|
right_label = QLabel("Proposed")
|
||||||
|
right_label.setStyleSheet("font-weight: bold; color: #4a9eff; padding: 4px;")
|
||||||
|
right_layout.addWidget(right_label)
|
||||||
|
|
||||||
|
new_text = QPlainTextEdit()
|
||||||
|
new_text.setPlainText(new_content or "(no content)")
|
||||||
|
new_text.setReadOnly(True)
|
||||||
|
new_text.setStyleSheet("""
|
||||||
|
QPlainTextEdit {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
border: none;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
right_layout.addWidget(new_text)
|
||||||
|
|
||||||
|
splitter.addWidget(right_container)
|
||||||
|
|
||||||
|
# Equal split
|
||||||
|
splitter.setSizes([700, 700])
|
||||||
|
|
||||||
|
layout.addWidget(splitter)
|
||||||
|
|
||||||
|
# Store references for highlighting
|
||||||
|
self._text_widgets[filename] = (old_text, new_text, old_content, new_content)
|
||||||
|
|
||||||
|
return widget
|
||||||
|
|
||||||
|
def _toggle_highlighting(self, enabled: bool):
|
||||||
|
"""Toggle diff highlighting on/off."""
|
||||||
|
self._highlight_enabled = enabled
|
||||||
|
|
||||||
|
for filename, (old_widget, new_widget, old_content, new_content) in self._text_widgets.items():
|
||||||
|
if enabled:
|
||||||
|
self._apply_diff_highlighting(old_widget, new_widget, old_content, new_content)
|
||||||
|
else:
|
||||||
|
# Reset to plain text
|
||||||
|
old_widget.setPlainText(old_content or "(file does not exist)")
|
||||||
|
new_widget.setPlainText(new_content or "(no content)")
|
||||||
|
|
||||||
|
def _apply_diff_highlighting(self, old_widget: QPlainTextEdit, new_widget: QPlainTextEdit,
|
||||||
|
old_content: str, new_content: str):
|
||||||
|
"""Apply diff highlighting to show changed lines."""
|
||||||
|
import difflib
|
||||||
|
|
||||||
|
old_lines = (old_content or "").splitlines(keepends=True)
|
||||||
|
new_lines = (new_content or "").splitlines(keepends=True)
|
||||||
|
|
||||||
|
# Use difflib to find differences
|
||||||
|
matcher = difflib.SequenceMatcher(None, old_lines, new_lines)
|
||||||
|
|
||||||
|
# Build highlighted HTML for old content
|
||||||
|
old_html_parts = []
|
||||||
|
new_html_parts = []
|
||||||
|
|
||||||
|
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
||||||
|
if tag == 'equal':
|
||||||
|
for line in old_lines[i1:i2]:
|
||||||
|
escaped = self._escape_html(line)
|
||||||
|
old_html_parts.append(f'<span style="color: #888888;">{escaped}</span>')
|
||||||
|
for line in new_lines[j1:j2]:
|
||||||
|
escaped = self._escape_html(line)
|
||||||
|
new_html_parts.append(f'<span style="color: #b0b0b0;">{escaped}</span>')
|
||||||
|
elif tag == 'delete':
|
||||||
|
for line in old_lines[i1:i2]:
|
||||||
|
escaped = self._escape_html(line)
|
||||||
|
old_html_parts.append(f'<span style="background-color: #4a2020; color: #ff8888;">{escaped}</span>')
|
||||||
|
elif tag == 'insert':
|
||||||
|
for line in new_lines[j1:j2]:
|
||||||
|
escaped = self._escape_html(line)
|
||||||
|
new_html_parts.append(f'<span style="background-color: #1a3a1a; color: #88ff88;">{escaped}</span>')
|
||||||
|
elif tag == 'replace':
|
||||||
|
for line in old_lines[i1:i2]:
|
||||||
|
escaped = self._escape_html(line)
|
||||||
|
old_html_parts.append(f'<span style="background-color: #4a3a20; color: #ffcc88;">{escaped}</span>')
|
||||||
|
for line in new_lines[j1:j2]:
|
||||||
|
escaped = self._escape_html(line)
|
||||||
|
new_html_parts.append(f'<span style="background-color: #1a3a3a; color: #88ccff;">{escaped}</span>')
|
||||||
|
|
||||||
|
# Convert widgets to show HTML (need to use QTextEdit behavior)
|
||||||
|
old_html = '<pre style="font-family: monospace; font-size: 12px; margin: 0;">' + ''.join(old_html_parts) + '</pre>'
|
||||||
|
new_html = '<pre style="font-family: monospace; font-size: 12px; margin: 0;">' + ''.join(new_html_parts) + '</pre>'
|
||||||
|
|
||||||
|
# QPlainTextEdit doesn't support HTML, so we'll just use colored markers
|
||||||
|
# Instead, let's add markers to the plain text
|
||||||
|
old_marked = []
|
||||||
|
new_marked = []
|
||||||
|
|
||||||
|
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
||||||
|
if tag == 'equal':
|
||||||
|
old_marked.extend(old_lines[i1:i2])
|
||||||
|
new_marked.extend(new_lines[j1:j2])
|
||||||
|
elif tag == 'delete':
|
||||||
|
for line in old_lines[i1:i2]:
|
||||||
|
old_marked.append(f"- {line.rstrip()}\n")
|
||||||
|
elif tag == 'insert':
|
||||||
|
for line in new_lines[j1:j2]:
|
||||||
|
new_marked.append(f"+ {line.rstrip()}\n")
|
||||||
|
elif tag == 'replace':
|
||||||
|
for line in old_lines[i1:i2]:
|
||||||
|
old_marked.append(f"~ {line.rstrip()}\n")
|
||||||
|
for line in new_lines[j1:j2]:
|
||||||
|
new_marked.append(f"~ {line.rstrip()}\n")
|
||||||
|
|
||||||
|
old_widget.setPlainText(''.join(old_marked) if old_marked else "(file does not exist)")
|
||||||
|
new_widget.setPlainText(''.join(new_marked) if new_marked else "(no content)")
|
||||||
|
|
||||||
|
def _escape_html(self, text: str) -> str:
|
||||||
|
"""Escape HTML special characters."""
|
||||||
|
return text.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
|
def _update_stats(self):
|
||||||
|
"""Update the stats label."""
|
||||||
|
total = len(self.changes)
|
||||||
|
new_files = sum(1 for old, new in self.changes.values() if not old)
|
||||||
|
modified = sum(1 for old, new in self.changes.values() if old and old != new)
|
||||||
|
unchanged = sum(1 for old, new in self.changes.values() if old == new)
|
||||||
|
|
||||||
|
self.stats_label.setText(
|
||||||
|
f"{total} files: {new_files} new, {modified} modified, {unchanged} unchanged"
|
||||||
|
)
|
||||||
|
self.stats_label.setStyleSheet("color: #888888;")
|
||||||
|
|
||||||
|
def _accept_changes(self):
|
||||||
|
"""Mark changes as accepted and close."""
|
||||||
|
self.accepted_changes = True
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def was_accepted(self) -> bool:
|
||||||
|
"""Return True if user accepted the changes."""
|
||||||
|
return self.accepted_changes
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ from PyQt6.QtWidgets import (
|
||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
from development_hub.dialogs import DeployDocsThread, RebuildMainDocsThread
|
from development_hub.dialogs import DeployDocsThread, RebuildMainDocsThread, DocsPreviewDialog
|
||||||
from development_hub.project_discovery import Project, discover_projects
|
from development_hub.project_discovery import Project, discover_projects
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -395,70 +395,169 @@ class ProjectListWidget(QWidget):
|
||||||
self._rebuild_thread = None
|
self._rebuild_thread = None
|
||||||
|
|
||||||
def _update_docs(self, project: Project):
|
def _update_docs(self, project: Project):
|
||||||
"""Update documentation using CmdForge update-docs tool."""
|
"""Update documentation using CmdForge update-docs tool with preview."""
|
||||||
from PyQt6.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox, QProgressDialog
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
# Ask if user wants to deploy after updating
|
# Confirm before starting
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"Update Documentation",
|
"Update Documentation",
|
||||||
f"This will use AI to analyze {project.title} and update its documentation.\n\n"
|
f"This will use AI to analyze {project.title} and generate updated documentation.\n\n"
|
||||||
f"The update-docs tool will:\n"
|
f"You will be able to review all changes before they are applied.\n\n"
|
||||||
f"1. Read the project's code and current docs\n"
|
f"Continue?",
|
||||||
f"2. Generate updated overview.md\n"
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
f"3. Optionally deploy to Gitea Pages\n\n"
|
|
||||||
f"Deploy after updating?",
|
|
||||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel,
|
|
||||||
QMessageBox.StandardButton.Yes
|
QMessageBox.StandardButton.Yes
|
||||||
)
|
)
|
||||||
|
|
||||||
if reply == QMessageBox.StandardButton.Cancel:
|
if reply != QMessageBox.StandardButton.Yes:
|
||||||
return
|
return
|
||||||
|
|
||||||
deploy_flag = "true" if reply == QMessageBox.StandardButton.Yes else "false"
|
# Determine docs path
|
||||||
|
docs_path = Path.home() / "PycharmProjects" / "project-docs" / "docs" / "projects" / project.key
|
||||||
|
doc_files = ["overview.md", "goals.md", "milestones.md", "todos.md"]
|
||||||
|
|
||||||
# Run the update-docs CmdForge tool
|
# Read existing docs as backup
|
||||||
# The tool expects input on stdin (can be empty) and --project argument
|
backup = {}
|
||||||
|
for filename in doc_files:
|
||||||
|
file_path = docs_path / filename
|
||||||
|
if file_path.exists():
|
||||||
|
backup[filename] = file_path.read_text()
|
||||||
|
else:
|
||||||
|
backup[filename] = ""
|
||||||
|
|
||||||
|
# Show progress dialog
|
||||||
|
progress = QProgressDialog(
|
||||||
|
f"Generating documentation for {project.title}...\n\nThis may take a minute.",
|
||||||
|
"Cancel",
|
||||||
|
0, 0,
|
||||||
|
self
|
||||||
|
)
|
||||||
|
progress.setWindowTitle("Updating Documentation")
|
||||||
|
progress.setWindowModality(Qt.WindowModality.WindowModal)
|
||||||
|
progress.setMinimumDuration(0)
|
||||||
|
progress.show()
|
||||||
|
|
||||||
|
# Process events to show the dialog
|
||||||
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
|
# Run the update-docs CmdForge tool (don't deploy yet)
|
||||||
cmd = [
|
cmd = [
|
||||||
"python3", "-m", "cmdforge.runner", "update-docs",
|
"python3", "-m", "cmdforge.runner", "update-docs",
|
||||||
"--project", project.key,
|
"--project", project.key,
|
||||||
"--deploy", deploy_flag
|
"--deploy", "false"
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Run with empty stdin, capture output
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
cmd,
|
cmd,
|
||||||
input="",
|
input="",
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=120 # 2 minute timeout for AI processing
|
timeout=180 # 3 minute timeout for AI processing
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode == 0:
|
progress.close()
|
||||||
QMessageBox.information(
|
|
||||||
self,
|
if result.returncode != 0:
|
||||||
"Documentation Updated",
|
|
||||||
f"Documentation for {project.title} has been updated.\n\n"
|
|
||||||
f"{result.stdout}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"Update Failed",
|
"Update Failed",
|
||||||
f"Failed to update documentation:\n\n{result.stderr or result.stdout}"
|
f"Failed to generate documentation:\n\n{result.stderr or result.stdout}"
|
||||||
)
|
)
|
||||||
|
return
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
|
progress.close()
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"Update Timeout",
|
"Update Timeout",
|
||||||
"Documentation update timed out. Try running manually:\n\n"
|
"Documentation generation timed out. Try running manually:\n\n"
|
||||||
f"update-docs --project {project.key}"
|
f"update-docs --project {project.key}"
|
||||||
)
|
)
|
||||||
|
return
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
progress.close()
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"Tool Not Found",
|
"Tool Not Found",
|
||||||
"CmdForge update-docs tool not found.\n\n"
|
"CmdForge update-docs tool not found.\n\n"
|
||||||
"Make sure CmdForge is installed and the update-docs tool exists."
|
"Make sure CmdForge is installed and the update-docs tool exists."
|
||||||
)
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Read the newly generated docs
|
||||||
|
new_docs = {}
|
||||||
|
for filename in doc_files:
|
||||||
|
file_path = docs_path / filename
|
||||||
|
if file_path.exists():
|
||||||
|
new_docs[filename] = file_path.read_text()
|
||||||
|
else:
|
||||||
|
new_docs[filename] = ""
|
||||||
|
|
||||||
|
# Build changes dict: filename -> (old_content, new_content)
|
||||||
|
changes = {}
|
||||||
|
for filename in doc_files:
|
||||||
|
changes[filename] = (backup[filename], new_docs.get(filename, ""))
|
||||||
|
|
||||||
|
# Show preview dialog
|
||||||
|
dialog = DocsPreviewDialog(changes, self)
|
||||||
|
dialog.exec()
|
||||||
|
|
||||||
|
if dialog.was_accepted():
|
||||||
|
# User accepted - ask about deployment
|
||||||
|
deploy_reply = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Deploy Documentation",
|
||||||
|
f"Changes have been saved.\n\n"
|
||||||
|
f"Would you like to deploy the documentation to Gitea Pages?",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
|
QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
|
||||||
|
if deploy_reply == QMessageBox.StandardButton.Yes:
|
||||||
|
# Deploy using build script
|
||||||
|
build_script = Path.home() / "PycharmProjects" / "project-docs" / "scripts" / "build-public-docs.sh"
|
||||||
|
if build_script.exists():
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
[str(build_script), project.key, "--deploy"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True
|
||||||
|
)
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Deployed",
|
||||||
|
f"Documentation deployed to:\nhttps://pages.brrd.tech/rob/{project.key}/"
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"Deploy Failed",
|
||||||
|
f"Failed to deploy documentation:\n\n{e.stderr if e.stderr else str(e)}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Changes Saved",
|
||||||
|
"Documentation changes have been saved locally.\n\n"
|
||||||
|
"You can deploy later using the 'Deploy Docs' option."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# User rejected - restore backup
|
||||||
|
for filename, content in backup.items():
|
||||||
|
file_path = docs_path / filename
|
||||||
|
if content:
|
||||||
|
file_path.write_text(content)
|
||||||
|
elif file_path.exists():
|
||||||
|
# File was created but user rejected - delete it
|
||||||
|
file_path.unlink()
|
||||||
|
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Changes Discarded",
|
||||||
|
"Documentation changes have been discarded.\n\n"
|
||||||
|
"Original files have been restored."
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue