ContentEventHandlers.ts raw
1 import {
2 EventBookmarked,
3 EventUnbookmarked,
4 BookmarkListPublished,
5 NotePinned,
6 NoteUnpinned,
7 PinsLimitExceeded,
8 PinListPublished,
9 ReactionAdded,
10 ContentReposted
11 } from '@/domain/content'
12 import { EventHandler, eventDispatcher } from '@/domain/shared'
13
14 /**
15 * Handlers for content domain events
16 *
17 * These handlers coordinate cross-context updates when content events occur.
18 * They enable real-time UI updates and cross-context coordination.
19 */
20
21 /**
22 * Callback for updating reaction counts in UI
23 */
24 export type UpdateReactionCountCallback = (eventId: string, emoji: string, delta: number) => void
25
26 /**
27 * Callback for updating repost counts in UI
28 */
29 export type UpdateRepostCountCallback = (eventId: string, delta: number) => void
30
31 /**
32 * Callback for creating notifications
33 */
34 export type CreateNotificationCallback = (
35 type: 'reaction' | 'repost' | 'mention' | 'reply',
36 actorPubkey: string,
37 targetEventId: string
38 ) => void
39
40 /**
41 * Callback for showing toast messages
42 */
43 export type ShowToastCallback = (message: string, type: 'info' | 'warning' | 'error') => void
44
45 /**
46 * Callback for updating profile pinned notes
47 */
48 export type UpdateProfilePinsCallback = (pubkey: string) => void
49
50 /**
51 * Service callbacks for cross-context coordination
52 */
53 export interface ContentHandlerCallbacks {
54 onUpdateReactionCount?: UpdateReactionCountCallback
55 onUpdateRepostCount?: UpdateRepostCountCallback
56 onCreateNotification?: CreateNotificationCallback
57 onShowToast?: ShowToastCallback
58 onUpdateProfilePins?: UpdateProfilePinsCallback
59 }
60
61 let callbacks: ContentHandlerCallbacks = {}
62
63 /**
64 * Set the callbacks for cross-context coordination
65 * Call this during provider initialization
66 */
67 export function setContentHandlerCallbacks(newCallbacks: ContentHandlerCallbacks): void {
68 callbacks = { ...callbacks, ...newCallbacks }
69 }
70
71 /**
72 * Clear all callbacks (for cleanup/testing)
73 */
74 export function clearContentHandlerCallbacks(): void {
75 callbacks = {}
76 }
77
78 /**
79 * Handler for event bookmarked
80 * Can be used to:
81 * - Update bookmark count displays
82 * - Prefetch bookmarked content for offline access
83 */
84 export const handleEventBookmarked: EventHandler<EventBookmarked> = async (event) => {
85 console.debug('[ContentEventHandler] Event bookmarked:', {
86 actor: event.actor.formatted,
87 bookmarkedEventId: event.bookmarkedEventId,
88 type: event.bookmarkType
89 })
90
91 // Future: Trigger prefetch of bookmarked content
92 }
93
94 /**
95 * Handler for event unbookmarked
96 */
97 export const handleEventUnbookmarked: EventHandler<EventUnbookmarked> = async (event) => {
98 console.debug('[ContentEventHandler] Event unbookmarked:', {
99 actor: event.actor.formatted,
100 unbookmarkedEventId: event.unbookmarkedEventId
101 })
102 }
103
104 /**
105 * Handler for bookmark list published
106 */
107 export const handleBookmarkListPublished: EventHandler<BookmarkListPublished> = async (event) => {
108 console.debug('[ContentEventHandler] Bookmark list published:', {
109 owner: event.owner.formatted,
110 bookmarkCount: event.bookmarkCount
111 })
112 }
113
114 /**
115 * Handler for note pinned
116 * Coordinates with:
117 * - Profile context: Update pinned notes display
118 * - Cache context: Ensure pinned content is cached
119 */
120 export const handleNotePinned: EventHandler<NotePinned> = async (event) => {
121 console.debug('[ContentEventHandler] Note pinned:', {
122 actor: event.actor.formatted,
123 pinnedEventId: event.pinnedEventId.hex
124 })
125
126 // Update profile display to show new pinned note
127 if (callbacks.onUpdateProfilePins) {
128 callbacks.onUpdateProfilePins(event.actor.hex)
129 }
130 }
131
132 /**
133 * Handler for note unpinned
134 * Coordinates with:
135 * - Profile context: Update pinned notes display
136 */
137 export const handleNoteUnpinned: EventHandler<NoteUnpinned> = async (event) => {
138 console.debug('[ContentEventHandler] Note unpinned:', {
139 actor: event.actor.formatted,
140 unpinnedEventId: event.unpinnedEventId
141 })
142
143 // Update profile display to remove unpinned note
144 if (callbacks.onUpdateProfilePins) {
145 callbacks.onUpdateProfilePins(event.actor.hex)
146 }
147 }
148
149 /**
150 * Handler for pins limit exceeded
151 * Coordinates with:
152 * - UI context: Show toast notification about removed pins
153 */
154 export const handlePinsLimitExceeded: EventHandler<PinsLimitExceeded> = async (event) => {
155 console.debug('[ContentEventHandler] Pins limit exceeded:', {
156 actor: event.actor.formatted,
157 removedCount: event.removedEventIds.length
158 })
159
160 // Show toast notification about removed pins
161 if (callbacks.onShowToast) {
162 callbacks.onShowToast(
163 `Pin limit reached. ${event.removedEventIds.length} older pin(s) were removed.`,
164 'warning'
165 )
166 }
167 }
168
169 /**
170 * Handler for pin list published
171 */
172 export const handlePinListPublished: EventHandler<PinListPublished> = async (event) => {
173 console.debug('[ContentEventHandler] Pin list published:', {
174 owner: event.owner.formatted,
175 pinCount: event.pinCount
176 })
177 }
178
179 /**
180 * Handler for reaction added
181 * Coordinates with:
182 * - UI context: Update reaction counts in real-time
183 * - Notification context: Create notification for content author
184 */
185 export const handleReactionAdded: EventHandler<ReactionAdded> = async (event) => {
186 console.debug('[ContentEventHandler] Reaction added:', {
187 actor: event.actor.formatted,
188 targetEventId: event.targetEventId.hex,
189 targetAuthor: event.targetAuthor.formatted,
190 emoji: event.emoji,
191 isLike: event.isLike
192 })
193
194 // Update reaction count in UI
195 if (callbacks.onUpdateReactionCount) {
196 callbacks.onUpdateReactionCount(event.targetEventId.hex, event.emoji, 1)
197 }
198
199 // Create notification for the content author (if not self)
200 if (callbacks.onCreateNotification && event.actor.hex !== event.targetAuthor.hex) {
201 callbacks.onCreateNotification('reaction', event.actor.hex, event.targetEventId.hex)
202 }
203 }
204
205 /**
206 * Handler for content reposted
207 * Coordinates with:
208 * - UI context: Update repost counts in real-time
209 * - Notification context: Create notification for original author
210 */
211 export const handleContentReposted: EventHandler<ContentReposted> = async (event) => {
212 console.debug('[ContentEventHandler] Content reposted:', {
213 actor: event.actor.formatted,
214 originalEventId: event.originalEventId.hex,
215 originalAuthor: event.originalAuthor.formatted
216 })
217
218 // Update repost count in UI
219 if (callbacks.onUpdateRepostCount) {
220 callbacks.onUpdateRepostCount(event.originalEventId.hex, 1)
221 }
222
223 // Create notification for the original author (if not self)
224 if (callbacks.onCreateNotification && event.actor.hex !== event.originalAuthor.hex) {
225 callbacks.onCreateNotification('repost', event.actor.hex, event.originalEventId.hex)
226 }
227 }
228
229 /**
230 * Register all content event handlers with the event dispatcher
231 */
232 export function registerContentEventHandlers(): void {
233 eventDispatcher.on('content.event_bookmarked', handleEventBookmarked)
234 eventDispatcher.on('content.event_unbookmarked', handleEventUnbookmarked)
235 eventDispatcher.on('content.bookmark_list_published', handleBookmarkListPublished)
236 eventDispatcher.on('content.note_pinned', handleNotePinned)
237 eventDispatcher.on('content.note_unpinned', handleNoteUnpinned)
238 eventDispatcher.on('content.pins_limit_exceeded', handlePinsLimitExceeded)
239 eventDispatcher.on('content.pin_list_published', handlePinListPublished)
240 eventDispatcher.on('content.reaction_added', handleReactionAdded)
241 eventDispatcher.on('content.reposted', handleContentReposted)
242 }
243
244 /**
245 * Unregister all content event handlers
246 */
247 export function unregisterContentEventHandlers(): void {
248 eventDispatcher.off('content.event_bookmarked', handleEventBookmarked)
249 eventDispatcher.off('content.event_unbookmarked', handleEventUnbookmarked)
250 eventDispatcher.off('content.bookmark_list_published', handleBookmarkListPublished)
251 eventDispatcher.off('content.note_pinned', handleNotePinned)
252 eventDispatcher.off('content.note_unpinned', handleNoteUnpinned)
253 eventDispatcher.off('content.pins_limit_exceeded', handlePinsLimitExceeded)
254 eventDispatcher.off('content.pin_list_published', handlePinListPublished)
255 eventDispatcher.off('content.reaction_added', handleReactionAdded)
256 eventDispatcher.off('content.reposted', handleContentReposted)
257 }
258