Merge upstream rmdes:main — v2.10.0 (Delete, visibility, CW, polls, Flag) into svemagie/main (v2.10.1)

Upstream v2.10.0 adds: outbound Delete, visibility addressing (unlisted/
followers-only), Content Warning (sensitive flag + summary), inbound poll
rendering, Flag/report handler, DM support files.

Conflict resolution — all four conflicts were additive (no code removed):

  lib/controllers/reader.js: union of validTabs — fork added "mention",
    upstream added "dm" and "report"; result keeps all five additions.

  lib/storage/notifications.js: union of count keys — fork added mention:0,
    upstream added dm:0 and report:0; result keeps the fork's mention split
    logic alongside the new upstream keys.

  views/partials/ap-notification-card.njk: fork kept isDirect 🔒 badge for
    direct mentions; upstream added ✉ for dm and ⚑ for report; result keeps
    the isDirect branch and appends the two new type badges.

  package.json: upstream bumped to 2.10.0; we bump to 2.10.1 to reflect our
    own Alpine.js and publication-aware docloader bug fixes on top.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sven Giersig
2026-03-14 13:00:58 +01:00
25 changed files with 4780 additions and 18 deletions

View File

@@ -100,6 +100,11 @@
{# Media hidden behind CW #}
{% include "partials/ap-item-media.njk" %}
{# Poll options #}
{% if item.type == "question" or (item.pollOptions and item.pollOptions.length > 0) %}
{% include "partials/ap-poll-options.njk" %}
{% endif %}
</div>
</div>
{% else %}
@@ -118,6 +123,11 @@
{# Media visible directly #}
{% include "partials/ap-item-media.njk" %}
{# Poll options #}
{% if item.type == "question" or (item.pollOptions and item.pollOptions.length > 0) %}
{% include "partials/ap-poll-options.njk" %}
{% endif %}
{% endif %}
{# Mentions and hashtags #}

View File

@@ -0,0 +1,65 @@
{# Message card partial — inbound/outbound DM display #}
<div class="ap-notification ap-message{% if not item.read %} ap-notification--unread{% endif %}{% if item.direction == 'outbound' %} ap-message--outbound{% endif %}">
{# Dismiss button #}
<form method="post" action="{{ mountPath }}/admin/reader/messages/delete" class="ap-notification__dismiss">
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
<input type="hidden" name="uid" value="{{ item.uid }}">
<button type="submit" class="ap-notification__dismiss-btn" title="{{ __('activitypub.messages.delete') }}">&times;</button>
</form>
{# Avatar — outbound: our profile photo, inbound: sender's photo #}
<div class="ap-notification__avatar-wrap" data-avatar-fallback>
{% if item.direction == "outbound" and myProfile and myProfile.icon %}
<img src="{{ myProfile.icon }}" alt="{{ myProfile.name or 'Me' }}" class="ap-notification__avatar" loading="lazy" crossorigin="anonymous">
<span class="ap-notification__avatar ap-notification__avatar--default" aria-hidden="true">{{ (myProfile.name or "M")[0] | upper }}</span>
{% else %}
{% if item.actorPhoto %}
<img src="{{ item.actorPhoto }}" alt="{{ item.actorName }}" class="ap-notification__avatar" loading="lazy" crossorigin="anonymous">
{% endif %}
<span class="ap-notification__avatar ap-notification__avatar--default" aria-hidden="true">{{ item.actorName[0] | upper if item.actorName else "?" }}</span>
{% endif %}
<span class="ap-notification__type-badge">
{% if item.direction == "outbound" %}↗{% else %}✉{% endif %}
</span>
</div>
{# Message body #}
<div class="ap-notification__body">
<span class="ap-notification__actor">
{% if item.direction == "outbound" %}
<span class="ap-message__direction">{{ __("activitypub.messages.sentTo") }}</span>
{% endif %}
<a href="{{ item.actorUrl }}">{{ item.actorName }}</a>
{% if item.actorHandle %}
<span class="ap-notification__handle">{{ item.actorHandle }}</span>
{% endif %}
</span>
{% if item.content and item.content.html %}
<div class="ap-message__content">
{{ item.content.html | safe }}
</div>
{% elif item.content and item.content.text %}
<div class="ap-message__content">
{{ item.content.text }}
</div>
{% endif %}
{# Reply action (only for inbound messages) #}
{% if item.direction == "inbound" %}
<div class="ap-notification__actions">
<a href="{{ mountPath }}/admin/reader/messages/compose?to={{ item.actorHandle | urlencode }}&replyTo={{ item.uid | urlencode }}" class="ap-notification__reply-btn">
↩ {{ __("activitypub.reader.actions.reply") }}
</a>
</div>
{% endif %}
</div>
{# Timestamp #}
{% if item.published %}
<time datetime="{{ item.published }}" class="ap-notification__time" x-data x-relative-time>
{{ item.published | date("PPp") }}
</time>
{% endif %}
</div>

View File

@@ -15,7 +15,7 @@
{% endif %}
<span class="ap-notification__avatar ap-notification__avatar--default" aria-hidden="true">{{ item.actorName[0] | upper if item.actorName else "?" }}</span>
<span class="ap-notification__type-badge">
{% if item.type == "like" %}❤{% elif item.type == "boost" %}🔁{% elif item.type == "follow" %}👤{% elif item.type == "reply" %}💬{% elif item.type == "mention" %}{% if item.isDirect %}🔒{% else %}@{% endif %}{% endif %}
{% if item.type == "like" %}❤{% elif item.type == "boost" %}🔁{% elif item.type == "follow" %}👤{% elif item.type == "reply" %}💬{% elif item.type == "mention" %}{% if item.isDirect %}🔒{% else %}@{% endif %}{% elif item.type == "dm" %}✉{% elif item.type == "report" %}⚑{% endif %}
</span>
</div>
@@ -36,6 +36,10 @@
{{ __("activitypub.notifications.repliedTo") }}
{% elif item.type == "mention" %}
{{ __("activitypub.notifications.mentionedYou") }}
{% elif item.type == "dm" %}
{{ __("activitypub.messages.sentYouDM") }}
{% elif item.type == "report" %}
{{ __("activitypub.reports.sentReport") }}
{% endif %}
</span>
@@ -60,6 +64,12 @@
💬 {{ __("activitypub.notifications.viewThread") }}
</a>
</div>
{% elif item.type == "dm" %}
<div class="ap-notification__actions">
<a href="{{ mountPath }}/admin/reader/messages?partner={{ item.actorUrl | urlencode }}" class="ap-notification__thread-btn" title="{{ __('activitypub.messages.title') }}">
✉ {{ __("activitypub.messages.viewMessage") }}
</a>
</div>
{% endif %}
</div>

View File

@@ -0,0 +1,30 @@
{# Poll options partial — renders vote results for Question-type posts #}
{% if item.pollOptions and item.pollOptions.length > 0 %}
{% set totalVotes = 0 %}
{% for opt in item.pollOptions %}
{% set totalVotes = totalVotes + opt.votes %}
{% endfor %}
<div class="ap-poll">
{% for opt in item.pollOptions %}
{% set pct = (totalVotes > 0) and ((opt.votes / totalVotes * 100) | round) or 0 %}
<div class="ap-poll__option">
<div class="ap-poll__bar" style="width: {{ pct }}%"></div>
<span class="ap-poll__label">{{ opt.name }}</span>
<span class="ap-poll__votes">{{ pct }}%</span>
</div>
{% endfor %}
<div class="ap-poll__footer">
{% if item.votersCount > 0 %}
{{ item.votersCount }} {{ __("activitypub.poll.voters") }}
{% elif totalVotes > 0 %}
{{ totalVotes }} {{ __("activitypub.poll.votes") }}
{% endif %}
{% if item.pollClosed %}
· {{ __("activitypub.poll.closed") }}
{% elif item.pollEndTime %}
· {{ __("activitypub.poll.endsAt") }} <time datetime="{{ item.pollEndTime }}">{{ item.pollEndTime | date("PPp") }}</time>
{% endif %}
</div>
</div>
{% endif %}