diff --git a/CHANGELOG.md b/CHANGELOG.md
index 595c060..4b2e036 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+# 0.5.2
+
+ * Update icon again to remove background
+ * Adjust History icon and reorder toolbar items
+ * Try to address checkbox/bullet size issues (again)
+ * Fix HTML export of markdown (with newlines, tables and other styling preserved)
+
# 0.5.1
* Try to address Noto Sans font issue that works for both numbers and checkbox/bullets.
diff --git a/README.md b/README.md
index cdfdfc5..5cf77e5 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
## Introduction
-Bouquin ("Book-ahn") is a notebook and planner application written in Python, PyQt and SQLCipher.
+Bouquin ("Book-ahn") is a notebook and planner application written in Python, Qt and SQLCipher.
It is designed to treat each day as its own 'page', complete with Markdown rendering, tagging,
search, reminders and time logging for those of us who need to keep track of not just TODOs, but
diff --git a/bouquin/db.py b/bouquin/db.py
index d6211f5..ba6b6ce 100644
--- a/bouquin/db.py
+++ b/bouquin/db.py
@@ -5,6 +5,7 @@ import datetime as _dt
import hashlib
import html
import json
+import markdown
import re
from dataclasses import dataclass
@@ -440,14 +441,33 @@ class DBManager:
'',
'',
f"
{html.escape(title)}",
- "",
+ "",
"",
f"{html.escape(title)}
",
]
for d, c in entries:
+ body_html = markdown.markdown(
+ c,
+ extensions=[
+ "extra",
+ "nl2br",
+ ],
+ output_format="html5",
+ )
+
parts.append(
- f""
+ f""
+ f""
+ f""
+ f""
)
parts.append("")
diff --git a/bouquin/icons/bouquin.svg b/bouquin/icons/bouquin.svg
index e3d5e51..b282050 100644
--- a/bouquin/icons/bouquin.svg
+++ b/bouquin/icons/bouquin.svg
@@ -4,21 +4,10 @@
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
-
-
-
diff --git a/bouquin/markdown_editor.py b/bouquin/markdown_editor.py
index 27850f6..ac8a849 100644
--- a/bouquin/markdown_editor.py
+++ b/bouquin/markdown_editor.py
@@ -119,6 +119,22 @@ class MarkdownEditor(QTextEdit):
self._apply_code_block_spacing()
QTimer.singleShot(0, self._update_code_block_row_backgrounds)
+ def setFont(self, font: QFont) -> None: # type: ignore[override]
+ """
+ Ensure that whenever the base editor font changes, our highlighter
+ re-computes checkbox / bullet formats.
+ """
+ # Keep qfont in sync
+ self.qfont = QFont(font)
+ super().setFont(self.qfont)
+
+ # If the highlighter is already attached, let it rebuild its formats
+ highlighter = getattr(self, "highlighter", None)
+ if highlighter is not None:
+ refresh = getattr(highlighter, "refresh_for_font_change", None)
+ if callable(refresh):
+ refresh()
+
def showEvent(self, e):
super().showEvent(e)
# First time the widget is shown, Qt may rebuild layout once more.
diff --git a/bouquin/markdown_highlighter.py b/bouquin/markdown_highlighter.py
index 7674d1b..f9826ff 100644
--- a/bouquin/markdown_highlighter.py
+++ b/bouquin/markdown_highlighter.py
@@ -6,6 +6,7 @@ from PySide6.QtGui import (
QColor,
QFont,
QFontDatabase,
+ QFontMetrics,
QGuiApplication,
QPalette,
QSyntaxHighlighter,
@@ -33,6 +34,14 @@ class MarkdownHighlighter(QSyntaxHighlighter):
self._setup_formats()
self.rehighlight()
+ def refresh_for_font_change(self) -> None:
+ """
+ Called when the editor's base font changes (zoom / settings).
+ It rebuilds any formats that depend on the editor font metrics.
+ """
+ self._setup_formats()
+ self.rehighlight()
+
def _setup_formats(self):
"""Setup text formats for different markdown elements."""
@@ -110,8 +119,21 @@ class MarkdownHighlighter(QSyntaxHighlighter):
# Use Symbols font for checkbox and bullet glyphs if present
if self._editor is not None and hasattr(self._editor, "symbols_font_family"):
- base_size = self._editor.qfont.pointSize()
- symbols_font = QFont(self._editor.symbols_font_family, base_size)
+ base_font = QFont(self._editor.qfont) # copy of editor font
+ symbols_font = QFont(self._editor.symbols_font_family)
+ symbols_font.setPointSizeF(base_font.pointSizeF())
+
+ base_metrics = QFontMetrics(base_font)
+ sym_metrics = QFontMetrics(symbols_font)
+
+ # If Symbols glyphs are noticeably shorter than the text,
+ # scale them up so the visual heights roughly match.
+ if sym_metrics.height() > 0:
+ ratio = base_metrics.height() / sym_metrics.height()
+ if ratio > 1.05: # more than ~5% smaller
+ ratio = min(ratio, 1.4) # Oh, Tod, Tod. Don't overdo it.
+ symbols_font.setPointSizeF(symbols_font.pointSizeF() * ratio)
+
self.checkbox_format.setFont(symbols_font)
self.bullet_format.setFont(symbols_font)
diff --git a/bouquin/reminders.py b/bouquin/reminders.py
index f99eef7..5306206 100644
--- a/bouquin/reminders.py
+++ b/bouquin/reminders.py
@@ -191,7 +191,7 @@ class UpcomingRemindersWidget(QFrame):
self.toggle_btn.clicked.connect(self._on_toggle)
self.add_btn = QToolButton()
- self.add_btn.setIcon(self.style().standardIcon(QStyle.SP_FileDialogNewFolder))
+ self.add_btn.setText("⏰")
self.add_btn.setToolTip("Add Reminder")
self.add_btn.setAutoRaise(True)
self.add_btn.clicked.connect(self._add_reminder)
diff --git a/bouquin/toolbar.py b/bouquin/toolbar.py
index cba1820..c4274a4 100644
--- a/bouquin/toolbar.py
+++ b/bouquin/toolbar.py
@@ -100,6 +100,17 @@ class ToolBar(QToolBar):
self.actCheckboxes.setToolTip(strings._("toolbar_toggle_checkboxes"))
self.actCheckboxes.triggered.connect(self.checkboxesRequested)
+ # Images
+ self.actInsertImg = QAction("📸", self)
+ self.actInsertImg.setToolTip(strings._("insert_images"))
+ self.actInsertImg.setShortcut("Ctrl+Shift+I")
+ self.actInsertImg.triggered.connect(self.insertImageRequested)
+
+ # History button
+ self.actHistory = QAction("🔁", self)
+ self.actHistory.setToolTip(strings._("history"))
+ self.actHistory.triggered.connect(self.historyRequested)
+
# Alarm / reminder
self.actAlarm = QAction("⏰", self)
self.actAlarm.setToolTip(strings._("toolbar_alarm"))
@@ -115,17 +126,6 @@ class ToolBar(QToolBar):
self.actTable.setToolTip(strings._("toolbar_insert_table"))
self.actTable.triggered.connect(self.tableRequested)
- # Images
- self.actInsertImg = QAction("📸", self)
- self.actInsertImg.setToolTip(strings._("insert_images"))
- self.actInsertImg.setShortcut("Ctrl+Shift+I")
- self.actInsertImg.triggered.connect(self.insertImageRequested)
-
- # History button
- self.actHistory = QAction("⎌", self)
- self.actHistory.setToolTip(strings._("history"))
- self.actHistory.triggered.connect(self.historyRequested)
-
# Set exclusive buttons in QActionGroups
self.grpHeadings = QActionGroup(self)
self.grpHeadings.setExclusive(True)
@@ -162,10 +162,10 @@ class ToolBar(QToolBar):
self.actBullets,
self.actNumbers,
self.actCheckboxes,
- self.actAlarm,
- self.actTimer,
self.actTable,
self.actInsertImg,
+ self.actAlarm,
+ self.actTimer,
self.actHistory,
]
)
@@ -195,7 +195,7 @@ class ToolBar(QToolBar):
self._style_letter_button(self.actTable, "⊞")
# History
- self._style_letter_button(self.actHistory, "⎌")
+ self._style_letter_button(self.actHistory, "🔁")
def _style_letter_button(
self,
diff --git a/poetry.lock b/poetry.lock
index ac46700..b968699 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -307,6 +307,21 @@ files = [
{file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
]
+[[package]]
+name = "markdown"
+version = "3.10"
+description = "Python implementation of John Gruber's Markdown."
+optional = false
+python-versions = ">=3.10"
+files = [
+ {file = "markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c"},
+ {file = "markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e"},
+]
+
+[package.extras]
+docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
+testing = ["coverage", "pyyaml"]
+
[[package]]
name = "packaging"
version = "25.0"
@@ -744,4 +759,4 @@ zstd = ["zstandard (>=0.18.0)"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.14"
-content-hash = "d5fd8ea759b6bd3f23336930bdce9241659256ed918ec31746787cc86e817235"
+content-hash = "86db2b4d6498ce9eba7fa9aedf9ce58fec6a63542b5f3bdb3cf6c4067e5b87df"
diff --git a/pyproject.toml b/pyproject.toml
index 6f13468..ce8e44a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "bouquin"
-version = "0.5.1"
+version = "0.5.2"
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
authors = ["Miguel Jacq "]
readme = "README.md"
@@ -14,6 +14,7 @@ python = ">=3.10,<3.14"
pyside6 = ">=6.8.1,<7.0.0"
sqlcipher3-wheels = "^0.5.5.post0"
requests = "^2.32.5"
+markdown = "^3.10"
[tool.poetry.scripts]
bouquin = "bouquin.__main__:main"
diff --git a/tests/test_reminders.py b/tests/test_reminders.py
index 94be08c..c003d86 100644
--- a/tests/test_reminders.py
+++ b/tests/test_reminders.py
@@ -765,35 +765,6 @@ def test_reminder_not_today_skipped(qtbot, fresh_db):
assert len(triggered_texts) == 0
-def test_reminder_context_menu_single_item(qtbot, fresh_db):
- """Test context menu for a single reminder item."""
- reminder = Reminder(
- id=None,
- text="Test reminder",
- reminder_type=ReminderType.ONCE,
- time_str="14:30",
- date_iso=date.today().isoformat(),
- active=True,
- )
- fresh_db.save_reminder(reminder)
-
- reminders_widget = UpcomingRemindersWidget(fresh_db)
- qtbot.addWidget(reminders_widget)
- reminders_widget.show()
-
- # Refresh to populate the list
- reminders_widget.refresh()
-
- # Select the first item
- if reminders_widget.reminder_list.count() > 0:
- reminders_widget.reminder_list.setCurrentRow(0)
-
- # Show context menu (won't actually display in tests)
- reminders_widget._show_reminder_context_menu(
- reminders_widget.reminder_list.pos()
- )
-
-
def test_reminder_context_menu_no_selection(qtbot, fresh_db):
"""Test context menu with no selection returns early."""
reminders_widget = UpcomingRemindersWidget(fresh_db)