fix: reserve detail cover box and async-decode list/shelf covers
- The manga detail desktop overview cover had no reserved aspect-ratio, so the meta column reflowed when it decoded. Give .cover aspect-ratio: 2/3 + object-fit: cover (mirrors MangaCard). - Add decoding="async" to the detail hero/overview covers and the list/shelf covers (bookmarks, history, continue-reading, collections, tagged rows) so decode stays off the main thread — previously only MangaCard had it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@@ -1558,7 +1558,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.115.1"
|
version = "0.115.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.115.1"
|
version = "0.115.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "mangalord"
|
default-run = "mangalord"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mangalord-frontend",
|
"name": "mangalord-frontend",
|
||||||
"version": "0.115.1",
|
"version": "0.115.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
alt=""
|
alt=""
|
||||||
class="cover"
|
class="cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="cover cover-placeholder">
|
<div class="cover cover-placeholder">
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
alt=""
|
alt=""
|
||||||
class="collage-cover"
|
class="collage-cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
alt=""
|
alt=""
|
||||||
class="cover"
|
class="cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="cover cover-placeholder">
|
<span class="cover cover-placeholder">
|
||||||
|
|||||||
@@ -89,6 +89,7 @@
|
|||||||
alt=""
|
alt=""
|
||||||
class="cover"
|
class="cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="cover cover-placeholder">
|
<div class="cover cover-placeholder">
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ describe('HistoryList', () => {
|
|||||||
expect(cont.getAttribute('href')).toBe('/manga/m1/chapter/c9');
|
expect(cont.getAttribute('href')).toBe('/manga/m1/chapter/c9');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders covers with lazy loading and async decode', () => {
|
||||||
|
render(HistoryList, {
|
||||||
|
props: { entries: [entry({ manga_cover_image_path: 'mangas/m1/cover.jpg' })] }
|
||||||
|
});
|
||||||
|
const img = document.querySelector('img.cover') as HTMLImageElement;
|
||||||
|
expect(img).not.toBeNull();
|
||||||
|
expect(img.getAttribute('loading')).toBe('lazy');
|
||||||
|
expect(img.getAttribute('decoding')).toBe('async');
|
||||||
|
});
|
||||||
|
|
||||||
it('appends the page number to the continue line only past page 1', () => {
|
it('appends the page number to the continue line only past page 1', () => {
|
||||||
// The page suffix is a separate text node from "Continue Chapter N",
|
// The page suffix is a separate text node from "Continue Chapter N",
|
||||||
// so assert against the link's normalized text rather than getByText.
|
// so assert against the link's normalized text rather than getByText.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<li class="row" data-testid={testid}>
|
<li class="row" data-testid={testid}>
|
||||||
<a {href} class="cover-link" aria-hidden="true" tabindex="-1">
|
<a {href} class="cover-link" aria-hidden="true" tabindex="-1">
|
||||||
{#if primaryCover}
|
{#if primaryCover}
|
||||||
<img src={fileUrl(primaryCover)} alt="" class="cover" loading="lazy" />
|
<img src={fileUrl(primaryCover)} alt="" class="cover" loading="lazy" decoding="async" />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="cover cover-placeholder"></div>
|
<div class="cover cover-placeholder"></div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
{#if item.sample_storage_keys.length > 1}
|
{#if item.sample_storage_keys.length > 1}
|
||||||
<div class="samples">
|
<div class="samples">
|
||||||
{#each item.sample_storage_keys.slice(1) as key (key)}
|
{#each item.sample_storage_keys.slice(1) as key (key)}
|
||||||
<img src={fileUrl(key)} alt="" class="sample" loading="lazy" />
|
<img src={fileUrl(key)} alt="" class="sample" loading="lazy" decoding="async" />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<li class="row" data-testid={testid}>
|
<li class="row" data-testid={testid}>
|
||||||
<a {href} class="cover-link" aria-hidden="true" tabindex="-1">
|
<a {href} class="cover-link" aria-hidden="true" tabindex="-1">
|
||||||
{#if primaryCover}
|
{#if primaryCover}
|
||||||
<img src={fileUrl(primaryCover)} alt="" class="cover" loading="lazy" />
|
<img src={fileUrl(primaryCover)} alt="" class="cover" loading="lazy" decoding="async" />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="cover cover-placeholder"></div>
|
<div class="cover cover-placeholder"></div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
{#if sampleStrip.length > 0}
|
{#if sampleStrip.length > 0}
|
||||||
<div class="samples">
|
<div class="samples">
|
||||||
{#each sampleStrip as key (key)}
|
{#each sampleStrip as key (key)}
|
||||||
<img src={fileUrl(key)} alt="" class="sample" loading="lazy" />
|
<img src={fileUrl(key)} alt="" class="sample" loading="lazy" decoding="async" />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
alt=""
|
alt=""
|
||||||
class="cover"
|
class="cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
|||||||
@@ -380,6 +380,7 @@
|
|||||||
src={fileUrl(manga.cover_image_path)}
|
src={fileUrl(manga.cover_image_path)}
|
||||||
alt=""
|
alt=""
|
||||||
loading="eager"
|
loading="eager"
|
||||||
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="hero-text">
|
<div class="hero-text">
|
||||||
@@ -401,6 +402,7 @@
|
|||||||
alt="{manga.title} cover"
|
alt="{manga.title} cover"
|
||||||
class="cover"
|
class="cover"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
|
decoding="async"
|
||||||
data-testid="manga-cover"
|
data-testid="manga-cover"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -791,7 +793,10 @@
|
|||||||
|
|
||||||
.cover {
|
.cover {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
/* Reserve the 2/3 box so the meta column doesn't reflow when the
|
||||||
|
cover decodes (mirrors MangaCard's cover). */
|
||||||
|
aspect-ratio: 2 / 3;
|
||||||
|
object-fit: cover;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user