__( 'Add Theme', 'desktop-mode' ), 'url' => admin_url( 'theme-install.php?browse=popular' ), ); // Idempotent: skip if a previous filter pass (or another plugin) // already added an entry pointing at theme-install.php. foreach ( $dock_item['submenu'] as $existing ) { if ( ! empty( $existing['url'] ) && false !== strpos( $existing['url'], 'theme-install.php' ) ) { return $dock_item; } } array_unshift( $dock_item['submenu'], $add_theme ); return $dock_item; } add_filter( 'openstation_dock_item', 'openstation_inject_appearance_tabs', 10, 2 ); /** * Renders the orientation header for the chromeless Themes workspace. * * Core's page heading is intentionally hidden inside OpenStation because the * window chrome already names the screen. The Themes page still needs a little * more context than a generic list screen, though: a theme changes the public * face of the site, and Core's single-theme state otherwise opens directly on * an unexplained details panel. * * The markup is emitted through `admin_notices`, which places it immediately * before the page's `.wrap`. Core continues to own the theme cards, details * overlay, actions, AJAX updates, and keyboard behavior. * * The screen is identified by `get_current_screen()`, not `$pagenow`. Every * page registered with `add_theme_page()` also reports `themes.php` as its * `$pagenow`, but its body class comes from the hook suffix * (`appearance_page_`) and so matches none of the workspace CSS, which * is scoped to `.themes-php`. Gating on `$pagenow` would emit this header * unstyled on top of unrelated Appearance screens. The screen ID also keeps * network admin (`themes-network`, a different list table entirely) out. */ function openstation_render_themes_workspace_intro() { if ( ! openstation_is_chromeless_request() ) { return; } $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || 'themes' !== $screen->id ) { return; } /* * Mirrors how `wp_prepare_themes_for_js()` builds its own list, without * paying for the preparation itself. Core already called that function * on this request before `admin-header.php` ran, and calling it again * would re-resolve every screenshot, tag and author URL and fire * `pre_prepare_themes_for_js` / `wp_prepare_themes_for_js` a second * time — so any third-party callback hanging off them would run twice * per page load. `wp_get_themes()` is cached, so this is a lookup. */ if ( current_user_can( 'switch_themes' ) ) { $themes = wp_get_themes( array( 'allowed' => true ) ); $stylesheet = get_stylesheet(); if ( ! isset( $themes[ $stylesheet ] ) ) { $themes[ $stylesheet ] = wp_get_theme(); } $theme_count = count( $themes ); } else { $theme_count = 1; } ?>

` (the paragraph role prohibits an author-supplied name), so * pairing it with `aria-hidden` children hid the count from * assistive tech entirely — and Core's own `.title-count` inside * the page `

` is display:none in chromeless mode, making this * the only count on the screen. Read in DOM order the two spans * announce as "3 Themes installed" on their own. */ ?>

a[data-sort="' + browseParam + '"]' ); if ( ! match ) { return false; } var tabs = document.querySelectorAll( '.filter-links li > a[data-sort]' ); var alreadyCorrect = match.classList.contains( 'current' ) && Array.prototype.every.call( tabs, function ( a ) { return a === match || ! a.classList.contains( 'current' ); } ); if ( alreadyCorrect ) { return true; } Array.prototype.forEach.call( tabs, function ( a ) { a.classList.remove( 'current' ); a.removeAttribute( 'aria-current' ); } ); match.classList.add( 'current' ); match.setAttribute( 'aria-current', 'page' ); return true; } function init() { if ( ! applyActiveTab() ) { window.requestAnimationFrame( init ); return; } var container = document.querySelector( '.filter-links' ); if ( ! container ) { return; } var pending = false; var observer = new MutationObserver( function () { if ( pending ) { return; } pending = true; window.requestAnimationFrame( function () { pending = false; applyActiveTab(); } ); } ); observer.observe( container, { attributes: true, subtree: true, attributeFilter: [ 'class', 'aria-current' ], } ); } if ( document.readyState === 'loading' ) { document.addEventListener( 'DOMContentLoaded', init ); } else { init(); } } )(); JS; wp_print_inline_script_tag( $js ); } add_action( 'admin_footer', 'openstation_theme_install_active_tab_script', 100 );