- Introduce a modular application structure with a new app configuration file to manage module settings and features. - Implement a dependency injection container for service management across modules. - Create a plugin manager to handle module registration, installation, and lifecycle management. - Develop a global event bus for inter-module communication, enhancing loose coupling between components. - Add core modules including base functionalities, Nostr feed, and PWA services, with support for dynamic loading and configuration. - Establish a Nostr client hub for managing WebSocket connections and event handling. - Enhance user experience with a responsive Nostr feed component, integrating admin announcements and community posts. - Refactor existing components to align with the new modular architecture, improving maintainability and scalability.
130 lines
No EOL
3.3 KiB
TypeScript
130 lines
No EOL
3.3 KiB
TypeScript
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
|
import { eventBus } from '@/core/event-bus'
|
|
import type { Event as NostrEvent, Filter } from 'nostr-tools'
|
|
|
|
export interface FeedConfig {
|
|
feedType: 'announcements' | 'general' | 'mentions'
|
|
maxPosts?: number
|
|
refreshInterval?: number
|
|
adminPubkeys?: string[]
|
|
}
|
|
|
|
export function useFeed(config: FeedConfig) {
|
|
const relayHub = injectService<any>(SERVICE_TOKENS.RELAY_HUB)
|
|
const posts = ref<NostrEvent[]>([])
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
|
|
let refreshTimer: number | null = null
|
|
let unsubscribe: (() => void) | null = null
|
|
|
|
const filteredPosts = computed(() => {
|
|
let filtered = posts.value
|
|
|
|
// Filter by feed type
|
|
if (config.feedType === 'announcements' && config.adminPubkeys) {
|
|
filtered = filtered.filter(post => config.adminPubkeys!.includes(post.pubkey))
|
|
}
|
|
|
|
// Sort by created timestamp (newest first)
|
|
filtered = filtered.sort((a, b) => b.created_at - a.created_at)
|
|
|
|
// Limit posts
|
|
if (config.maxPosts) {
|
|
filtered = filtered.slice(0, config.maxPosts)
|
|
}
|
|
|
|
return filtered
|
|
})
|
|
|
|
const loadFeed = async () => {
|
|
if (!relayHub) {
|
|
error.value = 'RelayHub not available'
|
|
return
|
|
}
|
|
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
// Create filter based on feed type
|
|
const filter: Filter = {
|
|
kinds: [1], // Text notes
|
|
limit: config.maxPosts || 50
|
|
}
|
|
|
|
if (config.feedType === 'announcements' && config.adminPubkeys) {
|
|
filter.authors = config.adminPubkeys
|
|
}
|
|
|
|
// Subscribe to events
|
|
await relayHub.subscribe('feed-subscription', [filter], {
|
|
onEvent: (event: NostrEvent) => {
|
|
// Add new event if not already present
|
|
if (!posts.value.some(p => p.id === event.id)) {
|
|
posts.value = [event, ...posts.value]
|
|
|
|
// Emit event for other modules
|
|
eventBus.emit('nostr-feed:new-post', { event, feedType: config.feedType }, 'nostr-feed')
|
|
}
|
|
},
|
|
onEose: () => {
|
|
console.log('Feed subscription end of stored events')
|
|
isLoading.value = false
|
|
},
|
|
onClose: () => {
|
|
console.log('Feed subscription closed')
|
|
}
|
|
})
|
|
|
|
unsubscribe = () => {
|
|
relayHub.unsubscribe('feed-subscription')
|
|
}
|
|
|
|
} catch (err) {
|
|
console.error('Failed to load feed:', err)
|
|
error.value = err instanceof Error ? err.message : 'Failed to load feed'
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const refreshFeed = () => {
|
|
posts.value = []
|
|
loadFeed()
|
|
}
|
|
|
|
const startAutoRefresh = () => {
|
|
if (config.refreshInterval && config.refreshInterval > 0) {
|
|
refreshTimer = setInterval(refreshFeed, config.refreshInterval) as unknown as number
|
|
}
|
|
}
|
|
|
|
const stopAutoRefresh = () => {
|
|
if (refreshTimer) {
|
|
clearInterval(refreshTimer)
|
|
refreshTimer = null
|
|
}
|
|
}
|
|
|
|
// Lifecycle
|
|
onMounted(() => {
|
|
loadFeed()
|
|
startAutoRefresh()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
stopAutoRefresh()
|
|
if (unsubscribe) {
|
|
unsubscribe()
|
|
}
|
|
})
|
|
|
|
return {
|
|
posts: filteredPosts,
|
|
isLoading,
|
|
error,
|
|
refreshFeed,
|
|
loadFeed
|
|
}
|
|
} |