PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.5
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.5
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 0.8.7 All 34 releases
desktop-mode / includes / assets.php

assets.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.9.5, at includes/assets.php

420 lines 16.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Desktop Mode asset registration.
4 *
5 * Registers all desktop-mode CSS and JS handles with WordPress so they can
6 * be enqueued from anywhere in the plugin (or by third parties).
7 *
8 * @package WPDesktopMode
9 */
10
11 defined( 'ABSPATH' ) || exit;
12
13 /**
14 * Cache-buster for a stylesheet that `@import`s sub-sheets: the max
15 * `filemtime` across the file AND everything it (transitively) imports.
16 * Sub-sheet URLs carry no `?ver=` of their own, so the parent's stamp is
17 * the only cache key the browser ever sees for the subtree — it must
18 * move when any member changes.
19 *
20 * @since 0.9.4
21 *
22 * @param string $relative Stylesheet path relative to the plugin dir.
23 * @param string $fallback Version to use when the file is missing.
24 * @return string Version string.
25 */
26 function desktop_mode_css_subtree_version( $relative, $fallback ) {
27 $root = DESKTOP_MODE_DIR . $relative;
28 if ( ! file_exists( $root ) ) {
29 return (string) $fallback;
30 }
31 $max = (int) filemtime( $root );
32 $queue = array( $root );
33 $seen = array( $root => true );
34 while ( ! empty( $queue ) ) {
35 $file = array_pop( $queue );
36 $css = (string) file_get_contents( $file );
37 if ( ! preg_match_all( '/@import\s+url\(\s*["\']?([^"\')\s]+)["\']?\s*\)/i', $css, $matches ) ) {
38 continue;
39 }
40 foreach ( $matches[1] as $import ) {
41 $path = dirname( $file ) . '/' . $import;
42 if ( ! file_exists( $path ) || isset( $seen[ $path ] ) ) {
43 continue;
44 }
45 $seen[ $path ] = true;
46 $max = max( $max, (int) filemtime( $path ) );
47 $queue[] = $path;
48 }
49 }
50 return (string) $max;
51 }
52
53 /**
54 * Registers the desktop mode CSS and JS handles.
55 *
56 * @since 0.1.0
57 */
58 function desktop_mode_register_assets() {
59 $version = DESKTOP_MODE_VERSION;
60 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
61
62 // `filemtime`-stamped version for built bundles. The plugin-wide
63 // `DESKTOP_MODE_VERSION` is bumped per release, but the bundles
64 // iterate faster — without a per-file mtime stamp, two clients
65 // loading the same `?ver=…` URL can be served different bytes
66 // (whichever build was on disk at upload time). Stamping with the
67 // file's modification time guarantees the URL changes whenever the
68 // file does, so "is my fix deployed?" is answerable from the
69 // network tab. Falls back to `$version` when the file is missing
70 // (test envs that import this file before the build runs).
71 $built_version = static function ( $relative ) use ( $version ) {
72 $path = DESKTOP_MODE_DIR . $relative;
73 return file_exists( $path ) ? (string) filemtime( $path ) : $version;
74 };
75
76
77 // Styles.
78 wp_register_style(
79 'desktop-mode-variables',
80 DESKTOP_MODE_URL . 'assets/css/variables.css',
81 array(),
82 $version
83 );
84 // `filemtime`-stamped so the `<link rel="stylesheet">` URL matches the
85 // `<link rel="preload" as="style">` hint emitted by
86 // `desktop_mode_print_preload_hints()` (which stamps with filemtime).
87 // Registering this with the plain `$version` instead produced two
88 // different `?ver=` query strings for the same file, so the browser
89 // never matched the preload to the stylesheet and logged "preloaded
90 // but not used within a few seconds from the window's load event".
91 wp_register_style(
92 'desktop-mode',
93 DESKTOP_MODE_URL . 'assets/css/desktop.css',
94 array( 'desktop-mode-variables' ),
95 $built_version( 'assets/css/desktop.css' )
96 );
97 // `filemtime`-stamped — window-chrome iteration (drop overlays,
98 // new drag affordances, third-party-plugin compat) lands faster
99 // than plugin version bumps. Without an mtime stamp the browser
100 // keeps `?ver=<plugin-version>` valid for the whole release cycle
101 // and the user keeps seeing yesterday's CSS even after a hard
102 // reload.
103 //
104 // The stamp covers the whole `@import` SUBTREE, not just the parent
105 // file: `windows.css` imports `window-chrome.css`,
106 // `os-settings.css`, … and those sub-sheet URLs carry no `?ver=` of
107 // their own. Stamping only the parent meant an edit to a sub-sheet
108 // never changed any URL the browser knew about, so users kept
109 // yesterday's rules until an unrelated parent edit came along
110 // (exactly how the wallpaper-description card shipped with stale
111 // layout). The subtree max-mtime busts the parent whenever ANY
112 // sub-sheet changes; the re-parsed parent then re-requests the
113 // sub-sheets, whose fresh mtimes fail heuristic-cache reuse.
114 wp_register_style(
115 'desktop-mode-windows',
116 DESKTOP_MODE_URL . 'assets/css/windows.css',
117 array( 'desktop-mode-variables', 'dashicons' ),
118 desktop_mode_css_subtree_version( 'assets/css/windows.css', $version )
119 );
120 wp_register_style(
121 'desktop-mode-dock',
122 DESKTOP_MODE_URL . 'assets/css/dock.css',
123 array( 'desktop-mode-variables', 'dashicons' ),
124 $version
125 );
126 wp_register_style(
127 'desktop-mode-dock-peek',
128 DESKTOP_MODE_URL . 'assets/css/dock-peek.css',
129 array( 'desktop-mode-dock' ),
130 $version
131 );
132 // `filemtime`-stamped — the chromeless overrides iterate faster
133 // than the plugin-wide version bumps (per-page compat shims and
134 // page-title-action exceptions land in patches), and a stale
135 // cached copy means the user sees yesterday's rules. Without the
136 // stamp, the browser keeps `?ver=0.8.1` valid for the whole
137 // release cycle.
138 wp_register_style(
139 'desktop-mode-chromeless',
140 DESKTOP_MODE_URL . 'assets/css/chromeless.css',
141 array( 'desktop-mode' ),
142 $built_version( 'assets/css/chromeless.css' )
143 );
144
145 // `filemtime`-stamped — the AI assistant surface (error states,
146 // inline affordances) iterates faster than plugin version bumps.
147 // Without an mtime stamp the browser keeps `?ver=<plugin-version>`
148 // valid for the whole release cycle and the user keeps seeing
149 // yesterday's CSS even after a hard reload.
150 wp_register_style(
151 'desktop-mode-ai-assistant',
152 DESKTOP_MODE_URL . 'assets/css/ai-assistant.css',
153 array( 'desktop-mode-variables' ),
154 $built_version( 'assets/css/ai-assistant.css' )
155 );
156
157 wp_register_style(
158 'desktop-mode-bug-report',
159 DESKTOP_MODE_URL . 'assets/css/bug-report.css',
160 array( 'desktop-mode-variables' ),
161 $version
162 );
163
164 // `filemtime` instead of the plugin-wide `$version` for the
165 // recycle-bin CSS — this file iterates faster than the bundle
166 // and we never want a stale CSS cache to mask a real fix.
167 $recycle_bin_css = DESKTOP_MODE_DIR . 'assets/css/recycle-bin.css';
168 wp_register_style(
169 'desktop-mode-recycle-bin',
170 DESKTOP_MODE_URL . 'assets/css/recycle-bin.css',
171 array( 'desktop-mode-variables', 'dashicons' ),
172 file_exists( $recycle_bin_css ) ? (string) filemtime( $recycle_bin_css ) : $version
173 );
174
175 // `filemtime` for the native Posts window CSS — same rationale as
176 // the recycle-bin CSS: bundle iterates faster than the plugin
177 // version and stale caches are worse than the cost of a 304.
178 $posts_window_css = DESKTOP_MODE_DIR . 'assets/css/posts-window.css';
179 wp_register_style(
180 'desktop-mode-posts-window',
181 DESKTOP_MODE_URL . 'assets/css/posts-window.css',
182 array( 'desktop-mode-variables', 'dashicons' ),
183 file_exists( $posts_window_css ) ? (string) filemtime( $posts_window_css ) : $version
184 );
185
186 // Native Plugins window CSS — same `filemtime`-cache-bust posture
187 // as the Posts/Recycle Bin styles.
188 $plugins_window_css = DESKTOP_MODE_DIR . 'assets/css/plugins-window.css';
189 wp_register_style(
190 'desktop-mode-plugins-window',
191 DESKTOP_MODE_URL . 'assets/css/plugins-window.css',
192 array( 'desktop-mode-variables', 'dashicons' ),
193 file_exists( $plugins_window_css ) ? (string) filemtime( $plugins_window_css ) : $version
194 );
195
196 // Native Comments window CSS — same `filemtime` posture.
197 $comments_window_css = DESKTOP_MODE_DIR . 'assets/css/comments-window.css';
198 wp_register_style(
199 'desktop-mode-comments-window',
200 DESKTOP_MODE_URL . 'assets/css/comments-window.css',
201 array( 'desktop-mode-variables', 'dashicons' ),
202 file_exists( $comments_window_css ) ? (string) filemtime( $comments_window_css ) : $version
203 );
204
205 // Files-on-the-Desktop tile + layer styles. `filemtime` for the
206 // same reason as the recycle-bin / posts-window CSS: this file
207 // iterates faster than the plugin version, and a stale cache
208 // would mask a real fix.
209 $desktop_files_css = DESKTOP_MODE_DIR . 'assets/css/desktop-files.css';
210 wp_register_style(
211 'desktop-mode-files',
212 DESKTOP_MODE_URL . 'assets/css/desktop-files.css',
213 array( 'desktop-mode-variables', 'dashicons' ),
214 file_exists( $desktop_files_css ) ? (string) filemtime( $desktop_files_css ) : $version
215 );
216
217 // Scripts.
218 //
219 // `wp-hooks` — the shell exposes a WordPress-style filter/action
220 // API (`window.wp.hooks`) to third-party plugins.
221 // `wp-i18n` — the TS `__()` / `_x()` / `sprintf()` wrappers in
222 // `src/i18n.ts` delegate to `window.wp.i18n` for translation
223 // lookups. Both handles are core-shipped but only pre-enqueued
224 // when Gutenberg-adjacent deps pull them in, so we list them
225 // explicitly to guarantee load order.
226 wp_register_script(
227 'desktop-mode',
228 DESKTOP_MODE_URL . 'assets/js/desktop' . $suffix . '.js',
229 // `heartbeat` + `jquery` — the recycle-bin badge module
230 // (loaded as part of this bundle) opts into the WordPress
231 // Heartbeat API so the count tile / desktop-icon badge
232 // stays in sync even when the bin window is closed.
233 array( 'wp-hooks', 'wp-i18n', 'heartbeat', 'jquery' ),
234 $built_version( 'assets/js/desktop' . $suffix . '.js' ),
235 true
236 );
237
238 // `desktop-mode-iframe-bridge` — opt-in iframe-side bridge that
239 // provides `wp.desktop.iframe.publish/subscribe/onConnection/
240 // requestConnection` to any same-origin iframe that enqueues it.
241 // Same code is also injected inline by the chromeless bridge
242 // (so chromeless wp-admin pages don't need a separate enqueue)
243 // and auto-injected when a native window opts in via
244 // `iframeContent: { bridge: true }`. Plugins targeting their
245 // own iframe pages just enqueue this handle.
246 wp_register_script(
247 'desktop-mode-iframe-bridge',
248 DESKTOP_MODE_URL . 'assets/js/iframe-bridge' . $suffix . '.js',
249 array(),
250 $built_version( 'assets/js/iframe-bridge' . $suffix . '.js' ),
251 true
252 );
253
254 // `desktop-mode-gutenberg-drop-receiver` — iframe-side bundle
255 // enqueued only on the Block Editor screens (`post.php` /
256 // `post-new.php`) for desktop-mode users. Listens for
257 // `desktop-mode-drop` postMessages from the parent shell (see
258 // `src/drag/iframe-drop-targets.ts`) and inserts the matching
259 // Gutenberg block via `wp.data.dispatch('core/block-editor')`.
260 // Depends on `wp-blocks` + `wp-data` so the editor stores are
261 // guaranteed enqueued before the receiver runs its first message
262 // handler.
263 wp_register_script(
264 'desktop-mode-gutenberg-drop-receiver',
265 DESKTOP_MODE_URL . 'assets/js/gutenberg-drop-receiver' . $suffix . '.js',
266 array( 'wp-blocks', 'wp-data' ),
267 $built_version( 'assets/js/gutenberg-drop-receiver' . $suffix . '.js' ),
268 true
269 );
270
271 // `desktop-mode-recycle-bin` — small bundle for the Recycle Bin
272 // native window. Lazy-loaded by the native-window sync the first
273 // time the bin opens; registers a render callback on
274 // `window.desktopModeNativeWindows['desktop-mode-recycle-bin']`.
275 $recycle_bin_js = DESKTOP_MODE_DIR . 'assets/js/recycle-bin' . $suffix . '.js';
276 wp_register_script(
277 'desktop-mode-recycle-bin',
278 DESKTOP_MODE_URL . 'assets/js/recycle-bin' . $suffix . '.js',
279 // `heartbeat` + `jquery` — the bin opts in to the WordPress
280 // Heartbeat API while its window is open as the catch-all
281 // real-time channel for deletes that don't render an admin
282 // footer (REST/AJAX/other tabs/WP-CLI). See
283 // `src/recycle-bin/realtime.ts` for the subscriber.
284 array( 'wp-i18n', 'heartbeat', 'jquery' ),
285 file_exists( $recycle_bin_js ) ? (string) filemtime( $recycle_bin_js ) : $version,
286 true
287 );
288 wp_set_script_translations(
289 'desktop-mode-recycle-bin',
290 'desktop-mode',
291 DESKTOP_MODE_DIR . 'languages'
292 );
293
294 // `desktop-mode-posts-window` — small bundle for the native Posts
295 // window. Lazy-loaded by the native-window sync the first time the
296 // window opens (via the dock-click swap when the user opts in);
297 // registers a render callback on
298 // `window.desktopModeNativeWindows['desktop-mode-posts']`.
299 $posts_window_js = DESKTOP_MODE_DIR . 'assets/js/posts-window' . $suffix . '.js';
300 wp_register_script(
301 'desktop-mode-posts-window',
302 DESKTOP_MODE_URL . 'assets/js/posts-window' . $suffix . '.js',
303 array( 'wp-i18n' ),
304 file_exists( $posts_window_js ) ? (string) filemtime( $posts_window_js ) : $version,
305 true
306 );
307 wp_set_script_translations(
308 'desktop-mode-posts-window',
309 'desktop-mode',
310 DESKTOP_MODE_DIR . 'languages'
311 );
312
313 // `desktop-mode-plugins-window` — small bundle for the native
314 // Plugins window. Lazy-loaded by the native-window sync the first
315 // time the window opens (via the dock-click swap when the user
316 // opts in); registers a render callback on
317 // `window.desktopModeNativeWindows['desktop-mode-plugins']`.
318 $plugins_window_js = DESKTOP_MODE_DIR . 'assets/js/plugins-window' . $suffix . '.js';
319 wp_register_script(
320 'desktop-mode-plugins-window',
321 DESKTOP_MODE_URL . 'assets/js/plugins-window' . $suffix . '.js',
322 array( 'wp-i18n' ),
323 file_exists( $plugins_window_js ) ? (string) filemtime( $plugins_window_js ) : $version,
324 true
325 );
326 wp_set_script_translations(
327 'desktop-mode-plugins-window',
328 'desktop-mode',
329 DESKTOP_MODE_DIR . 'languages'
330 );
331
332 // `desktop-mode-comments-window` — small bundle for the native
333 // Comments window. Lazy-loaded by the native-window sync the first
334 // time the window opens (via the dock-click swap when the user
335 // opts in); registers a render callback on
336 // `window.desktopModeNativeWindows['desktop-mode-comments']`.
337 $comments_window_js = DESKTOP_MODE_DIR . 'assets/js/comments-window' . $suffix . '.js';
338 wp_register_script(
339 'desktop-mode-comments-window',
340 DESKTOP_MODE_URL . 'assets/js/comments-window' . $suffix . '.js',
341 array( 'wp-i18n' ),
342 file_exists( $comments_window_js ) ? (string) filemtime( $comments_window_js ) : $version,
343 true
344 );
345 wp_set_script_translations(
346 'desktop-mode-comments-window',
347 'desktop-mode',
348 DESKTOP_MODE_DIR . 'languages'
349 );
350
351 // `desktop-mode-animated-logo-wallpaper` — built-in PixiJS canvas
352 // wallpaper, moved out of `desktop.min.js` in 0.8.4. The wallpaper
353 // `server-sync` loads this handle when the user selects the
354 // `wp-animated-logo` wallpaper (or opens OS Settings → Wallpaper
355 // and the picker pulls every registered canvas def in). The
356 // bundle's only side effect is publishing the `WallpaperDef` on
357 // `window.desktopModeWallpapers['wp-animated-logo']`.
358 $animated_logo_js = DESKTOP_MODE_DIR . 'assets/js/animated-logo-wallpaper' . $suffix . '.js';
359 wp_register_script(
360 'desktop-mode-animated-logo-wallpaper',
361 DESKTOP_MODE_URL . 'assets/js/animated-logo-wallpaper' . $suffix . '.js',
362 array( 'wp-hooks' ),
363 file_exists( $animated_logo_js ) ? (string) filemtime( $animated_logo_js ) : $version,
364 true
365 );
366
367 // `desktop-mode-snow-wallpaper` — built-in PixiJS canvas
368 // wallpaper: snowfall that accumulates on window tops and melts
369 // away. Same lazy-load path as the animated logo: the wallpaper
370 // `server-sync` injects this handle when the user selects the
371 // `wp-snow` wallpaper (or opens OS Settings → Wallpaper and the
372 // picker pulls the def in). The bundle's only side effect is
373 // publishing the `WallpaperDef` on
374 // `window.desktopModeWallpapers['wp-snow']`.
375 $snow_js = DESKTOP_MODE_DIR . 'assets/js/snow-wallpaper' . $suffix . '.js';
376 wp_register_script(
377 'desktop-mode-snow-wallpaper',
378 DESKTOP_MODE_URL . 'assets/js/snow-wallpaper' . $suffix . '.js',
379 array( 'wp-hooks', 'wp-i18n' ),
380 file_exists( $snow_js ) ? (string) filemtime( $snow_js ) : $version,
381 true
382 );
383 wp_set_script_translations(
384 'desktop-mode-snow-wallpaper',
385 'desktop-mode',
386 DESKTOP_MODE_DIR . 'languages'
387 );
388
389 // `desktop-mode-ai-assistant` — AI Copilot spotlight overlay,
390 // moved out of `desktop.min.js` in 0.8.4. The main bundle ships a
391 // stub matching the public `wp.desktop.ai` contract; the stub
392 // `<script>`-injects this handle the first time the user opens
393 // the assistant (Cmd+K or admin-bar button).
394 $ai_assistant_js = DESKTOP_MODE_DIR . 'assets/js/ai-assistant' . $suffix . '.js';
395 wp_register_script(
396 'desktop-mode-ai-assistant',
397 DESKTOP_MODE_URL . 'assets/js/ai-assistant' . $suffix . '.js',
398 array( 'wp-hooks', 'wp-i18n' ),
399 file_exists( $ai_assistant_js ) ? (string) filemtime( $ai_assistant_js ) : $version,
400 true
401 );
402 wp_set_script_translations(
403 'desktop-mode-ai-assistant',
404 'desktop-mode',
405 DESKTOP_MODE_DIR . 'languages'
406 );
407
408 // Wire the translation bundle to this script handle. WP looks
409 // for `languages/desktop-mode-{locale}-desktop-mode.json` and
410 // injects its `locale_data` into `wp.i18n` just before the
411 // script runs — so every `__()` call resolves to the right
412 // language without any runtime fetch.
413 wp_set_script_translations(
414 'desktop-mode',
415 'desktop-mode',
416 DESKTOP_MODE_DIR . 'languages'
417 );
418 }
419 add_action( 'init', 'desktop_mode_register_assets' );
420