add_node(
array(
'parent' => 'top-secondary',
'id' => 'os-toggle',
'title' => ''
. '' . $label . '',
'href' => '#',
'meta' => array(
'tabindex' => 0,
'title' => $label,
),
)
);
}
add_action( 'admin_bar_menu', 'openstation_admin_bar_toggle', 190 );
/**
* Enqueues the CSS and JS for the OpenStation toggle.
*
* The CSS is inline, attached to the `admin-bar` style handle so it always
* ships with the admin bar itself — no matter which admin screen is showing.
* The JS is the external assets/js/admin-bar.js bundle, registered as
* `os-admin-bar` with `admin-bar` as a dependency; its config is
* emitted as an inline JSON literal `before` the script.
*/
function openstation_enqueue_toggle_assets() {
if ( ! is_admin() || ! is_user_logged_in() ) {
return;
}
// Inside a chromeless window the admin bar is suppressed outright
// (`show_admin_bar` + the `wp_admin_bar_render` removal in
// helpers.php), so `#wpadminbar` never reaches the DOM. Every byte
// below — the toggle bundle, its inline config, the node styling —
// would load and run against markup that does not exist. See
// `includes/render/chromeless-trim.php`, which drops the rest of
// that family (core's `admin-bar`, host masterbar extras) for the
// same reason.
if ( openstation_is_chromeless_request() ) {
return;
}
// The item is `display: flex`, never `inline-flex`. An
// inline-level box sits on a line box the
lays out at its
// 32px line-height and aligns on the baseline, so the line grows
// by the descender: the item became 37px inside a 32px bar. Under
// Core's float layout that overflow was invisible. On a host that
// lays the secondary group out as a flex row (WordPress.com's Debug
// Bar does, to order its own item first) every sibling stretched
// to the tallest one and the group's background painted 5px into
// the shell, under the windows' title bars. Block-level, the item
// is exactly the bar. `Tests_OpenStation_AdminBarDesktopToggle`
// pins it; `desktop.css` caps the group as well, for items we do
// not own.
$css = '
#wpadminbar #wp-admin-bar-os-toggle > .ab-item {
display: flex;
align-items: center;
gap: 6px;
}
#wpadminbar #wp-admin-bar-os-toggle .ab-icon {
float: none;
margin: 0;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
}
#wp-admin-bar-os-toggle .ab-icon.dashicons {
font: normal 20px/1 dashicons;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#wp-admin-bar-os-toggle .ab-icon.dashicons::before {
content: "\f472";
top: 0;
position: static;
}
@media screen and (max-width: 782px) {
/* WP core hides most admin-bar items on mobile. Force the toggle to
stay visible so users can switch back to OpenStation/Classic. */
#wpadminbar #wp-admin-bar-os-toggle {
display: block;
}
#wpadminbar #wp-admin-bar-os-toggle > .ab-item {
padding: 0 14px;
}
#wpadminbar #wp-admin-bar-os-toggle .ab-label {
display: none;
}
}
';
wp_add_inline_style( 'admin-bar', $css );
// All PHP→JS values are emitted as JSON literals (never interpolated
// raw into the script body) so special characters, quotes, and
// unexpected shapes can't break the parser or be exploited.
//
// The config is emitted on every admin screen, including the ones
// where the toggle node is absent: the shell reads the same global
// for the "Exit OpenStation" tile's AJAX call and for the
// keyboard-shortcuts window.
wp_register_script(
'os-admin-bar',
OPENSTATION_URL . 'assets/js/admin-bar.js',
array( 'admin-bar' ),
OPENSTATION_VERSION,
true
);
// Emit the config as a JSON literal via wp_add_inline_script, not
// wp_localize_script — the latter casts every value to a string,
// so `network` would arrive as '' / '1'. 'before' runs ahead of
// admin-bar.js so the global is ready when the IIFE fires.
wp_add_inline_script(
'os-admin-bar',
'var openStationAdminBar = ' . wp_json_encode(
array(
'nonce' => wp_create_nonce( 'save-openstation' ),
// `self_admin_url()`: switching off from the network
// admin returns there, not to the main site.
'classicUrl' => esc_url_raw( self_admin_url() ),
'portalUrl' => esc_url_raw( openstation_portal_url() ),
// Passed back on the enable / disable AJAX call: the
// handler runs on `admin-ajax.php`, where
// `is_network_admin()` is always false.
'network' => is_network_admin(),
'ajaxUrl' => esc_url_raw( admin_url( 'admin-ajax.php' ) ),
/*
* Keyboard-shortcuts reference content. Translated once
* on the server and read by `src/shortcuts.ts`, which
* renders the Keyboard shortcuts window.
*
* `contextual` is a small table that distinguishes the
* three modes the shortcuts operate in (Outside
* Workspaces, Inside Workspaces, Show Desktop).
* `general` is a flat list for shortcuts whose
* behaviour doesn't shift by mode.
*/
'shortcuts' => array(
'title' => __( 'Keyboard shortcuts', 'desktop-mode' ),
'contextual' => array(
'heading' => __( 'Workspaces', 'desktop-mode' ),
'headers' => array(
'key' => __( 'Key', 'desktop-mode' ),
'outside' => __( 'Outside Workspaces', 'desktop-mode' ),
'inside' => __( 'Inside Workspaces', 'desktop-mode' ),
'showDesktop' => __( 'In Show Desktop', 'desktop-mode' ),
),
'rows' => array(
array(
'keys' => array( '←' ),
'outside' => __( 'Previous workspace (wraps)', 'desktop-mode' ),
'inside' => __( 'Previous workspace (grid + top-bar update)', 'desktop-mode' ),
'showDesktop' => __( 'Previous workspace', 'desktop-mode' ),
),
array(
'keys' => array( '→' ),
'outside' => __( 'Next workspace (wraps)', 'desktop-mode' ),
'inside' => __( 'Next workspace (grid + top-bar update)', 'desktop-mode' ),
'showDesktop' => __( 'Next workspace', 'desktop-mode' ),
),
array(
'keys' => array( '↑' ),
'outside' => __( 'Enter Workspaces', 'desktop-mode' ),
'inside' => __( 'Exit onto the active workspace', 'desktop-mode' ),
'showDesktop' => __( 'Restore windows (exit Show Desktop)', 'desktop-mode' ),
),
array(
'keys' => array( '↓' ),
'outside' => __( 'Toggle Show Desktop', 'desktop-mode' ),
'inside' => __( 'Exit Workspaces (no minimize)', 'desktop-mode' ),
'showDesktop' => __( 'Toggle Show Desktop', 'desktop-mode' ),
),
array(
'keys' => array( 'Enter' ),
'note' => __( '(in Workspaces)', 'desktop-mode' ),
'outside' => '—',
'inside' => __( 'Commit current workspace, exit Workspaces', 'desktop-mode' ),
'showDesktop' => '—',
),
),
),
'general' => array(
'heading' => __( 'Windows & palette', 'desktop-mode' ),
'items' => array(
array(
'keys' => array( '`' ),
'description' => __( 'Cycle to the next window on the active workspace.', 'desktop-mode' ),
),
array(
'keys' => array( 'Shift', '`' ),
'description' => __( 'Cycle to the previous window on the active workspace.', 'desktop-mode' ),
),
array(
'keys' => array( '⌘/Ctrl', 'K' ),
'description' => __( 'Open the command palette / Ask AI overlay.', 'desktop-mode' ),
),
array(
'keys' => array( '⌥/Alt', '⌘/Ctrl', 'W' ),
'description' => __( 'Close every open window on the current workspace (asks first).', 'desktop-mode' ),
),
array(
'keys' => array( 'Esc' ),
'description' => __( 'Exit Workspaces (or Snap Overview) without changing window state.', 'desktop-mode' ),
),
),
),
),
)
) . ';',
'before'
);
wp_enqueue_script( 'os-admin-bar' );
}
add_action( 'admin_enqueue_scripts', 'openstation_enqueue_toggle_assets' );