bearcli 1.2.0__tar.gz → 1.3.0__tar.gz
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.
- {bearcli-1.2.0 → bearcli-1.3.0}/PKG-INFO +30 -9
- {bearcli-1.2.0 → bearcli-1.3.0}/README.md +28 -8
- {bearcli-1.2.0 → bearcli-1.3.0}/pyproject.toml +3 -1
- {bearcli-1.2.0 → bearcli-1.3.0}/src/bearcli/cli.py +90 -0
- {bearcli-1.2.0 → bearcli-1.3.0}/src/bearcli/db.py +12 -0
- bearcli-1.3.0/src/bearcli/tui.py +83 -0
- {bearcli-1.2.0 → bearcli-1.3.0}/LICENSE +0 -0
- {bearcli-1.2.0 → bearcli-1.3.0}/src/bearcli/__init__.py +0 -0
- {bearcli-1.2.0 → bearcli-1.3.0}/src/bearcli/actions.py +0 -0
- {bearcli-1.2.0 → bearcli-1.3.0}/src/bearcli/export.py +0 -0
- {bearcli-1.2.0 → bearcli-1.3.0}/src/bearcli/gitsync.py +0 -0
- {bearcli-1.2.0 → bearcli-1.3.0}/src/bearcli/markdown.py +0 -0
- {bearcli-1.2.0 → bearcli-1.3.0}/src/bearcli/search.py +0 -0
- {bearcli-1.2.0 → bearcli-1.3.0}/src/bearcli/secrets.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bearcli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: The missing CLI for Bear notes — read, search, export, and manage your notes from the terminal
|
|
5
5
|
Keywords: bear,notes,cli,markdown,macos
|
|
6
6
|
Author: Michel Tricot
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
14
|
Classifier: Topic :: Utilities
|
|
15
15
|
Requires-Dist: detect-secrets>=1.5.0
|
|
16
16
|
Requires-Dist: rapidfuzz>=3.14.5
|
|
17
|
+
Requires-Dist: textual>=8.2.8
|
|
17
18
|
Requires-Dist: typer>=0.27.0
|
|
18
19
|
Requires-Python: >=3.13
|
|
19
20
|
Project-URL: Documentation, https://github.com/michel-tricot/bearcli/blob/main/docs/IMPLEMENTATION.md
|
|
@@ -23,9 +24,9 @@ Description-Content-Type: text/markdown
|
|
|
23
24
|
|
|
24
25
|
<div align="center">
|
|
25
26
|
|
|
26
|
-
# 🐻 bearcli
|
|
27
|
+
# 🐻 `bearcli`
|
|
27
28
|
|
|
28
|
-
**The missing CLI for [Bear](https://bear.app) notes**
|
|
29
|
+
**The missing CLI for [Bear](https://bear.app) notes** - read, search, export, and
|
|
29
30
|
manage your notes from the terminal.
|
|
30
31
|
|
|
31
32
|
[](https://github.com/michel-tricot/bearcli/actions/workflows/ci.yml)
|
|
@@ -79,6 +80,8 @@ bearcli get C44D09DC-... --meta # with YAML-style frontmatter
|
|
|
79
80
|
bearcli get C44D09DC-... -r # rewrite attachment refs to absolute paths
|
|
80
81
|
bearcli get C44D09DC-... --redact-secrets # secrets replaced by placeholders
|
|
81
82
|
bearcli open C44D09DC-... # open in the Bear app
|
|
83
|
+
bearcli browse # interactive: type to filter, Enter opens
|
|
84
|
+
bearcli stats # counts, words, top tags, notes per year
|
|
82
85
|
```
|
|
83
86
|
|
|
84
87
|
### Search
|
|
@@ -90,7 +93,7 @@ bearcli search "quarterly planing" --fuzzy # typo-tolerant, ranked by score
|
|
|
90
93
|
|
|
91
94
|
### Write
|
|
92
95
|
|
|
93
|
-
Writes go through Bear's x-callback-url API
|
|
96
|
+
Writes go through Bear's x-callback-url API - the database itself is never
|
|
94
97
|
written. These launch the Bear app if needed.
|
|
95
98
|
|
|
96
99
|
```sh
|
|
@@ -115,20 +118,28 @@ bearcli tag delete old-name # across all notes (asks first)
|
|
|
115
118
|
|
|
116
119
|
### Export
|
|
117
120
|
|
|
118
|
-
Every note becomes a self-contained directory
|
|
119
|
-
attachments
|
|
121
|
+
Every note becomes a self-contained directory - `<slug>/README.md` plus its
|
|
122
|
+
attachments - with a generated index, so GitHub renders the whole export as a
|
|
120
123
|
browsable tree.
|
|
121
124
|
|
|
122
125
|
Before anything is written, the notes are scanned for potential secrets
|
|
123
|
-
(token formats, key blocks, credential assignments);
|
|
124
|
-
with a list of the affected notes
|
|
126
|
+
(token formats, key blocks, credential assignments, high-entropy strings);
|
|
127
|
+
findings block the export with a list of the affected notes. Override with
|
|
128
|
+
`--allow-secrets` (export as-is) or `--redact-secrets` (replace each secret
|
|
129
|
+
with a `[redacted: <rule>]` placeholder - notes in Bear are untouched).
|
|
130
|
+
|
|
131
|
+
> **⚠️ Warning** - detection and redaction are best-effort: a secret that
|
|
132
|
+
> reads like ordinary text (a password written as prose, an account number)
|
|
133
|
+
> will not be caught. Ideally, don't keep secrets in notes at all - use a
|
|
134
|
+
> password manager, or at least Bear's encrypted notes, which never leave
|
|
135
|
+
> the app.
|
|
125
136
|
|
|
126
137
|
```sh
|
|
127
138
|
bearcli export ~/bear-backup
|
|
128
139
|
bearcli export ~/bear-backup --sync # only rewrite notes that changed
|
|
129
140
|
bearcli export ~/bear-backup --redact-secrets # secrets become [redacted: <rule>]
|
|
130
141
|
|
|
131
|
-
# Mirror to a git repository (clone it first; use a *private* repo
|
|
142
|
+
# Mirror to a git repository (clone it first; use a *private* repo - these are
|
|
132
143
|
# your notes). Bear is the source of truth: remote or manual edits are kept in
|
|
133
144
|
# git history but overwritten in HEAD. Never force-pushes, never gets stuck.
|
|
134
145
|
git clone git@github.com:you/bear-notes.git ~/bear-notes
|
|
@@ -150,6 +161,16 @@ defaults to Bear's standard location and can be overridden with `--db` or the
|
|
|
150
161
|
`BEAR_DB_PATH` environment variable. Encrypted notes are listed but their
|
|
151
162
|
content cannot be read.
|
|
152
163
|
|
|
164
|
+
## Agent skill
|
|
165
|
+
|
|
166
|
+
An [Agent Skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills)
|
|
167
|
+
teaching AI agents (Claude Code, etc.) how to use `bearcli` ships in
|
|
168
|
+
[`skills/bear-notes`](skills/bear-notes/SKILL.md):
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
cp -r skills/bear-notes ~/.claude/skills/ # or a project's .claude/skills/
|
|
172
|
+
```
|
|
173
|
+
|
|
153
174
|
## Development
|
|
154
175
|
|
|
155
176
|
```sh
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
# 🐻 bearcli
|
|
3
|
+
# 🐻 `bearcli`
|
|
4
4
|
|
|
5
|
-
**The missing CLI for [Bear](https://bear.app) notes**
|
|
5
|
+
**The missing CLI for [Bear](https://bear.app) notes** - read, search, export, and
|
|
6
6
|
manage your notes from the terminal.
|
|
7
7
|
|
|
8
8
|
[](https://github.com/michel-tricot/bearcli/actions/workflows/ci.yml)
|
|
@@ -56,6 +56,8 @@ bearcli get C44D09DC-... --meta # with YAML-style frontmatter
|
|
|
56
56
|
bearcli get C44D09DC-... -r # rewrite attachment refs to absolute paths
|
|
57
57
|
bearcli get C44D09DC-... --redact-secrets # secrets replaced by placeholders
|
|
58
58
|
bearcli open C44D09DC-... # open in the Bear app
|
|
59
|
+
bearcli browse # interactive: type to filter, Enter opens
|
|
60
|
+
bearcli stats # counts, words, top tags, notes per year
|
|
59
61
|
```
|
|
60
62
|
|
|
61
63
|
### Search
|
|
@@ -67,7 +69,7 @@ bearcli search "quarterly planing" --fuzzy # typo-tolerant, ranked by score
|
|
|
67
69
|
|
|
68
70
|
### Write
|
|
69
71
|
|
|
70
|
-
Writes go through Bear's x-callback-url API
|
|
72
|
+
Writes go through Bear's x-callback-url API - the database itself is never
|
|
71
73
|
written. These launch the Bear app if needed.
|
|
72
74
|
|
|
73
75
|
```sh
|
|
@@ -92,20 +94,28 @@ bearcli tag delete old-name # across all notes (asks first)
|
|
|
92
94
|
|
|
93
95
|
### Export
|
|
94
96
|
|
|
95
|
-
Every note becomes a self-contained directory
|
|
96
|
-
attachments
|
|
97
|
+
Every note becomes a self-contained directory - `<slug>/README.md` plus its
|
|
98
|
+
attachments - with a generated index, so GitHub renders the whole export as a
|
|
97
99
|
browsable tree.
|
|
98
100
|
|
|
99
101
|
Before anything is written, the notes are scanned for potential secrets
|
|
100
|
-
(token formats, key blocks, credential assignments);
|
|
101
|
-
with a list of the affected notes
|
|
102
|
+
(token formats, key blocks, credential assignments, high-entropy strings);
|
|
103
|
+
findings block the export with a list of the affected notes. Override with
|
|
104
|
+
`--allow-secrets` (export as-is) or `--redact-secrets` (replace each secret
|
|
105
|
+
with a `[redacted: <rule>]` placeholder - notes in Bear are untouched).
|
|
106
|
+
|
|
107
|
+
> **⚠️ Warning** - detection and redaction are best-effort: a secret that
|
|
108
|
+
> reads like ordinary text (a password written as prose, an account number)
|
|
109
|
+
> will not be caught. Ideally, don't keep secrets in notes at all - use a
|
|
110
|
+
> password manager, or at least Bear's encrypted notes, which never leave
|
|
111
|
+
> the app.
|
|
102
112
|
|
|
103
113
|
```sh
|
|
104
114
|
bearcli export ~/bear-backup
|
|
105
115
|
bearcli export ~/bear-backup --sync # only rewrite notes that changed
|
|
106
116
|
bearcli export ~/bear-backup --redact-secrets # secrets become [redacted: <rule>]
|
|
107
117
|
|
|
108
|
-
# Mirror to a git repository (clone it first; use a *private* repo
|
|
118
|
+
# Mirror to a git repository (clone it first; use a *private* repo - these are
|
|
109
119
|
# your notes). Bear is the source of truth: remote or manual edits are kept in
|
|
110
120
|
# git history but overwritten in HEAD. Never force-pushes, never gets stuck.
|
|
111
121
|
git clone git@github.com:you/bear-notes.git ~/bear-notes
|
|
@@ -127,6 +137,16 @@ defaults to Bear's standard location and can be overridden with `--db` or the
|
|
|
127
137
|
`BEAR_DB_PATH` environment variable. Encrypted notes are listed but their
|
|
128
138
|
content cannot be read.
|
|
129
139
|
|
|
140
|
+
## Agent skill
|
|
141
|
+
|
|
142
|
+
An [Agent Skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills)
|
|
143
|
+
teaching AI agents (Claude Code, etc.) how to use `bearcli` ships in
|
|
144
|
+
[`skills/bear-notes`](skills/bear-notes/SKILL.md):
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
cp -r skills/bear-notes ~/.claude/skills/ # or a project's .claude/skills/
|
|
148
|
+
```
|
|
149
|
+
|
|
130
150
|
## Development
|
|
131
151
|
|
|
132
152
|
```sh
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bearcli"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.0"
|
|
4
4
|
description = "The missing CLI for Bear notes — read, search, export, and manage your notes from the terminal"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
keywords = ["bear", "notes", "cli", "markdown", "macos"]
|
|
@@ -20,6 +20,7 @@ requires-python = ">=3.13"
|
|
|
20
20
|
dependencies = [
|
|
21
21
|
"detect-secrets>=1.5.0",
|
|
22
22
|
"rapidfuzz>=3.14.5",
|
|
23
|
+
"textual>=8.2.8",
|
|
23
24
|
"typer>=0.27.0",
|
|
24
25
|
]
|
|
25
26
|
|
|
@@ -37,6 +38,7 @@ build-backend = "uv_build"
|
|
|
37
38
|
|
|
38
39
|
[dependency-groups]
|
|
39
40
|
dev = [
|
|
41
|
+
"pytest>=9.1.1",
|
|
40
42
|
"ruff>=0.16.0",
|
|
41
43
|
"ty>=0.0.64",
|
|
42
44
|
]
|
|
@@ -841,3 +841,93 @@ _alias("search", "note search", search)
|
|
|
841
841
|
_alias("get", "note get", get)
|
|
842
842
|
_alias("open", "note open", open_note)
|
|
843
843
|
_alias("create", "note create", create)
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
@app.command()
|
|
847
|
+
def stats(
|
|
848
|
+
fmt: Annotated[
|
|
849
|
+
OutputFormat,
|
|
850
|
+
typer.Option("--format", "-f", help="Output format: table or json."),
|
|
851
|
+
] = OutputFormat.table,
|
|
852
|
+
db_path: DbPathOption = DEFAULT_DB_PATH,
|
|
853
|
+
) -> None:
|
|
854
|
+
"""Show statistics about the note library."""
|
|
855
|
+
db = _open_db(db_path)
|
|
856
|
+
try:
|
|
857
|
+
notes = db.list_notes(limit=None, include_trashed=True, include_archived=True, with_text=True)
|
|
858
|
+
tag_counts = db.list_tags()
|
|
859
|
+
attachment_count, attachment_bytes = db.attachment_stats()
|
|
860
|
+
finally:
|
|
861
|
+
db.close()
|
|
862
|
+
|
|
863
|
+
active = [n for n in notes if not n.trashed and not n.archived]
|
|
864
|
+
by_year: dict[str, int] = {}
|
|
865
|
+
for n in notes:
|
|
866
|
+
if not n.trashed and n.created:
|
|
867
|
+
year = str(n.created.year)
|
|
868
|
+
by_year[year] = by_year.get(year, 0) + 1
|
|
869
|
+
|
|
870
|
+
data = {
|
|
871
|
+
"notes": len(notes),
|
|
872
|
+
"active": len(active),
|
|
873
|
+
"archived": sum(1 for n in notes if n.archived and not n.trashed),
|
|
874
|
+
"trashed": sum(1 for n in notes if n.trashed),
|
|
875
|
+
"pinned": sum(1 for n in notes if n.pinned and not n.trashed),
|
|
876
|
+
"encrypted": sum(1 for n in notes if n.encrypted and not n.trashed),
|
|
877
|
+
"words": sum(len((n.text or "").split()) for n in notes if not n.trashed),
|
|
878
|
+
"tags": len(tag_counts),
|
|
879
|
+
"attachments": attachment_count,
|
|
880
|
+
"attachment_bytes": attachment_bytes,
|
|
881
|
+
"notes_by_year": dict(sorted(by_year.items())),
|
|
882
|
+
"top_tags": [{"tag": t, "notes": c} for t, c in sorted(tag_counts, key=lambda tc: -tc[1])[:10]],
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if fmt is OutputFormat.json:
|
|
886
|
+
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
887
|
+
return
|
|
888
|
+
|
|
889
|
+
table = Table(box=box.ROUNDED, show_header=False)
|
|
890
|
+
table.add_column(style="bold")
|
|
891
|
+
table.add_column(justify="right")
|
|
892
|
+
table.add_row("Notes", str(data["notes"]))
|
|
893
|
+
table.add_row(" active", str(data["active"]))
|
|
894
|
+
table.add_row(" archived", str(data["archived"]))
|
|
895
|
+
table.add_row(" trashed", str(data["trashed"]))
|
|
896
|
+
table.add_row(" pinned", str(data["pinned"]))
|
|
897
|
+
table.add_row(" encrypted", str(data["encrypted"]))
|
|
898
|
+
table.add_row("Words", f"{data['words']:,}")
|
|
899
|
+
table.add_row("Tags", str(data["tags"]))
|
|
900
|
+
table.add_row("Attachments", f"{attachment_count} ({attachment_bytes / 1_000_000:.1f} MB)")
|
|
901
|
+
console.print(table)
|
|
902
|
+
|
|
903
|
+
if data["top_tags"]:
|
|
904
|
+
tag_table = Table(box=box.ROUNDED, header_style="bold")
|
|
905
|
+
tag_table.add_column("Top tags", style="cyan")
|
|
906
|
+
tag_table.add_column("Notes", justify="right")
|
|
907
|
+
for entry in data["top_tags"]:
|
|
908
|
+
tag_table.add_row(str(entry["tag"]), str(entry["notes"]))
|
|
909
|
+
console.print(tag_table)
|
|
910
|
+
|
|
911
|
+
year_table = Table(box=box.ROUNDED, header_style="bold")
|
|
912
|
+
year_table.add_column("Created", style="cyan")
|
|
913
|
+
year_table.add_column("Notes", justify="right")
|
|
914
|
+
for year, count in data["notes_by_year"].items():
|
|
915
|
+
year_table.add_row(year, str(count))
|
|
916
|
+
console.print(year_table)
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
@app.command()
|
|
920
|
+
def browse(
|
|
921
|
+
fuzzy: Annotated[bool, typer.Option("--fuzzy", help="Typo-tolerant ranked filtering.")] = False,
|
|
922
|
+
tag_filter: Annotated[str | None, typer.Option("--tag", "-t", help="Restrict to notes with this tag.")] = None,
|
|
923
|
+
db_path: DbPathOption = DEFAULT_DB_PATH,
|
|
924
|
+
) -> None:
|
|
925
|
+
"""Browse notes interactively: type to filter, Enter opens the note in Bear."""
|
|
926
|
+
from bearcli.tui import browse as run_browser
|
|
927
|
+
|
|
928
|
+
db = _open_db(db_path)
|
|
929
|
+
try:
|
|
930
|
+
notes = db.list_notes(limit=None, tag=tag_filter, with_text=True)
|
|
931
|
+
finally:
|
|
932
|
+
db.close()
|
|
933
|
+
run_browser(notes, fuzzy=fuzzy)
|
|
@@ -144,6 +144,18 @@ class BearDB:
|
|
|
144
144
|
).fetchall()
|
|
145
145
|
return [(row[0], row[1]) for row in rows]
|
|
146
146
|
|
|
147
|
+
def attachment_stats(self) -> tuple[int, int]:
|
|
148
|
+
"""(count, total bytes) of attachments on non-deleted notes."""
|
|
149
|
+
row = self.conn.execute(
|
|
150
|
+
"""
|
|
151
|
+
SELECT COUNT(*), COALESCE(SUM(f.ZFILESIZE), 0)
|
|
152
|
+
FROM ZSFNOTEFILE f
|
|
153
|
+
JOIN ZSFNOTE n ON n.Z_PK = f.ZNOTE
|
|
154
|
+
WHERE f.ZPERMANENTLYDELETED = 0 AND n.ZPERMANENTLYDELETED = 0 AND n.ZTRASHED = 0
|
|
155
|
+
"""
|
|
156
|
+
).fetchone()
|
|
157
|
+
return row[0], row[1]
|
|
158
|
+
|
|
147
159
|
def _attachments_for_note(self, note_pk: int) -> list[Attachment]:
|
|
148
160
|
rows = self.conn.execute(
|
|
149
161
|
"""
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Interactive note browser (Textual)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from textual.app import App, ComposeResult
|
|
6
|
+
from textual.containers import Horizontal
|
|
7
|
+
from textual.widgets import Input, OptionList, Static
|
|
8
|
+
from textual.widgets.option_list import Option
|
|
9
|
+
|
|
10
|
+
from bearcli import actions
|
|
11
|
+
from bearcli.db import Note
|
|
12
|
+
from bearcli.search import naive_search, search_notes
|
|
13
|
+
|
|
14
|
+
MAX_RESULTS = 50
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BrowseApp(App):
|
|
18
|
+
"""Type to filter, arrows to move, Enter to open in Bear, Esc to quit."""
|
|
19
|
+
|
|
20
|
+
CSS = """
|
|
21
|
+
#query { dock: top; }
|
|
22
|
+
#status { dock: bottom; height: 1; color: $text-muted; padding: 0 1; }
|
|
23
|
+
#results { height: 1fr; }
|
|
24
|
+
#preview { width: 45%; border-left: solid $surface; padding: 0 1; color: $text-muted; }
|
|
25
|
+
"""
|
|
26
|
+
BINDINGS = [("escape", "quit", "Quit")]
|
|
27
|
+
|
|
28
|
+
def __init__(self, notes: list[Note], fuzzy: bool = False):
|
|
29
|
+
super().__init__()
|
|
30
|
+
self.notes = notes
|
|
31
|
+
self.fuzzy = fuzzy
|
|
32
|
+
self.shown: list[Note] = []
|
|
33
|
+
|
|
34
|
+
def compose(self) -> ComposeResult:
|
|
35
|
+
yield Input(placeholder="Search notes…", id="query")
|
|
36
|
+
with Horizontal():
|
|
37
|
+
yield OptionList(id="results")
|
|
38
|
+
yield Static(id="preview")
|
|
39
|
+
yield Static(id="status")
|
|
40
|
+
|
|
41
|
+
def on_mount(self) -> None:
|
|
42
|
+
self._refresh("")
|
|
43
|
+
|
|
44
|
+
def on_input_changed(self, event: Input.Changed) -> None:
|
|
45
|
+
self._refresh(event.value)
|
|
46
|
+
|
|
47
|
+
def _refresh(self, query: str) -> None:
|
|
48
|
+
if not query.strip():
|
|
49
|
+
self.shown = self.notes[:MAX_RESULTS]
|
|
50
|
+
elif self.fuzzy:
|
|
51
|
+
self.shown = [r.note for r in search_notes(self.notes, query)[:MAX_RESULTS]]
|
|
52
|
+
else:
|
|
53
|
+
self.shown = [r.note for r in naive_search(self.notes, query)[:MAX_RESULTS]]
|
|
54
|
+
|
|
55
|
+
results = self.query_one("#results", OptionList)
|
|
56
|
+
results.clear_options()
|
|
57
|
+
for note in self.shown:
|
|
58
|
+
date = note.modified.strftime("%Y-%m-%d") if note.modified else " "
|
|
59
|
+
tags = f" #{' #'.join(note.tags)}" if note.tags else ""
|
|
60
|
+
results.add_option(Option(f"{date} {note.title}{tags}", id=note.id))
|
|
61
|
+
if self.shown:
|
|
62
|
+
results.highlighted = 0
|
|
63
|
+
self.query_one("#status", Static).update(
|
|
64
|
+
f"{len(self.shown)} shown / {len(self.notes)} notes · Enter: open in Bear · Esc: quit"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def on_option_list_option_highlighted(self, event: OptionList.OptionHighlighted) -> None:
|
|
68
|
+
note = next((n for n in self.shown if n.id == event.option_id), None)
|
|
69
|
+
preview = "\n".join((note.text or "").splitlines()[:40]) if note else ""
|
|
70
|
+
self.query_one("#preview", Static).update(preview)
|
|
71
|
+
|
|
72
|
+
def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
|
|
73
|
+
if event.option_id:
|
|
74
|
+
actions.open_note(event.option_id)
|
|
75
|
+
|
|
76
|
+
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
77
|
+
results = self.query_one("#results", OptionList)
|
|
78
|
+
if self.shown and results.highlighted is not None:
|
|
79
|
+
actions.open_note(self.shown[results.highlighted].id)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def browse(notes: list[Note], fuzzy: bool = False) -> None:
|
|
83
|
+
BrowseApp(notes, fuzzy=fuzzy).run()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|