mirror of
https://github.com/svemagie/indiekit-endpoint-activitypub.git
synced 2026-04-02 15:44:58 +02:00
feat: skeleton loaders replace loading text (Release 6)
Animated card-shaped placeholders with shimmer effect shown during content loading instead of plain "Loading..." text. Applied to reader, tag timeline, and explore tabs (both first-load and load-more states). Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
This commit is contained in:
@@ -1595,6 +1595,81 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Skeleton Loaders
|
||||
========================================================================== */
|
||||
|
||||
@keyframes ap-skeleton-shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
.ap-skeleton {
|
||||
background: linear-gradient(90deg,
|
||||
var(--color-offset) 25%,
|
||||
var(--color-background) 50%,
|
||||
var(--color-offset) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: ap-skeleton-shimmer 1.5s ease-in-out infinite;
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
|
||||
.ap-card--skeleton {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ap-card--skeleton .ap-card__author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-s);
|
||||
}
|
||||
|
||||
.ap-skeleton--avatar {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ap-skeleton-lines {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.ap-skeleton--name {
|
||||
height: 0.85rem;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.ap-skeleton--handle {
|
||||
height: 0.7rem;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.ap-skeleton-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: var(--space-s);
|
||||
}
|
||||
|
||||
.ap-skeleton--line {
|
||||
height: 0.75rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ap-skeleton--short {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.ap-skeleton-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-m);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Responsive
|
||||
========================================================================== */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
||||
"version": "2.5.4",
|
||||
"version": "2.5.5",
|
||||
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
||||
"keywords": [
|
||||
"indiekit",
|
||||
|
||||
@@ -256,10 +256,14 @@
|
||||
x-data="apInfiniteScroll()"
|
||||
x-init="init()">
|
||||
<div class="ap-load-more__sentinel" x-ref="sentinel"></div>
|
||||
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done">
|
||||
<span x-show="!loading">{{ __("activitypub.reader.pagination.loadMore") }}</span>
|
||||
<span x-show="loading">{{ __("activitypub.reader.pagination.loading") }}</span>
|
||||
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done && !loading">
|
||||
{{ __("activitypub.reader.pagination.loadMore") }}
|
||||
</button>
|
||||
<div class="ap-skeleton-group" x-show="loading" x-cloak>
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
</div>
|
||||
<p class="ap-load-more__done" x-show="done" x-cloak>{{ __("activitypub.reader.pagination.noMore") }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -283,10 +287,12 @@
|
||||
<template x-if="tab.type === 'instance'">
|
||||
<div class="ap-explore-instance-panel">
|
||||
|
||||
{# Loading spinner — first load, no content yet #}
|
||||
<div class="ap-explore-tab-loading"
|
||||
{# Skeleton loaders — first load, no content yet #}
|
||||
<div class="ap-skeleton-group"
|
||||
x-show="tabState[tab._id] && tabState[tab._id].loading && !tabState[tab._id].html">
|
||||
<span class="ap-explore-tab-loading__text">{{ __("activitypub.reader.pagination.loading") }}</span>
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
</div>
|
||||
|
||||
{# Error state with retry #}
|
||||
@@ -304,7 +310,7 @@
|
||||
x-html="tabState[tab._id] ? tabState[tab._id].html : ''">
|
||||
</div>
|
||||
|
||||
{# Load more button + loading spinner for subsequent pages #}
|
||||
{# Load more button + skeleton loaders for subsequent pages #}
|
||||
<div class="ap-load-more"
|
||||
x-show="tabState[tab._id] && tabState[tab._id].html && !tabState[tab._id].done">
|
||||
<button class="ap-load-more__btn"
|
||||
@@ -313,10 +319,11 @@
|
||||
:disabled="tabState[tab._id]?.loading">
|
||||
{{ __("activitypub.reader.pagination.loadMore") }}
|
||||
</button>
|
||||
<span class="ap-explore-tab-loading__text"
|
||||
<div class="ap-skeleton-group"
|
||||
x-show="tabState[tab._id]?.loading">
|
||||
{{ __("activitypub.reader.pagination.loading") }}
|
||||
</span>
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Empty state — loaded successfully but no posts #}
|
||||
@@ -347,10 +354,12 @@
|
||||
x-text="hashtagSourcesLine(tab)"
|
||||
x-cloak></p>
|
||||
|
||||
{# Loading spinner — first load, no content yet #}
|
||||
<div class="ap-explore-tab-loading"
|
||||
{# Skeleton loaders — first load, no content yet #}
|
||||
<div class="ap-skeleton-group"
|
||||
x-show="tabState[tab._id] && tabState[tab._id].loading && !tabState[tab._id].html">
|
||||
<span class="ap-explore-tab-loading__text">{{ __("activitypub.reader.pagination.loading") }}</span>
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
</div>
|
||||
|
||||
{# Error state with retry #}
|
||||
@@ -368,7 +377,7 @@
|
||||
x-html="tabState[tab._id] ? tabState[tab._id].html : ''">
|
||||
</div>
|
||||
|
||||
{# Load more button + loading spinner for subsequent pages #}
|
||||
{# Load more button + skeleton loaders for subsequent pages #}
|
||||
<div class="ap-load-more"
|
||||
x-show="tabState[tab._id] && tabState[tab._id].html && !tabState[tab._id].done">
|
||||
<button class="ap-load-more__btn"
|
||||
@@ -377,10 +386,11 @@
|
||||
:disabled="tabState[tab._id]?.loading">
|
||||
{{ __("activitypub.reader.pagination.loadMore") }}
|
||||
</button>
|
||||
<span class="ap-explore-tab-loading__text"
|
||||
<div class="ap-skeleton-group"
|
||||
x-show="tabState[tab._id]?.loading">
|
||||
{{ __("activitypub.reader.pagination.loading") }}
|
||||
</span>
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Empty state — no instance tabs pinned yet #}
|
||||
|
||||
@@ -150,10 +150,14 @@
|
||||
x-data="apInfiniteScroll()"
|
||||
x-init="init()">
|
||||
<div class="ap-load-more__sentinel" x-ref="sentinel"></div>
|
||||
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done">
|
||||
<span x-show="!loading">{{ __("activitypub.reader.pagination.loadMore") }}</span>
|
||||
<span x-show="loading">{{ __("activitypub.reader.pagination.loading") }}</span>
|
||||
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done && !loading">
|
||||
{{ __("activitypub.reader.pagination.loadMore") }}
|
||||
</button>
|
||||
<div class="ap-skeleton-group" x-show="loading" x-cloak>
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
</div>
|
||||
<p class="ap-load-more__done" x-show="done" x-cloak>{{ __("activitypub.reader.pagination.noMore") }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -75,10 +75,14 @@
|
||||
x-data="apInfiniteScroll()"
|
||||
x-init="init()">
|
||||
<div class="ap-load-more__sentinel" x-ref="sentinel"></div>
|
||||
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done">
|
||||
<span x-show="!loading">{{ __("activitypub.reader.pagination.loadMore") }}</span>
|
||||
<span x-show="loading">{{ __("activitypub.reader.pagination.loading") }}</span>
|
||||
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done && !loading">
|
||||
{{ __("activitypub.reader.pagination.loadMore") }}
|
||||
</button>
|
||||
<div class="ap-skeleton-group" x-show="loading" x-cloak>
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
{% include "partials/ap-skeleton-card.njk" %}
|
||||
</div>
|
||||
<p class="ap-load-more__done" x-show="done">{{ __("activitypub.reader.pagination.noMore") }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
15
views/partials/ap-skeleton-card.njk
Normal file
15
views/partials/ap-skeleton-card.njk
Normal file
@@ -0,0 +1,15 @@
|
||||
{# Skeleton loading card — animated placeholder while content loads #}
|
||||
<div class="ap-card ap-card--skeleton" aria-hidden="true">
|
||||
<header class="ap-card__author">
|
||||
<div class="ap-skeleton ap-skeleton--avatar"></div>
|
||||
<div class="ap-skeleton-lines">
|
||||
<div class="ap-skeleton ap-skeleton--name"></div>
|
||||
<div class="ap-skeleton ap-skeleton--handle"></div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="ap-skeleton-body">
|
||||
<div class="ap-skeleton ap-skeleton--line"></div>
|
||||
<div class="ap-skeleton ap-skeleton--line"></div>
|
||||
<div class="ap-skeleton ap-skeleton--line ap-skeleton--short"></div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user