This commit is contained in:
Tim Bendt
2025-05-12 13:10:44 -06:00
parent 2fcad5700d
commit 64146abb4e
2 changed files with 62 additions and 48 deletions

View File

@@ -57,7 +57,9 @@ class EmailViewerApp(App):
folder = reactive("INBOX") folder = reactive("INBOX")
header_expanded = reactive(False) header_expanded = reactive(False)
reload_needed = reactive(True) reload_needed = reactive(True)
all_envelopes = reactive([]) all_envelopes = reactive([]) # Keep as list for compatibility with ListView
envelope_map = {} # Add a dictionary to map IDs to envelope data
envelope_index_map = {} # Map indices in the list to envelope IDs
oldest_id: Reactive[int] = reactive(0) oldest_id: Reactive[int] = reactive(0)
newest_id: Reactive[int] = reactive(0) newest_id: Reactive[int] = reactive(0)
msg_worker: Worker | None = None msg_worker: Worker | None = None
@@ -107,7 +109,7 @@ class EmailViewerApp(App):
Binding("1", "focus_1", "Focus Accounts Panel"), Binding("1", "focus_1", "Focus Accounts Panel"),
Binding("2", "focus_2", "Focus Folders Panel"), Binding("2", "focus_2", "Focus Folders Panel"),
Binding("3", "focus_3", "Focus Envelopes Panel"), Binding("3", "focus_3", "Focus Envelopes Panel"),
Binding("f", "toggle_mode", "Toggle Content Mode"), Binding("m", "toggle_mode", "Toggle Content Mode"),
] ]
BINDINGS.extend( BINDINGS.extend(
@@ -232,7 +234,12 @@ class EmailViewerApp(App):
message_date = datetime.strptime(message_date, "%Y-%m-%d %H:%M").strftime( message_date = datetime.strptime(message_date, "%Y-%m-%d %H:%M").strftime(
"%a %b %d %H:%M" "%a %b %d %H:%M"
) )
self.current_message_index = metadata["index"]
# Only update the current_message_index if it's different from the index in the ListView
# This prevents the sidebar selection from getting out of sync with the displayed content
if self.current_message_index != metadata["index"]:
self.current_message_index = metadata["index"]
content_container.update_header( content_container.update_header(
subject=metadata.get("subject", "").strip(), subject=metadata.get("subject", "").strip(),
from_=metadata["from"].get("addr", ""), from_=metadata["from"].get("addr", ""),
@@ -240,50 +247,29 @@ class EmailViewerApp(App):
date=message_date, date=message_date,
cc=metadata["cc"].get("addr", "") if "cc" in metadata else "", cc=metadata["cc"].get("addr", "") if "cc" in metadata else "",
) )
self.query_one(ListView).index = metadata["index"]
# Make sure the ListView index matches the current message index
list_view = self.query_one("#envelopes_list")
if list_view.index != metadata["index"]:
list_view.index = metadata["index"]
else: else:
logging.warning(f"Message ID {new_message_id} not found in metadata.") logging.warning(f"Message ID {new_message_id} not found in metadata.")
def on_list_view_selected(self, event: ListView.Selected) -> None: def on_list_view_selected(self, event: ListView.Selected) -> None:
"""Called when an item in the list view is selected.""" """Called when an item in the list view is selected."""
# logging.info(f"Selected item: {self.all_envelopes[event.list_view.index]}") current_item = self.all_envelopes[event.list_view.index]
if (
self.all_envelopes[event.list_view.index] is None # Skip if it's a header or None
or self.all_envelopes[event.list_view.index].get("type") == "header" if current_item is None or current_item.get("type") == "header":
):
# If the selected item is a header, do not change the current message ID
return return
self.current_message_id = int(self.all_envelopes[event.list_view.index]["id"])
# @work(exclusive=False) # Get the message ID and update current index in a consistent way
# async def fetch_one_message(self, new_message_id: int) -> None: message_id = int(current_item["id"])
# content_container = self.query_one(ContentContainer) self.current_message_id = message_id
# try: # Update the index directly based on the ListView selection
# process = await asyncio.create_subprocess_shell( # This ensures the sidebar selection and displayed content stay in sync
# f"himalaya message read {str(new_message_id)} -p", self.current_message_index = event.list_view.index
# stdout=asyncio.subprocess.PIPE,
# stderr=asyncio.subprocess.PIPE,
# )
# stdout, stderr = await process.communicate()
# logging.info(f"stdout: {stdout.decode()[0:50]}...")
# if process.returncode == 0:
# # Render the email content as Markdown
# fixedText = stdout.decode().replace("(https://urldefense.com/v3/", "(")
# fixedText = re.sub(r"atlOrigin.+?\)", ")", fixedText)
# logging.info(f"rendering fixedText: {fixedText[0:50]}")
# self.message_body_cache[new_message_id] = fixedText
# await content_container.display_content(new_message_id)
# self.query_one("#main_content").loading = False
# logging.info(fixedText)
# except Exception as e:
# self.show_status(f"Error fetching message content: {e}", "error")
# logging.error(f"Error fetching message content: {e}")
@work(exclusive=False) @work(exclusive=False)
async def fetch_envelopes(self) -> None: async def fetch_envelopes(self) -> None:
@@ -313,6 +299,12 @@ class EmailViewerApp(App):
) )
grouped_envelopes = group_envelopes_by_date(envelopes) grouped_envelopes = group_envelopes_by_date(envelopes)
self.all_envelopes = grouped_envelopes self.all_envelopes = grouped_envelopes
# Update our dictionary mappings
self.envelope_map = {int(envelope["id"]): envelope for envelope in grouped_envelopes if "id" in envelope}
self.envelope_index_map = {index: int(envelope["id"]) for index, envelope in enumerate(grouped_envelopes) if "id" in envelope}
# Store metadata with correct indices
self.message_metadata = { self.message_metadata = {
int(envelope["id"]): { int(envelope["id"]): {
"subject": envelope.get("subject", ""), "subject": envelope.get("subject", ""),
@@ -325,6 +317,8 @@ class EmailViewerApp(App):
for index, envelope in enumerate(self.all_envelopes) for index, envelope in enumerate(self.all_envelopes)
if "id" in envelope if "id" in envelope
} }
# Add items to the ListView
for item in grouped_envelopes: for item in grouped_envelopes:
if item.get("type") == "header": if item.get("type") == "header":
msglist.append( msglist.append(
@@ -431,8 +425,6 @@ class EmailViewerApp(App):
message, title="Status", severity=severity, timeout=2.6, markup=True message, title="Status", severity=severity, timeout=2.6, markup=True
) )
async def action_toggle_sort_order(self) -> None: async def action_toggle_sort_order(self) -> None:
"""Toggle the sort order of the envelope list.""" """Toggle the sort order of the envelope list."""
self.sort_order_ascending = not self.sort_order_ascending self.sort_order_ascending = not self.sort_order_ascending
@@ -485,7 +477,10 @@ class EmailViewerApp(App):
self.fetch_envelopes() if self.reload_needed else None self.fetch_envelopes() if self.reload_needed else None
async def action_delete(self) -> None: async def action_delete(self) -> None:
# Remove from all data structures
self.all_envelopes = [item for item in self.all_envelopes if item and item.get("id") != self.current_message_id] self.all_envelopes = [item for item in self.all_envelopes if item and item.get("id") != self.current_message_id]
self.envelope_map.pop(self.current_message_id, None)
self.envelope_index_map = {index: id for index, id in self.envelope_index_map.items() if id != self.current_message_id}
self.message_metadata = { self.message_metadata = {
k: v for k, v in self.message_metadata.items() if k != self.current_message_id k: v for k, v in self.message_metadata.items() if k != self.current_message_id
} }
@@ -493,16 +488,34 @@ class EmailViewerApp(App):
k: v for k, v in self.message_body_cache.items() if k != self.current_message_id k: v for k, v in self.message_body_cache.items() if k != self.current_message_id
} }
self.total_messages = len(self.message_metadata) self.total_messages = len(self.message_metadata)
# Perform delete operation
delete_current(self) delete_current(self)
newmsg = self.all_envelopes[self.current_message_index]
if newmsg.get("type") == "header": # Get next message to display
newmsg = self.all_envelopes[self.current_message_index + 1] try:
return newmsg = self.all_envelopes[self.current_message_index]
self.show_message(newmsg["id"]) # Skip headers
if newmsg.get("type") == "header":
if self.current_message_index + 1 < len(self.all_envelopes):
newmsg = self.all_envelopes[self.current_message_index + 1]
else:
# If we're at the end, go to the previous message
newmsg = self.all_envelopes[self.current_message_index - 1]
self.current_message_index -= 1
# Show the next message
if "id" in newmsg:
self.show_message(newmsg["id"])
except (IndexError, KeyError):
# If no more messages, just reload envelopes
self.reload_needed = True
self.fetch_envelopes()
async def action_archive(self) -> None: async def action_archive(self) -> None:
# self.query_one("#envelopes_list").pop(self.current_message_index)
self.all_envelopes = [item for item in self.all_envelopes if item and item.get("id") != self.current_message_id] self.all_envelopes = [item for item in self.all_envelopes if item and item.get("id") != self.current_message_id]
self.envelope_map.pop(self.current_message_id, None)
self.envelope_index_map = {index: id for index, id in self.envelope_index_map.items() if id != self.current_message_id}
self.message_metadata = { self.message_metadata = {
k: v for k, v in self.message_metadata.items() if k != self.current_message_id k: v for k, v in self.message_metadata.items() if k != self.current_message_id
} }

View File

@@ -13,6 +13,7 @@ from widgets.EnvelopeHeader import EnvelopeHeader
class ContentContainer(ScrollableContainer): class ContentContainer(ScrollableContainer):
"""A custom container that can switch between plaintext and markdown rendering.""" """A custom container that can switch between plaintext and markdown rendering."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.plaintext_mode = True self.plaintext_mode = True
@@ -26,7 +27,7 @@ class ContentContainer(ScrollableContainer):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
"""Compose the container with a label for plaintext and markdown for rich content.""" """Compose the container with a label for plaintext and markdown for rich content."""
yield EnvelopeHeader() yield EnvelopeHeader()
yield Label(id="plaintext_content") yield Label(id="plaintext_content", markup=False)
yield Markdown(id="markdown_content", classes="hidden") yield Markdown(id="markdown_content", classes="hidden")
def update_header(self, subject: str = "", date: str = "", from_: str = "", to: str = "", cc: str = "", bcc: str = "") -> None: def update_header(self, subject: str = "", date: str = "", from_: str = "", to: str = "", cc: str = "", bcc: str = "") -> None: