| @@ -67,18 +67,19 @@ | ||
| 67 | 67 | * @return array<string,mixed> |
| 68 | 68 | */ |
| 69 | 69 | function openstation_posts_app_state( $orderby = 'date', $order = 'desc' ) { |
| 70 | 70 | return array( |
| 71 | - 'page' => 1, | |
| 72 | - 'perPage' => 20, | |
| 73 | - 'search' => '', | |
| 71 | + 'page' => 1, | |
| 72 | + 'feedAppend' => false, | |
| 73 | + 'perPage' => 20, | |
| 74 | + 'search' => '', | |
| 74 | 75 | // `''` is the "All" sentinel — sent as `status=any`. |
| 75 | - 'status' => '', | |
| 76 | - 'orderby' => (string) $orderby, | |
| 77 | - 'order' => (string) $order, | |
| 76 | + 'status' => '', | |
| 77 | + 'orderby' => (string) $orderby, | |
| 78 | + 'order' => (string) $order, | |
| 78 | 79 | // Author user ids and tag term ids to filter by; empty = no filter. |
| 79 | - 'author' => array(), | |
| 80 | - 'tag' => array(), | |
| 80 | + 'author' => array(), | |
| 81 | + 'tag' => array(), | |
| 81 | 82 | ); |
| 82 | 83 | } |
| 83 | 84 | |
| 84 | 85 | /** |
| @@ -182,12 +183,11 @@ | ||
| 182 | 183 | return $query; |
| 183 | 184 | } |
| 184 | 185 | |
| 185 | 186 | /** |
| 186 | - * The client data: the current page of rows as the paged-list | |
| 187 | - * envelope (plus `error`), with the "page out of range → page 1" | |
| 188 | - * recovery the legacy bundle did client-side (the typical case is | |
| 189 | - * the user on page 7 changing per-page from 10 to 100). | |
| 187 | + * The client data: a continuation page or a refreshed loaded prefix, | |
| 188 | + * with a query identity for rejecting stale responses. Out-of-range | |
| 189 | + * continuations recover to page one; failed refreshes retain paging. | |
| 190 | 190 | * |
| 191 | 191 | * @param string $route `wp/v2/posts` | `wp/v2/pages`. |
| 192 | 192 | * @param array<string,mixed> $defaults Filtered default args. |
| 193 | 193 | * @param State $state Declared state. |
| @@ -193,10 +193,43 @@ | ||
| 193 | 193 | * @param State $state Declared state. |
| 194 | 194 | * @return array<string,mixed> `list`. |
| 195 | 195 | */ |
| 196 | 196 | function openstation_posts_app_data( $route, array $defaults, State $state ) { |
| 197 | - $query = openstation_posts_app_query( $defaults, $state ); | |
| 198 | - $list = openstation_app_rest_page( $route, $query ); | |
| 197 | + $query = openstation_posts_app_query( $defaults, $state ); | |
| 198 | + $append = (bool) $state->get( 'feedAppend' ); | |
| 199 | + $state->set( 'feedAppend', false ); | |
| 200 | + // Refresh the loaded prefix in one HTTP response. Core still enforces | |
| 201 | + // permissions, query filters and its 100-row bound for each internal batch. | |
| 202 | + if ( ! $append && $query['page'] > 1 ) { | |
| 203 | + $wanted = $query['page'] * $query['per_page']; | |
| 204 | + $refresh = $query; | |
| 205 | + $refresh['page'] = 1; | |
| 206 | + $refresh['per_page'] = min( 100, $wanted ); | |
| 207 | + $list = openstation_app_rest_page( $route, $refresh ); | |
| 208 | + $wanted = min( $wanted, $list['total'] ); | |
| 209 | + $received = count( $list['items'] ); | |
| 210 | + while ( empty( $list['error'] ) && $received < $wanted ) { | |
| 211 | + ++$refresh['page']; | |
| 212 | + $batch = openstation_app_rest_page( $route, $refresh ); | |
| 213 | + if ( ! empty( $batch['error'] ) ) { | |
| 214 | + $list = $batch; | |
| 215 | + break; | |
| 216 | + } | |
| 217 | + if ( empty( $batch['items'] ) ) { | |
| 218 | + break; | |
| 219 | + } | |
| 220 | + $list['items'] = array_merge( $list['items'], $batch['items'] ); | |
| 221 | + $received = count( $list['items'] ); | |
| 222 | + } | |
| 223 | + $list['items'] = array_slice( $list['items'], 0, $wanted ); | |
| 224 | + $list['perPage'] = $query['per_page']; | |
| 225 | + $list['pages'] = (int) ceil( $list['total'] / $query['per_page'] ); | |
| 226 | + $list['page'] = empty( $list['error'] ) ? min( $query['page'], max( 1, $list['pages'] ) ) : $query['page']; | |
| 227 | + $state->set( 'page', $list['page'] ); | |
| 228 | + $list['replace'] = true; | |
| 229 | + } else { | |
| 230 | + $list = openstation_app_rest_page( $route, $query ); | |
| 231 | + } | |
| 199 | 232 | // A page past the end — Core's `rest_post_invalid_page_number` |
| 200 | 233 | // refusal, or an empty page on a controller that tolerates it — |
| 201 | 234 | // lands on page 1 silently rather than render an empty table. A |
| 202 | 235 | // refusal for any other reason (a capability, a bad argument) is |
| @@ -210,9 +243,16 @@ | ||
| 210 | 243 | // The legacy pager read "No posts" off a zero page count; the |
| 211 | 244 | // envelope floors `pages` at 1, so hand the client the truth. |
| 212 | 245 | $list['pages'] = 0; |
| 213 | 246 | } |
| 214 | - return array( 'list' => $list ); | |
| 247 | + $identity = array(); | |
| 248 | + foreach ( array( 'search', 'status', 'orderby', 'order', 'author', 'tag', 'perPage' ) as $key ) { | |
| 249 | + $identity[ $key ] = $state->get( $key ); | |
| 250 | + } | |
| 251 | + return array( | |
| 252 | + 'list' => $list, | |
| 253 | + 'query' => $identity, | |
| 254 | + ); | |
| 215 | 255 | } |
| 216 | 256 | |
| 217 | 257 | /** |
| 218 | 258 | * `filter` — a query change (status, search, per-page, column |
| @@ -233,8 +273,9 @@ | ||
| 233 | 273 | * @return void |
| 234 | 274 | */ |
| 235 | 275 | function openstation_posts_app_page( State $state, array $args ) { |
| 236 | 276 | $state->set( 'page', max( 1, isset( $args['page'] ) ? (int) $args['page'] : 1 ) ); |
| 277 | + $state->set( 'feedAppend', true ); | |
| 237 | 278 | } |
| 238 | 279 | |
| 239 | 280 | /** |
| 240 | 281 | * `sort` — a column header click; the client maps the column key to |
| @@ -253,8 +294,9 @@ | ||
| 253 | 294 | if ( ! in_array( $orderby, openstation_posts_app_allowed_orderby(), true ) ) { |
| 254 | 295 | $orderby = (string) $default_orderby; |
| 255 | 296 | } |
| 256 | 297 | $order = isset( $args['order'] ) ? strtolower( (string) $args['order'] ) : (string) $default_order; |
| 298 | + $state->set( 'page', 1 ); | |
| 257 | 299 | $state->set( 'orderby', $orderby ); |
| 258 | 300 | $state->set( 'order', 'asc' === $order ? 'asc' : 'desc' ); |
| 259 | 301 | } |
| 260 | 302 | |