Files
indiekit-endpoint-microsub/views/actor.njk
Ricardo 1512bcecb2 feat: add CSS stacking avatar fallback to all templates (v1.0.49)
Phase 3 frontend harmonization: apply the same avatar fallback pattern
used in ActivityPub to all Microsub templates. A fallback initials span
is always rendered; when the <img> loads, it covers the fallback via
absolute positioning. Broken images are removed by event delegation,
revealing the initials underneath.

Templates updated: item-card, item, actor, author (3 avatar sizes:
40px card, 48px detail, 80px profile). Error delegation script added
to reader layout.

Confab-Link: http://localhost:8080/sessions/bb4a6ec4-b711-48cd-b3d7-942ec2a9851d
2026-03-13 13:53:45 +01:00

187 lines
7.2 KiB
Plaintext

{% extends "layouts/reader.njk" %}
{% block reader %}
<div class="ms-channel">
<header class="ms-channel__header">
<a href="{{ baseUrl }}/channels/activitypub" class="back-link">
{{ icon("previous") }} Fediverse
</a>
</header>
{# Actor profile card #}
<div class="ms-actor-profile">
<div class="ms-actor-profile__header">
<div class="ms-actor-profile__avatar-wrap" data-avatar-fallback>
{% if actor.photo %}
<img src="{{ actor.photo }}"
alt=""
class="ms-actor-profile__avatar"
width="80"
height="80">
{% endif %}
<span class="ms-actor-profile__avatar ms-actor-profile__avatar--default" aria-hidden="true">{{ actor.name[0] | upper if actor.name else "?" }}</span>
</div>
<div class="ms-actor-profile__info">
<h2 class="ms-actor-profile__name">{{ actor.name }}</h2>
{% if actor.handle %}
<span class="ms-actor-profile__handle">@{{ actor.handle }}</span>
{% endif %}
{% if actor.summary %}
<p class="ms-actor-profile__summary">{{ actor.summary }}</p>
{% endif %}
<div class="ms-actor-profile__stats">
{% if actor.followersCount %}
<span>{{ actor.followersCount }} followers</span>
{% endif %}
{% if actor.followingCount %}
<span>{{ actor.followingCount }} following</span>
{% endif %}
</div>
</div>
</div>
<div class="ms-actor-profile__actions">
<a href="{{ actor.url }}" class="button button--secondary button--small" target="_blank" rel="noopener">
{{ icon("syndicate") }} View profile
</a>
{% if canFollow %}
{% if isFollowing %}
<form action="{{ baseUrl }}/actor/unfollow" method="POST" style="display: inline;">
<input type="hidden" name="actorUrl" value="{{ actorUrl }}">
<button type="submit" class="button button--secondary button--small">
{{ icon("tick") }} Following
</button>
</form>
{% else %}
<form action="{{ baseUrl }}/actor/follow" method="POST" style="display: inline;">
<input type="hidden" name="actorUrl" value="{{ actorUrl }}">
<input type="hidden" name="actorName" value="{{ actor.name }}">
<button type="submit" class="button button--primary button--small">
{{ icon("syndicate") }} Follow
</button>
</form>
{% endif %}
{% endif %}
</div>
</div>
{% if error %}
<div class="ms-reader__empty">
{{ icon("warning") }}
<p>{{ error }}</p>
</div>
{% elif items.length > 0 %}
<div class="ms-timeline" id="timeline">
{% for item in items %}
<article class="ms-item-card">
{# Author #}
{% if item.author %}
<div class="ms-item-card__author" style="padding: 12px 16px 0;">
<div class="ms-item-card__avatar-wrap" data-avatar-fallback>
{% if item.author.photo %}
<img src="{{ item.author.photo }}"
alt=""
class="ms-item-card__author-photo"
width="40"
height="40"
loading="lazy">
{% endif %}
<span class="ms-item-card__author-photo ms-item-card__author-photo--default" aria-hidden="true">{{ item.author.name[0] | upper if item.author.name else "?" }}</span>
</div>
<div class="ms-item-card__author-info">
<span class="ms-item-card__author-name">{{ item.author.name or "Unknown" }}</span>
{% if item.author.url %}
<span class="ms-item-card__source">{{ item.author.url | replace("https://", "") | replace("http://", "") }}</span>
{% endif %}
</div>
</div>
{% endif %}
<a href="{{ item.url }}" class="ms-item-card__link" target="_blank" rel="noopener">
{# Reply context #}
{% if item["in-reply-to"] and item["in-reply-to"].length > 0 %}
<div class="ms-item-card__context">
{{ icon("reply") }}
<span>Reply to</span>
<span>{{ item["in-reply-to"][0] | replace("https://", "") | replace("http://", "") | truncate(50) }}</span>
</div>
{% endif %}
{# Title #}
{% if item.name %}
<h3 class="ms-item-card__title">{{ item.name }}</h3>
{% endif %}
{# Content #}
{% if item.content %}
<div class="ms-item-card__content{% if (item.content.text or '') | length > 300 %} ms-item-card__content--truncated{% endif %}">
{% if item.content.html %}
{{ item.content.html | safe | striptags | truncate(400) }}
{% elif item.content.text %}
{{ item.content.text | truncate(400) }}
{% endif %}
</div>
{% endif %}
{# Tags #}
{% if item.category and item.category.length > 0 %}
<div class="ms-item-card__categories">
{% for cat in item.category %}
{% if loop.index0 < 5 %}
<span class="ms-item-card__category">#{{ cat }}</span>
{% endif %}
{% endfor %}
</div>
{% endif %}
{# Photos #}
{% if item.photo and item.photo.length > 0 %}
{% set photoCount = item.photo.length if item.photo.length <= 4 else 4 %}
<div class="ms-item-card__photos ms-item-card__photos--{{ photoCount }}">
{% for photo in item.photo %}
{% if loop.index0 < 4 %}
<img src="{{ photo }}" alt="" class="ms-item-card__photo" loading="lazy">
{% endif %}
{% endfor %}
</div>
{% endif %}
{# Footer #}
<footer class="ms-item-card__footer">
{% if item.published %}
<time datetime="{{ item.published }}" class="ms-item-card__date">
{{ item.published | date("PP", { locale: locale, timeZone: application.timeZone }) }}
</time>
{% endif %}
</footer>
</a>
{# Actions #}
<div class="ms-item-actions">
<a href="{{ item.url }}" class="ms-item-actions__button" target="_blank" rel="noopener" title="View original">
{{ icon("syndicate") }}
</a>
<a href="{{ baseUrl }}/compose?reply={{ item.url | urlencode }}" class="ms-item-actions__button" title="Reply">
{{ icon("reply") }}
</a>
<a href="{{ baseUrl }}/compose?like={{ item.url | urlencode }}" class="ms-item-actions__button" title="Like">
{{ icon("like") }}
</a>
<a href="{{ baseUrl }}/compose?repost={{ item.url | urlencode }}" class="ms-item-actions__button" title="Repost">
{{ icon("repost") }}
</a>
<a href="{{ baseUrl }}/compose?bookmark={{ item.url | urlencode }}" class="ms-item-actions__button" title="Bookmark">
{{ icon("bookmark") }}
</a>
</div>
</article>
{% endfor %}
</div>
{% else %}
<div class="ms-reader__empty">
{{ icon("syndicate") }}
<p>No posts found for this actor.</p>
</div>
{% endif %}
</div>
{% endblock %}