add_node(
array(
'parent' => 'top-secondary',
'id' => 'desktop-mode-toggle',
'title' => ''
. '' . $label . '',
'href' => '#',
'meta' => array(
'class' => $is_active ? 'desktop-mode-active' : '',
'tabindex' => 0,
'title' => $label,
),
)
);
// Fullscreen toggle — sits next to "Switch to Classic Admin" so the
// shell can occupy the whole screen without browser chrome. Only
// makes sense in desktop mode; kept out of classic where it would
// just be a redundant browser-fullscreen shortcut.
if ( $is_active ) {
$wp_admin_bar->add_node(
array(
'parent' => 'top-secondary',
'id' => 'desktop-fullscreen',
'title' => '',
'href' => '#',
'meta' => array(
'class' => 'desktop-fullscreen-btn',
'title' => __( 'Enter fullscreen', 'desktop-mode' ),
'tabindex' => 0,
),
)
);
}
// Layout menu — only surfaced when the user is actually viewing
// the desktop shell (the actions don't make sense in classic
// admin, which has no windows to arrange). Parent renders as a
// dashicon with a hover-opened submenu; each child is routed to
// `wp.desktop.windowManager.*` by the inline JS.
//
// Sits next to Fullscreen so the two shell-state actions group
// visually. Ask AI, then the help/meta cluster (Keyboard shortcuts
// + Report a bug) follow, with the help/meta pair anchored to the
// far right of the secondary bar next to the user identity menu.
if ( $is_active ) {
$wp_admin_bar->add_node(
array(
'parent' => 'top-secondary',
'id' => 'desktop-layout-menu',
'title' => '',
'href' => '#',
'meta' => array(
'title' => __( 'Arrange windows', 'desktop-mode' ),
'tabindex' => 0,
),
)
);
$wp_admin_bar->add_node(
array(
'parent' => 'desktop-layout-menu',
'id' => 'desktop-layout-cascade',
'title' => esc_html__( 'Cascade', 'desktop-mode' ),
'href' => '#',
'meta' => array(
'class' => 'desktop-mode-layout-action',
'title' => __( 'Lay all windows out from top-left, offset so every title bar stays visible.', 'desktop-mode' ),
),
)
);
$wp_admin_bar->add_node(
array(
'parent' => 'desktop-layout-menu',
'id' => 'desktop-layout-overview',
'title' => esc_html__( 'Overview', 'desktop-mode' ),
'href' => '#',
'meta' => array(
'class' => 'desktop-mode-layout-action',
'title' => __( 'Zoom out to see every window at once. Click one to focus it.', 'desktop-mode' ),
),
)
);
// Snap-to-grid toggle. Renders as a checkbox-style entry that
// must NOT dismiss the parent menu on click — see the inline
// JS below for the stop-propagation handling. Initial check
// state is painted from the persisted preference once the
// shell has booted.
$wp_admin_bar->add_node(
array(
'parent' => 'desktop-layout-menu',
'id' => 'desktop-layout-snap',
'title' => '☐ '
. esc_html__( 'Snap to grid', 'desktop-mode' ),
'href' => '#',
'meta' => array(
'class' => 'desktop-mode-layout-snap',
'title' => __( 'Snap windows to a grid while dragging or resizing.', 'desktop-mode' ),
),
)
);
$wp_admin_bar->add_node(
array(
'parent' => 'desktop-layout-menu',
'id' => 'desktop-layout-tile',
'title' => esc_html__( 'Tile all windows', 'desktop-mode' ),
'href' => '#',
'meta' => array(
'class' => 'desktop-mode-layout-action',
'title' => __( 'Pack every window into an evenly tiled grid that fills the desktop.', 'desktop-mode' ),
),
)
);
/**
* Filter the list of custom arrange-menu items contributed by
* plugins. Each entry becomes an additional node under the
* "Arrange" submenu, rendered with the same styling as the
* built-in Cascade / Overview / Tile items. Clicking a custom
* item dispatches the JS action `desktop-mode.arrange.custom-action`
* with payload `{ id }` — plugins subscribe via
* `wp.hooks.addAction()` and run their own arrangement logic.
*
* Each item is an associative array:
*
* 'id' string Unique slug (letters, digits, dashes).
* 'title' string Menu label (already translated).
* 'description' string Optional tooltip / aria description.
* 'position' int Optional sort key; lower sorts earlier.
* Built-ins are effectively at 0-3; use
* 10+ to append.
*
* Entries with missing/invalid `id` or `title` are dropped.
*
* @since 0.6.2
*
* @param array $items Existing custom items (default empty).
*/
$custom = apply_filters( 'desktop_mode_arrange_menu_items', array() );
if ( is_array( $custom ) ) {
// Stable sort by `position` (default 10 — after built-ins),
// preserving registration order within a tie.
$sortable = array();
foreach ( $custom as $index => $item ) {
if ( ! is_array( $item ) ) {
continue;
}
$id = isset( $item['id'] ) ? sanitize_key( (string) $item['id'] ) : '';
$title = isset( $item['title'] ) ? (string) $item['title'] : '';
if ( '' === $id || '' === $title ) {
continue;
}
$sortable[] = array(
'id' => $id,
'title' => $title,
'description' => isset( $item['description'] ) ? (string) $item['description'] : '',
'position' => isset( $item['position'] ) && is_numeric( $item['position'] )
? (int) $item['position']
: 10,
'index' => $index,
);
}
usort(
$sortable,
static function ( $a, $b ) {
if ( $a['position'] === $b['position'] ) {
return $a['index'] - $b['index'];
}
return $a['position'] - $b['position'];
}
);
foreach ( $sortable as $item ) {
// The custom id is round-tripped through the DOM id
// (stripping the `desktop-layout-custom-` prefix in the
// click handler). Keeps us inside the documented
// WP_Admin_Bar::add_node meta surface — no non-standard
// attributes, no custom render callbacks.
$wp_admin_bar->add_node(
array(
'parent' => 'desktop-layout-menu',
'id' => 'desktop-layout-custom-' . $item['id'],
'title' => esc_html( $item['title'] ),
'href' => '#',
'meta' => array(
'class' => 'desktop-mode-layout-action desktop-mode-layout-custom',
'title' => $item['description'],
),
)
);
}
}
}
// AI Assistant trigger — shown when desktop mode is active AND the
// current user has AI features configured. Clicking (or pressing
// Cmd+K anywhere) opens the spotlight-style AI overlay.
if ( $is_active && function_exists( 'desktop_mode_ai_is_enabled' ) && desktop_mode_ai_is_enabled( get_current_user_id() ) ) {
// Use dashicons-admin-comments (speech bubble) — same rendering
// path as the toggle + arrange buttons, no SVG / HTML parsing
// issues in the admin-bar context. The ⌘K badge is added via
// a CSS ::after on the label so it never touches the DOM.
$wp_admin_bar->add_node(
array(
'parent' => 'top-secondary',
'id' => 'desktop-ai-assistant',
'title' => ''
. '' . esc_html__( 'Ask AI', 'desktop-mode' ) . '',
'href' => '#',
'meta' => array(
'class' => 'desktop-ai-btn',
'title' => __( 'Open AI Assistant (Cmd+K)', 'desktop-mode' ),
'tabindex' => 0,
),
)
);
}
// "Keyboard shortcuts" trigger — shown only when desktop mode is
// active. Dispatches `desktop-mode-open-help` on click; the shell
// answers by opening the Keyboard Shortcuts reference window.
if ( $is_active ) {
$wp_admin_bar->add_node(
array(
'parent' => 'top-secondary',
'id' => 'desktop-help',
'title' => '',
'href' => '#',
'meta' => array(
'class' => 'desktop-help-btn',
'title' => __( 'Keyboard shortcuts', 'desktop-mode' ),
'tabindex' => 0,
),
)
);
}
// "Report a bug" trigger — shown only when desktop mode is active.
// Clicking dispatches a `desktop-mode-open-bug-report` document
// CustomEvent that the shell listens for and answers by opening the
// Bug Report native window. PHP doesn't know the JS side exists; the
// event lets us add the button without coupling to any specific
// shell module. Anchored to the far right of the secondary bar so
// the meta/help cluster (Keyboard shortcuts + Report a bug) sits
// next to the user identity menu — matches the convention used by
// most SaaS products (Help / "?" at the user-menu corner).
if ( $is_active ) {
$wp_admin_bar->add_node(
array(
'parent' => 'top-secondary',
'id' => 'desktop-bug-report',
'title' => '',
'href' => '#',
'meta' => array(
'class' => 'desktop-bug-report-btn',
'title' => __( 'Open the Bug Report window', 'desktop-mode' ),
'tabindex' => 0,
),
)
);
}
}
add_action( 'admin_bar_menu', 'desktop_mode_admin_bar_toggle', 190 );
/**
* Enqueues the inline CSS and JS for the desktop mode toggle.
*
* Uses `admin-bar` as the carrier handle so the inline assets always ship
* with the admin bar itself — no matter which admin screen is showing.
*
* @since 0.1.0
*/
function desktop_mode_enqueue_toggle_assets() {
if ( ! is_admin() || ! is_user_logged_in() ) {
return;
}
$css = '
#wpadminbar #wp-admin-bar-desktop-mode-toggle > .ab-item,
#wpadminbar #wp-admin-bar-desktop-layout-menu > .ab-item,
#wpadminbar #wp-admin-bar-desktop-ai-assistant > .ab-item,
#wpadminbar #wp-admin-bar-desktop-fullscreen > .ab-item,
#wpadminbar #wp-admin-bar-desktop-bug-report > .ab-item,
#wpadminbar #wp-admin-bar-desktop-help > .ab-item {
display: inline-flex;
align-items: center;
gap: 6px;
}
#wpadminbar #wp-admin-bar-desktop-mode-toggle .ab-icon,
#wpadminbar #wp-admin-bar-desktop-layout-menu .ab-icon,
#wpadminbar #wp-admin-bar-desktop-ai-assistant .ab-icon,
#wpadminbar #wp-admin-bar-desktop-fullscreen .ab-icon,
#wpadminbar #wp-admin-bar-desktop-bug-report .ab-icon,
#wpadminbar #wp-admin-bar-desktop-help .ab-icon {
float: none;
margin: 0;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
}
#wp-admin-bar-desktop-mode-toggle .ab-icon.dashicons,
#wp-admin-bar-desktop-layout-menu .ab-icon.dashicons {
font: normal 20px/1 dashicons;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#wp-admin-bar-desktop-mode-toggle .ab-icon.dashicons::before {
content: "\f472";
top: 0;
position: static;
}
#wp-admin-bar-desktop-layout-menu .ab-icon.dashicons::before {
/* dashicons-grid-view */
content: "\f509";
top: 0;
position: static;
}
#wp-admin-bar-desktop-mode-toggle.desktop-mode-active .ab-icon.dashicons::before {
/* Inherit the admin-bar text color so the active state
matches the +New, Home, Comments dashicons. Previously
hardcoded #72aee6, which forced blue regardless of the
profile color scheme. */
color: inherit;
}
@media screen and (max-width: 782px) {
#wp-admin-bar-desktop-mode-toggle .ab-label {
display: none;
}
}
/* AI Assistant admin-bar button — same icon/label pattern as the
desktop-mode-toggle; ⌘K badge added via CSS ::after so we keep
the title HTML clean and avoid admin-bar sanitisation edge-cases. */
#wp-admin-bar-desktop-ai-assistant .ab-icon.dashicons,
#wp-admin-bar-desktop-ai-assistant .ab-icon.dashicons {
font: normal 20px/1 dashicons;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#wp-admin-bar-desktop-ai-assistant .ab-icon.dashicons::before {
content: "\f101";
top: 0;
position: static;
/* See note on desktop-mode-toggle above — inherit so the
icon matches the rest of the admin-bar dashicons
across all WP profile color schemes. */
color: inherit;
}
/* ⌘K badge rendered purely in CSS to the right of the label.
inline-flex + align-items:center centers the glyph inside
its own padding box; a 1px upward translate compensates
for the optical sag from the admin-bar label baseline. */
#wpadminbar #wp-admin-bar-desktop-ai-assistant .ab-label::after {
content: "\2318K";
display: inline-flex;
align-items: center;
justify-content: center;
margin-inline-start: 5px;
font-size: 10px;
line-height: 1;
padding: 2px 5px;
background: rgba( 255, 255, 255, 0.1 );
border: 1px solid rgba( 255, 255, 255, 0.18 );
border-radius: 3px;
color: rgba( 255, 255, 255, 0.55 );
vertical-align: middle;
font-weight: 400;
letter-spacing: 0;
position: relative;
top: -1px;
}
@media screen and (max-width: 782px) {
#wp-admin-bar-desktop-ai-assistant .ab-label {
display: none;
}
}
/* Fullscreen admin-bar button — same dashicons rendering pattern
as the toggle. Icon swaps between fullscreen-alt (enter) and
fullscreen-exit-alt (exit) via the .is-fullscreen class set by
admin-bar.js on `fullscreenchange`. */
#wp-admin-bar-desktop-fullscreen .ab-icon.dashicons {
font: normal 20px/1 dashicons;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#wp-admin-bar-desktop-fullscreen .ab-icon.dashicons::before {
/* dashicons-fullscreen-alt */
content: "\f211";
top: 0;
position: static;
}
#wp-admin-bar-desktop-fullscreen.is-fullscreen .ab-icon.dashicons::before {
/* dashicons-fullscreen-exit-alt */
content: "\f212";
}
/* Defuse the implicit-drag-on-anchor click eater. WP renders
admin-bar items as , which is implicitly
draggable; tiny pointer jitter on click commits to a native
link-drag (the floating "Exit fullscreen / http://…#" ghost
the user sees) and the click handler never runs. admin-bar.js
also sets draggable="false" on the ; user-drag: none here
covers the WebKit path where the attribute alone is not
honoured reliably. */
#wp-admin-bar-desktop-fullscreen .ab-item,
#wp-admin-bar-desktop-fullscreen .ab-item * {
-webkit-user-drag: none;
user-drag: none;
-webkit-user-select: none;
user-select: none;
}
/* Hide admin-bar items that sit closest to the right corner
while in browser fullscreen. The corner is a reveal hot zone
the OS / browser reserve for menu-bar / window-controls,
where pointer events get intercepted before reaching the
page. Hiding these two shifts the Fullscreen toggle further
from the corner so its own click lands. Toggled by a body
class admin-bar.js sets on `fullscreenchange`. */
body.desktop-mode-browser-fs #wp-admin-bar-desktop-mode-toggle,
body.desktop-mode-browser-fs #wp-admin-bar-desktop-bug-report {
display: none;
}
/* Bug Report admin-bar button — same dashicons rendering pattern
as the toggle. dashicons-buddicons-replies is a speech bubble
with stylized lines that reads as a comment / report glyph at
admin-bar size, while dashicons-bug is too literal and small. */
#wp-admin-bar-desktop-bug-report .ab-icon.dashicons {
font: normal 20px/1 dashicons;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#wp-admin-bar-desktop-bug-report .ab-icon.dashicons::before {
/* dashicons-buddicons-replies — same glyph as the dock tile so
the two surfaces (admin-bar + dock) match. */
content: "\f451";
top: 0;
position: static;
}
/* Keyboard shortcuts admin-bar button. Dashicons has no keyboard
glyph, so we paint a small inline SVG via CSS `mask` with
`background-color: currentColor` — that way the icon inherits
the admin-bar text color across every WP profile scheme
without us hardcoding a fill. */
#wp-admin-bar-desktop-help .ab-icon.desktop-mode-keyboard-icon {
background-color: currentColor;
-webkit-mask: url( "data:image/svg+xml;utf8," ) no-repeat center / 20px 20px;
mask: url( "data:image/svg+xml;utf8," ) no-repeat center / 20px 20px;
}
/* Keyboard-shortcuts floating popover — anchored under the
keyboard button. Styled to match the Arrange submenu (light
bg on the otherwise-dark admin bar, soft shadow, rounded
corners). Built dynamically in admin-bar.js from translated
content shipped via `desktopModeAdminBar.shortcuts`. */
#wpadminbar #wp-admin-bar-desktop-help {
position: relative;
}
#wpadminbar .desktop-mode-shortcuts-popover {
display: none;
position: absolute;
top: 100%;
right: 0;
margin-top: 0;
padding: 12px 14px;
min-width: 460px;
max-width: min( 90vw, 720px );
background: var( --desktop-mode-window-bg, #fff );
color: var( --desktop-mode-text, #1d2327 );
border: 1px solid var( --desktop-mode-window-border, #c3c4c7 );
border-radius: 8px;
box-shadow: 0 8px 24px rgba( 0, 0, 0, 0.18 ),
0 2px 6px rgba( 0, 0, 0, 0.08 );
z-index: 99999;
font-size: 12px;
line-height: 1.4;
text-align: left;
}
#wpadminbar .desktop-mode-shortcuts-popover.is-open {
display: block;
}
#wpadminbar .desktop-mode-shortcuts-popover__section + .desktop-mode-shortcuts-popover__section {
margin-top: 12px;
}
#wpadminbar .desktop-mode-shortcuts-popover__heading {
margin: 0 0 6px;
padding: 0;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var( --desktop-mode-muted-fg, #50575e );
line-height: 1.2;
}
#wpadminbar .desktop-mode-shortcuts-popover__table {
width: 100%;
border-collapse: collapse;
color: inherit;
}
#wpadminbar .desktop-mode-shortcuts-popover__table th,
#wpadminbar .desktop-mode-shortcuts-popover__table td {
padding: 6px 8px;
vertical-align: middle;
text-align: left;
border-bottom: 1px solid rgba( 0, 0, 0, 0.06 );
color: inherit;
font-weight: 400;
font-size: 12px;
line-height: 1.4;
}
#wpadminbar .desktop-mode-shortcuts-popover__table th {
font-weight: 600;
font-size: 11px;
color: var( --desktop-mode-muted-fg, #50575e );
letter-spacing: 0.02em;
}
#wpadminbar .desktop-mode-shortcuts-popover__table tbody tr:last-child td {
border-bottom: none;
}
#wpadminbar .desktop-mode-shortcuts-popover__key-cell {
white-space: nowrap;
}
#wpadminbar .desktop-mode-shortcuts-popover__note {
color: var( --desktop-mode-muted-fg, #50575e );
font-size: 11px;
}
#wpadminbar .desktop-mode-shortcuts-popover__list {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 4px;
}
#wpadminbar .desktop-mode-shortcuts-popover__item {
display: grid;
grid-template-columns: 120px 1fr;
align-items: center;
gap: 12px;
padding: 4px 8px;
border-radius: 4px;
background: rgba( 0, 0, 0, 0.03 );
}
#wpadminbar .desktop-mode-shortcuts-popover__keys {
display: inline-flex;
align-items: center;
gap: 4px;
flex-wrap: wrap;
}
#wpadminbar .desktop-mode-shortcuts-popover__kbd {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 22px;
height: 20px;
padding: 0 5px;
border: 1px solid rgba( 0, 0, 0, 0.18 );
border-bottom-width: 2px;
border-radius: 4px;
background: #fff;
color: var( --desktop-mode-text, #1d2327 );
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
font-size: 11px;
font-weight: 600;
line-height: 1;
white-space: nowrap;
}
#wpadminbar .desktop-mode-shortcuts-popover__plus {
font-size: 10px;
color: var( --desktop-mode-muted-fg, #50575e );
}
#wpadminbar .desktop-mode-shortcuts-popover__description {
color: inherit;
}
/* Dock-style custom tooltip for icon-only admin-bar buttons.
admin-bar.js moves the native `title` to `data-desktop-tooltip`
so the OS delayed tooltip never competes with this one. Floats
below the button, dark pill with white text — visually
identical to the dock tooltip pattern. */
#wpadminbar .ab-item[ data-desktop-tooltip ] {
position: relative;
}
#wpadminbar .ab-item[ data-desktop-tooltip ]::after {
content: attr( data-desktop-tooltip );
position: absolute;
top: 100%;
left: 50%;
margin-top: 4px;
transform: translateX( -50% ) translateY( 4px );
padding: 6px 12px;
background: rgba( 0, 0, 0, 0.85 );
color: #fff;
font-size: 12px;
font-weight: 400;
line-height: 1.4;
white-space: nowrap;
border-radius: 6px;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s ease, transform 0.15s ease;
z-index: 100000;
}
#wpadminbar .ab-item[ data-desktop-tooltip ]:hover::after,
#wpadminbar .ab-item[ data-desktop-tooltip ]:focus-visible::after {
opacity: 1;
transform: translateX( -50% ) translateY( 0 );
}
/* When the keyboard-shortcuts popover is open, suppress the
tooltip on its own anchor so the two never stack on top of
each other. */
#wpadminbar #wp-admin-bar-desktop-help.is-open .ab-item[ data-desktop-tooltip ]::after {
opacity: 0;
}
/* Arrange submenu — aligned visually with the component
(src/ui/components/wpd-menu/wpd-menu.styles.ts). The submenu
flips from the native dark-on-dark admin bar to a light theme
(white bg, dark text) so it matches the rest of our UI AND so
the hover state stays legible (a light tint on a dark bg
would render as invisible overlap with native admin-bar hover
colors). Selectors prefixed with #wpadminbar to win the
admin-bar specificity without !important. */
#wpadminbar #wp-admin-bar-desktop-layout-menu .ab-sub-wrapper {
background: var( --desktop-mode-window-bg, #fff );
padding: 4px;
min-width: 220px;
border: 1px solid var( --desktop-mode-window-border, #c3c4c7 );
border-radius: 8px;
box-shadow: 0 8px 24px rgba( 0, 0, 0, 0.18 ),
0 2px 6px rgba( 0, 0, 0, 0.08 );
}
#wpadminbar #wp-admin-bar-desktop-layout-menu .ab-sub-wrapper .ab-submenu {
padding: 0;
background: transparent;
}
#wpadminbar #wp-admin-bar-desktop-layout-menu .ab-sub-wrapper .ab-submenu > li {
background: transparent;
}
#wpadminbar #wp-admin-bar-desktop-layout-menu .ab-sub-wrapper .ab-submenu > li > .ab-item {
display: flex;
align-items: center;
gap: 10px;
height: auto;
min-height: 32px;
padding: 6px 10px;
font-size: 13px;
line-height: 1.3;
color: var( --desktop-mode-text, #1d2327 );
background: transparent;
border-radius: 6px;
transition: background-color 0.12s ease, color 0.12s ease;
}
/* Hover + keyboard focus share the same tinted background. Text
stays dark so it stays legible on the light bg. `focus` wins
here instead of the native `.ab-item:focus { color: #72aee6 }`
thanks to the extra id in our selector chain. */
#wpadminbar #wp-admin-bar-desktop-layout-menu .ab-sub-wrapper .ab-submenu > li > .ab-item:hover,
#wpadminbar #wp-admin-bar-desktop-layout-menu .ab-sub-wrapper .ab-submenu > li > .ab-item:focus,
#wpadminbar.nojq #wp-admin-bar-desktop-layout-menu .ab-sub-wrapper .ab-submenu > li:hover > .ab-item {
background: rgba( 0, 0, 0, 0.06 );
color: #000;
}
#wp-admin-bar-desktop-layout-snap .desktop-mode-layout-checkbox {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
font-size: 14px;
line-height: 1;
}
#wp-admin-bar-desktop-layout-snap[aria-checked="true"] .desktop-mode-layout-checkbox {
color: var( --wp-admin-theme-color, #2271b1 );
}
';
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.
// `active` must match the visual state shown on the toggle above —
// i.e. "currently viewing desktop mode." Using `desktop_mode_is_enabled()`
// alone would misclassify a classic-override request (meta = '1',
// URL carrying `desktop_mode_classic=1`) as active, causing the
// click handler to send `enabled=0` when the user actually wants
// to return to the shell.
wp_register_script(
'desktop-mode-admin-bar',
DESKTOP_MODE_URL . 'assets/js/admin-bar.js',
array( 'admin-bar' ),
DESKTOP_MODE_VERSION,
true
);
// Emit the config as a JSON literal via wp_add_inline_script (not
// wp_localize_script — the latter casts booleans to '' / '1', which
// breaks the click handler's `!! cfg.active` check). 'before' runs
// before admin-bar.js so the global is ready when the IIFE fires.
wp_add_inline_script(
'desktop-mode-admin-bar',
'var desktopModeAdminBar = ' . wp_json_encode(
array(
'nonce' => wp_create_nonce( 'save-desktop-mode' ),
'active' => desktop_mode_is_enabled() && ! desktop_mode_is_classic_request(),
'classicUrl' => esc_url_raw( admin_url() ),
'portalUrl' => esc_url_raw( desktop_mode_portal_url() ),
'ajaxUrl' => esc_url_raw( admin_url( 'admin-ajax.php' ) ),
'i18n' => array(
'enterFullscreen' => __( 'Fullscreen', 'desktop-mode' ),
'exitFullscreen' => __( 'Exit fullscreen', 'desktop-mode' ),
'enterTitle' => __( 'Enter fullscreen', 'desktop-mode' ),
'exitTitle' => __( 'Exit fullscreen', 'desktop-mode' ),
),
/*
* Keyboard-shortcuts popover content. Translated once on
* the server and shipped to admin-bar.js, which renders
* the popover anchored under the keyboard button.
*
* `contextual` is a small table that distinguishes the
* three modes the shortcuts operate in (Outside Overview,
* Inside Overview, 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' => __( 'Desktops & Overview', 'desktop-mode' ),
'headers' => array(
'key' => __( 'Key', 'desktop-mode' ),
'outside' => __( 'Outside overview', 'desktop-mode' ),
'inside' => __( 'Inside overview', 'desktop-mode' ),
'showDesktop' => __( 'In Show Desktop', 'desktop-mode' ),
),
'rows' => array(
array(
'keys' => array( '←' ),
'outside' => __( 'Previous desktop (wraps)', 'desktop-mode' ),
'inside' => __( 'Previous desktop (grid + top-bar update)', 'desktop-mode' ),
'showDesktop' => __( 'Previous desktop', 'desktop-mode' ),
),
array(
'keys' => array( '→' ),
'outside' => __( 'Next desktop (wraps)', 'desktop-mode' ),
'inside' => __( 'Next desktop (grid + top-bar update)', 'desktop-mode' ),
'showDesktop' => __( 'Next desktop', 'desktop-mode' ),
),
array(
'keys' => array( '↑' ),
'outside' => __( 'Enter Overview', 'desktop-mode' ),
'inside' => __( 'Exit Overview onto active desktop', 'desktop-mode' ),
'showDesktop' => __( 'Restore windows (exit Show Desktop)', 'desktop-mode' ),
),
array(
'keys' => array( '↓' ),
'outside' => __( 'Toggle Show Desktop', 'desktop-mode' ),
'inside' => __( 'Exit Overview (no minimize)', 'desktop-mode' ),
'showDesktop' => __( 'Toggle Show Desktop', 'desktop-mode' ),
),
array(
'keys' => array( 'Enter' ),
'note' => __( '(in overview)', 'desktop-mode' ),
'outside' => '—',
'inside' => __( 'Commit current desktop, exit overview', 'desktop-mode' ),
'showDesktop' => '—',
),
),
),
'general' => array(
'heading' => __( 'Windows & palette', 'desktop-mode' ),
'items' => array(
array(
'keys' => array( '`' ),
'description' => __( 'Cycle to the next window on the active desktop.', 'desktop-mode' ),
),
array(
'keys' => array( 'Shift', '`' ),
'description' => __( 'Cycle to the previous window on the active desktop.', 'desktop-mode' ),
),
array(
'keys' => array( '⌘/Ctrl', 'K' ),
'description' => __( 'Open the command palette / Ask AI overlay.', 'desktop-mode' ),
),
array(
'keys' => array( 'Esc' ),
'description' => __( 'Exit Overview (or Snap Overview) without changing window state.', 'desktop-mode' ),
),
),
),
),
)
) . ';',
'before'
);
wp_enqueue_script( 'desktop-mode-admin-bar' );
}
add_action( 'admin_enqueue_scripts', 'desktop_mode_enqueue_toggle_assets' );