timely-calendar 1.0.1 → 1.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db1c94b2b016e7ce47aaa79da922a03e65956b992251ab0b263f8d2f36cf965e
4
- data.tar.gz: 5d620757c14c5ffeca1a9e9b1fba5686357cf3ad573780dfa091e2a62f45450a
3
+ metadata.gz: a336a4e6f30f78ab53e5da5005c8a95114d0dc6657d2c5323101615d3cfa4015
4
+ data.tar.gz: fc5783786223c1d421832cb0cf46599192fb0f06386fae3c6a7cfc0050aeedf6
5
5
  SHA512:
6
- metadata.gz: c86cfd413b6f262f4247473b982b554b395e4d554c185260ebae8c764f26a0399e94503a0ea632406a6f1fca851c1a9fc7eef30c811fb5a91a93fff97e58b1c0
7
- data.tar.gz: c8d4fb8be5e6aa6b80a2a991e93caaa8bc828cf8085621ccd17c70ad3c5c520b8f203dc4c51b9a6197e8d2b48750d480365de8f25cb9ba8f7d7c9b47c3b8c9ad
6
+ metadata.gz: 3a62d684e33d3f597748b142e18e0d0c5dc579d4bdb11c3d143efe4fec76129d982d5b47a4944b89e7466a72be92a680a8d10feac54539b1220a23048cc34cf4
7
+ data.tar.gz: f2613ecb9a86d8febc5cd8de611c42b371b54440ee6a6c5b7c2c4bffabaf29854be4795d7ba9aa601f982fee6179962956096e567e0a27ba200d67254e3be1cf
data/img/timely-kb.svg ADDED
@@ -0,0 +1,282 @@
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">Timely 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="sp">close popup</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="127" class="kl">1</text>
28
+
29
+ <rect x="138" y="97" width="62" height="55" class="key"/>
30
+ <text x="169" y="127" class="kl">2</text>
31
+
32
+ <rect x="204" y="97" width="62" height="55" class="key"/>
33
+ <text x="235" y="127" class="kl">3</text>
34
+
35
+ <rect x="270" y="97" width="62" height="55" class="key"/>
36
+ <text x="301" y="127" class="kl">4</text>
37
+
38
+ <rect x="336" y="97" width="62" height="55" class="key"/>
39
+ <text x="367" y="127" class="kl">5</text>
40
+
41
+ <rect x="402" y="97" width="62" height="55" class="key"/>
42
+ <text x="433" y="127" class="kl">6</text>
43
+
44
+ <rect x="468" y="97" width="62" height="55" class="key"/>
45
+ <text x="499" y="127" class="kl">7</text>
46
+
47
+ <rect x="534" y="97" width="62" height="55" class="key"/>
48
+ <text x="565" y="127" class="kl">8</text>
49
+
50
+ <rect x="600" y="97" width="62" height="55" class="key"/>
51
+ <text x="631" y="127" class="kl">9</text>
52
+
53
+ <rect x="666" y="97" width="62" height="55" class="key"/>
54
+ <text x="697" y="127" class="kl">0</text>
55
+
56
+ <rect x="732" y="97" width="62" height="55" class="key"/>
57
+ <text x="763" y="127" class="kl">- _</text>
58
+
59
+ <rect x="798" y="97" width="62" height="55" class="key"/>
60
+ <text x="829" y="127" class="kl">= +</text>
61
+
62
+ <rect x="864" y="97" width="76" height="55" class="kw"/>
63
+ <text x="902" y="130" class="nv">Bksp</text>
64
+
65
+ <!-- Row 2: QWERTY start x=10+80=90 offset, kw=62 -->
66
+ <rect x="10" y="160" width="76" height="55" class="kw"/>
67
+ <text x="48" y="193" class="nv">Tab</text>
68
+
69
+ <rect x="90" y="160" width="62" height="55" class="key"/>
70
+ <text x="121" y="176" class="kl">Q</text>
71
+ <text x="121" y="207" class="lo">q quit</text>
72
+
73
+ <rect x="156" y="160" width="62" height="55" class="key"/>
74
+ <text x="187" y="176" class="kl">W</text>
75
+ <text x="187" y="194" class="up">W Week-1</text>
76
+ <text x="187" y="207" class="lo">w Week+1</text>
77
+
78
+ <rect x="222" y="160" width="62" height="55" class="key"/>
79
+ <text x="253" y="176" class="kl">E</text>
80
+ <text x="253" y="194" class="up">E Prev evt</text>
81
+ <text x="253" y="207" class="lo">e Next evt</text>
82
+
83
+ <rect x="288" y="160" width="62" height="55" class="key"/>
84
+ <text x="319" y="176" class="kl">R</text>
85
+ <text x="319" y="207" class="lo">r reply</text>
86
+
87
+ <rect x="354" y="160" width="62" height="55" class="key"/>
88
+ <text x="385" y="176" class="kl">T</text>
89
+ <text x="385" y="207" class="lo">t today</text>
90
+
91
+ <rect x="420" y="160" width="62" height="55" class="key"/>
92
+ <text x="451" y="176" class="kl">Y</text>
93
+ <text x="451" y="194" class="up">Y Year-1</text>
94
+ <text x="451" y="207" class="lo">y Year+1</text>
95
+
96
+ <rect x="486" y="160" width="62" height="55" class="key"/>
97
+ <text x="517" y="193" class="kl">U</text>
98
+
99
+ <rect x="552" y="160" width="62" height="55" class="key"/>
100
+ <text x="583" y="176" class="kl">I</text>
101
+ <text x="583" y="207" class="lo">i import ICS</text>
102
+
103
+ <rect x="618" y="160" width="62" height="55" class="key"/>
104
+ <text x="649" y="176" class="kl">O</text>
105
+ <text x="649" y="194" class="up">O Outlook</text>
106
+
107
+ <rect x="684" y="160" width="62" height="55" class="key"/>
108
+ <text x="715" y="176" class="kl">P</text>
109
+ <text x="715" y="194" class="up">P Prefs</text>
110
+
111
+ <rect x="750" y="160" width="62" height="55" class="key"/>
112
+ <text x="781" y="193" class="kl">Å</text>
113
+
114
+ <rect x="816" y="160" width="62" height="55" class="key"/>
115
+ <text x="847" y="193" class="kl">^</text>
116
+
117
+ <rect x="882" y="160" width="58" height="55" class="key"/>
118
+ <text x="911" y="193" class="kl">\</text>
119
+
120
+ <!-- Row 3: ASDF start x=10+90=100 offset -->
121
+ <rect x="10" y="223" width="86" height="55" class="kw"/>
122
+ <text x="53" y="255" class="nv">Ctrl</text>
123
+
124
+ <rect x="100" y="223" width="62" height="55" class="key"/>
125
+ <text x="131" y="239" class="kl">A</text>
126
+ <text x="131" y="270" class="lo">a accept</text>
127
+
128
+ <rect x="166" y="223" width="62" height="55" class="key"/>
129
+ <text x="197" y="239" class="kl">S</text>
130
+ <text x="197" y="257" class="up">S Sync</text>
131
+
132
+ <rect x="232" y="223" width="62" height="55" class="key"/>
133
+ <text x="263" y="239" class="kl">D</text>
134
+ <text x="263" y="257" class="up">D Day-1</text>
135
+ <text x="263" y="270" class="lo">d Day+1</text>
136
+
137
+ <rect x="298" y="223" width="62" height="55" class="key"/>
138
+ <text x="329" y="255" class="kl">F</text>
139
+
140
+ <rect x="364" y="223" width="62" height="55" class="key"/>
141
+ <text x="395" y="239" class="kl">G</text>
142
+ <text x="395" y="257" class="up">G Google</text>
143
+ <text x="395" y="270" class="lo">g go to date</text>
144
+
145
+ <rect x="430" y="223" width="62" height="55" class="key"/>
146
+ <text x="461" y="239" class="kl">H</text>
147
+ <text x="461" y="270" class="nv">h = Day-1</text>
148
+
149
+ <rect x="496" y="223" width="62" height="55" class="key"/>
150
+ <text x="527" y="239" class="kl">J</text>
151
+ <text x="527" y="270" class="lo">j next e/d</text>
152
+
153
+ <rect x="562" y="223" width="62" height="55" class="key"/>
154
+ <text x="593" y="239" class="kl">K</text>
155
+ <text x="593" y="270" class="lo">k prev e/d</text>
156
+
157
+ <rect x="628" y="223" width="62" height="55" class="key"/>
158
+ <text x="659" y="239" class="kl">L</text>
159
+ <text x="659" y="270" class="nv">l = Day+1</text>
160
+
161
+ <rect x="694" y="223" width="62" height="55" class="key"/>
162
+ <text x="725" y="255" class="kl">Ø</text>
163
+
164
+ <rect x="760" y="223" width="62" height="55" class="key"/>
165
+ <text x="791" y="255" class="kl">Æ</text>
166
+
167
+ <rect x="826" y="223" width="114" height="55" class="kw"/>
168
+ <text x="883" y="248" class="nv">Enter</text>
169
+ <text x="883" y="268" class="sp">edit event</text>
170
+
171
+ <!-- Row 4: ZXCV start x=10+100=110 offset -->
172
+ <rect x="10" y="286" width="96" height="55" class="kw"/>
173
+ <text x="58" y="318" class="nv">Shift</text>
174
+
175
+ <rect x="110" y="286" width="62" height="55" class="key"/>
176
+ <text x="141" y="315" class="kl">Z</text>
177
+
178
+ <rect x="176" y="286" width="62" height="55" class="key"/>
179
+ <text x="207" y="302" class="kl">X</text>
180
+ <text x="207" y="333" class="sp">x del event</text>
181
+
182
+ <rect x="242" y="286" width="62" height="55" class="key"/>
183
+ <text x="273" y="302" class="kl">C</text>
184
+ <text x="273" y="320" class="up">C Calendars</text>
185
+
186
+ <rect x="308" y="286" width="62" height="55" class="key"/>
187
+ <text x="339" y="302" class="kl">V</text>
188
+ <text x="339" y="333" class="lo">v view popup</text>
189
+
190
+ <rect x="374" y="286" width="62" height="55" class="key"/>
191
+ <text x="405" y="315" class="kl">B</text>
192
+
193
+ <rect x="440" y="286" width="62" height="55" class="key"/>
194
+ <text x="471" y="302" class="kl">N</text>
195
+ <text x="471" y="333" class="lo">n new event</text>
196
+
197
+ <rect x="506" y="286" width="62" height="55" class="key"/>
198
+ <text x="537" y="302" class="kl">M</text>
199
+ <text x="537" y="320" class="up">M Month-1</text>
200
+ <text x="537" y="333" class="lo">m Month+1</text>
201
+
202
+ <rect x="572" y="286" width="62" height="55" class="key"/>
203
+ <text x="603" y="315" class="kl">, &lt;</text>
204
+
205
+ <rect x="638" y="286" width="62" height="55" class="key"/>
206
+ <text x="669" y="315" class="kl">. &gt;</text>
207
+
208
+ <rect x="704" y="286" width="62" height="55" class="key"/>
209
+ <text x="735" y="302" class="kl">/ ?</text>
210
+ <text x="735" y="333" class="sp">? help</text>
211
+
212
+ <rect x="770" y="286" width="170" height="55" class="kw"/>
213
+ <text x="855" y="318" class="nv">Shift</text>
214
+
215
+ <!-- Row 5: Bottom -->
216
+ <rect x="10" y="349" width="70" height="50" class="kw"/>
217
+ <text x="45" y="378" class="nv">Ctrl</text>
218
+
219
+ <rect x="84" y="349" width="58" height="50" class="kw"/>
220
+ <text x="113" y="378" class="nv">Alt</text>
221
+
222
+ <rect x="146" y="349" width="330" height="50" class="key"/>
223
+ <text x="311" y="378" class="nv">Space</text>
224
+
225
+ <rect x="480" y="349" width="62" height="50" class="key"/>
226
+ <text x="511" y="378" class="kl"># @</text>
227
+
228
+ <!-- Nav cluster -->
229
+ <rect x="990" y="97" width="64" height="55" class="key"/>
230
+ <text x="1022" y="120" class="nv">Home</text>
231
+ <text x="1022" y="143" class="nv">top slot</text>
232
+
233
+ <rect x="1058" y="97" width="64" height="55" class="key"/>
234
+ <text x="1090" y="120" class="nv">End</text>
235
+ <text x="1090" y="143" class="nv">btm slot</text>
236
+
237
+ <rect x="990" y="160" width="64" height="55" class="key"/>
238
+ <text x="1022" y="183" class="nv">PgUp</text>
239
+ <text x="1022" y="206" class="nv">page slots</text>
240
+
241
+ <rect x="1058" y="160" width="64" height="55" class="key"/>
242
+ <text x="1090" y="183" class="nv">PgDn</text>
243
+ <text x="1090" y="206" class="nv">page slots</text>
244
+
245
+ <rect x="990" y="223" width="64" height="55" class="key"/>
246
+ <text x="1022" y="248" class="nv">Del</text>
247
+ <text x="1022" y="268" class="sp">del event</text>
248
+
249
+ <rect x="1022" y="286" width="60" height="55" class="key"/>
250
+ <text x="1052" y="310" class="nv">UP</text>
251
+ <text x="1052" y="333" class="nv">prev slot</text>
252
+
253
+ <rect x="958" y="349" width="60" height="50" class="key"/>
254
+ <text x="988" y="370" class="nv">LEFT</text>
255
+ <text x="988" y="390" class="nv">prev day</text>
256
+
257
+ <rect x="1022" y="349" width="60" height="50" class="key"/>
258
+ <text x="1052" y="370" class="nv">DOWN</text>
259
+ <text x="1052" y="390" class="nv">next slot</text>
260
+
261
+ <rect x="1086" y="349" width="60" height="50" class="key"/>
262
+ <text x="1116" y="370" class="nv">RIGHT</text>
263
+ <text x="1116" y="390" class="nv">next day</text>
264
+
265
+ <!-- Right panel: Ctrl combos -->
266
+ <rect x="1180" y="34" width="300" height="200" rx="6" fill="#1a2a1a" stroke="#4a4" stroke-width="1"/>
267
+ <text x="1330" y="56" text-anchor="middle" font-size="13" font-weight="bold" fill="#66FF66">Ctrl Combos</text>
268
+ <text x="1194" y="80" class="ct">C-R Refresh all calendars</text>
269
+ <text x="1194" y="105" class="ct">C-L Redraw screen</text>
270
+ <text x="1194" y="130" class="ct">C-Y Copy event to clipboard</text>
271
+
272
+ <!-- Legend -->
273
+ <rect x="20" y="470" width="14" height="14" fill="#3a2222" rx="2" stroke="#FF6666" stroke-width="0.5"/>
274
+ <text x="40" y="482" font-size="10" fill="#FF6666">lowercase</text>
275
+ <rect x="120" y="470" width="14" height="14" fill="#22223a" rx="2" stroke="#6699FF" stroke-width="0.5"/>
276
+ <text x="140" y="482" font-size="10" fill="#6699FF">UPPERCASE</text>
277
+ <rect x="240" y="470" width="14" height="14" fill="#223a22" rx="2" stroke="#66FF66" stroke-width="0.5"/>
278
+ <text x="260" y="482" font-size="10" fill="#66FF66">Ctrl+key</text>
279
+ <rect x="350" y="470" width="14" height="14" fill="#2a223a" rx="2" stroke="#CC99FF" stroke-width="0.5"/>
280
+ <text x="370" y="482" font-size="10" fill="#CC99FF">special</text>
281
+ <text x="460" y="482" font-size="10" fill="#888">gray = navigation</text>
282
+ </svg>
@@ -123,6 +123,9 @@ module Timely
123
123
  when 't'
