Move text selection out of gr-diff.
Change-Id: I0734653066a1bb78f95c141aa8202fad315b13c0
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index f647957..d9d6c8a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -62,6 +62,26 @@
this._renderDiff();
},
+ getLineElByChild: function(node) {
+ while (node) {
+ if (node instanceof Element) {
+ if (node.classList.contains('lineNum')) {
+ return node;
+ }
+ if (node.classList.contains('section')) {
+ return null;
+ }
+ }
+ node = node.previousSibling || node.parentElement;
+ };
+ return null;
+ },
+
+ getSideByLineEl: function(lineEl) {
+ return lineEl.classList.contains(GrDiffBuilder.Side.RIGHT) ?
+ GrDiffBuilder.Side.RIGHT : GrDiffBuilder.Side.LEFT;
+ },
+
createCommentThread: function(changeNum, patchNum, path, side,
projectConfig) {
return this._builder.createCommentThread(changeNum, patchNum, path,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
new file mode 100644
index 0000000..09cab0b
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
@@ -0,0 +1,43 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<dom-module id="gr-diff-selection">
+ <template>
+ <style>
+ .contentWrapper ::content .content {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
+ :host.selected-right .contentWrapper ::content .right + .content,
+ :host.selected-left .contentWrapper ::content .left + .content,
+ :host.selected-right .contentWrapper ::content .unified .right ~ .content,
+ :host.selected-left .contentWrapper ::content .unified .left ~ .content {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ -ms-user-select: text;
+ user-select: text;
+ }
+ </style>
+ <div class="contentWrapper">
+ <content></content>
+ </div>
+ </template>
+ <script src="gr-diff-selection.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
new file mode 100644
index 0000000..6160c9d
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -0,0 +1,84 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-diff-selection',
+
+ properties: {
+ _cachedDiffBuilder: Object,
+ },
+
+ listeners: {
+ 'copy': '_handleCopy',
+ 'down': '_handleDown',
+ },
+
+ get diffBuilder() {
+ if (!this._cachedDiffBuilder) {
+ this._cachedDiffBuilder =
+ Polymer.dom(this).querySelector('gr-diff-builder');
+ }
+ return this._cachedDiffBuilder;
+ },
+
+ _handleDown: function(e) {
+ var lineEl = this.diffBuilder.getLineElByChild(e.target);
+ if (!lineEl) {
+ return;
+ }
+ var side = this.diffBuilder.getSideByLineEl(lineEl);
+ this.classList.remove('selected-right', 'selected-left');
+ this.classList.add('selected-' + side);
+ },
+
+ _handleCopy: function(e) {
+ if (!e.target.classList.contains('content')) {
+ return;
+ }
+ var lineEl = this.diffBuilder.getLineElByChild(e.target);
+ if (!lineEl) {
+ return;
+ }
+ var side = this.diffBuilder.getSideByLineEl(lineEl);
+ var text = this._getSelectedText(side);
+ e.clipboardData.setData('Text', text);
+ e.preventDefault();
+ },
+
+ _getSelectedText: function(opt_side) {
+ var sel = window.getSelection();
+ if (sel.rangeCount != 1) {
+ return; // No multi-select support yet.
+ }
+ var range = sel.getRangeAt(0);
+ var fragment = range.cloneContents();
+ var selector = '.content,td.content:nth-of-type(1)';
+ if (opt_side) {
+ selector = '.' + opt_side + ' + ' + selector;
+ }
+ var contentEls = Polymer.dom(fragment).querySelectorAll(selector);
+ if (contentEls.length === 0) {
+ return fragment.textContent;
+ }
+
+ var text = '';
+ for (var i = 0; i < contentEls.length; i++) {
+ text += contentEls[i].textContent + '\n';
+ }
+ return text;
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
new file mode 100644
index 0000000..3719de9
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-diff-selection</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-diff-selection.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-diff-selection>
+ <table>
+ <tr>
+ <td class="lineNum left">1</td>
+ <td class="content">ba ba</td>
+ <td class="lineNum right">1</td>
+ <td class="other">some other text</td>
+ </tr>
+ <tr>
+ <td class="lineNum left">2</td>
+ <td class="content">zin</td>
+ <td class="lineNum right">2</td>
+ <td class="content">more more more</td>
+ </tr>
+ <tr>
+ <td class="lineNum left">2</td>
+ <td class="content">ga ga</td>
+ <td class="lineNum right">3</td>
+ <td class="other">some other text</td>
+ </tr>
+ </table>
+ </gr-diff-selection>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-diff-selection', function() {
+ var element;
+
+ var emulateCopyOn = function(element) {
+ var event = new CustomEvent('copy', {bubbles: true});
+ event.clipboardData = {
+ setData: sinon.stub(),
+ };
+ element.dispatchEvent(event);
+ return event;
+ };
+
+ setup(function() {
+ element = fixture('basic');
+ element._cachedDiffBuilder = {
+ getLineElByChild: sinon.stub().returns({}),
+ getSideByLineEl: sinon.stub(),
+ };
+ });
+
+ test('applies selected-left on left side click', function() {
+ element.classList.add('selected-right');
+ element._cachedDiffBuilder.getSideByLineEl.returns('left');
+ MockInteractions.down(element);
+ assert.isTrue(
+ element.classList.contains('selected-left'), 'adds selected-left');
+ assert.isFalse(
+ element.classList.contains('selected-right'),
+ 'removes selected-right');
+ });
+
+ test('applies selected-right on right side click', function() {
+ element.classList.add('selected-left');
+ element._cachedDiffBuilder.getSideByLineEl.returns('right');
+ MockInteractions.down(element);
+ assert.isTrue(
+ element.classList.contains('selected-right'), 'adds selected-right');
+ assert.isFalse(
+ element.classList.contains('selected-left'), 'removes selected-left');
+ });
+
+ test('ignores copy for non-content Element', function() {
+ sinon.stub(element, '_getSelectedText');
+ emulateCopyOn(element.querySelector('.other'));
+ assert.isFalse(element._getSelectedText.called);
+ });
+
+ test('asks for text for right side Elements', function() {
+ element._cachedDiffBuilder.getSideByLineEl.returns('left');
+ sinon.stub(element, '_getSelectedText');
+ emulateCopyOn(element.querySelector('td.content'));
+ assert.deepEqual(['left'], element._getSelectedText.lastCall.args);
+ });
+
+ test('reacts to copy for content Elements', function() {
+ sinon.stub(element, '_getSelectedText');
+ emulateCopyOn(element.querySelector('td.content'));
+ assert.isTrue(element._getSelectedText.called);
+ });
+
+ test('inserts text into clipboard on copy', function() {
+ sinon.stub(element, '_getSelectedText').returns('the text');
+ var event = emulateCopyOn(element.querySelector('td.content'));
+ assert.deepEqual(
+ ['Text', 'the text'], event.clipboardData.setData.lastCall.args);
+ });
+
+ test('copies content correctly', function() {
+ var selection = window.getSelection();
+ var range = document.createRange();
+ range.setStart(element.querySelector('td.content').firstChild, 3);
+ range.setEnd(
+ element.querySelectorAll('td.content')[3].firstChild, 2);
+ selection.addRange(range);
+ assert.equal('ba\nzin\nga\n', element._getSelectedText('left'));
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index cfe5d66..7c66bf1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -17,8 +17,9 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
-<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
<link rel="import" href="../gr-diff-builder/gr-diff-builder.html">
+<link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html">
+<link rel="import" href="../gr-diff-selection/gr-diff-selection.html">
<dom-module id="gr-diff">
<template>
@@ -41,7 +42,7 @@
border-collapse: collapse;
border-right: 1px solid #ddd;
}
- .section {
+ .lineNum {
background-color: #eee;
}
.image-diff .gr-diff {
@@ -99,18 +100,6 @@
max-width: var(--content-width, 80ch);
min-width: var(--content-width, 80ch);
}
- .content.left {
- -webkit-user-select: var(--left-user-select, text);
- -moz-user-select: var(--left-user-select, text);
- -ms-user-select: var(--left-user-select, text);
- user-select: var(--left-user-select, text);
- }
- .content.right {
- -webkit-user-select: var(--right-user-select, text);
- -moz-user-select: var(--right-user-select, text);
- -ms-user-select: var(--right-user-select, text);
- user-select: var(--right-user-select, text);
- }
.content.add hl,
.content.add.darkHighlight {
background-color: var(--dark-add-highlight-color);
@@ -151,17 +140,17 @@
}
</style>
<div class$="[[_computeContainerClass(_loggedIn, viewMode)]]"
- on-tap="_handleTap"
- on-mousedown="_handleMouseDown"
- on-copy="_handleCopy">
- <gr-diff-builder
- id="diffBuilder"
- view-mode="[[viewMode]]"
- is-image-diff="[[isImageDiff]]"
- base-image="[[_baseImage]]"
- revision-image="[[_revisionImage]]">
- <table id="diffTable"></table>
- </gr-diff-builder>
+ on-tap="_handleTap">
+ <gr-diff-selection>
+ <gr-diff-builder
+ id="diffBuilder"
+ view-mode="[[viewMode]]"
+ is-image-diff="[[isImageDiff]]"
+ base-image="[[_baseImage]]"
+ revision-image="[[_revisionImage]]">
+ <table id="diffTable"></table>
+ </gr-diff-builder>
+ </gr-diff-selection>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index f89d1e8..f3a353b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -54,10 +54,6 @@
value: DiffViewMode.SIDE_BY_SIDE,
},
_diff: Object,
- _selectionSide: {
- type: String,
- observer: '_selectionSideChanged',
- },
_comments: Object,
},
@@ -297,66 +293,6 @@
});
},
- _handleMouseDown: function(e) {
- var el = Polymer.dom(e).rootTarget;
- var side;
- for (var node = el; node != null; node = node.parentNode) {
- if (!node.classList) { continue; }
-
- if (node.classList.contains(DiffSide.LEFT)) {
- side = DiffSide.LEFT;
- break;
- } else if (node.classList.contains(DiffSide.RIGHT)) {
- side = DiffSide.RIGHT;
- break;
- }
- }
- this._selectionSide = side;
- },
-
- _selectionSideChanged: function(side) {
- if (side) {
- var oppositeSide = side === DiffSide.RIGHT ?
- DiffSide.LEFT : DiffSide.RIGHT;
- this.customStyle['--' + side + '-user-select'] = 'text';
- this.customStyle['--' + oppositeSide + '-user-select'] = 'none';
- } else {
- this.customStyle['--left-user-select'] = 'text';
- this.customStyle['--right-user-select'] = 'text';
- }
- this.updateStyles();
- },
-
- _handleCopy: function(e) {
- if (!e.target.classList.contains('content')) {
- return;
- }
- var text = this._getSelectedText(this._selectionSide);
- e.clipboardData.setData('Text', text);
- e.preventDefault();
- },
-
- _getSelectedText: function(opt_side) {
- var sel = window.getSelection();
- var range = sel.getRangeAt(0);
- var doc = range.cloneContents();
- var selector = '.content';
- if (opt_side) {
- selector += '.' + opt_side;
- }
- var contentEls = Polymer.dom(doc).querySelectorAll(selector);
-
- if (contentEls.length === 0) {
- return doc.textContent;
- }
-
- var text = '';
- for (var i = 0; i < contentEls.length; i++) {
- text += contentEls[i].textContent + '\n';
- }
- return text;
- },
-
_prefsChanged: function(prefsChangeRecord) {
var prefs = prefsChangeRecord.base;
this.customStyle['--content-width'] = prefs.line_length + 'ch';
@@ -473,7 +409,6 @@
this.changeNum, this._diff, this.patchRange);
},
-
_projectConfigChanged: function(projectConfig) {
var threadEls = this._getCommentThreads();
for (var i = 0; i < threadEls.length; i++) {