Feed filter implementation
This commit is contained in:
@@ -6,7 +6,7 @@ import Modal from './modal/AddUrl.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { sync, showModal, viewMode, toggleViewMode, layout, toggleLayout, markAllRead, feeds, lastProgrammaticScroll } = useFeeds()
|
||||
const { sync, showModal, viewMode, toggleViewMode, layout, toggleLayout, markAllRead, feedFilter, feedTitles, setFeedFilter, unreadCount, lastProgrammaticScroll } = useFeeds()
|
||||
|
||||
const headerRef = ref(null)
|
||||
|
||||
@@ -77,15 +77,16 @@ onMounted(() => {
|
||||
|
||||
lastY = Math.max(0, window.scrollY)
|
||||
window.addEventListener('scroll', onScrollRaf, { passive: true })
|
||||
document.addEventListener('click', onDocumentClick)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', onScrollRaf)
|
||||
document.removeEventListener('click', onDocumentClick)
|
||||
})
|
||||
|
||||
const onFeedsPage = computed(() => route.path === '/feeds')
|
||||
|
||||
const unreadCount = computed(() => feeds.value.filter(f => !f.read).length)
|
||||
|
||||
const menuOpen = ref(false)
|
||||
|
||||
@@ -97,6 +98,19 @@ function closeMenu() {
|
||||
menuOpen.value = false
|
||||
}
|
||||
|
||||
// The open menu is a thin absolutely-positioned strip under the header, so its
|
||||
// `@click.self` only catches clicks on that strip — a click anywhere else on
|
||||
// the page never reaches it. This document-level listener closes the menu on
|
||||
// any outside click. The hamburger is excluded (it has its own toggle, so an
|
||||
// opening click mustn't immediately re-close), and clicks inside the menu are
|
||||
// left to the menu items' own handlers.
|
||||
function onDocumentClick(event) {
|
||||
if (!menuOpen.value) return
|
||||
const target = event.target
|
||||
if (target.closest?.('.app-nav__hamburger') || target.closest?.('.app-nav__menu')) return
|
||||
closeMenu()
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
await logoutSession()
|
||||
closeMenu()
|
||||
@@ -133,6 +147,16 @@ function handleToggleLayout() {
|
||||
<header ref="headerRef" class="app-nav" :class="{ 'app-nav--hidden': hidden }">
|
||||
<div class="app-nav__wrapper">
|
||||
<span class="app-nav__title">RSS Reader<span v-if="unreadCount" class="app-nav__unread"> ({{ unreadCount }})</span></span>
|
||||
<select
|
||||
v-if="onFeedsPage && feedTitles.length"
|
||||
class="app-nav__filter"
|
||||
:value="feedFilter ?? ''"
|
||||
aria-label="Filter by feed"
|
||||
@change="setFeedFilter($event.target.value || null)"
|
||||
>
|
||||
<option value="">All feeds</option>
|
||||
<option v-for="title in feedTitles" :key="title" :value="title">{{ title }}</option>
|
||||
</select>
|
||||
<button
|
||||
class="app-nav__hamburger"
|
||||
type="button"
|
||||
@@ -210,6 +234,7 @@ function handleToggleLayout() {
|
||||
}
|
||||
|
||||
.app-nav__title {
|
||||
margin-right: auto;
|
||||
font-weight: bold;
|
||||
font-size: clamp(0.95rem, 3.5vw, 1.1rem);
|
||||
}
|
||||
@@ -219,6 +244,23 @@ function handleToggleLayout() {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.app-nav__filter {
|
||||
min-height: 44px;
|
||||
max-width: clamp(120px, 40vw, 220px);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.app-nav__filter:hover {
|
||||
border-color: var(--color-border-hover);
|
||||
}
|
||||
|
||||
.app-nav__hamburger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user