From 6ad9a2582735a32a4fdfbe50c9c5c527a520adf0 Mon Sep 17 00:00:00 2001 From: Max Schmeling Date: Wed, 11 Mar 2026 23:24:36 -0500 Subject: [PATCH] Post Locking: Replace exclusive lock UI with collaborative editing indicator when RTC is enabled. When real-time collaboration is enabled, the post list no longer shows exclusive lock semantics (padlock icon, user avatar, "{user} is currently editing") since multiple users can collaboratively edit the same post. Instead: - Shows generic "Currently being edited" text without user-specific info. - Uses `wp-collaborative-editing` CSS class instead of `wp-locked`, which preserves checkbox and row action visibility (no exclusive lock restrictions). - Changes the "Edit" row action text to "Join" for locked posts. - Heartbeat responses send generic text with a `collaborative` flag. - Injects `_wpCollaborationEnabled` into `inline-edit-post` script so the heartbeat JS handler applies the correct class on dynamic lock updates. When RTC is disabled, classic lock behavior is completely unchanged. Backports https://github.com/WordPress/gutenberg/pull/76322 to core. Props pkevan. See #75313. --- src/js/_enqueues/admin/inline-edit-post.js | 14 ++++++---- src/wp-admin/css/list-tables.css | 4 +++ .../includes/class-wp-posts-list-table.php | 25 ++++++++++++----- src/wp-admin/includes/misc.php | 28 ++++++++++++------- src/wp-includes/collaboration.php | 9 +++--- 5 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/js/_enqueues/admin/inline-edit-post.js b/src/js/_enqueues/admin/inline-edit-post.js index 6f313fc0558c7..5e0eb2746a206 100644 --- a/src/js/_enqueues/admin/inline-edit-post.js +++ b/src/js/_enqueues/admin/inline-edit-post.js @@ -613,18 +613,20 @@ $( function() { wp.heartbeat.interval( 10 ); } }).on( 'heartbeat-tick.wp-check-locked-posts', function( e, data ) { - var locked = data['wp-check-locked-posts'] || {}; + var locked = data['wp-check-locked-posts'] || {}, + isRtc = window._wpCollaborationEnabled, + lockedClass = isRtc ? 'wp-collaborative-editing' : 'wp-locked'; $('#the-list tr').each( function(i, el) { var key = el.id, row = $(el), lock_data, avatar; if ( locked.hasOwnProperty( key ) ) { - if ( ! row.hasClass('wp-locked') ) { + if ( ! row.hasClass( lockedClass ) ) { lock_data = locked[key]; row.find('.column-title .locked-text').text( lock_data.text ); row.find('.check-column checkbox').prop('checked', false); - if ( lock_data.avatar_src ) { + if ( ! isRtc && lock_data.avatar_src ) { avatar = $( '', { 'class': 'avatar avatar-18 photo', width: 18, @@ -635,10 +637,10 @@ $( function() { } ); row.find('.column-title .locked-avatar').empty().append( avatar ); } - row.addClass('wp-locked'); + row.addClass( lockedClass ); } - } else if ( row.hasClass('wp-locked') ) { - row.removeClass( 'wp-locked' ).find( '.locked-info span' ).empty(); + } else if ( row.hasClass( lockedClass ) ) { + row.removeClass( lockedClass ).find( '.locked-info span' ).empty(); } }); }).on( 'heartbeat-send.wp-check-locked-posts', function( e, data ) { diff --git a/src/wp-admin/css/list-tables.css b/src/wp-admin/css/list-tables.css index 11edc53e0a1e0..2e04dc5e8ee90 100644 --- a/src/wp-admin/css/list-tables.css +++ b/src/wp-admin/css/list-tables.css @@ -635,6 +635,10 @@ tr.wp-locked .row-actions .trash { display: none; } +.wp-collaborative-editing .locked-info { + display: block; +} + #menu-locations-wrap .widefat { width: 60%; } diff --git a/src/wp-admin/includes/class-wp-posts-list-table.php b/src/wp-admin/includes/class-wp-posts-list-table.php index f9c08ad5c73ee..c9219c0b5a561 100644 --- a/src/wp-admin/includes/class-wp-posts-list-table.php +++ b/src/wp-admin/includes/class-wp-posts-list-table.php @@ -1119,10 +1119,15 @@ public function column_title( $post ) { $lock_holder = wp_check_post_lock( $post->ID ); if ( $lock_holder ) { - $lock_holder = get_userdata( $lock_holder ); - $locked_avatar = get_avatar( $lock_holder->ID, 18 ); - /* translators: %s: User's display name. */ - $locked_text = esc_html( sprintf( __( '%s is currently editing' ), $lock_holder->display_name ) ); + if ( get_option( 'wp_enable_real_time_collaboration' ) ) { + $locked_avatar = ''; + $locked_text = esc_html__( 'Currently being edited' ); + } else { + $lock_holder = get_userdata( $lock_holder ); + $locked_avatar = get_avatar( $lock_holder->ID, 18 ); + /* translators: %s: User's display name. */ + $locked_text = esc_html( sprintf( __( '%s is currently editing' ), $lock_holder->display_name ) ); + } } else { $locked_avatar = ''; $locked_text = ''; @@ -1427,7 +1432,11 @@ public function single_row( $post, $level = 0 ) { $lock_holder = wp_check_post_lock( $post->ID ); if ( $lock_holder ) { - $classes .= ' wp-locked'; + if ( get_option( 'wp_enable_real_time_collaboration' ) ) { + $classes .= ' wp-collaborative-editing'; + } else { + $classes .= ' wp-locked'; + } } if ( $post->post_parent ) { @@ -1481,12 +1490,14 @@ protected function handle_row_actions( $item, $column_name, $primary ) { $title = _draft_or_post_title(); if ( $can_edit_post && 'trash' !== $post->post_status ) { + $is_rtc_locked = get_option( 'wp_enable_real_time_collaboration' ) && wp_check_post_lock( $post->ID ); + $actions['edit'] = sprintf( '%s', get_edit_post_link( $post->ID ), /* translators: %s: Post title. */ - esc_attr( sprintf( __( 'Edit “%s”' ), $title ) ), - __( 'Edit' ) + esc_attr( sprintf( $is_rtc_locked ? __( 'Join editing “%s”' ) : __( 'Edit “%s”' ), $title ) ), + $is_rtc_locked ? __( 'Join' ) : __( 'Edit' ) ); /** diff --git a/src/wp-admin/includes/misc.php b/src/wp-admin/includes/misc.php index f60e1aedf037a..8d0b94795b5c7 100644 --- a/src/wp-admin/includes/misc.php +++ b/src/wp-admin/includes/misc.php @@ -1133,7 +1133,8 @@ function _customizer_mobile_viewport_meta( $viewport_meta ) { * @return array The Heartbeat response. */ function wp_check_locked_posts( $response, $data, $screen_id ) { - $checked = array(); + $checked = array(); + $is_rtc_enabled = (bool) get_option( 'wp_enable_real_time_collaboration' ); if ( array_key_exists( 'wp-check-locked-posts', $data ) && is_array( $data['wp-check-locked-posts'] ) ) { foreach ( $data['wp-check-locked-posts'] as $key ) { @@ -1149,15 +1150,22 @@ function wp_check_locked_posts( $response, $data, $screen_id ) { $user = get_userdata( $user_id ); if ( $user && current_user_can( 'edit_post', $post_id ) ) { - $send = array( - 'name' => $user->display_name, - /* translators: %s: User's display name. */ - 'text' => sprintf( __( '%s is currently editing' ), $user->display_name ), - ); - - if ( get_option( 'show_avatars' ) ) { - $send['avatar_src'] = get_avatar_url( $user->ID, array( 'size' => 18 ) ); - $send['avatar_src_2x'] = get_avatar_url( $user->ID, array( 'size' => 36 ) ); + if ( $is_rtc_enabled ) { + $send = array( + 'text' => __( 'Currently being edited' ), + 'collaborative' => true, + ); + } else { + $send = array( + 'name' => $user->display_name, + /* translators: %s: User's display name. */ + 'text' => sprintf( __( '%s is currently editing' ), $user->display_name ), + ); + + if ( get_option( 'show_avatars' ) ) { + $send['avatar_src'] = get_avatar_url( $user->ID, array( 'size' => 18 ) ); + $send['avatar_src_2x'] = get_avatar_url( $user->ID, array( 'size' => 36 ) ); + } } $checked[ $key ] = $send; diff --git a/src/wp-includes/collaboration.php b/src/wp-includes/collaboration.php index 6fdbe2889ba7a..bf30c60314344 100644 --- a/src/wp-includes/collaboration.php +++ b/src/wp-includes/collaboration.php @@ -15,10 +15,9 @@ */ function wp_collaboration_inject_setting() { if ( get_option( 'wp_enable_real_time_collaboration' ) ) { - wp_add_inline_script( - 'wp-core-data', - 'window._wpCollaborationEnabled = true;', - 'after' - ); + $inline_script = 'window._wpCollaborationEnabled = true;'; + + wp_add_inline_script( 'wp-core-data', $inline_script, 'after' ); + wp_add_inline_script( 'inline-edit-post', $inline_script, 'before' ); } }