From b2d86c76c66885ff35f29c44acb527f4d78d1930 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
(cherry picked from commit d6fb48c0ff848636f22a4bb9c8ea2aa03c70a6d1)
---
.../app/features/explore/QueryField.test.tsx | 32 ++++++++++++-
public/app/features/explore/QueryField.tsx | 45 +++++++++++++++++--
2 files changed, 72 insertions(+), 5 deletions(-)
diff --git a/public/app/features/explore/QueryField.test.tsx b/public/app/features/explore/QueryField.test.tsx
index e58127345f0..bf695e12eba 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 43e8bd1f717..9e2451dd94c 100644
--- a/public/app/features/explore/QueryField.tsx
+++ b/public/app/features/explore/QueryField.tsx
@@ -2,7 +2,7 @@ import _ from 'lodash';
import React, { Context } from 'react';
import ReactDOM from 'react-dom';
// @ts-ignore
-import { Change, Value } from 'slate';
+import { Change, Value, Block } from 'slate';
// @ts-ignore
import { Editor } from 'slate-react';
// @ts-ignore
@@ -476,10 +476,47 @@ 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 { 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: Change) => {
+ event.preventDefault();
const pastedValue = event.clipboardData.getData('Text');
- const newValue = change.value.change().insertText(pastedValue);
- this.onChange(newValue);
+ const lines = pastedValue.split('\n');
+
+ if (lines.length) {
+ change.insertText(lines[0]);
+ for (const line of lines.slice(1)) {
+ change.splitBlock().insertText(line);
+ }
+ }
+
+ return true;
+ };
+
+ handleCut = (event: ClipboardEvent, change: Change) => {
+ this.handleCopy(event, change);
+ change.deleteAtRange(change.value.selection);
return true;
};