heathrow 0.7.5 → 0.7.6
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/lib/heathrow/ui/application.rb +128 -172
- data/lib/heathrow/ui/threaded_view.rb +27 -39
- data/lib/heathrow/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: edb287ccce2f2ceb0b7582ce124efe53e9e7842662e241e12951d22c9bc202a6
|
|
4
|
+
data.tar.gz: 9ede8d4eab17aa9d0fff183b183eec501ebbdd011c8515497772e7d26cf51841
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3566cc3e67b80037396991d097b7eedac09f22a0ab6c85e9f01d098c51dfa5be7dfd1cc87d3be476adc866eb8ac05c75be48ca77b170aa2149b5f2fa3a676164
|
|
7
|
+
data.tar.gz: 245b04002f624724fc3e1b475656425efac505c10a28763308dcee1b2403c796c005284eb4a7f6be0c7bc272de79af1a7da2687e6a952b2b65914faf3599f8ad
|
|
@@ -206,14 +206,24 @@ module Heathrow
|
|
|
206
206
|
selected ? base.merge(selected) : base
|
|
207
207
|
end
|
|
208
208
|
|
|
209
|
+
def header_message?(msg)
|
|
210
|
+
msg['is_header'] || msg['is_channel_header'] || msg['is_thread_header'] || msg['is_dm_header']
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def invalidate_counts
|
|
214
|
+
@cached_unread = nil
|
|
215
|
+
@cached_starred = nil
|
|
216
|
+
@cached_total = nil
|
|
217
|
+
end
|
|
218
|
+
|
|
209
219
|
# Get the current message at @index (threaded or flat view)
|
|
210
220
|
def current_message
|
|
211
|
-
|
|
221
|
+
current_message_for_navigation
|
|
212
222
|
end
|
|
213
223
|
|
|
214
224
|
# Get the current message count (threaded or flat view)
|
|
215
225
|
def message_count
|
|
216
|
-
|
|
226
|
+
filtered_messages_size
|
|
217
227
|
end
|
|
218
228
|
|
|
219
229
|
# Mark the previous message as read when moving away from it
|
|
@@ -223,7 +233,7 @@ module Heathrow
|
|
|
223
233
|
# Remember current message so it gets marked read when we leave it
|
|
224
234
|
msg = current_message
|
|
225
235
|
return unless msg
|
|
226
|
-
return if msg
|
|
236
|
+
return if header_message?(msg)
|
|
227
237
|
return unless msg['id'] && !msg['id'].to_s.start_with?('header_')
|
|
228
238
|
|
|
229
239
|
if msg['is_read'].to_i == 0
|
|
@@ -246,6 +256,7 @@ module Heathrow
|
|
|
246
256
|
|
|
247
257
|
@db.mark_as_read(msg['id'])
|
|
248
258
|
msg['is_read'] = 1
|
|
259
|
+
invalidate_counts
|
|
249
260
|
sync_maildir_flag(msg, 'S', true)
|
|
250
261
|
|
|
251
262
|
# Also update any other references to this message in filtered/display lists
|
|
@@ -281,21 +292,22 @@ module Heathrow
|
|
|
281
292
|
count = @browsed_message_ids.size
|
|
282
293
|
return if count == 0
|
|
283
294
|
|
|
295
|
+
msg_by_id = @filtered_messages.each_with_object({}) { |m, h| h[m['id']] = m }
|
|
284
296
|
@browsed_message_ids.each do |msg_id|
|
|
285
297
|
@db.mark_as_read(msg_id)
|
|
286
|
-
|
|
287
|
-
msg = @filtered_messages.find { |m| m['id'] == msg_id }
|
|
298
|
+
msg = msg_by_id[msg_id]
|
|
288
299
|
msg['is_read'] = 1 if msg
|
|
289
300
|
end
|
|
290
301
|
|
|
291
302
|
@browsed_message_ids.clear
|
|
303
|
+
invalidate_counts
|
|
292
304
|
set_feedback("Marked #{count} browsed messages as read", 156, 3)
|
|
293
305
|
|
|
294
306
|
# Remove from view if in unread view
|
|
295
307
|
if is_unread_view?
|
|
296
308
|
@filtered_messages.select! { |m| m['is_read'].to_i == 0 }
|
|
297
309
|
@index = 0
|
|
298
|
-
reset_threading
|
|
310
|
+
reset_threading
|
|
299
311
|
end
|
|
300
312
|
|
|
301
313
|
render_all
|
|
@@ -391,16 +403,13 @@ module Heathrow
|
|
|
391
403
|
|
|
392
404
|
# EXACTLY LIKE RTFM
|
|
393
405
|
# Initialize rcurses
|
|
394
|
-
File.write('/tmp/heathrow_start.txt', "Starting run method\n") if ENV['DEBUG']
|
|
395
406
|
Rcurses.init!
|
|
396
|
-
File.write('/tmp/heathrow_start.txt', "Rcurses initialized\n", mode: 'a') if ENV['DEBUG']
|
|
397
407
|
|
|
398
408
|
# Clear screen to remove any artifacts
|
|
399
409
|
Rcurses.clear_screen
|
|
400
410
|
|
|
401
411
|
# Get terminal size
|
|
402
412
|
setup_display
|
|
403
|
-
File.write('/tmp/heathrow_start.txt', "Display setup complete: #{@w}x#{@h}\n", mode: 'a') if ENV['DEBUG']
|
|
404
413
|
|
|
405
414
|
# Create panes and show skeleton immediately
|
|
406
415
|
create_panes
|
|
@@ -440,8 +449,8 @@ module Heathrow
|
|
|
440
449
|
end
|
|
441
450
|
sort_messages
|
|
442
451
|
@index = 0
|
|
443
|
-
reset_threading
|
|
444
|
-
restore_view_thread_mode
|
|
452
|
+
reset_threading
|
|
453
|
+
restore_view_thread_mode
|
|
445
454
|
@initial_load_done = true
|
|
446
455
|
@needs_redraw = true
|
|
447
456
|
# Preload the heavy mail gem so 'v' (attachments) doesn't lag
|
|
@@ -481,14 +490,14 @@ module Heathrow
|
|
|
481
490
|
sort_messages
|
|
482
491
|
end
|
|
483
492
|
# Force re-organization in threaded mode
|
|
484
|
-
if @show_threaded
|
|
493
|
+
if @show_threaded
|
|
485
494
|
reset_threading(true)
|
|
486
|
-
organize_current_messages(true)
|
|
495
|
+
organize_current_messages(true)
|
|
487
496
|
end
|
|
488
497
|
# Defer index restoration for threaded views (display_messages is empty
|
|
489
498
|
# after reset_threading; it gets populated during render). For flat views,
|
|
490
499
|
# resolve immediately since @filtered_messages is the navigation list.
|
|
491
|
-
if @show_threaded
|
|
500
|
+
if @show_threaded
|
|
492
501
|
@pending_restore_id = selected_id
|
|
493
502
|
@index = 0
|
|
494
503
|
elsif selected_id
|
|
@@ -1143,7 +1152,7 @@ module Heathrow
|
|
|
1143
1152
|
set_feedback("Deleted: #{name} (#{deleted_msgs} messages removed)", 156, 3)
|
|
1144
1153
|
|
|
1145
1154
|
# Re-load the view to reflect removed messages
|
|
1146
|
-
reset_threading
|
|
1155
|
+
reset_threading
|
|
1147
1156
|
switch_to_view(@current_view) if @current_view
|
|
1148
1157
|
end
|
|
1149
1158
|
|
|
@@ -1362,10 +1371,15 @@ module Heathrow
|
|
|
1362
1371
|
count_text = "#{total} sources"
|
|
1363
1372
|
else
|
|
1364
1373
|
# For message views, show unread/total plus starred
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1374
|
+
if @cached_unread.nil?
|
|
1375
|
+
real_msgs = @filtered_messages.reject { |m| header_message?(m) }
|
|
1376
|
+
@cached_unread = real_msgs.count { |m| m['is_read'].to_i == 0 }
|
|
1377
|
+
@cached_starred = real_msgs.count { |m| m['is_starred'].to_i == 1 }
|
|
1378
|
+
@cached_total = real_msgs.size
|
|
1379
|
+
end
|
|
1380
|
+
unread = @cached_unread
|
|
1381
|
+
starred = @cached_starred
|
|
1382
|
+
total = @cached_total
|
|
1369
1383
|
count_text = "#{unread} unread / #{total} msgs"
|
|
1370
1384
|
count_text += " / #{starred}*" if starred > 0
|
|
1371
1385
|
end
|
|
@@ -1392,7 +1406,7 @@ module Heathrow
|
|
|
1392
1406
|
|
|
1393
1407
|
# Threading mode indicator
|
|
1394
1408
|
mode_part = ""
|
|
1395
|
-
if @current_view != 'S'
|
|
1409
|
+
if @current_view != 'S'
|
|
1396
1410
|
mode_part = " [#{thread_mode_label}]".fg(245)
|
|
1397
1411
|
end
|
|
1398
1412
|
|
|
@@ -1520,7 +1534,7 @@ module Heathrow
|
|
|
1520
1534
|
end
|
|
1521
1535
|
|
|
1522
1536
|
# Build the line with timestamp, icon and sender
|
|
1523
|
-
icon =
|
|
1537
|
+
icon = get_source_icon(msg['source_type'])
|
|
1524
1538
|
line_prefix = "#{timestamp} #{icon} #{sender_display} "
|
|
1525
1539
|
|
|
1526
1540
|
# Calculate remaining space for subject (use display_width for CJK)
|
|
@@ -1837,7 +1851,7 @@ module Heathrow
|
|
|
1837
1851
|
show_loading("Loading sources...")
|
|
1838
1852
|
|
|
1839
1853
|
# Reset threading state when changing views
|
|
1840
|
-
reset_threading
|
|
1854
|
+
reset_threading
|
|
1841
1855
|
|
|
1842
1856
|
# Reload source colors to ensure they're fresh
|
|
1843
1857
|
load_source_colors
|
|
@@ -1885,6 +1899,7 @@ module Heathrow
|
|
|
1885
1899
|
|
|
1886
1900
|
def show_all_messages
|
|
1887
1901
|
flush_pending_read
|
|
1902
|
+
invalidate_counts
|
|
1888
1903
|
@current_view = 'A'
|
|
1889
1904
|
@current_folder = nil
|
|
1890
1905
|
@in_source_view = false
|
|
@@ -1895,8 +1910,8 @@ module Heathrow
|
|
|
1895
1910
|
@last_rendered_index = nil # Force right pane refresh
|
|
1896
1911
|
|
|
1897
1912
|
# Restore per-view threading mode
|
|
1898
|
-
reset_threading
|
|
1899
|
-
restore_view_thread_mode
|
|
1913
|
+
reset_threading
|
|
1914
|
+
restore_view_thread_mode
|
|
1900
1915
|
|
|
1901
1916
|
# Show view name instantly
|
|
1902
1917
|
render_top_bar
|
|
@@ -1912,14 +1927,6 @@ module Heathrow
|
|
|
1912
1927
|
def render_message_content
|
|
1913
1928
|
current_msg = current_message
|
|
1914
1929
|
|
|
1915
|
-
if ENV['DEBUG']
|
|
1916
|
-
File.open('/tmp/heathrow_debug.log', 'a') do |f|
|
|
1917
|
-
f.puts "render_message_content START"
|
|
1918
|
-
f.puts " @index: #{@index}"
|
|
1919
|
-
f.puts " @filtered_messages[@index] exists: #{!current_msg.nil?}"
|
|
1920
|
-
end
|
|
1921
|
-
end
|
|
1922
|
-
|
|
1923
1930
|
# Reset scroll position when switching messages
|
|
1924
1931
|
if @last_rendered_index != @index
|
|
1925
1932
|
@panes[:right].ix = 0
|
|
@@ -1947,23 +1954,11 @@ module Heathrow
|
|
|
1947
1954
|
end
|
|
1948
1955
|
|
|
1949
1956
|
# Special handling for headers (channel headers, thread headers, etc.)
|
|
1950
|
-
if msg
|
|
1957
|
+
if header_message?(msg)
|
|
1951
1958
|
render_header_summary(msg)
|
|
1952
1959
|
return
|
|
1953
1960
|
end
|
|
1954
1961
|
|
|
1955
|
-
if ENV['DEBUG']
|
|
1956
|
-
File.open('/tmp/heathrow_debug.log', 'a') do |f|
|
|
1957
|
-
f.puts " msg details:"
|
|
1958
|
-
f.puts " id: #{msg['id']}"
|
|
1959
|
-
f.puts " sender: #{msg['sender']}"
|
|
1960
|
-
f.puts " recipient: #{msg['recipient']}"
|
|
1961
|
-
f.puts " subject: #{msg['subject']}"
|
|
1962
|
-
f.puts " timestamp: #{msg['timestamp'].inspect}"
|
|
1963
|
-
f.puts " source_type: #{msg['source_type'].inspect}"
|
|
1964
|
-
end
|
|
1965
|
-
end
|
|
1966
|
-
|
|
1967
1962
|
# Format message header
|
|
1968
1963
|
header = []
|
|
1969
1964
|
|
|
@@ -2000,23 +1995,20 @@ module Heathrow
|
|
|
2000
1995
|
else
|
|
2001
1996
|
# Regular message display
|
|
2002
1997
|
header << "From: #{msg['sender']}".fg(2) if msg['sender']
|
|
2003
|
-
# Show recipients (To field)
|
|
1998
|
+
# Show recipients (To field, already parsed by normalize_message_row)
|
|
2004
1999
|
to = msg['recipients'] || msg['recipient']
|
|
2005
2000
|
if to
|
|
2006
|
-
|
|
2007
|
-
to_str = to_list.is_a?(Array) ? to_list.join(', ') : to_list.to_s
|
|
2001
|
+
to_str = to.is_a?(Array) ? to.join(', ') : to.to_s
|
|
2008
2002
|
header << "To: #{to_str}".fg(2) unless to_str.empty?
|
|
2009
2003
|
end
|
|
2010
|
-
# Show CC recipients
|
|
2004
|
+
# Show CC recipients (already parsed by normalize_message_row)
|
|
2011
2005
|
cc = msg['cc']
|
|
2012
2006
|
if cc
|
|
2013
|
-
|
|
2014
|
-
cc_str = cc_list.is_a?(Array) ? cc_list.join(', ') : cc_list.to_s
|
|
2007
|
+
cc_str = cc.is_a?(Array) ? cc.join(', ') : cc.to_s
|
|
2015
2008
|
header << "Cc: #{cc_str}".fg(2) unless cc_str.empty?
|
|
2016
2009
|
end
|
|
2017
2010
|
# For weechat, show channel name from metadata instead of content preview
|
|
2018
2011
|
meta = msg['metadata']
|
|
2019
|
-
meta = JSON.parse(meta) if meta.is_a?(String) rescue nil
|
|
2020
2012
|
if meta.is_a?(Hash) && meta['channel_name']
|
|
2021
2013
|
header << "Subject: #{meta['channel_name']}".b.fg(1)
|
|
2022
2014
|
elsif msg['subject']
|
|
@@ -2024,22 +2016,9 @@ module Heathrow
|
|
|
2024
2016
|
end
|
|
2025
2017
|
end
|
|
2026
2018
|
|
|
2027
|
-
if ENV['DEBUG']
|
|
2028
|
-
File.open('/tmp/heathrow_debug.log', 'a') do |f|
|
|
2029
|
-
f.puts " About to parse timestamp: #{msg['timestamp'].inspect}"
|
|
2030
|
-
end
|
|
2031
|
-
end
|
|
2032
|
-
|
|
2033
2019
|
# Parse timestamp using helper method
|
|
2034
2020
|
date_str = parse_timestamp(msg['timestamp'], '%Y-%m-%d %H:%M:%S') || "Unknown date"
|
|
2035
|
-
|
|
2036
|
-
if ENV['DEBUG']
|
|
2037
|
-
File.open('/tmp/heathrow_debug.log', 'a') do |f|
|
|
2038
|
-
f.puts " Final date_str: #{date_str}"
|
|
2039
|
-
f.puts " About to add header lines"
|
|
2040
|
-
end
|
|
2041
|
-
end
|
|
2042
|
-
|
|
2021
|
+
|
|
2043
2022
|
header << "Date: #{date_str}".fg(240)
|
|
2044
2023
|
if msg['source_type']
|
|
2045
2024
|
source_label = case msg['source_type']
|
|
@@ -2050,10 +2029,10 @@ module Heathrow
|
|
|
2050
2029
|
header << "Type: #{source_label}".fg(get_source_color(msg))
|
|
2051
2030
|
end
|
|
2052
2031
|
|
|
2053
|
-
# Show labels (if any beyond the folder name)
|
|
2032
|
+
# Show labels (if any beyond the folder name, already parsed by normalize_message_row)
|
|
2054
2033
|
labels = msg['labels']
|
|
2055
|
-
labels =
|
|
2056
|
-
if labels.
|
|
2034
|
+
labels = [] unless labels.is_a?(Array)
|
|
2035
|
+
if labels.size > 0
|
|
2057
2036
|
# Skip folder name (first label) if it matches the folder
|
|
2058
2037
|
display_labels = labels.reject { |l| l == msg['folder'] }
|
|
2059
2038
|
unless display_labels.empty?
|
|
@@ -2061,11 +2040,6 @@ module Heathrow
|
|
|
2061
2040
|
end
|
|
2062
2041
|
end
|
|
2063
2042
|
|
|
2064
|
-
if ENV['DEBUG']
|
|
2065
|
-
File.open('/tmp/heathrow_debug.log', 'a') do |f|
|
|
2066
|
-
f.puts " Header built successfully, about to format content"
|
|
2067
|
-
end
|
|
2068
|
-
end
|
|
2069
2043
|
header << ("─" * 60).fg(238)
|
|
2070
2044
|
|
|
2071
2045
|
# Format message body — prefer HTML rendered via w3m
|
|
@@ -2134,13 +2108,6 @@ module Heathrow
|
|
|
2134
2108
|
content = content_parts.join("\n")
|
|
2135
2109
|
end
|
|
2136
2110
|
|
|
2137
|
-
if ENV['DEBUG']
|
|
2138
|
-
File.open('/tmp/heathrow_debug.log', 'a') do |f|
|
|
2139
|
-
f.puts " Content formatted, about to set pane text"
|
|
2140
|
-
f.puts " Content length: #{content.length}"
|
|
2141
|
-
end
|
|
2142
|
-
end
|
|
2143
|
-
|
|
2144
2111
|
# Ensure content is UTF-8 compatible (handle emails with various encodings)
|
|
2145
2112
|
# Create a mutable copy if frozen, then fix encoding
|
|
2146
2113
|
content = content.dup if content.frozen?
|
|
@@ -2154,9 +2121,8 @@ module Heathrow
|
|
|
2154
2121
|
# Colorize email content (quote levels + signature)
|
|
2155
2122
|
content = colorize_email_content(content)
|
|
2156
2123
|
|
|
2157
|
-
# Store maildir file path for calendar parser
|
|
2124
|
+
# Store maildir file path for calendar parser (metadata already parsed by normalize_message_row)
|
|
2158
2125
|
meta = msg['metadata']
|
|
2159
|
-
meta = JSON.parse(meta) if meta.is_a?(String) rescue nil
|
|
2160
2126
|
@_current_render_msg_file = meta['maildir_file'] if meta.is_a?(Hash)
|
|
2161
2127
|
|
|
2162
2128
|
# Attachment list under header, before body
|
|
@@ -2193,11 +2159,6 @@ module Heathrow
|
|
|
2193
2159
|
@panes[:right].text = full_text
|
|
2194
2160
|
@panes[:right].refresh
|
|
2195
2161
|
|
|
2196
|
-
if ENV['DEBUG']
|
|
2197
|
-
File.open('/tmp/heathrow_debug.log', 'a') do |f|
|
|
2198
|
-
f.puts "render_message_content END - success"
|
|
2199
|
-
end
|
|
2200
|
-
end
|
|
2201
2162
|
end
|
|
2202
2163
|
|
|
2203
2164
|
def render_header_summary(header_msg)
|
|
@@ -2392,7 +2353,7 @@ module Heathrow
|
|
|
2392
2353
|
end
|
|
2393
2354
|
|
|
2394
2355
|
# Force threaded view to rebuild organizer with new messages
|
|
2395
|
-
if @show_threaded
|
|
2356
|
+
if @show_threaded
|
|
2396
2357
|
organize_current_messages(true)
|
|
2397
2358
|
end
|
|
2398
2359
|
|
|
@@ -2455,7 +2416,7 @@ module Heathrow
|
|
|
2455
2416
|
}
|
|
2456
2417
|
end
|
|
2457
2418
|
|
|
2458
|
-
def
|
|
2419
|
+
def build_db_filters(view)
|
|
2459
2420
|
filters = view[:filters] || {}
|
|
2460
2421
|
db_filters = {}
|
|
2461
2422
|
if filters['rules'].is_a?(Array)
|
|
@@ -2476,6 +2437,11 @@ module Heathrow
|
|
|
2476
2437
|
end
|
|
2477
2438
|
end
|
|
2478
2439
|
end
|
|
2440
|
+
db_filters
|
|
2441
|
+
end
|
|
2442
|
+
|
|
2443
|
+
def apply_view_filters_with_limit(view, limit)
|
|
2444
|
+
db_filters = build_db_filters(view)
|
|
2479
2445
|
@filtered_messages = @db.get_messages(db_filters, limit, 0, light: true)
|
|
2480
2446
|
end
|
|
2481
2447
|
|
|
@@ -2552,6 +2518,7 @@ module Heathrow
|
|
|
2552
2518
|
# View switching
|
|
2553
2519
|
def show_new_messages
|
|
2554
2520
|
flush_pending_read
|
|
2521
|
+
invalidate_counts
|
|
2555
2522
|
@current_view = 'N'
|
|
2556
2523
|
@current_folder = nil
|
|
2557
2524
|
@in_source_view = false
|
|
@@ -2561,8 +2528,8 @@ module Heathrow
|
|
|
2561
2528
|
@last_rendered_index = nil # Force right pane refresh
|
|
2562
2529
|
|
|
2563
2530
|
# Restore per-view threading mode
|
|
2564
|
-
reset_threading
|
|
2565
|
-
restore_view_thread_mode
|
|
2531
|
+
reset_threading
|
|
2532
|
+
restore_view_thread_mode
|
|
2566
2533
|
|
|
2567
2534
|
render_top_bar
|
|
2568
2535
|
|
|
@@ -2585,6 +2552,7 @@ module Heathrow
|
|
|
2585
2552
|
|
|
2586
2553
|
def switch_to_view(key)
|
|
2587
2554
|
flush_pending_read
|
|
2555
|
+
invalidate_counts
|
|
2588
2556
|
@current_view = key
|
|
2589
2557
|
@current_folder = nil
|
|
2590
2558
|
@in_source_view = false
|
|
@@ -2592,8 +2560,8 @@ module Heathrow
|
|
|
2592
2560
|
@last_rendered_index = nil # Force right pane refresh
|
|
2593
2561
|
|
|
2594
2562
|
# Restore per-view threading mode
|
|
2595
|
-
reset_threading
|
|
2596
|
-
restore_view_thread_mode
|
|
2563
|
+
reset_threading
|
|
2564
|
+
restore_view_thread_mode
|
|
2597
2565
|
|
|
2598
2566
|
render_top_bar
|
|
2599
2567
|
|
|
@@ -2631,12 +2599,12 @@ module Heathrow
|
|
|
2631
2599
|
|
|
2632
2600
|
def apply_view_filters(view)
|
|
2633
2601
|
filters = view[:filters] || {}
|
|
2634
|
-
|
|
2602
|
+
|
|
2635
2603
|
# Check for special filter types
|
|
2636
2604
|
if filters['special'] == 'uncategorized'
|
|
2637
2605
|
# Get all messages first
|
|
2638
2606
|
all_messages = @db.get_messages({}, 1000, 0, light: true)
|
|
2639
|
-
|
|
2607
|
+
|
|
2640
2608
|
# Get messages from all other configured views
|
|
2641
2609
|
categorized_ids = Set.new
|
|
2642
2610
|
|
|
@@ -2648,7 +2616,7 @@ module Heathrow
|
|
|
2648
2616
|
other_view[:filters].each do |key, value|
|
|
2649
2617
|
symbolized_filters[key.to_sym] = value
|
|
2650
2618
|
end
|
|
2651
|
-
|
|
2619
|
+
|
|
2652
2620
|
view_messages = @db.get_messages(symbolized_filters, 1000, 0, light: true)
|
|
2653
2621
|
view_messages.each { |msg| categorized_ids.add(msg['id']) }
|
|
2654
2622
|
end
|
|
@@ -2657,32 +2625,12 @@ module Heathrow
|
|
|
2657
2625
|
# Filter to only uncategorized messages
|
|
2658
2626
|
@filtered_messages = all_messages.reject { |msg| categorized_ids.include?(msg['id']) }
|
|
2659
2627
|
else
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
if filters['rules'].is_a?(Array)
|
|
2664
|
-
# Convert rules array to simple database filters
|
|
2665
|
-
filters['rules'].each do |rule|
|
|
2666
|
-
field = rule['field']
|
|
2667
|
-
value = rule['value']
|
|
2668
|
-
case rule['op']
|
|
2669
|
-
when '='
|
|
2670
|
-
db_filters[field.to_sym] = value
|
|
2671
|
-
when 'like'
|
|
2672
|
-
case field
|
|
2673
|
-
when 'search' then db_filters[:search] = value
|
|
2674
|
-
when 'sender' then db_filters[:sender_pattern] = value
|
|
2675
|
-
when 'subject' then db_filters[:subject_pattern] = value
|
|
2676
|
-
when 'folder' then db_filters[:maildir_folder] = value
|
|
2677
|
-
when 'label' then db_filters[:label] = value
|
|
2678
|
-
when 'source' then db_filters[:source_name] = value
|
|
2679
|
-
end
|
|
2680
|
-
end
|
|
2681
|
-
end
|
|
2682
|
-
else
|
|
2683
|
-
# Legacy simple filters - convert string keys to symbols
|
|
2628
|
+
db_filters = build_db_filters(view)
|
|
2629
|
+
|
|
2630
|
+
# Legacy simple filters (no rules array)
|
|
2631
|
+
if !filters['rules'].is_a?(Array)
|
|
2684
2632
|
filters.each do |key, value|
|
|
2685
|
-
next if key == 'rules'
|
|
2633
|
+
next if key == 'rules'
|
|
2686
2634
|
db_filters[key.to_sym] = value
|
|
2687
2635
|
end
|
|
2688
2636
|
end
|
|
@@ -2694,7 +2642,6 @@ module Heathrow
|
|
|
2694
2642
|
ensure_all_feeds_loaded(db_filters[:source_id].to_i)
|
|
2695
2643
|
end
|
|
2696
2644
|
end
|
|
2697
|
-
|
|
2698
2645
|
end
|
|
2699
2646
|
|
|
2700
2647
|
# Message operations
|
|
@@ -2748,7 +2695,7 @@ module Heathrow
|
|
|
2748
2695
|
end
|
|
2749
2696
|
|
|
2750
2697
|
# Re-render
|
|
2751
|
-
render_message_list_threaded
|
|
2698
|
+
render_message_list_threaded
|
|
2752
2699
|
render_message_content
|
|
2753
2700
|
end
|
|
2754
2701
|
|
|
@@ -2786,7 +2733,7 @@ module Heathrow
|
|
|
2786
2733
|
end
|
|
2787
2734
|
|
|
2788
2735
|
# Re-render
|
|
2789
|
-
render_message_list_threaded
|
|
2736
|
+
render_message_list_threaded
|
|
2790
2737
|
|
|
2791
2738
|
# Restore position if we had one saved
|
|
2792
2739
|
if restore_position
|
|
@@ -2820,7 +2767,7 @@ module Heathrow
|
|
|
2820
2767
|
def toggle_tag
|
|
2821
2768
|
msg = current_message
|
|
2822
2769
|
return unless msg
|
|
2823
|
-
return if msg
|
|
2770
|
+
return if header_message?(msg)
|
|
2824
2771
|
return unless msg['id']
|
|
2825
2772
|
|
|
2826
2773
|
if @tagged_messages.include?(msg['id'])
|
|
@@ -2847,7 +2794,7 @@ module Heathrow
|
|
|
2847
2794
|
|
|
2848
2795
|
count = 0
|
|
2849
2796
|
@filtered_messages.each do |msg|
|
|
2850
|
-
next if msg
|
|
2797
|
+
next if header_message?(msg)
|
|
2851
2798
|
next unless msg['id']
|
|
2852
2799
|
|
|
2853
2800
|
match = [msg['sender'], msg['subject'], msg['content']].compact.any? { |f| f.match?(regex) }
|
|
@@ -2867,7 +2814,7 @@ module Heathrow
|
|
|
2867
2814
|
|
|
2868
2815
|
# Tag/untag all messages in current view
|
|
2869
2816
|
def tag_all_toggle
|
|
2870
|
-
msgs = @filtered_messages.reject { |m| m
|
|
2817
|
+
msgs = @filtered_messages.reject { |m| header_message?(m) }
|
|
2871
2818
|
ids = msgs.map { |m| m['id'] }.compact
|
|
2872
2819
|
if ids.all? { |id| @tagged_messages.include?(id) }
|
|
2873
2820
|
# All tagged, untag all
|
|
@@ -2895,7 +2842,7 @@ module Heathrow
|
|
|
2895
2842
|
idx = (@index + 1 + i) % display.size
|
|
2896
2843
|
msg = display[idx]
|
|
2897
2844
|
next unless msg
|
|
2898
|
-
next if msg
|
|
2845
|
+
next if header_message?(msg)
|
|
2899
2846
|
if msg['is_read'].to_i == 0
|
|
2900
2847
|
@index = idx
|
|
2901
2848
|
track_browsed_message
|
|
@@ -2914,14 +2861,14 @@ module Heathrow
|
|
|
2914
2861
|
if unread_msgs.any?
|
|
2915
2862
|
# Re-render to rebuild display_messages with uncollapsed sections
|
|
2916
2863
|
organize_current_messages(true)
|
|
2917
|
-
render_message_list_threaded
|
|
2864
|
+
render_message_list_threaded
|
|
2918
2865
|
new_display = @display_messages || @filtered_messages
|
|
2919
2866
|
# Find the first visible unread after current position
|
|
2920
2867
|
new_display.size.times do |i|
|
|
2921
2868
|
idx = (@index + 1 + i) % new_display.size
|
|
2922
2869
|
m = new_display[idx]
|
|
2923
2870
|
next unless m && m['id'] && m['is_read'].to_i == 0
|
|
2924
|
-
next if m
|
|
2871
|
+
next if header_message?(m)
|
|
2925
2872
|
@index = idx
|
|
2926
2873
|
track_browsed_message
|
|
2927
2874
|
render_all
|
|
@@ -2943,7 +2890,7 @@ module Heathrow
|
|
|
2943
2890
|
idx = (@index - 1 - i) % size
|
|
2944
2891
|
msg = display[idx]
|
|
2945
2892
|
next unless msg
|
|
2946
|
-
next if msg
|
|
2893
|
+
next if header_message?(msg)
|
|
2947
2894
|
if msg['is_read'].to_i == 0
|
|
2948
2895
|
@index = idx
|
|
2949
2896
|
track_browsed_message
|
|
@@ -2960,14 +2907,14 @@ module Heathrow
|
|
|
2960
2907
|
end
|
|
2961
2908
|
if unread_msgs.any?
|
|
2962
2909
|
organize_current_messages(true)
|
|
2963
|
-
render_message_list_threaded
|
|
2910
|
+
render_message_list_threaded
|
|
2964
2911
|
new_display = @display_messages || @filtered_messages
|
|
2965
2912
|
new_size = new_display.size
|
|
2966
2913
|
new_size.times do |i|
|
|
2967
2914
|
idx = (@index - 1 - i) % new_size
|
|
2968
2915
|
m = new_display[idx]
|
|
2969
2916
|
next unless m && m['id'] && m['is_read'].to_i == 0
|
|
2970
|
-
next if m
|
|
2917
|
+
next if header_message?(m)
|
|
2971
2918
|
@index = idx
|
|
2972
2919
|
track_browsed_message
|
|
2973
2920
|
render_all
|
|
@@ -3053,7 +3000,7 @@ module Heathrow
|
|
|
3053
3000
|
def open_message_external
|
|
3054
3001
|
msg = current_message
|
|
3055
3002
|
return unless msg
|
|
3056
|
-
return if msg
|
|
3003
|
+
return if header_message?(msg)
|
|
3057
3004
|
msg = ensure_full_message(msg)
|
|
3058
3005
|
|
|
3059
3006
|
# Mark as read
|
|
@@ -3147,7 +3094,7 @@ module Heathrow
|
|
|
3147
3094
|
def view_attachments
|
|
3148
3095
|
msg = current_message
|
|
3149
3096
|
return unless msg
|
|
3150
|
-
return set_feedback("Select a message first", 245, 2) if msg
|
|
3097
|
+
return set_feedback("Select a message first", 245, 2) if header_message?(msg)
|
|
3151
3098
|
|
|
3152
3099
|
# Load full message if needed (use numeric id only)
|
|
3153
3100
|
msg_id = msg['id']
|
|
@@ -3354,7 +3301,7 @@ module Heathrow
|
|
|
3354
3301
|
if @sort_order == 'unread'
|
|
3355
3302
|
sort_messages
|
|
3356
3303
|
# Reset threading to reorganize with new unread counts (preserve collapsed state)
|
|
3357
|
-
reset_threading(true)
|
|
3304
|
+
reset_threading(true)
|
|
3358
3305
|
end
|
|
3359
3306
|
|
|
3360
3307
|
# Update displays if UI is initialized
|
|
@@ -3451,6 +3398,8 @@ module Heathrow
|
|
|
3451
3398
|
rows = nil
|
|
3452
3399
|
end
|
|
3453
3400
|
|
|
3401
|
+
organized_sections = (@show_threaded && @organizer) ? @organizer.get_organized_view(@sort_order, @sort_inverted) : nil
|
|
3402
|
+
|
|
3454
3403
|
if rows
|
|
3455
3404
|
# Sync maildir flags for all affected messages
|
|
3456
3405
|
count = rows.size
|
|
@@ -3465,8 +3414,8 @@ module Heathrow
|
|
|
3465
3414
|
next unless list
|
|
3466
3415
|
list.each { |m| m['is_read'] = 1 if m['id'] && m['is_read'].to_i == 0 }
|
|
3467
3416
|
end
|
|
3468
|
-
if
|
|
3469
|
-
|
|
3417
|
+
if organized_sections
|
|
3418
|
+
organized_sections.each do |section|
|
|
3470
3419
|
next unless section[:messages]
|
|
3471
3420
|
section[:messages].each { |m| m['is_read'] = 1 if m['is_read'].to_i == 0 }
|
|
3472
3421
|
end
|
|
@@ -3481,14 +3430,15 @@ module Heathrow
|
|
|
3481
3430
|
return if msgs.nil? || msgs.empty?
|
|
3482
3431
|
count = mark_messages_read(msgs)
|
|
3483
3432
|
# Also mark messages inside collapsed sections
|
|
3484
|
-
if
|
|
3485
|
-
|
|
3433
|
+
if organized_sections
|
|
3434
|
+
organized_sections.each do |section|
|
|
3486
3435
|
next unless section[:messages]
|
|
3487
3436
|
count += mark_messages_read(section[:messages])
|
|
3488
3437
|
end
|
|
3489
3438
|
end
|
|
3490
3439
|
end
|
|
3491
3440
|
|
|
3441
|
+
invalidate_counts
|
|
3492
3442
|
set_feedback("Marked #{count} messages as read", 156, 3)
|
|
3493
3443
|
render_top_bar
|
|
3494
3444
|
render_message_list
|
|
@@ -3499,7 +3449,7 @@ module Heathrow
|
|
|
3499
3449
|
def mark_messages_read(msgs)
|
|
3500
3450
|
count = 0
|
|
3501
3451
|
msgs.each do |msg|
|
|
3502
|
-
next if msg
|
|
3452
|
+
next if header_message?(msg)
|
|
3503
3453
|
next if msg['is_read'].to_i == 1
|
|
3504
3454
|
next unless msg['id']
|
|
3505
3455
|
@db.mark_as_read(msg['id'])
|
|
@@ -3516,7 +3466,7 @@ module Heathrow
|
|
|
3516
3466
|
return nil unless msg && @show_threaded && @organizer
|
|
3517
3467
|
|
|
3518
3468
|
# If standing on a header, use its section_messages directly
|
|
3519
|
-
if msg
|
|
3469
|
+
if header_message?(msg)
|
|
3520
3470
|
return msg['section_messages'] if msg['section_messages']
|
|
3521
3471
|
end
|
|
3522
3472
|
|
|
@@ -3524,7 +3474,7 @@ module Heathrow
|
|
|
3524
3474
|
idx = @index - 1
|
|
3525
3475
|
while idx >= 0
|
|
3526
3476
|
prev = @display_messages[idx]
|
|
3527
|
-
if prev && (prev
|
|
3477
|
+
if prev && (header_message?(prev))
|
|
3528
3478
|
return prev['section_messages'] if prev['section_messages']
|
|
3529
3479
|
break
|
|
3530
3480
|
end
|
|
@@ -3566,7 +3516,7 @@ module Heathrow
|
|
|
3566
3516
|
# Toggle all messages in this section
|
|
3567
3517
|
messages.each do |msg|
|
|
3568
3518
|
next unless msg['id'] && !msg['id'].to_s.start_with?('header_') # Skip synthetic headers
|
|
3569
|
-
|
|
3519
|
+
|
|
3570
3520
|
if all_read
|
|
3571
3521
|
@db.mark_as_unread(msg['id'])
|
|
3572
3522
|
msg['is_read'] = 0
|
|
@@ -3575,12 +3525,13 @@ module Heathrow
|
|
|
3575
3525
|
msg['is_read'] = 1
|
|
3576
3526
|
end
|
|
3577
3527
|
end
|
|
3578
|
-
|
|
3528
|
+
invalidate_counts
|
|
3529
|
+
|
|
3579
3530
|
# Re-sort if sorting by unread
|
|
3580
3531
|
if @sort_order == 'unread'
|
|
3581
3532
|
sort_messages
|
|
3582
3533
|
# Reset threading to reorganize with new unread counts (preserve collapsed state)
|
|
3583
|
-
reset_threading(true)
|
|
3534
|
+
reset_threading(true)
|
|
3584
3535
|
end
|
|
3585
3536
|
|
|
3586
3537
|
# Update displays if UI is initialized
|
|
@@ -3601,6 +3552,7 @@ module Heathrow
|
|
|
3601
3552
|
|
|
3602
3553
|
# Toggle the specific message
|
|
3603
3554
|
current_read_status = msg['is_read'].to_i
|
|
3555
|
+
invalidate_counts
|
|
3604
3556
|
|
|
3605
3557
|
if current_read_status == 1
|
|
3606
3558
|
success = @db.mark_as_unread(msg['id'])
|
|
@@ -3620,10 +3572,10 @@ module Heathrow
|
|
|
3620
3572
|
if is_unread_view?
|
|
3621
3573
|
if @show_threaded
|
|
3622
3574
|
# In threaded view, need to reload filtered messages to rebuild threads
|
|
3623
|
-
reset_threading
|
|
3575
|
+
reset_threading
|
|
3624
3576
|
@filtered_messages = @db.get_messages({is_read: false}, 1000, 0, light: true)
|
|
3625
3577
|
sort_messages
|
|
3626
|
-
organize_current_messages(force_reinit: true)
|
|
3578
|
+
organize_current_messages(force_reinit: true)
|
|
3627
3579
|
@index = [@index, filtered_messages_size - 1].min
|
|
3628
3580
|
@index = 0 if @index < 0
|
|
3629
3581
|
else
|
|
@@ -3640,7 +3592,7 @@ module Heathrow
|
|
|
3640
3592
|
if @sort_order == 'unread'
|
|
3641
3593
|
sort_messages
|
|
3642
3594
|
# Reset threading to reorganize with new unread counts (preserve collapsed state)
|
|
3643
|
-
reset_threading(true)
|
|
3595
|
+
reset_threading(true)
|
|
3644
3596
|
end
|
|
3645
3597
|
|
|
3646
3598
|
# Update displays immediately if UI is initialized
|
|
@@ -3696,6 +3648,7 @@ module Heathrow
|
|
|
3696
3648
|
return unless msg
|
|
3697
3649
|
@db.toggle_star(msg['id'])
|
|
3698
3650
|
msg['is_starred'] = msg['is_starred'] == 1 ? 0 : 1
|
|
3651
|
+
invalidate_counts
|
|
3699
3652
|
|
|
3700
3653
|
# Sync flagged status to Maildir file
|
|
3701
3654
|
sync_maildir_flag(msg, 'F', msg['is_starred'] == 1)
|
|
@@ -3986,7 +3939,7 @@ module Heathrow
|
|
|
3986
3939
|
@current_source_filter = "Folder: #{folder_name}"
|
|
3987
3940
|
|
|
3988
3941
|
# Reset threading state so old @display_messages don't persist
|
|
3989
|
-
reset_threading
|
|
3942
|
+
reset_threading
|
|
3990
3943
|
|
|
3991
3944
|
# Show progress while loading
|
|
3992
3945
|
@panes[:bottom].text = " Loading #{folder_name}...".fg(226)
|
|
@@ -4273,7 +4226,7 @@ module Heathrow
|
|
|
4273
4226
|
if @show_threaded
|
|
4274
4227
|
@display_messages&.reject! { |m| m['id'] && filed_ids.include?(m['id']) }
|
|
4275
4228
|
# Force organizer to rebuild from updated @filtered_messages
|
|
4276
|
-
organize_current_messages(true)
|
|
4229
|
+
organize_current_messages(true)
|
|
4277
4230
|
end
|
|
4278
4231
|
@tagged_messages.clear if @tagged_messages.size > 0
|
|
4279
4232
|
@index = [@index, (@filtered_messages.size - 1)].min
|
|
@@ -4963,7 +4916,7 @@ module Heathrow
|
|
|
4963
4916
|
@filtered_messages = results
|
|
4964
4917
|
sort_messages
|
|
4965
4918
|
@index = 0
|
|
4966
|
-
reset_threading
|
|
4919
|
+
reset_threading
|
|
4967
4920
|
set_feedback("#{results.size} results for: #{query}", 156, 0)
|
|
4968
4921
|
render_all
|
|
4969
4922
|
end
|
|
@@ -4987,7 +4940,7 @@ module Heathrow
|
|
|
4987
4940
|
else
|
|
4988
4941
|
msg = current_message
|
|
4989
4942
|
return unless msg
|
|
4990
|
-
return if msg
|
|
4943
|
+
return if header_message?(msg)
|
|
4991
4944
|
|
|
4992
4945
|
if @delete_marked.include?(msg['id'])
|
|
4993
4946
|
@delete_marked.delete(msg['id'])
|
|
@@ -5052,7 +5005,7 @@ module Heathrow
|
|
|
5052
5005
|
@delete_marked.clear
|
|
5053
5006
|
|
|
5054
5007
|
# Force threaded view to rebuild with purged messages gone
|
|
5055
|
-
reset_threading(true)
|
|
5008
|
+
reset_threading(true)
|
|
5056
5009
|
|
|
5057
5010
|
# Position cursor on the message above the first deleted one
|
|
5058
5011
|
new_display = @display_messages || @filtered_messages
|
|
@@ -5100,7 +5053,7 @@ module Heathrow
|
|
|
5100
5053
|
msg = ensure_full_message(msg)
|
|
5101
5054
|
|
|
5102
5055
|
# Don't allow replying to header messages
|
|
5103
|
-
if msg
|
|
5056
|
+
if header_message?(msg)
|
|
5104
5057
|
set_feedback("Cannot reply to section headers. Select a message.", 226, 3)
|
|
5105
5058
|
render_bottom_bar
|
|
5106
5059
|
return
|
|
@@ -5241,7 +5194,7 @@ module Heathrow
|
|
|
5241
5194
|
def edit_message_content
|
|
5242
5195
|
msg = current_message
|
|
5243
5196
|
return unless msg
|
|
5244
|
-
return if msg
|
|
5197
|
+
return if header_message?(msg)
|
|
5245
5198
|
msg = ensure_full_message(msg)
|
|
5246
5199
|
|
|
5247
5200
|
source_id = msg['source_id']
|
|
@@ -6051,9 +6004,9 @@ module Heathrow
|
|
|
6051
6004
|
@panes[:right].w = @w - @panes[:left].w - 4
|
|
6052
6005
|
|
|
6053
6006
|
# Reset threading for sort changes
|
|
6054
|
-
reset_threading(true)
|
|
6007
|
+
reset_threading(true)
|
|
6055
6008
|
sort_messages
|
|
6056
|
-
organize_current_messages(true)
|
|
6009
|
+
organize_current_messages(true)
|
|
6057
6010
|
|
|
6058
6011
|
Rcurses.clear_screen
|
|
6059
6012
|
@panes.each_value { |p| p.cleanup if p.respond_to?(:cleanup) }
|
|
@@ -6322,13 +6275,13 @@ module Heathrow
|
|
|
6322
6275
|
end
|
|
6323
6276
|
|
|
6324
6277
|
# Reset threading state to force reorganization with new sort (preserve collapsed state)
|
|
6325
|
-
reset_threading(true)
|
|
6278
|
+
reset_threading(true)
|
|
6326
6279
|
|
|
6327
6280
|
# Re-sort and redisplay messages
|
|
6328
6281
|
sort_messages
|
|
6329
6282
|
|
|
6330
6283
|
# Force reinit the organizer with the newly sorted messages
|
|
6331
|
-
organize_current_messages(true)
|
|
6284
|
+
organize_current_messages(true)
|
|
6332
6285
|
|
|
6333
6286
|
@index = 0 # Reset to top
|
|
6334
6287
|
render_all # Re-render everything to show the new sort
|
|
@@ -6354,13 +6307,13 @@ module Heathrow
|
|
|
6354
6307
|
end
|
|
6355
6308
|
|
|
6356
6309
|
# Reset threading state to force reorganization with new sort (preserve collapsed state)
|
|
6357
|
-
reset_threading(true)
|
|
6310
|
+
reset_threading(true)
|
|
6358
6311
|
|
|
6359
6312
|
# Re-sort and redisplay messages
|
|
6360
6313
|
sort_messages
|
|
6361
6314
|
|
|
6362
6315
|
# Force reinit the organizer with the newly sorted messages
|
|
6363
|
-
organize_current_messages(true)
|
|
6316
|
+
organize_current_messages(true)
|
|
6364
6317
|
|
|
6365
6318
|
@index = 0 # Reset to top
|
|
6366
6319
|
render_all # Re-render everything
|
|
@@ -6386,7 +6339,11 @@ module Heathrow
|
|
|
6386
6339
|
|
|
6387
6340
|
# Make sure we have a mutable array
|
|
6388
6341
|
@filtered_messages = @filtered_messages.dup if @filtered_messages.frozen?
|
|
6389
|
-
|
|
6342
|
+
|
|
6343
|
+
# Pre-compute timestamp cache to avoid repeated parsing in sort comparisons
|
|
6344
|
+
ts_cache = {}
|
|
6345
|
+
@filtered_messages.each { |m| ts_cache[m.object_id] = timestamp_to_time(m['timestamp']) }
|
|
6346
|
+
|
|
6390
6347
|
begin
|
|
6391
6348
|
case @sort_order
|
|
6392
6349
|
when 'alphabetical'
|
|
@@ -6410,7 +6367,7 @@ module Heathrow
|
|
|
6410
6367
|
else
|
|
6411
6368
|
# If subjects are the same, sort by timestamp
|
|
6412
6369
|
begin
|
|
6413
|
-
|
|
6370
|
+
ts_cache[b.object_id] <=> ts_cache[a.object_id]
|
|
6414
6371
|
rescue
|
|
6415
6372
|
0
|
|
6416
6373
|
end
|
|
@@ -6428,7 +6385,7 @@ module Heathrow
|
|
|
6428
6385
|
else
|
|
6429
6386
|
# Safe timestamp comparison
|
|
6430
6387
|
begin
|
|
6431
|
-
|
|
6388
|
+
ts_cache[b.object_id] <=> ts_cache[a.object_id]
|
|
6432
6389
|
rescue
|
|
6433
6390
|
0
|
|
6434
6391
|
end
|
|
@@ -6443,7 +6400,7 @@ module Heathrow
|
|
|
6443
6400
|
else
|
|
6444
6401
|
# Safe timestamp comparison
|
|
6445
6402
|
begin
|
|
6446
|
-
|
|
6403
|
+
ts_cache[b.object_id] <=> ts_cache[a.object_id]
|
|
6447
6404
|
rescue
|
|
6448
6405
|
0 # If timestamp parsing fails, consider them equal
|
|
6449
6406
|
end
|
|
@@ -6460,7 +6417,7 @@ module Heathrow
|
|
|
6460
6417
|
conv_cmp
|
|
6461
6418
|
else
|
|
6462
6419
|
begin
|
|
6463
|
-
|
|
6420
|
+
ts_cache[b.object_id] <=> ts_cache[a.object_id]
|
|
6464
6421
|
rescue
|
|
6465
6422
|
0
|
|
6466
6423
|
end
|
|
@@ -6478,7 +6435,7 @@ module Heathrow
|
|
|
6478
6435
|
else
|
|
6479
6436
|
# Safe timestamp comparison
|
|
6480
6437
|
begin
|
|
6481
|
-
|
|
6438
|
+
ts_cache[b.object_id] <=> ts_cache[a.object_id]
|
|
6482
6439
|
rescue
|
|
6483
6440
|
0 # If timestamp parsing fails, consider them equal
|
|
6484
6441
|
end
|
|
@@ -6488,7 +6445,7 @@ module Heathrow
|
|
|
6488
6445
|
# Sort by timestamp descending (newest first)
|
|
6489
6446
|
@filtered_messages.sort! do |a, b|
|
|
6490
6447
|
begin
|
|
6491
|
-
|
|
6448
|
+
ts_cache[b.object_id] <=> ts_cache[a.object_id]
|
|
6492
6449
|
rescue
|
|
6493
6450
|
0 # If timestamp parsing fails, consider them equal
|
|
6494
6451
|
end
|
|
@@ -6512,8 +6469,7 @@ module Heathrow
|
|
|
6512
6469
|
@panes[:right].ix = 0 # Reset scroll position
|
|
6513
6470
|
help_text = get_help_text # Use the colored version directly
|
|
6514
6471
|
|
|
6515
|
-
|
|
6516
|
-
File.write('/tmp/heathrow_help_debug.txt', help_text) if ENV['DEBUG']
|
|
6472
|
+
|
|
6517
6473
|
|
|
6518
6474
|
# Just set the text and let rcurses handle everything
|
|
6519
6475
|
@panes[:right].text = help_text
|
|
@@ -21,6 +21,8 @@ module Heathrow
|
|
|
21
21
|
@all_start_collapsed = true # Start with everything collapsed
|
|
22
22
|
@section_order = nil # Custom section order (array of names)
|
|
23
23
|
@view_thread_modes = {} # Per-view threading mode: key => {threaded:, folder:}
|
|
24
|
+
@organized_cache = nil
|
|
25
|
+
@organized_cache_key = nil
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
# Toggle between flat and threaded view
|
|
@@ -69,20 +71,10 @@ module Heathrow
|
|
|
69
71
|
def save_view_thread_mode
|
|
70
72
|
return unless @current_view
|
|
71
73
|
@view_thread_modes[@current_view] = { threaded: @show_threaded, folder: @group_by_folder }
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
view[:filters]['view_thread_mode'] = thread_mode_key
|
|
77
|
-
@db.execute("UPDATE views SET filters = ?, updated_at = ? WHERE id = ?",
|
|
78
|
-
[JSON.generate(view[:filters]), Time.now.to_i, view[:id]])
|
|
79
|
-
else
|
|
80
|
-
# Built-in views (A, N): store in settings table
|
|
81
|
-
@db.execute(
|
|
82
|
-
"INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES (?, ?, ?)",
|
|
83
|
-
["thread_mode_#{@current_view}", thread_mode_key, Time.now.to_i]
|
|
84
|
-
)
|
|
85
|
-
end
|
|
74
|
+
@db.execute(
|
|
75
|
+
"INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES (?, ?, ?)",
|
|
76
|
+
["thread_mode_#{@current_view}", thread_mode_key, Time.now.to_i]
|
|
77
|
+
)
|
|
86
78
|
end
|
|
87
79
|
|
|
88
80
|
# Restore threading mode for the active view
|
|
@@ -97,19 +89,12 @@ module Heathrow
|
|
|
97
89
|
return true
|
|
98
90
|
end
|
|
99
91
|
|
|
100
|
-
# Load from
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
unless mode_key
|
|
107
|
-
row = @db.db.get_first_row(
|
|
108
|
-
"SELECT value FROM settings WHERE key = ?",
|
|
109
|
-
["thread_mode_#{@current_view}"]
|
|
110
|
-
)
|
|
111
|
-
mode_key = row && row['value']
|
|
112
|
-
end
|
|
92
|
+
# Load from settings table
|
|
93
|
+
row = @db.db.get_first_row(
|
|
94
|
+
"SELECT value FROM settings WHERE key = ?",
|
|
95
|
+
["thread_mode_#{@current_view}"]
|
|
96
|
+
)
|
|
97
|
+
mode_key = row && row['value']
|
|
113
98
|
return false unless mode_key
|
|
114
99
|
|
|
115
100
|
apply_thread_mode_key(mode_key)
|
|
@@ -161,6 +146,8 @@ module Heathrow
|
|
|
161
146
|
@threading_initialized = false
|
|
162
147
|
@display_messages = []
|
|
163
148
|
@scroll_offset = 0 # Reset scroll position
|
|
149
|
+
@organized_cache = nil
|
|
150
|
+
@organized_cache_key = nil
|
|
164
151
|
|
|
165
152
|
# Preserve or reset collapsed states
|
|
166
153
|
if !preserve_collapsed_state
|
|
@@ -195,17 +182,15 @@ module Heathrow
|
|
|
195
182
|
# Organize messages for current view
|
|
196
183
|
def organize_current_messages(force_reinit = false)
|
|
197
184
|
return unless @show_threaded
|
|
198
|
-
|
|
185
|
+
|
|
199
186
|
# Only organize once per message set - capture the base messages on first run
|
|
200
187
|
# OR if we're forcing reinitialization after a sort change
|
|
201
188
|
if !@threading_initialized || force_reinit
|
|
202
189
|
@base_messages = @filtered_messages.dup
|
|
203
190
|
@threading_initialized = true
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
end if ENV['DEBUG']
|
|
208
|
-
|
|
191
|
+
@organized_cache = nil
|
|
192
|
+
@organized_cache_key = nil
|
|
193
|
+
|
|
209
194
|
require_relative '../message_organizer'
|
|
210
195
|
@organizer = MessageOrganizer.new(@base_messages, @db, group_by_folder: @group_by_folder)
|
|
211
196
|
end
|
|
@@ -305,8 +290,15 @@ module Heathrow
|
|
|
305
290
|
visible_messages = []
|
|
306
291
|
current_index = 0
|
|
307
292
|
@scroll_offset ||= 0 # Track scroll position
|
|
308
|
-
|
|
309
|
-
|
|
293
|
+
|
|
294
|
+
cache_key = "#{@sort_order}|#{@sort_inverted}|#{@filtered_messages.size}"
|
|
295
|
+
if @organized_cache && @organized_cache_key == cache_key
|
|
296
|
+
organized = @organized_cache
|
|
297
|
+
else
|
|
298
|
+
organized = @organizer.get_organized_view(@sort_order, @sort_inverted)
|
|
299
|
+
@organized_cache = organized
|
|
300
|
+
@organized_cache_key = cache_key
|
|
301
|
+
end
|
|
310
302
|
|
|
311
303
|
# Apply custom section order if set
|
|
312
304
|
if @section_order && !@section_order.empty?
|
|
@@ -404,10 +396,6 @@ module Heathrow
|
|
|
404
396
|
end
|
|
405
397
|
end
|
|
406
398
|
|
|
407
|
-
File.open('/tmp/heathrow_debug.log', 'a') do |f|
|
|
408
|
-
f.puts "THREADING: Created #{@display_messages.size} display messages for navigation"
|
|
409
|
-
end if ENV['DEBUG']
|
|
410
|
-
|
|
411
399
|
# Give full text to rcurses and use its scrolling with markers
|
|
412
400
|
@panes[:left].scroll = true
|
|
413
401
|
new_text = lines.join("\n")
|
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.6
|
|
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-21 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rcurses
|