Browse Source

bug-fixes

master
Silberengel 2 weeks ago
parent
commit
1020712964
  1. 72
      src/imwald/core/author_html.py
  2. 4
      src/imwald/core/database.py
  3. 13
      src/imwald/ui/feed_page.py

72
src/imwald/core/author_html.py

@ -72,12 +72,15 @@ def format_nip05_chips_html(
container_margin_top: str | None = None, container_margin_top: str | None = None,
) -> str: ) -> str:
""" """
Horizontal row of NIP-05 chips: identifier with domain favicon in place of ``@``, plus verification mark. NIP-05 lines for rich text (e.g. ``QTextBrowser``): domain favicon replaces ``@`` between local and domain.
Uses block / inline-block and explicit margins Qt's HTML subset ignores ``display:flex`` and ``gap``,
which is why a flex row collapses into an illegible run.
``bool | None``: ``True`` verified, ``False`` failed, ``None`` pending / not checked. ``bool | None``: ``True`` verified, ``False`` failed, ``None`` pending / not checked.
""" """
parts: list[str] = [] parts: list[str] = []
for ident, okv in identifiers: for i, (ident, okv) in enumerate(identifiers):
parsed = parse_nip05_identifier(ident) parsed = parse_nip05_identifier(ident)
dom = parsed[1] if parsed else "" dom = parsed[1] if parsed else ""
fav = favicon_url_for_domain(dom) if dom else "" fav = favicon_url_for_domain(dom) if dom else ""
@ -92,47 +95,53 @@ def format_nip05_chips_html(
esc_local = html.escape(local_raw, quote=False) esc_local = html.escape(local_raw, quote=False)
esc_dom = html.escape(dom_raw, quote=False) esc_dom = html.escape(dom_raw, quote=False)
id_body = ( id_body = (
f'<span style="display:inline-flex;align-items:center;gap:5px;flex-wrap:nowrap" ' f'<span style="color:{muted};font-size:14px;line-height:1.55" title="{esc_full_attr}">'
f'title="{esc_full_attr}">' f'<span style="display:inline-block;margin-right:10px;vertical-align:middle">{esc_local}</span>'
f'<span style="color:{muted};font-size:14px;line-height:1.45">{esc_local}</span>' f'<img src="{esc_fav}" width="16" height="16" alt="" '
f'<img src="{esc_fav}" width="15" height="15" alt="" ' f'style="display:inline-block;vertical-align:middle;margin:0 14px;border-radius:3px;opacity:0.95" />'
f'style="display:block;border-radius:3px;opacity:0.95;flex-shrink:0" />' f'<span style="display:inline-block;margin-left:10px;vertical-align:middle">{esc_dom}</span>'
f'<span style="color:{muted};font-size:14px;line-height:1.45">{esc_dom}</span>'
f"</span>" f"</span>"
) )
else: else:
id_body = ( id_body = (
f'<span style="color:{muted};font-size:14px;line-height:1.45" title="{esc_full_attr}">{esc_full}</span>' f'<span style="color:{muted};font-size:14px;line-height:1.55" title="{esc_full_attr}">{esc_full}</span>'
) )
if okv is True: if okv is True:
mark = f'<span style="color:{ok};font-size:15px;line-height:1" title="NIP-05 verified">✓</span>' mark = (
f'<span style="display:inline-block;margin-left:22px;vertical-align:middle;'
f'color:{ok};font-size:16px;line-height:1" title="NIP-05 verified">✓</span>'
)
elif okv is False: elif okv is False:
mark = f'<span style="color:{bad};font-size:15px;line-height:1" title="NIP-05 not verified">✗</span>' mark = (
f'<span style="display:inline-block;margin-left:22px;vertical-align:middle;'
f'color:{bad};font-size:16px;line-height:1" title="NIP-05 not verified">✗</span>'
)
else: else:
mark = f'<span style="color:{dim};font-size:15px;line-height:1" title="NIP-05 not verified yet">○</span>' mark = (
f'<span style="display:inline-block;margin-left:22px;vertical-align:middle;'
f'color:{dim};font-size:16px;line-height:1" title="NIP-05 not verified yet">○</span>'
)
row_mt = "0" if i == 0 else "20px"
if chip_bg: if chip_bg:
brd = chip_border or dim brd = chip_border or dim
wrap = ( row = (
"display:inline-flex;align-items:center;gap:10px;padding:10px 18px;" f'<div style="margin-top:{row_mt};padding:12px 14px 12px 14px;'
"margin:0 12px 10px 0;" f'border-radius:12px;background:{chip_bg};border:1px solid {brd}">'
f"border-radius:999px;background:{chip_bg};border:1px solid {brd}" f"{id_body}{mark}</div>"
) )
else: else:
wrap = ( row = (
"display:inline-flex;align-items:center;gap:8px;padding:4px 4px 4px 0;" f'<div style="margin-top:{row_mt};padding:12px 8px 14px 4px;line-height:1.6">'
"margin:0 16px 6px 0" f"{id_body}{mark}</div>"
) )
parts.append(f'<span style="{wrap}">{id_body}{mark}</span>') parts.append(row)
if not parts: if not parts:
return "" return ""
if container_margin_top is not None: if container_margin_top is not None:
mt = container_margin_top mt = container_margin_top
else: else:
mt = "14px" if chip_bg else "4px" mt = "14px" if chip_bg else "4px"
return ( return f'<div style="margin-top:{mt}">{"".join(parts)}</div>'
f'<div style="display:flex;flex-wrap:wrap;align-items:flex-start;'
f'gap:10px 14px;margin-top:{mt}">{"".join(parts)}</div>'
)
def feed_op_author_compact_html( def feed_op_author_compact_html(
@ -166,6 +175,7 @@ def feed_op_author_compact_html(
f'<div style="color:{dim};font-size:13px;line-height:1.5;margin:0">{event_meta_inner_html}</div>' f'<div style="color:{dim};font-size:13px;line-height:1.5;margin:0">{event_meta_inner_html}</div>'
) )
row_mt = "12px" if nip38_block else "8px" row_mt = "12px" if nip38_block else "8px"
# ``QTextBrowser`` / ``QTextDocument`` ignore most flexbox; use a table so avatar + npub sit side‑by‑side.
return ( return (
f'<div style="background:{header_bg};border:1px solid {border};border-radius:10px;' f'<div style="background:{header_bg};border:1px solid {border};border-radius:10px;'
f'padding:14px 16px 18px;margin:0 0 22px 0">' f'padding:14px 16px 18px;margin:0 0 22px 0">'
@ -174,16 +184,18 @@ def feed_op_author_compact_html(
f"{name_e}</div>" f"{name_e}</div>"
f"</a>" f"</a>"
f"{nip38_block}" f"{nip38_block}"
f'<div style="display:flex;align-items:flex-start;gap:14px;margin-top:{row_mt}">' f'<table style="margin-top:{row_mt};width:100%;border:none;border-collapse:collapse" '
f"{av}" f'cellpadding="0" cellspacing="0"><tr>'
f'<div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:12px">' f'<td style="vertical-align:top;padding:0 14px 0 0;width:1px">{av}</td>'
f'<td style="vertical-align:top;padding:0">'
f'<a href="{href}" style="text-decoration:none;color:inherit" title="View profile">' f'<a href="{href}" style="text-decoration:none;color:inherit" title="View profile">'
f'<div style="font-size:13px;color:{dim};font-family:ui-monospace,Cascadia Mono,Consolas,monospace;' f'<div style="font-size:13px;color:{dim};font-family:ui-monospace,Cascadia Mono,Consolas,monospace;'
f'line-height:1.5;word-break:break-all" title="{npub_title}">{npub_line}</div>' f'line-height:1.5;word-break:break-all;margin:0 0 14px 0" title="{npub_title}">{npub_line}</div>'
f"</a>" f"</a>"
f"{nip05_chips_html}" f'<div style="margin:0 0 12px 0">{nip05_chips_html}</div>'
f"{meta_block}" f"{meta_block}"
f"</div></div></div>" f"</td></tr></table>"
f"</div>"
) )

4
src/imwald/core/database.py

@ -659,7 +659,7 @@ class Database:
) -> list[dict[str, Any]]: ) -> list[dict[str, Any]]:
""" """
Feed-shaped rows newest-first. With ``exclude_viewed`` and ``viewer_pubkey``, Feed-shaped rows newest-first. With ``exclude_viewed`` and ``viewer_pubkey``,
omits ids already in ``feed_views`` (the UI records an OP when it is shown). omits ids already in ``feed_views`` (the feed pager records an OP when the user leaves it).
""" """
kind_list = list(kinds) kind_list = list(kinds)
placeholders = ",".join("?" * len(kind_list)) placeholders = ",".join("?" * len(kind_list))
@ -695,7 +695,7 @@ class Database:
return out return out
def mark_feed_viewed(self, viewer_pubkey: str, event_id: str) -> None: def mark_feed_viewed(self, viewer_pubkey: str, event_id: str) -> None:
"""Persist that this viewer was shown this OP (pager); used to prefer fresh notes in the feed.""" """Persist that this viewer finished this OP (pager left / account switch); used for unseen feed ranking."""
with self.write_lock() as c: with self.write_lock() as c:
c.execute( c.execute(
""" """

13
src/imwald/ui/feed_page.py

@ -408,9 +408,16 @@ class FeedPage(QWidget):
following: set[str], following: set[str],
list30000_pubkeys: set[str] | None = None, list30000_pubkeys: set[str] | None = None,
) -> None: ) -> None:
old_vk = (self._my_pubkey or "_anon").lower()
self._my_pubkey = my_pubkey self._my_pubkey = my_pubkey
self._following = following self._following = following
self._list30000_pubkeys = list30000_pubkeys or set() self._list30000_pubkeys = list30000_pubkeys or set()
new_vk = (self._my_pubkey or "_anon").lower()
if old_vk != new_vk and self._rendered_op_id:
self._db.mark_feed_viewed(old_vk, self._rendered_op_id)
self._rendered_op_id = None
self._rendered_reply_sig = None
self._op_ev_snapshot = None
def _feed_viewer_key(self) -> str: def _feed_viewer_key(self) -> str:
"""Per-device feed history; logged-out users share `_anon`.""" """Per-device feed history; logged-out users share `_anon`."""
@ -600,6 +607,10 @@ class FeedPage(QWidget):
self._engagement_label.setText("") self._engagement_label.setText("")
return return
ev = self._queue[self._index % len(self._queue)] ev = self._queue[self._index % len(self._queue)]
root_id = str(ev["id"])
prev_rid = self._rendered_op_id
if prev_rid and prev_rid != root_id:
self._db.mark_feed_viewed(self._feed_viewer_key(), prev_rid)
if ev.get("deleted"): if ev.get("deleted"):
self._rendered_op_id = None self._rendered_op_id = None
self._rendered_reply_sig = None self._rendered_reply_sig = None
@ -631,7 +642,6 @@ class FeedPage(QWidget):
_format_engagement_html(stats, reaction_nip30_urls=author_nip30) _format_engagement_html(stats, reaction_nip30_urls=author_nip30)
) )
root_id = str(ev["id"])
replies = self._db.list_replies_to(ev) replies = self._db.list_replies_to(ev)
reply_sig = tuple(str(r["id"]) for r in replies) reply_sig = tuple(str(r["id"]) for r in replies)
if root_id == self._rendered_op_id and reply_sig == self._rendered_reply_sig: if root_id == self._rendered_op_id and reply_sig == self._rendered_reply_sig:
@ -644,7 +654,6 @@ class FeedPage(QWidget):
self._op_ev_snapshot = ev self._op_ev_snapshot = ev
body = self._build_op_html(ev, None) body = self._build_op_html(ev, None)
self._op.setHtml(body) self._op.setHtml(body)
self._db.mark_feed_viewed(self._feed_viewer_key(), root_id)
pk = op_pk pk = op_pk
prof_row2 = self._db.get_latest_kind0_profile(pk) prof_row2 = self._db.get_latest_kind0_profile(pk)

Loading…
Cancel
Save