feat: enriched media model with ALT badges (Release 3+4)
Change photo storage from bare URL strings to objects with url, alt, width, height (AP) plus blurhash and focus (Mastodon API). Templates handle both old string and new object format for backward compat. Add ALT text badges on gallery images — click to expand the full alt text in an overlay. Renders in both reader and explore views. Also pass alt text through to lightbox and quote embed photos. Bump version to 2.5.3. Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
This commit is contained in:
@@ -2537,3 +2537,45 @@
|
|||||||
margin: 0 0.05em;
|
margin: 0 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Gallery items — positioned for ALT badge overlay */
|
||||||
|
.ap-card__gallery-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ALT text badges */
|
||||||
|
.ap-media__alt-badge {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.5rem;
|
||||||
|
left: 0.5rem;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
color: white;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0.15rem 0.35rem;
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-media__alt-badge:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-media__alt-text {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2.2rem;
|
||||||
|
left: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
background: rgba(0, 0, 0, 0.85);
|
||||||
|
color: white;
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
max-height: 8rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,14 @@ export function mapMastodonStatusToItem(status, instance) {
|
|||||||
const url = att.url || att.remote_url || "";
|
const url = att.url || att.remote_url || "";
|
||||||
if (!url) continue;
|
if (!url) continue;
|
||||||
if (att.type === "image" || att.type === "gifv") {
|
if (att.type === "image" || att.type === "gifv") {
|
||||||
photo.push(url);
|
photo.push({
|
||||||
|
url,
|
||||||
|
alt: att.description || "",
|
||||||
|
width: att.meta?.original?.width || null,
|
||||||
|
height: att.meta?.original?.height || null,
|
||||||
|
blurhash: att.blurhash || "",
|
||||||
|
focus: att.meta?.focus || null,
|
||||||
|
});
|
||||||
} else if (att.type === "video") {
|
} else if (att.type === "video") {
|
||||||
video.push(url);
|
video.push(url);
|
||||||
} else if (att.type === "audio") {
|
} else if (att.type === "audio") {
|
||||||
@@ -144,7 +151,12 @@ export function mapMastodonStatusToItem(status, instance) {
|
|||||||
for (const att of q.media_attachments || []) {
|
for (const att of q.media_attachments || []) {
|
||||||
const attUrl = att.url || att.remote_url || "";
|
const attUrl = att.url || att.remote_url || "";
|
||||||
if (attUrl && (att.type === "image" || att.type === "gifv")) {
|
if (attUrl && (att.type === "image" || att.type === "gifv")) {
|
||||||
qPhoto.push(attUrl);
|
qPhoto.push({
|
||||||
|
url: attUrl,
|
||||||
|
alt: att.description || "",
|
||||||
|
width: att.meta?.original?.width || null,
|
||||||
|
height: att.meta?.original?.height || null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -256,7 +256,12 @@ export async function extractObjectData(object, options = {}) {
|
|||||||
const mediaType = att.mediaType?.toLowerCase() || "";
|
const mediaType = att.mediaType?.toLowerCase() || "";
|
||||||
|
|
||||||
if (mediaType.startsWith("image/")) {
|
if (mediaType.startsWith("image/")) {
|
||||||
photo.push(mediaUrl);
|
photo.push({
|
||||||
|
url: mediaUrl,
|
||||||
|
alt: att.name?.toString() || "",
|
||||||
|
width: att.width || null,
|
||||||
|
height: att.height || null,
|
||||||
|
});
|
||||||
} else if (mediaType.startsWith("video/")) {
|
} else if (mediaType.startsWith("video/")) {
|
||||||
video.push(mediaUrl);
|
video.push(mediaUrl);
|
||||||
} else if (mediaType.startsWith("audio/")) {
|
} else if (mediaType.startsWith("audio/")) {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
||||||
"version": "2.5.2",
|
"version": "2.5.3",
|
||||||
"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",
|
||||||
|
|||||||
@@ -6,14 +6,23 @@
|
|||||||
{% set extraCount = item.photo.length - 4 %}
|
{% set extraCount = item.photo.length - 4 %}
|
||||||
{% set totalPhotos = item.photo.length %}
|
{% set totalPhotos = item.photo.length %}
|
||||||
<div x-data="{ lightbox: false, idx: 0 }" class="ap-card__gallery ap-card__gallery--{{ displayCount }}">
|
<div x-data="{ lightbox: false, idx: 0 }" class="ap-card__gallery ap-card__gallery--{{ displayCount }}">
|
||||||
{% for photoUrl in item.photo %}
|
{% for photo in item.photo %}
|
||||||
|
{# Support both old string format and new object format #}
|
||||||
|
{% set photoSrc = photo.url if photo.url else photo %}
|
||||||
|
{% set photoAlt = photo.alt if photo.alt else "" %}
|
||||||
{% if loop.index0 < 4 %}
|
{% if loop.index0 < 4 %}
|
||||||
<button type="button" @click="idx = {{ loop.index0 }}; lightbox = true" class="ap-card__gallery-link{% if loop.index0 == 3 and extraCount > 0 %} ap-card__gallery-link--more{% endif %}">
|
<div class="ap-card__gallery-item" x-data="{ showAlt: false }">
|
||||||
<img src="{{ photoUrl }}" alt="" loading="lazy">
|
<button type="button" @click="idx = {{ loop.index0 }}; lightbox = true" class="ap-card__gallery-link{% if loop.index0 == 3 and extraCount > 0 %} ap-card__gallery-link--more{% endif %}">
|
||||||
{% if loop.index0 == 3 and extraCount > 0 %}
|
<img src="{{ photoSrc }}" alt="{{ photoAlt }}" loading="lazy">
|
||||||
<span class="ap-card__gallery-more">+{{ extraCount }}</span>
|
{% if loop.index0 == 3 and extraCount > 0 %}
|
||||||
|
<span class="ap-card__gallery-more">+{{ extraCount }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
{% if photoAlt %}
|
||||||
|
<button type="button" class="ap-media__alt-badge" @click.stop="showAlt = !showAlt" :aria-expanded="showAlt">ALT</button>
|
||||||
|
<div class="ap-media__alt-text" x-show="showAlt" x-cloak @click.stop>{{ photoAlt }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
@@ -24,7 +33,9 @@
|
|||||||
{% if totalPhotos > 1 %}
|
{% if totalPhotos > 1 %}
|
||||||
<button type="button" @click="idx = (idx - 1 + {{ totalPhotos }}) % {{ totalPhotos }}" class="ap-lightbox__prev" aria-label="Previous image">‹</button>
|
<button type="button" @click="idx = (idx - 1 + {{ totalPhotos }}) % {{ totalPhotos }}" class="ap-lightbox__prev" aria-label="Previous image">‹</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<img :src="[{% for p in item.photo %}'{{ p }}'{% if not loop.last %},{% endif %}{% endfor %}][idx]" class="ap-lightbox__img" alt="">
|
<img :src="[{% for photo in item.photo %}'{{ photo.url if photo.url else photo }}'{% if not loop.last %},{% endif %}{% endfor %}][idx]"
|
||||||
|
:alt="[{% for photo in item.photo %}'{{ (photo.alt if photo.alt else '') | replace(\"'\", \"\\'\") }}'{% if not loop.last %},{% endif %}{% endfor %}][idx]"
|
||||||
|
class="ap-lightbox__img">
|
||||||
{% if totalPhotos > 1 %}
|
{% if totalPhotos > 1 %}
|
||||||
<button type="button" @click="idx = (idx + 1) % {{ totalPhotos }}" class="ap-lightbox__next" aria-label="Next image">›</button>
|
<button type="button" @click="idx = (idx + 1) % {{ totalPhotos }}" class="ap-lightbox__next" aria-label="Next image">›</button>
|
||||||
<div class="ap-lightbox__counter" x-text="(idx + 1) + ' / ' + {{ totalPhotos }}"></div>
|
<div class="ap-lightbox__counter" x-text="(idx + 1) + ' / ' + {{ totalPhotos }}"></div>
|
||||||
|
|||||||
@@ -25,8 +25,9 @@
|
|||||||
<div class="ap-quote-embed__content">{{ item.quote.content.html | safe }}</div>
|
<div class="ap-quote-embed__content">{{ item.quote.content.html | safe }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if item.quote.photo and item.quote.photo.length > 0 %}
|
{% if item.quote.photo and item.quote.photo.length > 0 %}
|
||||||
|
{% set qPhoto = item.quote.photo[0] %}
|
||||||
<div class="ap-quote-embed__media">
|
<div class="ap-quote-embed__media">
|
||||||
<img src="{{ item.quote.photo[0] }}" alt="" loading="lazy" class="ap-quote-embed__photo">
|
<img src="{{ qPhoto.url if qPhoto.url else qPhoto }}" alt="{{ qPhoto.alt if qPhoto.alt else '' }}" loading="lazy" class="ap-quote-embed__photo">
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user