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 -420 0.9.2 → 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,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 } )();