heathrow 0.7.6 → 0.7.7

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: edb287ccce2f2ceb0b7582ce124efe53e9e7842662e241e12951d22c9bc202a6
4
- data.tar.gz: 9ede8d4eab17aa9d0fff183b183eec501ebbdd011c8515497772e7d26cf51841
3
+ metadata.gz: d7c8e6773f02f56ba4ac097d508bafaf526e2aed3bb327220614640d5c1bc3bd
4
+ data.tar.gz: 78db591713e7d944436a7c797c7099f337e5050bd4bce8689b38637330665056
5
5
  SHA512:
6
- metadata.gz: 3566cc3e67b80037396991d097b7eedac09f22a0ab6c85e9f01d098c51dfa5be7dfd1cc87d3be476adc866eb8ac05c75be48ca77b170aa2149b5f2fa3a676164
7
- data.tar.gz: 245b04002f624724fc3e1b475656425efac505c10a28763308dcee1b2403c796c005284eb4a7f6be0c7bc272de79af1a7da2687e6a952b2b65914faf3599f8ad
6
+ metadata.gz: c7b866bfaaf9c2f2edd272be1950fd1730d0e40df9500779dbc51fa8f166e9a149472edd7b1099eea0ece4b713b62201e010cd165161c1a8a0f15725400a00ec
7
+ data.tar.gz: b1bde82078786c429268cf54cce8bef68de0ef03368612e90414c9b484e76181fe3aba59e1157daa7808c968128c750e565d273ea4c30044fa19a847314d76fb
@@ -375,9 +375,9 @@ module Heathrow
375
375
  # Handle sender pattern (supports regex via pipe separation)
376
376
  if filters[:sender_pattern]
377
377
  patterns = filters[:sender_pattern].split('|')
378
- conditions = patterns.map { "sender LIKE ?" }.join(' OR ')
378
+ conditions = patterns.map { "(sender LIKE ? OR sender_name LIKE ?)" }.join(' OR ')
379
379
  query += " AND (#{conditions})"
380
- params += patterns.map { |p| "%#{p}%" }
380
+ patterns.each { |p| params += ["%#{p}%", "%#{p}%"] }
381
381
  end
382
382
 
383
383
  # Handle subject pattern
@@ -835,6 +835,8 @@ module Heathrow
835
835
  label_message
836
836
  when 'v'
837
837
  view_attachments
838
+ when 'Z'
839
+ open_in_timely
838
840
  when 'I'
839
841
  ai_assistant
840
842
  when 'V'
@@ -1395,6 +1397,7 @@ module Heathrow
1395
1397
  when 'latest' then 'Latest'
1396
1398
  when 'alphabetical' then 'A-Z'
1397
1399
  when 'sender' then 'Sender'
1400
+ when 'from' then 'From'
1398
1401
  when 'unread' then 'Unread'
1399
1402
  when 'source' then 'Source'
1400
1403
  else @sort_order.capitalize
@@ -2997,6 +3000,83 @@ module Heathrow
2997
3000
  (msg['content'] && msg['content'] =~ /\A\s*<(!DOCTYPE|html|head|body)\b/i)
2998
3001
  end
2999
3002
 
