Add ability to export to Markdown (and fix heading styles)

This commit is contained in:
Miguel Jacq 2025-11-05 18:58:38 +11:00
parent 19593403b9
commit 76806bca08
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
6 changed files with 124 additions and 11 deletions

View file

@ -6,6 +6,7 @@ import json
import os
from dataclasses import dataclass
from markdownify import markdownify as md
from pathlib import Path
from sqlcipher3 import dbapi2 as sqlite
from typing import List, Sequence, Tuple
@ -430,6 +431,29 @@ class DBManager:
with open(file_path, "w", encoding="utf-8") as f:
f.write("\n".join(parts))
def export_markdown(
self, entries: Sequence[Entry], file_path: str, title: str = "Bouquin export"
) -> None:
parts = [
"<!doctype html>",
'<html lang="en">',
"<body>",
f"<h1>{html.escape(title)}</h1>",
]
for d, c in entries:
parts.append(
f"<article><header><time>{html.escape(d)}</time></header><section>{c}</section></article>"
)
parts.append("</body></html>")
# Convert html to markdown
md_items = []
for item in parts:
md_items.append(md(item, heading_style="ATX"))
with open(file_path, "w", encoding="utf-8") as f:
f.write("\n".join(md_items))
def export_sql(self, file_path: str) -> None:
"""
Exports the encrypted database as plaintext SQL.

View file

@ -65,8 +65,9 @@ class Editor(QTextEdit):
def _is_heading_typing(self) -> bool:
"""Is the current *insertion* format using a heading size?"""
s = self.currentCharFormat().fontPointSize() or self.font().pointSizeF()
return any(self._approx(s, h) for h in self._HEADING_SIZES)
bf = self.textCursor().blockFormat()
if bf.headingLevel() > 0:
return True
def _apply_normal_typing(self):
"""Switch the *insertion* format to Normal (default size, normal weight)."""
@ -611,20 +612,43 @@ class Editor(QTextEdit):
Set heading point size for typing. If there's a selection, also apply bold
to that selection (for H1..H3). "Normal" clears bold on the selection.
"""
base_size = size if size else self.font().pointSizeF()
# Map toolbar's sizes to heading levels
level = 1 if size >= 24 else 2 if size >= 18 else 3 if size >= 14 else 0
c = self.textCursor()
# Update the typing (insertion) format to be size only, but don't represent
# it as if the Bold style has been toggled on
# On-screen look
ins = QTextCharFormat()
ins.setFontPointSize(base_size)
if size:
ins.setFontPointSize(float(size))
ins.setFontWeight(QFont.Weight.Bold)
else:
ins.setFontPointSize(self.font().pointSizeF())
ins.setFontWeight(QFont.Weight.Normal)
self.mergeCurrentCharFormat(ins)
# If user selected text, style that text visually as a heading
# Apply heading level to affected block(s)
def set_level_for_block(cur):
bf = cur.blockFormat()
if hasattr(bf, "setHeadingLevel"):
bf.setHeadingLevel(level) # 0 clears heading
cur.mergeBlockFormat(bf)
if c.hasSelection():
sel = QTextCharFormat(ins)
sel.setFontWeight(QFont.Weight.Bold if size else QFont.Weight.Normal)
c.mergeCharFormat(sel)
start, end = c.selectionStart(), c.selectionEnd()
bc = QTextCursor(self.document())
bc.setPosition(start)
while True:
set_level_for_block(bc)
if bc.position() >= end:
break
bc.movePosition(QTextCursor.EndOfBlock)
if bc.position() >= end:
break
bc.movePosition(QTextCursor.NextBlock)
else:
bc = QTextCursor(c)
set_level_for_block(bc)
def toggle_bullets(self):
c = self.textCursor()

View file

@ -616,6 +616,7 @@ If you want an encrypted backup, choose Backup instead of Export.
"JSON (*.json);;"
"CSV (*.csv);;"
"HTML (*.html);;"
"Markdown (*.md);;"
"SQL (*.sql);;"
)
@ -631,6 +632,7 @@ If you want an encrypted backup, choose Backup instead of Export.
"JSON (*.json)": ".json",
"CSV (*.csv)": ".csv",
"HTML (*.html)": ".html",
"Markdown (*.md)": ".md",
"SQL (*.sql)": ".sql",
}.get(selected_filter, ".txt")
@ -647,6 +649,8 @@ If you want an encrypted backup, choose Backup instead of Export.
self.db.export_csv(entries, filename)
elif selected_filter.startswith("HTML"):
self.db.export_html(entries, filename)
elif selected_filter.startswith("Markdown"):
self.db.export_markdown(entries, filename)
elif selected_filter.startswith("SQL"):
self.db.export_sql(filename)
else: