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;
|
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
|
Responsive
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
"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.",
|
"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",
|
||||||
|
|||||||
@@ -256,10 +256,14 @@
|
|||||||
x-data="apInfiniteScroll()"
|
x-data="apInfiniteScroll()"
|
||||||
x-init="init()">
|
x-init="init()">
|
||||||
<div class="ap-load-more__sentinel" x-ref="sentinel"></div>
|
<div class="ap-load-more__sentinel" x-ref="sentinel"></div>
|
||||||
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done">
|
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done && !loading">
|
||||||
<span x-show="!loading">{{ __("activitypub.reader.pagination.loadMore") }}</span>
|
{{ __("activitypub.reader.pagination.loadMore") }}
|
||||||
<span x-show="loading">{{ __("activitypub.reader.pagination.loading") }}</span>
|
|
||||||
</button>
|
</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>
|
<p class="ap-load-more__done" x-show="done" x-cloak>{{ __("activitypub.reader.pagination.noMore") }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -283,10 +287,12 @@
|
|||||||
<template x-if="tab.type === 'instance'">
|
<template x-if="tab.type === 'instance'">
|
||||||
<div class="ap-explore-instance-panel">
|
<div class="ap-explore-instance-panel">
|
||||||
|
|
||||||
{# Loading spinner — first load, no content yet #}
|
{# Skeleton loaders — first load, no content yet #}
|
||||||
<div class="ap-explore-tab-loading"
|
<div class="ap-skeleton-group"
|
||||||
x-show="tabState[tab._id] && tabState[tab._id].loading && !tabState[tab._id].html">
|
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>
|
</div>
|
||||||
|
|
||||||
{# Error state with retry #}
|
{# Error state with retry #}
|
||||||
@@ -304,7 +310,7 @@
|
|||||||
x-html="tabState[tab._id] ? tabState[tab._id].html : ''">
|
x-html="tabState[tab._id] ? tabState[tab._id].html : ''">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Load more button + loading spinner for subsequent pages #}
|
{# Load more button + skeleton loaders for subsequent pages #}
|
||||||
<div class="ap-load-more"
|
<div class="ap-load-more"
|
||||||
x-show="tabState[tab._id] && tabState[tab._id].html && !tabState[tab._id].done">
|
x-show="tabState[tab._id] && tabState[tab._id].html && !tabState[tab._id].done">
|
||||||
<button class="ap-load-more__btn"
|
<button class="ap-load-more__btn"
|
||||||
@@ -313,10 +319,11 @@
|
|||||||
:disabled="tabState[tab._id]?.loading">
|
:disabled="tabState[tab._id]?.loading">
|
||||||
{{ __("activitypub.reader.pagination.loadMore") }}
|
{{ __("activitypub.reader.pagination.loadMore") }}
|
||||||
</button>
|
</button>
|
||||||
<span class="ap-explore-tab-loading__text"
|
<div class="ap-skeleton-group"
|
||||||
x-show="tabState[tab._id]?.loading">
|
x-show="tabState[tab._id]?.loading">
|
||||||
{{ __("activitypub.reader.pagination.loading") }}
|
{% include "partials/ap-skeleton-card.njk" %}
|
||||||
</span>
|
{% include "partials/ap-skeleton-card.njk" %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Empty state — loaded successfully but no posts #}
|
{# Empty state — loaded successfully but no posts #}
|
||||||
@@ -347,10 +354,12 @@
|
|||||||
x-text="hashtagSourcesLine(tab)"
|
x-text="hashtagSourcesLine(tab)"
|
||||||
x-cloak></p>
|
x-cloak></p>
|
||||||
|
|
||||||
{# Loading spinner — first load, no content yet #}
|
{# Skeleton loaders — first load, no content yet #}
|
||||||
<div class="ap-explore-tab-loading"
|
<div class="ap-skeleton-group"
|
||||||
x-show="tabState[tab._id] && tabState[tab._id].loading && !tabState[tab._id].html">
|
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>
|
</div>
|
||||||
|
|
||||||
{# Error state with retry #}
|
{# Error state with retry #}
|
||||||
@@ -368,7 +377,7 @@
|
|||||||
x-html="tabState[tab._id] ? tabState[tab._id].html : ''">
|
x-html="tabState[tab._id] ? tabState[tab._id].html : ''">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Load more button + loading spinner for subsequent pages #}
|
{# Load more button + skeleton loaders for subsequent pages #}
|
||||||
<div class="ap-load-more"
|
<div class="ap-load-more"
|
||||||
x-show="tabState[tab._id] && tabState[tab._id].html && !tabState[tab._id].done">
|
x-show="tabState[tab._id] && tabState[tab._id].html && !tabState[tab._id].done">
|
||||||
<button class="ap-load-more__btn"
|
<button class="ap-load-more__btn"
|
||||||
@@ -377,10 +386,11 @@
|
|||||||
:disabled="tabState[tab._id]?.loading">
|
:disabled="tabState[tab._id]?.loading">
|
||||||
{{ __("activitypub.reader.pagination.loadMore") }}
|
{{ __("activitypub.reader.pagination.loadMore") }}
|
||||||
</button>
|
</button>
|
||||||
<span class="ap-explore-tab-loading__text"
|
<div class="ap-skeleton-group"
|
||||||
x-show="tabState[tab._id]?.loading">
|
x-show="tabState[tab._id]?.loading">
|
||||||
{{ __("activitypub.reader.pagination.loading") }}
|
{% include "partials/ap-skeleton-card.njk" %}
|
||||||
</span>
|
{% include "partials/ap-skeleton-card.njk" %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Empty state — no instance tabs pinned yet #}
|
{# Empty state — no instance tabs pinned yet #}
|
||||||
|
|||||||
@@ -150,10 +150,14 @@
|
|||||||
x-data="apInfiniteScroll()"
|
x-data="apInfiniteScroll()"
|
||||||
x-init="init()">
|
x-init="init()">
|
||||||
<div class="ap-load-more__sentinel" x-ref="sentinel"></div>
|
<div class="ap-load-more__sentinel" x-ref="sentinel"></div>
|
||||||
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done">
|
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done && !loading">
|
||||||
<span x-show="!loading">{{ __("activitypub.reader.pagination.loadMore") }}</span>
|
{{ __("activitypub.reader.pagination.loadMore") }}
|
||||||
<span x-show="loading">{{ __("activitypub.reader.pagination.loading") }}</span>
|
|
||||||
</button>
|
</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>
|
<p class="ap-load-more__done" x-show="done" x-cloak>{{ __("activitypub.reader.pagination.noMore") }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -75,10 +75,14 @@
|
|||||||
x-data="apInfiniteScroll()"
|
x-data="apInfiniteScroll()"
|
||||||
x-init="init()">
|
x-init="init()">
|
||||||
<div class="ap-load-more__sentinel" x-ref="sentinel"></div>
|
<div class="ap-load-more__sentinel" x-ref="sentinel"></div>
|
||||||
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done">
|
<button class="ap-load-more__btn" @click="loadMore()" :disabled="loading" x-show="!done && !loading">
|
||||||
<span x-show="!loading">{{ __("activitypub.reader.pagination.loadMore") }}</span>
|
{{ __("activitypub.reader.pagination.loadMore") }}
|
||||||
<span x-show="loading">{{ __("activitypub.reader.pagination.loading") }}</span>
|
|
||||||
</button>
|
</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>
|
<p class="ap-load-more__done" x-show="done">{{ __("activitypub.reader.pagination.noMore") }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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