heathrow 0.7.7 → 0.7.9
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.
- checksums.yaml +4 -4
- data/README.md +24 -2
- data/img/heathrow-kb.svg +321 -0
- data/lib/heathrow/ui/application.rb +145 -16
- data/lib/heathrow/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8023c45996b5eba44dfbb1e9718fe11d149ec7f14f35dd5569fe5172b53a018e
|
|
4
|
+
data.tar.gz: d1f94f8964b220fb7f831ca60298a3ca68f1dc4bdc4b9949282bb7add4c8d65e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5b438204942e8ad23d166b36be5acc3dc9a0fa1c2456f9946f258c02d1597176bf0dafde75081e21c469839487938bc8181bf3151cff4fca30d7bd617ae53d18
|
|
7
|
+
data.tar.gz: 1944ede7efac79dac8ace2640dee9c2260754898f8dc24ed1b7793898ed2b3eb57b47cef9fc7102ab8b1da09b20be898aa23312872704301b5aa3fb96a501caf
|
data/README.md
CHANGED
|
@@ -121,8 +121,9 @@ Press `?` for full help. Here are the essentials:
|
|
|
121
121
|
| `<` | Purge deleted |
|
|
122
122
|
| `x` | Open in browser |
|
|
123
123
|
| `v` | View attachments |
|
|
124
|
-
| `Y` | Copy right pane to clipboard |
|
|
124
|
+
| `Y`/`Ctrl-y` | Copy right pane to clipboard |
|
|
125
125
|
| `y` | Copy message ID to clipboard |
|
|
126
|
+
| `@` | Address book (a=add sender, e=edit) |
|
|
126
127
|
|
|
127
128
|
### Compose Prompt
|
|
128
129
|
| Key | Action |
|
|
@@ -139,11 +140,24 @@ Press `?` for full help. Here are the essentials:
|
|
|
139
140
|
| `w` | Cycle pane width |
|
|
140
141
|
| `Ctrl-b` | Cycle border style |
|
|
141
142
|
| `D` | Cycle date format |
|
|
142
|
-
| `o` | Cycle sort order |
|
|
143
|
+
| `o` | Cycle sort order (Latest, A-Z, Sender, From, Conversation, Unread, Source) |
|
|
144
|
+
| `Z` | Open calendar invite in [Timely](https://github.com/isene/timely) |
|
|
143
145
|
| `P` | Preferences popup |
|
|
144
146
|
| `?` | Help (press again for extended) |
|
|
145
147
|
| `q` | Quit |
|
|
146
148
|
|
|
149
|
+
### Sort Orders
|
|
150
|
+
|
|
151
|
+
| Order | Description |
|
|
152
|
+
|-------|-------------|
|
|
153
|
+
| Latest | Newest messages first (default) |
|
|
154
|
+
| A-Z | Alphabetical by subject |
|
|
155
|
+
| Sender | Alphabetical by sender |
|
|
156
|
+
| From | Group by sender, most recently active sender first |
|
|
157
|
+
| Conversation | Group by thread/conversation |
|
|
158
|
+
| Unread | Unread messages first |
|
|
159
|
+
| Source | Group by source type |
|
|
160
|
+
|
|
147
161
|
## Configuration
|
|
148
162
|
|
|
149
163
|
All settings live in `~/.heathrow/heathrowrc` (Ruby syntax):
|
|
@@ -200,6 +214,14 @@ Each source is a self-contained plugin. Sources crash independently without affe
|
|
|
200
214
|
- Built on [rcurses](https://github.com/isene/rcurses) TUI library
|
|
201
215
|
- Inspired by [RTFM](https://github.com/isene/RTFM) file manager
|
|
202
216
|
|
|
217
|
+
## Timely Integration
|
|
218
|
+
|
|
219
|
+
Heathrow works with [Timely](https://github.com/isene/timely), a terminal calendar app:
|
|
220
|
+
|
|
221
|
+
- **Z key**: On a calendar invite email, press `Z` to open it in Timely (extracts the event date from ICS data and copies the ICS for import)
|
|
222
|
+
- **Wezterm mailto**: Click any email address in wezterm to compose in Heathrow
|
|
223
|
+
- **From sort**: The "From" sort order groups messages by sender with the most recently active person at the top, perfect for family or team views
|
|
224
|
+
|
|
203
225
|
## License
|
|
204
226
|
|
|
205
227
|
[Unlicense](https://unlicense.org/) - released into the public domain.
|
data/img/heathrow-kb.svg
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1500 560" width="1500" height="560" font-family="monospace, Courier New, Courier">
|
|
2
|
+
<defs>
|
|
3
|
+
<style>
|
|
4
|
+
.key { fill: #333; stroke: #555; stroke-width: 1; rx: 5; ry: 5; }
|
|
5
|
+
.kw { fill: #2a2a2a; stroke: #555; stroke-width: 1; rx: 5; ry: 5; }
|
|
6
|
+
.kl { font-size: 12px; fill: #bbb; font-weight: bold; text-anchor: middle; }
|
|
7
|
+
.lo { font-size: 8.5px; fill: #FF8888; text-anchor: middle; }
|
|
8
|
+
.up { font-size: 8.5px; fill: #88BBFF; text-anchor: middle; }
|
|
9
|
+
.ct { font-size: 10px; fill: #88FF88; }
|
|
10
|
+
.sp { font-size: 8.5px; fill: #DDAAFF; text-anchor: middle; }
|
|
11
|
+
.nv { font-size: 8.5px; fill: #bbb; text-anchor: middle; }
|
|
12
|
+
</style>
|
|
13
|
+
</defs>
|
|
14
|
+
<rect width="1500" height="560" fill="#1a1a1a" rx="8"/>
|
|
15
|
+
<text x="470" y="22" text-anchor="middle" font-size="15" font-weight="bold" fill="#fff">Heathrow Keyboard Reference</text>
|
|
16
|
+
|
|
17
|
+
<!-- Row 0: Esc -->
|
|
18
|
+
<rect x="10" y="34" width="58" height="55" class="key"/>
|
|
19
|
+
<text x="39" y="54" class="kl">Esc</text>
|
|
20
|
+
<text x="39" y="80" class="lo">quit</text>
|
|
21
|
+
|
|
22
|
+
<!-- Row 1: Number row kw=62 gap=4 -->
|
|
23
|
+
<rect x="10" y="97" width="58" height="55" class="key"/>
|
|
24
|
+
<text x="39" y="127" class="kl">` ~</text>
|
|
25
|
+
|
|
26
|
+
<rect x="72" y="97" width="62" height="55" class="key"/>
|
|
27
|
+
<text x="103" y="117" class="kl">1</text>
|
|
28
|
+
<text x="103" y="143" class="lo">view 1</text>
|
|
29
|
+
|
|
30
|
+
<rect x="138" y="97" width="62" height="55" class="key"/>
|
|
31
|
+
<text x="169" y="117" class="kl">2</text>
|
|
32
|
+
<text x="169" y="143" class="lo">view 2</text>
|
|
33
|
+
|
|
34
|
+
<rect x="204" y="97" width="62" height="55" class="key"/>
|
|
35
|
+
<text x="235" y="117" class="kl">3</text>
|
|
36
|
+
<text x="235" y="143" class="lo">view 3</text>
|
|
37
|
+
|
|
38
|
+
<rect x="270" y="97" width="62" height="55" class="key"/>
|
|
39
|
+
<text x="301" y="117" class="kl">4</text>
|
|
40
|
+
<text x="301" y="143" class="lo">view 4</text>
|
|
41
|
+
|
|
42
|
+
<rect x="336" y="97" width="62" height="55" class="key"/>
|
|
43
|
+
<text x="367" y="117" class="kl">5</text>
|
|
44
|
+
<text x="367" y="143" class="lo">view 5</text>
|
|
45
|
+
|
|
46
|
+
<rect x="402" y="97" width="62" height="55" class="key"/>
|
|
47
|
+
<text x="433" y="117" class="kl">6</text>
|
|
48
|
+
<text x="433" y="143" class="lo">view 6</text>
|
|
49
|
+
|
|
50
|
+
<rect x="468" y="97" width="62" height="55" class="key"/>
|
|
51
|
+
<text x="499" y="117" class="kl">7</text>
|
|
52
|
+
<text x="499" y="143" class="lo">view 7</text>
|
|
53
|
+
|
|
54
|
+
<rect x="534" y="97" width="62" height="55" class="key"/>
|
|
55
|
+
<text x="565" y="117" class="kl">8</text>
|
|
56
|
+
<text x="565" y="143" class="lo">view 8</text>
|
|
57
|
+
|
|
58
|
+
<rect x="600" y="97" width="62" height="55" class="key"/>
|
|
59
|
+
<text x="631" y="117" class="kl">9</text>
|
|
60
|
+
<text x="631" y="143" class="lo">view 9</text>
|
|
61
|
+
|
|
62
|
+
<rect x="666" y="97" width="62" height="55" class="key"/>
|
|
63
|
+
<text x="697" y="117" class="kl">0</text>
|
|
64
|
+
<text x="697" y="143" class="lo">view 0</text>
|
|
65
|
+
|
|
66
|
+
<rect x="732" y="97" width="62" height="55" class="key"/>
|
|
67
|
+
<text x="763" y="115" class="kl">- _</text>
|
|
68
|
+
|
|
69
|
+
<rect x="798" y="97" width="62" height="55" class="key"/>
|
|
70
|
+
<text x="829" y="115" class="kl">= +</text>
|
|
71
|
+
<text x="829" y="140" class="sp">+ fav folder</text>
|
|
72
|
+
|
|
73
|
+
<rect x="864" y="97" width="76" height="55" class="kw"/>
|
|
74
|
+
<text x="902" y="130" class="nv">Bksp</text>
|
|
75
|
+
|
|
76
|
+
<!-- Row 2: QWERTY start x=10+80=90 offset, kw=62 -->
|
|
77
|
+
<rect x="10" y="160" width="76" height="55" class="kw"/>
|
|
78
|
+
<text x="48" y="183" class="nv">Tab</text>
|
|
79
|
+
<text x="48" y="206" class="nv">scroll R</text>
|
|
80
|
+
|
|
81
|
+
<rect x="90" y="160" width="62" height="55" class="key"/>
|
|
82
|
+
<text x="121" y="176" class="kl">Q</text>
|
|
83
|
+
<text x="121" y="194" class="up">Force quit</text>
|
|
84
|
+
<text x="121" y="207" class="lo">quit</text>
|
|
85
|
+
|
|
86
|
+
<rect x="156" y="160" width="62" height="55" class="key"/>
|
|
87
|
+
<text x="187" y="176" class="kl">W</text>
|
|
88
|
+
<text x="187" y="207" class="lo">width</text>
|
|
89
|
+
|
|
90
|
+
<rect x="222" y="160" width="62" height="55" class="key"/>
|
|
91
|
+
<text x="253" y="176" class="kl">E</text>
|
|
92
|
+
<text x="253" y="194" class="up">Edit as new</text>
|
|
93
|
+
<text x="253" y="207" class="lo">reply+edit</text>
|
|
94
|
+
|
|
95
|
+
<rect x="288" y="160" width="62" height="55" class="key"/>
|
|
96
|
+
<text x="319" y="176" class="kl">R</text>
|
|
97
|
+
<text x="319" y="194" class="up">Read/Unrd</text>
|
|
98
|
+
<text x="319" y="207" class="lo">reply</text>
|
|
99
|
+
|
|
100
|
+
<rect x="354" y="160" width="62" height="55" class="key"/>
|
|
101
|
+
<text x="385" y="176" class="kl">T</text>
|
|
102
|
+
<text x="385" y="194" class="up">Tag all</text>
|
|
103
|
+
<text x="385" y="207" class="lo">tag</text>
|
|
104
|
+
|
|
105
|
+
<rect x="420" y="160" width="62" height="55" class="key"/>
|
|
106
|
+
<text x="451" y="176" class="kl">Y</text>
|
|
107
|
+
<text x="451" y="194" class="up">Copy pane</text>
|
|
108
|
+
<text x="451" y="207" class="lo">copy ID</text>
|
|
109
|
+
|
|
110
|
+
<rect x="486" y="160" width="62" height="55" class="key"/>
|
|
111
|
+
<text x="517" y="176" class="kl">U</text>
|
|
112
|
+
|
|
113
|
+
<rect x="552" y="160" width="62" height="55" class="key"/>
|
|
114
|
+
<text x="583" y="176" class="kl">I</text>
|
|
115
|
+
<text x="583" y="194" class="up">AI assist</text>
|
|
116
|
+
<text x="583" y="207" class="lo">invert</text>
|
|
117
|
+
|
|
118
|
+
<rect x="618" y="160" width="62" height="55" class="key"/>
|
|
119
|
+
<text x="649" y="176" class="kl">O</text>
|
|
120
|
+
<text x="649" y="207" class="lo">sort</text>
|
|
121
|
+
|
|
122
|
+
<rect x="684" y="160" width="62" height="55" class="key"/>
|
|
123
|
+
<text x="715" y="176" class="kl">P</text>
|
|
124
|
+
<text x="715" y="194" class="up">Prefs</text>
|
|
125
|
+
<text x="715" y="207" class="lo">prev unrd</text>
|
|
126
|
+
|
|
127
|
+
<rect x="750" y="160" width="62" height="55" class="key"/>
|
|
128
|
+
<text x="781" y="176" class="kl">[ {</text>
|
|
129
|
+
|
|
130
|
+
<rect x="816" y="160" width="62" height="55" class="key"/>
|
|
131
|
+
<text x="847" y="176" class="kl">] }</text>
|
|
132
|
+
|
|
133
|
+
<rect x="882" y="160" width="58" height="55" class="key"/>
|
|
134
|
+
<text x="911" y="190" class="kl">\</text>
|
|
135
|
+
|
|
136
|
+
<!-- Row 3: ASDF start x=10+90=100 offset -->
|
|
137
|
+
<rect x="10" y="223" width="86" height="55" class="kw"/>
|
|
138
|
+
<text x="53" y="255" class="nv">Ctrl</text>
|
|
139
|
+
|
|
140
|
+
<rect x="100" y="223" width="62" height="55" class="key"/>
|
|
141
|
+
<text x="131" y="239" class="kl">A</text>
|
|
142
|
+
<text x="131" y="262" class="up">All msgs</text>
|
|
143
|
+
|
|
144
|
+
<rect x="166" y="223" width="62" height="55" class="key"/>
|
|
145
|
+
<text x="197" y="239" class="kl">S</text>
|
|
146
|
+
<text x="197" y="257" class="up">Sources</text>
|
|
147
|
+
<text x="197" y="270" class="lo">save</text>
|
|
148
|
+
|
|
149
|
+
<rect x="232" y="223" width="62" height="55" class="key"/>
|
|
150
|
+
<text x="263" y="239" class="kl">D</text>
|
|
151
|
+
<text x="263" y="257" class="up">Date fmt</text>
|
|
152
|
+
<text x="263" y="270" class="lo">del mark</text>
|
|
153
|
+
|
|
154
|
+
<rect x="298" y="223" width="62" height="55" class="key"/>
|
|
155
|
+
<text x="329" y="239" class="kl">F</text>
|
|
156
|
+
<text x="329" y="257" class="up">Favorites</text>
|
|
157
|
+
<text x="329" y="270" class="lo">forward</text>
|
|
158
|
+
|
|
159
|
+
<rect x="364" y="223" width="62" height="55" class="key"/>
|
|
160
|
+
<text x="395" y="239" class="kl">G</text>
|
|
161
|
+
<text x="395" y="257" class="up">View mode</text>
|
|
162
|
+
<text x="395" y="270" class="lo">reply all</text>
|
|
163
|
+
|
|
164
|
+
<rect x="430" y="223" width="62" height="55" class="key"/>
|
|
165
|
+
<text x="461" y="239" class="kl">H</text>
|
|
166
|
+
<text x="461" y="270" class="nv">h = back</text>
|
|
167
|
+
|
|
168
|
+
<rect x="496" y="223" width="62" height="55" class="key"/>
|
|
169
|
+
<text x="527" y="239" class="kl">J</text>
|
|
170
|
+
<text x="527" y="257" class="up">Jump date</text>
|
|
171
|
+
<text x="527" y="270" class="nv">j = down</text>
|
|
172
|
+
|
|
173
|
+
<rect x="562" y="223" width="62" height="55" class="key"/>
|
|
174
|
+
<text x="593" y="239" class="kl">K</text>
|
|
175
|
+
<text x="593" y="257" class="up">Kill view</text>
|
|
176
|
+
<text x="593" y="270" class="nv">k = up</text>
|
|
177
|
+
|
|
178
|
+
<rect x="628" y="223" width="62" height="55" class="key"/>
|
|
179
|
+
<text x="659" y="239" class="kl">L</text>
|
|
180
|
+
<text x="659" y="257" class="up">Load more</text>
|
|
181
|
+
<text x="659" y="270" class="nv">l = open</text>
|
|
182
|
+
|
|
183
|
+
<rect x="694" y="223" width="62" height="55" class="key"/>
|
|
184
|
+
<text x="725" y="239" class="kl">; :</text>
|
|
185
|
+
|
|
186
|
+
<rect x="760" y="223" width="62" height="55" class="key"/>
|
|
187
|
+
<text x="791" y="255" class="kl">'</text>
|
|
188
|
+
|
|
189
|
+
<rect x="826" y="223" width="114" height="55" class="kw"/>
|
|
190
|
+
<text x="883" y="248" class="nv">Enter</text>
|
|
191
|
+
<text x="883" y="268" class="nv">open msg</text>
|
|
192
|
+
|
|
193
|
+
<!-- Row 4: ZXCV start x=10+100=110 offset -->
|
|
194
|
+
<rect x="10" y="286" width="96" height="55" class="kw"/>
|
|
195
|
+
<text x="58" y="318" class="nv">Shift</text>
|
|
196
|
+
|
|
197
|
+
<rect x="110" y="286" width="62" height="55" class="key"/>
|
|
198
|
+
<text x="141" y="302" class="kl">Z</text>
|
|
199
|
+
<text x="141" y="325" class="up">Timely</text>
|
|
200
|
+
|
|
201
|
+
<rect x="176" y="286" width="62" height="55" class="key"/>
|
|
202
|
+
<text x="207" y="302" class="kl">X</text>
|
|
203
|
+
<text x="207" y="325" class="lo">browser</text>
|
|
204
|
+
|
|
205
|
+
<rect x="242" y="286" width="62" height="55" class="key"/>
|
|
206
|
+
<text x="273" y="302" class="kl">C</text>
|
|
207
|
+
|
|
208
|
+
<rect x="308" y="286" width="62" height="55" class="key"/>
|
|
209
|
+
<text x="339" y="302" class="kl">V</text>
|
|
210
|
+
<text x="339" y="320" class="up">Inline img</text>
|
|
211
|
+
<text x="339" y="333" class="lo">attach</text>
|
|
212
|
+
|
|
213
|
+
<rect x="374" y="286" width="62" height="55" class="key"/>
|
|
214
|
+
<text x="405" y="302" class="kl">B</text>
|
|
215
|
+
<text x="405" y="325" class="up">Browse</text>
|
|
216
|
+
|
|
217
|
+
<rect x="440" y="286" width="62" height="55" class="key"/>
|
|
218
|
+
<text x="471" y="302" class="kl">N</text>
|
|
219
|
+
<text x="471" y="320" class="up">New msgs</text>
|
|
220
|
+
<text x="471" y="333" class="lo">next unrd</text>
|
|
221
|
+
|
|
222
|
+
<rect x="506" y="286" width="62" height="55" class="key"/>
|
|
223
|
+
<text x="537" y="302" class="kl">M</text>
|
|
224
|
+
<text x="537" y="320" class="up">Mark read</text>
|
|
225
|
+
<text x="537" y="333" class="lo">compose</text>
|
|
226
|
+
|
|
227
|
+
<rect x="572" y="286" width="62" height="55" class="key"/>
|
|
228
|
+
<text x="603" y="302" class="kl">, <</text>
|
|
229
|
+
<text x="603" y="325" class="sp">< purge</text>
|
|
230
|
+
|
|
231
|
+
<rect x="638" y="286" width="62" height="55" class="key"/>
|
|
232
|
+
<text x="669" y="315" class="kl">. ></text>
|
|
233
|
+
|
|
234
|
+
<rect x="704" y="286" width="62" height="55" class="key"/>
|
|
235
|
+
<text x="735" y="302" class="kl">/ ?</text>
|
|
236
|
+
<text x="735" y="320" class="sp">? help</text>
|
|
237
|
+
<text x="735" y="333" class="sp">/ search</text>
|
|
238
|
+
|
|
239
|
+
<rect x="770" y="286" width="170" height="55" class="kw"/>
|
|
240
|
+
<text x="855" y="318" class="nv">Shift</text>
|
|
241
|
+
|
|
242
|
+
<!-- Row 5: Bottom -->
|
|
243
|
+
<rect x="10" y="349" width="70" height="50" class="kw"/>
|
|
244
|
+
<text x="45" y="378" class="nv">Ctrl</text>
|
|
245
|
+
|
|
246
|
+
<rect x="84" y="349" width="58" height="50" class="kw"/>
|
|
247
|
+
<text x="113" y="378" class="nv">Alt</text>
|
|
248
|
+
|
|
249
|
+
<rect x="146" y="349" width="330" height="50" class="key"/>
|
|
250
|
+
<text x="311" y="372" class="nv">Space</text>
|
|
251
|
+
<text x="311" y="390" class="sp">collapse</text>
|
|
252
|
+
|
|
253
|
+
<rect x="480" y="349" width="62" height="50" class="key"/>
|
|
254
|
+
<text x="511" y="366" class="kl"># @</text>
|
|
255
|
+
<text x="511" y="382" class="sp">* star</text>
|
|
256
|
+
<text x="511" y="394" class="sp">@ addr book</text>
|
|
257
|
+
|
|
258
|
+
<!-- Nav cluster -->
|
|
259
|
+
<rect x="990" y="97" width="64" height="55" class="key"/>
|
|
260
|
+
<text x="1022" y="120" class="nv">Home</text>
|
|
261
|
+
<text x="1022" y="143" class="nv">first</text>
|
|
262
|
+
|
|
263
|
+
<rect x="1058" y="97" width="64" height="55" class="key"/>
|
|
264
|
+
<text x="1090" y="120" class="nv">End</text>
|
|
265
|
+
<text x="1090" y="143" class="nv">last</text>
|
|
266
|
+
|
|
267
|
+
<rect x="990" y="160" width="64" height="55" class="key"/>
|
|
268
|
+
<text x="1022" y="183" class="nv">PgUp</text>
|
|
269
|
+
<text x="1022" y="206" class="nv">page up</text>
|
|
270
|
+
|
|
271
|
+
<rect x="1058" y="160" width="64" height="55" class="key"/>
|
|
272
|
+
<text x="1090" y="183" class="nv">PgDn</text>
|
|
273
|
+
<text x="1090" y="206" class="nv">page dn</text>
|
|
274
|
+
|
|
275
|
+
<rect x="1022" y="286" width="60" height="55" class="key"/>
|
|
276
|
+
<text x="1052" y="310" class="nv">UP</text>
|
|
277
|
+
<text x="1052" y="333" class="nv">k</text>
|
|
278
|
+
|
|
279
|
+
<rect x="958" y="349" width="60" height="50" class="key"/>
|
|
280
|
+
<text x="988" y="370" class="nv">LEFT</text>
|
|
281
|
+
<text x="988" y="390" class="nv">h/back</text>
|
|
282
|
+
|
|
283
|
+
<rect x="1022" y="349" width="60" height="50" class="key"/>
|
|
284
|
+
<text x="1052" y="370" class="nv">DOWN</text>
|
|
285
|
+
<text x="1052" y="390" class="nv">j</text>
|
|
286
|
+
|
|
287
|
+
<rect x="1086" y="349" width="60" height="50" class="key"/>
|
|
288
|
+
<text x="1116" y="370" class="nv">RIGHT</text>
|
|
289
|
+
<text x="1116" y="390" class="nv">l/open</text>
|
|
290
|
+
|
|
291
|
+
<!-- Right panel: Ctrl combos -->
|
|
292
|
+
<rect x="1180" y="34" width="300" height="370" rx="6" fill="#1a2a1a" stroke="#4a6a4a" stroke-width="1"/>
|
|
293
|
+
<text x="1330" y="56" text-anchor="middle" font-size="13" font-weight="bold" fill="#66FF66">Ctrl Combos</text>
|
|
294
|
+
<text x="1194" y="80" class="ct">C-B Toggle border</text>
|
|
295
|
+
<text x="1194" y="100" class="ct">C-F Edit filter</text>
|
|
296
|
+
<text x="1194" y="120" class="ct">C-L Redraw screen</text>
|
|
297
|
+
<text x="1194" y="140" class="ct">C-R Refresh sources</text>
|
|
298
|
+
<text x="1194" y="160" class="ct">C-T Tag by regex</text>
|
|
299
|
+
<text x="1194" y="180" class="ct">C-Y Copy pane</text>
|
|
300
|
+
|
|
301
|
+
<text x="1330" y="220" text-anchor="middle" font-size="13" font-weight="bold" fill="#888">Navigation</text>
|
|
302
|
+
<text x="1194" y="244" class="ct" style="fill:#888">Enter Open message</text>
|
|
303
|
+
<text x="1194" y="264" class="ct" style="fill:#888">Arrows Navigate</text>
|
|
304
|
+
<text x="1194" y="284" class="ct" style="fill:#888">PgUp/Dn Page scroll</text>
|
|
305
|
+
<text x="1194" y="304" class="ct" style="fill:#888">Home/End First/Last</text>
|
|
306
|
+
<text x="1194" y="324" class="ct" style="fill:#888">Tab Scroll right</text>
|
|
307
|
+
<text x="1194" y="344" class="ct" style="fill:#888">S-Arrows Scroll pane</text>
|
|
308
|
+
<text x="1194" y="364" class="ct" style="fill:#888">Space Collapse thread</text>
|
|
309
|
+
<text x="1194" y="384" class="ct" style="fill:#888">0-9 Switch views</text>
|
|
310
|
+
|
|
311
|
+
<!-- Legend -->
|
|
312
|
+
<rect x="20" y="470" width="14" height="14" fill="#3a2020" rx="2" stroke="#553333" stroke-width="0.5"/>
|
|
313
|
+
<text x="40" y="482" font-size="10" fill="#FF6666">lowercase</text>
|
|
314
|
+
<rect x="120" y="470" width="14" height="14" fill="#20203a" rx="2" stroke="#333355" stroke-width="0.5"/>
|
|
315
|
+
<text x="140" y="482" font-size="10" fill="#6699FF">UPPERCASE</text>
|
|
316
|
+
<rect x="240" y="470" width="14" height="14" fill="#203a20" rx="2" stroke="#335533" stroke-width="0.5"/>
|
|
317
|
+
<text x="260" y="482" font-size="10" fill="#66FF66">Ctrl+key</text>
|
|
318
|
+
<rect x="350" y="470" width="14" height="14" fill="#2a2030" rx="2" stroke="#443355" stroke-width="0.5"/>
|
|
319
|
+
<text x="370" y="482" font-size="10" fill="#CC99FF">special</text>
|
|
320
|
+
<text x="460" y="482" font-size="10" fill="#888">gray = navigation</text>
|
|
321
|
+
</svg>
|
|
@@ -736,6 +736,8 @@ module Heathrow
|
|
|
736
736
|
jump_to_date
|
|
737
737
|
when 'x'
|
|
738
738
|
open_message_external
|
|
739
|
+
when 'X'
|
|
740
|
+
open_in_brrowser
|
|
739
741
|
when 'HOME'
|
|
740
742
|
go_first
|
|
741
743
|
when 'END'
|
|
@@ -748,7 +750,7 @@ module Heathrow
|
|
|
748
750
|
load_more_messages
|
|
749
751
|
when 'w'
|
|
750
752
|
change_width
|
|
751
|
-
when 'Y'
|
|
753
|
+
when 'Y', 'C-Y'
|
|
752
754
|
copy_right_pane_to_clipboard
|
|
753
755
|
when 'c'
|
|
754
756
|
set_view_top_bg
|
|
@@ -841,6 +843,8 @@ module Heathrow
|
|
|
841
843
|
ai_assistant
|
|
842
844
|
when 'V'
|
|
843
845
|
toggle_inline_image
|
|
846
|
+
when '@'
|
|
847
|
+
address_book_menu
|
|
844
848
|
when 'ESC', "\e"
|
|
845
849
|
if @showing_image
|
|
846
850
|
clear_inline_image
|
|
@@ -1943,6 +1947,9 @@ module Heathrow
|
|
|
1943
1947
|
return
|
|
1944
1948
|
end
|
|
1945
1949
|
|
|
1950
|
+
# Auto-mark as read when content is rendered in the right pane
|
|
1951
|
+
mark_current_message_as_read
|
|
1952
|
+
|
|
1946
1953
|
msg = current_msg
|
|
1947
1954
|
|
|
1948
1955
|
# Lazily load full content if this was a light query result
|
|
@@ -2043,10 +2050,10 @@ module Heathrow
|
|
|
2043
2050
|
end
|
|
2044
2051
|
end
|
|
2045
2052
|
|
|
2046
|
-
header << ("─" * 60).fg(238)
|
|
2047
|
-
|
|
2048
|
-
# Format message body — prefer HTML rendered via w3m
|
|
2049
2053
|
pane_w = @panes[:right].w rescue 80
|
|
2054
|
+
header << ("─" * (pane_w - 2)).fg(238)
|
|
2055
|
+
|
|
2056
|
+
# Format message body — prefer HTML rendered via w3m
|
|
2050
2057
|
render_width = [pane_w - 2, 40].max
|
|
2051
2058
|
content = nil
|
|
2052
2059
|
if msg['html_content'] && !msg['html_content'].to_s.strip.empty?
|
|
@@ -2251,8 +2258,14 @@ module Heathrow
|
|
|
2251
2258
|
end
|
|
2252
2259
|
|
|
2253
2260
|
def render_bottom_bar
|
|
2254
|
-
# Check if there's an active feedback message
|
|
2255
|
-
if @
|
|
2261
|
+
# Check if there's an active feedback message (timed or sticky)
|
|
2262
|
+
if @feedback_sticky && @feedback_message
|
|
2263
|
+
if @panes[:bottom]
|
|
2264
|
+
@panes[:bottom].text = " #{@feedback_message}".fg(@feedback_color || 156)
|
|
2265
|
+
@panes[:bottom].refresh
|
|
2266
|
+
end
|
|
2267
|
+
return
|
|
2268
|
+
elsif @feedback_expires_at && Time.now < @feedback_expires_at
|
|
2256
2269
|
if @panes[:bottom]
|
|
2257
2270
|
@panes[:bottom].text = " #{@feedback_message}".fg(@feedback_color || 156)
|
|
2258
2271
|
@panes[:bottom].refresh
|
|
@@ -3171,6 +3184,47 @@ module Heathrow
|
|
|
3171
3184
|
end
|
|
3172
3185
|
end
|
|
3173
3186
|
|
|
3187
|
+
def open_in_brrowser
|
|
3188
|
+
unless system("which brrowser > /dev/null 2>&1")
|
|
3189
|
+
set_feedback("brrowser not installed. See https://github.com/isene/brrowser", 196, 4)
|
|
3190
|
+
return
|
|
3191
|
+
end
|
|
3192
|
+
|
|
3193
|
+
msg = current_message
|
|
3194
|
+
return unless msg
|
|
3195
|
+
return if header_message?(msg)
|
|
3196
|
+
msg = ensure_full_message(msg)
|
|
3197
|
+
|
|
3198
|
+
# Build URL from message (same logic as open_message_external)
|
|
3199
|
+
url = nil
|
|
3200
|
+
if message_has_html?(msg)
|
|
3201
|
+
html = msg['html_content']
|
|
3202
|
+
html = msg['content'] if !html || html.to_s.strip.empty?
|
|
3203
|
+
tmpfile = "/tmp/heathrow-view-#{msg['id']}.html"
|
|
3204
|
+
File.write(tmpfile, html)
|
|
3205
|
+
url = "file://#{tmpfile}"
|
|
3206
|
+
else
|
|
3207
|
+
meta = msg['metadata']
|
|
3208
|
+
if meta
|
|
3209
|
+
parsed = meta.is_a?(Hash) ? meta : (JSON.parse(meta) rescue {})
|
|
3210
|
+
url = parsed['link'] || parsed['url']
|
|
3211
|
+
end
|
|
3212
|
+
url ||= msg['url'] || msg['link'] || msg['permalink']
|
|
3213
|
+
url ||= msg['external_id'] if msg['external_id']&.start_with?('http')
|
|
3214
|
+
end
|
|
3215
|
+
|
|
3216
|
+
unless url
|
|
3217
|
+
set_feedback("No URL or HTML content to open", 226, 3)
|
|
3218
|
+
return
|
|
3219
|
+
end
|
|
3220
|
+
|
|
3221
|
+
Rcurses.clear_screen
|
|
3222
|
+
system("brrowser '#{url}'")
|
|
3223
|
+
setup_display
|
|
3224
|
+
create_panes
|
|
3225
|
+
render_all
|
|
3226
|
+
end
|
|
3227
|
+
|
|
3174
3228
|
def view_attachments
|
|
3175
3229
|
msg = current_message
|
|
3176
3230
|
return unless msg
|
|
@@ -3376,7 +3430,8 @@ module Heathrow
|
|
|
3376
3430
|
success = @db.mark_as_read(msg['id'])
|
|
3377
3431
|
if success
|
|
3378
3432
|
msg['is_read'] = 1
|
|
3379
|
-
|
|
3433
|
+
sync_maildir_flag(msg, 'S', true)
|
|
3434
|
+
|
|
3380
3435
|
# Re-sort if sorting by unread
|
|
3381
3436
|
if @sort_order == 'unread'
|
|
3382
3437
|
sort_messages
|
|
@@ -4295,13 +4350,19 @@ module Heathrow
|
|
|
4295
4350
|
return if msgs.empty?
|
|
4296
4351
|
|
|
4297
4352
|
count = 0
|
|
4353
|
+
failed = 0
|
|
4354
|
+
filed_ids = Set.new
|
|
4298
4355
|
msgs.each do |msg|
|
|
4299
|
-
|
|
4300
|
-
|
|
4356
|
+
begin
|
|
4357
|
+
file_single_message(msg, dest)
|
|
4358
|
+
filed_ids << msg['id'] if msg['id']
|
|
4359
|
+
count += 1
|
|
4360
|
+
rescue => e
|
|
4361
|
+
failed += 1
|
|
4362
|
+
end
|
|
4301
4363
|
end
|
|
4302
4364
|
|
|
4303
4365
|
# Remove filed messages from current view (by id, works in both flat and threaded mode)
|
|
4304
|
-
filed_ids = msgs.map { |m| m['id'] }.compact.to_set
|
|
4305
4366
|
@filtered_messages.reject! { |m| m['id'] && filed_ids.include?(m['id']) }
|
|
4306
4367
|
if @show_threaded
|
|
4307
4368
|
@display_messages&.reject! { |m| m['id'] && filed_ids.include?(m['id']) }
|
|
@@ -4312,7 +4373,9 @@ module Heathrow
|
|
|
4312
4373
|
@index = [@index, (@filtered_messages.size - 1)].min
|
|
4313
4374
|
@index = 0 if @index < 0 || @filtered_messages.empty?
|
|
4314
4375
|
|
|
4315
|
-
|
|
4376
|
+
msg_text = "Moved #{count} message#{count > 1 ? 's' : ''} to #{dest}"
|
|
4377
|
+
msg_text += " (#{failed} failed)" if failed > 0
|
|
4378
|
+
set_feedback(msg_text, failed > 0 ? 208 : 156, 2)
|
|
4316
4379
|
render_all
|
|
4317
4380
|
end
|
|
4318
4381
|
|
|
@@ -4757,6 +4820,7 @@ module Heathrow
|
|
|
4757
4820
|
ai_reply_with_draft(msg, response.strip)
|
|
4758
4821
|
when 'y'
|
|
4759
4822
|
IO.popen('xclip -selection clipboard', 'w') { |io| io.write(response.strip) } rescue nil
|
|
4823
|
+
IO.popen('xclip -selection primary', 'w') { |io| io.write(response.strip) } rescue nil
|
|
4760
4824
|
set_feedback("Draft copied to clipboard", 156, 2)
|
|
4761
4825
|
end
|
|
4762
4826
|
@panes[:right].content_update = true
|
|
@@ -4852,6 +4916,7 @@ module Heathrow
|
|
|
4852
4916
|
chr = getchr
|
|
4853
4917
|
if chr == 'y'
|
|
4854
4918
|
IO.popen('xclip -selection clipboard', 'w') { |io| io.write(response.strip) } rescue nil
|
|
4919
|
+
IO.popen('xclip -selection primary', 'w') { |io| io.write(response.strip) } rescue nil
|
|
4855
4920
|
set_feedback("Corrected text copied to clipboard", 156, 2)
|
|
4856
4921
|
end
|
|
4857
4922
|
@panes[:right].content_update = true
|
|
@@ -6898,8 +6963,9 @@ Required: URL, optional CSS selector
|
|
|
6898
6963
|
#{"D".fg(10)} = Cycle date/time format
|
|
6899
6964
|
#{"o".fg(10)} = Cycle sort order (newest/oldest/unread first)
|
|
6900
6965
|
#{"i".fg(10)} = Invert sort order (toggle reverse)
|
|
6901
|
-
#{"Y".fg(10)}
|
|
6966
|
+
#{"Y/C-y".fg(10)} = Copy right pane content to clipboard
|
|
6902
6967
|
#{"P".fg(10)} = Settings popup (theme, format, etc.)
|
|
6968
|
+
#{"@".fg(10)} = Address book (a=add sender, e=edit file)
|
|
6903
6969
|
#{custom_bindings_help}
|
|
6904
6970
|
Press #{"?".fg(10)} again for extended help • Any other key to continue
|
|
6905
6971
|
HELP
|
|
@@ -7121,7 +7187,8 @@ Required: URL, optional CSS selector
|
|
|
7121
7187
|
msg = current_message
|
|
7122
7188
|
return unless msg && msg['id']
|
|
7123
7189
|
id_str = "heathrow:#{msg['id']}"
|
|
7124
|
-
IO.popen('xclip -selection clipboard', 'w') { |io| io.write(id_str) }
|
|
7190
|
+
IO.popen('xclip -selection clipboard', 'w') { |io| io.write(id_str) } rescue nil
|
|
7191
|
+
IO.popen('xclip -selection primary', 'w') { |io| io.write(id_str) } rescue nil
|
|
7125
7192
|
set_feedback("Message ID #{msg['id']} copied", 156, 2)
|
|
7126
7193
|
rescue => e
|
|
7127
7194
|
set_feedback("Copy failed: #{e.message}", 196, 2)
|
|
@@ -7132,7 +7199,8 @@ Required: URL, optional CSS selector
|
|
|
7132
7199
|
if text && !text.strip.empty?
|
|
7133
7200
|
# Strip ANSI codes for clean clipboard content
|
|
7134
7201
|
clean = text.gsub(/\e\[[0-9;]*m/, '')
|
|
7135
|
-
IO.popen('xclip -selection clipboard', 'w') { |io| io.write(clean) }
|
|
7202
|
+
IO.popen('xclip -selection clipboard', 'w') { |io| io.write(clean) } rescue nil
|
|
7203
|
+
IO.popen('xclip -selection primary', 'w') { |io| io.write(clean) } rescue nil
|
|
7136
7204
|
set_feedback("Copied to clipboard", 156, 2)
|
|
7137
7205
|
else
|
|
7138
7206
|
set_feedback("Nothing to copy", 196, 2)
|
|
@@ -7141,6 +7209,69 @@ Required: URL, optional CSS selector
|
|
|
7141
7209
|
set_feedback("Copy failed: #{e.message}", 196, 2)
|
|
7142
7210
|
end
|
|
7143
7211
|
|
|
7212
|
+
# Address book: add sender or edit file
|
|
7213
|
+
def address_book_menu
|
|
7214
|
+
@panes[:bottom].text = " @: a=Add sender to address book, e=Edit address book".fg(245)
|
|
7215
|
+
@panes[:bottom].refresh
|
|
7216
|
+
chr = getchr
|
|
7217
|
+
case chr
|
|
7218
|
+
when 'a'
|
|
7219
|
+
add_sender_to_address_book
|
|
7220
|
+
when 'e'
|
|
7221
|
+
edit_address_book
|
|
7222
|
+
else
|
|
7223
|
+
set_feedback("Cancelled", 245, 1)
|
|
7224
|
+
end
|
|
7225
|
+
end
|
|
7226
|
+
|
|
7227
|
+
def add_sender_to_address_book
|
|
7228
|
+
msg = current_message
|
|
7229
|
+
return set_feedback("No message selected", 196, 2) unless msg
|
|
7230
|
+
|
|
7231
|
+
sender = msg['sender'].to_s.strip
|
|
7232
|
+
sender_name = msg['sender_name'].to_s.strip
|
|
7233
|
+
return set_feedback("No sender", 196, 2) if sender.empty?
|
|
7234
|
+
|
|
7235
|
+
# Build full address: "Name <email>" or just "email"
|
|
7236
|
+
full = if sender_name.empty? || sender_name == sender
|
|
7237
|
+
sender
|
|
7238
|
+
else
|
|
7239
|
+
"#{sender_name} <#{sender}>"
|
|
7240
|
+
end
|
|
7241
|
+
|
|
7242
|
+
# Suggest alias from name or email local part
|
|
7243
|
+
suggested = if !sender_name.empty? && sender_name != sender
|
|
7244
|
+
sender_name.split.first.downcase
|
|
7245
|
+
else
|
|
7246
|
+
sender.split('@').first.downcase
|
|
7247
|
+
end
|
|
7248
|
+
|
|
7249
|
+
@panes[:bottom].text = " Add: #{full}".fg(156)
|
|
7250
|
+
@panes[:bottom].refresh
|
|
7251
|
+
alias_name = @panes[:bottom].ask(" Alias: ", suggested)
|
|
7252
|
+
return set_feedback("Cancelled", 245, 1) if alias_name.nil? || alias_name.strip.empty?
|
|
7253
|
+
alias_name = alias_name.strip.gsub(/\s+/, '.')
|
|
7254
|
+
|
|
7255
|
+
ab_path = File.expand_path('~/setup/addressbook')
|
|
7256
|
+
File.open(ab_path, 'a') { |f| f.puts "alias #{alias_name} #{full}" }
|
|
7257
|
+
@address_book = AddressBook.new(ab_path)
|
|
7258
|
+
set_feedback("Added: #{alias_name} → #{full}", 156, 3)
|
|
7259
|
+
rescue => e
|
|
7260
|
+
set_feedback("Failed: #{e.message}", 196, 2)
|
|
7261
|
+
end
|
|
7262
|
+
|
|
7263
|
+
def edit_address_book
|
|
7264
|
+
ab_path = File.expand_path('~/setup/addressbook')
|
|
7265
|
+
editor = ENV['EDITOR'] || 'vim'
|
|
7266
|
+
Rcurses.clear_screen
|
|
7267
|
+
system("#{editor} #{ab_path}")
|
|
7268
|
+
@address_book = AddressBook.new(ab_path)
|
|
7269
|
+
setup_display
|
|
7270
|
+
create_panes
|
|
7271
|
+
render_all
|
|
7272
|
+
set_feedback("Address book reloaded", 156, 2)
|
|
7273
|
+
end
|
|
7274
|
+
|
|
7144
7275
|
def set_view_top_bg
|
|
7145
7276
|
view = @views[@current_view]
|
|
7146
7277
|
unless view
|
|
@@ -8171,7 +8302,6 @@ Required: URL, optional CSS selector
|
|
|
8171
8302
|
|
|
8172
8303
|
# Format the event for display
|
|
8173
8304
|
lines = []
|
|
8174
|
-
lines << ("─" * 50).fg(238)
|
|
8175
8305
|
lines << "Calendar Event".b.fg(226)
|
|
8176
8306
|
lines << ""
|
|
8177
8307
|
lines << "WHAT: #{event[:summary]}".fg(156) if event[:summary]
|
|
@@ -8192,7 +8322,6 @@ Required: URL, optional CSS selector
|
|
|
8192
8322
|
end
|
|
8193
8323
|
# Skip description (email body already shows it, and ICS descriptions
|
|
8194
8324
|
# often contain raw URLs that can overflow the pane)
|
|
8195
|
-
lines << ("─" * 50).fg(238)
|
|
8196
8325
|
lines.join("\n")
|
|
8197
8326
|
rescue => e
|
|
8198
8327
|
nil # Don't crash on calendar parse errors
|
data/lib/heathrow/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: heathrow
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Geir Isene
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-03-
|
|
12
|
+
date: 2026-03-27 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rcurses
|
|
@@ -66,6 +66,7 @@ files:
|
|
|
66
66
|
- docs/WHATSAPP_TELEGRAM_SETUP.md
|
|
67
67
|
- heathrow.gemspec
|
|
68
68
|
- heathrowd.service
|
|
69
|
+
- img/heathrow-kb.svg
|
|
69
70
|
- img/heathrow.svg
|
|
70
71
|
- img/rss_threaded.png
|
|
71
72
|
- img/sources.png
|