{{ feeds[currentIndex].feedTitle }}
-{{ feeds[currentIndex].title }}
+{{ feeds[currentIndex].title }}
{{ feeds[currentIndex].timestamp }}
Read original article ↗ @@ -182,21 +194,11 @@ onMounted(async () => { opacity: 0.6; } -.list-topbar__next { - display: inline-flex; - align-items: center; - min-height: 44px; - padding: 0.5rem 0.9rem; - margin-left: auto; - border: 1px solid var(--color-border); - border-radius: 4px; - background: var(--color-background-soft); - color: var(--color-text); - cursor: pointer; -} - -.list-topbar__next:hover { - border-color: var(--color-border-hover); +.list-skip-btn { + position: fixed; + right: 1rem; + bottom: 1.5rem; + z-index: 20; } .observe { @@ -256,6 +258,38 @@ onMounted(async () => { width: auto; } +.feed-content--readable :deep(img), +.feed-content--readable :deep(video) { + display: block; + width: 100vw; + max-width: 100vw; + height: auto; + margin-top: 1.5em; + margin-bottom: 1.5em; + margin-left: 50%; + transform: translateX(-50%); +} + +.feed-content--readable :deep(img.article-feature__image--small) { + display: block; + width: auto; + max-width: 100%; + margin: 1.5em auto; + transform: none; +} + +@media (min-width: 720px) { + .feed-content--readable :deep(img), + .feed-content--readable :deep(video) { + display: block; + width: auto; + max-width: 100%; + height: auto; + margin: 1.5em auto; + transform: none; + } +} + .feed-original-link { display: flex; flex-wrap: wrap; diff --git a/vue/src/components/__tests__/AppNav.spec.js b/vue/src/components/__tests__/AppNav.spec.js index 4fd9480..7570444 100644 --- a/vue/src/components/__tests__/AppNav.spec.js +++ b/vue/src/components/__tests__/AppNav.spec.js @@ -7,6 +7,15 @@ import { useFeeds } from '../../composables/useFeeds' vi.mock('axios') +// jsdom does not implement IntersectionObserver, but AppNav sets one up on mount +// to track whether the list view's title is scrolled into view. +class FakeIntersectionObserver { + observe() {} + unobserve() {} + disconnect() {} +} +vi.stubGlobal('IntersectionObserver', FakeIntersectionObserver) + describe('AppNav', () => { let router diff --git a/vue/src/components/__tests__/RssFeeds.spec.js b/vue/src/components/__tests__/RssFeeds.spec.js index 0a9a51d..493a704 100644 --- a/vue/src/components/__tests__/RssFeeds.spec.js +++ b/vue/src/components/__tests__/RssFeeds.spec.js @@ -252,13 +252,13 @@ describe('RssFeeds', () => { useFeeds().toggleViewMode() await flushPromises() - expect(wrapper.find('.article-single .feed-title').text()).toBe('Article one') + expect(wrapper.find('.article-single .article-feature__title').text()).toBe('Article one') // Same as in list view: the readable content is loaded on demand by // clicking the headline, not fetched automatically on entering the view. expect(axios.post).not.toHaveBeenCalled() expect(wrapper.find('.article-single .feed-original-link a').exists()).toBe(true) - await wrapper.find('.article-single .feed-title').trigger('click') + await wrapper.find('.article-single .article-feature__title').trigger('click') await flushPromises() expect(axios.post).toHaveBeenCalledWith('/api/v1/article/read', { url: 'https://example.test/1' }, expect.anything()) @@ -269,13 +269,13 @@ describe('RssFeeds', () => { await wrapper.findAll('.article-nav__btn')[1].trigger('click') await flushPromises() - expect(wrapper.find('.article-single .feed-title').text()).toBe('Article two') + expect(wrapper.find('.article-single .article-feature__title').text()).toBe('Article two') expect(wrapper.findAll('.article-nav__btn')[1].attributes('disabled')).toBeDefined() await wrapper.findAll('.article-nav__btn')[0].trigger('click') await flushPromises() - expect(wrapper.find('.article-single .feed-title').text()).toBe('Article one') + expect(wrapper.find('.article-single .article-feature__title').text()).toBe('Article one') }) it('drops articles read while paging through article view once back in the list', async () => {