td-task 0.2.3__tar.gz → 0.2.5__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.
- td_task-0.2.5/.github/workflows/pages.yml +50 -0
- {td_task-0.2.3 → td_task-0.2.5}/PKG-INFO +7 -2
- {td_task-0.2.3 → td_task-0.2.5}/README.md +5 -0
- td_task-0.2.5/index.html +78 -0
- {td_task-0.2.3 → td_task-0.2.5}/pyproject.toml +1 -1
- {td_task-0.2.3 → td_task-0.2.5}/src/td/CHANGELOG.md +9 -0
- {td_task-0.2.3 → td_task-0.2.5}/src/td/terminal.py +72 -20
- {td_task-0.2.3 → td_task-0.2.5}/src/td/tui.py +330 -83
- td_task-0.2.5/tests/test_resize.py +333 -0
- {td_task-0.2.3 → td_task-0.2.5}/tests/test_tui_notes.py +181 -1
- {td_task-0.2.3 → td_task-0.2.5}/uv.lock +1 -1
- td_task-0.2.5/website/README.md +29 -0
- td_task-0.2.5/website/favicon.svg +4 -0
- td_task-0.2.5/website/main.js +21 -0
- td_task-0.2.5/website/style.css +57 -0
- {td_task-0.2.3 → td_task-0.2.5}/.env.enc +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/.github/workflows/publish.yml +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/.gitignore +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/.python-version +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/assets/demo.gif +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/demo.tape +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/src/td/__init__.py +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/src/td/__main__.py +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/src/td/db.py +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/tests/test_cli_notes.py +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/tests/test_notes.py +0 -0
- {td_task-0.2.3 → td_task-0.2.5}/tests/test_update.py +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Publish homepage
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
paths:
|
|
8
|
+
- index.html
|
|
9
|
+
- website/**
|
|
10
|
+
- assets/demo.gif
|
|
11
|
+
- .github/workflows/pages.yml
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: github-pages
|
|
18
|
+
cancel-in-progress: false
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
deploy:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
permissions:
|
|
24
|
+
pages: write
|
|
25
|
+
id-token: write
|
|
26
|
+
environment:
|
|
27
|
+
name: github-pages
|
|
28
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
29
|
+
steps:
|
|
30
|
+
- name: Checkout
|
|
31
|
+
uses: actions/checkout@v6
|
|
32
|
+
|
|
33
|
+
- name: Configure Pages
|
|
34
|
+
uses: actions/configure-pages@v5
|
|
35
|
+
|
|
36
|
+
- name: Stage public files
|
|
37
|
+
run: |
|
|
38
|
+
mkdir -p _site/assets
|
|
39
|
+
cp index.html _site/
|
|
40
|
+
cp -R website _site/website
|
|
41
|
+
cp assets/demo.gif _site/assets/
|
|
42
|
+
|
|
43
|
+
- name: Upload site
|
|
44
|
+
uses: actions/upload-pages-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
path: _site
|
|
47
|
+
|
|
48
|
+
- name: Deploy
|
|
49
|
+
id: deployment
|
|
50
|
+
uses: actions/deploy-pages@v4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: td-task
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Fast TUI and CLI manager for tasks and Markdown notes
|
|
5
5
|
Requires-Python: >=3.14
|
|
6
6
|
Requires-Dist: cryptography>=42.0.0
|
|
@@ -110,6 +110,9 @@ td notes delete 12 --json
|
|
|
110
110
|
|
|
111
111
|
## TUI Keybindings
|
|
112
112
|
|
|
113
|
+
The TUI redraws automatically when you resize the terminal, keeping the current
|
|
114
|
+
selection and unsaved edits. Help supports arrow-key and PageUp/PageDown scrolling.
|
|
115
|
+
|
|
113
116
|
### Tasks
|
|
114
117
|
|
|
115
118
|
| Key | Action |
|
|
@@ -121,8 +124,10 @@ td notes delete 12 --json
|
|
|
121
124
|
| `d` | Delete selected task |
|
|
122
125
|
| `Space` | Toggle done / active |
|
|
123
126
|
| `s` | Star task (pin to top, bold yellow) |
|
|
127
|
+
| `S` | Cycle task sort: A-Z / Z-A / original order |
|
|
124
128
|
| `c` | Archive all completed tasks |
|
|
125
129
|
| `y` | Copy task list to clipboard |
|
|
130
|
+
| `f` | Filter notes by title in current list; submit empty query to clear |
|
|
126
131
|
| `↑` / `k` `↓` / `j` | Navigate tasks |
|
|
127
132
|
| `PageUp` / `PageDown` | Move one viewport |
|
|
128
133
|
| `Ctrl+↑` / `Ctrl+↓` | Reorder task position |
|
|
@@ -99,6 +99,9 @@ td notes delete 12 --json
|
|
|
99
99
|
|
|
100
100
|
## TUI Keybindings
|
|
101
101
|
|
|
102
|
+
The TUI redraws automatically when you resize the terminal, keeping the current
|
|
103
|
+
selection and unsaved edits. Help supports arrow-key and PageUp/PageDown scrolling.
|
|
104
|
+
|
|
102
105
|
### Tasks
|
|
103
106
|
|
|
104
107
|
| Key | Action |
|
|
@@ -110,8 +113,10 @@ td notes delete 12 --json
|
|
|
110
113
|
| `d` | Delete selected task |
|
|
111
114
|
| `Space` | Toggle done / active |
|
|
112
115
|
| `s` | Star task (pin to top, bold yellow) |
|
|
116
|
+
| `S` | Cycle task sort: A-Z / Z-A / original order |
|
|
113
117
|
| `c` | Archive all completed tasks |
|
|
114
118
|
| `y` | Copy task list to clipboard |
|
|
119
|
+
| `f` | Filter notes by title in current list; submit empty query to clear |
|
|
115
120
|
| `↑` / `k` `↓` / `j` | Navigate tasks |
|
|
116
121
|
| `PageUp` / `PageDown` | Move one viewport |
|
|
117
122
|
| `Ctrl+↑` / `Ctrl+↓` | Reorder task position |
|
td_task-0.2.5/index.html
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="theme-color" content="#141414">
|
|
7
|
+
<meta name="description" content="td: tasks and Markdown notes in your terminal.">
|
|
8
|
+
<meta property="og:type" content="website">
|
|
9
|
+
<meta property="og:title" content="td">
|
|
10
|
+
<meta property="og:description" content="Tasks and Markdown notes in your terminal.">
|
|
11
|
+
<title>td — terminal tasks and notes</title>
|
|
12
|
+
<link rel="icon" href="website/favicon.svg" type="image/svg+xml">
|
|
13
|
+
<link rel="stylesheet" href="website/style.css">
|
|
14
|
+
<script src="website/main.js" defer></script>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<a class="skip-link" href="#main">Skip to content</a>
|
|
18
|
+
<div class="page">
|
|
19
|
+
<header>
|
|
20
|
+
<p>td — tasks and Markdown notes in your terminal.</p>
|
|
21
|
+
</header>
|
|
22
|
+
|
|
23
|
+
<main id="main">
|
|
24
|
+
<ul class="features" aria-label="Features">
|
|
25
|
+
<li>Multiple lists and keyboard navigation.</li>
|
|
26
|
+
<li>Markdown notes with a built-in editor.</li>
|
|
27
|
+
<li>Optional database and list encryption.</li>
|
|
28
|
+
<li>JSON export and a scriptable CLI.</li>
|
|
29
|
+
</ul>
|
|
30
|
+
<figure>
|
|
31
|
+
<a href="assets/demo.gif" aria-label="Open the demo GIF at full size">
|
|
32
|
+
<img src="assets/demo.gif" width="680" height="360" alt="Demo of td: adding tasks, starring priorities, and switching lists with the keyboard." fetchpriority="high">
|
|
33
|
+
</a>
|
|
34
|
+
</figure>
|
|
35
|
+
|
|
36
|
+
<section id="install" aria-label="Installation and usage">
|
|
37
|
+
<dl class="commands">
|
|
38
|
+
<div>
|
|
39
|
+
<dt>install (uv)</dt>
|
|
40
|
+
<dd>
|
|
41
|
+
<code>uv tool install td-task</code>
|
|
42
|
+
<button type="button" data-copy="uv tool install td-task" aria-label="Copy uv install command" hidden>[copy]</button>
|
|
43
|
+
</dd>
|
|
44
|
+
</div>
|
|
45
|
+
<div>
|
|
46
|
+
<dt>or pip</dt>
|
|
47
|
+
<dd>
|
|
48
|
+
<code>pip install td-task</code>
|
|
49
|
+
<button type="button" data-copy="pip install td-task" aria-label="Copy pip install command" hidden>[copy]</button>
|
|
50
|
+
</dd>
|
|
51
|
+
</div>
|
|
52
|
+
<div>
|
|
53
|
+
<dt>run</dt>
|
|
54
|
+
<dd>
|
|
55
|
+
<code>td</code>
|
|
56
|
+
<button type="button" data-copy="td" aria-label="Copy td launch command" hidden>[copy]</button>
|
|
57
|
+
</dd>
|
|
58
|
+
</div>
|
|
59
|
+
<div>
|
|
60
|
+
<dt>or uvx</dt>
|
|
61
|
+
<dd>
|
|
62
|
+
<code>uvx td-task</code>
|
|
63
|
+
<button type="button" data-copy="uvx td-task" aria-label="Copy run without installing command" hidden>[copy]</button>
|
|
64
|
+
</dd>
|
|
65
|
+
</div>
|
|
66
|
+
</dl>
|
|
67
|
+
<p class="requirement">Python 3.14+. <a href="https://docs.astral.sh/uv/getting-started/installation/">uv docs</a>.</p>
|
|
68
|
+
<p class="copy-status" role="status" aria-live="polite"></p>
|
|
69
|
+
</section>
|
|
70
|
+
</main>
|
|
71
|
+
|
|
72
|
+
<footer>
|
|
73
|
+
<a href="https://github.com/lucaspon/td">github.com/lucaspon/td</a>
|
|
74
|
+
<a href="https://twitter.com/lucasponn">@lucasponn</a>
|
|
75
|
+
</footer>
|
|
76
|
+
</div>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.5] - 2026-11-04
|
|
4
|
+
- Redraw automatically when terminal dimensions change, without waiting for a keypress.
|
|
5
|
+
- Preserve selection, unsaved edits, note cursor, and confirmations while resizing.
|
|
6
|
+
- Fit frames to smaller windows and reflow note content when the terminal grows.
|
|
7
|
+
- Scroll help with arrow keys and PageUp/PageDown.
|
|
8
|
+
|
|
9
|
+
## [0.2.4] - 2026-08-05
|
|
10
|
+
- Add live, case-insensitive note-title filtering with `f` in the current list.
|
|
11
|
+
|
|
3
12
|
## [0.2.3] - 2026-08-05
|
|
4
13
|
- Scroll large task lists inside a fixed header and footer viewport.
|
|
5
14
|
- Buffer complete TUI frames to prevent flicker and partial header rendering.
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
|
+
import time
|
|
5
6
|
|
|
6
7
|
IS_WINDOWS = sys.platform == "win32"
|
|
7
8
|
|
|
@@ -38,6 +39,30 @@ KEY_CTRL_BACKSPACE = "\x17"
|
|
|
38
39
|
KEY_CMD_BACKSPACE = "\x15"
|
|
39
40
|
KEY_CTRL_P = "\x10"
|
|
40
41
|
KEY_CTRL_S = "\x13"
|
|
42
|
+
KEY_RESIZE = "<resize>"
|
|
43
|
+
|
|
44
|
+
_last_size: os.terminal_size | None = None
|
|
45
|
+
_RESIZE_POLL_INTERVAL = 0.05
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_terminal_size() -> os.terminal_size:
|
|
49
|
+
"""Read live terminal dimensions, without stale COLUMNS/LINES overrides."""
|
|
50
|
+
for stream in (sys.stdout, sys.stdin):
|
|
51
|
+
try:
|
|
52
|
+
size = os.get_terminal_size(stream.fileno())
|
|
53
|
+
except (AttributeError, OSError, ValueError):
|
|
54
|
+
continue
|
|
55
|
+
if size.columns > 0 and size.lines > 0:
|
|
56
|
+
return size
|
|
57
|
+
return os.terminal_size((80, 24))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _terminal_resized() -> bool:
|
|
61
|
+
global _last_size
|
|
62
|
+
size = get_terminal_size()
|
|
63
|
+
changed = _last_size is not None and size != _last_size
|
|
64
|
+
_last_size = size
|
|
65
|
+
return changed
|
|
41
66
|
|
|
42
67
|
|
|
43
68
|
if IS_WINDOWS:
|
|
@@ -86,6 +111,8 @@ if IS_WINDOWS:
|
|
|
86
111
|
class _RawContext:
|
|
87
112
|
"""Context manager for raw terminal mode on Windows."""
|
|
88
113
|
def __enter__(self):
|
|
114
|
+
global _last_size
|
|
115
|
+
_last_size = get_terminal_size()
|
|
89
116
|
return self
|
|
90
117
|
|
|
91
118
|
def __exit__(self, *args):
|
|
@@ -101,6 +128,12 @@ if IS_WINDOWS:
|
|
|
101
128
|
we translate to ANSI sequences. A literal Esc (or a VT-input terminal
|
|
102
129
|
sending real escape sequences) is handled as a fallback.
|
|
103
130
|
"""
|
|
131
|
+
while True:
|
|
132
|
+
if _terminal_resized():
|
|
133
|
+
return KEY_RESIZE
|
|
134
|
+
if msvcrt.kbhit():
|
|
135
|
+
break
|
|
136
|
+
time.sleep(_RESIZE_POLL_INTERVAL)
|
|
104
137
|
ch = msvcrt.getwch()
|
|
105
138
|
|
|
106
139
|
# Native special-key protocol.
|
|
@@ -131,9 +164,15 @@ if IS_WINDOWS:
|
|
|
131
164
|
return ch
|
|
132
165
|
|
|
133
166
|
else:
|
|
167
|
+
import codecs
|
|
168
|
+
import select
|
|
134
169
|
import tty
|
|
135
170
|
import termios
|
|
136
171
|
|
|
172
|
+
_input_decoder = codecs.getincrementaldecoder(
|
|
173
|
+
sys.stdin.encoding or "utf-8"
|
|
174
|
+
)(errors="replace")
|
|
175
|
+
|
|
137
176
|
class _RawContext:
|
|
138
177
|
"""Context manager for raw terminal mode on Unix."""
|
|
139
178
|
def __init__(self):
|
|
@@ -141,7 +180,12 @@ else:
|
|
|
141
180
|
self.old_settings = termios.tcgetattr(self.fd)
|
|
142
181
|
|
|
143
182
|
def __enter__(self):
|
|
183
|
+
global _last_size, _input_decoder
|
|
144
184
|
tty.setcbreak(self.fd)
|
|
185
|
+
_last_size = get_terminal_size()
|
|
186
|
+
_input_decoder = codecs.getincrementaldecoder(
|
|
187
|
+
sys.stdin.encoding or "utf-8"
|
|
188
|
+
)(errors="replace")
|
|
145
189
|
return self
|
|
146
190
|
|
|
147
191
|
def __exit__(self, *args):
|
|
@@ -152,30 +196,38 @@ else:
|
|
|
152
196
|
|
|
153
197
|
ESC = "\x1b"
|
|
154
198
|
|
|
199
|
+
def _read_char(fd: int, timeout: float | None = None) -> str:
|
|
200
|
+
"""Wait for input or a resize; keep pasted UTF-8 input out of TextIO buffers."""
|
|
201
|
+
deadline = time.monotonic() + timeout if timeout is not None else None
|
|
202
|
+
while True:
|
|
203
|
+
if deadline is None and _terminal_resized():
|
|
204
|
+
return KEY_RESIZE
|
|
205
|
+
wait = _RESIZE_POLL_INTERVAL
|
|
206
|
+
if deadline is not None:
|
|
207
|
+
wait = min(wait, max(0, deadline - time.monotonic()))
|
|
208
|
+
if select.select([fd], [], [], wait)[0]:
|
|
209
|
+
data = os.read(fd, 1)
|
|
210
|
+
if not data:
|
|
211
|
+
return ""
|
|
212
|
+
char = _input_decoder.decode(data)
|
|
213
|
+
if char:
|
|
214
|
+
return char
|
|
215
|
+
if deadline is not None and time.monotonic() >= deadline:
|
|
216
|
+
return ""
|
|
217
|
+
|
|
155
218
|
def read_key() -> str:
|
|
156
|
-
"""Read a
|
|
219
|
+
"""Read a key or resize event without waiting for the next keypress."""
|
|
157
220
|
fd = sys.stdin.fileno()
|
|
158
|
-
ch =
|
|
221
|
+
ch = _read_char(fd)
|
|
159
222
|
if ch == ESC:
|
|
160
223
|
seq = ch
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
while True:
|
|
169
|
-
c = sys.stdin.read(1)
|
|
170
|
-
if c:
|
|
171
|
-
seq += c
|
|
172
|
-
if c.isalpha() or c == "~":
|
|
173
|
-
break
|
|
174
|
-
else:
|
|
175
|
-
break
|
|
176
|
-
termios.tcsetattr(fd, termios.TCSANOW, old)
|
|
177
|
-
except Exception:
|
|
178
|
-
pass
|
|
224
|
+
while True:
|
|
225
|
+
char = _read_char(fd, timeout=0.1)
|
|
226
|
+
if not char:
|
|
227
|
+
break
|
|
228
|
+
seq += char
|
|
229
|
+
if char.isalpha() or char == "~":
|
|
230
|
+
break
|
|
179
231
|
return seq
|
|
180
232
|
return ch
|
|
181
233
|
|