3003
+ def open_in_timely
3004
+ msg = current_message
3005
+ return unless msg
3006
+ msg = ensure_full_message(msg)
3007
+
3008
+ # Try to find a date from calendar data or message timestamp
3009
+ timely_home = File.expand_path('~/.timely')
3010
+ return set_feedback("Timely not configured (~/.timely missing)", 196, 3) unless File.directory?(timely_home)
3011
+
3012
+ # Check for ICS attachment or inline calendar data
3013
+ date_str = nil
3014
+ meta = msg['metadata']
3015
+ meta = JSON.parse(meta) if meta.is_a?(String) rescue nil
3016
+ file = meta['maildir_file'] if meta.is_a?(Hash)
3017
+
3018
+ if file && File.exist?(file)
3019
+ begin
3020
+ require 'mail'
3021
+ mail = Mail.read(file)
3022
+ if mail.multipart?
3023
+ mail.parts.each do |part|
3024
+ ct = (part.content_type || '').downcase
3025
+ if ct.include?('calendar') || ct.include?('ics')
3026
+ ics = part.decoded
3027
+ vevent = ics[/BEGIN:VEVENT(.*?)END:VEVENT/m, 1]
3028
+ if vevent
3029
+ vevent = vevent.gsub(/\r?\n[ \t]/, '')
3030
+ if vevent =~ /^DTSTART;TZID=[^:]*:(\d{8})/i ||
3031
+ vevent =~ /^DTSTART:(\d{8})/i ||
3032
+ vevent =~ /^DTSTART;VALUE=DATE:(\d{8})/i
3033
+ d = $1
3034
+ date_str = "#{d[0,4]}-#{d[4,2]}-#{d[6,2]}"
3035
+ end
3036
+ end
3037
+ break
3038
+ end
3039
+ end
3040
+ end
3041
+ rescue => e
3042
+ # Fall through to timestamp
3043
+ end
3044
+ end
3045
+
3046
+ # Fallback: use message timestamp
3047
+ unless date_str
3048
+ ts = msg['timestamp'].to_i
3049
+ date_str = Time.at(ts).strftime('%Y-%m-%d') if ts > 0
3050
+ end
3051
+
3052
+ return set_feedback("Could not determine date for Timely", 245, 2) unless date_str
3053
+
3054
+ # Write goto file for Timely
3055
+ File.write(File.join(timely_home, 'goto'), date_str)
3056
+
3057
+ # Also copy ICS to incoming if it has calendar data
3058
+ if file && File.exist?(file)
3059
+ begin
3060
+ incoming = File.join(timely_home, 'incoming')
3061
+ FileUtils.mkdir_p(incoming)
3062
+ require 'mail'
3063
+ mail = Mail.read(file)
3064
+ mail.parts.each do |part|
3065
+ ct = (part.content_type || '').downcase
3066
+ if ct.include?('calendar') || ct.include?('ics')
3067
+ ics_file = File.join(incoming, "heathrow_#{msg['id']}.ics")
3068
+ File.write(ics_file, part.decoded) unless File.exist?(ics_file)
3069
+ break
3070
+ end
3071
+ end
3072
+ rescue => e
3073
+ # Non-fatal
3074
+ end
3075
+ end
3076
+
3077
+ set_feedback("Sent to Timely: #{date_str}", 156, 0)
3078
+ end
3079
+
3000
3080
  def open_message_external
3001
3081
  msg = current_message
3002
3082
  return unless msg
@@ -5819,7 +5899,7 @@ module Heathrow
5819
5899
  ['%d %b %H:%M', 'DD Mon HH:MM'],
5820
5900
  ['%b %d %H:%M', 'Mon DD HH:MM']
5821
5901
  ]
5822
- sort_orders = ['latest', 'alphabetical', 'sender', 'conversation', 'unread', 'source']
5902
+ sort_orders = ['latest', 'alphabetical', 'sender', 'from', 'conversation', 'unread', 'source']
5823
5903
  border_labels = ['none', 'right', 'both', 'left']
5824
5904
  # Build view choices: A, N, plus any user-defined views
5825
5905
  view_choices = [['A', 'All'], ['N', 'New/Unread']]
@@ -6249,11 +6329,12 @@ module Heathrow
6249
6329
  end
6250
6330
 
6251
6331
  def cycle_sort_order
6252
- # Cycle through: latest -> alphabetical -> sender -> conversation -> unread -> source -> latest
6332
+ # Cycle through: latest -> alphabetical -> sender -> from -> conversation -> unread -> source -> latest
6253
6333
  @sort_order = case @sort_order
6254
6334
  when 'latest' then 'alphabetical'
6255
6335
  when 'alphabetical' then 'sender'
6256
- when 'sender' then 'conversation'
6336
+ when 'sender' then 'from'
6337
+ when 'from' then 'conversation'
6257
6338
  when 'conversation' then 'unread'
6258
6339
  when 'unread' then 'source'
6259
6340
  else 'latest' # This handles 'source' and any other value
@@ -6391,6 +6472,24 @@ module Heathrow
6391
6472
  end
6392
6473
  end
6393
6474
  end
6475
+ when 'from'
6476
+ # Group by sender, most recently active sender first
6477
+ # Within each sender group, newest message first
6478
+ latest_per_sender = {}
6479
+ @filtered_messages.each do |m|
6480
+ s = display_sender(m).downcase
6481
+ t = ts_cache[m.object_id] || Time.at(0)
6482
+ latest_per_sender[s] = t if !latest_per_sender[s] || t > latest_per_sender[s]
6483
+ end
6484
+ @filtered_messages.sort! do |a, b|
6485
+ sa = display_sender(a).downcase
6486
+ sb = display_sender(b).downcase
6487
+ if sa == sb
6488
+ (ts_cache[b.object_id] || Time.at(0)) <=> (ts_cache[a.object_id] || Time.at(0))
6489
+ else
6490
+ (latest_per_sender[sb] || Time.at(0)) <=> (latest_per_sender[sa] || Time.at(0))
6491
+ end
6492
+ end
6394
6493
  when 'unread'
6395
6494
  # Sort by unread first, then by timestamp
6396
6495
  @filtered_messages.sort! do |a, b|
@@ -1,3 +1,3 @@
1
1
  module Heathrow
2
- VERSION = '0.7.6'
2
+ VERSION = '0.7.7'
3
3
  end
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.6
4
+ version: 0.7.7
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-21 00:00:00.000000000 Z
12
+ date: 2026-03-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rcurses