Skip to content

Commit

Permalink
AI Assistant: Add ambiguous words to proofread features list (#38292)
Browse files Browse the repository at this point in the history
* fix ts issues and add types

* add ambiguous words feature

* fix menu spacing

* changelog
  • Loading branch information
dhasilva authored Jul 11, 2024
1 parent f673540 commit 8ff0828
Show file tree
Hide file tree
Showing 19 changed files with 225 additions and 68 deletions.
4 changes: 4 additions & 0 deletions projects/plugins/jetpack/changelog/add-proofread-weasel-words
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: other

AI Assistant: Add ambiguous words to proofread features list
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

.jetpack-ai-proofread {
margin-bottom: 24px;

.components-checkbox-control {
color: #757575;

Expand All @@ -22,4 +23,10 @@
color: #757575;
margin-left: 12px;
}

.feature-checkboxes-container {
display: flex;
flex-direction: column;
gap: 12px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,17 @@ const Controls = ( { blocks, disabledFeatures } ) => {
onChange={ handleAiFeedbackToggle }
label={ __( 'Show suggestions', 'jetpack' ) }
/>
{ features.map( feature => (
<CheckboxControl
data-type={ feature.config.name }
key={ feature.config.name }
label={ feature.config.title }
checked={ ! disabledFeatures.includes( feature.config.name ) }
onChange={ handleToggleFeature( feature.config.name ) }
/>
) ) }
<div className="feature-checkboxes-container">
{ features.map( feature => (
<CheckboxControl
data-type={ feature.config.name }
key={ feature.config.name }
label={ feature.config.title }
checked={ ! disabledFeatures.includes( feature.config.name ) }
onChange={ handleToggleFeature( feature.config.name ) }
/>
) ) }
</div>
</BaseControl>
</PanelRow>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import phrases from '../features/complex-words/phrases';
import weaselWords from '../features/weasel-words/words';
import { escapeRegExp } from '../utils/escapeRegExp';
import adjectives from './adjectives';
import adverbs from './adverbs';
import weaselWords from './weaselWords';

const config = {
dictionaries: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
@use 'sass:string';

$features-colors: (
'complex-words': rgba( 251, 192, 45, 1 ),
'complex-words': rgba( 240, 184, 73, 1 ),
'ambiguous-words': rgba( 0, 175, 82, 1 ),
);

@mixin properties( $feature, $color, $properties ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Internal dependencies
*/
import { escapeRegExp } from '../../utils/escapeRegExp';
import weaselWords from './words';
/**
* Types
*/
import type { BreveFeatureConfig, HighlightedWord } from '../../types';

export const AMBIGUOUS_WORDS: BreveFeatureConfig = {
name: 'ambiguous-words',
title: 'Ambiguous words',
tagName: 'span',
className: 'has-proofread-highlight--ambiguous-words',
};

const list = new RegExp( `\\b(${ weaselWords.map( escapeRegExp ).join( '|' ) })\\b`, 'gi' );

export default function ambiguousWords( text: string ): Array< HighlightedWord > {
const matches = text.matchAll( list );
const highlightedWords: Array< HighlightedWord > = [];

for ( const match of matches ) {
const word = match[ 0 ].trim();
highlightedWords.push( {
word,
startIndex: match.index,
endIndex: match.index + word.length,
} );
}

return highlightedWords;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,36 @@
*/
import { escapeRegExp } from '../../utils/escapeRegExp';
import phrases from './phrases';
/**
* Types
*/
import type { BreveFeatureConfig, HighlightedWord } from '../../types';

export const COMPLEX_WORDS = {
export const COMPLEX_WORDS: BreveFeatureConfig = {
name: 'complex-words',
title: 'Complex words',
tagName: 'span',
className: 'has-proofread-highlight',
className: 'has-proofread-highlight--complex-words',
};

export default function complexWords( text ) {
const list = new RegExp(
`\\b(${ Object.keys( phrases ).map( escapeRegExp ).join( '|' ) })\\b`,
'gi'
);
const list = new RegExp(
`\\b(${ Object.keys( phrases ).map( escapeRegExp ).join( '|' ) })\\b`,
'gi'
);

export default function complexWords( text: string ): Array< HighlightedWord > {
const matches = text.matchAll( list );
const words = [];
const highlightedWords: Array< HighlightedWord > = [];

for ( const match of matches ) {
const word = match[ 0 ].trim();
words.push( {
highlightedWords.push( {
word,
suggestion: phrases[ word ],
startIndex: match.index,
endIndex: match.index + word.length,
} );
}

return words;
return highlightedWords;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,24 @@ import { dispatch } from '@wordpress/data';
*/
import getContainer from './container';
import features from './index';
/**
* Types
*/
import type { BreveDispatch } from '../types';

let timeout;
let timeout: number;

function handleMouseEnter( e ) {
function handleMouseEnter( e: React.MouseEvent ) {
e.stopPropagation();
clearTimeout( timeout );
// eslint-disable-next-line @typescript-eslint/no-explicit-any
( dispatch( 'jetpack/ai-breve' ) as any ).setHighlightHover( true );
// eslint-disable-next-line @typescript-eslint/no-explicit-any
( dispatch( 'jetpack/ai-breve' ) as any ).setPopoverAnchor( e.target );
( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( true );
( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setPopoverAnchor( e.target );
}

function handleMouseLeave( e ) {
function handleMouseLeave( e: React.MouseEvent ) {
e.stopPropagation();
timeout = setTimeout( () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
( dispatch( 'jetpack/ai-breve' ) as any ).setHighlightHover( false );
( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( false );
}, 100 );
}

Expand All @@ -33,7 +34,8 @@ export default function registerEvents( clientId: string ) {
const block = container?.querySelector?.( `#${ id }` );

features.forEach( ( { config } ) => {
const items = block?.querySelectorAll?.( `[data-type='${ config.name }']` );
const items = block?.querySelectorAll?.( `[data-type='${ config.name }']` ) || [];

if ( items?.length > 0 ) {
items.forEach( highlightEl => {
highlightEl?.removeEventListener?.( 'mouseenter', handleMouseEnter );
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
/**
* Features
*/
import ambiguousWords, { AMBIGUOUS_WORDS } from './ambiguous-words';
import complexWords, { COMPLEX_WORDS } from './complex-words';
/**
* Types
*/
import type { BreveFeature } from '../types';

// Breve Highlights Features
export default [
const features: Array< BreveFeature > = [
{
config: COMPLEX_WORDS,
highlight: complexWords,
},
{
config: AMBIGUOUS_WORDS,
highlight: ambiguousWords,
},
];

export default features;
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,34 @@ import { applyFormat } from '@wordpress/rich-text';
*/
import type { RichTextFormat, RichTextValue } from '@wordpress/rich-text/build-types/types';

const applyHighlightFormat = ( { content, type, indexes, attributes = {} } ): RichTextValue => {
const applyHighlightFormat = ( {
content,
type,
indexes,
attributes = {},
}: {
content: RichTextValue;
type: string;
indexes: Array< { startIndex: number; endIndex: number } >;
attributes: { [ key: string ]: string };
} ): RichTextValue => {
let newContent = content;

if ( indexes.length > 0 ) {
newContent = indexes.reduce( ( acc, { startIndex, endIndex } ) => {
const format = {
type,
attributes,
} as RichTextFormat;
newContent = indexes.reduce(
(
acc: RichTextValue,
{ startIndex, endIndex }: { startIndex: number; endIndex: number }
) => {
const format = {
type,
attributes,
} as RichTextFormat;

return applyFormat( acc, format, startIndex, endIndex );
}, content );
return applyFormat( acc, format, startIndex, endIndex );
},
content
);
}

return newContent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Button, Popover } from '@wordpress/components';
import { select as globalSelect, useDispatch, useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { registerFormatType, removeFormat, RichTextValue } from '@wordpress/rich-text';
import React from 'react';
/**
* Internal dependencies
*/
Expand All @@ -13,27 +14,30 @@ import features from '../features';
import registerEvents from '../features/events';
import highlight from './highlight';
import './style.scss';
/**
* Types
*/
import type { BreveSelect } from '../types';
import type { RichTextFormatList } from '@wordpress/rich-text/build-types/types';

// Setup the Breve highlights
export default function Highlight() {
const { setPopoverHover } = useDispatch( 'jetpack/ai-breve' );

const popoverOpen = useSelect( select => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const store = select( 'jetpack/ai-breve' ) as any;
const store = select( 'jetpack/ai-breve' ) as BreveSelect;
const isPopoverHover = store.isPopoverHover();
const isHighlightHover = store.isHighlightHover();
return isHighlightHover || isPopoverHover;
}, [] );

const anchor = useSelect( select => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return ( select( 'jetpack/ai-breve' ) as any ).getPopoverAnchor();
return ( select( 'jetpack/ai-breve' ) as BreveSelect ).getPopoverAnchor();
}, [] );

const isPopoverOpen = popoverOpen && anchor;

const selectedFeatured = anchor ? anchor?.getAttribute?.( 'data-type' ) : null;
const selectedFeatured = anchor ? ( anchor as HTMLElement )?.getAttribute?.( 'data-type' ) : null;

const featureConfig = features?.find?.( feature => feature.config.name === selectedFeatured )
?.config ?? {
Expand Down Expand Up @@ -79,21 +83,28 @@ export default function Highlight() {
}

export function registerBreveHighlights() {
features.forEach( ( { config, highlight: featureHighlight } ) => {
features.forEach( feature => {
const { highlight: featureHighlight, config } = feature;
const { name, ...configSettings } = config;

const settings = {
...configSettings,

__experimentalGetPropsForEditableTreePreparation() {
return {
isProofreadEnabled: globalSelect( 'jetpack/ai-breve' ).isProofreadEnabled(),
isFeatureEnabled: globalSelect( 'jetpack/ai-breve' ).isFeatureEnabled( config.name ),
isProofreadEnabled: (
globalSelect( 'jetpack/ai-breve' ) as BreveSelect
).isProofreadEnabled(),
isFeatureEnabled: ( globalSelect( 'jetpack/ai-breve' ) as BreveSelect ).isFeatureEnabled(
config.name
),
};
},
__experimentalCreatePrepareEditableTree(
{ isProofreadEnabled, isFeatureEnabled },
{ blockClientId }
) {
return ( formats, text ) => {
return ( formats: Array< RichTextFormatList >, text: string ) => {
const record = { formats, text } as RichTextValue;
const type = `jetpack/ai-proofread-${ config.name }`;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import '../features/features.colors';

.has-proofread-highlight {
[class^="has-proofread-highlight"] {
border-bottom: 3px solid;
@include features-colors( ( 'border-bottom-color' ) );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
/**
* Internal dependencies
*/
import Controls from './controls';
import { store } from './store'; // Register the store
/**
* Types
*/
import { BreveControls } from './types';

const Breve = Controls as () => React.JSX.Element;
const Breve = Controls as BreveControls;

export { Breve };
export { default as Highlight, registerBreveHighlights } from './highlight';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ import { select } from '@wordpress/data';

// ACTIONS

export function setHighlightHover( isHover ) {
export function setHighlightHover( isHover: boolean ) {
return {
type: 'SET_HIGHLIGHT_HOVER',
isHover,
};
}

export function setPopoverHover( isHover ) {
export function setPopoverHover( isHover: boolean ) {
return {
type: 'SET_POPOVER_HOVER',
isHover,
};
}

export function setPopoverAnchor( anchor ) {
export function setPopoverAnchor( anchor: HTMLElement | EventTarget ) {
return {
type: 'SET_POPOVER_ANCHOR',
anchor,
Expand Down
Loading

0 comments on commit 8ff0828

Please sign in to comment.