fix: reader UI — navigation, Alpine.js loading, avatar fallback, Temporal dates
- Return multiple navigation items (ActivityPub, Reader, Notifications, Moderation)
so all AP sub-pages are accessible from the sidebar
- Fix Alpine.js not loading: `{% block head %}` was silently discarded because
the parent template chain has no such block — moved script/css into content block
- Pin Alpine.js to exact version 3.14.9 to prevent CDN resolution issues
- Add fallback avatar (first letter) when author photo is missing
- Guard empty author URLs to prevent broken links
- Fix Temporal.Instant TypeError: use String() instead of new Date() for
Fedify published timestamps in inbox-listeners and timeline-store
- Link author names to remote profile view instead of raw AP URLs
- Bump to 1.1.3
This commit is contained in:
@@ -123,6 +123,16 @@
|
|||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ap-card__avatar--default {
|
||||||
|
align-items: center;
|
||||||
|
background: var(--color-offset);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 600;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.ap-card__author-info {
|
.ap-card__author-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -95,11 +95,28 @@ export default class ActivityPubEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get navigationItems() {
|
get navigationItems() {
|
||||||
return {
|
return [
|
||||||
href: `${this.options.mountPath}/admin/reader`,
|
{
|
||||||
text: "activitypub.reader.title",
|
href: this.options.mountPath,
|
||||||
requiresDatabase: true,
|
text: "activitypub.title",
|
||||||
};
|
requiresDatabase: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: `${this.options.mountPath}/admin/reader`,
|
||||||
|
text: "activitypub.reader.title",
|
||||||
|
requiresDatabase: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: `${this.options.mountPath}/admin/reader/notifications`,
|
||||||
|
text: "activitypub.notifications.title",
|
||||||
|
requiresDatabase: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: `${this.options.mountPath}/admin/reader/moderation`,
|
||||||
|
text: "activitypub.moderation.title",
|
||||||
|
requiresDatabase: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
actorName: followerInfo.name,
|
actorName: followerInfo.name,
|
||||||
actorPhoto: followerInfo.photo,
|
actorPhoto: followerInfo.photo,
|
||||||
actorHandle: followerInfo.handle,
|
actorHandle: followerInfo.handle,
|
||||||
published: follow.published ? new Date(follow.published) : new Date(),
|
published: follow.published ? String(follow.published) : new Date().toISOString(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -258,7 +258,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
actorHandle: actorInfo.handle,
|
actorHandle: actorInfo.handle,
|
||||||
targetUrl: objectId,
|
targetUrl: objectId,
|
||||||
targetName: "", // Could fetch post title, but not critical
|
targetName: "", // Could fetch post title, but not critical
|
||||||
published: like.published ? new Date(like.published) : new Date(),
|
published: like.published ? String(like.published) : new Date().toISOString(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -306,7 +306,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
actorHandle: actorInfo.handle,
|
actorHandle: actorInfo.handle,
|
||||||
targetUrl: objectId,
|
targetUrl: objectId,
|
||||||
targetName: "", // Could fetch post title, but not critical
|
targetName: "", // Could fetch post title, but not critical
|
||||||
published: announce.published ? new Date(announce.published).toISOString() : new Date().toISOString(),
|
published: announce.published ? String(announce.published) : new Date().toISOString(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -328,7 +328,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
// Extract and store with boost metadata
|
// Extract and store with boost metadata
|
||||||
const timelineItem = await extractObjectData(object, {
|
const timelineItem = await extractObjectData(object, {
|
||||||
boostedBy: boosterInfo,
|
boostedBy: boosterInfo,
|
||||||
boostedAt: announce.published ? new Date(announce.published).toISOString() : new Date().toISOString(),
|
boostedAt: announce.published ? String(announce.published) : new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
await addTimelineItem(collections, timelineItem);
|
await addTimelineItem(collections, timelineItem);
|
||||||
@@ -404,7 +404,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
text: contentText,
|
text: contentText,
|
||||||
html: contentHtml,
|
html: contentHtml,
|
||||||
},
|
},
|
||||||
published: object.published ? new Date(object.published).toISOString() : new Date().toISOString(),
|
published: object.published ? String(object.published) : new Date().toISOString(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -433,7 +433,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
text: contentText,
|
text: contentText,
|
||||||
html: mentionHtml,
|
html: mentionHtml,
|
||||||
},
|
},
|
||||||
published: object.published ? new Date(object.published).toISOString() : new Date().toISOString(),
|
published: object.published ? String(object.published) : new Date().toISOString(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export async function extractObjectData(object, options = {}) {
|
|||||||
|
|
||||||
// Published date — store as ISO string per Indiekit convention
|
// Published date — store as ISO string per Indiekit convention
|
||||||
const published = object.published
|
const published = object.published
|
||||||
? new Date(object.published).toISOString()
|
? String(object.published)
|
||||||
: new Date().toISOString();
|
: new Date().toISOString();
|
||||||
|
|
||||||
// Extract author — use async getAttributedTo() for Fedify objects
|
// Extract author — use async getAttributedTo() for Fedify objects
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
||||||
"version": "1.1.1",
|
"version": "1.1.3",
|
||||||
"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",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% from "heading/macro.njk" import heading with context %}
|
{% from "heading/macro.njk" import heading with context %}
|
||||||
|
|
||||||
{% block content %}
|
{% block readercontent %}
|
||||||
{{ heading({
|
{{ heading({
|
||||||
text: title,
|
text: title,
|
||||||
level: 1,
|
level: 1,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% from "heading/macro.njk" import heading with context %}
|
{% from "heading/macro.njk" import heading with context %}
|
||||||
{% from "prose/macro.njk" import prose with context %}
|
{% from "prose/macro.njk" import prose with context %}
|
||||||
|
|
||||||
{% block content %}
|
{% block readercontent %}
|
||||||
{{ heading({
|
{{ heading({
|
||||||
text: title,
|
text: title,
|
||||||
level: 1,
|
level: 1,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% from "heading/macro.njk" import heading with context %}
|
{% from "heading/macro.njk" import heading with context %}
|
||||||
{% from "prose/macro.njk" import prose with context %}
|
{% from "prose/macro.njk" import prose with context %}
|
||||||
|
|
||||||
{% block content %}
|
{% block readercontent %}
|
||||||
{{ heading({
|
{{ heading({
|
||||||
text: __("activitypub.notifications.title"),
|
text: __("activitypub.notifications.title"),
|
||||||
level: 1,
|
level: 1,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% from "heading/macro.njk" import heading with context %}
|
{% from "heading/macro.njk" import heading with context %}
|
||||||
{% from "prose/macro.njk" import prose with context %}
|
{% from "prose/macro.njk" import prose with context %}
|
||||||
|
|
||||||
{% block content %}
|
{% block readercontent %}
|
||||||
{{ heading({
|
{{ heading({
|
||||||
text: __("activitypub.reader.title"),
|
text: __("activitypub.reader.title"),
|
||||||
level: 1,
|
level: 1,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% from "heading/macro.njk" import heading with context %}
|
{% from "heading/macro.njk" import heading with context %}
|
||||||
{% from "prose/macro.njk" import prose with context %}
|
{% from "prose/macro.njk" import prose with context %}
|
||||||
|
|
||||||
{% block content %}
|
{% block readercontent %}
|
||||||
{{ heading({
|
{{ heading({
|
||||||
text: title,
|
text: title,
|
||||||
level: 1,
|
level: 1,
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
{% extends "document.njk" %}
|
{% extends "document.njk" %}
|
||||||
|
|
||||||
{% block head %}
|
{% block content %}
|
||||||
{# Alpine.js for client-side reactivity #}
|
{# Alpine.js for client-side reactivity (CW toggles, interaction buttons) #}
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14/dist/cdn.min.js"></script>
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js"></script>
|
||||||
|
|
||||||
{# Reader stylesheet #}
|
{# Reader stylesheet — loaded in body is fine for modern browsers #}
|
||||||
<link rel="stylesheet" href="/assets/@rmdes-indiekit-endpoint-activitypub/reader.css">
|
<link rel="stylesheet" href="/assets/@rmdes-indiekit-endpoint-activitypub/reader.css">
|
||||||
|
|
||||||
|
{% block readercontent %}
|
||||||
|
{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{# Boost header if this is a boosted post #}
|
{# Boost header if this is a boosted post #}
|
||||||
{% if item.type == "boost" and item.boostedBy %}
|
{% if item.type == "boost" and item.boostedBy %}
|
||||||
<div class="ap-card__boost">
|
<div class="ap-card__boost">
|
||||||
🔁 <a href="{{ item.boostedBy.url }}">{{ item.boostedBy.name }}</a> {{ __("activitypub.reader.boosted") }}
|
🔁 {% if item.boostedBy.url %}<a href="{{ mountPath }}/admin/reader/profile?url={{ item.boostedBy.url | urlencode }}">{{ item.boostedBy.name or "Someone" }}</a>{% else %}{{ item.boostedBy.name or "Someone" }}{% endif %} {{ __("activitypub.reader.boosted") }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -17,16 +17,28 @@
|
|||||||
|
|
||||||
{# Author header #}
|
{# Author header #}
|
||||||
<header class="ap-card__author">
|
<header class="ap-card__author">
|
||||||
<img src="{{ item.author.photo }}" alt="{{ item.author.name }}" class="ap-card__avatar">
|
{% if item.author.photo %}
|
||||||
|
<img src="{{ item.author.photo }}" alt="{{ item.author.name }}" class="ap-card__avatar" loading="lazy">
|
||||||
|
{% else %}
|
||||||
|
<span class="ap-card__avatar ap-card__avatar--default" aria-hidden="true">{{ item.author.name[0] | upper if item.author.name else "?" }}</span>
|
||||||
|
{% endif %}
|
||||||
<div class="ap-card__author-info">
|
<div class="ap-card__author-info">
|
||||||
<div class="ap-card__author-name">
|
<div class="ap-card__author-name">
|
||||||
<a href="{{ item.author.url }}">{{ item.author.name }}</a>
|
{% if item.author.url %}
|
||||||
|
<a href="{{ mountPath }}/admin/reader/profile?url={{ item.author.url | urlencode }}">{{ item.author.name or "Unknown" }}</a>
|
||||||
|
{% else %}
|
||||||
|
<span>{{ item.author.name or "Unknown" }}</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="ap-card__author-handle">{{ item.author.handle }}</div>
|
{% if item.author.handle %}
|
||||||
|
<div class="ap-card__author-handle">{{ item.author.handle }}</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<time datetime="{{ item.published }}" class="ap-card__timestamp">
|
{% if item.published %}
|
||||||
{{ item.published | date("PPp") }}
|
<time datetime="{{ item.published }}" class="ap-card__timestamp">
|
||||||
</time>
|
{{ item.published | date("PPp") }}
|
||||||
|
</time>
|
||||||
|
{% endif %}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{# Post title (articles only) #}
|
{# Post title (articles only) #}
|
||||||
|
|||||||
Reference in New Issue
Block a user