PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.11
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.11
1.1.11 1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 All 35 releases
← All changes | assets/js/admin-bar.js +16 -408 0.9.8 → 1.1.11 View file →
@@ -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,403 +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 - // Fullscreen button — toggles browser fullscreen on the document
155 - // element so the shell occupies the whole screen with no browser
156 - // chrome. The click counts as a user gesture, which is required by
157 - // the Fullscreen API. We listen for `fullscreenchange` instead of
158 - // flipping state inside the click handler because the user can also
159 - // exit via Esc or the OS, and we want the icon/label to stay in sync
160 - // in those cases too.
161 - var fsBtn = document.getElementById( 'wp-admin-bar-desktop-fullscreen' );
162 - if ( fsBtn ) {
163 - var fsLink = fsBtn.querySelector( '.ab-item' );
164 - var fsLabel = fsBtn.querySelector( '.ab-label' );
165 - var fsI18n = ( cfg && cfg.i18n ) || {};
166 - // WP renders admin-bar items as `<a href="#">`, which HTML5
167 - // marks implicitly draggable. In browser fullscreen the button
168 - // sits at top:0 where cursor reveals (menu bar, exit
169 - // affordance) produce enough pointer jitter between mousedown
170 - // and mouseup that the browser commits to a native link-drag
171 - // instead of a click — the handler never runs and the user
172 - // sees a ghost flicker. Opt the element out of native drag so
173 - // the click always lands.
174 - if ( fsLink ) {
175 - fsLink.setAttribute( 'draggable', 'false' );
176 - }
177 - function paintFullscreen( on ) {
178 - // Body class drives the CSS that hides admin-bar items
179 - // which sit too close to the screen-corner reveal hot zones
180 - // in browser fullscreen — the OS / browser intercept clicks
181 - // in those bands. Hiding them shifts the Fullscreen button
182 - // further from the right edge so its own click lands.
183 - document.body.classList.toggle( 'desktop-mode-browser-fs', !! on );
184 - if ( on ) {
185 - fsBtn.classList.add( 'is-fullscreen' );
186 - if ( fsLabel ) fsLabel.textContent = fsI18n.exitFullscreen || 'Exit fullscreen';
187 - if ( fsLink ) fsLink.setAttribute( 'title', fsI18n.exitTitle || 'Exit fullscreen' );
188 - } else {
189 - fsBtn.classList.remove( 'is-fullscreen' );
190 - if ( fsLabel ) fsLabel.textContent = fsI18n.enterFullscreen || 'Fullscreen';
191 - if ( fsLink ) fsLink.setAttribute( 'title', fsI18n.enterTitle || 'Enter fullscreen' );
192 - }
193 - }
194 - fsBtn.addEventListener( 'click', function( e ) {
195 - e.preventDefault();
196 - // `window.top` so the whole tab goes fullscreen even when the
197 - // admin bar happens to be rendered inside an iframe (today
198 - // it's suppressed there, but a plugin could surface it). Fall
199 - // back to the current document if cross-origin blocks access.
200 - var doc;
201 - try {
202 - doc = window.top.document;
203 - } catch ( err ) {
204 - doc = document;
205 - }
206 - if ( doc.fullscreenElement ) {
207 - if ( doc.exitFullscreen ) {
208 - doc.exitFullscreen();
209 - }
210 - } else if ( doc.documentElement && doc.documentElement.requestFullscreen ) {
211 - doc.documentElement.requestFullscreen();
212 - }
213 - } );
214 - // Listen on whichever document we're driving so OS/Esc exits
215 - // repaint the button too. Bound on both to cover the iframe edge
216 - // case without extra branching.
217 - document.addEventListener( 'fullscreenchange', function() {
218 - paintFullscreen( !! document.fullscreenElement );
219 - } );
220 - try {
221 - if ( window.top.document !== document ) {
222 - window.top.document.addEventListener( 'fullscreenchange', function() {
223 - paintFullscreen( !! window.top.document.fullscreenElement );
224 - } );
225 - }
226 - } catch ( err ) {}
227 - }
228 -
229 - // Bug Report button — same decoupling pattern as Ask AI. Dispatches
230 - // `desktop-mode-open-bug-report`; the shell answers by opening the
231 - // Bug Report native window. Wired in `src/desktop.ts`.
232 - var bugBtn = document.getElementById( 'wp-admin-bar-desktop-bug-report' );
233 - if ( bugBtn ) {
234 - bugBtn.addEventListener( 'click', function( e ) {
235 - e.preventDefault();
236 - document.dispatchEvent( new CustomEvent( 'desktop-mode-open-bug-report' ) );
237 - } );
238 - }
239 -
240 - // Keyboard Shortcuts button — toggles a floating popover anchored
241 - // under the button, styled like the Arrange submenu. Content is
242 - // passed in via `cfg.shortcuts` (translated server-side in PHP).
243 - wireShortcutsPopover( document.getElementById( 'wp-admin-bar-desktop-help' ), cfg.shortcuts );
244 -
245 - // Custom tooltips for the icon-only admin-bar buttons. The native
246 - // `title` attribute renders a slow, OS-styled tooltip that conflicts
247 - // with our dock-style custom tooltip; we swap each `title` to
248 - // `data-desktop-tooltip` + `aria-label` so screen readers still see
249 - // the label but the visual tooltip is fully ours.
250 - wireTooltipsFor( [
251 - 'wp-admin-bar-desktop-fullscreen',
252 - 'wp-admin-bar-desktop-help',
253 - 'wp-admin-bar-desktop-bug-report',
254 - 'wp-admin-bar-desktop-layout-menu',
255 - ] );
256 -
257 - /**
258 - * Re-anchors a native `title` attribute to a `data-desktop-tooltip`
259 - * data attribute on the same node, plus mirrors it to `aria-label`
260 - * so assistive tech keeps the description. Pure-CSS tooltip then
261 - * renders from the data attribute via the `[data-desktop-tooltip]
262 - * .ab-item::after` rule in admin-bar.php.
263 - */
264 - function wireTooltipsFor( ids ) {
265 - for ( var i = 0; i < ids.length; i++ ) {
266 - var node = document.getElementById( ids[ i ] );
267 - if ( ! node ) continue;
268 - var link = node.querySelector( '.ab-item' );
269 - if ( ! link ) continue;
270 - var label = link.getAttribute( 'title' );
271 - if ( ! label ) continue;
272 - link.removeAttribute( 'title' );
273 - link.setAttribute( 'data-desktop-tooltip', label );
274 - if ( ! link.hasAttribute( 'aria-label' ) ) {
275 - link.setAttribute( 'aria-label', label );
276 - }
277 - }
278 - }
279 -
280 - function wireShortcutsPopover( btn, data ) {
281 - if ( ! btn || ! data ) {
282 - return;
283 - }
284 - var anchor = btn.querySelector( '.ab-item' ) || btn;
285 - var popover = buildShortcutsPopover( data );
286 - btn.appendChild( popover );
287 -
288 - var isOpen = false;
289 - function open() {
290 - if ( isOpen ) return;
291 - isOpen = true;
292 - popover.classList.add( 'is-open' );
293 - btn.classList.add( 'is-open' );
294 - document.addEventListener( 'mousedown', onDocMouseDown, true );
295 - document.addEventListener( 'keydown', onKey, true );
296 - }
297 - function close() {
298 - if ( ! isOpen ) return;
299 - isOpen = false;
300 - popover.classList.remove( 'is-open' );
301 - btn.classList.remove( 'is-open' );
302 - document.removeEventListener( 'mousedown', onDocMouseDown, true );
303 - document.removeEventListener( 'keydown', onKey, true );
304 - }
305 - function onDocMouseDown( e ) {
306 - if ( btn.contains( e.target ) ) return;
307 - close();
308 - }
309 - function onKey( e ) {
310 - if ( e.key === 'Escape' ) {
311 - e.stopPropagation();
312 - close();
313 - }
314 - }
315 -
316 - anchor.addEventListener( 'click', function( e ) {
317 - e.preventDefault();
318 - e.stopPropagation();
319 - isOpen ? close() : open();
320 - } );
321 - // Swallow stray clicks inside the popover so they don't bubble
322 - // up to admin-bar handlers (and so the popover stays open while
323 - // the user reads it).
324 - popover.addEventListener( 'mousedown', function( e ) {
325 - e.stopPropagation();
326 - } );
327 - popover.addEventListener( 'click', function( e ) {
328 - e.stopPropagation();
329 - } );
330 - }
331 -
332 - function buildShortcutsPopover( data ) {
333 - var root = document.createElement( 'div' );
334 - root.className = 'desktop-mode-shortcuts-popover';
335 - root.setAttribute( 'role', 'dialog' );
336 - if ( data.title ) {
337 - root.setAttribute( 'aria-label', data.title );
338 - }
339 -
340 - if ( data.contextual ) {
341 - root.appendChild( buildShortcutsTable( data.contextual ) );
342 - }
343 - if ( data.general ) {
344 - root.appendChild( buildShortcutsList( data.general ) );
345 - }
346 - return root;
347 - }
348 -
349 - function buildShortcutsTable( section ) {
350 - var wrap = document.createElement( 'section' );
351 - wrap.className = 'desktop-mode-shortcuts-popover__section';
352 -
353 - if ( section.heading ) {
354 - var h = document.createElement( 'h3' );
355 - h.className = 'desktop-mode-shortcuts-popover__heading';
356 - h.textContent = section.heading;
357 - wrap.appendChild( h );
358 - }
359 -
360 - var table = document.createElement( 'table' );
361 - table.className = 'desktop-mode-shortcuts-popover__table';
362 -
363 - var thead = document.createElement( 'thead' );
364 - var headRow = document.createElement( 'tr' );
365 - [ 'key', 'outside', 'inside', 'showDesktop' ].forEach( function( col ) {
366 - var th = document.createElement( 'th' );
367 - th.scope = 'col';
368 - th.textContent = section.headers && section.headers[ col ] ? section.headers[ col ] : '';
369 - headRow.appendChild( th );
370 - } );
371 - thead.appendChild( headRow );
372 - table.appendChild( thead );
373 -
374 - var tbody = document.createElement( 'tbody' );
375 - ( section.rows || [] ).forEach( function( row ) {
376 - var tr = document.createElement( 'tr' );
377 -
378 - var keyCell = document.createElement( 'td' );
379 - keyCell.className = 'desktop-mode-shortcuts-popover__key-cell';
380 - keyCell.appendChild( renderKeys( row.keys || [] ) );
381 - if ( row.note ) {
382 - var note = document.createElement( 'span' );
383 - note.className = 'desktop-mode-shortcuts-popover__note';
384 - note.textContent = ' ' + row.note;
385 - keyCell.appendChild( note );
386 - }
387 - tr.appendChild( keyCell );
388 -
389 - [ 'outside', 'inside', 'showDesktop' ].forEach( function( col ) {
390 - var td = document.createElement( 'td' );
391 - td.textContent = row[ col ] || '—';
392 - tr.appendChild( td );
393 - } );
394 -
395 - tbody.appendChild( tr );
396 - } );
397 - table.appendChild( tbody );
398 - wrap.appendChild( table );
399 -
400 - return wrap;
401 - }
402 -
403 - function buildShortcutsList( section ) {
404 - var wrap = document.createElement( 'section' );
405 - wrap.className = 'desktop-mode-shortcuts-popover__section';
406 -
407 - if ( section.heading ) {
408 - var h = document.createElement( 'h3' );
409 - h.className = 'desktop-mode-shortcuts-popover__heading';
410 - h.textContent = section.heading;
411 - wrap.appendChild( h );
412 - }
413 -
414 - var list = document.createElement( 'ul' );
415 - list.className = 'desktop-mode-shortcuts-popover__list';
416 - ( section.items || [] ).forEach( function( item ) {
417 - var li = document.createElement( 'li' );
418 - li.className = 'desktop-mode-shortcuts-popover__item';
419 - li.appendChild( renderKeys( item.keys || [] ) );
420 - var desc = document.createElement( 'span' );
421 - desc.className = 'desktop-mode-shortcuts-popover__description';
422 - desc.textContent = item.description || '';
423 - li.appendChild( desc );
424 - list.appendChild( li );
425 - } );
426 - wrap.appendChild( list );
427 -
428 - return wrap;
429 - }
430 -
431 - function renderKeys( keys ) {
432 - var span = document.createElement( 'span' );
433 - span.className = 'desktop-mode-shortcuts-popover__keys';
434 - keys.forEach( function( key, idx ) {
435 - if ( idx > 0 ) {
436 - var plus = document.createElement( 'span' );
437 - plus.className = 'desktop-mode-shortcuts-popover__plus';
438 - plus.textContent = '+';
439 - plus.setAttribute( 'aria-hidden', 'true' );
440 - span.appendChild( plus );
441 - }
442 - var kbd = document.createElement( 'kbd' );
443 - kbd.className = 'desktop-mode-shortcuts-popover__kbd';
444 - kbd.textContent = key;
445 - span.appendChild( kbd );
446 - } );
447 - return span;
448 - }
449 57 } )();