| @@ -1,19 +1,20 @@ | ||
| 1 | 1 | ( function() { |
| 2 | - var toggle = document.getElementById( 'wp-admin-bar-desktop-mode-toggle' ); | |
| 2 | + // The "Switch to OpenStation" node only renders in classic admin — | |
| 3 | + // PHP skips it once the user is viewing the shell, where the | |
| 4 | + // "Exit OpenStation" dock tile is the way back out. So this handler | |
| 5 | + // only ever enables. | |
| 6 | + var toggle = document.getElementById( 'wp-admin-bar-os-toggle' ); | |
| 3 | 7 | if ( ! toggle ) { |
| 4 | 8 | return; |
| 5 | 9 | } |
| 6 | - var cfg = window.desktopModeAdminBar || {}; | |
| 10 | + var cfg = window.openStationAdminBar || {}; | |
| 7 | 11 | toggle.addEventListener( 'click', function( e ) { |
| 8 | 12 | e.preventDefault(); |
| 9 | - var isActive = !! cfg.active; | |
| 10 | - var newValue = isActive ? '' : '1'; | |
| 11 | - // Fallback targets if the server response is missing a `redirect` | |
| 13 | + // Fallback target if the server response is missing a `redirect` | |
| 12 | 14 | // field (shouldn't happen, but keep the click functional either |
| 13 | - // way). Disabling -> classic admin (NOT the portal, which would | |
| 14 | - // auto-re-enable); enabling -> portal URL so the shell takes over. | |
| 15 | - var fallback = isActive ? cfg.classicUrl : cfg.portalUrl; | |
| 15 | + // way): the portal URL, so the shell takes over. | |
| 16 | + var fallback = cfg.portalUrl; | |
| 16 | 17 | // The toggle lives in an admin bar that may be rendered either in |
| 17 | 18 | // the top window (classic) or — today it's suppressed in iframes, |
| 18 | 19 | // but a plugin could surface it — inside a chromeless iframe. In |
| 19 | 20 | // either case we want the ENTIRE browser tab to navigate, so we |
| @@ -26,11 +27,16 @@ | ||
| 26 | 27 | window.location.href = url; |
| 27 | 28 | } |
| 28 | 29 | } |
| 29 | 30 | var body = new URLSearchParams(); |
| 30 | - body.set( 'action', 'save-desktop-mode' ); | |
| 31 | + body.set( 'action', 'save-openstation' ); | |
| 31 | 32 | body.set( 'nonce', cfg.nonce ); |
| 32 | - body.set( 'enabled', newValue ); | |
| 33 | + body.set( 'enabled', '1' ); | |
| 34 | + // Tells the handler which admin this click came from; see the | |
| 35 | + // note in `includes/ajax.php`. | |
| 36 | + if ( cfg.network ) { | |
| 37 | + body.set( 'network', '1' ); | |
| 38 | + } | |
| 33 | 39 | var xhr = new XMLHttpRequest(); |
| 34 | 40 | xhr.open( 'POST', cfg.ajaxUrl, true ); |
| 35 | 41 | xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' ); |
| 36 | 42 | xhr.onload = function() { |
| @@ -47,415 +53,5 @@ | ||
| 47 | 53 | navigate( target ); |
| 48 | 54 | }; |
| 49 | 55 | xhr.send( body.toString() ); |
| 50 | 56 | } ); |
| 51 | - | |
| 52 | - // Layout menu — each child item calls a WindowManager method on | |
| 53 | - // the public shell API. We bind one delegated click listener on | |
| 54 | - // the parent submenu so adding more layouts in the future | |
| 55 | - // (split, full-width, etc.) is a matter of adding nodes in PHP, | |
| 56 | - // not new JS. `href=#` is set server-side; we preventDefault + | |
| 57 | - // intercept. | |
| 58 | - // | |
| 59 | - // The snap-to-grid checkbox is special: clicking it toggles the | |
| 60 | - // preference AND repaints the box without dismissing the menu | |
| 61 | - // (default WP behaviour would close the submenu on any click, | |
| 62 | - // breaking the "set it and forget it" feel of a checkbox). | |
| 63 | - var layoutMenu = document.getElementById( 'wp-admin-bar-desktop-layout-menu' ); | |
| 64 | - if ( ! layoutMenu ) return; | |
| 65 | - | |
| 66 | - function paintSnapCheckbox( enabled ) { | |
| 67 | - var node = document.querySelector( | |
| 68 | - '#wp-admin-bar-desktop-layout-snap .desktop-mode-layout-checkbox' | |
| 69 | - ); | |
| 70 | - if ( ! node ) return; | |
| 71 | - node.textContent = enabled ? '☑' : '☐'; // ☑ / ☐ | |
| 72 | - var item = document.getElementById( 'wp-admin-bar-desktop-layout-snap' ); | |
| 73 | - if ( item ) { | |
| 74 | - item.setAttribute( 'aria-checked', enabled ? 'true' : 'false' ); | |
| 75 | - item.setAttribute( 'role', 'menuitemcheckbox' ); | |
| 76 | - } | |
| 77 | - } | |
| 78 | - | |
| 79 | - function getManager() { | |
| 80 | - return window.wp && window.wp.desktop && window.wp.desktop.windowManager; | |
| 81 | - } | |
| 82 | - | |
| 83 | - // Initial paint — wait for the shell to publish the manager, | |
| 84 | - // then mirror the persisted snap preference. Polled rather than | |
| 85 | - // hooked because the inline script ships with the admin bar | |
| 86 | - // (loads early) and the shell's WindowManager arrives later. | |
| 87 | - function initFromManager() { | |
| 88 | - var wm = getManager(); | |
| 89 | - if ( ! wm || typeof wm.isSnapEnabled !== 'function' ) { | |
| 90 | - window.setTimeout( initFromManager, 60 ); | |
| 91 | - return; | |
| 92 | - } | |
| 93 | - paintSnapCheckbox( wm.isSnapEnabled() ); | |
| 94 | - } | |
| 95 | - initFromManager(); | |
| 96 | - | |
| 97 | - layoutMenu.addEventListener( 'click', function( e ) { | |
| 98 | - var t = e.target; | |
| 99 | - if ( ! t || ! t.closest ) return; | |
| 100 | - | |
| 101 | - var snapItem = t.closest( '.desktop-mode-layout-snap' ); | |
| 102 | - if ( snapItem ) { | |
| 103 | - // Stop propagation so WP's own "click closes submenu" | |
| 104 | - // chain never fires. preventDefault keeps the `#` href | |
| 105 | - // from scrolling the page to top. | |
| 106 | - e.preventDefault(); | |
| 107 | - e.stopPropagation(); | |
| 108 | - var wm = getManager(); | |
| 109 | - if ( ! wm || typeof wm.setSnapEnabled !== 'function' ) return; | |
| 110 | - var next = ! wm.isSnapEnabled(); | |
| 111 | - wm.setSnapEnabled( next ); | |
| 112 | - paintSnapCheckbox( next ); | |
| 113 | - return; | |
| 114 | - } | |
| 115 | - | |
| 116 | - var actionLink = t.closest( '.desktop-mode-layout-action > .ab-item, .desktop-mode-layout-action' ); | |
| 117 | - if ( ! actionLink ) return; | |
| 118 | - e.preventDefault(); | |
| 119 | - var id = actionLink.closest( '[id^="wp-admin-bar-desktop-layout-"]' ); | |
| 120 | - if ( ! id ) return; | |
| 121 | - var manager = getManager(); | |
| 122 | - if ( ! manager ) return; | |
| 123 | - if ( id.id === 'wp-admin-bar-desktop-layout-cascade' && typeof manager.cascade === 'function' ) { | |
| 124 | - manager.cascade(); | |
| 125 | - } else if ( id.id === 'wp-admin-bar-desktop-layout-overview' && typeof manager.enterOverview === 'function' ) { | |
| 126 | - manager.enterOverview(); | |
| 127 | - } else if ( id.id === 'wp-admin-bar-desktop-layout-tile' && typeof manager.tile === 'function' ) { | |
| 128 | - manager.tile(); | |
| 129 | - } else if ( id.id.indexOf( 'wp-admin-bar-desktop-layout-custom-' ) === 0 ) { | |
| 130 | - // Plugin-registered custom item. Strip the shared prefix to | |
| 131 | - // recover the `id` the plugin supplied via the PHP filter, | |
| 132 | - // then dispatch the public JS action. Plugins subscribe | |
| 133 | - // via wp.hooks.addAction( 'desktop-mode.arrange.custom-action', ... ). | |
| 134 | - var customId = id.id.replace( 'wp-admin-bar-desktop-layout-custom-', '' ); | |
| 135 | - var hooks = window.wp && window.wp.hooks; | |
| 136 | - if ( hooks && typeof hooks.doAction === 'function' ) { | |
| 137 | - hooks.doAction( 'desktop-mode.arrange.custom-action', { id: customId } ); | |
| 138 | - } | |
| 139 | - } | |
| 140 | - // After running an action, dismiss the submenu so the user | |
| 141 | - // lands in the newly arranged desktop instead of the menu | |
| 142 | - // hanging open on top. WP's admin bar toggles visibility via | |
| 143 | - // a `.hover` class on the parent `li.menupop` — we remove it | |
| 144 | - // AND blur the active element so a re-hover is required for | |
| 145 | - // the next open. The snap checkbox stays open by design | |
| 146 | - // (it's handled by the earlier branch and never reaches | |
| 147 | - // this close path). | |
| 148 | - layoutMenu.classList.remove( 'hover' ); | |
| 149 | - if ( document.activeElement && typeof document.activeElement.blur === 'function' ) { | |
| 150 | - document.activeElement.blur(); | |
| 151 | - } | |
| 152 | - } ); | |
| 153 | - | |
| 154 | - // AI Assistant button — dispatches the `desktop-mode-open-ai` event that | |
| 155 | - // the AiAssistant class listens for. Using an event instead of a direct | |
| 156 | - // call decouples the admin-bar inline script (which runs early, before | |
| 157 | - // the desktop shell has initialised) from the AiAssistant instance. | |
| 158 | - var aiBtn = document.getElementById( 'wp-admin-bar-desktop-ai-assistant' ); | |
| 159 | - if ( aiBtn ) { | |
| 160 | - aiBtn.addEventListener( 'click', function( e ) { | |
| 161 | - e.preventDefault(); | |
| 162 | - document.dispatchEvent( new CustomEvent( 'desktop-mode-open-ai' ) ); | |
| 163 | - } ); | |
| 164 | - } | |
| 165 | - | |
| 166 | - // Fullscreen button — toggles browser fullscreen on the document | |
| 167 | - // element so the shell occupies the whole screen with no browser | |
| 168 | - // chrome. The click counts as a user gesture, which is required by | |
| 169 | - // the Fullscreen API. We listen for `fullscreenchange` instead of | |
| 170 | - // flipping state inside the click handler because the user can also | |
| 171 | - // exit via Esc or the OS, and we want the icon/label to stay in sync | |
| 172 | - // in those cases too. | |
| 173 | - var fsBtn = document.getElementById( 'wp-admin-bar-desktop-fullscreen' ); | |
| 174 | - if ( fsBtn ) { | |
| 175 | - var fsLink = fsBtn.querySelector( '.ab-item' ); | |
| 176 | - var fsLabel = fsBtn.querySelector( '.ab-label' ); | |
| 177 | - var fsI18n = ( cfg && cfg.i18n ) || {}; | |
| 178 | - // WP renders admin-bar items as `<a href="#">`, which HTML5 | |
| 179 | - // marks implicitly draggable. In browser fullscreen the button | |
| 180 | - // sits at top:0 where cursor reveals (menu bar, exit | |
| 181 | - // affordance) produce enough pointer jitter between mousedown | |
| 182 | - // and mouseup that the browser commits to a native link-drag | |
| 183 | - // instead of a click — the handler never runs and the user | |
| 184 | - // sees a ghost flicker. Opt the element out of native drag so | |
| 185 | - // the click always lands. | |
| 186 | - if ( fsLink ) { | |
| 187 | - fsLink.setAttribute( 'draggable', 'false' ); | |
| 188 | - } | |
| 189 | - function paintFullscreen( on ) { | |
| 190 | - // Body class drives the CSS that hides admin-bar items | |
| 191 | - // which sit too close to the screen-corner reveal hot zones | |
| 192 | - // in browser fullscreen — the OS / browser intercept clicks | |
| 193 | - // in those bands. Hiding them shifts the Fullscreen button | |
| 194 | - // further from the right edge so its own click lands. | |
| 195 | - document.body.classList.toggle( 'desktop-mode-browser-fs', !! on ); | |
| 196 | - if ( on ) { | |
| 197 | - fsBtn.classList.add( 'is-fullscreen' ); | |
| 198 | - if ( fsLabel ) fsLabel.textContent = fsI18n.exitFullscreen || 'Exit fullscreen'; | |
| 199 | - if ( fsLink ) fsLink.setAttribute( 'title', fsI18n.exitTitle || 'Exit fullscreen' ); | |
| 200 | - } else { | |
| 201 | - fsBtn.classList.remove( 'is-fullscreen' ); | |
| 202 | - if ( fsLabel ) fsLabel.textContent = fsI18n.enterFullscreen || 'Fullscreen'; | |
| 203 | - if ( fsLink ) fsLink.setAttribute( 'title', fsI18n.enterTitle || 'Enter fullscreen' ); | |
| 204 | - } | |
| 205 | - } | |
| 206 | - fsBtn.addEventListener( 'click', function( e ) { | |
| 207 | - e.preventDefault(); | |
| 208 | - // `window.top` so the whole tab goes fullscreen even when the | |
| 209 | - // admin bar happens to be rendered inside an iframe (today | |
| 210 | - // it's suppressed there, but a plugin could surface it). Fall | |
| 211 | - // back to the current document if cross-origin blocks access. | |
| 212 | - var doc; | |
| 213 | - try { | |
| 214 | - doc = window.top.document; | |
| 215 | - } catch ( err ) { | |
| 216 | - doc = document; | |
| 217 | - } | |
| 218 | - if ( doc.fullscreenElement ) { | |
| 219 | - if ( doc.exitFullscreen ) { | |
| 220 | - doc.exitFullscreen(); | |
| 221 | - } | |
| 222 | - } else if ( doc.documentElement && doc.documentElement.requestFullscreen ) { | |
| 223 | - doc.documentElement.requestFullscreen(); | |
| 224 | - } | |
| 225 | - } ); | |
| 226 | - // Listen on whichever document we're driving so OS/Esc exits | |
| 227 | - // repaint the button too. Bound on both to cover the iframe edge | |
| 228 | - // case without extra branching. | |
| 229 | - document.addEventListener( 'fullscreenchange', function() { | |
| 230 | - paintFullscreen( !! document.fullscreenElement ); | |
| 231 | - } ); | |
| 232 | - try { | |
| 233 | - if ( window.top.document !== document ) { | |
| 234 | - window.top.document.addEventListener( 'fullscreenchange', function() { | |
| 235 | - paintFullscreen( !! window.top.document.fullscreenElement ); | |
| 236 | - } ); | |
| 237 | - } | |
| 238 | - } catch ( err ) {} | |
| 239 | - } | |
| 240 | - | |
| 241 | - // Bug Report button — same decoupling pattern as Ask AI. Dispatches | |
| 242 | - // `desktop-mode-open-bug-report`; the shell answers by opening the | |
| 243 | - // Bug Report native window. Wired in `src/desktop.ts`. | |
| 244 | - var bugBtn = document.getElementById( 'wp-admin-bar-desktop-bug-report' ); | |
| 245 | - if ( bugBtn ) { | |
| 246 | - bugBtn.addEventListener( 'click', function( e ) { | |
| 247 | - e.preventDefault(); | |
| 248 | - document.dispatchEvent( new CustomEvent( 'desktop-mode-open-bug-report' ) ); | |
| 249 | - } ); | |
| 250 | - } | |
| 251 | - | |
| 252 | - // Keyboard Shortcuts button — toggles a floating popover anchored | |
| 253 | - // under the button, styled like the Arrange submenu. Content is | |
| 254 | - // passed in via `cfg.shortcuts` (translated server-side in PHP). | |
| 255 | - wireShortcutsPopover( document.getElementById( 'wp-admin-bar-desktop-help' ), cfg.shortcuts ); | |
| 256 | - | |
| 257 | - // Custom tooltips for the icon-only admin-bar buttons. The native | |
| 258 | - // `title` attribute renders a slow, OS-styled tooltip that conflicts | |
| 259 | - // with our dock-style custom tooltip; we swap each `title` to | |
| 260 | - // `data-desktop-tooltip` + `aria-label` so screen readers still see | |
| 261 | - // the label but the visual tooltip is fully ours. | |
| 262 | - wireTooltipsFor( [ | |
| 263 | - 'wp-admin-bar-desktop-fullscreen', | |
| 264 | - 'wp-admin-bar-desktop-help', | |
| 265 | - 'wp-admin-bar-desktop-bug-report', | |
| 266 | - 'wp-admin-bar-desktop-layout-menu', | |
| 267 | - ] ); | |
| 268 | - | |
| 269 | - /** | |
| 270 | - * Re-anchors a native `title` attribute to a `data-desktop-tooltip` | |
| 271 | - * data attribute on the same node, plus mirrors it to `aria-label` | |
| 272 | - * so assistive tech keeps the description. Pure-CSS tooltip then | |
| 273 | - * renders from the data attribute via the `[data-desktop-tooltip] | |
| 274 | - * .ab-item::after` rule in admin-bar.php. | |
| 275 | - */ | |
| 276 | - function wireTooltipsFor( ids ) { | |
| 277 | - for ( var i = 0; i < ids.length; i++ ) { | |
| 278 | - var node = document.getElementById( ids[ i ] ); | |
| 279 | - if ( ! node ) continue; | |
| 280 | - var link = node.querySelector( '.ab-item' ); | |
| 281 | - if ( ! link ) continue; | |
| 282 | - var label = link.getAttribute( 'title' ); | |
| 283 | - if ( ! label ) continue; | |
| 284 | - link.removeAttribute( 'title' ); | |
| 285 | - link.setAttribute( 'data-desktop-tooltip', label ); | |
| 286 | - if ( ! link.hasAttribute( 'aria-label' ) ) { | |
| 287 | - link.setAttribute( 'aria-label', label ); | |
| 288 | - } | |
| 289 | - } | |
| 290 | - } | |
| 291 | - | |
| 292 | - function wireShortcutsPopover( btn, data ) { | |
| 293 | - if ( ! btn || ! data ) { | |
| 294 | - return; | |
| 295 | - } | |
| 296 | - var anchor = btn.querySelector( '.ab-item' ) || btn; | |
| 297 | - var popover = buildShortcutsPopover( data ); | |
| 298 | - btn.appendChild( popover ); | |
| 299 | - | |
| 300 | - var isOpen = false; | |
| 301 | - function open() { | |
| 302 | - if ( isOpen ) return; | |
| 303 | - isOpen = true; | |
| 304 | - popover.classList.add( 'is-open' ); | |
| 305 | - btn.classList.add( 'is-open' ); | |
| 306 | - document.addEventListener( 'mousedown', onDocMouseDown, true ); | |
| 307 | - document.addEventListener( 'keydown', onKey, true ); | |
| 308 | - } | |
| 309 | - function close() { | |
| 310 | - if ( ! isOpen ) return; | |
| 311 | - isOpen = false; | |
| 312 | - popover.classList.remove( 'is-open' ); | |
| 313 | - btn.classList.remove( 'is-open' ); | |
| 314 | - document.removeEventListener( 'mousedown', onDocMouseDown, true ); | |
| 315 | - document.removeEventListener( 'keydown', onKey, true ); | |
| 316 | - } | |
| 317 | - function onDocMouseDown( e ) { | |
| 318 | - if ( btn.contains( e.target ) ) return; | |
| 319 | - close(); | |
| 320 | - } | |
| 321 | - function onKey( e ) { | |
| 322 | - if ( e.key === 'Escape' ) { | |
| 323 | - e.stopPropagation(); | |
| 324 | - close(); | |
| 325 | - } | |
| 326 | - } | |
| 327 | - | |
| 328 | - anchor.addEventListener( 'click', function( e ) { | |
| 329 | - e.preventDefault(); | |
| 330 | - e.stopPropagation(); | |
| 331 | - isOpen ? close() : open(); | |
| 332 | - } ); | |
| 333 | - // Swallow stray clicks inside the popover so they don't bubble | |
| 334 | - // up to admin-bar handlers (and so the popover stays open while | |
| 335 | - // the user reads it). | |
| 336 | - popover.addEventListener( 'mousedown', function( e ) { | |
| 337 | - e.stopPropagation(); | |
| 338 | - } ); | |
| 339 | - popover.addEventListener( 'click', function( e ) { | |
| 340 | - e.stopPropagation(); | |
| 341 | - } ); | |
| 342 | - } | |
| 343 | - | |
| 344 | - function buildShortcutsPopover( data ) { | |
| 345 | - var root = document.createElement( 'div' ); | |
| 346 | - root.className = 'desktop-mode-shortcuts-popover'; | |
| 347 | - root.setAttribute( 'role', 'dialog' ); | |
| 348 | - if ( data.title ) { | |
| 349 | - root.setAttribute( 'aria-label', data.title ); | |
| 350 | - } | |
| 351 | - | |
| 352 | - if ( data.contextual ) { | |
| 353 | - root.appendChild( buildShortcutsTable( data.contextual ) ); | |
| 354 | - } | |
| 355 | - if ( data.general ) { | |
| 356 | - root.appendChild( buildShortcutsList( data.general ) ); | |
| 357 | - } | |
| 358 | - return root; | |
| 359 | - } | |
| 360 | - | |
| 361 | - function buildShortcutsTable( section ) { | |
| 362 | - var wrap = document.createElement( 'section' ); | |
| 363 | - wrap.className = 'desktop-mode-shortcuts-popover__section'; | |
| 364 | - | |
| 365 | - if ( section.heading ) { | |
| 366 | - var h = document.createElement( 'h3' ); | |
| 367 | - h.className = 'desktop-mode-shortcuts-popover__heading'; | |
| 368 | - h.textContent = section.heading; | |
| 369 | - wrap.appendChild( h ); | |
| 370 | - } | |
| 371 | - | |
| 372 | - var table = document.createElement( 'table' ); | |
| 373 | - table.className = 'desktop-mode-shortcuts-popover__table'; | |
| 374 | - | |
| 375 | - var thead = document.createElement( 'thead' ); | |
| 376 | - var headRow = document.createElement( 'tr' ); | |
| 377 | - [ 'key', 'outside', 'inside', 'showDesktop' ].forEach( function( col ) { | |
| 378 | - var th = document.createElement( 'th' ); | |
| 379 | - th.scope = 'col'; | |
| 380 | - th.textContent = section.headers && section.headers[ col ] ? section.headers[ col ] : ''; | |
| 381 | - headRow.appendChild( th ); | |
| 382 | - } ); | |
| 383 | - thead.appendChild( headRow ); | |
| 384 | - table.appendChild( thead ); | |
| 385 | - | |
| 386 | - var tbody = document.createElement( 'tbody' ); | |
| 387 | - ( section.rows || [] ).forEach( function( row ) { | |
| 388 | - var tr = document.createElement( 'tr' ); | |
| 389 | - | |
| 390 | - var keyCell = document.createElement( 'td' ); | |
| 391 | - keyCell.className = 'desktop-mode-shortcuts-popover__key-cell'; | |
| 392 | - keyCell.appendChild( renderKeys( row.keys || [] ) ); | |
| 393 | - if ( row.note ) { | |
| 394 | - var note = document.createElement( 'span' ); | |
| 395 | - note.className = 'desktop-mode-shortcuts-popover__note'; | |
| 396 | - note.textContent = ' ' + row.note; | |
| 397 | - keyCell.appendChild( note ); | |
| 398 | - } | |
| 399 | - tr.appendChild( keyCell ); | |
| 400 | - | |
| 401 | - [ 'outside', 'inside', 'showDesktop' ].forEach( function( col ) { | |
| 402 | - var td = document.createElement( 'td' ); | |
| 403 | - td.textContent = row[ col ] || '—'; | |
| 404 | - tr.appendChild( td ); | |
| 405 | - } ); | |
| 406 | - | |
| 407 | - tbody.appendChild( tr ); | |
| 408 | - } ); | |
| 409 | - table.appendChild( tbody ); | |
| 410 | - wrap.appendChild( table ); | |
| 411 | - | |
| 412 | - return wrap; | |
| 413 | - } | |
| 414 | - | |
| 415 | - function buildShortcutsList( section ) { | |
| 416 | - var wrap = document.createElement( 'section' ); | |
| 417 | - wrap.className = 'desktop-mode-shortcuts-popover__section'; | |
| 418 | - | |
| 419 | - if ( section.heading ) { | |
| 420 | - var h = document.createElement( 'h3' ); | |
| 421 | - h.className = 'desktop-mode-shortcuts-popover__heading'; | |
| 422 | - h.textContent = section.heading; | |
| 423 | - wrap.appendChild( h ); | |
| 424 | - } | |
| 425 | - | |
| 426 | - var list = document.createElement( 'ul' ); | |
| 427 | - list.className = 'desktop-mode-shortcuts-popover__list'; | |
| 428 | - ( section.items || [] ).forEach( function( item ) { | |
| 429 | - var li = document.createElement( 'li' ); | |
| 430 | - li.className = 'desktop-mode-shortcuts-popover__item'; | |
| 431 | - li.appendChild( renderKeys( item.keys || [] ) ); | |
| 432 | - var desc = document.createElement( 'span' ); | |
| 433 | - desc.className = 'desktop-mode-shortcuts-popover__description'; | |
| 434 | - desc.textContent = item.description || ''; | |
| 435 | - li.appendChild( desc ); | |
| 436 | - list.appendChild( li ); | |
| 437 | - } ); | |
| 438 | - wrap.appendChild( list ); | |
| 439 | - | |
| 440 | - return wrap; | |
| 441 | - } | |
| 442 | - | |
| 443 | - function renderKeys( keys ) { | |
| 444 | - var span = document.createElement( 'span' ); | |
| 445 | - span.className = 'desktop-mode-shortcuts-popover__keys'; | |
| 446 | - keys.forEach( function( key, idx ) { | |
| 447 | - if ( idx > 0 ) { | |
| 448 | - var plus = document.createElement( 'span' ); | |
| 449 | - plus.className = 'desktop-mode-shortcuts-popover__plus'; | |
| 450 | - plus.textContent = '+'; | |
| 451 | - plus.setAttribute( 'aria-hidden', 'true' ); | |
| 452 | - span.appendChild( plus ); | |
| 453 | - } | |
| 454 | - var kbd = document.createElement( 'kbd' ); | |
| 455 | - kbd.className = 'desktop-mode-shortcuts-popover__kbd'; | |
| 456 | - kbd.textContent = key; | |
| 457 | - span.appendChild( kbd ); | |
| 458 | - } ); | |
| 459 | - return span; | |
| 460 | - } | |
| 461 | 57 | } )(); |