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);