Skip to content

Commit

Permalink
Implement a basic text annotation proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
atimmer committed Jul 24, 2018
1 parent f508995 commit 7079812
Show file tree
Hide file tree
Showing 8 changed files with 386 additions and 3 deletions.
23 changes: 22 additions & 1 deletion editor/components/block-settings-menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { castArray } from 'lodash';
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { IconButton, Dropdown, NavigableMenu } from '@wordpress/components';
import { withDispatch } from '@wordpress/data';
import { withSelect, withDispatch } from '@wordpress/data';
import { compose } from '@wordpress/compose';

/**
Expand Down Expand Up @@ -56,6 +56,8 @@ export class BlockSettingsMenu extends Component {
focus,
rootClientId,
isHidden,
isMultiSelecting,
onAnnotate,
} = this.props;
const { isFocused } = this.state;
const blockClientIds = castArray( clientIds );
Expand Down Expand Up @@ -139,6 +141,19 @@ export class BlockSettingsMenu extends Component {
clientIds={ clientIds }
role="menuitem"
/>
{ ! isMultiSelecting &&
<IconButton
key="annotate"
className="editor-block-settings-menu__control"
onClick={ () => {
onAnnotate( firstBlockClientId );
onClose();
} }
icon="admin-generic"
>
{ __( 'Annotate' ) }
</IconButton>
}
</NavigableMenu>
) }
/>
Expand All @@ -149,9 +164,15 @@ export class BlockSettingsMenu extends Component {

export default compose( [
withDeprecatedUniqueId,
withSelect( ( select ) => ( {
isMultiSelecting: select( 'core/editor' ).isMultiSelecting(),
} ) ),
withDispatch( ( dispatch ) => ( {
onSelect( clientId ) {
dispatch( 'core/editor' ).selectBlock( clientId );
},
onAnnotate( clientId ) {
dispatch( 'core/editor' ).addAnnotationOnSelection( clientId );
},
} ) ),
] )( BlockSettingsMenu );
134 changes: 134 additions & 0 deletions editor/components/rich-text/annotations/annotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { Component, Fragment } from '@wordpress/element';
import { isEqual, map } from 'lodash';

class Annotation extends Component {
constructor( props ) {
super( props );

this.state = {
prevAnnotation: this.props.annotation,
isDirty: true,
positions: [],
};

this.calculatePosition = this.calculatePosition.bind( this );
}

matchEditorXPath( xpath ) {
const body = this.props.editor.getBody();

const results = document.evaluate(
xpath,
body,
null,
XPathResult.UNORDERED_NODE_ITERATOR_TYPE
);
const firstResult = results.iterateNext();
if ( firstResult ) {
return firstResult;
}

return null;
}

createRange( startNode, endNode, startOffset, endOffset ) {
const range = document.createRange();

range.setStart( startNode, startOffset );
range.setEnd( endNode, endOffset );

return range;
}

calculatePosition() {
const { annotation } = this.props;

const {
startXPath,
startOffset,
endXPath,
endOffset,
} = annotation;

let startNode, endNode;

try {
startNode = this.matchEditorXPath( startXPath );
endNode = this.matchEditorXPath( endXPath );
} catch ( e ) {
return;
}

const range = this.createRange( startNode, endNode, startOffset, endOffset );
const editorRect = this.props.containerRef.current.getBoundingClientRect();

const positions = map( range.getClientRects(), ( rect ) => {
return {
width: rect.width,
height: rect.height,
top: rect.top - editorRect.top,
left: rect.left - editorRect.left,
};
} );

this.setState( {
positions,
prevAnnotation: this.props.annotation,
isDirty: false,
} );
}

updatePosition() {
if ( this.state.isDirty ) {
// Makes sure that this is not done synchronously on the same frame.
setTimeout( this.calculatePosition, 10 );
}
}

componentDidMount() {
this.updatePosition();
}

componentDidUpdate() {
this.updatePosition();
}

static getDerivedStateFromProps( props, state ) {
const isDirty = ! isEqual( props.annotation, state.prevAnnotation );

return {
isDirty,
};
}

render() {
const { annotation } = this.props;

return <Fragment>
<div className="annotation-marker-debug">
{ JSON.stringify( this.props.annotation ) }
</div>
{ this.state.positions.map( ( position, index ) => {
let {
width,
height,
top,
left,
} = position;

width = width + 'px';
height = height + 'px';
top = top + 'px';
left = left + 'px';

return <div
className="annotation-marker"
key={ annotation.id + '-' + index }
style={ { width, height, top, left } }
/>;
} ) }
</Fragment>;
}
}

export default Annotation;
26 changes: 26 additions & 0 deletions editor/components/rich-text/annotations/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Component } from '@wordpress/element';
import Annotation from './annotation';

class Annotations extends Component {
render() {
const { annotations } = this.props;

// Don't try to render annotations while we don't have an editor.
if ( ! this.props.editor ) {
return null;
}

return <div className="annotation-markers">
{ annotations.map( ( annotation ) => {
return <Annotation
key={ annotation.id }
annotation={ annotation }
containerRef={ this.props.containerRef }
editor={ this.props.editor }
/>;
} ) }
</div>;
}
}

export default Annotations;
18 changes: 16 additions & 2 deletions editor/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import patterns from './patterns';
import { withBlockEditContext } from '../block-edit/context';
import { domToFormat, valueToString } from './format';
import TokenUI from './tokens/ui';
import Annotations from './annotations';

/**
* Browser dependencies
Expand Down Expand Up @@ -800,7 +801,13 @@ export class RichText extends Component {
// mount and initialize a new child element in its place.
const key = [ 'editor', Tagname ].join();
const isPlaceholderVisible = placeholder && ( ! isSelected || keepPlaceholderOnFocus ) && this.isEmpty();
const classes = classnames( wrapperClassName, 'editor-rich-text' );
let classes = classnames( wrapperClassName, 'editor-rich-text' );

// if ( annotationTarget !== '' ) {
classes += " annotation-target";
// classes.push( 'annotation-target' );
// classes.push( 'annotation-target' + annotationTarget );
// }

const formatToolbar = (
<FormatToolbar
Expand Down Expand Up @@ -865,6 +872,11 @@ export class RichText extends Component {
</Fragment>
) }
</Autocomplete>
<Annotations
annotations={ this.props.annotations }
containerRef={ this.containerRef }
editor={ this.editor }
/>
</div>
);
}
Expand Down Expand Up @@ -900,15 +912,17 @@ const RichTextContainer = compose( [
return {
isSelected: context.isSelected && context.focusedElement === ownProps.instanceId,
setFocusedElement: context.setFocusedElement,
clientId: context.clientId,
};
} ),
withSelect( ( select ) => {
withSelect( ( select, props ) => {
const { isViewportMatch = identity } = select( 'core/viewport' ) || {};
const { canUserUseUnfilteredHTML } = select( 'core/editor' );

return {
isViewportSmall: isViewportMatch( '< small' ),
canUserUseUnfilteredHTML: canUserUseUnfilteredHTML(),
annotations: select( 'core/editor' ).getAnnotationsForBlock( props.clientId ),
};
} ),
withSafeTimeout,
Expand Down
16 changes: 16 additions & 0 deletions editor/components/rich-text/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
.editor-rich-text {
// This is needed to position the formatting toolbar.
position: relative;

* {
z-index: 1
}

> .annotation-markers {
> .annotation-marker-debug {
margin: 1em 0;
}

> .annotation-marker {
z-index: 0;
position: absolute;
background: #fdf451 !important;
}
}
}

.editor-rich-text__tinymce {
Expand Down
Loading

0 comments on commit 7079812

Please sign in to comment.