bouquin 0.1.10__py3-none-any.whl → 0.2.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bouquin/save_dialog.py CHANGED
@@ -18,6 +18,9 @@ class SaveDialog(QDialog):
18
18
  title: str = "Enter a name for this version",
19
19
  message: str = "Enter a name for this version?",
20
20
  ):
21
+ """
22
+ Used for explicitly saving a new version of a page.
23
+ """
21
24
  super().__init__(parent)
22
25
  self.setWindowTitle(title)
23
26
  v = QVBoxLayout(self)
bouquin/search.py CHANGED
@@ -4,7 +4,6 @@ import re
4
4
  from typing import Iterable, Tuple
5
5
 
6
6
  from PySide6.QtCore import Qt, Signal
7
- from PySide6.QtGui import QFont, QTextCharFormat, QTextCursor, QTextDocument
8
7
  from PySide6.QtWidgets import (
9
8
  QFrame,
10
9
  QLabel,
@@ -70,7 +69,6 @@ class Search(QWidget):
70
69
  try:
71
70
  rows: Iterable[Row] = self._db.search_entries(q)
72
71
  except Exception:
73
- # be quiet on DB errors here; caller can surface if desired
74
72
  rows = []
75
73
 
76
74
  self._populate_results(q, rows)
@@ -150,10 +148,12 @@ class Search(QWidget):
150
148
  self.results.setItemWidget(item, container)
151
149
 
152
150
  # --- Snippet/highlight helpers -----------------------------------------
153
- def _make_html_snippet(self, html_src: str, query: str, *, radius=60, maxlen=180):
154
- doc = QTextDocument()
155
- doc.setHtml(html_src)
156
- plain = doc.toPlainText()
151
+ def _make_html_snippet(
152
+ self, markdown_src: str, query: str, *, radius=60, maxlen=180
153
+ ):
154
+ # For markdown, we can work directly with the text
155
+ # Strip markdown formatting for display
156
+ plain = self._strip_markdown(markdown_src)
157
157
  if not plain:
158
158
  return "", False, False
159
159
 
@@ -180,30 +180,45 @@ class Search(QWidget):
180
180
  start = max(0, min(idx - radius, max(0, L - maxlen)))
181
181
  end = min(L, max(idx + mlen + radius, start + maxlen))
182
182
 
183
- # Bold all token matches that fall inside [start, end)
183
+ # Extract snippet and highlight matches
184
+ snippet = plain[start:end]
185
+
186
+ # Escape HTML and bold matches
187
+ import html as _html
188
+
189
+ snippet_html = _html.escape(snippet)
184
190
  if tokens:
185
- lower = plain.lower()
186
- fmt = QTextCharFormat()
187
- fmt.setFontWeight(QFont.Weight.Bold)
188
191
  for t in tokens:
189
- t_low = t.lower()
190
- pos = start
191
- while True:
192
- k = lower.find(t_low, pos)
193
- if k == -1 or k >= end:
194
- break
195
- c = QTextCursor(doc)
196
- c.setPosition(k)
197
- c.setPosition(k + len(t), QTextCursor.MoveMode.KeepAnchor)
198
- c.mergeCharFormat(fmt)
199
- pos = k + len(t)
200
-
201
- # Select the window and export as HTML fragment
202
- c = QTextCursor(doc)
203
- c.setPosition(start)
204
- c.setPosition(end, QTextCursor.MoveMode.KeepAnchor)
205
- fragment_html = (
206
- c.selection().toHtml()
207
- ) # preserves original styles + our bolding
208
-
209
- return fragment_html, start > 0, end < L
192
+ # Case-insensitive replacement
193
+ pattern = re.compile(re.escape(t), re.IGNORECASE)
194
+ snippet_html = pattern.sub(
195
+ lambda m: f"<b>{m.group(0)}</b>", snippet_html
196
+ )
197
+
198
+ return snippet_html, start > 0, end < L
199
+
200
+ def _strip_markdown(self, markdown: str) -> str:
201
+ """Strip markdown formatting for plain text display."""
202
+ # Remove images
203
+ text = re.sub(r"!\[.*?\]\(.*?\)", "[Image]", markdown)
204
+ # Remove links but keep text
205
+ text = re.sub(r"\[([^\]]+)\]\([^\)]+\)", r"\1", text)
206
+ # Remove inline code backticks
207
+ text = re.sub(r"`([^`]+)`", r"\1", text)
208
+ # Remove bold/italic markers
209
+ text = re.sub(r"\*\*([^*]+)\*\*", r"\1", text)
210
+ text = re.sub(r"__([^_]+)__", r"\1", text)
211
+ text = re.sub(r"\*([^*]+)\*", r"\1", text)
212
+ text = re.sub(r"_([^_]+)_", r"\1", text)
213
+ # Remove strikethrough
214
+ text = re.sub(r"~~([^~]+)~~", r"\1", text)
215
+ # Remove heading markers
216
+ text = re.sub(r"^#{1,6}\s+", "", text, flags=re.MULTILINE)
217
+ # Remove list markers
218
+ text = re.sub(r"^\s*[-*+]\s+", "", text, flags=re.MULTILINE)
219
+ text = re.sub(r"^\s*\d+\.\s+", "", text, flags=re.MULTILINE)
220
+ # Remove checkbox markers
221
+ text = re.sub(r"^\s*-\s*\[[x ☐☑]\]\s+", "", text, flags=re.MULTILINE)
222
+ # Remove code block fences
223
+ text = re.sub(r"```[^\n]*\n", "", text)
224
+ return text.strip()
@@ -246,7 +246,7 @@ class SettingsDialog(QDialog):
246
246
  )
