bouquin 0.1.12__py3-none-any.whl → 0.2.1.3__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/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,
@@ -149,10 +148,12 @@ class Search(QWidget):
149
148
  self.results.setItemWidget(item, container)
150
149
 
151
150
  # --- Snippet/highlight helpers -----------------------------------------
152
- def _make_html_snippet(self, html_src: str, query: str, *, radius=60, maxlen=180):
153
- doc = QTextDocument()
154
- doc.setHtml(html_src)
155
- 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)
156
157
  if not plain:
157
158
  return "", False, False
158
159
 
@@ -179,30 +180,45 @@ class Search(QWidget):
179
180
  start = max(0, min(idx - radius, max(0, L - maxlen)))
180
181
  end = min(L, max(idx + mlen + radius, start + maxlen))
181
182
 
182
- # 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)
183
190
  if tokens:
184
- lower = plain.lower()
185
- fmt = QTextCharFormat()
186
- fmt.setFontWeight(QFont.Weight.Bold)
187
191
  for t in tokens:
188
- t_low = t.lower()
189
- pos = start
190
- while True:
191
- k = lower.find(t_low, pos)
192
- if k == -1 or k >= end:
193
- break
194
- c = QTextCursor(doc)
195
- c.setPosition(k)
196
- c.setPosition(k + len(t), QTextCursor.MoveMode.KeepAnchor)
197
- c.mergeCharFormat(fmt)
198
- pos = k + len(t)
199
-
200
- # Select the window and export as HTML fragment
201
- c = QTextCursor(doc)
202
- c.setPosition(start)
203
- c.setPosition(end, QTextCursor.MoveMode.KeepAnchor)
204
- fragment_html = (
205
- c.selection().toHtml()
206
- ) # preserves original styles + our bolding
207
-
208
- 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()
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,11 +108,6 @@ 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)
142
-
143
111
  self.grpLists = QActionGroup(self)
144
112
  self.grpLists.setExclusive(True)
145
113
  for a in (self.actBullets, self.actNumbers, self.actCheckboxes):
@@ -150,7 +118,6 @@ class ToolBar(QToolBar):
150
118
  [
151
119
  self.actBold,
152
120
  self.actItalic,
153
- self.actUnderline,
154
121
  self.actStrike,
155
122
  self.actCode,
156
123
  self.actH1,
@@ -161,9 +128,6 @@ class ToolBar(QToolBar):
161
128
  self.actNumbers,
162
129
  self.actCheckboxes,
163
130
  self.actInsertImg,
164
- self.actAlignL,
165
- self.actAlignC,
166
- self.actAlignR,
167
131
  self.actHistory,
168
132
  ]
169
133
  )
@@ -171,7 +135,6 @@ class ToolBar(QToolBar):
171
135
  def _apply_toolbar_styles(self):
172
136
  self._style_letter_button(self.actBold, "B", bold=True)
173
137
  self._style_letter_button(self.actItalic, "I", italic=True)
174
- self._style_letter_button(self.actUnderline, "U", underline=True)
175
138
  self._style_letter_button(self.actStrike, "S", strike=True)
176
139
  # Monospace look for code; use a fixed font
177
140
  code_font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
@@ -187,11 +150,6 @@ class ToolBar(QToolBar):
187
150
  self._style_letter_button(self.actBullets, "•")
188
151
  self._style_letter_button(self.actNumbers, "1.")
189
152
 
190
- # Alignment
191
- self._style_letter_button(self.actAlignL, "L")
192
- self._style_letter_button(self.actAlignC, "C")
193
- self._style_letter_button(self.actAlignR, "R")
194
-
195
153
  # History
196
154
  self._style_letter_button(self.actHistory, "View History")
197
155
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bouquin
3
- Version: 0.1.12
3
+ Version: 0.2.1.3
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,9 +35,7 @@ There is deliberately no network connectivity or syncing intended.
36
35
 
37
36
  ## Screenshot
38
37
 
39
- ![Screenshot of Bouquin](./screenshot.png)
40
-
41
- ![Screenshot of Bouquin in dark mode](./screenshot_dark.png)
38
+ ![Screenshot of Bouquin](https://git.mig5.net/mig5/bouquin/raw/branch/main/screenshot.png)
42
39
 
43
40
  ## Features
44
41
 
@@ -46,9 +43,10 @@ There is deliberately no network connectivity or syncing intended.
46
43
  * Encryption key is prompted for and never stored, unless user chooses to via Settings
47
44
  * Every 'page' is linked to the calendar day
48
45
  * All changes are version controlled, with ability to view/diff versions and revert
49
- * 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.
50
48
  * Images are supported
51
- * Search
49
+ * Search all pages, or find text on page (Ctrl+F)
52
50
  * Automatic periodic saving (or explicitly save)
53
51
  * Transparent integrity checking of the database when it opens
54
52
  * Automatic locking of the app after a period of inactivity (default 15 min)
@@ -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=5esRhZuUEKySw2YjVPaeXOA0TOJW13OaEK1h9I_ZDdw,33318
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.3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
18
+ bouquin-0.2.1.3.dist-info/METADATA,sha256=S2vVxGNzrff0_pLwOBfzXjIR3DEWG0Fhjt0IVFzNt-8,3141
19
+ bouquin-0.2.1.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
20
+ bouquin-0.2.1.3.dist-info/entry_points.txt,sha256=d2C5Mc85suj1vWg_mmcfFuEBAYEkdwhZquusme5EWuQ,49
21
+ bouquin-0.2.1.3.dist-info/RECORD,,