Implement Note Composer and enhance NostrFeed interactions
- Added NoteComposer.vue for composing notes, including reply functionality and mention support. - Integrated NoteComposer into Home.vue, allowing users to publish notes and reply to existing ones. - Enhanced NostrFeed.vue to handle reply events, improving user engagement with notes. - Updated Home.vue to manage note publishing and reply states effectively. These changes enhance the user experience by providing a seamless way to compose and interact with notes within the feed. Enhance NostrFeed and Home components for improved user experience - Added compact mode support in NostrFeed.vue to optimize display for mobile users. - Refactored layout in NostrFeed.vue for better responsiveness and usability, including adjustments to padding and spacing. - Updated Home.vue to integrate a collapsible filter panel and a floating action button for composing notes, enhancing accessibility and interaction. - Implemented quick filter presets for mobile, allowing users to easily switch between content types. These changes improve the overall functionality and user engagement within the feed, providing a more streamlined experience across devices. Enhance NoteComposer and Home components for improved note management - Updated NoteComposer.vue to include a close button for better user control when composing notes. - Modified Home.vue to handle the close event from NoteComposer, allowing users to dismiss the composer easily. These changes enhance the user experience by providing a more intuitive interface for composing and managing notes.
This commit is contained in:
parent
77ed913e1b
commit
2d0aadccb7
3 changed files with 634 additions and 128 deletions
|
|
@ -1,32 +1,92 @@
|
|||
<template>
|
||||
<div class="w-full px-4 sm:px-6 lg:px-8 xl:px-12 2xl:px-16 py-8 space-y-6">
|
||||
<div class="flex flex-col h-screen bg-background">
|
||||
<PWAInstallPrompt auto-show />
|
||||
<!-- TODO: Implement push notifications properly - currently commenting out admin notifications dialog -->
|
||||
<!-- <NotificationPermission auto-show /> -->
|
||||
|
||||
<!-- Feed Filter Controls -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Filter class="h-5 w-5" />
|
||||
Content Filters
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Choose what types of content you want to see in your feed
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FeedFilters v-model="selectedFilters" :admin-pubkeys="adminPubkeys" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<!-- Compact Header with Filters Toggle (Mobile) -->
|
||||
<div class="sticky top-0 z-40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b">
|
||||
<div class="flex items-center justify-between px-4 py-2 sm:px-6">
|
||||
<h1 class="text-lg font-semibold">Feed</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Active Filter Indicator -->
|
||||
<div class="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<span v-if="activeFilterCount > 0">{{ activeFilterCount }} filters</span>
|
||||
<span v-else>All content</span>
|
||||
</div>
|
||||
<!-- Filter Toggle Button -->
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="showFilters = !showFilters"
|
||||
class="h-8 w-8 p-0"
|
||||
>
|
||||
<Filter class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Feed Content -->
|
||||
<NostrFeed
|
||||
:feed-type="feedType"
|
||||
:content-filters="selectedFilters"
|
||||
:admin-pubkeys="adminPubkeys"
|
||||
:key="feedKey"
|
||||
/>
|
||||
<!-- Collapsible Filter Panel -->
|
||||
<div v-if="showFilters" class="border-t bg-background/95 backdrop-blur">
|
||||
<div class="px-4 py-3 sm:px-6">
|
||||
<FeedFilters v-model="selectedFilters" :admin-pubkeys="adminPubkeys" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Feed Area - Takes remaining height -->
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<!-- Collapsible Note Composer -->
|
||||
<div v-if="showComposer || replyTo" class="border-b bg-background">
|
||||
<div class="px-4 py-3 sm:px-6">
|
||||
<NoteComposer
|
||||
:reply-to="replyTo"
|
||||
@note-published="onNotePublished"
|
||||
@clear-reply="onClearReply"
|
||||
@close="onCloseComposer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Feed Content - Full height scroll -->
|
||||
<div class="h-full">
|
||||
<NostrFeed
|
||||
:feed-type="feedType"
|
||||
:content-filters="selectedFilters"
|
||||
:admin-pubkeys="adminPubkeys"
|
||||
:key="feedKey"
|
||||
:compact-mode="true"
|
||||
@reply-to-note="onReplyToNote"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Floating Action Button for Compose -->
|
||||
<div v-if="!showComposer && !replyTo" class="fixed bottom-6 right-6 z-50">
|
||||
<Button
|
||||
@click="showComposer = true"
|
||||
size="lg"
|
||||
class="h-14 w-14 rounded-full shadow-lg hover:shadow-xl transition-shadow"
|
||||
>
|
||||
<Plus class="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Quick Filters Bar (Mobile) -->
|
||||
<div class="md:hidden sticky bottom-0 z-30 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-t">
|
||||
<div class="flex overflow-x-auto px-4 py-2 gap-2 scrollbar-hide">
|
||||
<Button
|
||||
v-for="(preset, key) in quickFilterPresets"
|
||||
:key="key"
|
||||
:variant="isPresetActive(key) ? 'default' : 'outline'"
|
||||
size="sm"
|
||||
@click="setQuickFilter(key)"
|
||||
class="whitespace-nowrap"
|
||||
>
|
||||
{{ preset.label }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -37,20 +97,50 @@
|
|||
// import NotificationPermission from '@/components/notifications/NotificationPermission.vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Filter } from 'lucide-vue-next'
|
||||
import { Filter, Plus } from 'lucide-vue-next'
|
||||
import PWAInstallPrompt from '@/components/pwa/PWAInstallPrompt.vue'
|
||||
import FeedFilters from '@/modules/nostr-feed/components/FeedFilters.vue'
|
||||
import NoteComposer from '@/modules/nostr-feed/components/NoteComposer.vue'
|
||||
import NostrFeed from '@/modules/nostr-feed/components/NostrFeed.vue'
|
||||
import { FILTER_PRESETS } from '@/modules/nostr-feed/config/content-filters'
|
||||
import appConfig from '@/app.config'
|
||||
import type { ContentFilter } from '@/modules/nostr-feed/services/FeedService'
|
||||
import type { ReplyToNote } from '@/modules/nostr-feed/components/NoteComposer.vue'
|
||||
|
||||
// Get admin pubkeys from app config
|
||||
const adminPubkeys = appConfig.modules['nostr-feed']?.config?.adminPubkeys || []
|
||||
|
||||
// UI state
|
||||
const showFilters = ref(false)
|
||||
const showComposer = ref(false)
|
||||
|
||||
// Feed configuration
|
||||
const selectedFilters = ref<ContentFilter[]>(FILTER_PRESETS.all)
|
||||
const feedKey = ref(0) // Force feed component to re-render when filters change
|
||||
|
||||
// Note composer state
|
||||
const replyTo = ref<ReplyToNote | undefined>()
|
||||
|
||||
// Quick filter presets for mobile bottom bar
|
||||
const quickFilterPresets = {
|
||||
all: { label: 'All', filters: FILTER_PRESETS.all },
|
||||
announcements: { label: 'News', filters: FILTER_PRESETS.announcements },
|
||||
marketplace: { label: 'Market', filters: FILTER_PRESETS.marketplace },
|
||||
social: { label: 'Social', filters: FILTER_PRESETS.social },
|
||||
events: { label: 'Events', filters: FILTER_PRESETS.events }
|
||||
}
|
||||
|
||||
// Computed properties
|
||||
const activeFilterCount = computed(() => selectedFilters.value.length)
|
||||
|
||||
const isPresetActive = (presetKey: string) => {
|
||||
const preset = quickFilterPresets[presetKey as keyof typeof quickFilterPresets]
|
||||
if (!preset) return false
|
||||
|
||||
return preset.filters.length === selectedFilters.value.length &&
|
||||
preset.filters.every(pf => selectedFilters.value.some(sf => sf.id === pf.id))
|
||||
}
|
||||
|
||||
// Determine feed type based on selected filters
|
||||
const feedType = computed(() => {
|
||||
if (selectedFilters.value.length === 0) return 'all'
|
||||
|
|
@ -70,4 +160,37 @@ const feedType = computed(() => {
|
|||
watch(selectedFilters, () => {
|
||||
feedKey.value++
|
||||
}, { deep: true })
|
||||
|
||||
// Handle note composer events
|
||||
// Methods
|
||||
const setQuickFilter = (presetKey: string) => {
|
||||
const preset = quickFilterPresets[presetKey as keyof typeof quickFilterPresets]
|
||||
if (preset) {
|
||||
selectedFilters.value = preset.filters
|
||||
}
|
||||
}
|
||||
|
||||
const onNotePublished = (noteId: string) => {
|
||||
console.log('Note published:', noteId)
|
||||
// Refresh the feed to show the new note
|
||||
feedKey.value++
|
||||
// Clear reply state and hide composer
|
||||
replyTo.value = undefined
|
||||
showComposer.value = false
|
||||
}
|
||||
|
||||
const onClearReply = () => {
|
||||
replyTo.value = undefined
|
||||
showComposer.value = false
|
||||
}
|
||||
|
||||
const onReplyToNote = (note: ReplyToNote) => {
|
||||
replyTo.value = note
|
||||
showComposer.value = true
|
||||
}
|
||||
|
||||
const onCloseComposer = () => {
|
||||
showComposer.value = false
|
||||
replyTo.value = undefined
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue