fix card layout

This commit is contained in:
2026-06-08 16:59:30 +02:00
parent 0ee9e22109
commit 83fdb464af
2 changed files with 24 additions and 29 deletions
+17 -23
View File
@@ -50,7 +50,7 @@ onMounted(async () => {
<p v-if="!feed.readable" class="feed-original-link"> <p v-if="!feed.readable" class="feed-original-link">
<a :href="feed.url" target="_blank" rel="noopener noreferrer">Read original article &#8599;</a> <a :href="feed.url" target="_blank" rel="noopener noreferrer">Read original article &#8599;</a>
</p> </p>
<p class="feed-content" :class="{ 'feed-content--clamped': layout === 'cards' && !feed.readable }" v-html='feed.content'></p> <p class="feed-content" v-html='feed.content'></p>
</div> </div>
</template> </template>
</div> </div>
@@ -89,22 +89,20 @@ onMounted(async () => {
</template> </template>
<style scoped> <style scoped>
.article--cards { /* Plain vertical stack of bordered "cards" — deliberately not flex/grid, and
display: grid; with no truncation/max-height: normal block flow lets each card grow to fit
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); its own full content (images included), with no cross-element interaction. */
align-items: start;
gap: 1rem;
}
.article--cards .observe { .article--cards .observe {
display: flex;
flex-direction: column;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
background: var(--color-background-soft); background: var(--color-background-soft);
} }
.article--cards .observe + .observe {
margin-top: 1rem;
}
.article--cards .feed-title { .article--cards .feed-title {
border-bottom: none; border-bottom: none;
} }
@@ -114,19 +112,15 @@ onMounted(async () => {
padding: 0 1em 0.5em; padding: 0 1em 0.5em;
} }
.article--cards .feed-content { /* `v-html` content isn't part of the component's render output, so it never
flex: 1; gets the scoped `data-v-*` attribute — `:deep()` is required for this rule
} to actually reach the injected <img> tags (without it, the selector silently
never matches). Cap the height so a large article photo reads as a tidy
.article--cards .feed-content--clamped { preview thumbnail; the card itself is left to grow to whatever height its
display: -webkit-box; content (image included) naturally needs — no clamping, no max-height. */
-webkit-box-orient: vertical; .article--cards .feed-content :deep(img) {
-webkit-line-clamp: 4; max-height: 220px;
overflow: hidden; width: auto;
}
.article--cards .feed-content img {
display: none;
} }
.feed-original-link a { .feed-original-link a {
@@ -100,7 +100,7 @@ describe('RssFeeds', () => {
expect(wrapper.find('.article').classes()).toContain('article--cards') expect(wrapper.find('.article').classes()).toContain('article--cards')
}) })
it('lets a card grow to fit the full article once its readable content has loaded', async () => { it('shows the full article content in cards with no truncation, growing the card to fit', async () => {
axios.get.mockResolvedValueOnce({ axios.get.mockResolvedValueOnce({
data: { data: {
feeds: [ feeds: [
@@ -110,7 +110,7 @@ describe('RssFeeds', () => {
{ {
id: 1, id: 1,
title: 'Article one', title: 'Article one',
content: '<p>short summary</p>', content: '<img src="https://example.test/photo.jpg" alt="photo"><br>short summary',
url: 'https://example.test/1', url: 'https://example.test/1',
timestamp: '2026-01-01', timestamp: '2026-01-01',
}, },
@@ -127,14 +127,15 @@ describe('RssFeeds', () => {
const wrapper = mount(RssFeeds) const wrapper = mount(RssFeeds)
await flushPromises() await flushPromises()
// Clamped to a fixed number of lines while only the short summary is shown... // Preview images are shown (not hidden/truncated) and the snippet isn't clamped.
expect(wrapper.find('.feed-content').classes()).toContain('feed-content--clamped') expect(wrapper.find('.feed-content img').exists()).toBe(true)
expect(wrapper.find('.feed-content').classes()).not.toContain('feed-content--clamped')
expect(wrapper.text()).toContain('short summary')
await wrapper.find('.feed-title').trigger('click') await wrapper.find('.feed-title').trigger('click')
await flushPromises() await flushPromises()
// ...but allowed to grow once the user has loaded the full readable article. expect(wrapper.text()).toContain('full text')
expect(wrapper.find('.feed-content').classes()).not.toContain('feed-content--clamped')
}) })
it('sorts articles by date across feeds, newest first', async () => { it('sorts articles by date across feeds, newest first', async () => {