124
124
  @selected_date = Date.today
125
125
  @selected_event_index = 0
126
+ now = Time.now
127
+ @selected_slot = now.hour * 2 + (now.min >= 30 ? 1 : 0)
128
+ @slot_offset = [@selected_slot - 5, 0].max
126
129
  date_changed
127
130
  when 'g'
128
131
  go_to_date
@@ -150,6 +153,17 @@ module Timely
150
153
  manual_sync
151
154
  when 'C'
152
155
  show_calendars
156
+ when 'C-R'
157
+ @_cached_planets_date = nil # Force planet recalculation
158
+ @weather_forecast = nil # Force weather refresh
159
+ @_weather_fetched_at = nil
160
+ @db.execute("DELETE FROM weather_cache") rescue nil # Clear DB cache too
161
+ load_events_for_range
162
+ render_all
163
+ when 'C-L'
164
+ Rcurses.clear_screen
165
+ create_panes
166
+ render_all
153
167
  when 'P'
154
168
  show_preferences
155
169
  when '?'
@@ -267,6 +281,19 @@ module Timely
267
281
  def date_changed
268
282
  @selected_event_index = 0
269
283
  load_events_for_range
284
+ # If slot is in all-day area but no event there, jump to first timed event or current time
285
+ if @selected_slot && @selected_slot < 0 && event_at_selected_slot.nil?
286
+ events = events_on_selected_day
287
+ first_timed = events.find { |e| e['all_day'].to_i != 1 }
288
+ if first_timed
289
+ t = Time.at(first_timed['start_time'].to_i)
290
+ @selected_slot = t.hour * 2 + (t.min >= 30 ? 1 : 0)
291
+ else
292
+ now = Time.now
293
+ @selected_slot = now.hour * 2 + (now.min >= 30 ? 1 : 0)
294
+ end
295
+ @slot_offset = [@selected_slot - 5, 0].max
296
+ end
270
297
  render_all
