Browse Source

bug-fixes

master
Silberengel 2 weeks ago
parent
commit
cfd81b8b3e
  1. 50
      src/imwald/core/author_html.py
  2. 6
      src/imwald/core/md_render.py
  3. 3
      src/imwald/core/nostr_entity_render.py
  4. 3
      src/imwald/ui/profile_page.py
  5. 6
      tests/test_md_render.py

50
src/imwald/core/author_html.py

@ -21,7 +21,12 @@ def avatar_img_or_placeholder(
size_px: int, size_px: int,
*, *,
border_hex: str = "#2a3d34", border_hex: str = "#2a3d34",
profile_href: str | None = None,
) -> str: ) -> str:
"""
``profile_href``: if set (e.g. ``imwald://pub/``), the avatar is clickable for that profile.
If unset but a picture URL exists, the avatar links to the full-size image (same URL as ``src``).
"""
pic = safe_http_url(parsed.get("picture")) pic = safe_http_url(parsed.get("picture"))
r = max(6, size_px // 5) r = max(6, size_px // 5)
if pic: if pic:
@ -29,6 +34,11 @@ def avatar_img_or_placeholder(
f'<img src="{pic}" width="{size_px}" height="{size_px}" alt="" ' f'<img src="{pic}" width="{size_px}" height="{size_px}" alt="" '
f'style="border-radius:{r}px;object-fit:cover;vertical-align:middle;flex-shrink:0">' f'style="border-radius:{r}px;object-fit:cover;vertical-align:middle;flex-shrink:0">'
) )
if profile_href:
return (
f'<a href="{profile_href}" title="View profile" '
f'style="text-decoration:none;display:inline-block">{img}</a>'
)
return ( return (
f'<a class="imwald-fullimg" href="{pic}" title="View full size" ' f'<a class="imwald-fullimg" href="{pic}" title="View full size" '
f'style="text-decoration:none;display:inline-block">{img}</a>' f'style="text-decoration:none;display:inline-block">{img}</a>'
@ -55,11 +65,11 @@ def feed_op_author_block_html(
) -> str: ) -> str:
"""Top-of-note author row: picture, display name, npub, optional nip05/about lines (links to profile tab).""" """Top-of-note author row: picture, display name, npub, optional nip05/about lines (links to profile tab)."""
disp = html.escape(display_name_from_profile(parsed)) disp = html.escape(display_name_from_profile(parsed))
av = avatar_img_or_placeholder(parsed, 52, border_hex=border)
npub_e = html.escape(npub_bech)
pk_s = html.escape(pk_short)
pk_l = pubkey_hex.strip().lower() pk_l = pubkey_hex.strip().lower()
href = html.escape(f"imwald://pub/{pk_l}", quote=True) href = html.escape(f"imwald://pub/{pk_l}", quote=True)
av = avatar_img_or_placeholder(parsed, 52, border_hex=border, profile_href=href)
npub_e = html.escape(npub_bech)
pk_s = html.escape(pk_short)
inner = ( inner = (
f'<div style="display:flex;align-items:flex-start;margin-bottom:12px">' f'<div style="display:flex;align-items:flex-start;margin-bottom:12px">'
f"{av}" f"{av}"
@ -86,11 +96,11 @@ def thread_reply_author_row_html(
border: str, border: str,
) -> str: ) -> str:
"""Single-row author badge for thread replies (avatar + kind + name + npub).""" """Single-row author badge for thread replies (avatar + kind + name + npub)."""
av = avatar_img_or_placeholder(parsed, 40, border_hex=border)
name = html.escape(display_name_from_profile_or_hex(parsed, pubkey_hex))
npub_e = html.escape(npub_bech)
pk_l = pubkey_hex.strip().lower() pk_l = pubkey_hex.strip().lower()
href = html.escape(f"imwald://pub/{pk_l}", quote=True) href = html.escape(f"imwald://pub/{pk_l}", quote=True)
av = avatar_img_or_placeholder(parsed, 40, border_hex=border, profile_href=href)
name = html.escape(display_name_from_profile_or_hex(parsed, pubkey_hex))
npub_e = html.escape(npub_bech)
inner = ( inner = (
'<div style="display:flex;align-items:center;gap:10px;margin:0 0 6px 0">' '<div style="display:flex;align-items:center;gap:10px;margin:0 0 6px 0">'
f"{av}" f"{av}"
@ -111,9 +121,20 @@ def inline_profile_badge_html(parsed: dict[str, str | None], pubkey_hex: str, np
tip = html.escape(npub_tooltip) tip = html.escape(npub_tooltip)
pic = safe_http_url(parsed.get("picture")) pic = safe_http_url(parsed.get("picture"))
img = "" img = ""
pk_l = pubkey_hex.strip().lower()
prof_href = (
html.escape(f"imwald://pub/{pk_l}", quote=True)
if len(pk_l) == 64 and all(c in "0123456789abcdef" for c in pk_l)
else None
)
if pic: if pic:
wrap = (
f'<a href="{prof_href}" title="View profile" style="text-decoration:none">'
if prof_href
else f'<a class="imwald-fullimg" href="{pic}" title="View full size" style="text-decoration:none">'
)
img = ( img = (
f'<a class="imwald-fullimg" href="{pic}" title="View full size" style="text-decoration:none">' f"{wrap}"
f'<img src="{pic}" width="20" height="20" alt="" ' f'<img src="{pic}" width="20" height="20" alt="" '
'style="border-radius:50%;object-fit:cover;margin-right:6px;vertical-align:middle;flex-shrink:0">' 'style="border-radius:50%;object-fit:cover;margin-right:6px;vertical-align:middle;flex-shrink:0">'
"</a>" "</a>"
@ -122,9 +143,20 @@ def inline_profile_badge_html(parsed: dict[str, str | None], pubkey_hex: str, np
return f'<span class="nostr-user-badge" style="{badge_style}" title="{tip}">{inner}</span>' return f'<span class="nostr-user-badge" style="{badge_style}" title="{tip}">{inner}</span>'
def embed_author_row_html(parsed: dict[str, str | None], head_line_html: str) -> str: def embed_author_row_html(
parsed: dict[str, str | None],
head_line_html: str,
*,
pubkey_hex: str | None = None,
) -> str:
"""Avatar + headline row for embedded events.""" """Avatar + headline row for embedded events."""
av = avatar_img_or_placeholder(parsed, 36, border_hex="#2a3d34") pk_l = (pubkey_hex or "").strip().lower()
prof = (
html.escape(f"imwald://pub/{pk_l}", quote=True)
if len(pk_l) == 64 and all(c in "0123456789abcdef" for c in pk_l)
else None
)
av = avatar_img_or_placeholder(parsed, 36, border_hex="#2a3d34", profile_href=prof)
return ( return (
'<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px">' '<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px">'
f"{av}" f"{av}"

6
src/imwald/core/md_render.py

@ -23,6 +23,8 @@ if TYPE_CHECKING:
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
_NH3_URL_SCHEMES = frozenset({"http", "https", "mailto", "imwald"})
# Bare HTTPS image URLs in notes → Markdown image (so renderer emits ``<img>``). # Bare HTTPS image URLs in notes → Markdown image (so renderer emits ``<img>``).
# Exclude ``<`` so ``![](<https://…>)`` / HTML ``src="https://…"`` are not double-wrapped. # Exclude ``<`` so ``![](<https://…>)`` / HTML ``src="https://…"`` are not double-wrapped.
_STANDALONE_IMAGE_URL = re.compile( _STANDALONE_IMAGE_URL = re.compile(
@ -96,6 +98,7 @@ def _nh3_clean(html: str) -> str:
html, html,
attributes=_nh3_attributes(), attributes=_nh3_attributes(),
filter_style_properties=_NH3_STYLE_FILTER, filter_style_properties=_NH3_STYLE_FILTER,
url_schemes=_NH3_URL_SCHEMES,
) )
@ -145,6 +148,9 @@ def _wrap_http_images_for_fullsize(fragment: str) -> str:
if re.search(r'<a\s[^>]*class="[^"\']*imwald-fullimg[^"\']*"[^>]*>\s*$', pre, re.I): if re.search(r'<a\s[^>]*class="[^"\']*imwald-fullimg[^"\']*"[^>]*>\s*$', pre, re.I):
out.append(full_tag) out.append(full_tag)
continue continue
if re.search(r'<a\s[^>]*href=["\']imwald://[^"\']*["\'][^>]*>\s*$', pre, re.I):
out.append(full_tag)
continue
w, h = _parse_img_width_height(attr_blob) w, h = _parse_img_width_height(attr_blob)
if w is not None and h is not None and w <= 64 and h <= 64: if w is not None and h is not None and w <= 64 and h <= 64:
out.append(full_tag) out.append(full_tag)

3
src/imwald/core/nostr_entity_render.py

@ -62,7 +62,8 @@ def _event_embed_html(db: Database, event_id_hex: str) -> str:
if len(raw) > 400: if len(raw) > 400:
body += "" body += ""
head = html.escape(head_plain) head = html.escape(head_plain)
head_row = embed_author_row_html(p, head) pk_hex = str(pk).strip().lower()
head_row = embed_author_row_html(p, head, pubkey_hex=pk_hex)
return ( return (
"\n\n" "\n\n"
f'<div class="nostr-embed" style="{_EMBED_STYLE}">' f'<div class="nostr-embed" style="{_EMBED_STYLE}">'

3
src/imwald/ui/profile_page.py

@ -137,7 +137,8 @@ class ProfilePage(QWidget):
) )
disp = html.escape(display_name_from_profile_or_hex(parsed, pk)) disp = html.escape(display_name_from_profile_or_hex(parsed, pk))
av = avatar_img_or_placeholder(parsed, 72, border_hex=BORDER) prof_href = html.escape(f"imwald://pub/{pk}", quote=True)
av = avatar_img_or_placeholder(parsed, 72, border_hex=BORDER, profile_href=prof_href)
nip05 = html.escape((parsed.get("nip05") or "").strip()) if parsed.get("nip05") else "" nip05 = html.escape((parsed.get("nip05") or "").strip()) if parsed.get("nip05") else ""
nip05_html = ( nip05_html = (
f"<div style='color:{TEXT_MUTED};font-size:16px;margin-top:6px'>{nip05}</div>" if nip05 else "" f"<div style='color:{TEXT_MUTED};font-size:16px;margin-top:6px'>{nip05}</div>" if nip05 else ""

6
tests/test_md_render.py

@ -28,3 +28,9 @@ def test_markdown_image_wrapped_for_fullsize_link() -> None:
assert "imwald-fullimg" in html assert "imwald-fullimg" in html
assert "https://example.com/wide.png" in html assert "https://example.com/wide.png" in html
assert "<img" in html assert "<img" in html
def test_nh3_preserves_imwald_profile_href() -> None:
pk = "c" * 64
html = markdown_html_fragment(f"[profile](imwald://pub/{pk})")
assert f'href="imwald://pub/{pk}"' in html

Loading…
Cancel
Save