@rmdes/indiekit-endpoint-activitypub 2.0.20 → 2.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/assets/reader.css CHANGED
@@ -1281,6 +1281,45 @@
1281
1281
  .ap-my-profile__bio .invisible { display: none; }
1282
1282
  .ap-my-profile__bio .ellipsis::after { content: "…"; }
1283
1283
 
1284
+ .ap-my-profile__fields {
1285
+ border: var(--border-width-thin) solid var(--color-outline);
1286
+ border-radius: var(--border-radius-small);
1287
+ margin: var(--space-s) 0;
1288
+ overflow: hidden;
1289
+ }
1290
+
1291
+ .ap-my-profile__field {
1292
+ border-bottom: var(--border-width-thin) solid var(--color-outline);
1293
+ display: grid;
1294
+ grid-template-columns: 120px 1fr;
1295
+ }
1296
+
1297
+ .ap-my-profile__field:last-child {
1298
+ border-bottom: 0;
1299
+ }
1300
+
1301
+ .ap-my-profile__field-name {
1302
+ background: var(--color-offset);
1303
+ color: var(--color-on-offset);
1304
+ font-size: var(--font-size-s);
1305
+ font-weight: 600;
1306
+ padding: var(--space-xs) var(--space-s);
1307
+ text-transform: uppercase;
1308
+ letter-spacing: 0.03em;
1309
+ }
1310
+
1311
+ .ap-my-profile__field-value {
1312
+ font-size: var(--font-size-s);
1313
+ overflow: hidden;
1314
+ padding: var(--space-xs) var(--space-s);
1315
+ text-overflow: ellipsis;
1316
+ white-space: nowrap;
1317
+ }
1318
+
1319
+ .ap-my-profile__field-value a {
1320
+ color: var(--color-primary);
1321
+ }
1322
+
1284
1323
  .ap-my-profile__stats {
1285
1324
  display: flex;
1286
1325
  gap: var(--space-m);
@@ -89,6 +89,14 @@ async function sendFedifyResponse(res, response, request) {
89
89
  if (json.endpoints?.type) {
90
90
  delete json.endpoints.type;
91
91
  }
92
+ // WORKAROUND: Fedify's JSON-LD compaction collapses single-element
93
+ // arrays to a plain object. Mastodon's update_account_fields checks
94
+ // `attachment.is_a?(Array)` and skips if it's not an array, so
95
+ // profile links/PropertyValues are silently ignored.
96
+ // Force `attachment` to always be an array for Mastodon compatibility.
97
+ if (json.attachment && !Array.isArray(json.attachment)) {
98
+ json.attachment = [json.attachment];
99
+ }
92
100
  const patched = JSON.stringify(json);
93
101
  res.setHeader("content-length", Buffer.byteLength(patched));
94
102
  res.end(patched);
@@ -721,6 +721,20 @@ export async function buildPersonActor(
721
721
  personOptions.assertionMethods = keyPairs.map((k) => k.multikey);
722
722
  }
723
723
 
724
+ // Build profile field attachments (PropertyValue).
725
+ // Always include a "Fediverse" field with the actor's handle — this serves
726
+ // two purposes: (1) shows the canonical fediverse address on the profile,
727
+ // and (2) ensures 2+ attachments when combined with user-defined fields,
728
+ // preventing Fedify's JSON-LD compaction from collapsing single-element
729
+ // arrays to plain objects (which Mastodon's update_account_fields rejects).
730
+ const actorUrl = ctx.getActorUri(identifier)?.href;
731
+ const fediverseField = actorUrl
732
+ ? new PropertyValue({
733
+ name: "Fediverse",
734
+ value: `<a href="${actorUrl}" rel="me">@${identifier}@${new URL(actorUrl).hostname}</a>`,
735
+ })
736
+ : null;
737
+
724
738
  if (profile.attachments?.length > 0) {
725
739
  personOptions.attachments = profile.attachments.map(
726
740
  (att) =>
@@ -729,6 +743,12 @@ export async function buildPersonActor(
729
743
  value: formatAttachmentValue(att.value),
730
744
  }),
731
745
  );
746
+ // Append fediverse field if not already present in user-defined fields
747
+ if (fediverseField && !profile.attachments.some((a) => a.name === "Fediverse")) {
748
+ personOptions.attachments.push(fediverseField);
749
+ }
750
+ } else if (fediverseField) {
751
+ personOptions.attachments = [fediverseField];
732
752
  }
733
753
 
734
754
  if (profile.alsoKnownAs?.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-activitypub",
3
- "version": "2.0.20",
3
+ "version": "2.0.22",
4
4
  "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
5
5
  "keywords": [
6
6
  "indiekit",
@@ -26,6 +26,23 @@
26
26
  {% if profile.summary %}
27
27
  <div class="ap-my-profile__bio">{{ profile.summary | safe }}</div>
28
28
  {% endif %}
29
+
30
+ {% if profile.attachments and profile.attachments.length > 0 %}
31
+ <dl class="ap-my-profile__fields">
32
+ {% for field in profile.attachments %}
33
+ <div class="ap-my-profile__field">
34
+ <dt class="ap-my-profile__field-name">{{ field.name }}</dt>
35
+ <dd class="ap-my-profile__field-value">
36
+ {% if field.value and (field.value.startsWith("http://") or field.value.startsWith("https://")) %}
37
+ <a href="{{ field.value }}" rel="me noopener" target="_blank">{{ field.value | replace("https://", "") | replace("http://", "") }}</a>
38
+ {% else %}
39
+ {{ field.value }}
40
+ {% endif %}
41
+ </dd>
42
+ </div>
43
+ {% endfor %}
44
+ </dl>
45
+ {% endif %}
29
46
  </div>
30
47
 
31
48
  <div class="ap-my-profile__stats">
@@ -480,7 +480,7 @@
480
480
  <dt class="ap-pub__field-name">{{ field.name }}</dt>
481
481
  <dd class="ap-pub__field-value">
482
482
  {% if field.value and (field.value.startsWith("http://") or field.value.startsWith("https://")) %}
483
- <a href="{{ field.value }}" rel="noopener nofollow" target="_blank">{{ field.value | replace("https://", "") | replace("http://", "") }}</a>
483
+ <a href="{{ field.value }}" rel="me noopener" target="_blank">{{ field.value | replace("https://", "") | replace("http://", "") }}</a>
484
484
  {% else %}
485
485
  {{ field.value }}
486
486
  {% endif %}