From d6fb48c0ff848636f22a4bb9c8ea2aa03c70a6d1 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Fri, 30 Aug 2019 16:09:00 +0100 Subject: [PATCH] Editor: Fixes issue where only entire lines were being copied (#18806) * Editor: Fixes issue where only entire lines were being copied Closes #18768 * Simplifies onCopy handler and factors out logic for easier testing Also adds tests to verify behaviour --- .../app/features/explore/QueryField.test.tsx | 32 ++++++++++++++++++- public/app/features/explore/QueryField.tsx | 25 ++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/public/app/features/explore/QueryField.test.tsx b/public/app/features/explore/QueryField.test.tsx index 378115ec9fd..274de4e7ceb 100644 --- a/public/app/features/explore/QueryField.test.tsx +++ b/public/app/features/explore/QueryField.test.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; - import { QueryField } from './QueryField'; describe('', () => { @@ -28,4 +27,35 @@ describe('', () => { expect(handleEnterAndTabKeySpy).toBeCalled(); expect(instance.executeOnChangeAndRunQueries).toBeCalled(); }); + + it('should copy selected text', () => { + const wrapper = shallow(); + const instance = wrapper.instance() as QueryField; + const textBlocks = ['ignore this text. copy this text']; + const copiedText = instance.getCopiedText(textBlocks, 18, 32); + + expect(copiedText).toBe('copy this text'); + }); + + it('should copy selected text across 2 lines', () => { + const wrapper = shallow(); + const instance = wrapper.instance() as QueryField; + const textBlocks = ['ignore this text. start copying here', 'lorem ipsum. stop copying here. lorem ipsum']; + const copiedText = instance.getCopiedText(textBlocks, 18, 30); + + expect(copiedText).toBe('start copying here\nlorem ipsum. stop copying here'); + }); + + it('should copy selected text across > 2 lines', () => { + const wrapper = shallow(); + const instance = wrapper.instance() as QueryField; + const textBlocks = [ + 'ignore this text. start copying here', + 'lorem ipsum doler sit amet', + 'lorem ipsum. stop copying here. lorem ipsum', + ]; + const copiedText = instance.getCopiedText(textBlocks, 18, 30); + + expect(copiedText).toBe('start copying here\nlorem ipsum doler sit amet\nlorem ipsum. stop copying here'); + }); }); diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index fd3d4fdb09f..8a61a4397e8 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -620,15 +620,30 @@ export class QueryField extends React.PureComponent { + getCopiedText(textBlocks: string[], startOffset: number, endOffset: number) { + if (!textBlocks.length) { + return undefined; + } + + const excludingLastLineLength = textBlocks.slice(0, -1).join('').length + textBlocks.length - 1; + return textBlocks.join('\n').slice(startOffset, excludingLastLineLength + endOffset); + } + + handleCopy = (event: ClipboardEvent, change: Change) => { event.preventDefault(); - const selectedBlocks = change.value.document.getBlocksAtRange(change.value.selection); - event.clipboardData.setData('Text', selectedBlocks.map((block: Block) => block.text).join('\n')); + + const { document, selection, startOffset, endOffset } = change.value; + const selectedBlocks = document.getBlocksAtRangeAsArray(selection).map((block: Block) => block.text); + + const copiedText = this.getCopiedText(selectedBlocks, startOffset, endOffset); + if (copiedText) { + event.clipboardData.setData('Text', copiedText); + } return true; }; - handlePaste = (event: ClipboardEvent, change: Editor) => { + handlePaste = (event: ClipboardEvent, change: Change) => { event.preventDefault(); const pastedValue = event.clipboardData.getData('Text'); const lines = pastedValue.split('\n'); @@ -643,7 +658,7 @@ export class QueryField extends React.PureComponent { + handleCut = (event: ClipboardEvent, change: Change) => { this.handleCopy(event, change); change.deleteAtRange(change.value.selection);