Add option to automatically move yesterday's unchecked TODOs to today on startup
This commit is contained in:
parent
f7903c2cd9
commit
58f4f0a0b5
8 changed files with 99 additions and 4 deletions
|
|
@ -6,6 +6,7 @@
|
||||||
* Represent in the History diff pane when an image was the thing that changed
|
* Represent in the History diff pane when an image was the thing that changed
|
||||||
* Support theme choice in settings (light/dark/system)
|
* 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 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
|
# 0.1.9
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class DBConfig:
|
||||||
key: str
|
key: str
|
||||||
idle_minutes: int = 15 # 0 = never lock
|
idle_minutes: int = 15 # 0 = never lock
|
||||||
theme: str = "system"
|
theme: str = "system"
|
||||||
|
move_todos: bool = False
|
||||||
|
|
||||||
|
|
||||||
class DBManager:
|
class DBManager:
|
||||||
|
|
|
||||||
|
|
@ -886,5 +886,12 @@ class Editor(QTextEdit):
|
||||||
|
|
||||||
def setHtml(self, html: str) -> None:
|
def setHtml(self, html: str) -> None:
|
||||||
super().setHtml(html)
|
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
|
# Ensure anchors adopt the palette color on startup
|
||||||
self._retint_anchors_to_palette()
|
self._retint_anchors_to_palette()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from PySide6.QtCore import (
|
from PySide6.QtCore import (
|
||||||
|
|
@ -224,7 +225,8 @@ class MainWindow(QMainWindow):
|
||||||
self.editor.textChanged.connect(self._on_text_changed)
|
self.editor.textChanged.connect(self._on_text_changed)
|
||||||
|
|
||||||
# First load + mark dates in calendar with content
|
# 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()
|
self._refresh_calendar_marks()
|
||||||
|
|
||||||
# Restore window position from settings
|
# Restore window position from settings
|
||||||
|
|
@ -469,17 +471,31 @@ class MainWindow(QMainWindow):
|
||||||
d = self.calendar.selectedDate()
|
d = self.calendar.selectedDate()
|
||||||
return f"{d.year():04d}-{d.month():02d}-{d.day():02d}"
|
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:
|
if not date_iso:
|
||||||
date_iso = self._current_date_iso()
|
date_iso = self._current_date_iso()
|
||||||
try:
|
try:
|
||||||
text = self.db.get_entry(date_iso)
|
text = self.db.get_entry(date_iso)
|
||||||
|
if extra_data:
|
||||||
|
# Wrap extra_data in a <p> tag for HTML rendering
|
||||||
|
extra_data_html = f"<p>{extra_data}</p>"
|
||||||
|
|
||||||
|
# Inject the extra_data before the closing </body></html>
|
||||||
|
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:
|
except Exception as e:
|
||||||
QMessageBox.critical(self, "Read Error", str(e))
|
QMessageBox.critical(self, "Read Error", str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.editor.blockSignals(True)
|
self.editor.blockSignals(True)
|
||||||
self.editor.setHtml(text)
|
self.editor.setHtml(text)
|
||||||
self.editor.blockSignals(False)
|
self.editor.blockSignals(False)
|
||||||
|
|
||||||
self._dirty = False
|
self._dirty = False
|
||||||
# track which date the editor currently represents
|
# track which date the editor currently represents
|
||||||
self._active_date_iso = date_iso
|
self._active_date_iso = date_iso
|
||||||
|
|
@ -500,6 +516,56 @@ class MainWindow(QMainWindow):
|
||||||
today = QDate.currentDate()
|
today = QDate.currentDate()
|
||||||
self.calendar.setSelectedDate(today)
|
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"<span[^>]*>(☐)</span>\s*(.*?)</p>", 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"<span[^>]*>☐</span>\s*(.*?)</p>", 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"<p>{item}</p>" 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):
|
def _on_date_changed(self):
|
||||||
"""
|
"""
|
||||||
When the calendar selection changes, save the previous day's note if dirty,
|
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.key = new_cfg.key
|
||||||
self.cfg.idle_minutes = getattr(new_cfg, "idle_minutes", self.cfg.idle_minutes)
|
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.theme = getattr(new_cfg, "theme", self.cfg.theme)
|
||||||
|
self.cfg.move_todos = getattr(new_cfg, "move_todos", self.cfg.move_todos)
|
||||||
|
|
||||||
# Persist once
|
# Persist once
|
||||||
save_db_config(self.cfg)
|
save_db_config(self.cfg)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,10 @@ def load_db_config() -> DBConfig:
|
||||||
key = s.value("db/key", "")
|
key = s.value("db/key", "")
|
||||||
idle = s.value("ui/idle_minutes", 15, type=int)
|
idle = s.value("ui/idle_minutes", 15, type=int)
|
||||||
theme = s.value("ui/theme", "system", type=str)
|
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:
|
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("db/key", str(cfg.key))
|
||||||
s.setValue("ui/idle_minutes", str(cfg.idle_minutes))
|
s.setValue("ui/idle_minutes", str(cfg.idle_minutes))
|
||||||
s.setValue("ui/theme", str(cfg.theme))
|
s.setValue("ui/theme", str(cfg.theme))
|
||||||
|
s.setValue("ui/move_todos", str(cfg.move_todos))
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,19 @@ class SettingsDialog(QDialog):
|
||||||
|
|
||||||
form.addRow(theme_group)
|
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 = QLineEdit(str(self._cfg.path))
|
||||||
self.path_edit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
self.path_edit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
browse_btn = QPushButton("Browse…")
|
browse_btn = QPushButton("Browse…")
|
||||||
|
|
@ -223,11 +236,13 @@ class SettingsDialog(QDialog):
|
||||||
selected_theme = Theme.SYSTEM
|
selected_theme = Theme.SYSTEM
|
||||||
|
|
||||||
key_to_save = self.key if self.save_key_btn.isChecked() else ""
|
key_to_save = self.key if self.save_key_btn.isChecked() else ""
|
||||||
|
|
||||||
self._cfg = DBConfig(
|
self._cfg = DBConfig(
|
||||||
path=Path(self.path_edit.text()),
|
path=Path(self.path_edit.text()),
|
||||||
key=key_to_save,
|
key=key_to_save,
|
||||||
idle_minutes=self.idle_spin.value(),
|
idle_minutes=self.idle_spin.value(),
|
||||||
theme=selected_theme.value,
|
theme=selected_theme.value,
|
||||||
|
move_todos=self.move_todos.isChecked(),
|
||||||
)
|
)
|
||||||
|
|
||||||
save_db_config(self._cfg)
|
save_db_config(self._cfg)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "bouquin"
|
name = "bouquin"
|
||||||
version = "0.1.9"
|
version = "0.1.10"
|
||||||
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
|
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
|
||||||
authors = ["Miguel Jacq <mig@mig5.net>"]
|
authors = ["Miguel Jacq <mig@mig5.net>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
||||||
0
tests.sh
Normal file → Executable file
0
tests.sh
Normal file → Executable file
Loading…
Add table
Add a link
Reference in a new issue