271
298
  end
272
299
 
@@ -313,8 +340,13 @@ module Timely
313
340
  desc = desc.gsub(/<[^>]+>/, ' ').gsub(/&nbsp;/i, ' ').gsub(/&amp;/i, '&').gsub(/&lt;/i, '<').gsub(/&gt;/i, '>')
314
341
  end
315
342
  end
316
- # Remove common garbage from Google/Outlook descriptions
317
- desc.gsub(/BC\d+-Color:\s*-?\d+\s*/, '').gsub(/-::~:~::~:~.*$/m, '').gsub(/\s+/, ' ').strip
343
+ # Remove common garbage from Google/Outlook/Teams descriptions
344
+ desc.gsub(/BC\d+-Color:\s*-?\d+\s*/, '') # Google color metadata
345
+ .gsub(/-::~:~::~:~.*$/m, '') # Google Meet block
346
+ .gsub(/_{3,}/, '') # Long underscore lines (Teams HR)
347
+ .gsub(/-{5,}/, '') # Long dash lines
348
+ .gsub(/\n{3,}/, "\n\n") # Collapse excessive blank lines
349
+ .strip
318
350
  end
319
351
 
320
352
  # Find the event at the currently selected time slot
@@ -323,11 +355,18 @@ module Timely
323
355
  events = events_on_selected_day
