diff --git a/projects/plugins/jetpack/changelog/add-proofread-ignore-button b/projects/plugins/jetpack/changelog/add-proofread-ignore-button
new file mode 100644
index 0000000000000..0c6924aa76a8d
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/add-proofread-ignore-button
@@ -0,0 +1,4 @@
+Significance: minor
+Type: other
+
+Add ignore button for AI Proofread
diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/index.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/index.ts
index 368c928eed4d4..de7662258558d 100644
--- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/index.ts
+++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/index.ts
@@ -1,3 +1,7 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
/**
* Features
*/
@@ -15,14 +19,17 @@ const features: Array< BreveFeature > = [
config: COMPLEX_WORDS,
highlight: complexWords,
dictionary: dicComplex,
+ description: __( 'Use simple, direct words.', 'jetpack' ),
},
{
config: LONG_SENTENCES,
highlight: longSentences,
+ description: __( 'Long sentences are hard to read.', 'jetpack' ),
},
{
config: UNCONFIDENT_WORDS,
highlight: unconfidentWords,
+ description: __( 'Remove weasel words.', 'jetpack' ),
},
];
diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/highlight.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/highlight.ts
index ce1a2060f71d7..52c032bad86ca 100644
--- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/highlight.ts
+++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/highlight.ts
@@ -14,6 +14,13 @@ export type HighlightProps = {
type: string;
indexes: Array< HighlightedText >;
attributes?: { [ key: string ]: string };
+ ignored: Array< string >;
+};
+
+type HighlightData = {
+ start: number;
+ end: number;
+ id: string;
};
const applyHighlightFormat = ( {
@@ -21,6 +28,7 @@ const applyHighlightFormat = ( {
type,
indexes,
attributes = {},
+ ignored = [],
}: HighlightProps ): RichTextValue => {
let newContent = content;
@@ -28,10 +36,12 @@ const applyHighlightFormat = ( {
newContent = indexes
.map( highlightedText => {
const { startIndex, endIndex, text } = highlightedText;
- return { start: startIndex, end: endIndex, text } as RichTextValue;
+ const id = md5( `${ text }-${ startIndex }-${ endIndex }` ).toString();
+ return { start: startIndex, end: endIndex, id } as HighlightData;
} )
- .reduce( ( acc: RichTextValue, { start, end, text }: RichTextValue ) => {
- const currentAttr = { ...attributes, 'data-id': md5( `${ text }-${ start }-${ end }` ) };
+ .filter( data => ! ignored.includes( data?.id ) )
+ .reduce( ( acc: RichTextValue, { start, end, id }: HighlightData ) => {
+ const currentAttr = { ...attributes, 'data-id': id };
const format = {
type,
@@ -45,6 +55,12 @@ const applyHighlightFormat = ( {
return newContent;
};
-export default function highlight( { content, type, indexes, attributes }: HighlightProps ) {
- return applyHighlightFormat( { indexes, content, type, attributes } );
+export default function highlight( {
+ content,
+ type,
+ indexes,
+ attributes,
+ ignored,
+}: HighlightProps ) {
+ return applyHighlightFormat( { indexes, content, type, attributes, ignored } );
}
diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/index.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/index.tsx
index 923c174224142..9cf54fc0339b1 100644
--- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/index.tsx
+++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/index.tsx
@@ -43,7 +43,7 @@ type CoreBlockEditorSelect = {
// Setup the Breve highlights
export default function Highlight() {
- const { setPopoverHover, setSuggestions, invalidateSuggestions } = useDispatch(
+ const { setPopoverHover, setSuggestions, invalidateSuggestions, ignoreSuggestion } = useDispatch(
'jetpack/ai-breve'
) as BreveDispatch;
@@ -55,54 +55,64 @@ export default function Highlight() {
return { getBlock: selector.getBlock };
}, [] );
- const { anchor, virtual, popoverOpen, id, feature, blockId, title, loading, suggestions } =
- useSelect( select => {
- const breveSelect = select( 'jetpack/ai-breve' ) as BreveSelect;
-
- // Popover
- const isPopoverHover = breveSelect.isPopoverHover();
- const isHighlightHover = breveSelect.isHighlightHover();
-
- // Anchor data
- const { target: anchorEl, virtual: virtualEl } = breveSelect.getPopoverAnchor() ?? {
- target: null,
- virtual: null,
- };
- const anchorFeature = anchorEl?.getAttribute?.( 'data-type' ) as string;
- const anchorId = anchorEl?.getAttribute?.( 'data-id' ) as string;
- const anchorBlockId = anchorEl?.getAttribute?.( 'data-block' ) as string;
-
- const config = features?.find?.( ftr => ftr.config.name === anchorFeature )?.config ?? {
- name: '',
- title: '',
- };
-
- // Suggestions
- const loadingSuggestions = breveSelect.getSuggestionsLoading( {
- feature: anchorFeature,
- id: anchorId,
- blockId: anchorBlockId,
- } );
+ const {
+ anchor,
+ virtual,
+ popoverOpen,
+ id,
+ feature,
+ blockId,
+ title,
+ loading,
+ suggestions,
+ description,
+ } = useSelect( select => {
+ const breveSelect = select( 'jetpack/ai-breve' ) as BreveSelect;
+
+ // Popover
+ const isPopoverHover = breveSelect.isPopoverHover();
+ const isHighlightHover = breveSelect.isHighlightHover();
+
+ // Anchor data
+ const defaultAnchor = { target: null, virtual: null };
+ const { target: anchorEl, virtual: virtualEl } =
+ breveSelect.getPopoverAnchor() ?? defaultAnchor;
+ const anchorFeature = anchorEl?.getAttribute?.( 'data-type' ) as string;
+ const anchorId = anchorEl?.getAttribute?.( 'data-id' ) as string;
+ const anchorBlockId = anchorEl?.getAttribute?.( 'data-block' ) as string;
+
+ // Feature data
+ const featureData = features?.find?.( ftr => ftr.config.name === anchorFeature );
+ const featureConfig = featureData?.config ?? { name: '', title: '' };
+ const featureDescription = featureData?.description ?? '';
+ const featureTitle = featureConfig?.title ?? '';
+
+ // Suggestions
+ const loadingSuggestions = breveSelect.getSuggestionsLoading( {
+ feature: anchorFeature,
+ id: anchorId,
+ blockId: anchorBlockId,
+ } );
- const suggestionsData = breveSelect.getSuggestions( {
- feature: anchorFeature,
- id: anchorId,
- blockId: anchorBlockId,
- } );
+ const suggestionsData = breveSelect.getSuggestions( {
+ feature: anchorFeature,
+ id: anchorId,
+ blockId: anchorBlockId,
+ } );
- return {
- config,
- anchor: anchorEl,
- virtual: virtualEl,
- title: config?.title,
- feature: anchorFeature,
- id: anchorId,
- blockId: anchorBlockId,
- popoverOpen: isHighlightHover || isPopoverHover,
- loading: loadingSuggestions,
- suggestions: suggestionsData,
- };
- }, [] );
+ return {
+ title: featureTitle,
+ description: featureDescription,
+ anchor: anchorEl,
+ virtual: virtualEl,
+ feature: anchorFeature,
+ id: anchorId,
+ blockId: anchorBlockId,
+ popoverOpen: isHighlightHover || isPopoverHover,
+ loading: loadingSuggestions,
+ suggestions: suggestionsData,
+ };
+ }, [] );
const isPopoverOpen = popoverOpen && virtual;
const hasSuggestions = Boolean( suggestions?.suggestion );
@@ -187,6 +197,15 @@ export default function Highlight() {
} );
};
+ const handleIgnoreSuggestion = () => {
+ ignoreSuggestion( feature, blockId, id );
+ setPopoverHover( false );
+ tracks.recordEvent( 'jetpack_ai_breve_ignore', {
+ feature: BREVE_FEATURE_NAME,
+ type: feature,
+ } );
+ };
+
return (
<>
{ isPopoverOpen && (
@@ -205,32 +224,44 @@ export default function Highlight() {
'has-suggestions': hasSuggestions,
} ) }
>
-
-
-
{ title }
+
+
+ { ! hasSuggestions && (
+
+ { loading ? (
+
+
+
+ ) : (
+
+ ) }
+
+ ) }
- { hasSuggestions ? (
-
+
+ { hasSuggestions && (
-
- { __( 'Click on the suggestion to insert it.', 'jetpack' ) }
-
-
- ) : (
-
- { loading ? (
-
-
-
+ ) }
+
+ { hasSuggestions ? (
+ __( 'Click on the suggestion to insert it.', 'jetpack' )
) : (
-
+ <>
+ { description }
+
+ >
) }
- ) }
+
) }
@@ -249,51 +280,53 @@ export function registerBreveHighlights() {
interactive: false,
edit: () => {},
...configSettings,
- __experimentalGetPropsForEditableTreePreparation() {
+ __experimentalGetPropsForEditableTreePreparation( _select, { blockClientId } ) {
+ const { getIgnoredSuggestions, isFeatureEnabled, isProofreadEnabled } = globalSelect(
+ 'jetpack/ai-breve'
+ ) as BreveSelect;
+
return {
- isProofreadEnabled: (
- globalSelect( 'jetpack/ai-breve' ) as BreveSelect
- ).isProofreadEnabled(),
- isFeatureEnabled: ( globalSelect( 'jetpack/ai-breve' ) as BreveSelect ).isFeatureEnabled(
- config.name
- ),
+ isProofreadEnabled: isProofreadEnabled(),
+ isFeatureEnabled: isFeatureEnabled( config.name ),
+ ignored: getIgnoredSuggestions( { feature: config.name, blockId: blockClientId } ),
};
},
__experimentalCreatePrepareEditableTree(
- { isProofreadEnabled, isFeatureEnabled },
+ { isProofreadEnabled, isFeatureEnabled, ignored },
{ blockClientId, richTextIdentifier }
) {
return ( formats: Array< RichTextFormatList >, text: string ) => {
+ const { getBlock } = globalSelect( 'core/block-editor' ) as CoreBlockEditorSelect;
+ const { getBlockMd5 } = globalSelect( 'jetpack/ai-breve' ) as BreveSelect;
+ const { invalidateSuggestions, setBlockMd5 } = globalDispatch(
+ 'jetpack/ai-breve'
+ ) as BreveDispatch;
+
const record = { formats, text } as RichTextValue;
const type = formatName;
+ // Ignored suggestions
+ let ignoredList = ignored;
+
// Has to be defined here, as adding it to __experimentalGetPropsForEditableTreePreparation
// causes an issue with the block inserter. ref p1721746774569699-slack-C054LN8RNVA
- const currentMd5 = ( globalSelect( 'jetpack/ai-breve' ) as BreveSelect ).getBlockMd5(
- formatName,
- blockClientId
- );
+ const currentMd5 = getBlockMd5( formatName, blockClientId );
if ( text && isProofreadEnabled && isFeatureEnabled ) {
- const block = globalSelect( 'core/block-editor' ).getBlock( blockClientId );
- const blockContent = getBlockContent( block );
+ const block = getBlock( blockClientId );
+ // Only use block content for complex blocks like tables
+ const blockContent = richTextIdentifier === 'content' ? text : getBlockContent( block );
const textMd5 = md5( blockContent ).toString();
if ( currentMd5 !== textMd5 ) {
- ( globalDispatch( 'jetpack/ai-breve' ) as BreveDispatch ).invalidateSuggestions(
- type,
- blockClientId
- );
-
- ( globalDispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setBlockMd5(
- type,
- blockClientId,
- textMd5
- );
+ ignoredList = [];
+ invalidateSuggestions( type, blockClientId );
+ setBlockMd5( type, blockClientId, textMd5 );
}
const highlights = featureHighlight( text );
const applied = highlight( {
+ ignored: ignoredList,
content: record,
type,
indexes: highlights,
diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/style.scss b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/style.scss
index 39b84c95806df..ec39a3bb92795 100644
--- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/style.scss
+++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/style.scss
@@ -20,70 +20,86 @@
.highlight-content {
align-items: center;
display: flex;
- justify-content: space-between;
- gap: 32px;
+ flex-direction: column;
&.has-suggestions {
align-items: flex-start;
- flex-direction: column;
padding-top: 8px;
gap: 8px;
}
- .title,
- .action {
- padding: 8px 12px;
- }
-
- .title {
+ .header-container {
display: flex;
- align-items: center;
- justify-content: flex-start;
- gap: 8px;
- font-size: 14px;
- font-weight: 500;
- white-space: nowrap;
+ width: 100%;
+ justify-content: space-between;
+ gap: 32px;
- .color {
- width: 10px;
- height: 10px;
- border-radius: 50%;
- @include features-colors( ( 'background-color' ) );
+ .title,
+ .action {
+ padding: 8px 12px;
}
- }
- .action {
- font-size: 13px;
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: 4px;
+ .title {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 8px;
+ font-size: 14px;
+ font-weight: 500;
+ white-space: nowrap;
- .components-button {
- padding-top: 0px;
- padding-bottom: 0px;
- padding-right: 0px;
- height: unset;
+ .color {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ @include features-colors( ( 'background-color' ) );
+ }
}
- & > svg,
- path {
- fill: #0277a8;
+ .action {
+ font-size: 13px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 4px;
+
+ .suggest.components-button {
+ padding-top: 0px;
+ padding-bottom: 0px;
+ padding-right: 0px;
+ height: unset;
+
+ & > svg,
+ path {
+ fill: #0277a8;
+ }
+ }
}
}
- .suggestion-container {
+ .bottom-container {
display: flex;
flex-direction: column;
border-top: 1px solid #dcdcde;
+ width: 100%;
.helper {
- padding: 8px;
+ padding: 4px 8px;
background-color: #f6f7f7;
white-space: nowrap;
color: #646970;
- margin: 0 4px 4px 4px;
+ margin: 4px;
border-radius: 4px;
+ font-size: 12px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 8px;
+
+ .components-button {
+ padding: 0px;
+ color: #646970;
+ }
}
.components-button.is-tertiary {
diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/actions.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/actions.ts
index 4f6de14523e49..bbe40db68d434 100644
--- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/actions.ts
+++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/actions.ts
@@ -66,6 +66,15 @@ export function invalidateSuggestions( feature: string, blockId: string ) {
};
}
+export function ignoreSuggestion( feature: string, blockId: string, id: string ) {
+ return {
+ type: 'IGNORE_SUGGESTION',
+ feature,
+ blockId,
+ id,
+ };
+}
+
export function setSuggestions( {
anchor,
id,
diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/reducer.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/reducer.ts
index 1c96c035e18e1..922b91e9a9ae7 100644
--- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/reducer.ts
+++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/reducer.ts
@@ -178,6 +178,19 @@ export function suggestions(
},
};
}
+
+ case 'IGNORE_SUGGESTION': {
+ return {
+ ...current,
+ [ feature ]: {
+ ...( current[ feature ] ?? {} ),
+ [ blockId ]: {
+ ...currentBlock,
+ ignored: [ ...( currentBlock.ignored ?? [] ), id ],
+ },
+ },
+ };
+ }
}
return state;
diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts
index ade9a7fe14c37..3753f1cd89733 100644
--- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts
+++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts
@@ -54,3 +54,10 @@ export function getSuggestions(
) {
return state.suggestions?.[ feature ]?.[ blockId ]?.[ id ]?.suggestions;
}
+
+export function getIgnoredSuggestions(
+ state: BreveState,
+ { feature, blockId }: { feature: string; blockId: string }
+) {
+ return state.suggestions?.[ feature ]?.[ blockId ]?.ignored;
+}
diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts
index 6e25ceeec4b9e..922b1d4807a80 100644
--- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts
+++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts
@@ -64,6 +64,13 @@ export type BreveSelect = {
html: string;
suggestion: string;
};
+ getIgnoredSuggestions: ( {
+ feature,
+ blockId,
+ }: {
+ feature: string;
+ blockId: string;
+ } ) => Array< string >;
};
export type BreveDispatch = {
@@ -73,6 +80,7 @@ export type BreveDispatch = {
toggleProofread: ( force?: boolean ) => void;
toggleFeature: ( feature: string, force?: boolean ) => void;
invalidateSuggestions: ( feature: string, blockId: string ) => void;
+ ignoreSuggestion: ( feature: string, blockId: string, id: string ) => void;
setBlockMd5: ( feature: string, blockId: string, md5: string ) => void;
setSuggestions: ( suggestions: {
anchor: Anchor[ 'target' ];
@@ -97,6 +105,7 @@ export type BreveFeature = {
config: BreveFeatureConfig;
highlight: ( text: string ) => Array< HighlightedText >;
dictionary?: { [ key: string ]: string };
+ description: string;
};
export type HighlightedText = {