PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.7
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.7
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.7, at includes/assets.php

542 lines 21.5 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 = desktop_mode_asset_suffix();
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 // Split out of the `windows.css` @import chain so they can load
121 // deferred (see `desktop_mode_defer_non_critical_styles()`): the
122 // UI they style — the OS Settings panel and the window overview —
123 // is lazy-loaded JS that can never be on screen at first paint,
124 // so ~47 KB of CSS has no business blocking render. They depend
125 // on `desktop-mode-windows` to keep the historical cascade order
126 // (they used to be imported after the chrome/states sub-sheets).
127 wp_register_style(
128 'desktop-mode-window-overview',
129 DESKTOP_MODE_URL . 'assets/css/window-overview.css',
130 array( 'desktop-mode-windows' ),
131 $built_version( 'assets/css/window-overview.css' )
132 );
133 wp_register_style(
134 'desktop-mode-os-settings',
135 DESKTOP_MODE_URL . 'assets/css/os-settings.css',
136 array( 'desktop-mode-windows' ),
137 $built_version( 'assets/css/os-settings.css' )
138 );
139 wp_register_style(
140 'desktop-mode-dock',
141 DESKTOP_MODE_URL . 'assets/css/dock.css',
142 array( 'desktop-mode-variables', 'dashicons' ),
143 $built_version( 'assets/css/dock.css' )
144 );
145 wp_register_style(
146 'desktop-mode-dock-peek',
147 DESKTOP_MODE_URL . 'assets/css/dock-peek.css',
148 array( 'desktop-mode-dock' ),
149 $version
150 );
151 // `filemtime`-stamped — the chromeless overrides iterate faster
152 // than the plugin-wide version bumps (per-page compat shims and
153 // page-title-action exceptions land in patches), and a stale
154 // cached copy means the user sees yesterday's rules. Without the
155 // stamp, the browser keeps `?ver=0.8.1` valid for the whole
156 // release cycle.
157 wp_register_style(
158 'desktop-mode-chromeless',
159 DESKTOP_MODE_URL . 'assets/css/chromeless.css',
160 array( 'desktop-mode' ),
161 $built_version( 'assets/css/chromeless.css' )
162 );
163
164 // `filemtime`-stamped — the AI assistant surface (error states,
165 // inline affordances) iterates faster than plugin version bumps.
166 // Without an mtime stamp the browser keeps `?ver=<plugin-version>`
167 // valid for the whole release cycle and the user keeps seeing
168 // yesterday's CSS even after a hard reload.
169 wp_register_style(
170 'desktop-mode-ai-assistant',
171 DESKTOP_MODE_URL . 'assets/css/ai-assistant.css',
172 array( 'desktop-mode-variables' ),
173 $built_version( 'assets/css/ai-assistant.css' )
174 );
175
176 wp_register_style(
177 'desktop-mode-bug-report',
178 DESKTOP_MODE_URL . 'assets/css/bug-report.css',
179 array( 'desktop-mode-variables' ),
180 $version
181 );
182
183 // `filemtime` instead of the plugin-wide `$version` for the
184 // recycle-bin CSS — this file iterates faster than the bundle
185 // and we never want a stale CSS cache to mask a real fix.
186 $recycle_bin_css = DESKTOP_MODE_DIR . 'assets/css/recycle-bin.css';
187 wp_register_style(
188 'desktop-mode-recycle-bin',
189 DESKTOP_MODE_URL . 'assets/css/recycle-bin.css',
190 array( 'desktop-mode-variables', 'dashicons' ),
191 file_exists( $recycle_bin_css ) ? (string) filemtime( $recycle_bin_css ) : $version
192 );
193
194 // `filemtime` for the native Posts window CSS — same rationale as
195 // the recycle-bin CSS: bundle iterates faster than the plugin
196 // version and stale caches are worse than the cost of a 304.
197 $posts_window_css = DESKTOP_MODE_DIR . 'assets/css/posts-window.css';
198 wp_register_style(
199 'desktop-mode-posts-window',
200 DESKTOP_MODE_URL . 'assets/css/posts-window.css',
201 array( 'desktop-mode-variables', 'dashicons' ),
202 file_exists( $posts_window_css ) ? (string) filemtime( $posts_window_css ) : $version
203 );
204
205 // Native Plugins window CSS — same `filemtime`-cache-bust posture
206 // as the Posts/Recycle Bin styles.
207 $plugins_window_css = DESKTOP_MODE_DIR . 'assets/css/plugins-window.css';
208 wp_register_style(
209 'desktop-mode-plugins-window',
210 DESKTOP_MODE_URL . 'assets/css/plugins-window.css',
211 array( 'desktop-mode-variables', 'dashicons' ),
212 file_exists( $plugins_window_css ) ? (string) filemtime( $plugins_window_css ) : $version
213 );
214
215 // Native Comments window CSS — same `filemtime` posture.
216 $comments_window_css = DESKTOP_MODE_DIR . 'assets/css/comments-window.css';
217 wp_register_style(
218 'desktop-mode-comments-window',
219 DESKTOP_MODE_URL . 'assets/css/comments-window.css',
220 array( 'desktop-mode-variables', 'dashicons' ),
221 file_exists( $comments_window_css ) ? (string) filemtime( $comments_window_css ) : $version
222 );
223
224 // Files-on-the-Desktop tile + layer styles. `filemtime` for the
225 // same reason as the recycle-bin / posts-window CSS: this file
226 // iterates faster than the plugin version, and a stale cache
227 // would mask a real fix.
228 $desktop_files_css = DESKTOP_MODE_DIR . 'assets/css/desktop-files.css';
229 wp_register_style(
230 'desktop-mode-files',
231 DESKTOP_MODE_URL . 'assets/css/desktop-files.css',
232 array( 'desktop-mode-variables', 'dashicons' ),
233 file_exists( $desktop_files_css ) ? (string) filemtime( $desktop_files_css ) : $version
234 );
235
236 // Games hub window (launcher grid, scoreboard, challenges) +
237 // per-game styles. Same `filemtime` cache-bust posture as the
238 // other fast-iterating feature stylesheets.
239 $games_css = DESKTOP_MODE_DIR . 'assets/css/games.css';
240 wp_register_style(
241 'desktop-mode-games',
242 DESKTOP_MODE_URL . 'assets/css/games.css',
243 array( 'desktop-mode-variables', 'dashicons' ),
244 file_exists( $games_css ) ? (string) filemtime( $games_css ) : $version
245 );
246 $game_inkfall_css = DESKTOP_MODE_DIR . 'assets/css/game-inkfall.css';
247 wp_register_style(
248 'desktop-mode-game-inkfall',
249 DESKTOP_MODE_URL . 'assets/css/game-inkfall.css',
250 array( 'desktop-mode-variables' ),
251 file_exists( $game_inkfall_css ) ? (string) filemtime( $game_inkfall_css ) : $version
252 );
253 $game_alphabet_soup_css = DESKTOP_MODE_DIR . 'assets/css/game-alphabet-soup.css';
254 wp_register_style(
255 'desktop-mode-game-alphabet-soup',
256 DESKTOP_MODE_URL . 'assets/css/game-alphabet-soup.css',
257 array( 'desktop-mode-variables' ),
258 file_exists( $game_alphabet_soup_css ) ? (string) filemtime( $game_alphabet_soup_css ) : $version
259 );
260
261 // Pinned-notes layer styles (paper, pushpin, pastel tokens, pin
262 // animations). Same `filemtime` cache-bust posture as the other
263 // fast-iterating feature stylesheets above.
264 $notes_css = DESKTOP_MODE_DIR . 'assets/css/notes.css';
265 wp_register_style(
266 'desktop-mode-notes',
267 DESKTOP_MODE_URL . 'assets/css/notes.css',
268 array( 'desktop-mode-variables', 'dashicons' ),
269 file_exists( $notes_css ) ? (string) filemtime( $notes_css ) : $version
270 );
271
272 // Scripts.
273 //
274 // `wp-hooks` — the shell exposes a WordPress-style filter/action
275 // API (`window.wp.hooks`) to third-party plugins.
276 // `wp-i18n` — the TS `__()` / `_x()` / `sprintf()` wrappers in
277 // `src/i18n.ts` delegate to `window.wp.i18n` for translation
278 // lookups. Both handles are core-shipped but only pre-enqueued
279 // when Gutenberg-adjacent deps pull them in, so we list them
280 // explicitly to guarantee load order.
281 wp_register_script(
282 'desktop-mode',
283 DESKTOP_MODE_URL . 'assets/js/desktop' . $suffix . '.js',
284 // `heartbeat` + `jquery` — the recycle-bin badge module
285 // (loaded as part of this bundle) opts into the WordPress
286 // Heartbeat API so the count tile / desktop-icon badge
287 // stays in sync even when the bin window is closed.
288 array( 'wp-hooks', 'wp-i18n', 'heartbeat', 'jquery' ),
289 $built_version( 'assets/js/desktop' . $suffix . '.js' ),
290 // Footer + defer: the shell boots on DOMContentLoaded anyway,
291 // so deferring frees the parser instead of blocking at the
292 // footer print point. Both inline payloads attached to this
293 // handle (`__desktopModeMenuCommands`, the jazz-quote version
294 // stamp) are `'before'`-position, which WP keeps as blocking
295 // inline ahead of a deferred tag — order is preserved. On WP
296 // < 6.3 the array collapses to a truthy `$in_footer`, same as
297 // before.
298 array(
299 'in_footer' => true,
300 'strategy' => 'defer',
301 )
302 );
303
304 // `desktop-mode-iframe-bridge` — opt-in iframe-side bridge that
305 // provides `wp.desktop.iframe.publish/subscribe/onConnection/
306 // requestConnection` to any same-origin iframe that enqueues it.
307 // Same code is also injected inline by the chromeless bridge
308 // (so chromeless wp-admin pages don't need a separate enqueue)
309 // and auto-injected when a native window opts in via
310 // `iframeContent: { bridge: true }`. Plugins targeting their
311 // own iframe pages just enqueue this handle.
312 wp_register_script(
313 'desktop-mode-iframe-bridge',
314 DESKTOP_MODE_URL . 'assets/js/iframe-bridge' . $suffix . '.js',
315 array(),
316 $built_version( 'assets/js/iframe-bridge' . $suffix . '.js' ),
317 true
318 );
319
320 // `desktop-mode-gutenberg-drop-receiver` — iframe-side bundle
321 // enqueued only on the Block Editor screens (`post.php` /
322 // `post-new.php`) for desktop-mode users. Listens for
323 // `desktop-mode-drop` postMessages from the parent shell (see
324 // `src/drag/iframe-drop-targets.ts`) and inserts the matching
325 // Gutenberg block via `wp.data.dispatch('core/block-editor')`.
326 // Depends on `wp-blocks` + `wp-data` so the editor stores are
327 // guaranteed enqueued before the receiver runs its first message
328 // handler.
329 wp_register_script(
330 'desktop-mode-gutenberg-drop-receiver',
331 DESKTOP_MODE_URL . 'assets/js/gutenberg-drop-receiver' . $suffix . '.js',
332 array( 'wp-blocks', 'wp-data' ),
333 $built_version( 'assets/js/gutenberg-drop-receiver' . $suffix . '.js' ),
334 true
335 );
336
337 // `desktop-mode-recycle-bin` — small bundle for the Recycle Bin
338 // native window. Lazy-loaded by the native-window sync the first
339 // time the bin opens; registers a render callback on
340 // `window.desktopModeNativeWindows['desktop-mode-recycle-bin']`.
341 $recycle_bin_js = DESKTOP_MODE_DIR . 'assets/js/recycle-bin' . $suffix . '.js';
342 wp_register_script(
343 'desktop-mode-recycle-bin',
344 DESKTOP_MODE_URL . 'assets/js/recycle-bin' . $suffix . '.js',
345 // `heartbeat` + `jquery` — the bin opts in to the WordPress
346 // Heartbeat API while its window is open as the catch-all
347 // real-time channel for deletes that don't render an admin
348 // footer (REST/AJAX/other tabs/WP-CLI). See
349 // `src/recycle-bin/realtime.ts` for the subscriber.
350 array( 'wp-i18n', 'heartbeat', 'jquery' ),
351 file_exists( $recycle_bin_js ) ? (string) filemtime( $recycle_bin_js ) : $version,
352 true
353 );
354 wp_set_script_translations(
355 'desktop-mode-recycle-bin',
356 'desktop-mode',
357 DESKTOP_MODE_DIR . 'languages'
358 );
359
360 // `desktop-mode-games` — bundle for the Games hub native window
361 // (launcher grid, scoreboard, challenges client). Lazy-loaded by
362 // the native-window sync the first time the hub opens; registers a
363 // render callback on
364 // `window.desktopModeNativeWindows['desktop-mode-games']`.
365 // `heartbeat` + `jquery` — the challenges client rides the
366 // WordPress Heartbeat bus for live delivery.
367 $games_js = DESKTOP_MODE_DIR . 'assets/js/games' . $suffix . '.js';
368 wp_register_script(
369 'desktop-mode-games',
370 DESKTOP_MODE_URL . 'assets/js/games' . $suffix . '.js',
371 array( 'wp-i18n', 'heartbeat', 'jquery' ),
372 file_exists( $games_js ) ? (string) filemtime( $games_js ) : $version,
373 true
374 );
375 wp_set_script_translations(
376 'desktop-mode-games',
377 'desktop-mode',
378 DESKTOP_MODE_DIR . 'languages'
379 );
380
381 // `desktop-mode-game-inkfall` — the Inkfall game bundle. Loaded
382 // lazily by the games framework on first launch; publishes the
383 // game def on `window.desktopModeGames.inkfall`.
384 $game_inkfall_js = DESKTOP_MODE_DIR . 'assets/js/game-inkfall' . $suffix . '.js';
385 wp_register_script(
386 'desktop-mode-game-inkfall',
387 DESKTOP_MODE_URL . 'assets/js/game-inkfall' . $suffix . '.js',
388 array( 'wp-i18n' ),
389 file_exists( $game_inkfall_js ) ? (string) filemtime( $game_inkfall_js ) : $version,
390 true
391 );
392 wp_set_script_translations(
393 'desktop-mode-game-inkfall',
394 'desktop-mode',
395 DESKTOP_MODE_DIR . 'languages'
396 );
397
398 // `desktop-mode-game-alphabet-soup` — the Alphabet Soup game
399 // bundle. Loaded lazily by the games framework on first launch;
400 // publishes the game def on
401 // `window.desktopModeGames['alphabet-soup']`.
402 $game_alphabet_soup_js = DESKTOP_MODE_DIR . 'assets/js/game-alphabet-soup' . $suffix . '.js';
403 wp_register_script(
404 'desktop-mode-game-alphabet-soup',
405 DESKTOP_MODE_URL . 'assets/js/game-alphabet-soup' . $suffix . '.js',
406 array( 'wp-i18n' ),
407 file_exists( $game_alphabet_soup_js ) ? (string) filemtime( $game_alphabet_soup_js ) : $version,
408 true
409 );
410 wp_set_script_translations(
411 'desktop-mode-game-alphabet-soup',
412 'desktop-mode',
413 DESKTOP_MODE_DIR . 'languages'
414 );
415
416 // `desktop-mode-posts-window` — small bundle for the native Posts
417 // window. Lazy-loaded by the native-window sync the first time the
418 // window opens (via the dock-click swap when the user opts in);
419 // registers a render callback on
420 // `window.desktopModeNativeWindows['desktop-mode-posts']`.
421 $posts_window_js = DESKTOP_MODE_DIR . 'assets/js/posts-window' . $suffix . '.js';
422 wp_register_script(
423 'desktop-mode-posts-window',
424 DESKTOP_MODE_URL . 'assets/js/posts-window' . $suffix . '.js',
425 array( 'wp-i18n' ),
426 file_exists( $posts_window_js ) ? (string) filemtime( $posts_window_js ) : $version,
427 true
428 );
429 wp_set_script_translations(
430 'desktop-mode-posts-window',
431 'desktop-mode',
432 DESKTOP_MODE_DIR . 'languages'
433 );
434
435 // `desktop-mode-plugins-window` — small bundle for the native
436 // Plugins window. Lazy-loaded by the native-window sync the first
437 // time the window opens (via the dock-click swap when the user
438 // opts in); registers a render callback on
439 // `window.desktopModeNativeWindows['desktop-mode-plugins']`.
440 $plugins_window_js = DESKTOP_MODE_DIR . 'assets/js/plugins-window' . $suffix . '.js';
441 wp_register_script(
442 'desktop-mode-plugins-window',
443 DESKTOP_MODE_URL . 'assets/js/plugins-window' . $suffix . '.js',
444 array( 'wp-i18n' ),
445 file_exists( $plugins_window_js ) ? (string) filemtime( $plugins_window_js ) : $version,
446 true
447 );
448 wp_set_script_translations(
449 'desktop-mode-plugins-window',
450 'desktop-mode',
451 DESKTOP_MODE_DIR . 'languages'
452 );
453
454 // `desktop-mode-comments-window` — small bundle for the native
455 // Comments window. Lazy-loaded by the native-window sync the first
456 // time the window opens (via the dock-click swap when the user
457 // opts in); registers a render callback on
458 // `window.desktopModeNativeWindows['desktop-mode-comments']`.
459 $comments_window_js = DESKTOP_MODE_DIR . 'assets/js/comments-window' . $suffix . '.js';
460 wp_register_script(
461 'desktop-mode-comments-window',
462 DESKTOP_MODE_URL . 'assets/js/comments-window' . $suffix . '.js',
463 array( 'wp-i18n' ),
464 file_exists( $comments_window_js ) ? (string) filemtime( $comments_window_js ) : $version,
465 true
466 );
467 wp_set_script_translations(
468 'desktop-mode-comments-window',
469 'desktop-mode',
470 DESKTOP_MODE_DIR . 'languages'
471 );
472
473 // `desktop-mode-animated-logo-wallpaper` — built-in PixiJS canvas
474 // wallpaper, moved out of `desktop.min.js` in 0.8.4. The wallpaper
475 // `server-sync` loads this handle when the user selects the
476 // `wp-animated-logo` wallpaper (or opens OS Settings → Wallpaper
477 // and the picker pulls every registered canvas def in). The
478 // bundle's only side effect is publishing the `WallpaperDef` on
479 // `window.desktopModeWallpapers['wp-animated-logo']`.
480 $animated_logo_js = DESKTOP_MODE_DIR . 'assets/js/animated-logo-wallpaper' . $suffix . '.js';
481 wp_register_script(
482 'desktop-mode-animated-logo-wallpaper',
483 DESKTOP_MODE_URL . 'assets/js/animated-logo-wallpaper' . $suffix . '.js',
484 array( 'wp-hooks' ),
485 file_exists( $animated_logo_js ) ? (string) filemtime( $animated_logo_js ) : $version,
486 true
487 );
488
489 // `desktop-mode-snow-wallpaper` — built-in PixiJS canvas
490 // wallpaper: snowfall that accumulates on window tops and melts
491 // away. Same lazy-load path as the animated logo: the wallpaper
492 // `server-sync` injects this handle when the user selects the
493 // `wp-snow` wallpaper (or opens OS Settings → Wallpaper and the
494 // picker pulls the def in). The bundle's only side effect is
495 // publishing the `WallpaperDef` on
496 // `window.desktopModeWallpapers['wp-snow']`.
497 $snow_js = DESKTOP_MODE_DIR . 'assets/js/snow-wallpaper' . $suffix . '.js';
498 wp_register_script(
499 'desktop-mode-snow-wallpaper',
500 DESKTOP_MODE_URL . 'assets/js/snow-wallpaper' . $suffix . '.js',
501 array( 'wp-hooks', 'wp-i18n' ),
502 file_exists( $snow_js ) ? (string) filemtime( $snow_js ) : $version,
503 true
504 );
505 wp_set_script_translations(
506 'desktop-mode-snow-wallpaper',
507 'desktop-mode',
508 DESKTOP_MODE_DIR . 'languages'
509 );
510
511 // `desktop-mode-ai-assistant` — AI Copilot spotlight overlay,
512 // moved out of `desktop.min.js` in 0.8.4. The main bundle ships a
513 // stub matching the public `wp.desktop.ai` contract; the stub
514 // `<script>`-injects this handle the first time the user opens
515 // the assistant (Cmd+K or admin-bar button).
516 $ai_assistant_js = DESKTOP_MODE_DIR . 'assets/js/ai-assistant' . $suffix . '.js';
517 wp_register_script(
518 'desktop-mode-ai-assistant',
519 DESKTOP_MODE_URL . 'assets/js/ai-assistant' . $suffix . '.js',
520 array( 'wp-hooks', 'wp-i18n' ),
521 file_exists( $ai_assistant_js ) ? (string) filemtime( $ai_assistant_js ) : $version,
522 true
523 );
524 wp_set_script_translations(
525 'desktop-mode-ai-assistant',
526 'desktop-mode',
527 DESKTOP_MODE_DIR . 'languages'
528 );
529
530 // Wire the translation bundle to this script handle. WP looks
531 // for `languages/desktop-mode-{locale}-desktop-mode.json` and
532 // injects its `locale_data` into `wp.i18n` just before the
533 // script runs — so every `__()` call resolves to the right
534 // language without any runtime fetch.
535 wp_set_script_translations(
536 'desktop-mode',
537 'desktop-mode',
538 DESKTOP_MODE_DIR . 'languages'
539 );
540 }
541 add_action( 'init', 'desktop_mode_register_assets' );
542