wip
This commit is contained in:
@@ -216,6 +216,8 @@ async def _sync_outlook_data(dry_run, vdir, icsfile, org, days_back, days_forwar
|
||||
task_archive = progress.add_task("[yellow]Archiving mail...", total=0)
|
||||
task_delete = progress.add_task("[red]Deleting mail...", total=0)
|
||||
|
||||
# Stage 1: Synchronize local changes (read, archive, delete) to the server
|
||||
progress.console.print("[bold cyan]Step 1: Syncing local changes to server...[/bold cyan]")
|
||||
await asyncio.gather(
|
||||
synchronize_maildir_async(
|
||||
maildir_path, headers, progress, task_read, dry_run
|
||||
@@ -224,6 +226,12 @@ async def _sync_outlook_data(dry_run, vdir, icsfile, org, days_back, days_forwar
|
||||
progress, task_archive, dry_run),
|
||||
delete_mail_async(maildir_path, headers,
|
||||
progress, task_delete, dry_run),
|
||||
)
|
||||
progress.console.print("[bold green]Step 1: Local changes synced.[/bold green]")
|
||||
|
||||
# Stage 2: Fetch new data from the server
|
||||
progress.console.print("\n[bold cyan]Step 2: Fetching new data from server...[/bold cyan]")
|
||||
await asyncio.gather(
|
||||
fetch_mail_async(
|
||||
maildir_path,
|
||||
attachments_dir,
|
||||
@@ -235,6 +243,7 @@ async def _sync_outlook_data(dry_run, vdir, icsfile, org, days_back, days_forwar
|
||||
),
|
||||
fetch_calendar_async(headers, progress, task_calendar, dry_run, vdir, icsfile, org, days_back, days_forward, continue_iteration),
|
||||
)
|
||||
progress.console.print("[bold green]Step 2: New data fetched.[/bold green]")
|
||||
click.echo("Sync complete.")
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from .widgets.EnvelopeHeader import EnvelopeHeader
|
||||
from .actions.task import action_create_task
|
||||
from .actions.open import action_open
|
||||
from .actions.delete import delete_current
|
||||
from .actions.archive import archive_current
|
||||
from src.services.taskwarrior import client as taskwarrior_client
|
||||
from src.services.himalaya import client as himalaya_client
|
||||
from textual.containers import ScrollableContainer, Vertical, Horizontal
|
||||
@@ -69,6 +68,7 @@ class EmailViewerApp(App):
|
||||
status_title = reactive("Message View")
|
||||
sort_order_ascending: Reactive[bool] = reactive(True)
|
||||
selected_messages: Reactive[set[int]] = reactive(set())
|
||||
main_content_visible: Reactive[bool] = reactive(True)
|
||||
|
||||
def get_system_commands(self, screen: Screen) -> Iterable[SystemCommand]:
|
||||
yield from super().get_system_commands(screen)
|
||||
@@ -110,7 +110,7 @@ class EmailViewerApp(App):
|
||||
Binding("2", "focus_2", "Focus Folders Panel"),
|
||||
Binding("3", "focus_3", "Focus Envelopes Panel"),
|
||||
Binding("4", "focus_4", "Focus Main Content"),
|
||||
Binding("m", "toggle_mode", "Toggle Content Mode"),
|
||||
Binding("w", "toggle_main_content", "Toggle Message View Window"),
|
||||
]
|
||||
|
||||
BINDINGS.extend(
|
||||
@@ -215,35 +215,35 @@ class EmailViewerApp(App):
|
||||
)
|
||||
if new_message_id == old_message_id:
|
||||
return
|
||||
self.msg_worker.cancel() if self.msg_worker else None
|
||||
logging.info(f"new_message_id: {new_message_id}, type: {
|
||||
type(new_message_id)}")
|
||||
|
||||
# If the main content view is not visible, don't load the message
|
||||
if not self.main_content_visible:
|
||||
return
|
||||
|
||||
# Cancel any existing message loading worker
|
||||
if self.msg_worker:
|
||||
self.msg_worker.cancel()
|
||||
|
||||
# Start a new worker to load the message content
|
||||
self.msg_worker = self.load_message_content(new_message_id)
|
||||
|
||||
@work(exclusive=True)
|
||||
async def load_message_content(self, message_id: int) -> None:
|
||||
"""Worker to load message content asynchronously."""
|
||||
content_container = self.query_one(ContentContainer)
|
||||
content_container.display_content(new_message_id)
|
||||
content_container.display_content(message_id)
|
||||
|
||||
metadata = self.message_store.get_metadata(new_message_id)
|
||||
metadata = self.message_store.get_metadata(message_id)
|
||||
if metadata:
|
||||
# Pass the complete date string with timezone information
|
||||
message_date = metadata["date"]
|
||||
|
||||
if self.current_message_index != metadata["index"]:
|
||||
self.current_message_index = metadata["index"]
|
||||
|
||||
# content_container.update_header(
|
||||
# subject=metadata.get("subject", "").strip(),
|
||||
# from_=metadata["from"].get("addr", ""),
|
||||
# to=metadata["to"].get("addr", ""),
|
||||
# date=message_date,
|
||||
# cc=metadata["cc"].get("addr", "") if "cc" in metadata else "",
|
||||
# )
|
||||
|
||||
list_view = self.query_one("#envelopes_list", ListView)
|
||||
if list_view.index != metadata["index"]:
|
||||
list_view.index = metadata["index"]
|
||||
else:
|
||||
logging.warning(
|
||||
f"Message ID {new_message_id} not found in metadata.")
|
||||
logging.warning(f"Message ID {message_id} not found in metadata.")
|
||||
|
||||
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
||||
"""Called when an item in the list view is selected."""
|
||||
@@ -286,9 +286,11 @@ class EmailViewerApp(App):
|
||||
# Use the Himalaya client to fetch envelopes
|
||||
envelopes, success = await himalaya_client.list_envelopes()
|
||||
|
||||
if success and envelopes:
|
||||
if success:
|
||||
self.reload_needed = False
|
||||
self.message_store.load(envelopes, self.sort_order_ascending)
|
||||
# Ensure envelopes is a list, even if it's None from the client
|
||||
envelopes_list = envelopes if envelopes is not None else []
|
||||
self.message_store.load(envelopes_list, self.sort_order_ascending)
|
||||
self.total_messages = self.message_store.total_messages
|
||||
|
||||
# Use the centralized refresh method to update the ListView
|
||||
@@ -530,20 +532,72 @@ class EmailViewerApp(App):
|
||||
await worker.wait()
|
||||
|
||||
async def action_archive(self) -> None:
|
||||
"""Archive the current message and update UI consistently."""
|
||||
"""Archive the current or selected messages and update UI consistently."""
|
||||
next_id_to_select = None
|
||||
|
||||
if self.selected_messages:
|
||||
message_ids = [str(msg_id) for msg_id in self.selected_messages]
|
||||
_, success = await himalaya_client.archive_messages(message_ids)
|
||||
# --- Multi-message archive ---
|
||||
message_ids_to_archive = list(self.selected_messages)
|
||||
|
||||
if message_ids_to_archive:
|
||||
highest_archived_id = max(message_ids_to_archive)
|
||||
metadata = self.message_store.get_metadata(highest_archived_id)
|
||||
if metadata:
|
||||
next_id, _ = self.message_store.find_next_valid_id(metadata["index"])
|
||||
if next_id is None:
|
||||
next_id, _ = self.message_store.find_prev_valid_id(
|
||||
metadata["index"]
|
||||
)
|
||||
next_id_to_select = next_id
|
||||
|
||||
message, success = await himalaya_client.archive_messages(
|
||||
[str(mid) for mid in message_ids_to_archive]
|
||||
)
|
||||
|
||||
if success:
|
||||
self.show_status(f"{len(message_ids)} messages archived.")
|
||||
self.show_status(message)
|
||||
self.selected_messages.clear()
|
||||
self.fetch_envelopes()
|
||||
else:
|
||||
self.show_status("Failed to archive messages.", "error")
|
||||
self.show_status(f"Failed to archive messages: {message}", "error")
|
||||
return
|
||||
|
||||
else:
|
||||
# Call the archive_current function which uses our Himalaya client module
|
||||
worker = archive_current(self)
|
||||
await worker.wait()
|
||||
# --- Single message archive ---
|
||||
if not self.current_message_id:
|
||||
self.show_status("No message selected to archive.", "error")
|
||||
return
|
||||
|
||||
current_id = self.current_message_id
|
||||
current_idx = self.current_message_index
|
||||
|
||||
next_id, _ = self.message_store.find_next_valid_id(current_idx)
|
||||
if next_id is None:
|
||||
next_id, _ = self.message_store.find_prev_valid_id(current_idx)
|
||||
next_id_to_select = next_id
|
||||
|
||||
message, success = await himalaya_client.archive_message(current_id)
|
||||
|
||||
if success:
|
||||
self.show_status(message)
|
||||
else:
|
||||
self.show_status(
|
||||
f"Failed to archive message {current_id}: {message}", "error"
|
||||
)
|
||||
return
|
||||
|
||||
# Refresh the envelope list
|
||||
worker = self.fetch_envelopes()
|
||||
await worker.wait()
|
||||
|
||||
# After refresh, select the next message
|
||||
if next_id_to_select:
|
||||
new_metadata = self.message_store.get_metadata(next_id_to_select)
|
||||
if new_metadata:
|
||||
self.current_message_id = next_id_to_select
|
||||
else:
|
||||
self.action_oldest()
|
||||
else:
|
||||
self.action_oldest()
|
||||
|
||||
def action_open(self) -> None:
|
||||
action_open(self)
|
||||
@@ -567,6 +621,22 @@ class EmailViewerApp(App):
|
||||
"""Scroll the main content up by a page."""
|
||||
self.query_one("#main_content").scroll_page_up()
|
||||
|
||||
def action_toggle_main_content(self) -> None:
|
||||
"""Toggle the visibility of the main content pane."""
|
||||
self.main_content_visible = not self.main_content_visible
|
||||
|
||||
def watch_main_content_visible(self, visible: bool) -> None:
|
||||
"""Called when main_content_visible changes."""
|
||||
main_content = self.query_one("#main_content")
|
||||
accounts_list = self.query_one("#accounts_list")
|
||||
folders_list = self.query_one("#folders_list")
|
||||
|
||||
main_content.display = visible
|
||||
accounts_list.display = visible
|
||||
folders_list.display = visible
|
||||
|
||||
self.query_one("#envelopes_list").focus()
|
||||
|
||||
def action_quit(self) -> None:
|
||||
self.exit()
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from markitdown import MarkItDown
|
||||
from textual import work
|
||||
from textual.binding import Binding
|
||||
from textual.containers import Vertical, ScrollableContainer
|
||||
from textual.widgets import Static, Markdown, Label
|
||||
from src.services.himalaya import client as himalaya_client
|
||||
@@ -57,6 +58,9 @@ class EnvelopeHeader(Vertical):
|
||||
|
||||
class ContentContainer(ScrollableContainer):
|
||||
can_focus = True
|
||||
BINDINGS = [
|
||||
Binding("m", "toggle_mode", "Toggle View Mode")
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -79,7 +83,7 @@ class ContentContainer(ScrollableContainer):
|
||||
self.content.styles.display = "none"
|
||||
self.html_content.styles.display = "block"
|
||||
|
||||
async def toggle_mode(self):
|
||||
async def action_toggle_mode(self):
|
||||
"""Toggle between plaintext and HTML viewing modes."""
|
||||
if self.current_mode == "html":
|
||||
self.current_mode = "text"
|
||||
@@ -91,6 +95,7 @@ class ContentContainer(ScrollableContainer):
|
||||
self.html_content.styles.display = "block"
|
||||
# self.action_notify(f"switched to mode {self.current_mode}")
|
||||
# Reload the content if we have a message ID
|
||||
self.border_sibtitle = self.current_mode;
|
||||
if self.current_message_id:
|
||||
self.display_content(self.current_message_id)
|
||||
|
||||
@@ -119,6 +124,12 @@ class ContentContainer(ScrollableContainer):
|
||||
|
||||
self.current_message_id = message_id
|
||||
|
||||
# Immediately show a loading message
|
||||
if self.current_mode == "text":
|
||||
self.content.update("Loading...")
|
||||
else:
|
||||
self.html_content.update("Loading...")
|
||||
|
||||
# Cancel any existing content fetch operations
|
||||
if self.content_worker:
|
||||
self.content_worker.cancel()
|
||||
|
||||
Reference in New Issue
Block a user