fix: ensure attachment is always an array for Mastodon compatibility

Fedify's JSON-LD compaction collapses single-element arrays to plain
objects. Mastodon checks `attachment.is_a?(Array)` and silently skips
non-array values, causing profile links to never display.

Also adds profile links section to the my-profile admin page and
fixes rel=me on the public profile page for bidirectional verification.
This commit is contained in:
Ricardo
2026-02-24 11:39:42 +01:00
parent 4e159bfb9d
commit df5ccc397e
5 changed files with 66 additions and 2 deletions

View File

@@ -1281,6 +1281,45 @@
.ap-my-profile__bio .invisible { display: none; } .ap-my-profile__bio .invisible { display: none; }
.ap-my-profile__bio .ellipsis::after { content: "…"; } .ap-my-profile__bio .ellipsis::after { content: "…"; }
.ap-my-profile__fields {
border: var(--border-width-thin) solid var(--color-outline);
border-radius: var(--border-radius-small);
margin: var(--space-s) 0;
overflow: hidden;
}
.ap-my-profile__field {
border-bottom: var(--border-width-thin) solid var(--color-outline);
display: grid;
grid-template-columns: 120px 1fr;
}
.ap-my-profile__field:last-child {
border-bottom: 0;
}
.ap-my-profile__field-name {
background: var(--color-offset);
color: var(--color-on-offset);
font-size: var(--font-size-s);
font-weight: 600;
padding: var(--space-xs) var(--space-s);
text-transform: uppercase;
letter-spacing: 0.03em;
}
.ap-my-profile__field-value {
font-size: var(--font-size-s);
overflow: hidden;
padding: var(--space-xs) var(--space-s);
text-overflow: ellipsis;
white-space: nowrap;
}
.ap-my-profile__field-value a {
color: var(--color-primary);
}
.ap-my-profile__stats { .ap-my-profile__stats {
display: flex; display: flex;
gap: var(--space-m); gap: var(--space-m);

View File

@@ -89,6 +89,14 @@ async function sendFedifyResponse(res, response, request) {
if (json.endpoints?.type) { if (json.endpoints?.type) {
delete json.endpoints.type; delete json.endpoints.type;
} }
// WORKAROUND: Fedify's JSON-LD compaction collapses single-element
// arrays to a plain object. Mastodon's update_account_fields checks
// `attachment.is_a?(Array)` and skips if it's not an array, so
// profile links/PropertyValues are silently ignored.
// Force `attachment` to always be an array for Mastodon compatibility.
if (json.attachment && !Array.isArray(json.attachment)) {
json.attachment = [json.attachment];
}
const patched = JSON.stringify(json); const patched = JSON.stringify(json);
res.setHeader("content-length", Buffer.byteLength(patched)); res.setHeader("content-length", Buffer.byteLength(patched));
res.end(patched); res.end(patched);

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rmdes/indiekit-endpoint-activitypub", "name": "@rmdes/indiekit-endpoint-activitypub",
"version": "2.0.20", "version": "2.0.21",
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.", "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
"keywords": [ "keywords": [
"indiekit", "indiekit",

View File

@@ -26,6 +26,23 @@
{% if profile.summary %} {% if profile.summary %}
<div class="ap-my-profile__bio">{{ profile.summary | safe }}</div> <div class="ap-my-profile__bio">{{ profile.summary | safe }}</div>
{% endif %} {% endif %}
{% if profile.attachments and profile.attachments.length > 0 %}
<dl class="ap-my-profile__fields">
{% for field in profile.attachments %}
<div class="ap-my-profile__field">
<dt class="ap-my-profile__field-name">{{ field.name }}</dt>
<dd class="ap-my-profile__field-value">
{% if field.value and (field.value.startsWith("http://") or field.value.startsWith("https://")) %}
<a href="{{ field.value }}" rel="me noopener" target="_blank">{{ field.value | replace("https://", "") | replace("http://", "") }}</a>
{% else %}
{{ field.value }}
{% endif %}
</dd>
</div>
{% endfor %}
</dl>
{% endif %}
</div> </div>
<div class="ap-my-profile__stats"> <div class="ap-my-profile__stats">

View File

@@ -480,7 +480,7 @@
<dt class="ap-pub__field-name">{{ field.name }}</dt> <dt class="ap-pub__field-name">{{ field.name }}</dt>
<dd class="ap-pub__field-value"> <dd class="ap-pub__field-value">
{% if field.value and (field.value.startsWith("http://") or field.value.startsWith("https://")) %} {% if field.value and (field.value.startsWith("http://") or field.value.startsWith("https://")) %}
<a href="{{ field.value }}" rel="noopener nofollow" target="_blank">{{ field.value | replace("https://", "") | replace("http://", "") }}</a> <a href="{{ field.value }}" rel="me noopener" target="_blank">{{ field.value | replace("https://", "") | replace("http://", "") }}</a>
{% else %} {% else %}
{{ field.value }} {{ field.value }}
{% endif %} {% endif %}