Polyfills to enable editor positoning on MS Edge (issue #7)

This commit is contained in:
Simon Rainer 2020-05-05 13:13:10 +02:00
parent a22068960a
commit 41529a4cd1
4 changed files with 54 additions and 36 deletions

View File

@ -2,9 +2,11 @@ import React, { Component } from 'preact/compat';
import Editor from './editor/Editor'; import Editor from './editor/Editor';
import Highlighter from './highlighter/Highlighter'; import Highlighter from './highlighter/Highlighter';
import SelectionHandler from './selection/SelectionHandler'; import SelectionHandler from './selection/SelectionHandler';
import RelationsLayer from './relations/RelationsLayer'; import RelationsLayer from './relations/RelationsLayer';
import RelationEditor from './relations/editor/RelationEditor'; import RelationEditor from './relations/editor/RelationEditor';
import './utils/MSEdgePolyfills';
/** /**
* Pulls the strings between the annotation highlight layer * Pulls the strings between the annotation highlight layer
* and the editor popup. * and the editor popup.
@ -53,18 +55,18 @@ export default class TextAnnotator extends Component {
document.removeEventListener('keydown', this.handleEscape); document.removeEventListener('keydown', this.handleEscape);
} }
/**************************/ /**************************/
/* Annotation CRUD events */ /* Annotation CRUD events */
/**************************/ /**************************/
/** Selection on the text **/ /** Selection on the text **/
handleSelect = evt => { handleSelect = evt => {
const { selection, clientRect } = evt; const { selection, clientRect } = evt;
if (selection) { if (selection) {
this.setState({ this.setState({
selectedAnnotation: null, selectedAnnotation: null,
selectionBounds: null selectionBounds: null
}, () => this.setState({ }, () => this.setState({
selectedAnnotation: selection, selectedAnnotation: selection,
selectionBounds: clientRect selectionBounds: clientRect
})) }))
@ -76,12 +78,12 @@ export default class TextAnnotator extends Component {
/** /**
* A convenience method that allows the external application to * A convenience method that allows the external application to
* override the autogenerated Id for an annotation. * override the autogenerated Id for an annotation.
* *
* Usually, the override will happen almost immediately after * Usually, the override will happen almost immediately after
* the annotation is created. But we need to be defensive and assume * the annotation is created. But we need to be defensive and assume
* that the override might come in with considerable delay, thus * that the override might come in with considerable delay, thus
* the user might have made further edits already. * the user might have made further edits already.
* *
* A key challenge here is that there may be dependencies between * A key challenge here is that there may be dependencies between
* the original annotation and relations that were created meanwhile. * the original annotation and relations that were created meanwhile.
*/ */
@ -113,14 +115,14 @@ export default class TextAnnotator extends Component {
} }
} }
/** /**
* A convenience method that allows the external application to * A convenience method that allows the external application to
* override the autogenerated Id for a relation. * override the autogenerated Id for a relation.
* *
* This operation is less problematic than .overrideAnnotation(). * This operation is less problematic than .overrideAnnotation().
* We just need to make sure the RelationEditor is closed, so that * We just need to make sure the RelationEditor is closed, so that
* the annotation doesn't become orphaned. Otherwise, there are * the annotation doesn't become orphaned. Otherwise, there are
* no dependencies. * no dependencies.
*/ */
overrideRelationId = originalId => forcedId => { overrideRelationId = originalId => forcedId => {
if (this.state.selectedRelation) { if (this.state.selectedRelation) {
@ -134,14 +136,14 @@ export default class TextAnnotator extends Component {
/** Common handler for annotation CREATE or UPDATE **/ /** Common handler for annotation CREATE or UPDATE **/
onCreateOrUpdateAnnotation = method => (annotation, previous) => { onCreateOrUpdateAnnotation = method => (annotation, previous) => {
this.clearState(); this.clearState();
this.selectionHandler.clearSelection(); this.selectionHandler.clearSelection();
this.highlighter.addOrUpdateAnnotation(annotation, previous); this.highlighter.addOrUpdateAnnotation(annotation, previous);
// Call CREATE or UPDATE handler // Call CREATE or UPDATE handler
if (previous) if (previous)
this.props[method](annotation.clone(), previous.clone()); this.props[method](annotation.clone(), previous.clone());
else else
this.props[method](annotation.clone(), this.overrideAnnotationId(annotation)); this.props[method](annotation.clone(), this.overrideAnnotationId(annotation));
} }
@ -162,9 +164,9 @@ export default class TextAnnotator extends Component {
this.selectionHandler.clearSelection(); this.selectionHandler.clearSelection();
} }
/************************/ /************************/
/* Relation CRUD events */ /* Relation CRUD events */
/************************/ /************************/
// Shorthand // Shorthand
closeRelationsEditor = () => { closeRelationsEditor = () => {
@ -177,7 +179,7 @@ export default class TextAnnotator extends Component {
* or newly created connection for editing. * or newly created connection for editing.
*/ */
onEditRelation = relation => { onEditRelation = relation => {
this.setState({ this.setState({
selectedRelation: relation selectedRelation: relation
}); });
} }
@ -205,9 +207,9 @@ export default class TextAnnotator extends Component {
this.props.onAnnotationDeleted(relation.annotation); this.props.onAnnotationDeleted(relation.annotation);
} }
/****************/ /****************/
/* External API */ /* External API */
/****************/ /****************/
addAnnotation = annotation => { addAnnotation = annotation => {
this.highlighter.addOrUpdateAnnotation(annotation.clone()); this.highlighter.addOrUpdateAnnotation(annotation.clone());
@ -223,7 +225,7 @@ export default class TextAnnotator extends Component {
} }
setAnnotations = annotations => { setAnnotations = annotations => {
const clones = annotations.map(a => a.clone()); const clones = annotations.map(a => a.clone());
this.highlighter.init(clones).then(() => this.highlighter.init(clones).then(() =>
this.relationsLayer.init(clones)); this.relationsLayer.init(clones));
} }
@ -237,14 +239,14 @@ export default class TextAnnotator extends Component {
setMode = mode => { setMode = mode => {
if (mode === 'RELATIONS') { if (mode === 'RELATIONS') {
this.clearState(); this.clearState();
this.selectionHandler.enabled = false; this.selectionHandler.enabled = false;
this.relationsLayer.readOnly = false; this.relationsLayer.readOnly = false;
this.relationsLayer.startDrawing(); this.relationsLayer.startDrawing();
} else { } else {
this.setState({ selectedRelation: null }); this.setState({ selectedRelation: null });
this.selectionHandler.enabled = true; this.selectionHandler.enabled = true;
this.relationsLayer.readOnly = true; this.relationsLayer.readOnly = true;
@ -252,7 +254,7 @@ export default class TextAnnotator extends Component {
} }
} }
applyTemplate = (bodies, headless) => applyTemplate = (bodies, headless) =>
this.setState({ applyTemplate: bodies, headless }) this.setState({ applyTemplate: bodies, headless })
render() { render() {
@ -276,17 +278,17 @@ export default class TextAnnotator extends Component {
</Editor> </Editor>
} }
{ this.state.selectedRelation && { this.state.selectedRelation &&
<RelationEditor <RelationEditor
relation={this.state.selectedRelation} relation={this.state.selectedRelation}
onRelationCreated={this.onCreateOrUpdateRelation} onRelationCreated={this.onCreateOrUpdateRelation}
onRelationUpdated={this.onCreateOrUpdateRelation} onRelationUpdated={this.onCreateOrUpdateRelation}
onRelationDeleted={this.onDeleteRelation} onRelationDeleted={this.onDeleteRelation}
onCancel={this.closeRelationsEditor} onCancel={this.closeRelationsEditor}
/> />
} }
</> </>
); );
} }
} }

View File

@ -11,9 +11,9 @@ const setPosition = (wrapperEl, editorEl, annotationBounds) => {
editorEl.style.opacity = 1; editorEl.style.opacity = 1;
// Default orientation // Default orientation
const { x, y, height, top, right } = annotationBounds; const { left, top, right, height } = annotationBounds;
editorEl.style.top = `${y + height - containerBounds.top}px`; editorEl.style.top = `${top + height - containerBounds.top}px`;
editorEl.style.left = `${x + scrollX - containerBounds.left}px`; editorEl.style.left = `${left + scrollX - containerBounds.left}px`;
const defaultOrientation = editorEl.getBoundingClientRect(); const defaultOrientation = editorEl.getBoundingClientRect();
@ -27,11 +27,11 @@ const setPosition = (wrapperEl, editorEl, annotationBounds) => {
// Flip vertically // Flip vertically
const annotationTop = top + scrollY; // Annotation top relative to parents const annotationTop = top + scrollY; // Annotation top relative to parents
const containerHeight = containerBounds.height + containerBounds.top + scrollY; const containerHeight = containerBounds.height + containerBounds.top + scrollY;
editorEl.classList.add('align-bottom'); editorEl.classList.add('align-bottom');
editorEl.style.top = 'auto'; editorEl.style.top = 'auto';
editorEl.style.bottom = `${containerHeight - annotationTop}px`; editorEl.style.bottom = `${containerHeight - annotationTop}px`;
} }
} }
export default setPosition; export default setPosition;

View File

@ -17,7 +17,7 @@ export default class SelectionHandler extends EventEmitter {
element.addEventListener('mouseup', this._onMouseUp); element.addEventListener('mouseup', this._onMouseUp);
if (IS_TOUCH) if (IS_TOUCH)
enableTouch(element, this._onMouseUp); enableTouch(element, this._onMouseUp);
} }
get enabled() { get enabled() {
@ -39,9 +39,9 @@ export default class SelectionHandler extends EventEmitter {
if (selection.isCollapsed) { if (selection.isCollapsed) {
const annotationSpan = evt.target.closest('.r6o-annotation'); const annotationSpan = evt.target.closest('.r6o-annotation');
if (annotationSpan) { if (annotationSpan) {
this.emit('select', { this.emit('select', {
selection: this.highlighter.getAnnotationsAt(annotationSpan)[0], selection: this.highlighter.getAnnotationsAt(annotationSpan)[0],
clientRect: annotationSpan.getBoundingClientRect() clientRect: annotationSpan.getBoundingClientRect()
}); });
} else { } else {
// De-select // De-select
@ -92,4 +92,4 @@ export default class SelectionHandler extends EventEmitter {
this.el.normalize(); this.el.normalize();
} }
} }

View File

@ -0,0 +1,16 @@
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector ||
Element.prototype.webkitMatchesSelector;
}
if (!Element.prototype.closest) {
Element.prototype.closest = function(s) {
let el = this;
do {
if (Element.prototype.matches.call(el, s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}