PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
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
← All changes | includes/widgets/widget-drafts.php +67 -59 0.9.81.1.10 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Drafts Widget.
3 + * OpenStation — Drafts Widget.
4 4 *
5 5 * A quick list of the current user's most recently edited draft posts,
6 6 * each a click away from reopening in the editor (the shell's admin-link
7 7 * interceptor turns the row into a native window).
@@ -10,11 +10,11 @@
10 10 * context, scoped to the viewer with `author` — without it an editor
11 11 * or admin would see every draft on the site, not their own).
12 12 * Refresh: every 60 seconds while the tab is visible, plus an
13 13 * immediate refresh when a window closes or blurs.
14 - * Requires: Desktop Mode 0.18.0+ (desktop_mode_register_widget).
14 + * Requires: OpenStation 0.18.0+ (openstation_register_widget).
15 15 *
16 - * @package WPDesktopMode
16 + * @package OpenStation
17 17 */
18 18
19 19 defined( 'ABSPATH' ) || exit;
20 20
@@ -20,46 +20,46 @@
20 20
21 21 /**
22 22 * Register the JS + CSS assets.
23 23 */
24 -function desktop_mode_register_drafts_widget_assets() {
25 - $suffix = desktop_mode_asset_suffix();
26 - $version = defined( 'DESKTOP_MODE_VERSION' ) ? DESKTOP_MODE_VERSION : '0';
24 +function openstation_register_drafts_widget_assets() {
25 + $suffix = openstation_asset_suffix();
26 + $version = defined( 'OPENSTATION_VERSION' ) ? OPENSTATION_VERSION : '0';
27 27
28 - $js_path = DESKTOP_MODE_DIR . 'assets/js/widget-drafts' . $suffix . '.js';
29 - $css_path = DESKTOP_MODE_DIR . 'assets/js/widget-drafts' . $suffix . '.css';
28 + $js_path = OPENSTATION_DIR . 'assets/js/widget-drafts' . $suffix . '.js';
29 + $css_path = OPENSTATION_DIR . 'assets/js/widget-drafts' . $suffix . '.css';
30 30
31 31 wp_register_style(
32 - 'desktop-mode-drafts-widget',
33 - DESKTOP_MODE_URL . 'assets/js/widget-drafts' . $suffix . '.css',
32 + 'os-drafts-widget',
33 + OPENSTATION_URL . 'assets/js/widget-drafts' . $suffix . '.css',
34 34 array(),
35 35 file_exists( $css_path ) ? (string) filemtime( $css_path ) : $version
36 36 );
37 37
38 38 wp_register_script(
39 - 'desktop-mode-drafts-widget',
40 - DESKTOP_MODE_URL . 'assets/js/widget-drafts' . $suffix . '.js',
39 + 'os-drafts-widget',
40 + OPENSTATION_URL . 'assets/js/widget-drafts' . $suffix . '.js',
41 41 array( 'wp-api-fetch' ),
42 42 file_exists( $js_path ) ? (string) filemtime( $js_path ) : $version,
43 43 true
44 44 );
45 45 }
46 -add_action( 'init', 'desktop_mode_register_drafts_widget_assets', 5 );
46 +add_action( 'init', 'openstation_register_drafts_widget_assets', 5 );
47 47
48 48 /**
49 49 * Eagerly enqueue the CSS on shell pages so there is no flash of
50 50 * unstyled content while the lazy JS bundle loads.
51 51 */
52 -function desktop_mode_enqueue_drafts_widget_styles() {
53 - if ( function_exists( 'desktop_mode_is_enabled' ) && ! desktop_mode_is_enabled() ) {
52 +function openstation_enqueue_drafts_widget_styles() {
53 + if ( function_exists( 'openstation_is_enabled' ) && ! openstation_is_enabled() ) {
54 54 return;
55 55 }
56 - if ( function_exists( 'desktop_mode_is_chromeless_request' ) && desktop_mode_is_chromeless_request() ) {
56 + if ( function_exists( 'openstation_is_chromeless_request' ) && openstation_is_chromeless_request() ) {
57 57 return;
58 58 }
59 - wp_enqueue_style( 'desktop-mode-drafts-widget' );
59 + wp_enqueue_style( 'os-drafts-widget' );
60 60 }
61 -add_action( 'admin_enqueue_scripts', 'desktop_mode_enqueue_drafts_widget_styles', 20 );
61 +add_action( 'admin_enqueue_scripts', 'openstation_enqueue_drafts_widget_styles', 20 );
62 62
63 63 /**
64 64 * Register the widget definition.
65 65 *
@@ -66,19 +66,19 @@
66 66 * @return true|WP_Error True on success, `WP_Error` when the registry
67 67 * rejects the entry (e.g. the viewer lacks
68 68 * `edit_posts`), false if the registry is absent.
69 69 */
70 -function desktop_mode_register_drafts_widget() {
71 - if ( ! function_exists( 'desktop_mode_register_widget' ) ) {
70 +function openstation_register_drafts_widget() {
71 + if ( ! function_exists( 'openstation_register_widget' ) ) {
72 72 return false;
73 73 }
74 - return desktop_mode_register_widget(
74 + return openstation_register_widget(
75 75 'desktop-mode/drafts',
76 76 array(
77 77 'label' => __( 'Drafts', 'desktop-mode' ),
78 78 'description' => __( 'Your unfinished posts — click to reopen in the editor.', 'desktop-mode' ),
79 79 'icon' => 'dashicons-edit',
80 - 'script' => 'desktop-mode-drafts-widget',
80 + 'script' => 'os-drafts-widget',
81 81 'movable' => true,
82 82 'resizable' => true,
83 83 'min_width' => 240,
84 84 'min_height' => 180,
@@ -90,9 +90,9 @@
90 90 'capabilities' => array( 'edit_posts' ),
91 91 )
92 92 );
93 93 }
94 -add_action( 'init', 'desktop_mode_register_drafts_widget', 6 );
94 +add_action( 'init', 'openstation_register_drafts_widget', 6 );
95 95
96 96
97 97 /**
98 98 * Register the AI writing-suggestions REST route.
@@ -111,16 +111,16 @@
111 111 * gate here is defence in depth.
112 112 *
113 113 * @return void
114 114 */
115 -function desktop_mode_register_drafts_ai_routes() {
115 +function openstation_register_drafts_ai_routes() {
116 116 register_rest_route(
117 117 'desktop-mode/v1',
118 118 '/draft-suggestions',
119 119 array(
120 120 'methods' => WP_REST_Server::CREATABLE,
121 - 'callback' => 'desktop_mode_rest_draft_suggestions',
122 - 'permission_callback' => 'desktop_mode_rest_draft_suggestions_permission',
121 + 'callback' => 'openstation_rest_draft_suggestions',
122 + 'permission_callback' => 'openstation_rest_draft_suggestions_permission',
123 123 'args' => array(
124 124 'post_id' => array(
125 125 'required' => true,
126 126 'type' => 'integer',
@@ -129,9 +129,9 @@
129 129 ),
130 130 )
131 131 );
132 132 }
133 -add_action( 'rest_api_init', 'desktop_mode_register_drafts_ai_routes' );
133 +add_action( 'rest_api_init', 'openstation_register_drafts_ai_routes' );
134 134
135 135 /**
136 136 * Permission gate: the user can edit the target post, and AI is configured.
137 137 *
@@ -141,9 +141,9 @@
141 141 *
142 142 * @param WP_REST_Request $request Request.
143 143 * @return true|WP_Error
144 144 */
145 -function desktop_mode_rest_draft_suggestions_permission( WP_REST_Request $request ) {
145 +function openstation_rest_draft_suggestions_permission( WP_REST_Request $request ) {
146 146 $post_id = absint( $request['post_id'] );
147 147 if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
148 148 return new WP_Error(
149 149 'rest_forbidden',
@@ -150,11 +150,11 @@
150 150 __( 'You are not allowed to edit this post.', 'desktop-mode' ),
151 151 array( 'status' => rest_authorization_required_code() )
152 152 );
153 153 }
154 - if ( ! function_exists( 'desktop_mode_ai_provider_configured' ) || ! desktop_mode_ai_provider_configured() ) {
154 + if ( ! function_exists( 'openstation_ai_provider_configured' ) || ! openstation_ai_provider_configured() ) {
155 155 return new WP_Error(
156 - 'desktop_mode_ai_unavailable',
156 + 'openstation_ai_unavailable',
157 157 __( 'No AI provider is configured.', 'desktop-mode' ),
158 158 array( 'status' => 503 )
159 159 );
160 160 }
@@ -168,9 +168,9 @@
168 168 *
169 169 * @param WP_Post $post The draft being described.
170 170 * @return string
171 171 */
172 -function desktop_mode_drafts_ai_instructions( WP_Post $post ) {
172 +function openstation_drafts_ai_instructions( WP_Post $post ) {
173 173 $instructions = 'You are a writing assistant for a WordPress author. Given a draft post\'s current title and content, help them finish and file it. Provide: exactly 3 concise, compelling title options (about 70 characters max each); one 1-2 sentence excerpt suitable as the post summary; 3 to 6 lowercase topical tags; 1 to 2 categories (strongly prefer the site\'s existing categories listed above — only propose a new concise name if none fit); and a readiness check.
174 174
175 175 The readiness check MUST be strict and evidence-based. Judge only STRUCTURE and COMPLETENESS: does the draft have a clear introduction, enough substance/depth, at least one concrete example or detail, and a conclusion? The "missing" array lists only what is GENUINELY ABSENT from the text you were given. CRITICAL: never invent, guess, or hallucinate problems. Do NOT claim there are typos, misspellings, or cut-off/incomplete sentences unless you can quote the exact offending text verbatim from the draft — if you are not quoting real text, do not mention it. If the draft already has an intro, body with a concrete detail, and a conclusion and reads as complete, return an EMPTY "missing" array and say it looks ready in the summary.
176 176
@@ -181,9 +181,9 @@
181 181 *
182 182 * @param string $instructions System instruction text.
183 183 * @param WP_Post $post The draft being described.
184 184 */
185 - return (string) apply_filters( 'desktop_mode_drafts_ai_instructions', $instructions, $post );
185 + return (string) apply_filters( 'openstation_drafts_ai_instructions', $instructions, $post );
186 186 }
187 187
188 188 /**
189 189 * JSON schema the model must answer in for draft suggestions.
@@ -190,9 +190,9 @@
190 190 *
191 191 * @param WP_Post $post The draft being described.
192 192 * @return array
193 193 */
194 -function desktop_mode_drafts_ai_schema( WP_Post $post ) {
194 +function openstation_drafts_ai_schema( WP_Post $post ) {
195 195 $schema = array(
196 196 'type' => 'object',
197 197 'additionalProperties' => false,
198 198 'required' => array( 'titles', 'excerpt', 'tags', 'categories', 'readiness' ),
@@ -243,9 +243,9 @@
243 243 *
244 244 * @param array $schema JSON schema.
245 245 * @param WP_Post $post The draft being described.
246 246 */
247 - return (array) apply_filters( 'desktop_mode_drafts_ai_schema', $schema, $post );
247 + return (array) apply_filters( 'openstation_drafts_ai_schema', $schema, $post );
248 248 }
249 249
250 250 /**
251 251 * Build the user-facing prompt body: title, trimmed content, existing terms.
@@ -252,9 +252,9 @@
252 252 *
253 253 * @param WP_Post $post The draft being described.
254 254 * @return string
255 255 */
256 -function desktop_mode_drafts_ai_prompt_text( WP_Post $post ) {
256 +function openstation_drafts_ai_prompt_text( WP_Post $post ) {
257 257 $title = (string) $post->post_title;
258 258 $content = trim( (string) preg_replace( '/\s+/', ' ', wp_strip_all_tags( (string) $post->post_content ) ) );
259 259
260 260 /**
@@ -262,9 +262,9 @@
262 262 *
263 263 * @param int $limit Character limit.
264 264 * @param WP_Post $post The draft being described.
265 265 */
266 - $limit = (int) apply_filters( 'desktop_mode_drafts_ai_content_limit', 4000, $post );
266 + $limit = (int) apply_filters( 'openstation_drafts_ai_content_limit', 4000, $post );
267 267
268 268 // mb_substr so a long draft isn't cut mid-multibyte-character.
269 269 if ( $limit > 0 && mb_strlen( $content ) > $limit ) {
270 270 $content = mb_substr( $content, 0, $limit ) . '…';
@@ -295,12 +295,12 @@
295 295 *
296 296 * @param WP_REST_Request $request Request.
297 297 * @return WP_REST_Response|WP_Error
298 298 */
299 -function desktop_mode_rest_draft_suggestions( WP_REST_Request $request ) {
299 +function openstation_rest_draft_suggestions( WP_REST_Request $request ) {
300 300 if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
301 301 return new WP_Error(
302 - 'desktop_mode_ai_unavailable',
302 + 'openstation_ai_unavailable',
303 303 __( 'AI is not available on this site.', 'desktop-mode' ),
304 304 array( 'status' => 503 )
305 305 );
306 306 }
@@ -317,19 +317,27 @@
317 317 // `generate_text()` with a JSON schema — same call shape the comment
318 318 // scorer uses. The SDK can throw as well as return a WP_Error, so both
319 319 // paths land on the same 502.
320 320 try {
321 - $json = wp_ai_client_prompt( desktop_mode_drafts_ai_prompt_text( $post ) )
322 - ->using_system_instruction( desktop_mode_drafts_ai_instructions( $post ) )
323 - ->as_json_response( desktop_mode_ai_normalize_response_schema( desktop_mode_drafts_ai_schema( $post ) ) )
324 - ->generate_text();
321 + $builder = wp_ai_client_prompt( openstation_drafts_ai_prompt_text( $post ) )
322 + ->using_system_instruction( openstation_drafts_ai_instructions( $post ) )
323 + ->as_json_response( openstation_ai_normalize_response_schema( openstation_drafts_ai_schema( $post ) ) );
324 +
325 + $json = openstation_ai_apply_model_config(
326 + $builder,
327 + array(
328 + 'user_id' => get_current_user_id(),
329 + 'source' => 'widgets/drafts-suggestions',
330 + 'has_schema' => true,
331 + )
332 + )->generate_text();
325 333 } catch ( \Throwable $e ) {
326 - $json = new WP_Error( 'desktop_mode_ai_failed', $e->getMessage() );
334 + $json = new WP_Error( 'openstation_ai_failed', $e->getMessage() );
327 335 }
328 336
329 337 if ( is_wp_error( $json ) ) {
330 338 return new WP_Error(
331 - 'desktop_mode_ai_failed',
339 + 'openstation_ai_failed',
332 340 $json->get_error_message(),
333 341 array( 'status' => 502 )
334 342 );
335 343 }
@@ -336,9 +344,9 @@
336 344
337 345 $data = json_decode( (string) $json, true );
338 346 if ( ! is_array( $data ) ) {
339 347 return new WP_Error(
340 - 'desktop_mode_ai_parse',
348 + 'openstation_ai_parse',
341 349 __( 'The AI response could not be parsed.', 'desktop-mode' ),
342 350 array( 'status' => 502 )
343 351 );
344 352 }
@@ -345,15 +353,15 @@
345 353
346 354 $readiness = isset( $data['readiness'] ) && is_array( $data['readiness'] ) ? $data['readiness'] : array();
347 355
348 356 $suggestions = array(
349 - 'titles' => desktop_mode_drafts_clean_list( isset( $data['titles'] ) ? $data['titles'] : array(), 5 ),
357 + 'titles' => openstation_drafts_clean_list( isset( $data['titles'] ) ? $data['titles'] : array(), 5 ),
350 358 'excerpt' => trim( wp_strip_all_tags( (string) ( isset( $data['excerpt'] ) ? $data['excerpt'] : '' ) ) ),
351 - 'tags' => desktop_mode_drafts_clean_list( isset( $data['tags'] ) ? $data['tags'] : array(), 8 ),
352 - 'categories' => desktop_mode_drafts_clean_list( isset( $data['categories'] ) ? $data['categories'] : array(), 5 ),
359 + 'tags' => openstation_drafts_clean_list( isset( $data['tags'] ) ? $data['tags'] : array(), 8 ),
360 + 'categories' => openstation_drafts_clean_list( isset( $data['categories'] ) ? $data['categories'] : array(), 5 ),
353 361 'readiness' => array(
354 362 'summary' => trim( wp_strip_all_tags( (string) ( isset( $readiness['summary'] ) ? $readiness['summary'] : '' ) ) ),
355 - 'missing' => desktop_mode_drafts_clean_list( isset( $readiness['missing'] ) ? $readiness['missing'] : array(), 5 ),
363 + 'missing' => openstation_drafts_clean_list( isset( $readiness['missing'] ) ? $readiness['missing'] : array(), 5 ),
356 364 ),
357 365 );
358 366
359 367 /**
@@ -364,9 +372,9 @@
364 372 *
365 373 * @param array $suggestions { titles, excerpt, tags, categories, readiness }.
366 374 * @param WP_Post $post The draft the suggestions describe.
367 375 */
368 - $suggestions = (array) apply_filters( 'desktop_mode_drafts_ai_suggestions', $suggestions, $post );
376 + $suggestions = (array) apply_filters( 'openstation_drafts_ai_suggestions', $suggestions, $post );
369 377
370 378 return new WP_REST_Response( $suggestions, 200 );
371 379 }
372 380
@@ -376,9 +384,9 @@
376 384 * @param mixed $list Raw list from the model.
377 385 * @param int $max Maximum entries to keep.
378 386 * @return string[]
379 387 */
380 -function desktop_mode_drafts_clean_list( $list, $max ) {
388 +function openstation_drafts_clean_list( $list, $max ) {
381 389 $out = array();
382 390 foreach ( (array) $list as $item ) {
383 391 if ( ! is_scalar( $item ) ) {
384 392 continue;
@@ -402,16 +410,16 @@
402 410 * AI-gated — this is a plain edit of the user's own draft.
403 411 *
404 412 * @return void
405 413 */
406 -function desktop_mode_register_drafts_apply_route() {
414 +function openstation_register_drafts_apply_route() {
407 415 register_rest_route(
408 416 'desktop-mode/v1',
409 417 '/draft-apply',
410 418 array(
411 419 'methods' => WP_REST_Server::CREATABLE,
412 - 'callback' => 'desktop_mode_rest_draft_apply',
413 - 'permission_callback' => 'desktop_mode_rest_draft_apply_permission',
420 + 'callback' => 'openstation_rest_draft_apply',
421 + 'permission_callback' => 'openstation_rest_draft_apply_permission',
414 422 'args' => array(
415 423 'post_id' => array(
416 424 'required' => true,
417 425 'type' => 'integer',
@@ -430,9 +438,9 @@
430 438 ),
431 439 )
432 440 );
433 441 }
434 -add_action( 'rest_api_init', 'desktop_mode_register_drafts_apply_route' );
442 +add_action( 'rest_api_init', 'openstation_register_drafts_apply_route' );
435 443
436 444 /**
437 445 * Permission gate: the user can edit the target post.
438 446 *
@@ -438,9 +446,9 @@
438 446 *
439 447 * @param WP_REST_Request $request Request.
440 448 * @return true|WP_Error
441 449 */
442 -function desktop_mode_rest_draft_apply_permission( WP_REST_Request $request ) {
450 +function openstation_rest_draft_apply_permission( WP_REST_Request $request ) {
443 451 $post_id = absint( $request['post_id'] );
444 452 if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
445 453 return new WP_Error(
446 454 'rest_forbidden',
@@ -456,9 +464,9 @@
456 464 *
457 465 * @param WP_REST_Request $request Request.
458 466 * @return WP_REST_Response|WP_Error
459 467 */
460 -function desktop_mode_rest_draft_apply( WP_REST_Request $request ) {
468 +function openstation_rest_draft_apply( WP_REST_Request $request ) {
461 469 $post_id = absint( $request['post_id'] );
462 470 $post = get_post( $post_id );
463 471 if ( ! $post instanceof WP_Post ) {
464 472 return new WP_Error(
@@ -487,9 +495,9 @@
487 495 if ( count( $update ) > 1 ) {
488 496 $result = wp_update_post( $update, true );
489 497 if ( is_wp_error( $result ) ) {
490 498 return new WP_Error(
491 - 'desktop_mode_apply_failed',
499 + 'openstation_apply_failed',
492 500 $result->get_error_message(),
493 501 array( 'status' => 500 )
494 502 );
495 503 }
@@ -554,8 +562,8 @@
554 562 * @param int $post_id Post that was updated.
555 563 * @param array $applied Fields written: { title?, excerpt?, tags?, categories? }.
556 564 * @param WP_Post $post The post as it was before the update.
557 565 */
558 - do_action( 'desktop_mode_drafts_suggestion_applied', $post_id, $applied, $post );
566 + do_action( 'openstation_drafts_suggestion_applied', $post_id, $applied, $post );
559 567
560 568 return new WP_REST_Response( array( 'applied' => $applied ), 200 );
561 569 }