Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3c6bfdd
refactor: migrate annotations package from js to ts for type safety
kushagra-goyal-14 Jul 3, 2025
6def6e9
fix: validate start and end properties in applyAnnotations function
kushagra-goyal-14 Jul 3, 2025
412b7d1
Merge branch 'trunk' into try/migrate-annotations-ts
kushagra-goyal-14 Jul 3, 2025
7bd5d8f
Merge branch 'trunk' into try/migrate-annotations-ts
kushagra-goyal-14 Jul 4, 2025
fef7d10
Merge branch 'trunk' into try/migrate-annotations-ts
kushagra-goyal-14 Apr 3, 2026
62e5919
refactor: improve type safety and structure in addAnnotationClassName…
kushagra-goyal-14 Apr 3, 2026
387c195
refactor: fix linting issues
kushagra-goyal-14 Apr 3, 2026
46068be
refactor: improve annotation action handling and update TypeScript co…
kushagra-goyal-14 Apr 6, 2026
bcff9c4
refactor: set interactive and object properties to false in annotatio…
kushagra-goyal-14 Apr 6, 2026
c20ce14
refactor: handle linting issues
kushagra-goyal-14 Apr 6, 2026
c3641a8
refactor: handle linting issues
kushagra-goyal-14 Apr 6, 2026
78dbf91
refactor: rename WPAnnotation to Annotation across the codebase and h…
kushagra-goyal-14 Apr 7, 2026
cc83a0d
refactor: update WPAnnotation to Annotation in annotation removal log…
kushagra-goyal-14 Apr 7, 2026
2a4f942
refactor: fix formatting and linting
kushagra-goyal-14 Apr 7, 2026
373e9de
refactor: update package.json to include types export and simplify An…
kushagra-goyal-14 Apr 9, 2026
37b1dfa
refactor: move types export declaration to maintain consistency in pa…
kushagra-goyal-14 Apr 9, 2026
65df6e9
refactor: update mapValues to handle undefined values and enhance __e…
kushagra-goyal-14 Apr 9, 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
8 changes: 4 additions & 4 deletions docs/reference-guides/data/data-core-annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ This package is still experimental. “Experimental” means this is an early im

## Selectors

<!-- START TOKEN(Autogenerated selectors|../../../packages/annotations/src/store/selectors.js) -->
<!-- START TOKEN(Autogenerated selectors|../../../packages/annotations/src/store/selectors.ts) -->

Nothing to document.

<!-- END TOKEN(Autogenerated selectors|../../../packages/annotations/src/store/selectors.js) -->
<!-- END TOKEN(Autogenerated selectors|../../../packages/annotations/src/store/selectors.ts) -->

## Actions

<!-- START TOKEN(Autogenerated actions|../../../packages/annotations/src/store/actions.js) -->
<!-- START TOKEN(Autogenerated actions|../../../packages/annotations/src/store/actions.ts) -->

Nothing to document.

<!-- END TOKEN(Autogenerated actions|../../../packages/annotations/src/store/actions.js) -->
<!-- END TOKEN(Autogenerated actions|../../../packages/annotations/src/store/actions.ts) -->
40 changes: 0 additions & 40 deletions packages/annotations/src/block/index.js

This file was deleted.

52 changes: 52 additions & 0 deletions packages/annotations/src/block/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* External dependencies
*/
import type { ComponentType } from 'react';

/**
* WordPress dependencies
*/
import { addFilter } from '@wordpress/hooks';
import { withSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store } from '../store';
import type { WPAnnotation } from '../types';

interface BlockListBlockProps {
clientId: string;
className?: string;
[ key: string ]: unknown;
}

/**
* Adds annotation className to the block-list-block component.
*
* @param OriginalComponent The original BlockListBlock component.
* @return The enhanced component.
*/
const addAnnotationClassName = ( OriginalComponent: ComponentType< any > ) => {
return withSelect( ( select, ownProps ) => {
const { clientId, className } = ownProps as BlockListBlockProps;
const annotations: WPAnnotation[] =
select( store ).__experimentalGetAnnotationsForBlock( clientId );

return {
className: annotations
.map( ( annotation ) => {
return 'is-annotated-by-' + annotation.source;
} )
.concat( className || '' )
.filter( Boolean )
.join( ' ' ),
};
} )( OriginalComponent );
};

addFilter(
'editor.BlockListBlock',
'core/annotations',
addAnnotationClassName
);
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,36 @@
*/
import { __ } from '@wordpress/i18n';
import { applyFormat, removeFormat } from '@wordpress/rich-text';
import type { RichTextValue } from '@wordpress/rich-text';

const FORMAT_NAME = 'core/annotation';

const ANNOTATION_ATTRIBUTE_PREFIX = 'annotation-text-';
/**
* Internal dependencies
*/
import { STORE_NAME } from '../store/constants';
import type { WPAnnotation, AnnotationFormat } from '../types';

const FORMAT_NAME = 'core/annotation';

const ANNOTATION_ATTRIBUTE_PREFIX = 'annotation-text-';

