Files
phabricator/webroot/rsrc/css/application/differential/changeset-view.css
epriestley 37f12a05ea Behold! Copy text from either side of a diff!
Summary:
Ref T12822. Ref T13161. By default, when users select text from a diff and copy it to the clipboard, they get both sides of the diff and all the line numbers. This is usually not what they intended to copy.

As of D20188, we use `content: attr(...)` to render line numbers. No browser copies this text, so that fixes line numbers.

We can use "user-select" CSS to visually prevent selection of line numbers and other stuff we don't want to copy. In Firefox and Chrome, "user-select" also applies to copied text, so getting "user-select" on the right nodes is largely good enough to do what we want.

In Safari, "user-select" is only visual, so we always need to crawl the DOM to figure out what text to pull out of it anyway.

In all browsers, we likely want to crawl the DOM anyway because this will let us show one piece of text and copy a different piece of text. We probably want to do this in the future to preserve "\t" tabs, and possibly to let us render certain character codes in one way but copy their original values. For example, we could render "\x07" as "␇".

Finally, we have to figure out which side of the diff we're copying from. The rule here is:

  - If you start the selection by clicking somewhere on the left or right side of the diff, that's what you're copying.
  - Otherwise, use normal document copy rules.

So the overall flow here is:

  - Listen for clicks.
  - When the user clicks the left or right side of the diff, store what they clicked.
  - When a selection starts, and something is actually selected, check if it was initiated by clicking a diff. If it was, apply a visual effect to get "user-select" where it needs to go and show the user what we think they're doing and what we're going to copy.
  - (Then, try to handle a bunch of degenerate cases where you start a selection and then click inside that selection.)
  - When a user clicks elsewhere or ends the selection with nothing selected, clear the selection mode.
  - When a user copies text, if we have an active selection mode, pull all the selected nodes out of the DOM and filter out the ones we don't want to copy, then stitch the text back together. Although I believe this didn't work well in ~2010, it appears to work well today.

Test Plan: This mostly seems to work in Safari, Chrome, and Firefox. T12822 has some errata. I haven't tested touch events but am satisfied if the touch event story is anything better than "permanently destroys data".

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13161, T12822

Differential Revision: https://secure.phabricator.com/D20191
2019-02-19 15:17:07 -08:00

457 lines
8.6 KiB
CSS