324
356
 
325
357
  if @selected_slot < 0
326
- # Negative slot: -n = top (first allday), -1 = bottom (last allday)
358
+ # Negative slot uses max_allday across the visible week (same as render_mid_pane)
359
+ week_start = @selected_date - (@selected_date.cwday - 1)
360
+ max_allday = 0
361
+ 7.times do |i|
362
+ day = week_start + i
363
+ n = (@events_by_date[day] || []).count { |e| e['all_day'].to_i == 1 }
364
+ max_allday = n if n > max_allday
365
+ end
327
366
  allday = events.select { |e| e['all_day'].to_i == 1 }
328
- n = allday.size
329
- idx = n - @selected_slot.abs # -n -> 0 (first), -1 -> n-1 (last)
330
- return allday[idx]
367
+ idx = max_allday - @selected_slot.abs # -max -> 0 (first), -1 -> max-1 (last)
368
+ return allday[idx] if idx >= 0 && idx < allday.size
369
+ return nil
331
370
  end
332
371
 
333
372
  hour = @selected_slot / 2
@@ -363,6 +402,7 @@ module Timely
363
402
  # If there are more events on the current day after the selected one, go to next
364
403
  if events.length > 0 && @selected_event_index < events.length - 1
365
404
  @selected_event_index += 1
