mirror of
https://github.com/svemagie/indiekit-endpoint-activitypub.git
synced 2026-04-02 15:44:58 +02:00
document.njk already renders title as h1 via the heading macro. All 14 AP templates were also calling heading() with level 1 inside their content block, producing two h1 elements per page. Removed the redundant calls and moved dynamic count prefixes into the title variable in followers/following controllers.
161 lines
6.9 KiB
Plaintext
161 lines
6.9 KiB
Plaintext
{% extends "document.njk" %}
|
|
|
|
{% from "heading/macro.njk" import heading with context %}
|
|
{% from "card/macro.njk" import card with context %}
|
|
{% from "card-grid/macro.njk" import cardGrid with context %}
|
|
{% from "prose/macro.njk" import prose with context %}
|
|
{% from "badge/macro.njk" import badge with context %}
|
|
|
|
{% block content %}
|
|
{{ cardGrid({ cardSize: "16rem", items: [
|
|
{
|
|
title: followerCount + " " + __("activitypub.followers"),
|
|
url: mountPath + "/admin/followers"
|
|
},
|
|
{
|
|
title: followingCount + " " + __("activitypub.following"),
|
|
url: mountPath + "/admin/following"
|
|
},
|
|
{
|
|
title: __("activitypub.activities"),
|
|
url: mountPath + "/admin/activities"
|
|
},
|
|
{
|
|
title: pinnedCount + " " + __("activitypub.featured"),
|
|
url: mountPath + "/admin/featured"
|
|
},
|
|
{
|
|
title: tagCount + " " + __("activitypub.featuredTags"),
|
|
url: mountPath + "/admin/tags"
|
|
},
|
|
{
|
|
title: __("activitypub.profile.title"),
|
|
url: mountPath + "/admin/profile"
|
|
},
|
|
{
|
|
title: __("activitypub.migrate.title"),
|
|
url: mountPath + "/admin/migrate"
|
|
}
|
|
]}) }}
|
|
|
|
{% if refollowStatus and refollowStatus.status !== "idle" %}
|
|
<section x-data="refollowProgress('{{ mountPath }}')" class="s-refollow" style="margin-block-end: var(--space-l);">
|
|
{{ heading({ text: __("activitypub.refollow.title"), level: 2 }) }}
|
|
|
|
{# Progress bar #}
|
|
<div style="background: var(--color-offset); border-radius: 4px; height: 1.5rem; margin-block-end: var(--space-m); overflow: hidden;">
|
|
<div
|
|
x-bind:style="'width:' + progress + '%; height: 100%; transition: width 0.5s ease; background: var(--color-primary);'"
|
|
style="width: {{ refollowStatus.progressPercent }}%; height: 100%; transition: width 0.5s ease; background: var(--color-primary);">
|
|
</div>
|
|
</div>
|
|
|
|
{# Stats grid #}
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: var(--space-s); margin-block-end: var(--space-m);">
|
|
<div style="padding: var(--space-s); background: var(--color-offset); border-radius: 4px; text-align: center;">
|
|
<div style="font-size: var(--font-size-xl);" x-text="remaining">{{ refollowStatus.remaining }}</div>
|
|
<div style="font-size: var(--font-size-s); color: var(--color-outline);">{{ __("activitypub.refollow.remaining") }}</div>
|
|
</div>
|
|
<div style="padding: var(--space-s); background: var(--color-offset); border-radius: 4px; text-align: center;">
|
|
<div style="font-size: var(--font-size-xl);" x-text="sent">{{ refollowStatus.sent }}</div>
|
|
<div style="font-size: var(--font-size-s); color: var(--color-outline);">{{ __("activitypub.refollow.awaitingAccept") }}</div>
|
|
</div>
|
|
<div style="padding: var(--space-s); background: var(--color-offset); border-radius: 4px; text-align: center;">
|
|
<div style="font-size: var(--font-size-xl);" x-text="federated">{{ refollowStatus.federated }}</div>
|
|
<div style="font-size: var(--font-size-s); color: var(--color-outline);">{{ __("activitypub.refollow.accepted") }}</div>
|
|
</div>
|
|
<div style="padding: var(--space-s); background: var(--color-offset); border-radius: 4px; text-align: center;">
|
|
<div style="font-size: var(--font-size-xl);" x-text="failed">{{ refollowStatus.failed }}</div>
|
|
<div style="font-size: var(--font-size-s); color: var(--color-outline);">{{ __("activitypub.refollow.failed") }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Status + controls #}
|
|
<div style="display: flex; align-items: center; gap: var(--space-s);">
|
|
<span class="badge" x-text="statusLabel">{{ __("activitypub.refollow.status." + refollowStatus.status) }}</span>
|
|
<form x-show="status === 'running'" method="post" action="{{ mountPath }}/admin/refollow/pause" x-on:submit.prevent="pause" style="display: none;">
|
|
<button type="submit" class="button" style="font-size: var(--font-size-s);">{{ __("activitypub.refollow.pause") }}</button>
|
|
</form>
|
|
<form x-show="status === 'paused'" method="post" action="{{ mountPath }}/admin/refollow/resume" x-on:submit.prevent="resume" style="display: none;">
|
|
<button type="submit" class="button" style="font-size: var(--font-size-s);">{{ __("activitypub.refollow.resume") }}</button>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
|
|
<script>
|
|
function refollowProgress(mountPath) {
|
|
const statusLabels = {
|
|
idle: '{{ __("activitypub.refollow.status.idle") }}',
|
|
running: '{{ __("activitypub.refollow.status.running") }}',
|
|
paused: '{{ __("activitypub.refollow.status.paused") }}',
|
|
completed: '{{ __("activitypub.refollow.status.completed") }}'
|
|
};
|
|
return {
|
|
progress: {{ refollowStatus.progressPercent }},
|
|
remaining: {{ refollowStatus.remaining }},
|
|
sent: {{ refollowStatus.sent }},
|
|
federated: {{ refollowStatus.federated }},
|
|
failed: {{ refollowStatus.failed }},
|
|
status: '{{ refollowStatus.status }}',
|
|
interval: null,
|
|
get statusLabel() {
|
|
return statusLabels[this.status] || this.status;
|
|
},
|
|
init() {
|
|
if (this.status === 'running' || this.status === 'paused') {
|
|
this.interval = setInterval(() => this.poll(), 10000);
|
|
}
|
|
},
|
|
destroy() {
|
|
if (this.interval) clearInterval(this.interval);
|
|
},
|
|
async poll() {
|
|
try {
|
|
const res = await fetch(mountPath + '/admin/refollow/status');
|
|
if (!res.ok) return;
|
|
const data = await res.json();
|
|
this.progress = data.progressPercent;
|
|
this.remaining = data.remaining;
|
|
this.sent = data.sent;
|
|
this.federated = data.federated;
|
|
this.failed = data.failed;
|
|
this.status = data.status;
|
|
if (data.status === 'completed' || data.status === 'idle') {
|
|
clearInterval(this.interval);
|
|
}
|
|
} catch {}
|
|
},
|
|
async pause() {
|
|
const res = await fetch(mountPath + '/admin/refollow/pause', { method: 'POST' });
|
|
if (res.ok) this.status = 'paused';
|
|
},
|
|
async resume() {
|
|
const res = await fetch(mountPath + '/admin/refollow/resume', { method: 'POST' });
|
|
if (res.ok) {
|
|
this.status = 'running';
|
|
if (!this.interval) {
|
|
this.interval = setInterval(() => this.poll(), 10000);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
{% endif %}
|
|
|
|
{{ heading({ text: __("activitypub.recentActivity"), level: 2 }) }}
|
|
|
|
{% if recentActivities.length > 0 %}
|
|
{% for activity in recentActivities %}
|
|
{{ card({
|
|
title: activity.actorName or activity.actorUrl,
|
|
description: { text: activity.summary },
|
|
published: activity.receivedAt,
|
|
badges: [{ text: activity.type }]
|
|
}) }}
|
|
{% endfor %}
|
|
{% else %}
|
|
{{ prose({ text: __("activitypub.noActivity") }) }}
|
|
{% endif %}
|
|
{% endblock %}
|