import asyncio import logging from datetime import datetime import re from textual.widgets import Static, Markdown, Label from textual.containers import Vertical, Horizontal, ScrollableContainer from textual import work from textual.worker import Worker from apis.himalaya import client as himalaya_client class EnvelopeHeader(Vertical): def __init__(self, **kwargs): super().__init__(**kwargs) self.subject_label = Label("") self.from_label = Label("") self.to_label = Label("") self.date_label = Label("") self.cc_label = Label("") def on_mount(self): self.styles.height = "auto" self.mount(self.subject_label) self.mount(self.from_label) self.mount(self.to_label) self.mount(self.cc_label) self.mount(self.date_label) def update(self, subject, from_, to, date, cc=None): self.subject_label.update(f"[b]Subject:[/b] {subject}") self.from_label.update(f"[b]From:[/b] {from_}") self.to_label.update(f"[b]To:[/b] {to}") # Format the date for better readability if date: try: # Try to convert the date string to a datetime object date_obj = datetime.fromisoformat(date.replace('Z', '+00:00')) formatted_date = date_obj.strftime("%a, %d %b %Y %H:%M:%S %Z") self.date_label.update(f"[b]Date:[/b] {formatted_date}") except (ValueError, TypeError): # If parsing fails, just use the original date string self.date_label.update(f"[b]Date:[/b] {date}") else: self.date_label.update("[b]Date:[/b] Unknown") if cc: self.cc_label.update(f"[b]CC:[/b] {cc}") self.cc_label.styles.display = "block" else: self.cc_label.styles.display = "none" class ContentContainer(ScrollableContainer): can_focus = True def __init__(self, **kwargs): super().__init__(**kwargs) # self.header = EnvelopeHeader(id="envelope_header") self.content = Markdown("", id="markdown_content") self.html_content = Static("", id="html_content", markup=False) self.current_mode = "html" # Default to HTML mode self.current_content = None self.current_message_id = None self.content_worker = None def compose(self): yield self.content yield self.html_content def on_mount(self): # Hide markdown content initially self.content.styles.display = "none" self.html_content.styles.display = "block" async def toggle_mode(self): """Toggle between plaintext and HTML viewing modes.""" if self.current_mode == "html": self.current_mode = "text" self.html_content.styles.display = "none" self.content.styles.display = "block" else: self.current_mode = "html" self.content.styles.display = "none" self.html_content.styles.display = "block" # Reload the content if we have a message ID if self.current_message_id: self.display_content(self.current_message_id) # def update_header(self, subject, from_, to, date, cc=None): # self.header.update(subject, from_, to, date, cc) @work(exclusive=True) async def fetch_message_content(self, message_id: int, format: str): """Fetch message content using the Himalaya client module.""" if not message_id: self.notify("No message ID provided.") return content, success = await himalaya_client.get_message_content(message_id, format) if success: self._update_content(content) else: self.notify(f"Failed to fetch content for message ID {message_id}.") def display_content(self, message_id: int) -> None: """Display the content of a message.""" if not message_id: return self.current_message_id = message_id # Cancel any existing content fetch operations if self.content_worker: self.content_worker.cancel() # Fetch content in the current mode format_type = "text" if self.current_mode == "text" else "html" self.content_worker = self.fetch_message_content(message_id, format_type) def _update_content(self, content: str) -> None: """Update the content widgets with the fetched content.""" try: if self.current_mode == "text": # For text mode, use the Markdown widget self.content.update(content) else: # For HTML mode, use the Static widget with markup # First, try to extract the body content if it's HTML body_match = re.search(r']*>(.*?)', content, re.DOTALL | re.IGNORECASE) if body_match: content = body_match.group(1) # Replace some common HTML elements with Textual markup content = content.replace('', '[b]').replace('', '[/b]') content = content.replace('', '[i]').replace('', '[/i]') content = content.replace('', '[u]').replace('', '[/u]') # Convert links to a readable format content = re.sub(r']*>([^<]+)', r'[\2](\1)', content) # Add CSS for better readability self.html_content.update(content) except Exception as e: logging.error(f"Error updating content: {e}") if self.current_mode == "text": self.content.update(f"Error displaying content: {e}") else: self.html_content.update(f"Error displaying content: {e}")