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
|
# View mode: "markdown" for pretty rendering, "html" for raw/plain display
|
||||||
default_view_mode: Literal["markdown", "html"] = "markdown"
|
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):
|
class LinkPanelConfig(BaseModel):
|
||||||
"""Configuration for the link panel."""
|
"""Configuration for the link panel."""
|
||||||
|
|||||||
@@ -6,10 +6,15 @@ from textual.widgets import Static, Markdown, Label
|
|||||||
from textual.reactive import reactive
|
from textual.reactive import reactive
|
||||||
from src.services.himalaya import client as himalaya_client
|
from src.services.himalaya import client as himalaya_client
|
||||||
from src.mail.config import get_config
|
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
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Literal, List
|
from typing import Literal, List, Dict
|
||||||
|
from urllib.parse import urlparse
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -18,6 +23,88 @@ import sys
|
|||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
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):
|
class EnvelopeHeader(Vertical):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@@ -192,10 +279,18 @@ class ContentContainer(ScrollableContainer):
|
|||||||
# Store the raw content for link extraction
|
# Store the raw content for link extraction
|
||||||
self.current_content = content
|
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:
|
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
|
||||||
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:
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user