Add URL compression for mail content viewer
This commit is contained in:
@@ -82,6 +82,10 @@ class ContentDisplayConfig(BaseModel):
|
||||
# View mode: "markdown" for pretty rendering, "html" for raw/plain display
|
||||
default_view_mode: Literal["markdown", "html"] = "markdown"
|
||||
|
||||
# URL compression: shorten long URLs for better readability
|
||||
compress_urls: bool = True
|
||||
max_url_length: int = 50 # Maximum length before URL is compressed
|
||||
|
||||
|
||||
class LinkPanelConfig(BaseModel):
|
||||
"""Configuration for the link panel."""
|
||||
|
||||
@@ -6,10 +6,15 @@ from textual.widgets import Static, Markdown, Label
|
||||
from textual.reactive import reactive
|
||||
from src.services.himalaya import client as himalaya_client
|
||||
from src.mail.config import get_config
|
||||
from src.mail.screens.LinkPanel import extract_links_from_content, LinkItem
|
||||
from src.mail.screens.LinkPanel import (
|
||||
extract_links_from_content,
|
||||
LinkItem,
|
||||
LinkItem as LinkItemClass,
|
||||
)
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Literal, List
|
||||
from typing import Literal, List, Dict
|
||||
from urllib.parse import urlparse
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
@@ -18,6 +23,88 @@ import sys
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
def compress_urls_in_content(content: str, max_url_len: int = 50) -> str:
|
||||
"""Compress long URLs in markdown/text content for better readability.
|
||||
|
||||
Replaces long URLs with shortened versions using the same algorithm
|
||||
as LinkPanel._shorten_url. Preserves markdown link syntax.
|
||||
|
||||
Args:
|
||||
content: The markdown/text content to process
|
||||
max_url_len: Maximum length for displayed URLs (default 50)
|
||||
|
||||
Returns:
|
||||
Content with compressed URLs
|
||||
"""
|
||||
|
||||
# Pattern for markdown links: [text](url)
|
||||
def replace_md_link(match):
|
||||
anchor_text = match.group(1)
|
||||
url = match.group(2)
|
||||
|
||||
# Don't compress if URL is already short
|
||||
if len(url) <= max_url_len:
|
||||
return match.group(0)
|
||||
|
||||
# Use LinkItem's shortening algorithm
|
||||
short_url = LinkItemClass._shorten_url(
|
||||
url,
|
||||
urlparse(url).netloc.replace("www.", ""),
|
||||
urlparse(url).path,
|
||||
max_url_len,
|
||||
)
|
||||
|
||||
# Keep original anchor text, but if it's the same as URL, use short version
|
||||
if anchor_text == url or anchor_text.startswith("http"):
|
||||
return f"[🔗 {short_url}]({url})"
|
||||
else:
|
||||
return match.group(0) # Keep original if anchor text is meaningful
|
||||
|
||||
# Pattern for bare URLs (not inside markdown links)
|
||||
def replace_bare_url(match):
|
||||
url = match.group(0)
|
||||
|
||||
# Don't compress if URL is already short
|
||||
if len(url) <= max_url_len:
|
||||
return url
|
||||
|
||||
parsed = urlparse(url)
|
||||
short_url = LinkItemClass._shorten_url(
|
||||
url, parsed.netloc.replace("www.", ""), parsed.path, max_url_len
|
||||
)
|
||||
|
||||
# Return as markdown link with icon
|
||||
return f"[🔗 {short_url}]({url})"
|
||||
|
||||
# First, process markdown links
|
||||
md_link_pattern = r"\[([^\]]+)\]\((https?://[^)]+)\)"
|
||||
content = re.sub(md_link_pattern, replace_md_link, content)
|
||||
|
||||
# Then process bare URLs that aren't already in markdown links
|
||||
# This regex matches URLs not preceded by ]( which would indicate markdown link
|
||||
bare_url_pattern = r'(?<!\]\()https?://[^\s<>"\'\)]+[^\s<>"\'\.\,\)\]]'
|
||||
|
||||
# Use a more careful approach to avoid double-processing
|
||||
# Split content, process bare URLs, rejoin
|
||||
result = []
|
||||
last_end = 0
|
||||
|
||||
for match in re.finditer(bare_url_pattern, content):
|
||||
# Check if this URL is inside a markdown link (preceded by "](")
|
||||
prefix_start = max(0, match.start() - 2)
|
||||
prefix = content[prefix_start : match.start()]
|
||||
if prefix.endswith("]("):
|
||||
continue # Skip URLs that are already markdown link targets
|
||||
|
||||
result.append(content[last_end : match.start()])
|
||||
result.append(replace_bare_url(match))
|
||||
last_end = match.end()
|
||||
|
||||
result.append(content[last_end:])
|
||||
|
||||
return "".join(result)
|
||||
|
||||
|
||||
class EnvelopeHeader(Vertical):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -192,10 +279,18 @@ class ContentContainer(ScrollableContainer):
|
||||
# Store the raw content for link extraction
|
||||
self.current_content = content
|
||||
|
||||
# Get URL compression settings from config
|
||||
config = get_config()
|
||||
compress_urls = config.content_display.compress_urls
|
||||
max_url_len = config.content_display.max_url_length
|
||||
|
||||
try:
|
||||
if self.current_mode == "markdown":
|
||||
# For markdown mode, use the Markdown widget
|
||||
self.content.update(content)
|
||||
display_content = content
|
||||
if compress_urls:
|
||||
display_content = compress_urls_in_content(content, max_url_len)
|
||||
self.content.update(display_content)
|
||||
else:
|
||||
# For HTML mode, use the Static widget with markup
|
||||
# First, try to extract the body content if it's HTML
|
||||
|
||||
Reference in New Issue
Block a user