405
+ move_slot_to_event(events[@selected_event_index], events)
366
406
  render_mid_pane
367
407
  render_bottom_pane
368
408
  return
@@ -375,7 +415,10 @@ module Timely
375
415
  if day_events && !day_events.empty?
376
416
  @selected_date = check_date
377
417
  @selected_event_index = 0
378
- date_changed
418
+ load_events_for_range
419
+ events = events_on_selected_day
420
+ move_slot_to_event(events.first, events) if events.any?
421
+ render_all
379
422
  return
380
423
  end
381
424
  end
@@ -388,6 +431,7 @@ module Timely
388
431
  # If there are more events on the current day before the selected one, go to prev
389
432
  if events.length > 0 && @selected_event_index > 0
390
433
  @selected_event_index -= 1
434
+ move_slot_to_event(events[@selected_event_index], events)
391
435
  render_mid_pane
392
436
  render_bottom_pane
393
437
  return
@@ -399,8 +443,11 @@ module Timely
399
443
  day_events = @db.get_events_for_date(check_date)
400
444
  if day_events && !day_events.empty?
401
445
  @selected_date = check_date
402
- @selected_event_index = day_events.length - 1
403
- date_changed
446
+ load_events_for_range
447
+ events = events_on_selected_day
448
+ @selected_event_index = [events.length - 1, 0].max
449
+ move_slot_to_event(events.last, events) if events.any?
450
+ render_all
404
451
  return
