diff --git a/CHANGELOG.md b/CHANGELOG.md index a27c1c4..55af76b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Represent in the History diff pane when an image was the thing that changed * Support theme choice in settings (light/dark/system) * Add Checkboxes in the editor. Typing 'TODO' at the start of a line will auto-convert into a checkbox. + * Add option to automatically move yesterday's unchecked TODOs to today on startup # 0.1.9 diff --git a/bouquin/db.py b/bouquin/db.py index e8c4903..20261eb 100644 --- a/bouquin/db.py +++ b/bouquin/db.py @@ -20,6 +20,7 @@ class DBConfig: key: str idle_minutes: int = 15 # 0 = never lock theme: str = "system" + move_todos: bool = False class DBManager: diff --git a/bouquin/editor.py b/bouquin/editor.py index 05ef128..5abf9b8 100644 --- a/bouquin/editor.py +++ b/bouquin/editor.py @@ -886,5 +886,12 @@ class Editor(QTextEdit): def setHtml(self, html: str) -> None: super().setHtml(html) + + doc = self.document() + block = doc.firstBlock() + while block.isValid(): + self._style_checkbox_glyph(block) # Apply checkbox styling to each block + block = block.next() + # Ensure anchors adopt the palette color on startup self._retint_anchors_to_palette() diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 7c9d1d0..9243177 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -3,6 +3,7 @@ from __future__ import annotations import datetime import os import sys +import re from pathlib import Path from PySide6.QtCore import ( @@ -224,7 +225,8 @@ class MainWindow(QMainWindow): self.editor.textChanged.connect(self._on_text_changed) # First load + mark dates in calendar with content - self._load_selected_date() + if not self._load_yesterday_todos(): + self._load_selected_date() self._refresh_calendar_marks() # Restore window position from settings @@ -469,17 +471,31 @@ class MainWindow(QMainWindow): d = self.calendar.selectedDate() return f"{d.year():04d}-{d.month():02d}-{d.day():02d}" - def _load_selected_date(self, date_iso=False): + def _load_selected_date(self, date_iso=False, extra_data=False): if not date_iso: date_iso = self._current_date_iso() try: text = self.db.get_entry(date_iso) + if extra_data: + # Wrap extra_data in a

tag for HTML rendering + extra_data_html = f"

{extra_data}

" + + # Inject the extra_data before the closing + modified = re.sub(r"(<\/body><\/html>)", extra_data_html + r"\1", text) + text = modified + self.editor.setHtml(text) + self._dirty = True + self._save_date(date_iso, True) + + print("end") except Exception as e: QMessageBox.critical(self, "Read Error", str(e)) return + self.editor.blockSignals(True) self.editor.setHtml(text) self.editor.blockSignals(False) + self._dirty = False # track which date the editor currently represents self._active_date_iso = date_iso @@ -500,6 +516,56 @@ class MainWindow(QMainWindow): today = QDate.currentDate() self.calendar.setSelectedDate(today) + def _load_yesterday_todos(self): + try: + if not self.cfg.move_todos: + return + yesterday_str = QDate.currentDate().addDays(-1).toString("yyyy-MM-dd") + text = self.db.get_entry(yesterday_str) + unchecked_items = [] + + # Regex to match the unchecked checkboxes and their associated text + checkbox_pattern = re.compile( + r"]*>(☐)\s*(.*?)

", re.DOTALL + ) + + # Find unchecked items and store them + for match in checkbox_pattern.finditer(text): + checkbox = match.group(1) # Either ☐ or ☑ + item_text = match.group(2).strip() # The text after the checkbox + if checkbox == "☐": # If it's an unchecked checkbox (☐) + unchecked_items.append("☐ " + item_text) # Store the unchecked item + + # Remove the unchecked items from yesterday's HTML content + if unchecked_items: + # This regex will find the entire checkbox line and remove it from the HTML content + uncheckbox_pattern = re.compile( + r"]*>☐\s*(.*?)

", re.DOTALL + ) + modified_text = re.sub( + uncheckbox_pattern, "", text + ) # Remove the checkbox lines + + # Save the modified HTML back to the database + self.db.save_new_version( + yesterday_str, + modified_text, + "Unchecked checkbox items moved to next day", + ) + + # Join unchecked items into a formatted string + unchecked_str = "\n".join( + [f"

{item}

" for item in unchecked_items] + ) + + # Load the unchecked items into the current editor + self._load_selected_date(False, unchecked_str) + else: + return False + + except Exception as e: + raise SystemError(e) + def _on_date_changed(self): """ When the calendar selection changes, save the previous day's note if dirty, @@ -592,6 +658,7 @@ class MainWindow(QMainWindow): self.cfg.key = new_cfg.key self.cfg.idle_minutes = getattr(new_cfg, "idle_minutes", self.cfg.idle_minutes) self.cfg.theme = getattr(new_cfg, "theme", self.cfg.theme) + self.cfg.move_todos = getattr(new_cfg, "move_todos", self.cfg.move_todos) # Persist once save_db_config(self.cfg) diff --git a/bouquin/settings.py b/bouquin/settings.py index 8860ed2..2201b09 100644 --- a/bouquin/settings.py +++ b/bouquin/settings.py @@ -24,7 +24,10 @@ def load_db_config() -> DBConfig: key = s.value("db/key", "") idle = s.value("ui/idle_minutes", 15, type=int) theme = s.value("ui/theme", "system", type=str) - return DBConfig(path=path, key=key, idle_minutes=idle, theme=theme) + move_todos = s.value("ui/move_todos", False, type=bool) + return DBConfig( + path=path, key=key, idle_minutes=idle, theme=theme, move_todos=move_todos + ) def save_db_config(cfg: DBConfig) -> None: @@ -33,3 +36,4 @@ def save_db_config(cfg: DBConfig) -> None: s.setValue("db/key", str(cfg.key)) s.setValue("ui/idle_minutes", str(cfg.idle_minutes)) s.setValue("ui/theme", str(cfg.theme)) + s.setValue("ui/move_todos", str(cfg.move_todos)) diff --git a/bouquin/settings_dialog.py b/bouquin/settings_dialog.py index ac36337..05f5af2 100644 --- a/bouquin/settings_dialog.py +++ b/bouquin/settings_dialog.py @@ -69,6 +69,19 @@ class SettingsDialog(QDialog): form.addRow(theme_group) + # Add Behaviour + behaviour_group = QGroupBox("Behaviour") + behaviour_layout = QVBoxLayout(behaviour_group) + + self.move_todos = QCheckBox( + "Move yesterday's unchecked TODOs to today on startup" + ) + self.move_todos.setChecked(current_settings.move_todos) + self.move_todos.setCursor(Qt.PointingHandCursor) + + behaviour_layout.addWidget(self.move_todos) + form.addRow(behaviour_group) + self.path_edit = QLineEdit(str(self._cfg.path)) self.path_edit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) browse_btn = QPushButton("Browse…") @@ -223,11 +236,13 @@ class SettingsDialog(QDialog): selected_theme = Theme.SYSTEM key_to_save = self.key if self.save_key_btn.isChecked() else "" + self._cfg = DBConfig( path=Path(self.path_edit.text()), key=key_to_save, idle_minutes=self.idle_spin.value(), theme=selected_theme.value, + move_todos=self.move_todos.isChecked(), ) save_db_config(self._cfg) diff --git a/pyproject.toml b/pyproject.toml index bcedf1e..301b86d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bouquin" -version = "0.1.9" +version = "0.1.10" description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher." authors = ["Miguel Jacq "] readme = "README.md" diff --git a/tests.sh b/tests.sh old mode 100644 new mode 100755