fix link shortcut and mark as read

This commit is contained in:
Bendt
2025-12-18 13:53:55 -05:00
parent 4a21eef6f8
commit 37be42884f
4 changed files with 127 additions and 7 deletions

View File

@@ -244,9 +244,42 @@ class EmailViewerApp(App):
list_view = self.query_one("#envelopes_list", ListView)
if list_view.index != metadata["index"]:
list_view.index = metadata["index"]
# Mark message as read
await self._mark_message_as_read(message_id, metadata["index"])
else:
logging.warning(f"Message ID {message_id} not found in metadata.")
async def _mark_message_as_read(self, message_id: int, index: int) -> None:
"""Mark a message as read and update the UI."""
# Check if already read
envelope_data = self.message_store.envelopes[index]
if envelope_data and envelope_data.get("type") != "header":
flags = envelope_data.get("flags", [])
if "Seen" in flags:
return # Already read
# Mark as read via himalaya
_, success = await himalaya_client.mark_as_read(message_id)
if success:
# Update the envelope flags in the store
if envelope_data:
if "flags" not in envelope_data:
envelope_data["flags"] = []
if "Seen" not in envelope_data["flags"]:
envelope_data["flags"].append("Seen")
# Update the visual state of the list item
try:
list_view = self.query_one("#envelopes_list", ListView)
list_item = list_view.children[index]
envelope_widget = list_item.query_one(EnvelopeListItem)
envelope_widget.is_read = True
envelope_widget.remove_class("unread")
except Exception:
pass # Widget may not exist
def on_list_view_selected(self, event: ListView.Selected) -> None:
"""Called when an item in the list view is selected."""
if event.list_view.index is None:

View File

@@ -411,6 +411,10 @@ class LinkPanel(ModalScreen):
self._mnemonic_map: dict[str, LinkItem] = {
link.mnemonic: link for link in links if link.mnemonic
}
self._key_buffer: str = ""
self._key_timer = None
# Check if we have any multi-char mnemonics
self._has_multi_char = any(len(m) > 1 for m in self._mnemonic_map.keys())
def compose(self) -> ComposeResult:
with Container(id="link-panel-container"):
@@ -436,18 +440,68 @@ class LinkPanel(ModalScreen):
self.query_one("#link-list").focus()
def on_key(self, event) -> None:
"""Handle mnemonic key presses."""
"""Handle mnemonic key presses with buffering for multi-char mnemonics."""
key = event.key.lower()
# Check for single-char mnemonic
if key in self._mnemonic_map:
self._open_link(self._mnemonic_map[key])
# Only buffer alphabetic keys
if not key.isalpha() or len(key) != 1:
return
# Cancel any pending timer
if self._key_timer:
self._key_timer.stop()
self._key_timer = None
# Add key to buffer
self._key_buffer += key
# Check for exact match with buffered keys
if self._key_buffer in self._mnemonic_map:
# If no multi-char mnemonics exist, open immediately
if not self._has_multi_char:
self._open_link(self._mnemonic_map[self._key_buffer])
self._key_buffer = ""
event.prevent_default()
return
# Check if any longer mnemonic starts with our buffer
has_longer_match = any(
m.startswith(self._key_buffer) and len(m) > len(self._key_buffer)
for m in self._mnemonic_map.keys()
)
if has_longer_match:
# Wait for possible additional keys
self._key_timer = self.set_timer(0.4, self._flush_key_buffer)
else:
# No longer matches possible, open immediately
self._open_link(self._mnemonic_map[self._key_buffer])
self._key_buffer = ""
event.prevent_default()
return
# Check for two-char mnemonics (accumulate?)
# For simplicity, we'll just support single-char for now
# A more sophisticated approach would use a timeout buffer
# Check if buffer could still match something
could_match = any(
m.startswith(self._key_buffer) for m in self._mnemonic_map.keys()
)
if could_match:
# Wait for more keys
self._key_timer = self.set_timer(0.4, self._flush_key_buffer)
event.prevent_default()
else:
# No possible match, clear buffer
self._key_buffer = ""
def _flush_key_buffer(self) -> None:
"""Called after timeout to process buffered keys."""
self._key_timer = None
if self._key_buffer and self._key_buffer in self._mnemonic_map:
self._open_link(self._mnemonic_map[self._key_buffer])
self._key_buffer = ""
def action_open_selected(self) -> None:
"""Open the currently selected link."""

View File

@@ -216,6 +216,39 @@ async def get_message_content(message_id: int) -> Tuple[Optional[str], bool]:
return None, False
async def mark_as_read(message_id: int) -> Tuple[Optional[str], bool]:
"""
Mark a message as read by adding the 'seen' flag.
Args:
message_id: The ID of the message to mark as read
Returns:
Tuple containing:
- Result message or error
- Success status (True if operation was successful)
"""
try:
cmd = f"himalaya flag add seen {message_id}"
process = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
if process.returncode == 0:
return stdout.decode().strip() or "Marked as read", True
else:
error_msg = stderr.decode().strip()
logging.error(f"Error marking message as read: {error_msg}")
return error_msg or "Unknown error", False
except Exception as e:
logging.error(f"Exception during marking message as read: {e}")
return str(e), False
def sync_himalaya():
"""This command does not exist. Halucinated by AI."""
try: