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