__( '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 ); /** * Fixes the visible "active tab" state inside chromeless * theme-install.php iframes. * * Background: WP's installer JS uses a Backbone router whose pattern * `theme-install.php?browse=:sort` greedy-captures everything past * `browse=` ([^/?]+ matches `&` too). Inside chromeless the URL also * carries `openstation_chromeless=1`, so `:sort` ends up as * `popular&openstation_chromeless=1`. WP's `view.sort()` then runs * with that polluted value and the `[data-sort]` selector finds * nothing — leaving every tab in the unselected state even though * the popular themes ARE the rendered listing. * * Reordering query params or re-encoding doesn't help: any URL that * exposes `browse=` to the router matches the route, and any URL * that hides it falls through to a redirect that strips the * chromeless flag (which would re-render the iframe with full admin * chrome on next paint). * * The minimum-viable shim: inline JS that reads the real `browse` * value dynamically via URLSearchParams on every check, finds the * matching `[data-sort]` tab, and sets `current` + `aria-current` on it. * A MutationObserver on `.filter-links` keeps the tab synced when WP's * router fires or state changes (e.g. switching tabs to Latest or * returning via pushState). */ function openstation_theme_install_active_tab_script() { if ( ! openstation_is_chromeless_request() ) { return; } if ( ! isset( $GLOBALS['pagenow'] ) || 'theme-install.php' !== $GLOBALS['pagenow'] ) { return; } $js = <<<'JS' ( function () { function getBrowseParam() { return new URLSearchParams( window.location.search ).get( 'browse' ); } if ( ! getBrowseParam() ) { return; } function applyActiveTab() { var browseParam = getBrowseParam(); if ( ! browseParam ) { return true; } var match = document.querySelector( '.filter-links li > 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 );