Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
19b54cf
initial function scaffold
mcrisp1972 Aug 16, 2026
ca542fb
notes query now actually queries notes
mcrisp1972 Aug 16, 2026
ee50531
formatting for notes in dashboard
ammist Aug 16, 2026
fea7334
Merge branch 'ticket/65890' of github.com:mcrisp1972/wordpress-develo…
ammist Aug 16, 2026
718a4d3
formatting for notes - first pass
ammist Aug 16, 2026
6488085
cleaned up the styling for the notes activity
ammist Aug 16, 2026
ea90ef6
update naming of the variable and fix formating
aosmichenko Aug 16, 2026
e8f2308
include notes to widget content check
aosmichenko Aug 16, 2026
7fa824a
merge conflict
mcrisp1972 Aug 16, 2026
c104eb1
Update the layout and view
aosmichenko Aug 16, 2026
4acbc98
Dashboard: Cast the note post ID to an integer before use.
adamsilverstein Aug 16, 2026
00da3a9
Merge branch 'ticket/65890' of github.com:mcrisp1972/wordpress-develo…
aosmichenko Aug 16, 2026
d529b4b
remove comments
aosmichenko Aug 16, 2026
cef6eee
Dashboard: Count open notes with a count query per post.
adamsilverstein Aug 16, 2026
7718c53
Dashboard: Add tests for the Notes section rows.
adamsilverstein Aug 16, 2026
8e1a922
Dashboard: Cover the Notes section listing, and store test notes on h…
adamsilverstein Aug 16, 2026
8162132
Dashboard: Date the Notes section by the last reply to a thread.
adamsilverstein Aug 16, 2026
d4d4678
Dashboard: Bound the note paging of the Notes section.
adamsilverstein Aug 17, 2026
c8c9b7f
Dashboard: Lay Notes rows out like the sections above them.
adamsilverstein Aug 17, 2026
f455b6e
Dashboard: Date the Notes rows with a human-readable time difference.
adamsilverstein Aug 31, 2026
38c132f
Merge remote-tracking branch 'origin/trunk' into pr13076
adamsilverstein Aug 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/wp-admin/css/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -937,21 +937,29 @@ body #dashboard-widgets .postbox form .submit {
}

#future-posts ul,
#published-posts ul {
#published-posts ul,
#latest-notes ul {
margin: 8px -12px 0 -12px;
}

#future-posts li,
#published-posts li {
#published-posts li,
#latest-notes li {
display: grid;
grid-template-columns: clamp(160px, calc(2vw + 140px), 200px) auto;
column-gap: 10px;
color: #646970;
padding: 4px 12px;
}

/* Recent Notes rows add a third column for the number of open notes. */
#latest-notes li {
grid-template-columns: clamp(160px, calc(2vw + 140px), 200px) auto max-content;
}

#future-posts li:nth-child(odd),
#published-posts li:nth-child(odd) {
#published-posts li:nth-child(odd),
#latest-notes li:nth-child(odd) {
background-color: #f6f7f7;
}

Expand Down
184 changes: 183 additions & 1 deletion src/wp-admin/includes/dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,47 @@ function _wp_dashboard_recent_comments_row( &$comment, $show_date = true ) {
$GLOBALS['comment'] = null;
}

/**
* Outputs a row for the Recent Notes widget. Notes are implemented as a type of comment.
*
* Each row represents a post with open notes, not an individual note.
*
* @access private
* @since 7.2.0
*
* @param WP_Comment $note The most recent open note of the post.
* @param int $open_notes Optional. Number of open notes the post has. Default 1.
*/
function _wp_dashboard_recent_notes_row( $note, $open_notes = 1 ) {
$note_post_id = (int) $note->comment_post_ID;

$time = strtotime( $note->comment_date_gmt . ' +0000' );

$note_post_title = _draft_or_post_title( $note_post_id );

// The post may have been deleted since the note was queried.
$note_post_link = get_edit_post_link( $note_post_id );

if ( ! $note_post_link ) {
return;
}

printf(
'<li><span>%1$s</span> <a href="%2$s" aria-label="%3$s">%4$s</a> <span class="open-notes-count">%5$s</span></li>',
/* translators: %s: Human-readable time difference. */
sprintf( __( '%s ago' ), human_time_diff( $time ) ),
esc_url( $note_post_link ),
/* translators: %s: Post title. */
esc_attr( sprintf( __( 'Edit &#8220;%s&#8221;' ), $note_post_title ) ),
$note_post_title,
sprintf(
/* translators: %s: Number of open notes. */
_n( '%s open note', '%s open notes', $open_notes ),
number_format_i18n( $open_notes )
)
);
}

