big improvements and bugs
This commit is contained in:
@@ -183,11 +183,30 @@ EnvelopeListItem.unread .email-subject {
|
|||||||
text-style: bold;
|
text-style: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Selected message styling */
|
/* Selected/checked message styling (for multi-select) */
|
||||||
EnvelopeListItem.selected {
|
EnvelopeListItem.selected {
|
||||||
tint: $accent 20%;
|
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 - date group separator */
|
||||||
GroupHeader {
|
GroupHeader {
|
||||||
height: 1;
|
height: 1;
|
||||||
@@ -254,12 +273,30 @@ GroupHeader .group-header-label {
|
|||||||
background: $surface-darken-1;
|
background: $surface-darken-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > ListItem {
|
/* Currently highlighted/focused item - make it very visible */
|
||||||
&.-highlight, .selection {
|
& > ListItem.-highlight {
|
||||||
color: $block-cursor-blurred-foreground;
|
background: $primary-darken-2;
|
||||||
background: $block-cursor-blurred-background;
|
color: $text;
|
||||||
text-style: $block-cursor-blurred-text-style;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class NotificationCompressor:
|
|||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append(f"*From: {from_addr}*")
|
lines.append(f"*From: {from_addr}*")
|
||||||
lines.append(
|
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)
|
return "\n".join(lines)
|
||||||
|
|||||||
@@ -309,6 +309,7 @@ class ContentContainer(Vertical):
|
|||||||
self.content = Markdown("", id="markdown_content")
|
self.content = Markdown("", id="markdown_content")
|
||||||
self.html_content = Static("", id="html_content", markup=False)
|
self.html_content = Static("", id="html_content", markup=False)
|
||||||
self.current_content = None
|
self.current_content = None
|
||||||
|
self.current_raw_content = None # Store original uncompressed content
|
||||||
self.current_message_id = None
|
self.current_message_id = None
|
||||||
self.current_folder: str | None = None
|
self.current_folder: str | None = None
|
||||||
self.current_account: 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_envelope: Optional[Dict[str, Any]] = None
|
||||||
self.current_notification_type: Optional[NotificationType] = None
|
self.current_notification_type: Optional[NotificationType] = None
|
||||||
self.is_compressed_view: bool = False
|
self.is_compressed_view: bool = False
|
||||||
|
self.compression_enabled: bool = True # Toggle for compression on/off
|
||||||
|
|
||||||
# Calendar invite state
|
# Calendar invite state
|
||||||
self.calendar_panel: Optional[CalendarInvitePanel] = None
|
self.calendar_panel: Optional[CalendarInvitePanel] = None
|
||||||
@@ -355,26 +357,59 @@ class ContentContainer(Vertical):
|
|||||||
|
|
||||||
def _update_mode_indicator(self) -> None:
|
def _update_mode_indicator(self) -> None:
|
||||||
"""Update the border subtitle to show current mode."""
|
"""Update the border subtitle to show current mode."""
|
||||||
mode_label = "Markdown" if self.current_mode == "markdown" else "HTML/Text"
|
if self.current_mode == "markdown":
|
||||||
mode_icon = (
|
if self.is_compressed_view:
|
||||||
"\ue73e" if self.current_mode == "markdown" else "\uf121"
|
mode_label = "Compressed"
|
||||||
) # nf-md-language_markdown / nf-fa-code
|
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}"
|
self.border_subtitle = f"{mode_icon} {mode_label}"
|
||||||
|
|
||||||
async def action_toggle_mode(self):
|
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.
|
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":
|
if self.current_mode == "html":
|
||||||
self.current_mode = "markdown"
|
self.current_mode = "markdown"
|
||||||
else:
|
else:
|
||||||
self.current_mode = "html"
|
self.current_mode = "html"
|
||||||
|
|
||||||
# Reload the content if we have a message ID
|
# Re-display content with new mode/compression settings
|
||||||
# This will re-apply compression if applicable for markdown mode
|
if self.current_raw_content is not None:
|
||||||
if self.current_message_id:
|
# 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.display_content(
|
||||||
self.current_message_id,
|
self.current_message_id,
|
||||||
folder=self.current_folder,
|
folder=self.current_folder,
|
||||||
@@ -507,6 +542,10 @@ class ContentContainer(Vertical):
|
|||||||
self.current_account = account
|
self.current_account = account
|
||||||
self.current_envelope = envelope
|
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
|
# Update the header with envelope data
|
||||||
if envelope:
|
if envelope:
|
||||||
subject = envelope.get("subject", "")
|
subject = envelope.get("subject", "")
|
||||||
@@ -780,17 +819,27 @@ class ContentContainer(Vertical):
|
|||||||
# Strip headers from content (they're shown in EnvelopeHeader)
|
# Strip headers from content (they're shown in EnvelopeHeader)
|
||||||
content = self._strip_headers_from_content(content)
|
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_content = content
|
||||||
|
self.current_raw_content = content # Keep original for mode toggling
|
||||||
|
|
||||||
# Apply notification compression if enabled
|
# Apply notification compression if enabled AND compression toggle is on
|
||||||
if self.compressor.mode != "off" and self.current_envelope:
|
display_content = content
|
||||||
|
if (
|
||||||
|
self.compressor.mode != "off"
|
||||||
|
and self.current_envelope
|
||||||
|
and self.compression_enabled
|
||||||
|
):
|
||||||
compressed_content, notif_type = self.compressor.compress(
|
compressed_content, notif_type = self.compressor.compress(
|
||||||
content, self.current_envelope
|
content, self.current_envelope
|
||||||
)
|
)
|
||||||
self.current_notification_type = notif_type
|
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
|
self.is_compressed_view = True
|
||||||
|
else:
|
||||||
|
self.is_compressed_view = False
|
||||||
else:
|
else:
|
||||||
self.current_notification_type = None
|
self.current_notification_type = None
|
||||||
self.is_compressed_view = False
|
self.is_compressed_view = False
|
||||||
@@ -803,11 +852,13 @@ class ContentContainer(Vertical):
|
|||||||
try:
|
try:
|
||||||
if self.current_mode == "markdown":
|
if self.current_mode == "markdown":
|
||||||
# For markdown mode, use the Markdown widget
|
# For markdown mode, use the Markdown widget
|
||||||
display_content = content
|
final_content = display_content
|
||||||
if compress_urls and not self.is_compressed_view:
|
if compress_urls and not self.is_compressed_view:
|
||||||
# Don't compress URLs in notification summaries (they're already formatted)
|
# Don't compress URLs in notification summaries (they're already formatted)
|
||||||
display_content = compress_urls_in_content(content, max_url_len)
|
final_content = compress_urls_in_content(
|
||||||
self.content.update(display_content)
|
display_content, max_url_len
|
||||||
|
)
|
||||||
|
self.content.update(final_content)
|
||||||
else:
|
else:
|
||||||
# For HTML mode, use the Static widget with markup
|
# For HTML mode, use the Static widget with markup
|
||||||
# First, try to extract the body content if it's HTML
|
# First, try to extract the body content if it's HTML
|
||||||
|
|||||||
@@ -193,7 +193,12 @@ class DstaskClient(TaskBackend):
|
|||||||
due: Optional[datetime] = None,
|
due: Optional[datetime] = None,
|
||||||
notes: Optional[str] = None,
|
notes: Optional[str] = None,
|
||||||
) -> Task:
|
) -> 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]
|
args = ["add", summary]
|
||||||
|
|
||||||
if project:
|
if project:
|
||||||
@@ -210,6 +215,13 @@ class DstaskClient(TaskBackend):
|
|||||||
# dstask uses various date formats
|
# dstask uses various date formats
|
||||||
args.append(f"due:{due.strftime('%Y-%m-%d')}")
|
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)
|
result = self._run_command(args)
|
||||||
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
@@ -221,10 +233,6 @@ class DstaskClient(TaskBackend):
|
|||||||
# Find task by summary (best effort)
|
# Find task by summary (best effort)
|
||||||
for task in reversed(tasks):
|
for task in reversed(tasks):
|
||||||
if task.summary == summary:
|
if task.summary == summary:
|
||||||
# Add notes if provided
|
|
||||||
if notes:
|
|
||||||
self._run_command(["note", str(task.id), notes])
|
|
||||||
task.notes = notes
|
|
||||||
return task
|
return task
|
||||||
|
|
||||||
# Return a placeholder if we can't find it
|
# Return a placeholder if we can't find it
|
||||||
|
|||||||
@@ -645,8 +645,8 @@ class TasksApp(App):
|
|||||||
from .screens.AddTaskScreen import AddTaskScreen
|
from .screens.AddTaskScreen import AddTaskScreen
|
||||||
from .widgets.AddTaskForm import TaskFormData
|
from .widgets.AddTaskForm import TaskFormData
|
||||||
|
|
||||||
# Get project names for dropdown
|
# Get project names for dropdown (use all_projects which is populated on mount)
|
||||||
project_names = [p.name for p in self.projects if p.name]
|
project_names = [p.name for p in self.all_projects if p.name]
|
||||||
|
|
||||||
def handle_task_created(data: TaskFormData | None) -> None:
|
def handle_task_created(data: TaskFormData | None) -> None:
|
||||||
if data is None or not self.backend:
|
if data is None or not self.backend:
|
||||||
|
|||||||
Reference in New Issue
Block a user