Generate clipboard data via diff rather than DOM reconstruction
This change uses a new method of generating the clipboard data that circumvents several strange issues caused by shadow/shady DOM. This new method is much more legible and abstracts more logic away from the DOM. Bug: Issue 4494 Change-Id: I8c186d6cbbe9536548d934f734856b1f9ced1a26
This commit is contained in:
@@ -26,11 +26,13 @@ limitations under the License.
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host.selected-right .contentWrapper ::content .side-by-side .right + .content,
|
:host-context(.selected-left:not(.selected-comment)) .contentWrapper ::content .side-by-side .left + .content .contentText,
|
||||||
:host.selected-left .contentWrapper ::content .side-by-side .left + .content,
|
:host-context(.selected-right:not(.selected-comment)) .contentWrapper ::content .side-by-side .right + .content .contentText,
|
||||||
:host.selected-right .contentWrapper ::content .unified .right.lineNum ~ .content,
|
:host-context(.selected-left:not(.selected-comment)) .contentWrapper ::content .unified .left.lineNum ~ .content:not(.both) .contentText,
|
||||||
:host.selected-left .contentWrapper ::content .unified .left.lineNum ~ .content:not(.both),
|
:host-context(.selected-right:not(.selected-comment)) .contentWrapper ::content .unified .right.lineNum ~ .content .contentText,
|
||||||
:host .contentWrapper ::content .unified .gr-diff-comment .message {
|
:host-context(.selected-left.selected-comment) .contentWrapper ::content .side-by-side .left + .content .message,
|
||||||
|
:host-context(.selected-right.selected-comment) .contentWrapper ::content .side-by-side .right + .content .message,
|
||||||
|
:host-context(.selected-comment) .contentWrapper ::content .unified .message {
|
||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
-moz-user-select: text;
|
-moz-user-select: text;
|
||||||
-ms-user-select: text;
|
-ms-user-select: text;
|
||||||
|
|||||||
@@ -18,7 +18,12 @@
|
|||||||
is: 'gr-diff-selection',
|
is: 'gr-diff-selection',
|
||||||
|
|
||||||
properties: {
|
properties: {
|
||||||
|
diff: Object,
|
||||||
_cachedDiffBuilder: Object,
|
_cachedDiffBuilder: Object,
|
||||||
|
_linesCache: {
|
||||||
|
type: Object,
|
||||||
|
value: function() { return {left: null, right: null}; },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
listeners: {
|
listeners: {
|
||||||
@@ -56,9 +61,12 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
_handleCopy: function(e) {
|
_handleCopy: function(e) {
|
||||||
if (!e.target.classList.contains('contentText') &&
|
var el = e.target;
|
||||||
!e.target.classList.contains('gr-syntax')) {
|
while (!el.classList.contains('content')) {
|
||||||
return;
|
if (!el.parentElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el = el.parentElement;
|
||||||
}
|
}
|
||||||
var lineEl = this.diffBuilder.getLineElByChild(e.target);
|
var lineEl = this.diffBuilder.getLineElByChild(e.target);
|
||||||
if (!lineEl) {
|
if (!lineEl) {
|
||||||
@@ -76,21 +84,47 @@
|
|||||||
return; // No multi-select support yet.
|
return; // No multi-select support yet.
|
||||||
}
|
}
|
||||||
var range = sel.getRangeAt(0);
|
var range = sel.getRangeAt(0);
|
||||||
var fragment = range.cloneContents();
|
var startLineEl = this.diffBuilder.getLineElByChild(range.startContainer);
|
||||||
var selector = '.contentText';
|
var endLineEl = this.diffBuilder.getLineElByChild(range.endContainer);
|
||||||
selector += '[data-side="' + side + '"]';
|
var startLineNum = parseInt(startLineEl.getAttribute('data-value'), 10);
|
||||||
selector += ':not(:empty)';
|
var endLineNum = parseInt(endLineEl.getAttribute('data-value'), 10);
|
||||||
|
|
||||||
var contentEls = Polymer.dom(fragment).querySelectorAll(selector);
|
return this._getRangeFromDiff(startLineNum, range.startOffset, endLineNum,
|
||||||
if (contentEls.length === 0) {
|
range.endOffset, side);
|
||||||
return fragment.textContent;
|
},
|
||||||
|
|
||||||
|
_getRangeFromDiff: function(startLineNum, startOffset, endLineNum,
|
||||||
|
endOffset, side) {
|
||||||
|
var lines = this._getDiffLines(side).slice(startLineNum - 1, endLineNum);
|
||||||
|
if (lines.length) {
|
||||||
|
lines[0] = lines[0].substring(startOffset);
|
||||||
|
lines[lines.length - 1] = lines[lines.length - 1]
|
||||||
|
.substring(0, endOffset);
|
||||||
|
}
|
||||||
|
return lines.join('\n');
|
||||||
|
},
|
||||||
|
|
||||||
|
_getDiffLines: function(side) {
|
||||||
|
if (this._linesCache[side]) {
|
||||||
|
return this._linesCache[side];
|
||||||
}
|
}
|
||||||
|
|
||||||
var text = '';
|
var lines = [];
|
||||||
for (var i = 0; i < contentEls.length; i++) {
|
var chunk;
|
||||||
text += contentEls[i].textContent + '\n';
|
var key = side === 'left' ? 'a' : 'b';
|
||||||
|
for (var chunkIndex = 0;
|
||||||
|
chunkIndex < this.diff.content.length;
|
||||||
|
chunkIndex++) {
|
||||||
|
chunk = this.diff.content[chunkIndex];
|
||||||
|
if (chunk.ab) {
|
||||||
|
lines = lines.concat(chunk.ab);
|
||||||
|
} else if (chunk[key]) {
|
||||||
|
lines = lines.concat(chunk[key]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return text;
|
|
||||||
|
this._linesCache[side] = lines;
|
||||||
|
return lines;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -29,31 +29,31 @@ limitations under the License.
|
|||||||
<gr-diff-selection>
|
<gr-diff-selection>
|
||||||
<table class="side-by-side">
|
<table class="side-by-side">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="lineNum left">1</td>
|
<td class="lineNum left" data-value="1">1</td>
|
||||||
<td class="content">
|
<td class="content">
|
||||||
<div class="contentText" data-side="left">ba ba</div>
|
<div class="contentText" data-side="left">ba ba</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="lineNum right">1</td>
|
<td class="lineNum right" data-value="1">1</td>
|
||||||
<td class="content">
|
<td class="content">
|
||||||
<div class="contentText" data-side="right">some other text</div>
|
<div class="contentText" data-side="right">some other text</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="lineNum left">2</td>
|
<td class="lineNum left" data-value="2">2</td>
|
||||||
<td class="content">
|
<td class="content">
|
||||||
<div class="contentText" data-side="left">zin</div>
|
<div class="contentText" data-side="left">zin</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="lineNum right">2</td>
|
<td class="lineNum right" data-value="2">2</td>
|
||||||
<td class="content">
|
<td class="content">
|
||||||
<div class="contentText" data-side="right">more more more</div>
|
<div class="contentText" data-side="right">more more more</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="lineNum left">2</td>
|
<td class="lineNum left" data-value="3">3</td>
|
||||||
<td class="content">
|
<td class="content">
|
||||||
<div class="contentText" data-side="left">ga ga</div>
|
<div class="contentText" data-side="left">ga ga</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="lineNum right">3</td>
|
<td class="lineNum right" data-value="3">3</td>
|
||||||
<td class="other">
|
<td class="other">
|
||||||
<div class="contentText" data-side="right">some other text</div>
|
<div class="contentText" data-side="right">some other text</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -85,6 +85,22 @@ limitations under the License.
|
|||||||
getLineElByChild: sinon.stub().returns({}),
|
getLineElByChild: sinon.stub().returns({}),
|
||||||
getSideByLineEl: sinon.stub(),
|
getSideByLineEl: sinon.stub(),
|
||||||
};
|
};
|
||||||
|
element.diff = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
a: ['ba ba'],
|
||||||
|
b: ['some other text'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
a: ['zin'],
|
||||||
|
b: ['more more more'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
a: ['ga ga'],
|
||||||
|
b: ['some other text'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
test('applies selected-left on left side click', function() {
|
test('applies selected-left on left side click', function() {
|
||||||
@@ -143,13 +159,17 @@ limitations under the License.
|
|||||||
test('copies content correctly', function() {
|
test('copies content correctly', function() {
|
||||||
element.classList.add('selected-left');
|
element.classList.add('selected-left');
|
||||||
element.classList.remove('selected-right');
|
element.classList.remove('selected-right');
|
||||||
|
// Fetch the line number.
|
||||||
|
element._cachedDiffBuilder.getLineElByChild = function(child) {
|
||||||
|
return child.parentElement.parentElement.previousElementSibling;
|
||||||
|
};
|
||||||
var selection = window.getSelection();
|
var selection = window.getSelection();
|
||||||
var range = document.createRange();
|
var range = document.createRange();
|
||||||
range.setStart(element.querySelector('div.contentText').firstChild, 3);
|
range.setStart(element.querySelector('div.contentText').firstChild, 3);
|
||||||
range.setEnd(
|
range.setEnd(
|
||||||
element.querySelectorAll('div.contentText')[4].firstChild, 2);
|
element.querySelectorAll('div.contentText')[4].firstChild, 2);
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
assert.equal('ba\nzin\nga\n', element._getSelectedText('left'));
|
assert.equal(element._getSelectedText('left'), 'ba\nzin\nga');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ limitations under the License.
|
|||||||
<style include="gr-theme-default"></style>
|
<style include="gr-theme-default"></style>
|
||||||
<div class$="[[_computeContainerClass(_loggedIn, viewMode)]]"
|
<div class$="[[_computeContainerClass(_loggedIn, viewMode)]]"
|
||||||
on-tap="_handleTap">
|
on-tap="_handleTap">
|
||||||
<gr-diff-selection>
|
<gr-diff-selection diff="[[_diff]]">
|
||||||
<gr-diff-highlight
|
<gr-diff-highlight
|
||||||
id="highlights"
|
id="highlights"
|
||||||
logged-in="[[_loggedIn]]"
|
logged-in="[[_loggedIn]]"
|
||||||
|
|||||||
Reference in New Issue
Block a user