feat: add breadcrumb navigation across all ActivityPub UI pages
Document.njk pages (followers, following, activities, featured, tags, profile, migrate) get parent breadcrumbs via the upstream heading component. Reader pages (explore, notifications, compose, moderation, tag timeline, post detail, remote profile, my profile) get a new breadcrumb nav bar in ap-reader.njk layout.
This commit is contained in:
@@ -4,6 +4,37 @@
|
||||
* Uses Indiekit CSS custom properties for automatic dark mode support
|
||||
*/
|
||||
|
||||
/* ==========================================================================
|
||||
Breadcrumb Navigation
|
||||
========================================================================== */
|
||||
|
||||
.ap-breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
margin-bottom: var(--space-m);
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-on-offset);
|
||||
}
|
||||
|
||||
.ap-breadcrumb a {
|
||||
color: var(--color-accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ap-breadcrumb a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.ap-breadcrumb__separator {
|
||||
color: var(--color-on-offset);
|
||||
}
|
||||
|
||||
.ap-breadcrumb__current {
|
||||
color: var(--color-on-background);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Fediverse Lookup
|
||||
========================================================================== */
|
||||
|
||||
@@ -12,6 +12,7 @@ export function activitiesController(mountPath) {
|
||||
if (!collection) {
|
||||
return response.render("activitypub-activities", {
|
||||
title: response.locals.__("activitypub.activities"),
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
activities: [],
|
||||
mountPath,
|
||||
});
|
||||
@@ -32,6 +33,7 @@ export function activitiesController(mountPath) {
|
||||
|
||||
response.render("activitypub-activities", {
|
||||
title: response.locals.__("activitypub.activities"),
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
activities,
|
||||
mountPath,
|
||||
cursor,
|
||||
|
||||
@@ -141,6 +141,7 @@ export function composeController(mountPath, plugin) {
|
||||
|
||||
response.render("activitypub-compose", {
|
||||
title: response.locals.__("activitypub.compose.title"),
|
||||
readerParent: { href: `${mountPath}/admin/reader`, text: response.locals.__("activitypub.reader.title") },
|
||||
replyTo,
|
||||
replyContext,
|
||||
syndicationTargets,
|
||||
|
||||
@@ -141,10 +141,13 @@ export function exploreController(mountPath) {
|
||||
const csrfToken = getToken(request.session);
|
||||
const deckCount = decks.length;
|
||||
|
||||
const readerParent = { href: `${mountPath}/admin/reader`, text: response.locals.__("activitypub.reader.title") };
|
||||
|
||||
// No instance specified — render clean initial page (no error)
|
||||
if (!rawInstance.trim()) {
|
||||
return response.render("activitypub-explore", {
|
||||
title: response.locals.__("activitypub.reader.explore.title"),
|
||||
readerParent,
|
||||
instance: "",
|
||||
scope,
|
||||
items: [],
|
||||
@@ -163,6 +166,7 @@ export function exploreController(mountPath) {
|
||||
if (!instance) {
|
||||
return response.render("activitypub-explore", {
|
||||
title: response.locals.__("activitypub.reader.explore.title"),
|
||||
readerParent,
|
||||
instance: rawInstance,
|
||||
scope,
|
||||
items: [],
|
||||
@@ -228,6 +232,7 @@ export function exploreController(mountPath) {
|
||||
|
||||
response.render("activitypub-explore", {
|
||||
title: response.locals.__("activitypub.reader.explore.title"),
|
||||
readerParent,
|
||||
instance,
|
||||
scope,
|
||||
items,
|
||||
|
||||
@@ -15,6 +15,7 @@ export function featuredTagsGetController(mountPath) {
|
||||
response.render("activitypub-featured-tags", {
|
||||
title:
|
||||
response.locals.__("activitypub.featuredTags") || "Featured Tags",
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
tags,
|
||||
mountPath,
|
||||
});
|
||||
|
||||
@@ -57,6 +57,7 @@ export function featuredGetController(mountPath) {
|
||||
|
||||
response.render("activitypub-featured", {
|
||||
title: response.locals.__("activitypub.featured") || "Pinned Posts",
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
pinned,
|
||||
availablePosts,
|
||||
maxPins: MAX_PINS,
|
||||
|
||||
@@ -12,6 +12,7 @@ export function followersController(mountPath) {
|
||||
if (!collection) {
|
||||
return response.render("activitypub-followers", {
|
||||
title: response.locals.__("activitypub.followers"),
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
followers: [],
|
||||
followerCount: 0,
|
||||
mountPath,
|
||||
@@ -33,6 +34,7 @@ export function followersController(mountPath) {
|
||||
|
||||
response.render("activitypub-followers", {
|
||||
title: `${totalCount} ${response.locals.__("activitypub.followers")}`,
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
followers,
|
||||
followerCount: totalCount,
|
||||
mountPath,
|
||||
|
||||
@@ -12,6 +12,7 @@ export function followingController(mountPath) {
|
||||
if (!collection) {
|
||||
return response.render("activitypub-following", {
|
||||
title: response.locals.__("activitypub.following"),
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
following: [],
|
||||
followingCount: 0,
|
||||
mountPath,
|
||||
@@ -33,6 +34,7 @@ export function followingController(mountPath) {
|
||||
|
||||
response.render("activitypub-following", {
|
||||
title: `${totalCount} ${response.locals.__("activitypub.following")}`,
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
following,
|
||||
followingCount: totalCount,
|
||||
mountPath,
|
||||
|
||||
@@ -24,6 +24,7 @@ export function migrateGetController(mountPath, pluginOptions) {
|
||||
|
||||
response.render("activitypub-migrate", {
|
||||
title: response.locals.__("activitypub.migrate.title"),
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
mountPath,
|
||||
currentAlias,
|
||||
result: null,
|
||||
@@ -61,6 +62,7 @@ export function migratePostController(mountPath, pluginOptions) {
|
||||
|
||||
response.render("activitypub-migrate", {
|
||||
title: response.locals.__("activitypub.migrate.title"),
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
mountPath,
|
||||
currentAlias,
|
||||
result,
|
||||
|
||||
@@ -301,6 +301,7 @@ export function moderationController(mountPath) {
|
||||
|
||||
response.render("activitypub-moderation", {
|
||||
title: response.locals.__("activitypub.moderation.title"),
|
||||
readerParent: { href: `${mountPath}/admin/reader`, text: response.locals.__("activitypub.reader.title") },
|
||||
muted,
|
||||
blocked,
|
||||
mutedActors,
|
||||
|
||||
@@ -219,6 +219,7 @@ export function myProfileController(plugin) {
|
||||
|
||||
response.render("activitypub-my-profile", {
|
||||
title: response.locals.__("activitypub.myProfile.title"),
|
||||
readerParent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
profile: profile || {},
|
||||
handle,
|
||||
domain,
|
||||
|
||||
@@ -197,6 +197,7 @@ export function postDetailController(mountPath, plugin) {
|
||||
// Truly not found (no local item either)
|
||||
return response.status(404).render("activitypub-post-detail", {
|
||||
title: response.locals.__("activitypub.reader.post.title"),
|
||||
readerParent: { href: `${mountPath}/admin/reader`, text: response.locals.__("activitypub.reader.title") },
|
||||
notFound: true, objectUrl, mountPath,
|
||||
item: null, interactionMap: {}, csrfToken: null,
|
||||
parentPosts: [], replyPosts: [],
|
||||
@@ -318,6 +319,7 @@ export function postDetailController(mountPath, plugin) {
|
||||
|
||||
response.render("activitypub-post-detail", {
|
||||
title: response.locals.__("activitypub.reader.post.title"),
|
||||
readerParent: { href: `${mountPath}/admin/reader`, text: response.locals.__("activitypub.reader.title") },
|
||||
item: timelineItem,
|
||||
interactionMap,
|
||||
csrfToken,
|
||||
|
||||
@@ -18,6 +18,7 @@ export function profileGetController(mountPath) {
|
||||
|
||||
response.render("activitypub-profile", {
|
||||
title: response.locals.__("activitypub.profile.title"),
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
mountPath,
|
||||
profile,
|
||||
actorTypes: ACTOR_TYPES,
|
||||
@@ -96,6 +97,7 @@ export function profilePostController(mountPath, plugin) {
|
||||
|
||||
response.render("activitypub-profile", {
|
||||
title: response.locals.__("activitypub.profile.title"),
|
||||
parent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
mountPath,
|
||||
profile,
|
||||
actorTypes: ACTOR_TYPES,
|
||||
|
||||
@@ -126,6 +126,7 @@ export function remoteProfileController(mountPath, plugin) {
|
||||
|
||||
response.render("activitypub-remote-profile", {
|
||||
title: name,
|
||||
readerParent: { href: `${mountPath}/admin/reader`, text: response.locals.__("activitypub.reader.title") },
|
||||
actorUrl,
|
||||
name,
|
||||
actorHandle,
|
||||
|
||||
@@ -203,6 +203,7 @@ export function readerController(mountPath) {
|
||||
|
||||
response.render("activitypub-reader", {
|
||||
title: response.locals.__("activitypub.reader.title"),
|
||||
readerParent: { href: mountPath, text: response.locals.__("activitypub.title") },
|
||||
items,
|
||||
tab,
|
||||
before: result.before,
|
||||
@@ -253,6 +254,7 @@ export function notificationsController(mountPath) {
|
||||
|
||||
response.render("activitypub-notifications", {
|
||||
title: response.locals.__("activitypub.notifications.title"),
|
||||
readerParent: { href: `${mountPath}/admin/reader`, text: response.locals.__("activitypub.reader.title") },
|
||||
items: result.items,
|
||||
before: result.before,
|
||||
tab,
|
||||
|
||||
@@ -131,6 +131,7 @@ export function tagTimelineController(mountPath) {
|
||||
|
||||
response.render("activitypub-tag-timeline", {
|
||||
title: `#${tag}`,
|
||||
readerParent: { href: `${mountPath}/admin/reader`, text: response.locals.__("activitypub.reader.title") },
|
||||
tag,
|
||||
items,
|
||||
before: result.before,
|
||||
|
||||
@@ -18,6 +18,14 @@
|
||||
{# AP link interception for internal navigation #}
|
||||
<script defer src="/assets/@rmdes-indiekit-endpoint-activitypub/reader-links.js"></script>
|
||||
|
||||
{% if readerParent %}
|
||||
<nav class="ap-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="{{ readerParent.href }}">{{ readerParent.text }}</a>
|
||||
<span class="ap-breadcrumb__separator" aria-hidden="true">/</span>
|
||||
<span class="ap-breadcrumb__current" aria-current="page">{{ title }}</span>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
{% block readercontent %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user