big improvements and bugs

This commit is contained in:
Bendt
2026-01-07 13:08:15 -05:00
parent 86297ae350
commit 0ca266ce1c
6 changed files with 132 additions and 36 deletions

BIN
.coverage

Binary file not shown.

View File

@@ -183,11 +183,30 @@ EnvelopeListItem.unread .email-subject {
text-style: bold;
}
/* Selected message styling */
/* Selected/checked message styling (for multi-select) */
EnvelopeListItem.selected {
tint: $accent 20%;
}
/* Currently highlighted/focused item styling - more prominent */
EnvelopeListItem.highlighted {
background: $primary-darken-1;
}
EnvelopeListItem.highlighted .sender-name {
color: $text;
text-style: bold;
}
EnvelopeListItem.highlighted .email-subject {
color: $text;
text-style: bold;
}
EnvelopeListItem.highlighted .message-datetime {
color: $secondary-lighten-2;
}
/* GroupHeader - date group separator */
GroupHeader {
height: 1;
@@ -254,12 +273,30 @@ GroupHeader .group-header-label {
background: $surface-darken-1;
}
& > ListItem {
&.-highlight, .selection {
color: $block-cursor-blurred-foreground;
background: $block-cursor-blurred-background;
text-style: $block-cursor-blurred-text-style;
/* Currently highlighted/focused item - make it very visible */
& > ListItem.-highlight {
background: $primary-darken-2;
color: $text;
text-style: bold;
}
/* Highlighted item's child elements */
& > ListItem.-highlight EnvelopeListItem {
tint: $primary 30%;
}
& > ListItem.-highlight .sender-name {
color: $text;
text-style: bold;
}
& > ListItem.-highlight .email-subject {
color: $text;
text-style: bold;
}
& > ListItem.-highlight .message-datetime {
color: $secondary-lighten-2;
}
}

View File

@@ -126,7 +126,7 @@ class NotificationCompressor:
lines.append("")
lines.append(f"*From: {from_addr}*")
lines.append(
"*This is a compressed notification view. Press `m` to toggle full view.*"
"*This is a compressed notification. Press `m` to see full email.*"
)
return "\n".join(lines)

View File

@@ -309,6 +309,7 @@ class ContentContainer(Vertical):
self.content = Markdown("", id="markdown_content")
self.html_content = Static("", id="html_content", markup=False)
self.current_content = None
self.current_raw_content = None # Store original uncompressed content
self.current_message_id = None
self.current_folder: str | None = None
self.current_account: str | None = None
@@ -316,6 +317,7 @@ class ContentContainer(Vertical):
self.current_envelope: Optional[Dict[str, Any]] = None
self.current_notification_type: Optional[NotificationType] = None
self.is_compressed_view: bool = False
self.compression_enabled: bool = True # Toggle for compression on/off
# Calendar invite state
self.calendar_panel: Optional[CalendarInvitePanel] = None
@@ -355,26 +357,59 @@ class ContentContainer(Vertical):
def _update_mode_indicator(self) -> None:
"""Update the border subtitle to show current mode."""
mode_label = "Markdown" if self.current_mode == "markdown" else "HTML/Text"
mode_icon = (
"\ue73e" if self.current_mode == "markdown" else "\uf121"
) # nf-md-language_markdown / nf-fa-code
if self.current_mode == "markdown":
if self.is_compressed_view:
mode_label = "Compressed"
mode_icon = "\uf066" # nf-fa-compress
else:
mode_label = "Markdown"
mode_icon = "\ue73e" # nf-md-language_markdown
else:
mode_label = "HTML/Text"
mode_icon = "\uf121" # nf-fa-code
self.border_subtitle = f"{mode_icon} {mode_label}"
async def action_toggle_mode(self):
"""Toggle between markdown/compressed and HTML viewing modes.
"""Toggle between viewing modes.
For notification emails: cycles between compressed view and HTML.
For notification emails: cycles compressed → full markdown → HTML → compressed
For regular emails: cycles between markdown and HTML.
"""
# Check if this is a compressible notification email
is_notification = (
self.compressor.mode != "off"
and self.current_envelope
and self.compressor.should_compress(self.current_envelope)
)
if is_notification:
# Three-state cycle for notifications: compressed → full → html → compressed
if self.current_mode == "markdown" and self.compression_enabled:
# Currently compressed markdown → show full markdown
self.compression_enabled = False
# Don't change mode, just re-display with compression off
elif self.current_mode == "markdown" and not self.compression_enabled:
# Currently full markdown → switch to HTML
self.current_mode = "html"
else:
# Currently HTML → back to compressed markdown
self.current_mode = "markdown"
self.compression_enabled = True
else:
# Simple two-state toggle for regular emails
if self.current_mode == "html":
self.current_mode = "markdown"
else:
self.current_mode = "html"
# Reload the content if we have a message ID
# This will re-apply compression if applicable for markdown mode
if self.current_message_id:
# Re-display content with new mode/compression settings
if self.current_raw_content is not None:
# Use cached raw content instead of re-fetching
self._update_content(self.current_raw_content)
self._apply_view_mode()
self._update_mode_indicator()
elif self.current_message_id:
# Fall back to re-fetching if no cached content
self.display_content(
self.current_message_id,
folder=self.current_folder,
@@ -507,6 +542,10 @@ class ContentContainer(Vertical):
self.current_account = account
self.current_envelope = envelope
# Reset compression state for new message (start with compression enabled)
self.compression_enabled = True
self.current_raw_content = None
# Update the header with envelope data
if envelope:
subject = envelope.get("subject", "")
@@ -780,17 +819,27 @@ class ContentContainer(Vertical):
# Strip headers from content (they're shown in EnvelopeHeader)
content = self._strip_headers_from_content(content)
# Store the raw content for link extraction
# Store the raw content for link extraction and for toggle mode
self.current_content = content
self.current_raw_content = content # Keep original for mode toggling
# Apply notification compression if enabled
if self.compressor.mode != "off" and self.current_envelope:
# Apply notification compression if enabled AND compression toggle is on
display_content = content
if (
self.compressor.mode != "off"
and self.current_envelope
and self.compression_enabled
):
compressed_content, notif_type = self.compressor.compress(
content, self.current_envelope
)
self.current_notification_type = notif_type
content = compressed_content
if notif_type is not None:
# Only use compressed content if compression was actually applied
display_content = compressed_content
self.is_compressed_view = True
else:
self.is_compressed_view = False
else:
self.current_notification_type = None
self.is_compressed_view = False
@@ -803,11 +852,13 @@ class ContentContainer(Vertical):
try:
if self.current_mode == "markdown":
# For markdown mode, use the Markdown widget
display_content = content
final_content = display_content
if compress_urls and not self.is_compressed_view:
# Don't compress URLs in notification summaries (they're already formatted)
display_content = compress_urls_in_content(content, max_url_len)
self.content.update(display_content)
final_content = compress_urls_in_content(
display_content, max_url_len
)
self.content.update(final_content)
else:
# For HTML mode, use the Static widget with markup
# First, try to extract the body content if it's HTML

View File

@@ -193,7 +193,12 @@ class DstaskClient(TaskBackend):
due: Optional[datetime] = None,
notes: Optional[str] = None,
) -> Task:
"""Create a new task."""
"""Create a new task.
Notes are added using dstask's / syntax during creation, where
everything after / becomes the note content. Each word must be
a separate argument for this to work.
"""
args = ["add", summary]
if project:
@@ -210,6 +215,13 @@ class DstaskClient(TaskBackend):
# dstask uses various date formats
args.append(f"due:{due.strftime('%Y-%m-%d')}")
# Add notes using / syntax - each word must be a separate argument
# dstask interprets everything after "/" as note content
if notes:
args.append("/")
# Split notes into words to pass as separate arguments
args.extend(notes.split())
result = self._run_command(args)
if result.returncode != 0:
@@ -221,10 +233,6 @@ class DstaskClient(TaskBackend):
# Find task by summary (best effort)
for task in reversed(tasks):
if task.summary == summary:
# Add notes if provided
if notes:
self._run_command(["note", str(task.id), notes])
task.notes = notes
return task
# Return a placeholder if we can't find it

View File

@@ -645,8 +645,8 @@ class TasksApp(App):
from .screens.AddTaskScreen import AddTaskScreen
from .widgets.AddTaskForm import TaskFormData
# Get project names for dropdown
project_names = [p.name for p in self.projects if p.name]
# Get project names for dropdown (use all_projects which is populated on mount)
project_names = [p.name for p in self.all_projects if p.name]
def handle_task_created(data: TaskFormData | None) -> None:
if data is None or not self.backend: