fix link shortcut and mark as read
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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 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 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 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."""
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user