diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e05a4c..7f9514b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.2.1.1 + + * Fix history preview pane to be in markdown + * Some other code cleanups + # 0.2.1 * Introduce tabs! diff --git a/bouquin/db.py b/bouquin/db.py index 4e1fbf8..b2a3a73 100644 --- a/bouquin/db.py +++ b/bouquin/db.py @@ -427,29 +427,6 @@ class DBManager: cur.execute("SELECT sqlcipher_export('backup')") 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() - - if ext == ".json": - self.export_json(entries, file_path) - elif ext == ".csv": - self.export_csv(entries, file_path) - elif ext == ".txt": - self.export_txt(entries, file_path) - elif ext in {".html", ".htm"}: - self.export_html(entries, file_path) - elif ext in {".sql", ".sqlite"}: - self.export_sql(file_path) - elif ext == ".md": - self.export_markdown(entries, file_path) - else: - raise ValueError(f"Unsupported extension: {ext}") - def compact(self) -> None: """ Runs VACUUM on the db. diff --git a/bouquin/find_bar.py b/bouquin/find_bar.py index 8f88449..8136c06 100644 --- a/bouquin/find_bar.py +++ b/bouquin/find_bar.py @@ -21,9 +21,8 @@ from PySide6.QtWidgets import ( class FindBar(QWidget): """Widget for finding text in the Editor""" - closed = ( - Signal() - ) # emitted when the bar is hidden (Esc/✕), so caller can refocus editor + # emitted when the bar is hidden (Esc/✕), so caller can refocus editor + closed = Signal() def __init__( self, @@ -45,7 +44,7 @@ class FindBar(QWidget): layout.addWidget(QLabel("Find:")) self.edit = QLineEdit(self) - self.edit.setPlaceholderText("Type to search…") + self.edit.setPlaceholderText("Type to search") layout.addWidget(self.edit) self.case = QCheckBox("Match case", self) @@ -79,7 +78,7 @@ class FindBar(QWidget): @property def editor(self) -> QTextEdit | None: - """Get the current editor (no side effects).""" + """Get the current editor""" return self._editor_getter() # ----- Public API ----- diff --git a/bouquin/history_dialog.py b/bouquin/history_dialog.py index 1a4c029..212cb37 100644 --- a/bouquin/history_dialog.py +++ b/bouquin/history_dialog.py @@ -118,9 +118,9 @@ class HistoryDialog(QDialog): return local.strftime("%Y-%m-%d %H:%M:%S %Z") def _load_versions(self): - self._versions = self._db.list_versions( - self._date - ) # [{id,version_no,created_at,note,is_current}] + # [{id,version_no,created_at,note,is_current}] + self._versions = self._db.list_versions(self._date) + self._current_id = next( (v["id"] for v in self._versions if v["is_current"]), None ) @@ -152,13 +152,8 @@ class HistoryDialog(QDialog): self.btn_revert.setEnabled(False) return sel_id = item.data(Qt.UserRole) - # Preview selected as plain text (markdown) sel = self._db.get_version(version_id=sel_id) - # Show markdown as plain text with monospace font for better readability - self.preview.setPlainText(sel["content"]) - self.preview.setStyleSheet( - "font-family: Consolas, Menlo, Monaco, monospace; font-size: 13px;" - ) + self.preview.setMarkdown(sel["content"]) # 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"])) diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 6418726..8dc9c3f 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -808,7 +808,7 @@ class MainWindow(QMainWindow): def _adjust_today(self): """Jump to today.""" today = QDate.currentDate() - self.calendar.setSelectedDate(today) + self._create_new_tab(today) def _load_yesterday_todos(self): try: @@ -1090,7 +1090,7 @@ If you want an encrypted backup, choose Backup instead of Export. elif selected_filter.startswith("SQL"): self.db.export_sql(filename) else: - self.db.export_by_extension(filename) + raise ValueError("Unrecognised extension!") QMessageBox.information(self, "Export complete", f"Saved to:\n{filename}") except Exception as e: diff --git a/bouquin/markdown_editor.py b/bouquin/markdown_editor.py index 87ccfac..d864770 100644 --- a/bouquin/markdown_editor.py +++ b/bouquin/markdown_editor.py @@ -112,7 +112,7 @@ class MarkdownHighlighter(QSyntaxHighlighter): self.setCurrentBlockState(1 if in_code_block else 0) # Format the fence markers - but keep them somewhat visible for editing # Use code format instead of syntax format so cursor is visible - self.setFormat(0, len(text), self.code_block_format) + self.setFormat(0, len(text), self.code_format) return if in_code_block: @@ -258,13 +258,13 @@ class MarkdownEditor(QTextEdit): # Transform only this line: # - "TODO " at start (with optional indent) -> "- ☐ " - # - "- [ ] " -> "- ☐ " and "- [x] " -> "- ☑ " + # - "- [ ] " -> " ☐ " and "- [x] " -> " ☑ " def transform_line(s: str) -> str: - s = s.replace("- [x] ", f"- {self._CHECK_CHECKED_DISPLAY} ") - s = s.replace("- [ ] ", f"- {self._CHECK_UNCHECKED_DISPLAY} ") + s = s.replace("- [x] ", f"{self._CHECK_CHECKED_DISPLAY} ") + s = s.replace("- [ ] ", f"{self._CHECK_UNCHECKED_DISPLAY} ") s = re.sub( r"^([ \t]*)TODO\b[:\-]?\s+", - lambda m: f"{m.group(1)}- {self._CHECK_UNCHECKED_DISPLAY} ", + lambda m: f"{m.group(1)}\n{self._CHECK_UNCHECKED_DISPLAY} ", s, ) return s @@ -293,8 +293,8 @@ class MarkdownEditor(QTextEdit): text = self._extract_images_to_markdown() # Convert Unicode checkboxes back to markdown syntax - text = text.replace(f"- {self._CHECK_CHECKED_DISPLAY} ", "- [x] ") - text = text.replace(f"- {self._CHECK_UNCHECKED_DISPLAY} ", "- [ ] ") + text = text.replace(f"{self._CHECK_CHECKED_DISPLAY} ", "- [x] ") + text = text.replace(f"{self._CHECK_UNCHECKED_DISPLAY} ", "- [ ] ") return text @@ -336,15 +336,15 @@ class MarkdownEditor(QTextEdit): """Load markdown text into the editor (convert markdown checkboxes to Unicode).""" # Convert markdown checkboxes to Unicode for display display_text = markdown_text.replace( - "- [x] ", f"- {self._CHECK_CHECKED_DISPLAY} " + "- [x] ", f"{self._CHECK_CHECKED_DISPLAY} " ) display_text = display_text.replace( - "- [ ] ", f"- {self._CHECK_UNCHECKED_DISPLAY} " + "- [ ] ", f"{self._CHECK_UNCHECKED_DISPLAY} " ) # Also convert any plain 'TODO ' at the start of a line to an unchecked checkbox display_text = re.sub( r"(?m)^([ \t]*)TODO\s", - lambda m: f"{m.group(1)}- {self._CHECK_UNCHECKED_DISPLAY} ", + lambda m: f"{m.group(1)}\n{self._CHECK_UNCHECKED_DISPLAY} ", display_text, ) @@ -425,10 +425,10 @@ class MarkdownEditor(QTextEdit): line = line.lstrip() # Checkbox list (Unicode display format) - if line.startswith(f"- {self._CHECK_UNCHECKED_DISPLAY} ") or line.startswith( - f"- {self._CHECK_CHECKED_DISPLAY} " + if line.startswith(f"{self._CHECK_UNCHECKED_DISPLAY} ") or line.startswith( + f"{self._CHECK_CHECKED_DISPLAY} " ): - return ("checkbox", f"- {self._CHECK_UNCHECKED_DISPLAY} ") + return ("checkbox", f"{self._CHECK_UNCHECKED_DISPLAY} ") # Bullet list if re.match(r"^[-*+]\s", line): @@ -533,19 +533,19 @@ class MarkdownEditor(QTextEdit): # Check if clicking on a checkbox line if ( - f"- {self._CHECK_UNCHECKED_DISPLAY} " in line - or f"- {self._CHECK_CHECKED_DISPLAY} " in line + f"{self._CHECK_UNCHECKED_DISPLAY} " in line + or f"{self._CHECK_CHECKED_DISPLAY} " in line ): # Toggle the checkbox - if f"- {self._CHECK_UNCHECKED_DISPLAY} " in line: + if f"{self._CHECK_UNCHECKED_DISPLAY} " in line: new_line = line.replace( - f"- {self._CHECK_UNCHECKED_DISPLAY} ", - f"- {self._CHECK_CHECKED_DISPLAY} ", + f"{self._CHECK_UNCHECKED_DISPLAY} ", + f"{self._CHECK_CHECKED_DISPLAY} ", ) else: new_line = line.replace( - f"- {self._CHECK_CHECKED_DISPLAY} ", - f"- {self._CHECK_UNCHECKED_DISPLAY} ", + f"{self._CHECK_CHECKED_DISPLAY} ", + f"{self._CHECK_UNCHECKED_DISPLAY} ", ) cursor.insertText(new_line) @@ -745,18 +745,18 @@ class MarkdownEditor(QTextEdit): # Check if already has checkbox (Unicode display format) if ( - f"- {self._CHECK_UNCHECKED_DISPLAY} " in line - or f"- {self._CHECK_CHECKED_DISPLAY} " in line + f"{self._CHECK_UNCHECKED_DISPLAY} " in line + or f"{self._CHECK_CHECKED_DISPLAY} " in line ): # Remove checkbox - use raw string to avoid escape sequence warning new_line = re.sub( - rf"^\s*-\s*[{self._CHECK_UNCHECKED_DISPLAY}{self._CHECK_CHECKED_DISPLAY}]\s+", + rf"^\s*[{self._CHECK_UNCHECKED_DISPLAY}{self._CHECK_CHECKED_DISPLAY}]\s+", "", line, ) else: # Add checkbox (Unicode display format) - new_line = f"- {self._CHECK_UNCHECKED_DISPLAY} " + line.lstrip() + new_line = f"{self._CHECK_UNCHECKED_DISPLAY} " + line.lstrip() cursor.insertText(new_line) diff --git a/pyproject.toml b/pyproject.toml index d32b190..b094016 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bouquin" -version = "0.2.1" +version = "0.2.1.1" description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher." authors = ["Miguel Jacq "] readme = "README.md" diff --git a/tests/test_db.py b/tests/test_db.py index 7a36dd6..b5568d6 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -1,6 +1,8 @@ +import pytest import json, csv import datetime as dt +from bouquin.db import DBManager def _today(): return dt.date.today().isoformat() @@ -57,7 +59,7 @@ def test_dates_with_content_and_search(fresh_db): assert any(d == _tomorrow() for d, _ in hits) -def test_get_all_entries_and_export_by_extension(fresh_db, tmp_path): +def test_get_all_entries_and_export(fresh_db, tmp_path): for i in range(3): d = (dt.date.today() - dt.timedelta(days=i)).isoformat() fresh_db.save_new_version(d, _entry(f"note {i}"), f"note {i}") @@ -93,18 +95,12 @@ def test_get_all_entries_and_export_by_extension(fresh_db, tmp_path): fresh_db.export_sqlcipher(str(sqlc_path)) assert sqlc_path.exists() and sqlc_path.read_bytes() - for path in [json_path, csv_path, txt_path, md_path, html_path, sql_path]: - path.unlink(missing_ok=True) - fresh_db.export_by_extension(str(path)) - assert path.exists() - def test_rekey_and_reopen(fresh_db, tmp_db_cfg): fresh_db.save_new_version(_today(), _entry("secure"), "before rekey") fresh_db.rekey("new-key-123") fresh_db.close() - from bouquin.db import DBManager tmp_db_cfg.key = "new-key-123" db2 = DBManager(tmp_db_cfg) @@ -116,12 +112,3 @@ def test_rekey_and_reopen(fresh_db, tmp_db_cfg): def test_compact_and_close_dont_crash(fresh_db): fresh_db.compact() fresh_db.close() - - -import pytest - - -def test_export_by_extension_unsupported(fresh_db, tmp_path): - p = tmp_path / "export.xyz" - with pytest.raises(ValueError): - fresh_db.export_by_extension(str(p)) diff --git a/tests/test_find_bar.py b/tests/test_find_bar.py index 3dd3731..be0d988 100644 --- a/tests/test_find_bar.py +++ b/tests/test_find_bar.py @@ -3,7 +3,7 @@ import pytest from PySide6.QtGui import QTextCursor from bouquin.markdown_editor import MarkdownEditor from bouquin.theme import ThemeManager, ThemeConfig, Theme - +from bouquin.find_bar import FindBar @pytest.fixture def editor(app, qtbot): @@ -14,9 +14,6 @@ def editor(app, qtbot): return ed -from bouquin.find_bar import FindBar - - @pytest.mark.gui def test_findbar_basic_navigation(qtbot, editor): editor.from_markdown("alpha\nbeta\nalpha\nGamma\n") @@ -42,7 +39,6 @@ def test_findbar_basic_navigation(qtbot, editor): def test_show_bar_seeds_selection(qtbot, editor): - from PySide6.QtGui import QTextCursor editor.from_markdown("alpha beta") c = editor.textCursor() diff --git a/tests/test_main_window.py b/tests/test_main_window.py index 3942566..7b37dc0 100644 --- a/tests/test_main_window.py +++ b/tests/test_main_window.py @@ -8,7 +8,6 @@ from bouquin.key_prompt import KeyPrompt from PySide6.QtCore import QTimer from PySide6.QtWidgets import QApplication - @pytest.mark.gui def test_main_window_loads_and_saves(qtbot, app, tmp_db_cfg, fresh_db): s = get_settings() @@ -52,10 +51,6 @@ def test_main_window_loads_and_saves(qtbot, app, tmp_db_cfg, fresh_db): def test_load_yesterday_todos_moves_items(qtbot, app, tmp_db_cfg, fresh_db): - from PySide6.QtCore import QDate - from bouquin.theme import ThemeManager, ThemeConfig, Theme - from bouquin.settings import get_settings - s = get_settings() s.setValue("db/path", str(tmp_db_cfg.path)) s.setValue("db/key", tmp_db_cfg.key) @@ -66,7 +61,6 @@ def test_load_yesterday_todos_moves_items(qtbot, app, tmp_db_cfg, fresh_db): fresh_db.save_new_version(y, "- [ ] carry me\n- [x] done", "seed") themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT)) - from bouquin.main_window import MainWindow w = MainWindow(themes=themes) qtbot.addWidget(w) diff --git a/tests/test_settings_dialog.py b/tests/test_settings_dialog.py index 515e769..4615694 100644 --- a/tests/test_settings_dialog.py +++ b/tests/test_settings_dialog.py @@ -1,10 +1,11 @@ import pytest +from bouquin.db import DBManager, DBConfig +from bouquin.key_prompt import KeyPrompt from bouquin.settings_dialog import SettingsDialog from bouquin.theme import ThemeManager, ThemeConfig, Theme from PySide6.QtCore import QTimer from PySide6.QtWidgets import QApplication, QMessageBox, QWidget - @pytest.mark.gui def test_settings_dialog_config_roundtrip(qtbot, tmp_db_cfg, fresh_db, tmp_path): # Provide a parent that exposes a real ThemeManager (dialog calls parent().themes.set(...)) @@ -38,12 +39,6 @@ def test_settings_dialog_config_roundtrip(qtbot, tmp_db_cfg, fresh_db, tmp_path) def test_save_key_toggle_roundtrip(qtbot, tmp_db_cfg, fresh_db, app): - from PySide6.QtCore import QTimer - from PySide6.QtWidgets import QApplication, QMessageBox - from bouquin.key_prompt import KeyPrompt - from bouquin.theme import ThemeManager, ThemeConfig, Theme - from PySide6.QtWidgets import QWidget - parent = QWidget() parent.themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT)) @@ -81,12 +76,6 @@ def test_save_key_toggle_roundtrip(qtbot, tmp_db_cfg, fresh_db, app): def test_change_key_mismatch_shows_error(qtbot, tmp_db_cfg, tmp_path, app): - from PySide6.QtCore import QTimer - from PySide6.QtWidgets import QApplication, QMessageBox, QWidget - from bouquin.key_prompt import KeyPrompt - from bouquin.db import DBManager, DBConfig - from bouquin.theme import ThemeManager, ThemeConfig, Theme - cfg = DBConfig( path=tmp_path / "iso.db", key="oldkey", @@ -129,12 +118,6 @@ def test_change_key_mismatch_shows_error(qtbot, tmp_db_cfg, tmp_path, app): def test_change_key_success(qtbot, tmp_path, app): - from PySide6.QtCore import QTimer - from PySide6.QtWidgets import QApplication, QWidget, QMessageBox - from bouquin.key_prompt import KeyPrompt - from bouquin.db import DBManager, DBConfig - from bouquin.theme import ThemeManager, ThemeConfig, Theme - cfg = DBConfig( path=tmp_path / "iso2.db", key="oldkey", diff --git a/tests/test_toolbar.py b/tests/test_toolbar.py index 1022172..1faef70 100644 --- a/tests/test_toolbar.py +++ b/tests/test_toolbar.py @@ -2,6 +2,7 @@ import pytest from PySide6.QtWidgets import QWidget from bouquin.markdown_editor import MarkdownEditor from bouquin.theme import ThemeManager, ThemeConfig, Theme +from bouquin.toolbar import ToolBar @pytest.fixture @@ -12,10 +13,6 @@ def editor(app, qtbot): ed.show() return ed - -from bouquin.toolbar import ToolBar - - @pytest.mark.gui def test_toolbar_signals_and_styling(qtbot, editor): host = QWidget()