[web] center standalone homepage content #196

Merged
eleith merged 1 commits from fix/markdown-only-home into main 2026-11-05 05:15:04 +00:00
4 changed files with 91 additions and 43 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@luzzle/web": patch
---
Center custom homepage Markdown below the navigation when both latest and random sections are disabled, while preserving normal layouts when either section is enabled.
+2 -1
View File
@@ -27,5 +27,6 @@ export const load: PageServerLoad = async () => {
hydrateWithAssets(latestRows),
hydrateWithAssets(randomRows)
])
return { types, body, latest, random }
const centerContent = Boolean(body) && !settings.latest && !settings.random
return { types, body, latest, random, centerContent }
}
+53 -34
View File
@@ -22,46 +22,65 @@
/>
</svelte:head>
<ContentBanner showRandom showHome={false} />
<div class:centered-content={data.centerContent}>
<ContentBanner showRandom showHome={false} />
<main id="main-content">
{#if data.body === undefined}
<section class="section">
<h1>luzzle pieces</h1>
<p>export all the <a href={resolve('pieces')}>pieces</a></p>
{#if data.types.length}
<h2>types</h2>
<div class="types-list">
{#each data.types as type (type)}
<a href={resolve(`pieces/${type}`)} class="type-link">{type}</a>
{/each}
</div>
{/if}
</section>
{:else if data.body}
<section class="section"><Markdown html={data.body} /></section>
{/if}
{#each collections as collection (collection.title)}
{#if collection.pieces.length}
<main id="main-content">
{#if data.body === undefined}
<section class="section">
<h2>{collection.title}</h2>
<div class="pieces-grid">
{#each collection.pieces as piece (piece.id)}
<a href={resolve(`pieces/${piece.type}/${piece.slug}`)} class="piece">
<div class="piece-card">
<PieceIcon {piece} size={{ width: 80 }} />
<span>{piece.title}</span>
</div>
</a>
{/each}
</div>
<h1>luzzle pieces</h1>
<p>export all the <a href={resolve('pieces')}>pieces</a></p>
{#if data.types.length}
<h2>types</h2>
<div class="types-list">
{#each data.types as type (type)}
<a href={resolve(`pieces/${type}`)} class="type-link">{type}</a>
{/each}
</div>
{/if}
</section>
{:else if data.body}
<section class="section"><Markdown html={data.body} /></section>
{/if}
{/each}
</main>
{#each collections as collection (collection.title)}
{#if collection.pieces.length}
<section class="section">
<h2>{collection.title}</h2>
<div class="pieces-grid">
{#each collection.pieces as piece (piece.id)}
<a href={resolve(`pieces/${piece.type}/${piece.slug}`)} class="piece">
<div class="piece-card">
<PieceIcon {piece} size={{ width: 80 }} />
<span>{piece.title}</span>
</div>
</a>
{/each}
</div>
</section>
{/if}
{/each}
</main>
</div>
<style>
.centered-content {
min-height: 100svh;
display: flex;
flex-direction: column;
}
.centered-content main {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.centered-content .section {
width: 100%;
}
.section {
max-width: 800px;
margin: auto;
+31 -8
View File
@@ -36,7 +36,8 @@ test('stock home uses published latest pieces and does not request random data',
types: ['inventory'],
body: undefined,
latest: recent,
random: []
random: [],
centerContent: false
})
expect(getRecentPieces).toHaveBeenCalledExactlyOnceWith(db, 5, ['inventory'])
expect(getRandomPieces).not.toHaveBeenCalled()
@@ -46,24 +47,46 @@ test('Markdown body does not replace dynamic lists and random excludes latest ID
config.pages.home.body = 'home.md'
config.pages.home.random = true
loadPageMarkdown.mockResolvedValue('<h1>Welcome</h1>')
expect(await load(event)).toMatchObject({ body: '<h1>Welcome</h1>', latest: recent, random })
expect(await load(event)).toMatchObject({
body: '<h1>Welcome</h1>',
latest: recent,
random,
centerContent: false
})
expect(loadPageMarkdown).toHaveBeenCalledExactlyOnceWith('home.md')
expect(getRandomPieces).toHaveBeenCalledExactlyOnceWith(db, 5, ['inventory'], ['one'])
})
test('disabled lists skip piece queries and delegate empty collections to hydration', async () => {
test('Markdown alone selects the centered layout when both lists are disabled', async () => {
config.pages.home.body = 'home.md'
config.pages.home.latest = false
expect(await load(event)).toMatchObject({ latest: [], random: [] })
loadPageMarkdown.mockResolvedValue('<h1>Welcome</h1>')
expect(await load(event)).toMatchObject({
body: '<h1>Welcome</h1>',
latest: [],
random: [],
centerContent: true
})
expect(getRecentPieces).not.toHaveBeenCalled()
expect(getRandomPieces).not.toHaveBeenCalled()
expect(hydrateWithAssets.mock.calls).toEqual([[[]], [[]]])
})
test('random works independently of latest and delegates empty collections to hydration', async () => {
test.each([
{ label: 'no custom Markdown', file: undefined, body: undefined },
{ label: 'empty custom Markdown', file: 'home.md', body: '' }
])('$label keeps the normal layout with both lists disabled', async ({ file, body }) => {
config.pages.home = { body: file, latest: false, random: false }
loadPageMarkdown.mockResolvedValue(body)
expect(await load(event)).toMatchObject({ body, latest: [], random: [], centerContent: false })
})
test('enabled random keeps the normal layout even when its collection is empty', async () => {
config.pages.home.body = 'home.md'
config.pages.home.latest = false
config.pages.home.random = true
loadPageMarkdown.mockResolvedValue('<h1>Welcome</h1>')
getRandomPieces.mockResolvedValue([])
expect(await load(event)).toMatchObject({ latest: [], random: [] })
expect(await load(event)).toMatchObject({ latest: [], random: [], centerContent: false })
expect(getRandomPieces).toHaveBeenCalledExactlyOnceWith(db, 5, ['inventory'], [])
expect(hydrateWithAssets.mock.calls).toEqual([[[]], [[]]])
})