@@ -54,7 +54,11 @@ pub async fn get_followees(
5454 form. event_id
5555 . map ( |event_id| TimelineCursor :: ByEventTime ( EventPaginationCursor { ts, event_id } ) )
5656 } ) ;
57- let navbar = state. timeline_common_navbar ( & session) . await ?;
57+ let navbar = state
58+ . timeline_common_navbar ( )
59+ . session ( & session)
60+ . call ( )
61+ . await ?;
5862 Ok ( Maud (
5963 state
6064 . render_timeline_page (
@@ -80,7 +84,11 @@ pub async fn get_network(
8084 form. event_id
8185 . map ( |event_id| TimelineCursor :: ByEventTime ( EventPaginationCursor { ts, event_id } ) )
8286 } ) ;
83- let navbar = state. timeline_common_navbar ( & session) . await ?;
87+ let navbar = state
88+ . timeline_common_navbar ( )
89+ . session ( & session)
90+ . call ( )
91+ . await ?;
8492 Ok ( Maud (
8593 state
8694 . render_timeline_page (
@@ -106,7 +114,11 @@ pub async fn get_notifications(
106114 form. seq
107115 . map ( |seq| TimelineCursor :: ByReceivedTime ( ReceivedAtPaginationCursor { ts, seq } ) )
108116 } ) ;
109- let navbar = state. timeline_common_navbar ( & session) . await ?;
117+ let navbar = state
118+ . timeline_common_navbar ( )
119+ . session ( & session)
120+ . call ( )
121+ . await ?;
110122 Ok ( Maud (
111123 state
112124 . render_timeline_page (
@@ -127,6 +139,8 @@ pub struct UpdatesQuery {
127139 pub network : Option < usize > ,
128140 pub notifications : Option < usize > ,
129141 pub shoutbox : Option < usize > ,
142+ /// If true, we're on the shoutbox page - skip shoutbox counter updates
143+ pub on_shoutbox : Option < bool > ,
130144}
131145
132146pub async fn get_updates (
@@ -141,9 +155,10 @@ pub async fn get_updates(
141155 notifications : query. notifications . unwrap_or ( 0 ) ,
142156 shoutbox : query. shoutbox . unwrap_or ( 0 ) ,
143157 } ;
158+ let on_shoutbox = query. on_shoutbox . unwrap_or ( false ) ;
144159 ws. on_upgrade ( move |ws| async move {
145160 let _ = state
146- . handle_get_updates ( ws, & session, pending)
161+ . handle_get_updates ( ws, & session, pending, on_shoutbox )
147162 . await
148163 . inspect_err ( |err| {
149164 debug ! ( target: LOG_TARGET , err=%err. fmt_compact( ) , "WS handler failed" ) ;
@@ -165,9 +180,14 @@ pub async fn get_post_replies(
165180
166181#[ bon:: bon]
167182impl UiState {
183+ #[ builder]
168184 pub ( crate ) async fn timeline_common_navbar (
169185 & self ,
170186 session : & UserSession ,
187+ /// If true, the new post form is hidden (e.g., on shoutbox page which
188+ /// has its own input)
189+ #[ builder( default ) ]
190+ hide_new_post_form : bool ,
171191 ) -> RequestResult < Markup > {
172192 let client = self . client ( session. id ( ) ) . await ?;
173193 let client_ref = client. client_ref ( ) ?;
@@ -182,7 +202,9 @@ impl UiState {
182202 ( self . render_self_profile_summary( session, ro_mode) . await ?)
183203 }
184204
185- ( self . new_post_form( None , ro_mode, Some ( user_id) ) )
205+ @if !hide_new_post_form {
206+ ( self . new_post_form( None , ro_mode, Some ( user_id) ) )
207+ }
186208 }
187209 } )
188210 }
@@ -192,6 +214,7 @@ impl UiState {
192214 mut ws : WebSocket ,
193215 user : & UserSession ,
194216 initial_pending : PendingCounts ,
217+ on_shoutbox : bool ,
195218 ) -> RequestResult < ( ) > {
196219 let client = self . client ( user. id ( ) ) . await ?;
197220 let client_ref = client. client_ref ( ) ?;
@@ -261,17 +284,20 @@ impl UiState {
261284 }
262285 } ;
263286 let author = event_content. event. event. author;
264- // Count shoutbox posts from others
265- if author != self_id {
287+
288+ // Only count shoutbox posts if not on shoutbox page
289+ if !on_shoutbox && author != self_id {
266290 shoutbox_count += 1 ;
267291 }
268292
269- // Send the rendered shout for live updates
270- let shout_html = self
271- . render_shoutbox_post_live( & client_ref, self_id, author, & shoutbox_content)
272- . await
273- . into_string( ) ;
274- let _ = ws. send( shout_html. into( ) ) . await ;
293+ // Send the rendered shout for live updates (only if on shoutbox page)
294+ if on_shoutbox {
295+ let shout_html = self
296+ . render_shoutbox_post_live( & client_ref, self_id, author, & shoutbox_content)
297+ . await
298+ . into_string( ) ;
299+ let _ = ws. send( shout_html. into( ) ) . await ;
300+ }
275301 }
276302 }
277303
@@ -305,7 +331,7 @@ impl UiState {
305331 }
306332
307333 /// Render a shoutbox post for live WebSocket updates.
308- /// Returns HTML that will be inserted into the shoutbox via x-init .
334+ /// Returns HTML that will be appended to # shoutbox-posts via x-merge .
309335 async fn render_shoutbox_post_live (
310336 & self ,
311337 client : & ClientRef < ' _ > ,
@@ -327,27 +353,13 @@ impl UiState {
327353 . map ( |c| format ! ( r#"{{"ts":{},"seq":{}}}"# , u64 :: from( c. ts) , c. seq) )
328354 . unwrap_or_default ( ) ;
329355
330- // The WebSocket handler appends [x-init] elements to body, calls initTree, then
331- // removes them. We use x-data + x-init to ensure Alpine context is
332- // available.
356+ // WebSocket handler supports x-merge="append" for appending children to target
333357 html ! {
334- div
335- x-data
336- x-init=( format!( r#"
337- const container = document.getElementById('shoutbox-posts');
338- if (container) {{
339- const post = $el.querySelector('.o-shoutbox__post');
340- if (post) {{
341- container.appendChild(post);
342- const messages = document.getElementById('shoutbox-messages');
343- if (messages) messages.scrollTop = messages.scrollHeight;
344- // Update last-seen cookie since user is viewing shoutbox
345- document.cookie = '{cookie_name}=' + encodeURIComponent('{cookie_value}') + '; path=/; max-age=31536000';
346- }}
347- }}
348- "# ) )
349- {
350- div . "o-shoutbox__post -new" {
358+ div id="shoutbox-posts" x-merge="append" {
359+ div . "o-shoutbox__post -new"
360+ x-autofocus
361+ x-init=( format!( r#"document.cookie = '{cookie_name}=' + encodeURIComponent('{cookie_value}') + '; path=/; max-age=31536000';"# ) )
362+ {
351363 img . "o-shoutbox__avatar u-userImage"
352364 src=( self . avatar_url( author) )
353365 alt="Avatar"
0 commit comments