Skip to content

Commit

Permalink
Annotation API (#7718)
Browse files Browse the repository at this point in the history
* Implement annotations

Integrate Annotation API with Format API

Keep annotations when applying formatting

Change parameters for toTree after rebasing on master

Fix unit test

Add files after `npm run docs:build`

Only re-render one block when adding annotation

Do this by optimizing the annotations state to keep track of which
annotations belongs to which block. This information can then be used
in the selector to return the same reference if the annotations for a
block haven't changed.

Add unit tests for annotations reducer

Also remove MOVE_ANNOTATION action.

Rename block to blockClientId

Remove block ID from RichText documentation

It is not relevant at that level.

Change block annotation to use selector property

Build docs

Fix low hanging fruit

Add annotation support for block attributes

Rename blockAttribute to richTextIdentifier

Simplify annotations to only `start` and `end`

Revert changes to to-tree

Add annotation support to lists and headings

Build docs

Move the applyAnnotations call into formatToValue

Refactor applyAnnotation to only loop once

Add basic e2e test for annotation API

Disallow invalid annotation ranges in state

Drop general block annotation className

Add assertions for annotations behavior

Add selector to retrieve currently selected text

Move removeAnnotations to valueToFormat

Move applyAnnotations and removeAnnotations

They don't use `this`, so it is only fair.

Fix failing tests

Build docs

Move annotations state to @wordpress/annotations

* Mark Annotation API as experimental

* Move annotation format to annotation package

* Move annotation application to annotation package

* Remove getCurrentRichTextSelection selector

* Remove richText selection remnants

* Remove annotations from rich-text docs

* Remove duplicate props

* Move block annotation to annotations package

* Properly define dependencies

* Add _experimental flag to e2e tests

* Remove wp-annotations as a dependency of wp-editor

* Fix spacing

* Annotations: Add missing uuid dependency

* Annotations: Fix DocBlock inconsistency

* Annotations: Add missing Lodash dependency

* Annotations: Fix up a few more DocBlocks

* Annotations: Add a basic description for package

* Docs: Include Annotations document in root manifest

* Update dependencies
  • Loading branch information
atimmer authored and youknowriad committed Nov 9, 2018
1 parent 660e46e commit 52a3375
Show file tree
Hide file tree
Showing 31 changed files with 894 additions and 14 deletions.
1 change: 1 addition & 0 deletions docs/data/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Data Module Reference

- [**core**: WordPress Core Data](../../docs/data/data-core.md)
- [**core/annotations**: Annotations](../../docs/data/data-core-annotations.md)
- [**core/blocks**: Block Types Data](../../docs/data/data-core-blocks.md)
- [**core/editor**: The Editor’s Data](../../docs/data/data-core-editor.md)
- [**core/edit-post**: The Editor’s UI Data](../../docs/data/data-core-edit-post.md)
Expand Down
84 changes: 84 additions & 0 deletions docs/data/data-core-annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# **core/annotations**: Annotations

## Selectors

### __experimentalGetAnnotationsForBlock

Returns the annotations for a specific client ID.

*Parameters*

* state: Editor state.
* clientId: The ID of the block to get the annotations for.

### __experimentalGetAnnotationsForRichText

Returns the annotations that apply to the given RichText instance.

Both a blockClientId and a richTextIdentifier are required. This is because
a block might have multiple `RichText` components. This does mean that every
block needs to implement annotations itself.

*Parameters*

* state: Editor state.
* blockClientId: The client ID for the block.
* richTextIdentifier: Unique identifier that identifies the given RichText.

*Returns*

All the annotations relevant for the `RichText`.

### __experimentalGetAnnotations

Returns all annotations in the editor state.

*Parameters*

* state: Editor state.

*Returns*

All annotations currently applied.

## Actions

### __experimentalAddAnnotation

Adds an annotation to a block.

The `block` attribute refers to a block ID that needs to be annotated.
`isBlockAnnotation` controls whether or not the annotation is a block
annotation. The `source` is the source of the annotation, this will be used
to identity groups of annotations.

The `range` property is only relevant if the selector is 'range'.

*Parameters*

* annotation: The annotation to add.
* blockClientId: The blockClientId to add the annotation to.
* richTextIdentifier: Identifier for the RichText instance the annotation applies to.
* range: The range at which to apply this annotation.
* range.start: The offset where the annotation should start.
* range.end: The offset where the annotation should end.
* string: [selector="range"] The way to apply this annotation.
* string: [source="default"] The source that added the annotation.
* string: [id=uuid()] The ID the annotation should have.
Generates a UUID by default.

### __experimentalRemoveAnnotation

Removes an annotation with a specific ID.

*Parameters*

* annotationId: The annotation to remove.

### __experimentalRemoveAnnotationsBySource

Removes all annotations of a specific source.

*Parameters*

* source: The source to remove.
55 changes: 55 additions & 0 deletions docs/extensibility/annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Annotations

**Note: This API is experimental, that means it is subject to non-backward compatible changes or removal in any future version.**

Annotations are a way to highlight a specific piece in a Gutenberg post. Examples of this include commenting on a piece of text and spellchecking. Both can use the annotations API to mark a piece of text.

## API

To see the API for yourself the easiest way is to have a block that is at least 200 characters long without formatting and putting the following in the console:

```js
wp.data.dispatch( 'core/annotations' ).addAnnotation( {
source: "my-annotations-plugin",
blockClientId: wp.data.select( 'core/editor' ).getBlockOrder()[0],
richTextIdentifier: "content",
range: {
start: 50,
end: 100,
},
} );
```

The start and the end of the range should be calculated based only on the text of the relevant `RichText`. For example, in the following HTML position 0 will refer to the position before the capital S:

```html
<strong>Strong text</strong>
```

To help with determining the correct positions, the `wp.richText.create` method can be used. This will split a piece of HTML into text and formats.

All available properties can be found in the API documentation of the `addAnnotation` action.

## Block annotation

It is also possible to annotate a block completely. In that case just provide the `selector` property and set it to `block`. The default `selector` is `range`, which can be used for text annotation.

```js
wp.data.dispatch( 'core/annotations' ).addAnnotation( {
source: "my-annotations-plugin",
blockClientId: wp.data.select( 'core/editor' ).getBlockOrder()[0],
selector: "block",
} );
```

This doesn't provide any styling out of the box, so you have to provide some CSS to make sure your annotation is shown:

```css
.is-annotated-by-my-annotations-plugin {
outline: 1px solid black;
}
```

## Text annotation

The text annotation is controlled by the `start` and `end` properties. Simple `start` and `end` properties don't work for HTML, so these properties are assumed to be offsets within the `rich-text` internal structure. For simplicity you can think about this as if all HTML would be stripped out and then you calculate the `start` and the `end` of the annotation.
18 changes: 18 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/extensibility/autocomplete.md",
"parent": "extensibility"
},
{
"title": "Annotations",
"slug": "annotations",
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/extensibility/annotations.md",
"parent": "extensibility"
},
{
"title": "Design",
"slug": "design",
Expand Down Expand Up @@ -257,6 +263,12 @@
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/a11y/README.md",
"parent": "packages"
},
{
"title": "@wordpress/annotations",
"slug": "packages-annotations",
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/annotations/README.md",
"parent": "packages"
},
{
"title": "@wordpress/api-fetch",
"slug": "packages-api-fetch",
Expand Down Expand Up @@ -929,6 +941,12 @@
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/data/data-core.md",
"parent": "data"
},
{
"title": "Annotations",
"slug": "data-core-annotations",
"markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/data/data-core-annotations.md",
"parent": "data"
},
{
"title": "Block Types Data",
"slug": "data-core-blocks",
Expand Down
6 changes: 6 additions & 0 deletions docs/root-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
"markdown_source": "https:\/\/raw.githubusercontent.com\/WordPress\/gutenberg\/master\/docs\/extensibility/autocomplete.md",
"parent": "extensibility"
},
{
"title": "Annotations",
"slug": "annotations",
"markdown_source": "https:\/\/raw.githubusercontent.com\/WordPress\/gutenberg\/master\/docs\/extensibility/annotations.md",
"parent": "extensibility"
},
{
"title": "Design",
"slug": "design",
Expand Down
5 changes: 5 additions & 0 deletions docs/tool/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ module.exports = {
selectors: [ path.resolve( root, 'packages/core-data/src/selectors.js' ) ],
actions: [ path.resolve( root, 'packages/core-data/src/actions.js' ) ],
},
'core/annotations': {
title: 'Annotations',
selectors: [ path.resolve( root, 'packages/annotations/src/store/selectors.js' ) ],
actions: [ path.resolve( root, 'packages/annotations/src/store/actions.js' ) ],
},
'core/blocks': {
title: 'Block Types Data',
selectors: [ path.resolve( root, 'packages/blocks/src/store/selectors.js' ) ],
Expand Down
7 changes: 7 additions & 0 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,13 @@ function gutenberg_register_scripts_and_styles() {
)
)
);
gutenberg_override_script(
'wp-annotations',
gutenberg_url( 'build/annotations/index.js' ),
array( 'wp-polyfill', 'wp-data', 'wp-rich-text', 'wp-hooks', 'wp-i18n' ),
filemtime( gutenberg_dir_path() . 'build/annotations/index.js' ),
true
);
gutenberg_override_script(
'wp-core-data',
gutenberg_url( 'build/core-data/index.js' ),
Expand Down
29 changes: 24 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"dependencies": {
"@wordpress/a11y": "file:packages/a11y",
"@wordpress/annotations": "file:packages/annotations",
"@wordpress/api-fetch": "file:packages/api-fetch",
"@wordpress/autop": "file:packages/autop",
"@wordpress/blob": "file:packages/blob",
Expand Down Expand Up @@ -106,7 +107,7 @@
"stylelint": "9.5.0",
"stylelint-config-wordpress": "13.1.0",
"symlink-or-copy": "1.2.0",
"uuid": "3.1.0",
"uuid": "3.3.2",
"webpack": "4.8.3",
"webpack-bundle-analyzer": "3.0.2",
"webpack-cli": "2.1.3",
Expand Down
1 change: 1 addition & 0 deletions packages/annotations/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
5 changes: 5 additions & 0 deletions packages/annotations/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 1.0.0 (unreleased)

### New Features

- Implement annotations API in the editor.
15 changes: 15 additions & 0 deletions packages/annotations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Annotations

Annotate content in the Gutenberg editor.

## Installation

Install the module

```bash
npm install @wordpress/annotations --save
```

_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._

## Usage
35 changes: 35 additions & 0 deletions packages/annotations/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@wordpress/annotations",
"version": "1.0.0-beta1",
"description": "Annotate content in the Gutenberg editor.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"keywords": [
"wordpress",
"annotations"
],
"homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/annotations/README.md",
"repository": {
"type": "git",
"url": "https://github.com/WordPress/gutenberg.git"
},
"bugs": {
"url": "https://github.com/WordPress/gutenberg/issues"
},
"main": "build/index.js",
"module": "build-module/index.js",
"react-native": "src/index",
"dependencies": {
"@babel/runtime": "^7.0.0",
"@wordpress/data": "file:../data",
"@wordpress/hooks": "file:../hooks",
"@wordpress/i18n": "file:../i18n",
"@wordpress/rich-text": "file:../rich-text",
"lodash": "^4.17.10",
"rememo": "^3.0.0",
"uuid": "^3.3.2"
},
"publishConfig": {
"access": "public"
}
}
25 changes: 25 additions & 0 deletions packages/annotations/src/block/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* WordPress dependencies
*/
import { addFilter } from '@wordpress/hooks';
import { withSelect } from '@wordpress/data';

/**
* Adds annotation className to the block-list-block component.
*
* @param {Object} OriginalComponent The original BlockListBlock component.
* @return {Object} The enhanced component.
*/
const addAnnotationClassName = ( OriginalComponent ) => {
return withSelect( ( select, { clientId } ) => {
const annotations = select( 'core/annotations' ).__experimentalGetAnnotationsForBlock( clientId );

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

addFilter( 'editor.BlockListBlock', 'core/annotations', addAnnotationClassName );
Loading

0 comments on commit 52a3375

Please sign in to comment.