405
452
  end
406
453
  end
@@ -408,6 +455,27 @@ module Timely
408
455
  show_feedback("No earlier events found within the past year", 245)
409
456
  end
410
457
 
458
+ def move_slot_to_event(evt, day_events)
459
+ return unless evt
460
+ if evt['all_day'].to_i == 1
461
+ # Compute max_allday across the visible week (same as render_mid_pane)
462
+ week_start = @selected_date - (@selected_date.cwday - 1)
463
+ max_allday = 0
464
+ 7.times do |i|
465
+ day = week_start + i
466
+ n = (@events_by_date[day] || []).count { |e| e['all_day'].to_i == 1 }
467
+ max_allday = n if n > max_allday
468
+ end
469
+ allday = day_events.select { |e| e['all_day'].to_i == 1 }
470
+ idx = allday.index { |e| e['id'] == evt['id'] } || 0
471
+ @selected_slot = -(max_allday - idx)
472
+ else
473
+ t = Time.at(evt['start_time'].to_i)
474
+ @selected_slot = t.hour * 2 + (t.min >= 30 ? 1 : 0)
475
+ @slot_offset = [@selected_slot - 5, 0].max
476
+ end
477
+ end
478
+
411
479
  # --- Data loading ---
412
480
 
413
481
  def load_events_for_range
@@ -839,7 +907,16 @@ module Timely
839
907
  astro.each { |evt| lines << " #{evt}".fg(180) } if astro.any?
840
908
 
841
909
  lines << ""
842
- lines << " No events scheduled".fg(240)
910
+ if events.any?
911
+ allday = events.count { |e| e['all_day'].to_i == 1 }
912
+ timed = events.size - allday
913
+ parts = []
914
+ parts << "#{timed} timed" if timed > 0
915
+ parts << "#{allday} all-day" if allday > 0
916
+ lines << " #{parts.join(', ')} event#{'s' if events.size > 1} today".fg(240)
917
+ else
918
+ lines << " No events scheduled".fg(240)
919
+ end
843
920
  end
844
921
 
845
922
  # Pad to fill pane
@@ -854,6 +931,7 @@ module Timely
854
931
  # --- Actions ---
855
932
 
856
933
  def go_to_date
934
+ blank_bottom("")
857
935
  input = bottom_ask("Go to: ", "")
858
936
  return if input.nil? || input.strip.empty?
859
937
 
@@ -1112,8 +1190,8 @@ module Timely
1112
1190
  end
1113
1191
 
1114
1192
  text = lines.join("\n")
