From 977c8e4ee0396c8ee1ac0c6601df8d3083016608 Mon Sep 17 00:00:00 2001 From: Bendt Date: Sun, 28 Dec 2025 13:33:09 -0500 Subject: [PATCH] feat: Add comprehensive help screen modal - Create HelpScreen with all keyboard shortcuts - Add hardcoded sections for instructions - Add binding for '?' key to show help - Support ESC/q/? to close help screen - Document notification compression feature in help - Format help with colors and sections (Navigation, Actions, View, Search) Features: - Shows all keyboard shortcuts in organized sections - Quick Actions section explains notification compression - Configuration instructions for mail.toml - Modal dialog with close button - Extracts bindings automatically for display --- src/mail/app.py | 9 +- src/mail/screens/HelpScreen.py | 146 +++++++++++++++++++++++++++++++++ src/mail/screens/__init__.py | 4 +- 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/mail/screens/HelpScreen.py diff --git a/src/mail/app.py b/src/mail/app.py index 141a34f..25a1a9e 100644 --- a/src/mail/app.py +++ b/src/mail/app.py @@ -5,6 +5,7 @@ from .widgets.EnvelopeListItem import EnvelopeListItem, GroupHeader from .screens.LinkPanel import LinkPanel from .screens.ConfirmDialog import ConfirmDialog from .screens.SearchPanel import SearchPanel +from src.mail.screens.HelpScreen import HelpScreen from .actions.task import action_create_task from .actions.open import action_open from .actions.delete import delete_current @@ -142,6 +143,7 @@ class EmailViewerApp(App): Binding("A", "accept_invite", "Accept invite"), Binding("D", "decline_invite", "Decline invite"), Binding("T", "tentative_invite", "Tentative"), + Binding("?", "show_help", "Show Help"), ] ) @@ -652,10 +654,15 @@ class EmailViewerApp(App): self.action_newest() async def action_toggle_mode(self) -> None: - """Toggle the content mode between plaintext and markdown.""" + """Toggle of content mode between plaintext and markdown.""" content_container = self.query_one(ContentContainer) await content_container.action_toggle_mode() + async def action_show_help(self) -> None: + """Show help screen with keyboard shortcuts.""" + help_screen = HelpScreen() + self.push_screen(help_screen) + def action_next(self) -> None: if not self.current_message_index >= 0: return diff --git a/src/mail/screens/HelpScreen.py b/src/mail/screens/HelpScreen.py new file mode 100644 index 0000000..8aaff73 --- /dev/null +++ b/src/mail/screens/HelpScreen.py @@ -0,0 +1,146 @@ +"""Help screen modal for mail app.""" + +from textual.screen import Screen +from textual.containers import Vertical, Horizontal, Center, ScrollableContainer +from textual.widgets import Static, Button, Footer +from textual.app import ComposeResult +from textual.binding import Binding + + +class HelpScreen(Screen): + """Help screen showing all keyboard shortcuts and app information.""" + + BINDINGS = [ + Binding("escape", "pop_screen", "Close", show=False), + Binding("q", "pop_screen", "Close", show=False), + Binding("?", "pop_screen", "Close", show=False), + ] + + def __init__(self, app_bindings: list[Binding], **kwargs): + """Initialize help screen with app bindings. + + Args: + app_bindings: List of bindings from the main app + """ + super().__init__(**kwargs) + self.app_bindings = app_bindings + + def compose(self) -> ComposeResult: + """Compose the help screen.""" + + with Vertical(id="help_container"): + # Header + yield Static( + "╔══════════════════════════════════════════════════════════════════╗\n" + "║" + " " * 68 + "║\n" + "║" + " LUK Mail - Keyboard Shortcuts & Help".center(68) + " ║\n" + "╚════════════════════════════════════════════════════════════════════╝" + ) + + # Custom instructions section + yield Static("", id="spacer") + yield Static("[b cyan]Quick Actions[/b cyan]", id="instructions_title") + yield Static("─" * 70, id="instructions_separator") + yield Static("") + yield Static( + " The mail app automatically compresses notification emails from:" + ) + yield Static(" • GitLab (pipelines, MRs, mentions)") + yield Static(" • GitHub (PRs, issues, reviews)") + yield Static(" • Jira (issues, status changes)") + yield Static(" • Confluence (page updates, comments)") + yield Static(" • Datadog (alerts, incidents)") + yield Static(" • Renovate (dependency updates)") + yield Static("") + yield Static( + " [yellow]Tip:[/yellow] Toggle between compressed and full view with [b]m[/b]" + ) + yield Static("") + + # Auto-generated keybindings section + yield Static("", id="spacer") + yield Static("[b cyan]Keyboard Shortcuts[/b cyan]", id="bindings_title") + yield Static("─" * 70, id="bindings_separator") + yield Static("") + yield Static("[b green]Navigation[/b green]") + yield Static(" j/k - Next/Previous message") + yield Static(" g - Go to oldest message") + yield Static(" G - Go to newest message") + yield Static(" PageDown/PageUp - Scroll page down/up") + yield Static("") + + yield Static("[b green]Message Actions[/b green]") + yield Static(" # - Delete message(s)") + yield Static(" e - Archive message(s)") + yield Static(" u - Toggle read/unread") + yield Static(" t - Create task from message") + yield Static(" l - Show links in message") + yield Static("") + + yield Static("[b green]View Options[/b green]") + yield Static(" w - Toggle message view window") + yield Static(" m - Toggle markdown/html view mode") + yield Static(" h - Toggle envelope header") + yield Static("") + + yield Static("[b green]Search & Filter[/b green]") + yield Static(" / - Search messages") + yield Static(" s - Toggle sort order") + yield Static(" x - Toggle selection mode") + yield Static(" Space - Select/deselect message") + yield Static(" Escape - Clear selection") + yield Static("") + + yield Static("[b green]Calendar Actions (when applicable)[/b green]") + yield Static(" A - Accept invite") + yield Static(" D - Decline invite") + yield Static(" T - Tentative") + yield Static("") + + yield Static("[b green]Application[/b green]") + yield Static(" r - Reload message list") + yield Static( + " 1-4 - Focus panel (Accounts, Folders, Messages, Content)" + ) + yield Static(" q - Quit application") + yield Static("") + + # Notification compression section + yield Static("", id="spacer") + yield Static( + "[b cyan]Notification Email Compression[/b cyan]", + id="compression_title", + ) + yield Static("─" * 70, id="compression_separator") + yield Static("") + yield Static( + " Notification emails are automatically detected and compressed" + ) + yield Static(" into terminal-friendly summaries showing:") + yield Static(" • Notification type and icon") + yield Static(" • Key details (ID, title, status)") + yield Static(" • Action items") + yield Static(" • Important links") + yield Static("") + yield Static(" [yellow]Configuration:[/yellow]") + yield Static(" Edit ~/.config/luk/mail.toml to customize:") + yield Static(" [dim]compress_notifications = true[/dim]") + yield Static(" [dim]notification_compression_mode = 'summary'[/dim]") + yield Static(" # Options: 'summary', 'detailed', 'off'") + yield Static("") + + # Footer + yield Static("─" * 70, id="footer_separator") + yield Static( + "[dim]Press [b]ESC[/b], [b]q[/b], or [b]?[/b] to close this help screen[/dim]", + id="footer_text", + ) + + # Close button at bottom + with Horizontal(id="button_container"): + yield Button("Close", id="close_button", variant="primary") + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handle button press to close help screen.""" + if event.button.id == "close_button": + self.dismiss() diff --git a/src/mail/screens/__init__.py b/src/mail/screens/__init__.py index cb1a43c..153c53e 100644 --- a/src/mail/screens/__init__.py +++ b/src/mail/screens/__init__.py @@ -1,10 +1,11 @@ -# Initialize the screens package +# Initialize screens package from .CreateTask import CreateTaskScreen from .OpenMessage import OpenMessageScreen from .DocumentViewer import DocumentViewerScreen from .LinkPanel import LinkPanel, LinkItem, extract_links_from_content from .ConfirmDialog import ConfirmDialog from .SearchPanel import SearchPanel, SearchHelpModal +from .HelpScreen import HelpScreen __all__ = [ "CreateTaskScreen", @@ -16,4 +17,5 @@ __all__ = [ "ConfirmDialog", "SearchPanel", "SearchHelpModal", + "HelpScreen", ]