Code cleanup/comments, more test coverage (92%)

This commit is contained in:
Miguel Jacq 2025-11-07 11:42:29 +11:00
parent 66950eeff5
commit 74177f2cd3
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
19 changed files with 463 additions and 22 deletions

View file

@ -296,7 +296,6 @@ class DBManager:
) -> None:
"""
Point the page head (pages.current_version_id) to an existing version.
Fast revert: no content is rewritten.
"""
if self.conn is None:
raise RuntimeError("Database is not connected")
@ -356,6 +355,9 @@ class DBManager:
json.dump(data, f, ensure_ascii=False, separators=(",", ":"))
def export_csv(self, entries: Sequence[Entry], file_path: str) -> None:
"""
Export pages to CSV.
"""
# utf-8-sig adds a BOM so Excel opens as UTF-8 by default.
with open(file_path, "w", encoding="utf-8-sig", newline="") as f:
writer = csv.writer(f)
@ -369,6 +371,10 @@ class DBManager:
separator: str = "\n\n— — — — —\n\n",
strip_html: bool = True,
) -> None:
"""
Strip the HTML from the latest version of the pages
and save to a text file.
"""
import re, html as _html
# Precompiled patterns
@ -407,6 +413,9 @@ class DBManager:
def export_html(
self, entries: Sequence[Entry], file_path: str, title: str = "Bouquin export"
) -> None:
"""
Export to HTML with a heading.
"""
parts = [
"<!doctype html>",
'<html lang="en">',
@ -429,6 +438,10 @@ class DBManager:
def export_markdown(
self, entries: Sequence[Entry], file_path: str, title: str = "Bouquin export"
) -> None:
"""
Export to HTML, similar to export_html, but then convert to Markdown
using markdownify, and finally save to file.
"""
parts = [
"<!doctype html>",
'<html lang="en">',
@ -469,6 +482,10 @@ class DBManager:
cur.execute("DETACH DATABASE backup")
def export_by_extension(self, file_path: str) -> None:
"""
Fallback catch-all that runs one of the above functions based on
the extension of the file name that was chosen by the user.
"""
entries = self.get_all_entries()
ext = os.path.splitext(file_path)[1].lower()

View file

@ -156,7 +156,7 @@ class HistoryDialog(QDialog):
# Diff vs current (textual diff)
cur = self._db.get_version(version_id=self._current_id)
self.diff.setHtml(_colored_unified_diff_html(cur["content"], sel["content"]))
# Enable revert only if selecting a non-current
# Enable revert only if selecting a non-current version
self.btn_revert.setEnabled(sel_id != self._current_id)
@Slot()
@ -167,7 +167,7 @@ class HistoryDialog(QDialog):
sel_id = item.data(Qt.UserRole)
if sel_id == self._current_id:
return
# Flip head pointer
# Flip head pointer to the older version
try:
self._db.revert_to_version(self._date, version_id=sel_id)
except Exception as e:

View file

@ -17,6 +17,12 @@ class KeyPrompt(QDialog):
title: str = "Enter key",
message: str = "Enter key",
):
"""
Prompt the user for the key required to decrypt the database.
Used when opening the app, unlocking the idle locked screen,
or when rekeying.
"""
super().__init__(parent)
self.setWindowTitle(title)
v = QVBoxLayout(self)

View file

@ -7,6 +7,9 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton
class LockOverlay(QWidget):
def __init__(self, parent: QWidget, on_unlock: callable):
"""
Widget that 'locks' the screen after a configured idle time.
"""
super().__init__(parent)
self.setObjectName("LockOverlay")
self.setAttribute(Qt.WA_StyledBackground, True)
@ -39,6 +42,9 @@ class LockOverlay(QWidget):
self.hide()
def _is_dark(self, pal: QPalette) -> bool:
"""
Detect if dark mode is in use.
"""
c = pal.color(QPalette.Window)
luma = 0.2126 * c.redF() + 0.7152 * c.greenF() + 0.0722 * c.blueF()
return luma < 0.5
@ -58,7 +64,7 @@ class LockOverlay(QWidget):
self.setStyleSheet(
f"""
#LockOverlay {{ background-color: rgb(0,0,0); }} /* opaque, no transparency */
#LockOverlay {{ background-color: rgb(0,0,0); }}
#LockOverlay QLabel#lockLabel {{ color: {accent_hex}; font-weight: 600; }}
#LockOverlay QPushButton#unlockButton {{
@ -113,7 +119,7 @@ class LockOverlay(QWidget):
def changeEvent(self, ev):
super().changeEvent(ev)
# Only re-style on palette flips
# Only re-style on palette flips (user changed theme)
if ev.type() in (QEvent.PaletteChange, QEvent.ApplicationPaletteChange):
self._apply_overlay_style()

View file

@ -121,7 +121,7 @@ class MainWindow(QMainWindow):
split = QSplitter()
split.addWidget(left_panel)
split.addWidget(self.editor)
split.setStretchFactor(1, 1) # editor grows
split.setStretchFactor(1, 1)
container = QWidget()
lay = QVBoxLayout(container)
@ -281,7 +281,7 @@ class MainWindow(QMainWindow):
if hasattr(self, "_lock_overlay"):
self._lock_overlay._apply_overlay_style()
self._apply_calendar_text_colors()
self._apply_link_css() # Reapply link styles based on the current theme
self._apply_link_css()
self._apply_search_highlights(getattr(self, "_search_highlighted_dates", set()))
self.calendar.update()
self.editor.viewport().update()
@ -298,7 +298,6 @@ class MainWindow(QMainWindow):
css = "" # Default to no custom styling for links (system or light theme)
try:
# Apply to the editor (QTextEdit or any other relevant widgets)
self.editor.document().setDefaultStyleSheet(css)
except Exception:
pass
@ -347,7 +346,6 @@ class MainWindow(QMainWindow):
self.calendar.setPalette(app_pal)
self.calendar.setStyleSheet("")
# Keep weekend text color in sync with the current palette
self._apply_calendar_text_colors()
self.calendar.update()
@ -855,6 +853,9 @@ If you want an encrypted backup, choose Backup instead of Export.
return super().eventFilter(obj, event)
def _enter_lock(self):
"""
Trigger the lock overlay and disable widgets
"""
if self._locked:
return
self._locked = True
@ -870,6 +871,10 @@ If you want an encrypted backup, choose Backup instead of Export.
@Slot()
def _on_unlock_clicked(self):
"""
Prompt for key to unlock screen
If successful, re-enable widgets
"""
try:
ok = self._prompt_for_key_until_valid(first_time=False)
except Exception as e:

View file

@ -18,6 +18,9 @@ class SaveDialog(QDialog):
title: str = "Enter a name for this version",
message: str = "Enter a name for this version?",
):
"""
Used for explicitly saving a new version of a page.
"""
super().__init__(parent)
self.setWindowTitle(title)
v = QVBoxLayout(self)

View file

@ -70,7 +70,6 @@ class Search(QWidget):
try:
rows: Iterable[Row] = self._db.search_entries(q)
except Exception:
# be quiet on DB errors here; caller can surface if desired
rows = []
self._populate_results(q, rows)

View file

@ -49,7 +49,7 @@ class ThemeManager(QObject):
scheme = getattr(hints, "colorScheme", None)
if callable(scheme):
scheme = hints.colorScheme()
# 0=Light, 1=Dark in newer Qt; fall back to Light
# 0=Light, 1=Dark; fall back to Light
theme = Theme.DARK if scheme == 1 else Theme.LIGHT
# Always use Fusion so palette applies consistently cross-platform
@ -58,7 +58,6 @@ class ThemeManager(QObject):
if theme == Theme.DARK:
pal = self._dark_palette()
self._app.setPalette(pal)
# keep stylesheet empty unless you need widget-specific tweaks
self._app.setStyleSheet("")
else:
pal = self._light_palette()

View file

@ -140,6 +140,11 @@ class ToolBar(QToolBar):
for a in (self.actAlignL, self.actAlignC, self.actAlignR):
a.setActionGroup(self.grpAlign)
self.grpLists = QActionGroup(self)
self.grpLists.setExclusive(True)
for a in (self.actBullets, self.actNumbers, self.actCheckboxes):
a.setActionGroup(self.grpLists)
# Add actions
self.addActions(
[