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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -95,11 +95,28 @@ export default class ActivityPubEndpoint {
|
||||
}
|
||||
|
||||
get navigationItems() {
|
||||
return {
|
||||
href: `${this.options.mountPath}/admin/reader`,
|
||||
text: "activitypub.reader.title",
|
||||
requiresDatabase: true,
|
||||
};
|
||||
return [
|
||||
{
|
||||
href: this.options.mountPath,
|
||||
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,
|
||||
actorPhoto: followerInfo.photo,
|
||||
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(),
|
||||
});
|
||||
})
|
||||
@@ -258,7 +258,7 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
actorHandle: actorInfo.handle,
|
||||
targetUrl: objectId,
|
||||
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(),
|
||||
});
|
||||
})
|
||||
@@ -306,7 +306,7 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
actorHandle: actorInfo.handle,
|
||||
targetUrl: objectId,
|
||||
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(),
|
||||
});
|
||||
|
||||
@@ -328,7 +328,7 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
// Extract and store with boost metadata
|
||||
const timelineItem = await extractObjectData(object, {
|
||||
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);
|
||||
@@ -404,7 +404,7 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
text: contentText,
|
||||
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(),
|
||||
});
|
||||
}
|
||||
@@ -433,7 +433,7 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
text: contentText,
|
||||
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(),
|
||||
});
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ export async function extractObjectData(object, options = {}) {
|
||||
|
||||
// Published date — store as ISO string per Indiekit convention
|
||||
const published = object.published
|
||||
? new Date(object.published).toISOString()
|
||||
? String(object.published)
|
||||
: new Date().toISOString();
|
||||
|
||||
// Extract author — use async getAttributedTo() for Fedify objects
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"keywords": [
|
||||
"indiekit",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{% from "heading/macro.njk" import heading with context %}
|
||||
|
||||
{% block content %}
|
||||
{% block readercontent %}
|
||||
{{ heading({
|
||||
text: title,
|
||||
level: 1,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% from "heading/macro.njk" import heading with context %}
|
||||
{% from "prose/macro.njk" import prose with context %}
|
||||
|
||||
{% block content %}
|
||||
{% block readercontent %}
|
||||
{{ heading({
|
||||
text: title,
|
||||
level: 1,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% from "heading/macro.njk" import heading with context %}
|
||||
{% from "prose/macro.njk" import prose with context %}
|
||||
|
||||
{% block content %}
|
||||
{% block readercontent %}
|
||||
{{ heading({
|
||||
text: __("activitypub.notifications.title"),
|
||||
level: 1,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% from "heading/macro.njk" import heading with context %}
|
||||
{% from "prose/macro.njk" import prose with context %}
|
||||
|
||||
{% block content %}
|
||||
{% block readercontent %}
|
||||
{{ heading({
|
||||
text: __("activitypub.reader.title"),
|
||||
level: 1,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% from "heading/macro.njk" import heading with context %}
|
||||
{% from "prose/macro.njk" import prose with context %}
|
||||
|
||||
{% block content %}
|
||||
{% block readercontent %}
|
||||
{{ heading({
|
||||
text: title,
|
||||
level: 1,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
{% extends "document.njk" %}
|
||||
|
||||
{% block head %}
|
||||
{# Alpine.js for client-side reactivity #}
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14/dist/cdn.min.js"></script>
|
||||
{% block content %}
|
||||
{# Alpine.js for client-side reactivity (CW toggles, interaction buttons) #}
|
||||
<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">
|
||||
|
||||
{% block readercontent %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{# Boost header if this is a boosted post #}
|
||||
{% if item.type == "boost" and item.boostedBy %}
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
@@ -17,16 +17,28 @@
|
||||
|
||||
{# Author header #}
|
||||
<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-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 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>
|
||||
<time datetime="{{ item.published }}" class="ap-card__timestamp">
|
||||
{{ item.published | date("PPp") }}
|
||||
</time>
|
||||
{% if item.published %}
|
||||
<time datetime="{{ item.published }}" class="ap-card__timestamp">
|
||||
{{ item.published | date("PPp") }}
|
||||
</time>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
{# Post title (articles only) #}
|
||||
|
||||
Reference in New Issue
Block a user