fix card layout
This commit is contained in:
@@ -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 ↗</a>
|
<a :href="feed.url" target="_blank" rel="noopener noreferrer">Read original article ↗</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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user