/**
* Applies given annotations to the given record.
*
* @param {Object} record The record to apply annotations to.
* @param {Array} annotations The annotation to apply.
* @return {Object} A record with the annotations applied.
* @param record The record to apply annotations to.
* @param annotations The annotation to apply.
* @return A record with the annotations applied.
*/
export function applyAnnotations( record, annotations = [] ) {
export function applyAnnotations(
record: RichTextValue,
annotations: WPAnnotation[] = []
): RichTextValue {
annotations.forEach( ( annotation ) => {
let { start, end } = annotation;

if ( typeof start !== 'number' || typeof end !== 'number' ) {
return;
}

if ( start > record.text.length ) {
start = record.text.length;
}
Expand All @@ -42,7 +52,7 @@ export function applyAnnotations( record, annotations = [] ) {
className,
id,
},
},
} as any,
start,
end
);
Expand All @@ -54,21 +64,23 @@ export function applyAnnotations( record, annotations = [] ) {
/**
* Removes annotations from the given record.
*
* @param {Object} record Record to remove annotations from.
* @return {Object} The cleaned record.
* @param record Record to remove annotations from.
* @return The cleaned record.
*/
export function removeAnnotations( record ) {
export function removeAnnotations( record: RichTextValue ): RichTextValue {
return removeFormat( record, 'core/annotation', 0, record.text.length );
}

/**
* Retrieves the positions of annotations inside an array of formats.
*
* @param {Array} formats Formats with annotations in there.
* @return {Object} ID keyed positions of annotations.
* @param formats Formats with annotations in there.
* @return ID keyed positions of annotations.
*/
function retrieveAnnotationPositions( formats ) {
const positions = {};
function retrieveAnnotationPositions(
formats: any[][]
): Record< string, { start: number; end?: number } > {
const positions: Record< string, { start: number; end?: number } > = {};

formats.forEach( ( characterFormats, i ) => {
characterFormats = characterFormats || [];
Expand Down Expand Up @@ -98,17 +110,27 @@ function retrieveAnnotationPositions( formats ) {
/**
* Updates annotations in the state based on positions retrieved from RichText.
*
* @param {Array} annotations The annotations that are currently applied.
* @param {Array} positions The current positions of the given annotations.
* @param {Object} actions
* @param {Function} actions.removeAnnotation Function to remove an annotation from the state.
* @param {Function} actions.updateAnnotationRange Function to update an annotation range in the state.
* @param annotations The annotations that are currently applied.
* @param positions The current positions of the given annotations.
* @param actions
* @param actions.removeAnnotation Function to remove an annotation from the state.
* @param actions.updateAnnotationRange Function to update an annotation range in the state.
*/
function updateAnnotationsWithPositions(
annotations,
positions,
{ removeAnnotation, updateAnnotationRange }
) {
annotations: WPAnnotation[],
positions: Record< string, { start: number; end?: number } >,
{
removeAnnotation,
updateAnnotationRange,
}: {
removeAnnotation: ( annotationId: string ) => void;
updateAnnotationRange: (
annotationId: string,
start: number,
end: number
) => void;
}
): void {
annotations.forEach( ( currentAnnotation ) => {
const position = positions[ currentAnnotation.id ];
// If we cannot find an annotation, delete it.
Expand All @@ -120,17 +142,22 @@ function updateAnnotationsWithPositions(
}

const { start, end } = currentAnnotation;
if ( start !== position.start || end !== position.end ) {
if (
typeof start === 'number' &&
typeof end === 'number' &&
( start !== position.start ||
end !== ( position.end ?? position.start ) )
) {
updateAnnotationRange(
currentAnnotation.id,
position.start,
position.end
position.end ?? position.start
);
}
} );
}

export const annotation = {
export const annotation: AnnotationFormat = {
name: FORMAT_NAME,
title: __( 'Annotation' ),
tagName: 'mark',
Expand All @@ -139,13 +166,15 @@ export const annotation = {
className: 'class',
id: 'id',
},
edit() {
interactive: false,
object: false,
edit: () => {
return null;
},
__experimentalGetPropsForEditableTreePreparation(
__experimentalGetPropsForEditableTreePreparation: (
select,
{ richTextIdentifier, blockClientId }
) {
) => {
return {
annotations: select(
STORE_NAME
Expand All @@ -155,26 +184,32 @@ export const annotation = {
),
};
},
__experimentalCreatePrepareEditableTree( { annotations } ) {
__experimentalCreatePrepareEditableTree: ( { annotations } ) => {
return ( formats, text ) => {
if ( annotations.length === 0 ) {
return formats;
}

let record = { formats, text };
let record: RichTextValue = {
formats,
text,
replacements: [],
start: 0,
end: 0,
};
record = applyAnnotations( record, annotations );
return record.formats;
};
},
__experimentalGetPropsForEditableTreeChangeHandler( dispatch ) {
__experimentalGetPropsForEditableTreeChangeHandler: ( dispatch ) => {
return {
removeAnnotation:
dispatch( STORE_NAME ).__experimentalRemoveAnnotation,
updateAnnotationRange:
dispatch( STORE_NAME ).__experimentalUpdateAnnotationRange,
};
},
__experimentalCreateOnChangeEditableValue( props ) {
__experimentalCreateOnChangeEditableValue: ( props ) => {
return ( formats ) => {
const positions = retrieveAnnotationPositions( formats );
const { removeAnnotation, updateAnnotationRange, annotations } =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ import { annotation } from './annotation';

const { name, ...settings } = annotation;

registerFormatType( name, settings );
registerFormatType( name, settings as any );
File renamed without changes.
Loading
Loading