Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 12 additions & 1 deletion includes/Experiments/Editorial_Notes/Editorial_Notes.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,22 @@ public function maybe_set_ai_author( $prepared_comment, \WP_REST_Request $reques
*/
public function enqueue_assets(): void {
Asset_Loader::enqueue_script( 'editorial_notes', 'experiments/editorial-notes', array( 'include_core_abilities' => true ) );

/**
* Filters the minimum content length required to enable Editorial notes.
*
* @since x.x.x
*
* @param int $min_content_length The minimum number of characters required. Default 100.
*/
$min_content_length = (int) apply_filters( 'wpai_editorial_notes_min_content_length', 100 );

Asset_Loader::localize_script(
'editorial_notes',
'EditorialNotesData',
array(
'enabled' => $this->is_enabled(),
'enabled' => $this->is_enabled(),
'minContentLength' => $min_content_length,
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,20 @@ import {
* reviewable blocks.
*/
export default function EditorialNotesPlugin() {
const { isReviewing, progress, total, lastRunCount, runReview } =
useEditorialNotes();
const { isReviewing: isReviewingBlock, reviewBlock } = useEditorialBlock();
const {
isReviewing,
progress,
total,
lastRunCount,
runReview,
isContentTooShort,
minContentLength,
} = useEditorialNotes();
const {
isReviewing: isReviewingBlock,
reviewBlock,
isContentTooShort: isBlockReviewDisabled,
} = useEditorialBlock();
const { openGeneralSidebar } = useDispatch( editPostStore );
const openNotesPanel = () =>
openGeneralSidebar?.( 'edit-post/collab-sidebar' );
Expand All @@ -65,10 +76,19 @@ export default function EditorialNotesPlugin() {
const buttonLabel = isReviewing
? reviewingLabel
: __( 'Generate Editorial Notes', 'ai' );
const buttonDescription = __(
'This analyzes the content of this post block-by-block and adds editorial Notes with suggestions on each block.',
'ai'
);
const buttonDescription = isContentTooShort
? sprintf(
/* translators: %d: minimum number of characters required. */
__(
'Editorial Notes will be available when the post content has at least %d characters.',
'ai'
),
minContentLength
)
: __(
'This analyzes the content of this post block-by-block and adds editorial Notes with suggestions on each block.',
'ai'
);

return (
<>
Expand All @@ -80,7 +100,7 @@ export default function EditorialNotesPlugin() {
icon={ commentContent }
onClick={ runReview }
isBusy={ isReviewing }
disabled={ isReviewing }
disabled={ isReviewing || isContentTooShort }
style={ {
justifyContent: 'center',
width: '100%',
Expand Down Expand Up @@ -148,7 +168,9 @@ export default function EditorialNotesPlugin() {
icon={
isReviewingBlock ? <Spinner /> : commentContent
}
disabled={ isReviewingBlock }
disabled={
isReviewingBlock || isBlockReviewDisabled
}
onClick={ () => {
if ( clientId ) {
reviewBlock( clientId );
Expand Down
61 changes: 52 additions & 9 deletions src/experiments/editorial-notes/hooks/useEditorialNotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
/**
* WordPress dependencies
*/
import { dispatch, select } from '@wordpress/data';
import { dispatch, select, useSelect } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { store as coreStore } from '@wordpress/core-data';
import { store as editPostStore } from '@wordpress/edit-post';
import { store as editorStore } from '@wordpress/editor';
import { useState } from '@wordpress/element';
import { __, _n, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { count } from '@wordpress/wordcount';

/**
* Internal dependencies
Expand Down Expand Up @@ -70,6 +71,32 @@ interface NoteRecord {
[ key: string ]: unknown;
}

/**
* Hook for determining whether Review Notes should be available for the
* current post content.
*
* @return Availability state for the Review Notes feature.
*/
function useReviewNotesAvailability(): {
content: string;
minContentLength: number;
isContentTooShort: boolean;
} {
const content =
useSelect( ( selectStore ) => {
return ( selectStore( editorStore ) as any ).getEditedPostContent();
}, [] ) ?? '';
const minContentLength: number =
( window as any ).aiReviewNotesData?.minContentLength ?? 100;

return {
content,
minContentLength,
isContentTooShort:
count( content, 'characters_including_spaces' ) < minContentLength,
};
}

/**
* Reviews a single block and creates/updates a Note if suggestions are found.
*
Expand Down Expand Up @@ -165,18 +192,26 @@ export function useEditorialNotes(): {
progress: number;
total: number;
lastRunCount: number | null;
isContentTooShort: boolean;
minContentLength: number;
runReview: () => Promise< void >;
} {
const [ isReviewing, setIsReviewing ] = useState< boolean >( false );
const [ progress, setProgress ] = useState< number >( 0 );
const [ total, setTotal ] = useState< number >( 0 );
const [ lastRunCount, setLastRunCount ] = useState< number | null >( null );
const { content, isContentTooShort, minContentLength } =
useReviewNotesAvailability();

const runReview = async () => {
if ( ! ensureProvider( NOTICE_ID ) ) {
return;
}

if ( isContentTooShort ) {
return;
}

setIsReviewing( true );
setProgress( 0 );
setTotal( 0 );
Expand All @@ -188,9 +223,6 @@ export function useEditorialNotes(): {
const postId = (
select( editorStore ) as any
).getCurrentPostId() as number;
const content = (
select( editorStore ) as any
).getEditedPostContent() as string;

// Get all blocks and flatten the tree.
const allBlocks = (
Expand Down Expand Up @@ -279,7 +311,15 @@ export function useEditorialNotes(): {
}
};

return { isReviewing, progress, total, lastRunCount, runReview };
return {
isReviewing,
progress,
total,
lastRunCount,
isContentTooShort,
minContentLength,
runReview,
};
}

/**
Expand All @@ -289,11 +329,17 @@ export function useEditorialNotes(): {
*/
export function useEditorialBlock(): {
isReviewing: boolean;
isContentTooShort: boolean;
reviewBlock: ( clientId: string ) => Promise< void >;
} {
const [ isReviewing, setIsReviewing ] = useState< boolean >( false );
const { content, isContentTooShort } = useReviewNotesAvailability();

const reviewBlock = async ( clientId: string ) => {
if ( isContentTooShort ) {
return;
}

setIsReviewing( true );

( dispatch( noticesStore ) as any ).removeNotice(
Expand All @@ -312,9 +358,6 @@ export function useEditorialBlock(): {
const postId = (
select( editorStore ) as any
).getCurrentPostId() as number;
const content = (
select( editorStore ) as any
).getEditedPostContent() as string;

// Fetch fresh note state for this invocation.
const [ pendingNotes, approvedNotes ] = await Promise.all( [
Expand Down Expand Up @@ -379,7 +422,7 @@ export function useEditorialBlock(): {
}
};

return { isReviewing, reviewBlock };
return { isReviewing, isContentTooShort, reviewBlock };
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,46 @@ public function test_ai_note_comment_meta_is_registered() {
$this->assertTrue( $registered['ai_note']['show_in_rest'], 'ai_note meta should have show_in_rest enabled' );
}

/**
* Tests that enqueue_assets() localizes the default minimum content length.
*
* @since x.x.x
*/
public function test_enqueue_assets_localizes_default_min_content_length() {
$GLOBALS['wp_scripts'] = new \WP_Scripts();

$this->experiment->enqueue_assets();

$this->assertTrue( wp_script_is( 'ai_editorial_notes', 'enqueued' ) );
$this->assertStringContainsString(
'"minContentLength":"100"',
(string) wp_scripts()->get_data( 'ai_editorial_notes', 'data' )
);
}

/**
* Tests that enqueue_assets() localizes the filtered minimum content length.
*
* @since x.x.x
*/
public function test_enqueue_assets_localizes_filtered_min_content_length() {
$filter = static function () {
return 250;
};

add_filter( 'wpai_editorial_notes_min_content_length', $filter );
$GLOBALS['wp_scripts'] = new \WP_Scripts();

$this->experiment->enqueue_assets();

remove_filter( 'wpai_editorial_notes_min_content_length', $filter );

$this->assertStringContainsString(
'"minContentLength":"250"',
(string) wp_scripts()->get_data( 'ai_editorial_notes', 'data' )
);
}

// -------------------------------------------------------------------------
// maybe_set_ai_author()
// -------------------------------------------------------------------------
Expand Down
Loading
Loading