155 lines
5.7 KiB
Python
155 lines
5.7 KiB
Python
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'<body[^>]*>(.*?)</body>', 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>', '[b]').replace('</b>', '[/b]')
|
|
content = content.replace('<i>', '[i]').replace('</i>', '[/i]')
|
|
content = content.replace('<u>', '[u]').replace('</u>', '[/u]')
|
|
|
|
# Convert links to a readable format
|
|
content = re.sub(r'<a href="([^"]+)"[^>]*>([^<]+)</a>', 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}")
|
|
|
|
|