Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Ensure onSelectionChange isn't triggered while creating highlights #281

Merged
merged 4 commits into from Nov 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/Annotator.scss
Expand Up @@ -168,8 +168,7 @@

// Quad point positioning - the helper divs are positioned relative to the Rangy-created element
.bp-doc .rangy-highlight {
background-color: lighten($highlight-yellow, 10%);
opacity: .25;
background-color: $highlight-yellow;
position: relative;
}

Expand Down
3 changes: 3 additions & 0 deletions src/components/ActionControls/ActionControls.scss
Expand Up @@ -35,6 +35,9 @@
}

.ba-annotation-input-container {
bottom: 0;
padding: 15px;
position: absolute;
width: 100%;
}

Expand Down
5 changes: 1 addition & 4 deletions src/doc/CreateHighlightDialog.js
Expand Up @@ -265,13 +265,10 @@ class CreateHighlightDialog extends EventEmitter {
onCommentClick = () => {
this.emit(CREATE_EVENT.comment);
this.renderAnnotationPopover(TYPES.highlight_comment);

// $FlowFixMe
document.getSelection().removeAllRanges();
};

/**
* Cancels adding a comment to the highlgiht annotation by rendering a plain highlight popover
* Cancels adding a comment to the highlight annotation by rendering a plain highlight popover
*
* @return {void}
*/
Expand Down
40 changes: 25 additions & 15 deletions src/doc/DocAnnotator.js
Expand Up @@ -247,6 +247,12 @@ class DocAnnotator extends Annotator {
* @return {void}
*/
renderPage(pageNum: number) {
// $FlowFixMe
document.getSelection().removeAllRanges();
if (this.highlighter) {
this.highlighter.removeAllHighlights();
}

// Scale existing canvases on re-render
this.scaleAnnotationCanvases(pageNum);
super.renderPage(pageNum);
Expand Down Expand Up @@ -412,7 +418,6 @@ class DocAnnotator extends Annotator {
) {
mouseEvent.stopPropagation();
mouseEvent.preventDefault();
this.highlighter.removeAllHighlights();
this.resetHighlightSelection(mouseEvent);
return;
}
Expand Down Expand Up @@ -442,6 +447,7 @@ class DocAnnotator extends Annotator {

// $FlowFixMe
this.createHighlightDialog.unmountPopover();
this.isCreatingHighlight = false;
}

/**
Expand All @@ -455,6 +461,9 @@ class DocAnnotator extends Annotator {

// $FlowFixMe
document.getSelection().removeAllRanges();
if (this.highlighter) {
this.highlighter.removeAllHighlights();
}
}

/**
Expand Down Expand Up @@ -532,37 +541,38 @@ class DocAnnotator extends Annotator {
this.selectionEndTimeout = null;
}

// Bail if mid highlight and tapping on the screen
const selection = window.getSelection();
const isClickOutsideCreateDialog = this.isCreatingHighlight && util.isInDialog(event);
pramodsum marked this conversation as resolved.
Show resolved Hide resolved
if (!docUtil.isValidSelection(selection) || isClickOutsideCreateDialog) {
this.lastHighlightEvent = null;
this.resetHighlightSelection(event);
return;
}

// Do nothing if in a text area or mobile dialog or mobile create dialog is already open
const pointController = this.modeControllers[TYPES.point];
const isCreatingPoint = !!(pointController && pointController.pendingThreadID);
const isPopoverActive = !!util.findClosestElWithClass(document.activeElement, CLASS_ANNOTATION_POPOVER);
if (isCreatingPoint || isPopoverActive) {
if (this.isCreatingHighlight || isCreatingPoint || isPopoverActive) {
return;
}

const selection = window.getSelection();

// If we're creating a new selection, make sure to clear out to avoid
// incorrect text being selected
if (!this.lastSelection || !selection || !docUtil.hasSelectionChanged(selection, this.lastSelection)) {
this.highlighter.removeAllHighlights();
}

// Bail if mid highlight and tapping on the screen
if (!docUtil.isValidSelection(selection)) {
this.lastHighlightEvent = null;

// $FlowFixMe
this.createHighlightDialog.unmountPopover();
if (
this.highlighter &&
(!this.lastSelection || !selection || !docUtil.hasSelectionChanged(selection, this.lastSelection))
) {
this.highlighter.removeAllHighlights();
return;
}

this.selectionEndTimeout = setTimeout(() => {
if (this.createHighlightDialog && !this.createHighlightDialog.isVisible) {
this.createHighlightDialog.show(this.lastSelection);
}
}, SELECTION_TIMEOUT);
this.isCreatingHighlight = true;

const { page } = util.getPageInfo(event.target);

Expand Down
21 changes: 9 additions & 12 deletions src/doc/DocHighlightThread.js
Expand Up @@ -115,22 +115,19 @@ class DocHighlightThread extends AnnotationThread {
return super.save(type, text);
}

/**
* Deletes an annotation.
*
* @param {Object} annotation annotation to delete
* @param {boolean} [useServer] Whether or not to delete on server, default true
* @return {void}
*/
delete(annotation: Object, useServer: boolean = true): Promise<any> {
const promise = super.delete(annotation, useServer);
/** @inheritdoc */
deleteSuccessHandler = () => {
// Broadcast annotation deletion event
this.emit(THREAD_EVENT.delete);

if (this.threadID) {
this.renderAnnotationPopover();
} else {
// $FlowFixMe
const { page } = this.location;
this.emit(THREAD_EVENT.render, { page });
}

return promise;
}
};

/**
* Mousedown handler for thread. Deletes this thread if it is still pending.
Expand Down
3 changes: 0 additions & 3 deletions src/doc/__tests__/CreateHighlightDialog-test.js
Expand Up @@ -158,9 +158,6 @@ describe('doc/CreateHighlightDialog', () => {

describe('onCommentClick()', () => {
it('should create a plain highlight and render the popover', () => {
document.getSelection = jest.fn().mockReturnValue({
removeAllRanges: jest.fn()
});
dialog.onCommentClick({ preventDefault: jest.fn(), stopPropagation: jest.fn() });
expect(dialog.emit).toBeCalledWith(CREATE_EVENT.comment);
expect(dialog.renderAnnotationPopover).toBeCalled();
Expand Down
44 changes: 26 additions & 18 deletions src/doc/__tests__/DocAnnotator-test.js
Expand Up @@ -411,6 +411,12 @@ describe('doc/DocAnnotator', () => {

describe('renderPage()', () => {
beforeEach(() => {
document.getSelection = jest.fn().mockReturnValue({
removeAllRanges: jest.fn()
});
annotator.highlighter = {
removeAllHighlights: jest.fn()
};
annotator.scaleAnnotationCanvases = jest.fn();
annotator.modeControllers = {
type: {
Expand Down Expand Up @@ -785,10 +791,12 @@ describe('doc/DocAnnotator', () => {
annotator.lastSelection = {};

annotator.highlighter = { removeAllHighlights: jest.fn() };
annotator.resetHighlightSelection = jest.fn();

window.getSelection = jest.fn();
util.getPageInfo = jest.fn().mockReturnValue({ page: 1 });
util.findClosestElWithClass = jest.fn().mockReturnValue(null);
annotator.isCreatingHighlight = false;
});

it('should reset the selectionEndTimeout', () => {
Expand All @@ -798,10 +806,20 @@ describe('doc/DocAnnotator', () => {
expect(annotator.selectionEndTimeout).toBeNull();
});

it('should do nothing if focus is on a text input element', () => {
util.findClosestElWithClass = jest.fn().mockReturnValue({});
it('should clear selection if the user is currently creating a highlight annotation', () => {
annotator.isCreatingHighlight = true;
util.isInDialog = jest.fn().mockReturnValue(true);
annotator.onSelectionChange(event);
expect(annotator.resetHighlightSelection).toBeCalled();
});

it('should clear out highlights and exit "annotation creation" mode if an invalid selection', () => {
annotator.lastHighlightEvent = event;
docUtil.isValidSelection = jest.fn().mockReturnValue(false);

annotator.onSelectionChange(event);
expect(window.getSelection).not.toBeCalled();
expect(annotator.lastHighlightEvent).toBeNull();
expect(annotator.resetHighlightSelection).toBeCalled();
});

it('should do nothing the the user is currently creating a point annotation', () => {
Expand All @@ -810,7 +828,7 @@ describe('doc/DocAnnotator', () => {
};
controller.pendingThreadID = 'something';
annotator.onSelectionChange(event);
expect(window.getSelection).not.toBeCalled();
expect(annotator.isCreatingHighlight).toBeFalsy();
});

it('should clear selection if the highlight has not changed', () => {
Expand All @@ -825,20 +843,6 @@ describe('doc/DocAnnotator', () => {
expect(annotator.highlighter.removeAllHighlights).toBeCalled();
});

it('should clear out highlights and exit "annotation creation" mode if an invalid selection', () => {
const selection = {
toString: () => '' // Causes invalid selection
};
window.getSelection = jest.fn().mockReturnValue(selection);
annotator.lastHighlightEvent = event;
docUtil.hasSelectionChanged = jest.fn().mockReturnValue(true);
docUtil.isValidSelection = jest.fn().mockReturnValue(false);

annotator.onSelectionChange(event);
expect(annotator.lastHighlightEvent).toBeNull();
expect(annotator.highlighter.removeAllHighlights).toBeCalled();
});

it('should show the createHighlightDialog', () => {
const selection = {
rangeCount: 10,
Expand All @@ -852,6 +856,7 @@ describe('doc/DocAnnotator', () => {

annotator.onSelectionChange(event);
expect(annotator.selectionEndTimeout).not.toBeNull();
expect(annotator.isCreatingHighlight).toBeTruthy();
});

it('should set all of the highlight annotations on the page to "inactive" state', () => {
Expand Down Expand Up @@ -947,6 +952,9 @@ describe('doc/DocAnnotator', () => {
annotator.modeControllers = {
highlight: controller
};
annotator.highlighter = {
removeAllHighlights: jest.fn()
};
annotator.activeThread = undefined;
annotator.plainHighlightEnabled = false;
annotator.commentHighlightEnabled = false;
Expand Down