1115
- IO.popen('xclip -selection clipboard', 'w') { |io| io.write(text) } rescue
1116
- IO.popen('xsel --clipboard --input', 'w') { |io| io.write(text) } rescue nil
1193
+ IO.popen('xclip -selection clipboard', 'w') { |io| io.write(text) } rescue nil
1194
+ IO.popen('xclip -selection primary', 'w') { |io| io.write(text) } rescue nil
1117
1195
  show_feedback("Event copied to clipboard", 156)
1118
1196
  end
1119
1197
 
@@ -1192,7 +1270,7 @@ module Timely
1192
1270
  end
1193
1271
 
1194
1272
  lines << ""
1195
- lines << " " + "UP/DOWN:scroll ESC/q:close".fg(245)
1273
+ lines << " " + "UP/DOWN:scroll C-Y:copy ESC/q:close".fg(245)
1196
1274
 
1197
1275
  popup.text = lines.join("\n")
1198
1276
  popup.refresh
@@ -1210,6 +1288,13 @@ module Timely
1210
1288
  popup.pagedown
1211
1289
  when 'PgUP'
1212
1290
  popup.pageup
1291
+ when 'C-Y'
1292
+ clean = lines.map { |l| l.respond_to?(:pure) ? l.pure : l.gsub(/\e\[[0-9;]*m/, '') }.join("\n")
1293
+ IO.popen('xclip -selection clipboard', 'w') { |io| io.write(clean) } rescue nil
1294
+ IO.popen('xclip -selection primary', 'w') { |io| io.write(clean) } rescue nil
1295
+ lines[-1] = " " + "Copied to clipboard".fg(156)
1296
+ popup.text = lines.join("\n")
1297
+ popup.refresh
1213
1298
  end
1214
1299
  end
1215
1300
 
@@ -14,9 +14,9 @@ module Timely
14
14
  def create_panes
15
15
  @panes = {}
16
16
 
17
- # Layout: info(1) + top(months, fixed 9) + mid(week, flexible) + bottom(details) + status(1)
18
- # Top pane: 1 blank row + 8 month rows = 9
19
- top_h = 9
17
+ # Layout: info(1) + top(months, fixed 10) + mid(week, flexible) + bottom(details) + status(1)
18
+ # Top pane: 1 blank row + 8 month rows + 1 padding row = 10
19
+ top_h = 10
20
20
  bottom_h = (@h * 0.2).to_i
21
21
  bottom_h = [bottom_h, 5].max
22
22
  mid_h = @h - 2 - top_h - bottom_h # 2 = info + status
@@ -1,3 +1,3 @@
1
1
  module Timely
2
- VERSION = '1.0.1'
2
+ VERSION = '1.0.2'
3
3
  end
@@ -31,6 +31,7 @@ module Timely
31
31
  uri = URI("https://api.met.no/weatherapi/locationforecast/2.0/complete?lat=#{lat}&lon=#{lon}")
32
32
  req = Net::HTTP::Get.new(uri)
33
33
  req['User-Agent'] = 'timely-calendar/0.1 g@isene.com'
34
+ req['Accept-Encoding'] = 'identity'
34
35
 
35
36
  res = Net::HTTP.start(uri.hostname, uri.port,
36
37
  use_ssl: true,
@@ -91,7 +92,7 @@ module Timely
91
92
  if db
92
93
  db.execute(
93
94
  "INSERT OR REPLACE INTO weather_cache (date, hour, data, fetched_at) VALUES (?, ?, ?, ?)",
94
- 'forecast', '00', JSON.generate(forecast), Time.now.to_i
95
+ ['forecast', '00', JSON.generate(forecast), Time.now.to_i]
95
96
  )
96
97
  end
97
98
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timely-calendar
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
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-24 00:00:00.000000000 Z
12
+ date: 2026-03-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rcurses
@@ -53,6 +53,7 @@ files:
53
53
  - CLAUDE.md
54
54
  - README.md
55
55
  - bin/timely
56
+ - img/timely-kb.svg
56
57
  - img/timely.svg
57
58
  - lib/timely.rb
58
59
  - lib/timely/astronomy.rb