247
247
 
248
248
  save_db_config(self._cfg)
249
- self.parent().themes.apply(selected_theme)
249
+ self.parent().themes.set(selected_theme)
250
250
  self.accept()
251
251
 
252
252
  def _change_key(self):
bouquin/theme.py CHANGED
@@ -49,7 +49,7 @@ class ThemeManager(QObject):
49
49
  scheme = getattr(hints, "colorScheme", None)
50
50
  if callable(scheme):
51
51
  scheme = hints.colorScheme()
52
- # 0=Light, 1=Dark in newer Qt; fall back to Light
52
+ # 0=Light, 1=Dark; fall back to Light
53
53
  theme = Theme.DARK if scheme == 1 else Theme.LIGHT
54
54
 
55
55
  # Always use Fusion so palette applies consistently cross-platform
@@ -58,7 +58,6 @@ class ThemeManager(QObject):
58
58
  if theme == Theme.DARK:
59
59
  pal = self._dark_palette()
60
60
  self._app.setPalette(pal)
61
- # keep stylesheet empty unless you need widget-specific tweaks
62
61
  self._app.setStyleSheet("")
63
62
  else:
64
63
  pal = self._light_palette()
bouquin/toolbar.py CHANGED
@@ -8,14 +8,12 @@ from PySide6.QtWidgets import QToolBar
8
8
  class ToolBar(QToolBar):
9
9
  boldRequested = Signal()
10
10
  italicRequested = Signal()
11
- underlineRequested = Signal()
12
11
  strikeRequested = Signal()
13
12
  codeRequested = Signal()
14
13
  headingRequested = Signal(int)
15
14
  bulletsRequested = Signal()
16
15
  numbersRequested = Signal()
17
16
  checkboxesRequested = Signal()
18
- alignRequested = Signal(Qt.AlignmentFlag)
19
17
  historyRequested = Signal()
20
18
  insertImageRequested = Signal()
21
19
 
@@ -39,12 +37,6 @@ class ToolBar(QToolBar):
39
37
  self.actItalic.setShortcut(QKeySequence.Italic)
40
38
  self.actItalic.triggered.connect(self.italicRequested)
41
39
 
42
- self.actUnderline = QAction("U", self)
43
- self.actUnderline.setToolTip("Underline")
44
- self.actUnderline.setCheckable(True)
45
- self.actUnderline.setShortcut(QKeySequence.Underline)
46
- self.actUnderline.triggered.connect(self.underlineRequested)
47
-
48
40
  self.actStrike = QAction("S", self)
49
41
  self.actStrike.setToolTip("Strikethrough")
50
42
  self.actStrike.setCheckable(True)
@@ -97,24 +89,6 @@ class ToolBar(QToolBar):
97
89
  self.actInsertImg.setShortcut("Ctrl+Shift+I")
98
90
  self.actInsertImg.triggered.connect(self.insertImageRequested)
99
91
 
