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/db.py +34 -91
- bouquin/find_bar.py +208 -0
- bouquin/history_dialog.py +29 -28
- bouquin/key_prompt.py +6 -0
- bouquin/lock_overlay.py +8 -2
- bouquin/main_window.py +598 -119
- bouquin/markdown_editor.py +813 -0
- bouquin/save_dialog.py +3 -0
- bouquin/search.py +46 -31
- bouquin/settings_dialog.py +1 -1
- bouquin/theme.py +1 -2
- bouquin/toolbar.py +4 -41
- {bouquin-0.1.10.dist-info → bouquin-0.2.1.2.dist-info}/METADATA +10 -7
- bouquin-0.2.1.2.dist-info/RECORD +21 -0
- bouquin/editor.py +0 -897
- bouquin-0.1.10.dist-info/RECORD +0 -20
- {bouquin-0.1.10.dist-info → bouquin-0.2.1.2.dist-info}/LICENSE +0 -0
- {bouquin-0.1.10.dist-info → bouquin-0.2.1.2.dist-info}/WHEEL +0 -0
- {bouquin-0.1.10.dist-info → bouquin-0.2.1.2.dist-info}/entry_points.txt +0 -0
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(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
#
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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/settings_dialog.py
CHANGED
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
|
|
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.
|
|
139
|
-
self.
|
|
140
|
-
for a in (self.
|
|
141
|
-
a.setActionGroup(self.
|
|
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.
|
|
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
|
-

|
|
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
|
|
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 `
|
|
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,,
|