/**
* @provides differential-changeset-view-css
* @requires phui-inline-comment-view-css
*/
.differential-changeset {
position: relative;
margin: 0;
padding-top: 16px;
overflow-x: auto;
/* Fixes what seems to be a layout bug in Firefox which causes scrollbars,
to appear unpredictably, see discussion in T7690. */
overflow-y: hidden;
}
.device-phone .differential-changeset {
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
}
.differential-diff {
background: {$diff.background};
width: 100%;
border-top: 1px solid {$lightblueborder};
border-bottom: 1px solid {$lightblueborder};
table-layout: fixed;
}
.differential-diff.diff-2up {
min-width: 780px;
}
.differential-diff col.num {
width: 45px;
}
.device .differential-diff.diff-1up col.num {
width: 32px;
}
.differential-diff.diff-2up col.left,
.differential-diff.diff-2up col.right {
width: 49.25%;
}
.differential-diff.diff-1up col.unified {
width: 99.5%;
}
.differential-diff col.copy {
width: 0.5%;
}
.differential-diff col.cov {
width: 1%;
}
.differential-diff td {
vertical-align: top;
white-space: pre-wrap;
word-wrap: break-word;
padding: 1px 8px;
}
.device .differential-diff td {
padding: 1px 4px;
}
.prose-diff {
padding: 12px 0;
white-space: pre-wrap;
color: {$greytext};
}
.prose-diff-frame {
padding: 12px;
}
.prose-diff span.old,
.prose-diff span.new {
padding: 0 2px;
}
.prose-diff span.old,
.prose-diff span.new {
color: {$darkgreytext};
}
.differential-changeset-immutable .differential-diff th {
cursor: auto;
}
.differential-diff td.old {
background: {$old-background};
}
.differential-diff td.new {
background: {$new-background};
}
.differential-diff td.old-rebase {
background: #ffeeee;
}
.differential-diff td.new-rebase {
background: #eeffee;
}
.differential-diff td.old span.bright,
.differential-diff td.old-full,
.prose-diff span.old {
background: {$old-bright};
}
.differential-diff td.new span.bright,
.differential-diff td.new-full,
.prose-diff span.new {
background: {$new-bright};
}
.differential-diff td span.depth-out,
.differential-diff td span.depth-in {
padding: 2px 0;
background-size: 12px 12px;
background-repeat: no-repeat;
background-position: left center;
}
.differential-diff td span.depth-out {
background-image: url(/rsrc/image/chevron-out.png);
background-color: {$old-background-strong};
}
.differential-diff td span.depth-in {
background-position: 2px center;
background-image: url(/rsrc/image/chevron-in.png);
background-color: {$new-background-strong};
}
.differential-diff td.copy {
min-width: 0.5%;
width: 0.5%;
padding: 0;
}
.differential-diff td.new-copy,
.differential-diff td.new-copy span.bright {
background: {$copy-background};
}
.differential-diff td.new-move,
.differential-diff td.new-move span.bright {
background: {$move-background};
}
.differential-diff td.comment {
background: #dddddd;
}
.differential-diff .inline > td {
padding: 0;
}
/* Specify line number behaviors after other behaviors because line numbers
should always have a boring grey background. */
.differential-diff td.n {
text-align: right;
padding: 1px 6px 1px 0;
vertical-align: top;
background: {$lightbluebackground};
color: {$bluetext};
cursor: pointer;
border-right: 1px solid {$thinblueborder};
overflow: hidden;
}
.differential-diff td.n::before {
content: attr(data-n);
}
.differential-diff td.cov {
padding: 0;
}
td.cov-U {
background: #dd8866;
}
td.cov-C {
background: #66bbff;
}
td.cov-N {
background: #ddeeff;
}
td.cov-X {
background: #aa00aa;
}
td.cov-I {
background: {$lightgreybackground};
}
.differential-diff td.source-cov-C,
.differential-diff td.source-cov-C span.bright {
background: #cceeff;
}
.differential-diff td.source-cov-U,
.differential-diff td.source-cov-U span.bright {
background: #ffbb99;
}
.differential-diff td.source-cov-N,
.differential-diff td.source-cov-N span.bright {
background: #f3f6ff;
}
.differential-diff td.show-more,
.differential-diff th.show-context-line,
.differential-diff td.show-context,
.differential-diff td.differential-shield {
background: {$lightbluebackground};
padding: 12px 0;
border-top: 1px solid {$thinblueborder};
border-bottom: 1px solid {$thinblueborder};
}
.device .differential-diff td.show-more,
.device .differential-diff th.show-context-line,
.device .differential-diff td.show-context,
.device .differential-diff td.differential-shield {
padding: 6px 0;
}
.differential-diff td.show-more,
.differential-diff td.differential-shield {
font: {$basefont};
font-size: {$smallerfontsize};
white-space: normal;
}
.differential-diff td.show-more {
text-align: center;
color: {$bluetext};
}
.differential-diff th.show-context-line {
padding-right: 6px;
}
.differential-diff td.show-context {
padding-left: 14px;
}
.differential-diff td.differential-shield {
text-align: center;
}
.differential-diff td.differential-shield a {
font-weight: bold;
}
.differential-diff .differential-image-diff {
background-image: url(/rsrc/image/checker_light.png);
}
.differential-diff .differential-image-diff:hover {
background-image: url(/rsrc/image/checker_dark.png);
}
.differential-diff .differential-image-diff td {
padding: 8px;
}
.differential-image-stage {
overflow: auto;
}
.differential-meta-notice {
border-top: 1px solid {$gentle.highlight.border};
border-bottom: 1px solid {$gentle.highlight.border};
background-color: {$gentle.highlight};
padding: 12px;
}
.differential-meta-notice + .differential-diff {
border-top: none;
}
.differential-changeset h1 {
font-size: {$biggestfontsize};
padding: 2px 0 20px 12px;
line-height: 20px;
color: {$blacktext};
}
.device-phone .differential-changeset h1 {
word-break: break-word;
margin-right: 8px;
}
.differential-reticle {
background-color: {$sh-yellowbackground};
border: 1px solid {$sh-yellowborder};
position: absolute;
opacity: 0.5;
top: 0;
left: 0;
box-sizing: border-box;
pointer-events: none;
}
.differential-loading {
border-top: 1px solid {$gentle.highlight.border};
border-bottom: 1px solid {$gentle.highlight.border};
background-color: {$gentle.highlight};
padding: 12px;
text-align: center;
}
.differential-collapse-undo {
color: {$darkbluetext};
padding: 12px;
border: 1px solid {$blue};
text-align: center;
background-color: {$lightblue};
margin: 8px;
}
.differential-collapse-undo a {
font-weight: bold;
}
.differential-file-icon-header .phui-icon-view {
display: inline-block;
margin: 0 6px 2px 0;
vertical-align: middle;
font-size: 14px;
}
.device-phone .differential-file-icon-header .phui-icon-view {
display: none;
}
.differential-changeset-buttons {
float: right;
margin-right: 12px;
}
.device-phone .differential-changeset-buttons .button .phui-button-text {
visibility: hidden;
width: 0;
margin-left: 8px;
}
.differential-property-table {
margin: 12px;
background: {$lightgreybackground};
border: 1px solid {$lightblueborder};
border-bottom: 1px solid {$blueborder};
}
.differential-property-table td em {
color: {$lightgreytext};
}
.differential-property-table td.oval {
background: #ffd0d0;
width: 50%;
}
.differential-property-table td.nval {
background: #d0ffd0;
width: 50%;
}
tr.differential-inline-hidden {
display: none;
}
tr.differential-inline-loading {
opacity: 0.5;
}
.differential-review-stage {
position: relative;
}
.diff-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
background: {$page.content};
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
border-bottom: 1px solid {$lightgreyborder};
padding: 8px 18px;
vertical-align: middle;
font-weight: bold;
font-size: {$biggerfontsize};
line-height: 28px;
}
.diff-banner-path {
color: {$greytext};
}
.diff-banner-buttons .button {
margin-left: 8px;
}
.diff-banner-has-unsaved,
.diff-banner-has-unsubmitted,
.diff-banner-has-draft-done {
background: {$gentle.highlight};
}
.diff-banner-buttons {
float: right;
}
/* In Firefox, making the table unselectable and then making cells selectable
does not work: the cells remain unselectable. Narrowly mark the cells as
unselectable. */
.differential-diff.copy-l > tbody > tr > td,
.differential-diff.copy-r > tbody > tr > td {
-moz-user-select: -moz-none;
-khtml-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
}
.differential-diff.copy-l > tbody > tr > td,
.differential-diff.copy-r > tbody > tr > td {
opacity: 0.5;
}
.differential-diff.copy-l > tbody > tr > td:nth-child(2) {
-webkit-user-select: auto;
user-select: auto;
opacity: 1;
}
.differential-diff.copy-r > tbody > tr > td:nth-child(5) {
-webkit-user-select: auto;
user-select: auto;
opacity: 1;
}