100
- # Alignment
101
- self.actAlignL = QAction("L", self)
102
- self.actAlignL.setToolTip("Align Left")
103
- self.actAlignL.setCheckable(True)
104
- self.actAlignL.triggered.connect(lambda: self.alignRequested.emit(Qt.AlignLeft))
105
- self.actAlignC = QAction("C", self)
106
- self.actAlignC.setToolTip("Align Center")
107
- self.actAlignC.setCheckable(True)
108
- self.actAlignC.triggered.connect(
109
- lambda: self.alignRequested.emit(Qt.AlignHCenter)
110
- )
111
- self.actAlignR = QAction("R", self)
112
- self.actAlignR.setToolTip("Align Right")
113
- self.actAlignR.setCheckable(True)
114
- self.actAlignR.triggered.connect(
115
- lambda: self.alignRequested.emit(Qt.AlignRight)
116
- )
117
-
118
92
  # History button
119
93
  self.actHistory = QAction("History", self)
120
94
  self.actHistory.triggered.connect(self.historyRequested)
@@ -125,7 +99,6 @@ class ToolBar(QToolBar):
125
99
  for a in (
126
100
  self.actBold,
127
101
  self.actItalic,
128
- self.actUnderline,
129
102
  self.actStrike,
130
103
  self.actH1,
131
104
  self.actH2,
@@ -135,17 +108,16 @@ class ToolBar(QToolBar):
135
108
  a.setCheckable(True)
136
109
  a.setActionGroup(self.grpHeadings)
137
110
 
138
- self.grpAlign = QActionGroup(self)
139
- self.grpAlign.setExclusive(True)
140
- for a in (self.actAlignL, self.actAlignC, self.actAlignR):
141
- a.setActionGroup(self.grpAlign)
111
+ self.grpLists = QActionGroup(self)
112
+ self.grpLists.setExclusive(True)
113
+ for a in (self.actBullets, self.actNumbers, self.actCheckboxes):
114
+ a.setActionGroup(self.grpLists)
142
115
 
143
116
  # Add actions
144
117
  self.addActions(
145
118
  [
146
119
  self.actBold,
147
120
  self.actItalic,
148
- self.actUnderline,
149
121
  self.actStrike,
150
122
  self.actCode,
151
123
  self.actH1,
@@ -156,9 +128,6 @@ class ToolBar(QToolBar):
156
128
  self.actNumbers,
157
129
  self.actCheckboxes,
158
130
  self.actInsertImg,
159
- self.actAlignL,
160
- self.actAlignC,
161
- self.actAlignR,
162
131
  self.actHistory,
163
132
  ]
164
133
  )
@@ -166,7 +135,6 @@ class ToolBar(QToolBar):
166
135
  def _apply_toolbar_styles(self):
167
136
  self._style_letter_button(self.actBold, "B", bold=True)
168
137
  self._style_letter_button(self.actItalic, "I", italic=True)
169
- self._style_letter_button(self.actUnderline, "U", underline=True)
170
138
  self._style_letter_button(self.actStrike, "S", strike=True)
171
139
  # Monospace look for code; use a fixed font
172
140
  code_font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
@@ -182,11 +150,6 @@ class ToolBar(QToolBar):
182
150
  self._style_letter_button(self.actBullets, "•")
183
151
  self._style_letter_button(self.actNumbers, "1.")
184
152
 
185
- # Alignment
186
- self._style_letter_button(self.actAlignL, "L")
187
- self._style_letter_button(self.actAlignC, "C")
188
- self._style_letter_button(self.actAlignR, "R")
189
-
190
153
  # History
191
154
  self._style_letter_button(self.actHistory, "View History")
192
155
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bouquin
3
- Version: 0.1.10
3
+ Version: 0.2.1.2
4
4
  Summary: Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher.
5
5
  Home-page: https://git.mig5.net/mig5/bouquin
6
6
  License: GPL-3.0-or-later
@@ -13,7 +13,6 @@ Classifier: Programming Language :: Python :: 3.9
13
13
  Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
- Requires-Dist: markdownify (>=1.2.0,<2.0.0)
17
16
  Requires-Dist: pyside6 (>=6.8.1,<7.0.0)
18
17
  Requires-Dist: sqlcipher3-wheels (>=0.5.5.post0,<0.6.0)
19
18
  Project-URL: Repository, https://git.mig5.net/mig5/bouquin
@@ -36,7 +35,7 @@ There is deliberately no network connectivity or syncing intended.
36
35
 
37
36
  ## Screenshot
38
37
 
39
- ![Screenshot of Bouquin](./screenshot.png)
38
+ ![Screenshot of Bouquin](https://git.mig5.net/mig5/bouquin/raw/branch/main/screenshot.png)
40
39
 
41
40
  ## Features
42
41
 
@@ -44,15 +43,19 @@ There is deliberately no network connectivity or syncing intended.
44
43
  * Encryption key is prompted for and never stored, unless user chooses to via Settings
45
44
  * Every 'page' is linked to the calendar day
46
45
  * All changes are version controlled, with ability to view/diff versions and revert
47
- * Text is HTML with basic styling
46
+ * Text is Markdown with basic styling
47
+ * Tabs are supported - right-click on a date from the calendar to open it in a new tab.
48
48
  * Images are supported
49
- * Search
49
+ * Search all pages, or find text on page (Ctrl+F)
50
50
  * Automatic periodic saving (or explicitly save)
51
51
  * Transparent integrity checking of the database when it opens
52
52
  * Automatic locking of the app after a period of inactivity (default 15 min)
53
53
  * Rekey the database (change the password)
54
- * Export the database to json, txt, html, csv or .sql (for sqlite3)
54
+ * Export the database to json, txt, html, csv, markdown or .sql (for sqlite3)
55
55
  * Backup the database to encrypted SQLCipher format (which can then be loaded back in to a Bouquin)
56
+ * Dark and light themes
57
+ * Automatically generate checkboxes when typing 'TODO'
58
+ * Optionally automatically move unchecked checkboxes from yesterday to today, on startup
56
59
 
57
60
 
58
61
  ## How to install
@@ -79,5 +82,5 @@ Make sure you have `libxcb-cursor0` installed (it may be called something else o
79
82
  * Clone the repo
80
83
  * Ensure you have poetry installed
81
84
  * Run `poetry install --with test`
82
- * Run `poetry run pytest -vvvv --cov=bouquin`
85
+ * Run `./tests.sh`
83
86
 
@@ -0,0 +1,21 @@
1
+ bouquin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ bouquin/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
3
+ bouquin/db.py,sha256=mx7ogJnH_wVaCRpkOy7KBLLHyMOJDorHY14L9xz_QkE,15576
4
+ bouquin/find_bar.py,sha256=oDl3XyW7NY6q-MvDCuGglH62-nLER_HEi0W2blbWF0Y,6335
5
+ bouquin/history_dialog.py,sha256=m-crhYV_a3CMGh5-sjhvIOqpFdmB3VKNr5QCoiEaDHY,6259
6
+ bouquin/key_prompt.py,sha256=oQhLDOQv1QUr_ImA9Zu78JkDpVqPbZZJdhu0c_5Cq5U,1266
7
+ bouquin/lock_overlay.py,sha256=d1xoBMx2CSNk0zP5V6k65UqJCC4aiIrwNlfDld49ymA,4197
8
+ bouquin/main.py,sha256=lBOMS7THgHb4CAJVj8NRYABtNAEez9jlL0wI1oOtfT4,611
9
+ bouquin/main_window.py,sha256=idyUFj_lNlmSCZgfYecV16JMzQJAm2ZcmEmzNCM-jmQ,50690
10
+ bouquin/markdown_editor.py,sha256=MeKe8LkFh2XtEwUnIiIshKytGmMVVOV_3F-aUVhdKyQ,29017
11
+ bouquin/save_dialog.py,sha256=YUkZ8kL1hja15D8qv68yY2zPyjBAJZsDQbp6Y6EvDbA,1023
12
+ bouquin/search.py,sha256=jUhiy90pThmQUvsDnKYjes5SRnAWKBkqGpHPWMsfRJs,7764
13
+ bouquin/settings.py,sha256=F3WLkk2G_By3ppZsRbrnq3PtL2Zav7aA-mIegvGTc8Y,1128
14
+ bouquin/settings_dialog.py,sha256=YQHYjn3y2sgJGtkkApADxAodojDfLvnZYeQmynXLkos,10699
15
+ bouquin/theme.py,sha256=6ODq9oKLAk7lnvW9uGRMIIjfhf70SgPivLYh3ZN671M,3489
16
+ bouquin/toolbar.py,sha256=_FZ4lqJmQD86qx-0k7XpAu3HzyFedX2pG2mGyug0ea8,6588
17
+ bouquin-0.2.1.2.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
18
+ bouquin-0.2.1.2.dist-info/METADATA,sha256=LAbzz9A-S-ymz6cHn-oZ8IeksoxlKZiGc5qpc71Ugzs,3141
19
+ bouquin-0.2.1.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
20
+ bouquin-0.2.1.2.dist-info/entry_points.txt,sha256=d2C5Mc85suj1vWg_mmcfFuEBAYEkdwhZquusme5EWuQ,49
21
+ bouquin-0.2.1.2.dist-info/RECORD,,