` taxonomy tabs. The `data-os-posts-*` hooks
* are the contract the JS bundle relies on — keep them intact (or
* rename via the filter) when customizing the layout.
*/
function openstation_pages_window_render_template() {
ob_start();
?>
__( 'Pages', 'desktop-mode' ),
'icon' => 'dashicons-admin-page',
'template' => 'openstation_pages_window_render_template',
// Both Posts and Pages share a single bundle — `index.ts`
// registers `desktop-mode-posts` AND `desktop-mode-pages` from
// the same module. Loading `os-posts-window` for the
// Pages window reuses the already-cached script + style.
'script' => 'os-posts-window',
'styles' => array( 'os-posts-window' ),
'width' => 1100,
'height' => 720,
'min_width' => 720,
'min_height' => 480,
'placement' => 'none',
'config' => array(
// `mode` is the JS-side discriminator the shared bundle reads
// to gate Pages-only behavior (parent column, no taxonomy
// tabs). The Posts config omits this key, so the bundle
// defaults to `'posts'` for backwards compat.
'mode' => 'pages',
'restRoot' => esc_url_raw( rest_url() ),
'restNonce' => wp_create_nonce( 'wp_rest' ),
// Use the `/wp/v2/pages` collection rather than `/wp/v2/posts`
// so core's hierarchical column shapers (parent, menu_order)
// are surfaced without an explicit `post_type` query arg.
'postsUrl' => esc_url_raw( rest_url( 'wp/v2/pages' ) ),
'editPostUrlBase' => esc_url_raw( admin_url( 'post.php' ) ),
'newPostUrl' => esc_url_raw( add_query_arg( 'post_type', 'page', admin_url( 'post-new.php' ) ) ),
'usersUrl' => esc_url_raw( rest_url( 'wp/v2/users' ) ),
'currentUserId' => (int) get_current_user_id(),
'defaultPerPage' => 20,
'queryArgs' => openstation_pages_window_default_query_args(),
// Reading-page assignments — surfaced so the title cell can
// paint "Front page" / "Posts page" badges on matching rows
// (one of the most-asked Pages-list usability gaps in the
// WordPress trac + community forums). `0` when unset.
'frontPageId' => (int) get_option( 'page_on_front', 0 ),
'postsPageId' => (int) get_option( 'page_for_posts', 0 ),
// Page-template label map: `{ slug: human label }` for the
// templates the active theme registers. Falls back to the
// raw slug when a theme registers a template the table
// hasn't seen — better to show "page-fullwidth.php" than to
// hide which template is in use.
'pageTemplates' => openstation_pages_window_template_labels(),
),
);
/**
* Filter the args used to register the native Pages window.
*
* @param array $window_args Args passed to `openstation_register_window()`.
*/
$window_args = (array) apply_filters( 'openstation_pages_window_args', $window_args );
$registered = openstation_register_window( 'desktop-mode-pages', $window_args );
if ( is_wp_error( $registered ) ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log( '[openstation] Native Pages window registration failed: ' . $registered->get_error_message() );
}
}
add_action( 'init', 'openstation_pages_window_register_window', 20 );
/**
* Default REST query args for the Pages window.
*
* @return array
*/
function openstation_pages_window_default_query_args() {
$args = array(
// Pages are usually shallow + ordered by menu_order; embed the
// author + featured media for the table cells.
'_embed' => 'author,wp:featuredmedia',
// `slug`, `template`, `link` and the custom `openstation_comment_count`
// field are pulled in for the new Slug / Template / Comments
// columns and the public-URL "View" quick-action. Missing them
// from the whitelist costs nothing on the wire (REST will skip
// them) but does silently break the column — keep them here.
'_fields' =>
'id,title,status,date,date_gmt,modified,modified_gmt,author,parent,menu_order,slug,link,template,comment_status,excerpt,openstation_lock,openstation_comment_count,_links,_embedded',
'orderby' => 'menu_order',
'order' => 'asc',
);
/**
* Filter the default outbound REST query args for the Pages window.
*
* @param array $args Default args.
*/
return (array) apply_filters( 'openstation_pages_window_query_args', $args );
}
/**
* Build the `{ slug: label }` map for the active theme's registered
* page templates. Used by the Pages window's Template column to
* paint friendly labels instead of raw filenames.
*
* The "default" template (assigned when `page.template` is `''`) is
* keyed under the empty string for parity with what core returns in
* `/wp/v2/pages` responses.
*
* @return array
*/
function openstation_pages_window_template_labels() {
$labels = array(
'' => __( 'Default template', 'desktop-mode' ),
);
if ( function_exists( 'wp_get_theme' ) ) {
$theme = wp_get_theme();
if ( $theme && method_exists( $theme, 'get_page_templates' ) ) {
$registered = (array) $theme->get_page_templates( null, 'page' );
foreach ( $registered as $slug => $label ) {
$labels[ (string) $slug ] = (string) $label;
}
}
}
/**
* Filter the page-template label map handed to the Pages window.
*
* @param array $labels Slug → human label.
*/
return (array) apply_filters( 'openstation_pages_window_template_labels', $labels );
}
/**
* Register the `openstation_comment_count` REST field on `page`.
*
* The default `/wp/v2/pages` response doesn't include a comment
* count — surfacing one alongside the row keeps parity with the
* classic Pages list (where the count is a column) without forcing
* the table to embed `_embed=replies`, which is heavy and N+1.
*
* Registered on `page` only for now; other CPTs can opt in by
* cloning this `register_rest_field` call. Posts already track
* comments via the classic admin and can be wired the same way
* if/when the Posts window grows the column.
*/
function openstation_pages_window_register_comment_count_field() {
register_rest_field(
'page',
'openstation_comment_count',
array(
'get_callback' => static function ( $row ) {
$id = isset( $row['id'] ) ? (int) $row['id'] : 0;
if ( $id <= 0 ) {
return 0;
}
return (int) get_comments_number( $id );
},
'schema' => array(
'description' => __( 'Total non-trashed comments on this page.', 'desktop-mode' ),
'type' => 'integer',
'context' => array( 'view', 'embed' ),
'readonly' => true,
),
)
);
}
add_action( 'rest_api_init', 'openstation_pages_window_register_comment_count_field' );