/**
* Outputs the Activity widget.
*
Expand Down Expand Up @@ -967,7 +1008,9 @@ function wp_dashboard_site_activity() {

$recent_comments = wp_dashboard_recent_comments();

if ( ! $future_posts && ! $recent_posts && ! $recent_comments ) {
$recent_notes = wp_dashboard_recent_notes();

if ( ! $future_posts && ! $recent_posts && ! $recent_comments && ! $recent_notes ) {
echo '<div class="no-activity">';
echo '<p>' . __( 'No activity yet!' ) . '</p>';
echo '</div>';
Expand Down Expand Up @@ -1152,6 +1195,145 @@ function wp_dashboard_recent_comments( $total_items = 5 ) {
return true;
}

/**
* Show Notes section.
*
* Lists the posts the current user can edit that have at least one open note,
* ordered by the note or reply most recently added to them.
*
* @since 7.2.0
*
* @param int $total_items Optional. Number of posts to display. Default 5.
* @return bool False if no posts with open notes were found. True otherwise.
*/
function wp_dashboard_recent_notes( $total_items = 5 ) {
/*
* Replies are queried alongside the notes that start a thread, so that a
* thread someone replied to counts as added to when it was replied to.
* Resolving a thread approves the note that starts it, while the replies
* stay on hold, so a reply is only recent activity while its thread is.
*/
$notes_query = array(
'type' => 'note',
'status' => 'hold',
'orderby' => 'comment_date_gmt',
'order' => 'DESC',
'number' => $total_items * 5,
'offset' => 0,
// The posts are needed for the capability checks below, the note meta is not.
'update_comment_post_cache' => true,
'update_comment_meta_cache' => false,
);

// The most recent open note or reply of each post, keyed by post ID.
$latest_notes = array();

/*
* Only notes on posts the current user can edit are listed, and a user who
* can edit none of them would otherwise page through every open note on the
* site. Paging stops once a hundred notes per row have been looked at, so
* that the section costs a bounded number of queries however many notes the
* site has.
*/
$max_notes_examined = $total_items * 100;
$notes_examined = 0;

do {
$possible = get_comments( $notes_query );

if ( empty( $possible ) || ! is_array( $possible ) ) {
break;
}

$notes_examined += count( $possible );

/*
* Prime the threads the replies belong to, so that they are not
* queried for one at a time below.
*/
$thread_ids = array();

foreach ( $possible as $note ) {
if ( $note->comment_parent ) {
$thread_ids[] = (int) $note->comment_parent;
}
}

if ( $thread_ids ) {
_prime_comment_caches( array_unique( $thread_ids ), false );
}

foreach ( $possible as $note ) {
$note_post_id = (int) $note->comment_post_ID;

// Only the first note of a post is kept, as notes are ordered by date.
if ( isset( $latest_notes[ $note_post_id ] ) ) {
continue;
}

// A reply is resolved along with the thread it belongs to.
if ( $note->comment_parent ) {
$thread = get_comment( (int) $note->comment_parent );

if ( ! $thread || '0' !== $thread->comment_approved ) {
continue;
}
}

// Notes are only visible to users who can edit the post they belong to.
if ( ! current_user_can( 'edit_post', $note_post_id ) ) {
continue;
}

$latest_notes[ $note_post_id ] = $note;

if ( count( $latest_notes ) === $total_items ) {
break 2;
}
}

$notes_query['offset'] += $notes_query['number'];
$notes_query['number'] = min( $total_items * 10, $max_notes_examined - $notes_examined );
} while ( count( $latest_notes ) < $total_items && $notes_examined < $max_notes_examined );

if ( ! $latest_notes ) {
return false;
}

/*
* Count the open notes of each of the posts about to be displayed. A count
* query per post is cheaper than one query for every open note on them: the
* count is a single value, so the notes themselves are never loaded.
*/
$open_notes = array();

foreach ( array_keys( $latest_notes ) as $note_post_id ) {
$open_notes[ $note_post_id ] = (int) get_comments(
array(
'type' => 'note',
'parent' => 0,
'status' => 'hold',
'post_id' => $note_post_id,
'count' => true,
'orderby' => 'none',
)
);
}

echo '<div id="latest-notes" class="activity-block">';
echo '<h3>' . __( 'Recent Notes' ) . '</h3>';

echo '<ul>';
foreach ( $latest_notes as $note_post_id => $note ) {
_wp_dashboard_recent_notes_row( $note, $open_notes[ $note_post_id ] );
}
echo '</ul>';

echo '</div>';

return true;
}

/**
* Display generic dashboard RSS widget feed.
*
Expand Down
Loading
Loading