Move text selection out of gr-diff.
Change-Id: I0734653066a1bb78f95c141aa8202fad315b13c0
This commit is contained in:
@@ -62,6 +62,26 @@ limitations under the License.
|
||||
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,
|
||||
|
@@ -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>
|
@@ -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;
|
||||
},
|
||||
});
|
||||
})();
|
@@ -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>
|
@@ -17,8 +17,9 @@ limitations under the License.
|
||||
<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 @@ limitations under the License.
|
||||
border-collapse: collapse;
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
.section {
|
||||
.lineNum {
|
||||
background-color: #eee;
|
||||
}
|
||||
.image-diff .gr-diff {
|
||||
@@ -99,18 +100,6 @@ limitations under the License.
|
||||
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 @@ limitations under the License.
|
||||
}
|
||||
</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>
|
||||
|
@@ -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++) {
|
||||
|
@@ -47,6 +47,7 @@ limitations under the License.
|
||||
'diff/gr-diff-comment/gr-diff-comment_test.html',
|
||||
'diff/gr-diff-cursor/gr-diff-cursor_test.html',
|
||||
'diff/gr-diff-preferences/gr-diff-preferences_test.html',
|
||||
'diff/gr-diff-selection/gr-diff-selection_test.html',
|
||||
'diff/gr-diff-view/gr-diff-view_test.html',
|
||||
'diff/gr-diff/gr-diff-group_test.html',
|
||||
'diff/gr-diff/gr-diff_test.html',
|
||||
@@ -66,8 +67,8 @@ limitations under the License.
|
||||
'shared/gr-editable-label/gr-editable-label_test.html',
|
||||
'shared/gr-js-api-interface/gr-js-api-interface_test.html',
|
||||
'shared/gr-linked-text/gr-linked-text_test.html',
|
||||
'shared/gr-select/gr-select_test.html',
|
||||
'shared/gr-rest-api-interface/gr-rest-api-interface_test.html',
|
||||
'shared/gr-select/gr-select_test.html',
|
||||
'shared/gr-storage/gr-storage_test.html',
|
||||
].forEach(function(file) {
|
||||
file = basePath + file;
|
||||
|
Reference in New Issue
Block a user